From 54c87ba4ac6c0d1de14e8855e10edf6197e9ce57 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 1 May 2024 14:51:09 +0000 Subject: [PATCH 0001/1097] Increment version to 8.48.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 1dffbcec331..e7621c210f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.47.0", + "version": "8.48.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6e5022bcece..acac0a99da7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.47.0", + "version": "8.48.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f4d0ef011491811e5bb426ffc2e92038ea00b233 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 09:44:08 -0600 Subject: [PATCH 0002/1097] Bump ejs from 3.1.9 to 3.1.10 (#11432) Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/mde/ejs/releases) - [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7621c210f1..261cbdb1fb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.43.0-pre", + "version": "8.48.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -10979,9 +10979,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { "jake": "^10.8.5" @@ -37620,9 +37620,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "requires": { "jake": "^10.8.5" From 3350dee8ee32e07941d7871caf66b75bd66dc93b Mon Sep 17 00:00:00 2001 From: ecdrsvc <82906140+ecdrsvc@users.noreply.github.com> Date: Wed, 1 May 2024 17:16:33 -0400 Subject: [PATCH 0003/1097] Add lmpIdSystem userId submodule (#11431) Co-authored-by: Antoine Niek --- modules/.submodules.json | 1 + modules/lmpIdSystem.js | 61 +++++++++++++ modules/lmpIdSystem.md | 27 ++++++ modules/userId/userId.md | 3 + test/spec/modules/lmpIdSystem_spec.js | 124 ++++++++++++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 modules/lmpIdSystem.js create mode 100644 modules/lmpIdSystem.md create mode 100644 test/spec/modules/lmpIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 8409ea9918d..9dfeaf910f8 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -26,6 +26,7 @@ "justIdSystem", "kinessoIdSystem", "liveIntentIdSystem", + "lmpIdSystem", "lockrAIMIdSystem", "lotamePanoramaIdSystem", "merkleIdSystem", diff --git a/modules/lmpIdSystem.js b/modules/lmpIdSystem.js new file mode 100644 index 00000000000..b6dcae3118b --- /dev/null +++ b/modules/lmpIdSystem.js @@ -0,0 +1,61 @@ +/** + * This module adds lmpId support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/lmpIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'lmpid'; +const STORAGE_KEY = '__lmpid'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +function readFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(STORAGE_KEY) : null; +} + +function getLmpid() { + return window[STORAGE_KEY] || readFromLocalStorage(); +} + +/** @type {Submodule} */ +export const lmpIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @return { {lmpid: string} | undefined } + */ + decode(value) { + return value ? { lmpid: value } : undefined; + }, + + /** + * Retrieve the LMPID + * @function + * @param {SubmoduleConfig} config + * @return {{id: string | undefined} | undefined} + */ + getId(config) { + const id = getLmpid(); + return id ? { id } : undefined; + }, + + eids: { + 'lmpid': { + source: 'loblawmedia.ca', + atype: 3 + }, + } +}; + +submodule('userId', lmpIdSubmodule); diff --git a/modules/lmpIdSystem.md b/modules/lmpIdSystem.md new file mode 100644 index 00000000000..a56c9dbb3d6 --- /dev/null +++ b/modules/lmpIdSystem.md @@ -0,0 +1,27 @@ +# LMPID + +The Loblaw Media Private ID (LMPID) is the Loblaw Advance identity solution deployed by its media partners. LMPID leverages encrypted user registration information to provide a privacy-conscious, secure, and reliable identifier to power Loblaw Advance's digital advertising ecosystem. + +## LMPID Registration + +If you're a media company looking to partner with Loblaw Advance, please reach out to us through our [Contact page](https://www.loblawadvance.ca/contact-us) + +## LMPID Configuration + +First, make sure to add the LMPID submodule to your Prebid.js package with: + +``` +gulp build --modules=lmpIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'lmpid' + }] + } +}); +``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 7a01e128814..1ec109ff309 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -358,6 +358,9 @@ pbjs.setConfig({ }, { name: 'naveggId', + }, + { + name: 'lmpid', }], syncDelay: 5000 } diff --git a/test/spec/modules/lmpIdSystem_spec.js b/test/spec/modules/lmpIdSystem_spec.js new file mode 100644 index 00000000000..37c7351f143 --- /dev/null +++ b/test/spec/modules/lmpIdSystem_spec.js @@ -0,0 +1,124 @@ +import { expect } from 'chai'; +import { find } from 'src/polyfill.js'; +import { config } from 'src/config.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js'; +import { mockGdprConsent } from '../../helpers/consentData.js'; + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'lmpid' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: { banner: {}, native: {} }, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +describe('LMPID System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let windowLmpidStub; + + beforeEach(() => { + window.__lmpid = undefined; + windowLmpidStub = sinon.stub(window, '__lmpid'); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + windowLmpidStub.restore(); + }); + + describe('LMPID: test "getId" method', () => { + it('prefers the window cached LMPID', () => { + localStorageIsEnabledStub.returns(true); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + + windowLmpidStub.value('lmpid'); + expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'lmpid' }); + }); + + it('fallbacks on localStorage when window cache is falsy', () => { + localStorageIsEnabledStub.returns(true); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + + windowLmpidStub.value(''); + expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'stored-lmpid' }); + + windowLmpidStub.value(false); + expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'stored-lmpid' }); + }); + + it('fallbacks only if localStorageIsEnabled', () => { + localStorageIsEnabledStub.returns(false); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + + expect(lmpIdSubmodule.getId()).to.be.undefined; + }); + }); + + describe('LMPID: test "decode" method', () => { + it('provides the lmpid from a stored object', () => { + expect(lmpIdSubmodule.decode('lmpid')).to.deep.equal({ lmpid: 'lmpid' }); + }); + }); + + describe('LMPID: requestBids hook', () => { + let adUnits; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockGdprConsent(sandbox); + adUnits = [getAdUnitMock()]; + init(config); + setSubmoduleRegistry([lmpIdSubmodule]); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + localStorageIsEnabledStub.returns(true); + config.setConfig(getConfigMock()); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('when a stored LMPID exists it is added to bids', (done) => { + requestBidsHook(() => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lmpid'); + expect(bid.userId.lmpid).to.equal('stored-lmpid'); + const lmpidAsEid = find(bid.userIdAsEids, e => e.source == 'loblawmedia.ca'); + expect(lmpidAsEid).to.deep.equal({ + source: 'loblawmedia.ca', + uids: [{ + id: 'stored-lmpid', + atype: 3, + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); +}); From 8c72bc2239e3f9adbb14b40ec211b2be8eb8a0fb Mon Sep 17 00:00:00 2001 From: Giuseppe Cera <117671343+giuseppe-exads@users.noreply.github.com> Date: Thu, 2 May 2024 14:31:04 +0100 Subject: [PATCH 0004/1097] EXADS Bid Adapter: initial release (#11284) * First commit * fix: readme.md * fix: changed exads urls * fix: Tools and suggestions related to the doc * fix: from code review * fix: from code review * fix: from code review * fix: error from code review - native example * fox: from code review * fix: from code review * fix: from code review * fix: native img set as mandatory * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: bidfloor and bidfloorcur set as optional * fix: dsa * fix: mananing multiple responses * fix: unit test after code review * fix: fixing native snippet code * fix: from code review * fix: video events after code review * fix: video module into documentation * fix: impression tracker for native * fix: afeter code review * fix: unit tests * fix: added badv and bcat * fix: video -> mimes and protocols * fix * fix: removed image_output and video_output params, forcing always html for rtb banner * fix: gulp * fix: added site.name * fix: removed EXADS dir * fix: after linting * fix: unit tests * fix: final dsa solution * fix: dsa * fix: fix instream example * fix: doc media type context * fix: documented the endpoint param into native section * fix: related to markdown lint validation (#2) * fix: from CR (#3) --------- Co-authored-by: tfoliveira --- modules/exadsBidAdapter.js | 514 ++++++++++++++++++ modules/exadsBidAdapter.md | 484 +++++++++++++++++ test/spec/modules/exadsBidAdapter_spec.js | 632 ++++++++++++++++++++++ 3 files changed, 1630 insertions(+) create mode 100644 modules/exadsBidAdapter.js create mode 100644 modules/exadsBidAdapter.md create mode 100644 test/spec/modules/exadsBidAdapter_spec.js diff --git a/modules/exadsBidAdapter.js b/modules/exadsBidAdapter.js new file mode 100644 index 00000000000..31d75db470d --- /dev/null +++ b/modules/exadsBidAdapter.js @@ -0,0 +1,514 @@ +import * as utils from '../src/utils.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER = 'exadsadserver'; + +const PARTNERS = { + ORTB_2_4: 'ortb_2_4' +}; + +const GVL_ID = 1084; + +const htmlImageOutput = 'html'; +const htmlVideoOutput = 'html'; + +const adPartnerHandlers = { + [PARTNERS.ORTB_2_4]: { + request: handleReqORTB2Dot4, + response: handleResORTB2Dot4, + validation: handleValidORTB2Dot4 + } +}; + +function handleReqORTB2Dot4(validBidRequest, endpointUrl, bidderRequest) { + utils.logInfo(`Calling endpoint for ortb_2_4:`, endpointUrl); + const gdprConsent = getGdprConsentChoice(bidderRequest); + const envParams = getEnvParams(); + + // Make a dynamic bid request to the ad partner's endpoint + let bidRequestData = { + 'id': validBidRequest.bidId, // NOT bid.bidderRequestId or bid.auctionId + 'at': 1, + 'imp': [], + 'bcat': validBidRequest.params.bcat, + 'badv': validBidRequest.params.badv, + 'site': { + 'id': validBidRequest.params.siteId, + 'name': validBidRequest.params.siteName, + 'domain': envParams.domain, + 'page': envParams.page, + 'keywords': validBidRequest.params.keywords + }, + 'device': { + 'ua': envParams.userAgent, + 'ip': validBidRequest.params.userIp, + 'geo': { + 'country': validBidRequest.params.country + }, + 'language': envParams.lang, + 'os': envParams.osName, + 'js': 0, + 'ext': { + 'accept_language': envParams.language + } + }, + 'user': { + 'id': validBidRequest.params.userId, + }, + 'ext': { + 'sub': 0, + 'prebid': { + 'channel': { + 'name': 'pbjs', + 'version': '$prebid.version$' + } + } + } + }; + + if (gdprConsent && gdprConsent.gdprApplies) { + bidRequestData.user['ext'] = { + consent: gdprConsent.consentString + } + } + + if (validBidRequest.params.dsa && ( + hasValue(validBidRequest.params.dsa.dsarequired) || + hasValue(validBidRequest.params.dsa.pubrender) || + hasValue(validBidRequest.params.dsa.datatopub))) { + bidRequestData.regs = { + 'ext': { + 'dsa': { + 'dsarequired': validBidRequest.params.dsa.dsarequired, + 'pubrender': validBidRequest.params.dsa.pubrender, + 'datatopub': validBidRequest.params.dsa.datatopub + } + } + } + } + + const impData = imps.get(validBidRequest.params.impressionId); + + // Banner setup + const bannerMediaType = utils.deepAccess(validBidRequest, 'mediaTypes.banner'); + if (bannerMediaType != null) { + impData.mediaType = BANNER; + bidRequestData.imp = bannerMediaType.sizes.map(size => { + return ({ + 'id': validBidRequest.params.impressionId, + 'bidfloor': validBidRequest.params.bidfloor, + 'bidfloorcur': validBidRequest.params.bidfloorcur, + 'banner': { + 'w': size[0], + 'h': size[1], + 'mimes': validBidRequest.params.mimes ? validBidRequest.params.mimes : undefined, + 'ext': { + image_output: htmlImageOutput, + video_output: htmlVideoOutput, + } + }, + }); + }); + } + + const nativeMediaType = utils.deepAccess(validBidRequest, 'mediaTypes.native'); + + if (nativeMediaType != null) { + impData.mediaType = NATIVE; + const nativeVersion = '1.2'; + + const native = { + 'native': { + 'ver': nativeVersion, + 'plcmttype': 4, + 'plcmtcnt': validBidRequest.params.native.plcmtcnt + } + }; + + native.native.assets = bidRequestData.imp = nativeMediaType.ortb.assets.map(asset => { + const newAsset = asset; + if (newAsset.img != null) { + newAsset.img.wmin = newAsset.img.h; + newAsset.img.hmin = newAsset.img.w; + } + return newAsset; + }); + + const imp = [{ + 'id': validBidRequest.params.impressionId, + 'bidfloor': validBidRequest.params.bidfloor, + 'bidfloorcur': validBidRequest.params.bidfloorcur, + 'native': { + 'request': JSON.stringify(native), + 'ver': nativeVersion + }, + }]; + + bidRequestData.imp = imp; + }; + + const videoMediaType = utils.deepAccess(validBidRequest, 'mediaTypes.video'); + + if (videoMediaType != null) { + impData.mediaType = VIDEO; + const imp = [{ + 'id': validBidRequest.params.impressionId, + 'bidfloor': validBidRequest.params.bidfloor, + 'bidfloorcur': validBidRequest.params.bidfloorcur, + 'video': { + 'mimes': videoMediaType.mimes, + 'protocols': videoMediaType.protocols, + }, + 'ext': validBidRequest.params.imp.ext + }]; + + bidRequestData.imp = imp; + } + + utils.logInfo('PAYLOAD', bidRequestData, JSON.stringify(bidRequestData)); + utils.logInfo('FINAL URL', endpointUrl); + + return makeBidRequest(endpointUrl, bidRequestData); +}; + +function handleResORTB2Dot4(serverResponse, request, adPartner) { + utils.logInfo('on handleResORTB2Dot4 -> request:', request); + utils.logInfo('on handleResORTB2Dot4 -> request json data:', JSON.parse(request.data)); + utils.logInfo('on handleResORTB2Dot4 -> serverResponse:', serverResponse); + + let bidResponses = []; + const bidRq = JSON.parse(request.data); + + if (serverResponse.hasOwnProperty('body') && serverResponse.body.hasOwnProperty('id')) { + utils.logInfo('Ad server response', serverResponse.body.id); + + const requestId = serverResponse.body.id; + const currency = serverResponse.body.cur; + + serverResponse.body.seatbid.forEach((seatbid, seatIndex) => { + seatbid.bid.forEach((bidData, bidIndex) => { + utils.logInfo('serverResponse.body.seatbid[' + seatIndex + '].bid[' + bidIndex + ']', bidData); + + const bidResponseAd = bidData.adm; + const bannerInfo = utils.deepAccess(bidRq.imp[0], 'banner'); + const nativeInfo = utils.deepAccess(bidRq.imp[0], 'native'); + const videoInfo = utils.deepAccess(bidRq.imp[0], 'video'); + + let w; let h = 0; + let mediaType = ''; + const native = {}; + + if (bannerInfo != null) { + w = bidRq.imp[0].banner.w; + h = bidRq.imp[0].banner.h; + mediaType = BANNER; + } else if (nativeInfo != null) { + const responseADM = JSON.parse(bidResponseAd); + responseADM.native.assets.forEach(asset => { + if (asset.img != null) { + const imgAsset = JSON.parse(bidRq.imp[0].native.request) + .native.assets.filter(asset => asset.img != null).map(asset => asset.img); + w = imgAsset[0].w; + h = imgAsset[0].h; + native.image = { + url: asset.img.url, + height: h, + width: w + } + } else if (asset.title != null) { + native.title = asset.title.text; + } else if (asset.data != null) { + native.body = asset.data.value; + } else { + utils.logWarn('bidResponse->', 'wrong asset type or null'); + } + }); + + if (responseADM.native) { + if (responseADM.native.link) { + native.clickUrl = responseADM.native.link.url; + } + if (responseADM.native.eventtrackers) { + native.impressionTrackers = []; + + responseADM.native.eventtrackers.forEach(tracker => { + if (tracker.method == 1) { + native.impressionTrackers.push(tracker.url); + } + }); + } + } + mediaType = NATIVE; + } else if (videoInfo != null) { + mediaType = VIDEO; + } + + const metaData = {}; + + if (hasValue(bidData.ext.dsa)) { + metaData.dsa = bidData.ext.dsa; + } + + const bidResponse = { + requestId: requestId, + currency: currency, + ad: bidData.adm, + cpm: bidData.price, + creativeId: bidData.crid, + cid: bidData.cid, + width: w, + ttl: 360, + height: h, + netRevenue: true, + mediaType: mediaType, + meta: metaData, + nurl: bidData.nurl.replace(/^http:\/\//i, 'https://') + }; + + if (mediaType == 'native') { + bidResponse.native = native; + } + + if (mediaType == 'video') { + bidResponse.vastXml = bidData.adm; + bidResponse.width = bidData.w; + bidResponse.height = bidData.h; + } + + utils.logInfo('bidResponse->', bidResponse); + + bidResponses.push(bidResponse); + }); + }); + } else { + imps.delete(bidRq.imp[0].id); + utils.logInfo('NO Ad server response ->', serverResponse.body.id); + } + + utils.logInfo('interpretResponse -> bidResponses:', bidResponses); + + return bidResponses; +} + +function makeBidRequest(url, data) { + const payloadString = JSON.stringify(data); + + return { + method: 'POST', + url: url, + data: payloadString + } +} + +function getUrl(adPartner, bid) { + let endpointUrlMapping = { + [PARTNERS.ORTB_2_4]: bid.params.endpoint + '?idzone=' + bid.params.zoneId + '&fid=' + bid.params.fid + }; + + return endpointUrlMapping[adPartner] ? endpointUrlMapping[adPartner] : 'defaultEndpoint'; +} + +function getEnvParams() { + const envParams = { + lang: '', + userAgent: '', + osName: '', + page: '', + domain: '', + language: '' + }; + + envParams.domain = window.location.hostname; + envParams.page = window.location.protocol + '//' + window.location.host + window.location.pathname; + envParams.lang = navigator.language.indexOf('-') > -1 + ? navigator.language.split('-')[0] + : navigator.language; + envParams.userAgent = navigator.userAgent; + + if (envParams.userAgent.match(/Windows/i)) { + envParams.osName = 'Windows'; + } else if (envParams.userAgent.match(/Mac OS|Macintosh/i)) { + envParams.osName = 'MacOS'; + } else if (envParams.userAgent.match(/Unix/i)) { + envParams.osName = 'Unix'; + } else if (envParams.userAgent.userAgent.match(/Android/i)) { + envParams.osName = 'Android'; + } else if (envParams.userAgent.userAgent.match(/iPhone|iPad|iPod/i)) { + envParams.osName = 'iOS'; + } else if (envParams.userAgent.userAgent.match(/Linux/i)) { + envParams.osName = 'Linux'; + } else { + envParams.osName = 'Unknown'; + } + + let browserLanguage = navigator.language || navigator.userLanguage; + let acceptLanguage = browserLanguage.replace('_', '-'); + + envParams.language = acceptLanguage; + + utils.logInfo('Domain -> ', envParams.domain); + utils.logInfo('Page -> ', envParams.page); + utils.logInfo('Lang -> ', envParams.lang); + utils.logInfo('OS -> ', envParams.osName); + utils.logInfo('User Agent -> ', envParams.userAgent); + utils.logInfo('Primary Language -> ', envParams.language); + + return envParams; +} + +export const imps = new Map(); + +/* + Common mandatory parameters: + - endpoint + - userIp + - userIp - the minimum constraint is having the propery, empty or not + - zoneId + - partner + - fid + - siteId + - impressionId + - country + - mediaTypes?.banner or mediaTypes?.native or mediaTypes?.video + + for native parameters + - assets - it should contain the img property + + for video parameters + - mimes - it has to contain one mime type at least + - procols - it should contain one protocol at least + +*/ +function handleValidORTB2Dot4(bid) { + const bannerInfo = bid.mediaTypes?.banner; + const nativeInfo = bid.mediaTypes?.native; + const videoInfo = bid.mediaTypes?.video; + const isValid = ( + hasValue(bid.params.endpoint) && + hasValue(bid.params.userIp) && + bid.params.hasOwnProperty('userIp') && + hasValue(bid.params.zoneId) && + hasValue(bid.params.partner) && + hasValue(bid.params.fid) && + hasValue(bid.params.siteId) && + hasValue(bid.params.impressionId) && + hasValue(bid.params.country) && + hasValue(bid.params.country.length > 0) && + ((!hasValue(bid.params.bcat) || + bid.params.bcat.length > 0)) && + ((!hasValue(bid.params.badv) || + bid.params.badv.length > 0)) && + (bannerInfo || nativeInfo || videoInfo) && + (nativeInfo ? bid.params.native && + nativeInfo.ortb.assets && + nativeInfo.ortb.assets.some(asset => !!asset.img) : true) && + (videoInfo ? (videoInfo.mimes && + videoInfo.mimes.length > 0 && + videoInfo.protocols && + videoInfo.protocols.length > 0) : true)); + if (!isValid) { + utils.logError('Validation Error'); + } + + return isValid; +} + +function hasValue(value) { + return ( + value !== undefined && + value !== null + ); +} + +function getGdprConsentChoice(bidderRequest) { + const hasGdprConsent = + hasValue(bidderRequest) && + hasValue(bidderRequest.gdprConsent); + + if (hasGdprConsent) { + return bidderRequest.gdprConsent; + } + + return null; +} + +export const spec = { + aliases: ['exads'], // short code + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + isBidRequestValid: function (bid) { + utils.logInfo('on isBidRequestValid -> bid:', bid); + + if (!bid.params.partner) { + utils.logError('Validation Error', 'bid.params.partner missed'); + return false; + } else if (!Object.values(PARTNERS).includes(bid.params.partner)) { + utils.logError('Validation Error', 'bid.params.partner is not valid'); + return false; + } + + let adPartner = bid.params.partner; + + if (adPartnerHandlers[adPartner] && adPartnerHandlers[adPartner]['validation']) { + return adPartnerHandlers[adPartner]['validation'](bid); + } else { + // Handle unknown or unsupported ad partners + return false; + } + }, + buildRequests: function (validBidRequests, bidderRequest) { + utils.logInfo('on buildRequests -> validBidRequests:', validBidRequests); + utils.logInfo('on buildRequests -> bidderRequest:', bidderRequest); + + return validBidRequests.map(bid => { + let adPartner = bid.params.partner; + + imps.set(bid.params.impressionId, { adPartner: adPartner, mediaType: null }); + + let endpointUrl = getUrl(adPartner, bid); + + // Call the handler for the ad partner, passing relevant parameters + if (adPartnerHandlers[adPartner]['request']) { + return adPartnerHandlers[adPartner]['request'](bid, endpointUrl, bidderRequest); + } else { + // Handle unknown or unsupported ad partners + return null; + } + }); + }, + interpretResponse: function (serverResponse, request) { + const bid = JSON.parse(request.data); + const impData = imps.get(bid.imp[0].id); + const adPartner = impData.adPartner; + + // Call the handler for the ad partner, passing relevant parameters + if (adPartnerHandlers[adPartner]['response']) { + return adPartnerHandlers[adPartner]['response'](serverResponse, request, adPartner); + } else { + // Handle unknown or unsupported ad partners + return null; + } + }, + onTimeout: function (timeoutData) { + utils.logWarn(`onTimeout -> timeoutData:`, timeoutData); + }, + onBidWon: function (bid) { + utils.logInfo(`onBidWon -> bid:`, bid); + if (bid.nurl) { + utils.triggerPixel(bid.nurl); + } + }, + onSetTargeting: function (bid) { + utils.logInfo(`onSetTargeting -> bid:`, bid); + }, + onBidderError: function (bid) { + imps.delete(bid.bidderRequest.bids[0].params.impressionId); + utils.logInfo('onBidderError -> bid:', bid); + }, +}; + +registerBidder({ + code: BIDDER, + gvlid: GVL_ID, + ...spec +}); diff --git a/modules/exadsBidAdapter.md b/modules/exadsBidAdapter.md new file mode 100644 index 00000000000..06b873d8da8 --- /dev/null +++ b/modules/exadsBidAdapter.md @@ -0,0 +1,484 @@ +# Overview + +**Module Name**: Exads Bidder Adapter + +**Module Type**: Bidder Adapter + +**Maintainer**: + +## Description + +Module that connects to EXADS' bidder for bids. + +## Build + +If you don't need to use the prebidJS video module, please remove the videojsVideoProvider module. + +```bash +gulp build --modules=consentManagement,exadsBidAdapter,videojsVideoProvider +``` + +### Configuration + +Use `setConfig` to instruct Prebid.js to initilize the exadsBidAdapter, as specified below. + +* Set "debug" as true if you need to read logs; +* Set "gdprApplies" as true if you need to pass gdpr consent string; +* The tcString is the iabtcf consent string for gdpr; +* Uncomment the cache instruction if you need to configure a cache server (e.g. for instream video) + +```js +pbjs.setConfig({ + debug: false, + //cache: { url: "https://prebid.adnxs.com/pbc/v1/cache" }, + consentManagement: { + gdpr: { + cmpApi: 'static', + timeout: 1000000, + defaultGdprScope: true, + consentData: { + getTCData: { + tcString: consentString, + gdprApplies: false // set to true to pass the gdpr consent string + } + } + } + } +}); +``` + +Add the `video` config if you need to render videos using the video module. +For more info navigate to . + +```js +pbjs.setConfig({ + video: { + providers: [{ + divId: 'player', // the id related to the videojs tag in your body + vendorCode: 2, // videojs, + playerConfig: { + params: { + adPluginConfig: { + numRedirects: 10 + }, + vendorConfig: { + controls: true, + autoplay: true, + preload: "auto", + } + } + } + },] + }, +}); +``` + +### Test Parameters + +Now you will find the different parameters to set, based on publisher website. They are optional unless otherwise specified. + +#### RTB Banner 2.4 + +* **zoneId** (required) - you can get it from the endpoint created after configuring the zones (integer) +* **fid** (required) - you can get it from the endpoint created after configuring the zones (string) +* **partner** (required) - currently we support rtb 2.4 ("ortb_2_4") only (string) +* **siteId** (recommended) - unique Site ID (string) +* **siteName** site name (string) +* **banner.sizes** (required) - one integer array - [width, height] +* **userIp** (required) - IP address of the user, ipv4 or ipv6 (string) +* **userId** (*required) - unique user ID (string).*If you cannot generate a user ID, you can leave it empty (""). The request will get a response as long as "user" object is included in the request +* **country** - country ISO3 +* **impressionId** (required) - unique impression ID within this bid request (string) +* **keywords** - keywords can be used to ensure ad zones get the right type of advertising. Keywords should be a string of comma-separated words +* **bidfloor** - minimum bid for this impression (CPM) / click (CPC) and account currency (float) +* **bidfloorcur** - currency for minimum bid value specified using ISO-4217 alpha codes (string) +* **bcat** - blocked advertiser categories using the IAB content categories (string array) +* **badv** - block list of advertisers by their domains (string array) +* **mimes** - list of supported mime types. We support: image/jpeg, image/jpg, image/png, image/png, image/gif, image/webp, video/mp4 (string array) +* **dsa** - DSA transparency information + * **dsarequired** - flag to indicate if DSA information should be made available (integer) + *0 - Not required + * 1 - Supported, bid responses with or without DSA object will be accepted + *2 - Required, bid responses without DSA object will not be accepted + * 3 - Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * **pubrender** - flag to indicate if the publisher will render the DSA Transparency info (integer) + * 0 - Publisher can't render + * 1 - Publisher could render depending on adrender + * 2 - Publisher will render + * **datatopub** - independent of pubrender, the publisher may need the transparency data for audit purposes (integer) + * 0 - do not send transparency data + * 1 - optional to send transparency data + * 2 - send transparency data +* **endpoint** (required) - EXADS endpoint (URL) + +##### RTB Banner 2.4 (Image) + +```js + +adUnits = + [{ code: 'postbid_iframe', // the frame where to render the creative + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + country: 'IRL', + impressionId: impression_id.toString(), + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + mimes: ['image/jpg'], + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] + }]; +``` + +##### RTB Banner 2.4 (Video) + +```js +adUnits = + [{ code: 'postbid_iframe', // the frame where to render the creative + mediaTypes: { + banner: { + sizes: [900, 250] + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + country: 'IRL', + impressionId: '1234', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + mimes: ['image/jpg'], + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] + }]; +``` + +#### RTB 2.4 Video (Instream/OutStream/Video Slider) - VAST XML or VAST TAG (url) + +* **zoneId** (required) - you can get it from the endpoint created after configuring the zones (integer) +* **fid** (required) - you can get it from the endpoint created after configuring the zones (string) +* **partner** (required) - currently we support rtb 2.4 ("ortb_2_4") only (string) +* **siteId** (recommended) - unique Site ID (string) +* **siteName** site name (string) +* **userIp** (required) - IP address of the user, ipv4 or ipv6 (string) +* **userId** (required) - unique user ID (string). *If you cannot generate a user ID, you can leave it empty (""). The request will get a response as long as "user" object is included in the request +* **country** - Country ISO3 (string) +* **impressionId** (required) - unique impression ID within this bid request (string) +* **keywords** - keywords can be used to ensure ad zones get the right type of advertising. Keywords should be a string of comma-separated words +* **bidfloor** - minimum bid for this impression (CPM) / click (CPC) and account currency (float) +* **bidfloorcur** - currency for minimum bid value specified using ISO-4217 alpha codes (string) +* **bcat** - blocked advertiser categories using the IAB content categories (string array) +* **badv** - block list of advertisers by their domains (string array) +* **mediaTypes.video** (required) + * **mimes** (required) - list of supported mime types (string array) + * **protocols** (required) - list of supported video bid response protocols (integer array) + * **context** - (recommended) - the video context, either 'instream', 'outstream'. Defaults to ‘instream’ (string) +* **dsa** - DSA transparency information + * **dsarequired** - flag to indicate if DSA information should be made available (integer) + *0 - Not required + * 1 - Supported, bid responses with or without DSA object will be accepted + *2 - Required, bid responses without DSA object will not be accepted + * 3 - Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * **pubrender** - flag to indicate if the publisher will render the DSA Transparency info (integer) + * 0 - Publisher can't render + * 1 - Publisher could render depending on adrender + * 2 - Publisher will render + * **datatopub** - independent of pubrender, the publisher may need the transparency data for audit purposes (integer) + * 0 - do not send transparency data + * 1 - optional to send transparency data + * 2 - send transparency data +* **endpoint** (required) - EXADS endpoint (URL) + +```js +adUnits = [{ + code: 'postbid_iframe', + mediaTypes: { + video: { + mimes: ['video/mp4'], + context: 'instream', + protocols: [3, 6] + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + impressionId: '1234', + imp: { + ext: { + video_cta: 0 + } + }, + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + country: 'IRL', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] +}]; +``` + +#### RTB 2.4 Native + +* **zoneId** (required) - you can get it from the endpoint created after configuring the zones (integer) +* **fid** (required) - you can get it from the endpoint created after configuring the zones (string) +* **partner** (required) - currently we support rtb 2.4 ("ortb_2_4") only (string) +* **siteId** (recommended) - unique Site ID (string) +* **siteName** site name (string) +* **userIp** (required) - IP address of the user, ipv4 or ipv6 (string) +* **userId** (*required) - unique user ID (string).*If you cannot generate a user ID, you can leave it empty (""). The request will get a response as long as "user" object is included in the request +* **country** - country ISO3 (string) +* **impressionId** (required) - unique impression ID within this bid request (string) +* **keywords** - keywords can be used to ensure ad zones get the right type of advertising. Keywords should be a string of comma-separated words +* **bidfloor** - minimum bid for this impression (CPM) / click (CPC) and account currency (float) +* **bidfloorcur** - currency for minimum bid value specified using ISO-4217 alpha codes (string) +* **bcat** - blocked advertiser categories using the IAB content categories (string array) +* **badv** - block list of advertisers by their domains (string array) +* **dsa** - DSA transparency information + * **dsarequired** - flag to indicate if DSA information should be made available (integer) + *0 - Not required + * 1 - Supported, bid responses with or without DSA object will be accepted + *2 - Required, bid responses without DSA object will not be accepted + * 3 - Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * **pubrender** - flag to indicate if the publisher will render the DSA Transparency info (integer) + * 0 - Publisher can't render + * 1 - Publisher could render depending on adrender + * 2 - Publisher will render + * **datatopub** - independent of pubrender, the publisher may need the transparency data for audit purposes (integer) + * 0 - do not send transparency data + * 1 - optional to send transparency data + * 2 - send transparency data +* **native.plcmtcnt** - the number of identical placements in this Layout (integer) +* **assets (title)** + * **id** - unique asset ID, assigned by exchange. Typically a counter for the array (integer): + *1 - image asset ID + * 2 - title asset ID + * 3 - description asset ID + * **required** - set to 1 if asset is required or 0 if asset is optional (integer) + * **title** + * len (required) - maximum length of the text in the title element (integer) +* **assets (data)** + * **id** - unique asset ID, assigned by exchange. Typically a counter for the array (integer): + *1 - image asset ID + * 2 - title asset ID + * 3 - description asset ID + * **data** + * **type** - type ID of the element supported by the publisher (integer). We support: + *1 - sponsored - sponsored By message where response should contain the brand name of the sponsor + * 2 - desc - descriptive text associated with the product or service being advertised + * **len** - maximum length of the text in the element’s response (integer) +* **assets (img)** + * **id** - unique asset ID, assigned by exchange. Typically a counter for the array (integer): + *1 - image asset ID + * 2 - title asset ID + * 3 - description asset ID + * **required** - set to 1 if asset is required or 0 if asset is optional (integer) + * **img** + * **type** - type ID of the image element supported by the publisher. We support: + *1 - icon image (integer) + * 3 - large image preview for the ad (integer) + * **w** - width of the image in pixels, optional (integer) + * **h** - height of the image in pixels, optional (integer) +* **endpoint** (required) - EXADS endpoint (URL) + +```js +adUnits = [{ + code: 'postbid_iframe', + mediaTypes: { + native: { + ortb: { + assets: [{ + id: 2, + required: 1, + title: { + len: 124 + } + }, + { + id: 3, + data: { + type: 1, + len: 50 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 300 + } + }] + } + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + impressionId: '1234', + native: { + plcmtcnt: 4 + }, + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + country: 'IRL', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] +}]; +``` + +## DSA Transparency + +All DSA information, returned by the ad server, can be found into the **meta** tag of the response. As: + +```js +"meta": { + "dsa": { + "behalf": "...", + "paid": "...", + "transparency": [ + { + "params": [ + ... + ] + } + ], + "adrender": ... + } +} +``` + +For more information navigate to . + +## Tools and suggestions + +This section contains some suggestions that allow to set some parameters automatically. + +### User Ip / Country + +In order to detect the current user ip there are different approaches. An example is using public web services as ```https://api.ipify.org```. + +Example of usage (to add to the publisher websites): + +```html + +``` + +The same service gives the possibility to detect the country as well. Check the official web page about possible limitations of the free licence. + +### Impression Id + +Each advertising request has to be identified uniquely by an id. +One possible approach is using a classical hash function. + +```html + +``` + +### User Id + +The approach used for impression id could be used for generating a unique user id. +Also, it is recommended to store the id locally, e.g. by the browser localStorage. + +```html + +``` diff --git a/test/spec/modules/exadsBidAdapter_spec.js b/test/spec/modules/exadsBidAdapter_spec.js new file mode 100644 index 00000000000..9253f21ddf1 --- /dev/null +++ b/test/spec/modules/exadsBidAdapter_spec.js @@ -0,0 +1,632 @@ +import { expect } from 'chai'; +import { spec, imps } from 'modules/exadsBidAdapter.js'; +import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; + +describe('exadsBidAdapterTest', function () { + const bidder = 'exadsadserver'; + + const partners = { + ORTB_2_4: 'ortb_2_4' + }; + + const imageBanner = { + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + bidder: bidder, + params: { + zoneId: 5147485, + fid: '829a896f011475d505a0d89cfdd1af8d9cdb07ff', + partner: partners.ORTB_2_4, + siteId: '12345', + siteName: 'your-site.com', + catIab: ['IAB25-3'], + userIp: '0.0.0.0', + userId: '', + country: 'IRL', + impressionId: '123456', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39', 'IAB8-18', 'IAB8-5', 'IAB9-9'], + badv: ['first.com', 'second.com'], + mimes: ['image/jpg'], + endpoint: 'test.com', + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + } + }; + + const native = { + mediaTypes: { + native: { + ortb: { + assets: [{ + id: 3, + required: 1, + title: { + len: 124 + } + }, + { + id: 2, + data: { + type: 1, + len: 50 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 300, + } + }] + } + }, + }, + bidder: bidder, + params: { + zoneId: 5147485, + fid: '829a896f011475d505a0d89cfdd1af8d9cdb07ff', + partner: partners.ORTB_2_4, + siteId: '12345', + siteName: 'your-site.com', + catIab: ['IAB25-3'], + userIp: '0.0.0.0', + userId: '', + country: 'IRL', + impressionId: '123456', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + native: { + plcmtcnt: 4, + }, + dsa: { + pubrender: 0, + datatopub: 2 + }, + endpoint: 'test.com' + } + }; + + const instream = { + mediaTypes: { + video: { + mimes: ['video/mp4'], + protocols: [3, 6], + } + }, + bidder: bidder, + params: { + zoneId: 5147485, + fid: '829a896f011475d505a0d89cfdd1af8d9cdb07ff', + partner: partners.ORTB_2_4, + siteId: '12345', + siteName: 'your-site.com', + catIab: ['IAB25-3'], + userIp: '0.0.0.0', + userId: '', + country: 'IRL', + impressionId: '123456', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + imp: { + ext: { + video_cta: 0 + } + }, + dsa: { + datatopub: 2 + }, + endpoint: 'test.com', + } + }; + + describe('while validating bid request', function () { + it('should check the validity of bidRequest with all mandatory params for banner ad-format', function () { + expect(spec.isBidRequestValid(imageBanner)).to.equal(true); + }); + + it('should check the validity of a bidRequest with all mandatory params for native ad-format', function () { + expect(spec.isBidRequestValid(native)); + }); + + it('should check the validity of a bidRequest with all mandatory params for instream ad-format', function () { + expect(spec.isBidRequestValid(instream)).to.equal(true); + }); + + it('should check the validity of a bidRequest with wrong partner', function () { + expect(spec.isBidRequestValid({ + ...imageBanner, + params: { + ...imageBanner.params, + partner: 'not_ortb_2_4' + } + })).to.eql(false); + }); + + it('should check the validity of a bidRequest without params', function () { + expect(spec.isBidRequestValid({ + bidder: bidder, + params: { } + })).to.equal(false); + }); + }); + + describe('while building bid request for banner ad-format', function () { + const bidRequests = [imageBanner]; + + it('should make a bidRequest by HTTP method', function () { + const requests = spec.buildRequests(bidRequests, {}); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + }); + + describe('while building bid request for native ad-format', function () { + const bidRequests = [native]; + + it('should make a bidRequest by HTTP method', function () { + const requests = spec.buildRequests(bidRequests, {}); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + }); + + describe('while building bid request for instream ad-format', function () { + const bidRequests = [instream]; + + it('should make a bidRequest by HTTP method', function () { + const requests = spec.buildRequests(bidRequests, {}); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + }); + + describe('while interpreting bid response', function () { + beforeEach(() => { + imps.set('270544423272657', { adPartner: 'ortb_2_4', mediaType: null }); + }); + + it('should test the banner interpretResponse', function () { + const serverResponse = { + body: { + 'id': '2d2a496527398e', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8f7fa506af97bc193e7bf099d8ed6930bd50aaf1', + 'impid': '270544423272657', + 'price': 0.0045000000000000005, + 'adm': '\n\n', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'image/jpeg', + 'image/jpg' + ] + }, + 'nurl': 'http://your-ad-network.com/', + 'cid': '6260389', + 'crid': '89453173', + 'adomain': [ + 'test.com' + ], + 'w': 300, + 'h': 250, + 'attr': [ + 12 + ] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '2d2a496527398e', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'bidfloor': 1.1e-7, + 'bidfloorcur': 'EUR', + 'banner': { + 'w': 300, + 'h': 250 + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-banner.html', + 'keywords': 'lifestyle, humour' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(BANNER); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + }); + + it('should test the native interpretResponse', function () { + const serverResponse = { + body: { + 'id': '21dea1fc6c3e1b', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'cedc93987cd4a1e08fdfe97de97482d1ecc503ee', + 'impid': '270544423272657', + 'price': 0.0045000000000000005, + 'adm': '{"native":{"link":{"url":"https:\\/\\/your-ad-network.com"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/your-ad-network.com"}],"assets":[{"id":1,"title":{"text":"Title"}},{"id":2,"data":{"value":"Description"}},{"id":3,"img":{"url":"https:\\/\\/your-ad-network.com\\/32167\\/f85ee87ea23.jpg"}}]}}', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'image/jpeg', + 'image/jpg' + ] + }, + 'nurl': 'http://your-ad-network.com', + 'cid': '6260393', + 'crid': '89453189', + 'adomain': [ + 'test.com' + ], + 'w': 300, + 'h': 300, + 'attr': [] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '21dea1fc6c3e1b', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'bidfloor': 1.1e-7, + 'bidfloorcur': 'EUR', + 'native': { + 'request': '{"native":{"ver":"1.2","context":1,"contextsubtype":10,"plcmttype":4,"plcmtcnt":4,"assets":[{"id":1,"required":1,"title":{"len":124}},{"id":2,"data":{"type":1,"len":50}},{"id":3,"required":1,"img":{"type":3,"w":300,"h":300,"wmin":300,"hmin":300}}]}}', + 'ver': '1.2' + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-native.html' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(NATIVE); + }); + + it('should test the InStream Video interpretResponse', function () { + const serverResponse = { + body: { + 'id': '2218abc7ebca97', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'd2d2063517b126252f56e22767c53f936ff40411', + 'impid': '270544423272657', + 'price': 0.12474000000000002, + 'adm': '\n\n \n \n your-ad-network.com\n \n \n \n \n \n \n 00:00:20.32\n \n \n \n \n \n \n \n \n \n \n \n \n test.com\n \n \n \n \n \n \n \n \n\n', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'video/mp4' + ] + }, + 'nurl': 'http://your-ad-network.com', + 'cid': '6260395', + 'crid': '89453191', + 'adomain': [ + 'test.com' + ], + 'w': 0, + 'h': 0, + 'attr': [] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '2218abc7ebca97', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'video': { + 'mimes': [ + 'video/mp4' + ] + }, + 'protocols': [ + 3, + 6 + ], + 'ext': { + 'video_cta': 0 + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-InStreamVideo.html', + 'keywords': 'lifestyle, humour' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(VIDEO); + }); + }); + + describe('checking dsa information', function() { + it('should add dsa information to the request via bidderRequest.params.dsa', function () { + const bidRequests = [imageBanner]; + + const requests = spec.buildRequests(bidRequests, {}); + + requests.forEach(function(requestItem) { + const payload = JSON.parse(requestItem.data); + + expect(payload.regs.ext.dsa).to.exist; + expect(payload.regs.ext.dsa.dsarequired).to.equal(3); + expect(payload.regs.ext.dsa.pubrender).to.equal(0); + expect(payload.regs.ext.dsa.datatopub).to.equal(2); + }); + }); + + it('should test the dsa interpretResponse', function () { + const dsaResponse = { + 'behalf': '...', + 'paid': '...', + 'transparency': [ + { + 'params': [ + 2 + ] + } + ], + 'adrender': 0 + }; + + const serverResponse = { + body: { + 'id': '2d2a496527398e', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8f7fa506af97bc193e7bf099d8ed6930bd50aaf1', + 'impid': '270544423272657', + 'price': 0.0045000000000000005, + 'adm': '\n\n', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'image/jpeg', + 'image/jpg' + ], + 'dsa': dsaResponse + }, + 'nurl': 'http://your-ad-network.com/', + 'cid': '6260389', + 'crid': '89453173', + 'adomain': [ + 'test.com' + ], + 'w': 300, + 'h': 250, + 'attr': [ + 12 + ] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '2d2a496527398e', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'bidfloor': 1.1e-7, + 'bidfloorcur': 'EUR', + 'banner': { + 'w': 300, + 'h': 250 + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-banner.html', + 'keywords': 'lifestyle, humour' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + }, + 'regs': { + 'ext': { + 'dsa': { + 'dsarequired': 3, + 'pubrender': 0, + 'datatopub': 2 + } + } + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + const bidResponse = bidResponses[0]; + expect(bidResponse.meta).to.exist; + expect(bidResponse.meta.dsa).to.exist; + expect(bidResponse.meta.dsa).equal(dsaResponse); + }); + }); + + describe('on getting the win event', function() { + it('should not create nurl request if bid is undefined', function() { + const result = spec.onBidWon({}); + expect(result).to.be.undefined; + }); + }); + + describe('checking timeut', function () { + it('should exists and be a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + }); +}); From 5cfae1b34bd28d41e89c29f005b7abebe26a8c48 Mon Sep 17 00:00:00 2001 From: Hrechko Dmytro Date: Thu, 2 May 2024 17:38:22 +0300 Subject: [PATCH 0005/1097] Eight Pod Bid / Analytics Adapter : initial release (#11260) * eight pod init * fix mocks * specify eightPodAnalyticAdapter tests * update Event import --- modules/eightPodAnalyticsAdapter.js | 186 ++++++++++++++++ modules/eightPodAnalyticsAdapter.md | 19 ++ modules/eightPodBidAdapter.js | 193 +++++++++++++++++ modules/eightPodBidAdapter.md | 36 ++++ .../modules/eightPodAnalyticsAdapter_spec.js | 178 +++++++++++++++ test/spec/modules/eightPodBidAdapter_spec.js | 203 ++++++++++++++++++ 6 files changed, 815 insertions(+) create mode 100644 modules/eightPodAnalyticsAdapter.js create mode 100644 modules/eightPodAnalyticsAdapter.md create mode 100644 modules/eightPodBidAdapter.js create mode 100644 modules/eightPodBidAdapter.md create mode 100644 test/spec/modules/eightPodAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/eightPodBidAdapter_spec.js diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js new file mode 100644 index 00000000000..64ff845505d --- /dev/null +++ b/modules/eightPodAnalyticsAdapter.js @@ -0,0 +1,186 @@ +import {logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import { EVENTS } from '../src/constants.js'; +import adapterManager from '../src/adapterManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js' +import {getStorageManager} from '../src/storageManager.js'; + +const analyticsType = 'endpoint'; +const MODULE_NAME = `eightPod`; +const MODULE = `${MODULE_NAME}AnalyticProvider`; + +/** + * Custom tracking server that gets internal events from EightPod's ad unit + */ +const trackerUrl = 'https://demo.8pod.com/tracker/track'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}) + +const { + BID_WON +} = EVENTS; + +export let queue = []; +export let context; + +/** + * Create eightPod Analytic adapter + */ +let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { + /** + * Execute on bid won - setup basic settings, save context about EightPod's bid. We will send it with our events later + */ + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + if (args.bidder === 'eightPod') { + eightPodAnalytics.setupPage(args); + context = makeContext(args); + break; + } + } + }, + + /** + * Execute on bid won - subscribe on events from adUnit + */ + setupPage() { + eightPodAnalytics.eventSubscribe(); + queue = getEventFromLocalStorage(); + }, + /** + * Subscribe on internal ad unit tracking events + */ + eventSubscribe() { + window.addEventListener('message', async (event) => { + const data = event?.data; + + if (!data?.detail?.name) { + return; + } + + trackEvent(data); + }); + + // Send queue of event every 10 seconds + setInterval(sendEvents, 10_000); + }, + resetQueue() { + queue = []; + }, + getContext() { + return context; + } +}); + +/** + * Create context of event, who emits it + */ +function makeContext(args) { + const params = args?.params?.[0]; + return { + bidId: args.seatBidId, + variantId: args.creativeId || 'variantId', + campaignId: 'campaignId', + publisherId: params.publisherId, + placementId: params.placementId, + }; +} + +/** + * Create event, add context and push it to queue + */ +export function trackEvent(event) { + if (!event.detail) { + return; + } + const fullEvent = { + context: eightPodAnalytics.getContext(), + eventType: event.detail.type, + eventClass: 'adunit', + timestamp: new Date().getTime(), + eventName: event.detail.name, + payload: event.detail.payload + }; + + addEvent(fullEvent); +} + +/** + * Push event to queue, save event list in local storage + */ +function addEvent(eventPayload) { + queue.push(eventPayload); + storage.setDataInLocalStorage(`EIGHT_POD_EVENTS`, JSON.stringify(queue), null); +} + +/** + * Gets previously saved event that has not been sent + */ +function getEventFromLocalStorage() { + const storedEvents = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage('EIGHT_POD_EVENTS') : null; + + if (storedEvents) { + return JSON.parse(storedEvents); + } else { + return []; + } +} + +/** + * Send event to our custom tracking server and reset queue + */ +function sendEvents() { + eightPodAnalytics.eventsStorage = queue; + + if (queue.length) { + try { + sendEventsApi(queue, { + success: () => { + resetLocalStorage(); + eightPodAnalytics.resetQueue(); + }, + error: (e) => { + logError(MODULE, 'Cant send events', e); + } + }) + } catch (e) { + logError(MODULE, 'Cant send events', e); + } + } +} + +/** + * Send event to our custom tracking server + */ +function sendEventsApi(eventList, callbacks) { + ajax(trackerUrl, callbacks, JSON.stringify(eventList)); +} + +/** + * Remove saved events in success scenario + */ +const resetLocalStorage = () => { + storage.setDataInLocalStorage(`EIGHT_POD_EVENTS`, JSON.stringify([]), null); +} + +// save the base class function +eightPodAnalytics.originEnableAnalytics = eightPodAnalytics.enableAnalytics; +eightPodAnalytics.eventsStorage = []; + +// override enableAnalytics so we can get access to the config passed in from the page +eightPodAnalytics.enableAnalytics = function (config) { + eightPodAnalytics.originEnableAnalytics(config); + logInfo(MODULE, 'init', config); +}; + +/** + * Register Analytics Adapter + */ +adapterManager.registerAnalyticsAdapter({ + adapter: eightPodAnalytics, + code: MODULE_NAME +}); + +export default eightPodAnalytics; diff --git a/modules/eightPodAnalyticsAdapter.md b/modules/eightPodAnalyticsAdapter.md new file mode 100644 index 00000000000..fe37bf34459 --- /dev/null +++ b/modules/eightPodAnalyticsAdapter.md @@ -0,0 +1,19 @@ +# Overview +Module Name: 8pod Analytics by 8Pod + +Module Type: Analytics Adapter + +Maintainer: bianca@8pod.com + +# Description + +Analytics adapter for prebid provided by 8pod. It gets events from eightPod's ad unit and send it to our tracking server to improve user experience. +Please, use it ONLY with eightPodBidAdapter. + +# Analytics Adapter configuration example + +``` +{ + provider: 'eightPod' +} +``` diff --git a/modules/eightPodBidAdapter.js b/modules/eightPodBidAdapter.js new file mode 100644 index 00000000000..02f0954af07 --- /dev/null +++ b/modules/eightPodBidAdapter.js @@ -0,0 +1,193 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' +import * as utils from '../src/utils.js' + +export const BIDDER_CODE = 'eightPod' +const url = 'https://demo.8pod.com/bidder/rtb/eightpod_exchange/bid?trace=true'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + isBannerBid, + isVideoBid, + onBidWon +} + +registerBidder(spec) + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context) + return req + }, + response(buildResponse, bidResponses, ortbResponse, context) { + const response = buildResponse(bidResponses, ortbResponse, context) + return response.bids + }, + imp(buildImp, bidRequest, context) { + return buildImp(bidRequest, context) + }, + bidResponse +}) + +function hasRequiredParams(bidRequest) { + return !!bidRequest?.params?.placementId +} + +function isBidRequestValid(bidRequest) { + return hasRequiredParams(bidRequest) +} + +function buildRequests(bids, bidderRequest) { + let bannerBids = bids.filter((bid) => isBannerBid(bid)) + let requests = bannerBids.length + ? [createRequest(bannerBids, bidderRequest, BANNER)] + : [] + return requests +} + +function bidResponse(buildBidResponse, bid, context) { + bid.nurl = replacePriceInUrl(bid.nurl, bid.price); + + const bidResponse = buildBidResponse(bid, context); + + bidResponse.height = context?.imp?.banner?.format?.[0].h; + bidResponse.width = context?.imp?.banner?.format?.[0].w; + + bidResponse.burl = replacePriceInUrl(bid.burl, bidResponse.originalCpm || bidResponse.cpm); + return bidResponse; +} + +function onBidWon(bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl) + } +} +function replacePriceInUrl(url, price) { + return url.replace(/\${AUCTION_PRICE}/, price) +} + +export function parseUserAgent() { + const ua = navigator.userAgent.toLowerCase(); + + // Check if it's iOS + if (/iphone|ipad|ipod/.test(ua)) { + // Extract iOS version and device type + const iosInfo = /(iphone|ipad|ipod) os (\d+[._]\d+)|((iphone|ipad|ipod)(\D+cpu) os (\d+(?:[._\s]\d+)?))/.exec(ua); + return { + platform: 'ios', + version: iosInfo ? iosInfo[1] : '', + device: iosInfo ? iosInfo[2].replace('_', '.') : '' + }; + } else if (/android/.test(ua)) { + // Check if it's Android + // Extract Android version + const androidVersion = /android (\d+([._]\d+)?)/.exec(ua); + return { + platform: 'android', + version: androidVersion ? androidVersion[1].replace('_', '.') : '', + device: '' + }; + } else { + // If neither iOS nor Android, return unknown + return { + platform: 'Unknown', + version: '', + device: '' + }; + } +} + +export function getPageKeywords(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="keywords"]'); + } catch (e) { + element = document.querySelector('meta[name="keywords"]'); + } + + return ((element && element.content) || '').replaceAll(' ', ''); +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + const data = converter.toORTB({ + bidRequests, + bidderRequest, + context: { mediaType }, + }); + + data.adSlotPositionOnScreen = 'ABOVE_THE_FOLD'; + data.at = 1; + + const params = getBidderParams(bidRequests); + + data.device = { + ...data.device, + model: parseUserAgent().device, + os: parseUserAgent().platform, + osv: parseUserAgent().version, + geo: { + country: params.country || 'GRB' + }, + language: params.language || data.device.language, + } + data.site = { + ...data.site, + keywords: getPageKeywords(window), + } + data.imp = [ + { + ...data.imp?.[0], + pmp: params.dealId + ? { + ...data.pmp, + deals: [ + { + bidfloor: 0.5, + at: 2, + id: params.dealId, + }, + ], + private_auction: 1, + } + : data.pmp, + } + ] + data.adSlotPlacementId = params.placementId; + + const req = { + method: 'POST', + url, + data + } + return req +} + +function getBidderParams(bidRequests) { + const bid = bidRequests.find(bid => { + return bid.bidder === BIDDER_CODE + }); + + return bid?.params ? bid.params : undefined; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video') +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') +} + +function interpretResponse(resp, req) { + return converter.fromORTB({ request: req.data, response: resp.body }) +} diff --git a/modules/eightPodBidAdapter.md b/modules/eightPodBidAdapter.md new file mode 100644 index 00000000000..afefd5717de --- /dev/null +++ b/modules/eightPodBidAdapter.md @@ -0,0 +1,36 @@ +# Overview +Module Name: 8pod Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: bianca@8pod.com + +# Description + +Connect to 8pod for bids. + +This adapter requires setup and approval from the 8pod team. + +Please add eightPodAnalytics to collect user behavior and improve user experience as well. + +# Bidder Adapter configuration example + +``` +var adUnits = [{ + code: 'something', + mediaTypes: { + banner: { + sizes: [[350, 550]], + }, + }, + bids: [ + { + bidder: 'eightPod', + params: { + placementId: 13144370, + publisherId: 'publisherID-488864646', + }, + }, + ], + }]; +``` diff --git a/test/spec/modules/eightPodAnalyticsAdapter_spec.js b/test/spec/modules/eightPodAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..930b15bda31 --- /dev/null +++ b/test/spec/modules/eightPodAnalyticsAdapter_spec.js @@ -0,0 +1,178 @@ +import analyticsAdapter, { storage, queue, context, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import eightPodAnalytics from 'modules/eightPodAnalyticsAdapter.js'; +import { EVENTS } from '../../../src/constants.js'; + +const { + BID_WON +} = EVENTS; + +describe('eightPodAnalyticAdapter', function() { + let sandbox; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + adapterManager.enableAnalytics({ + provider: 'eightPod' + }); + }); + + afterEach(function() { + sandbox.restore(); + analyticsAdapter.disableAnalytics(); + }); + + describe('setup page', function() { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let addEventListenerSpy; + + beforeEach(function() { + localStorageIsEnabledStub = sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + getDataFromLocalStorageStub = sandbox.stub( + storage, + 'getDataFromLocalStorage' + ); + addEventListenerSpy = sandbox.spy(window, 'addEventListener'); + }); + + afterEach(function() { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + addEventListenerSpy.restore(); + }); + + it('should subscribe on messageEvents', function() { + getDataFromLocalStorageStub.returns(JSON.stringify([])); + sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + + analyticsAdapter.setupPage(); + + sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 1); + sandbox.assert.callCount(addEventListenerSpy, 1); + }); + + it('should receive saved events list', function() { + const eventList = [1, 2, 3]; + getDataFromLocalStorageStub.returns(JSON.stringify(eventList)); + sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + + analyticsAdapter.setupPage(); + expect(queue).to.deep.equal(eventList) + }); + }); + + describe('track event', function() { + let setupPageStub; + + beforeEach(function() { + setupPageStub = sandbox.stub(eightPodAnalytics, 'setupPage'); + }); + + afterEach(function() { + setupPageStub.restore(); + }); + + it('should NOT call setup page and get context', function() { + eightPodAnalytics.track({ + eventType: 'wrong_event_type', + }) + + sandbox.assert.callCount(setupPageStub, 0); + expect(context).to.deep.equal(undefined) + }); + + it('should call setup page and get context', function() { + eightPodAnalytics.track({ + eventType: BID_WON, + args: { + bidder: 'eightPod', + creativeId: 'creativeId', + seatBidId: 'seatBidId', + params: [ + { + publisherId: 'publisherId', + placementId: 'placementId', + } + ] + } + }) + + sandbox.assert.callCount(setupPageStub, 1); + expect(context).to.deep.equal({ + bidId: 'seatBidId', + campaignId: 'campaignId', + placementId: 'placementId', + publisherId: 'publisherId', + variantId: 'creativeId' + }) + }); + }); + + describe('trackEvent', function() { + let getContextStub, getTimeStub; + + beforeEach(function() { + getContextStub = sandbox.stub(eightPodAnalytics, 'getContext'); + getTimeStub = sandbox.stub(Date.prototype, 'getTime').returns(1234); + eightPodAnalytics.resetQueue(); + }); + + afterEach(function() { + getContextStub.restore(); + getTimeStub.restore(); + }); + + it('should add event to the queue', function() { + getContextStub.returns({}); + + const event1 = { + detail: { + type: 'Counter', + name: 'next_slide', + payload: { + from: '1.1', + to: '2.2', + value: 3 + } + } + } + const result1 = { + context: {}, + eventType: 'Counter', + eventClass: 'adunit', + timestamp: 1234, + eventName: 'next_slide', + payload: { + from: '1.1', + to: '2.2', + value: 3 + } + } + + const event2 = { + detail: { + type: 'Counter', + name: 'pod_impression', + payload: { + value: 2 + } + } + } + const result2 = { + context: {}, + eventType: 'Counter', + eventClass: 'adunit', + timestamp: 1234, + eventName: 'pod_impression', + payload: { + value: 2 + } + } + trackEvent(event1) + expect(queue).to.deep.equal([result1]); + trackEvent(event2); + expect(queue).to.deep.equal([result1, result2]); + }); + }); +}); diff --git a/test/spec/modules/eightPodBidAdapter_spec.js b/test/spec/modules/eightPodBidAdapter_spec.js new file mode 100644 index 00000000000..7d55997d8cd --- /dev/null +++ b/test/spec/modules/eightPodBidAdapter_spec.js @@ -0,0 +1,203 @@ +import { expect } from 'chai' +import { spec, getPageKeywords, parseUserAgent } from 'modules/eightPodBidAdapter' +import 'modules/priceFloors.js' +import { config } from 'src/config.js' +import { newBidder } from 'src/adapters/bidderFactory' +import * as utils from '../../../src/utils'; +import sinon from 'sinon'; + +describe('eightPodBidAdapter', function () { + const adapter = newBidder(spec) + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'eightPod', + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + params: { + placementId: 'placementId1', + }, + } + const invalidBid = { + bidder: 'eightPod', + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + } + + beforeEach(() => { + config.resetConfig() + }) + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true) + }) + + it('should return false when required params found and invalid bid', function () { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests, bidderRequest + beforeEach(function () { + bidRequests = [ + { + bidder: 'eightPod', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + params: { + placementId: 'placementId1', + } + } + ] + bidderRequest = { + refererInfo: {}, + ortb2: { + device: { + ua: 'ua', + language: 'en', + dnt: 1, + js: 1, + } + } } + }) + + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], bidderRequest) + expect(bidRequest).to.be.an('array') + expect(bidRequest.length).to.equal(0) + }) + + it('should return a valid bid request object', function () { + const request = spec.buildRequests(bidRequests, bidderRequest) + + expect(request).to.be.an('array') + expect(request[0].data).to.be.an('object') + expect(request[0].method).to.equal('POST') + expect(request[0].url).to.not.equal('') + expect(request[0].url).to.not.equal(undefined) + expect(request[0].url).to.not.equal(null) + }) + }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain nurl', function() { + spec.onBidWon({}); + expect(utils.triggerPixel.callCount).to.equal(0) + }) + + it('Should trigger pixel if bid nurl', function() { + spec.onBidWon({ + burl: 'https://example.com/some-tracker' + }); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) + + describe('getPageKeywords function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document keywords if available', function() { + const keywordsContent = 'keyword1,keyword2,keyword3'; + const fakeTopDocument = { + querySelector: sandbox.stub() + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + + const result = getPageKeywords({ top: fakeTopWindow }); + expect(result).to.equal(keywordsContent); + }); + + it('should return the current document keywords if top document is not accessible', function() { + const keywordsContent = 'keyword1,keyword2,keyword3'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }); + + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + + const result = getPageKeywords(fakeWindow); + expect(result).to.equal(keywordsContent); + }); + + it('should return an empty string if no keywords meta tag is found', function() { + sandbox.stub(document, 'querySelector').withArgs('meta[name="keywords"]').returns(null); + + const result = getPageKeywords(); + expect(result).to.equal(''); + }); + }); + + describe('parseUserAgent function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the platform and version IOS', function() { + const uaStub = sandbox.stub(window.navigator, 'userAgent'); + uaStub.value('Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1'); + + const result = parseUserAgent(); + expect(result.platform).to.equal('ios'); + expect(result.version).to.equal('iphone'); + expect(result.device).to.equal('16.6'); + }); + + it('should return the platform and version android', function() { + const uaStub = sandbox.stub(window.navigator, 'userAgent'); + uaStub.value('Mozilla/5.0 (Linux; Android 5.0.1; SM-G920V Build/LRX22C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36'); + + const result = parseUserAgent(); + expect(result.platform).to.equal('android'); + expect(result.version).to.equal('5.0'); + expect(result.device).to.equal(''); + }) + }) +}) From 7ab6165e342294fafa087135af219f3f88f0e220 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 2 May 2024 21:59:31 +0400 Subject: [PATCH 0006/1097] Limelight Digital Bid Adapter: fix page field filling (#11436) --- modules/limelightDigitalBidAdapter.js | 6 ++-- .../limelightDigitalBidAdapter_spec.js | 36 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index acc6876b822..c816f800fda 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -128,7 +128,8 @@ function buildRequest(winTop, host, adUnits, bidderRequest) { deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, adUnits: adUnits, - sua: bidderRequest?.ortb2?.device?.sua + sua: bidderRequest?.ortb2?.device?.sua, + page: bidderRequest?.ortb2?.site?.page || bidderRequest?.refererInfo?.page } } } @@ -170,8 +171,7 @@ function buildPlacement(bidRequest) { custom2: bidRequest.params.custom2, custom3: bidRequest.params.custom3, custom4: bidRequest.params.custom4, - custom5: bidRequest.params.custom5, - page: bidRequest.refererInfo.page + custom5: bidRequest.params.custom5 } } } diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 9e8a00959d4..51cf8cd14ee 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -17,9 +17,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page1' - }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', mediaTypes: { @@ -68,9 +65,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page2' - }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', sizes: [[350, 200]], @@ -121,9 +115,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page3' - }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', sizes: [[800, 600]], @@ -171,9 +162,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page4' - }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', video: { @@ -218,6 +206,9 @@ describe('limelightDigitalAdapter', function () { architecture: 'arm' } } + }, + refererInfo: { + page: 'testPage' } } const serverRequests = spec.buildRequests([bid1, bid2, bid3, bid4], bidderRequest) @@ -243,7 +234,8 @@ describe('limelightDigitalAdapter', function () { 'deviceHeight', 'secure', 'adUnits', - 'sua' + 'sua', + 'page' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); @@ -262,8 +254,7 @@ describe('limelightDigitalAdapter', function () { 'custom2', 'custom3', 'custom4', - 'custom5', - 'page' + 'custom5' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -277,12 +268,13 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.custom3).to.be.a('string'); expect(adUnit.custom4).to.be.a('string'); expect(adUnit.custom5).to.be.a('string'); - expect(adUnit.page).to.be.a('string'); }) expect(data.sua.browsers).to.be.a('array'); expect(data.sua.platform).to.be.a('array'); expect(data.sua.mobile).to.be.a('number'); expect(data.sua.architecture).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.page).to.be.equal('testPage'); }) }) it('Returns valid URL', function () { @@ -298,6 +290,17 @@ describe('limelightDigitalAdapter', function () { const serverRequests = spec.buildRequests([]) expect(serverRequests).to.be.an('array').that.is.empty }) + it('Returns request with page field value from ortb2 object if ortb2 has page field', function () { + bidderRequest.ortb2.site = { + page: 'testSitePage' + } + const serverRequests = spec.buildRequests([bid1], bidderRequest) + expect(serverRequests).to.have.lengthOf(1) + serverRequests.forEach(serverRequest => { + expect(serverRequest.data.page).to.be.a('string'); + expect(serverRequest.data.page).to.be.equal('testSitePage'); + }) + }) }) describe('interpretBannerResponse', function () { let resObject = { @@ -716,5 +719,4 @@ function validateAdUnit(adUnit, bid) { expect(adUnit.publisherId).to.equal(bid.params.publisherId); expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); expect(adUnit.supplyChain).to.deep.equal(bid.schain); - expect(adUnit.page).to.equal(bid.refererInfo.page); } From 7d52b11169ab715284fa72162ec6425c61726d9c Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Mon, 6 May 2024 10:32:16 -0700 Subject: [PATCH 0007/1097] Conversant Adapter - remove transformBidParams (#11441) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index ebcad38d866..76ff2a9e2ad 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -15,7 +15,6 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; @@ -180,20 +179,6 @@ export const spec = { return converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); }, - /** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'site_id': 'string', - 'secure': 'number', - 'mobile': 'number' - }, params); - }, - /** * Register User Sync. */ From 8a57af7acfd32e60c0cfa5d18117de3bde6a8746 Mon Sep 17 00:00:00 2001 From: Ruslan S Date: Tue, 7 May 2024 01:53:05 +0800 Subject: [PATCH 0008/1097] [Smaato] Migrating to ortbConverter requests build process (#11433) - Response handling will be done separately --- modules/smaatoBidAdapter.js | 421 ++++---- test/spec/modules/smaatoBidAdapter_spec.js | 1025 +++++++++++--------- 2 files changed, 755 insertions(+), 691 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 64d8765dc04..9a0a0f75623 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -3,10 +3,11 @@ import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import { NATIVE_IMAGE_TYPES } from '../src/constants.js'; +import {NATIVE_IMAGE_TYPES} from '../src/constants.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; import {fill} from '../libraries/appnexusUtils/anUtils.js'; import {chunk} from '../libraries/chunk/chunk.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -18,134 +19,14 @@ import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.8' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_2.0' +const TTL = 300; const CURRENCY = 'USD'; - -const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { - const requestTemplate = { - id: bidderRequest.bidderRequestId, - at: 1, - cur: [CURRENCY], - tmax: bidderRequest.timeout, - site: { - id: window.location.hostname, - // TODO: do the fallbacks make sense here? - domain: bidderRequest.refererInfo.domain || window.location.hostname, - page: bidderRequest.refererInfo.page || window.location.href, - ref: bidderRequest.refererInfo.ref - }, - device: { - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - ua: navigator.userAgent, - dnt: getDNT() ? 1 : 0, - h: screen.height, - w: screen.width - }, - regs: { - coppa: config.getConfig('coppa') === true ? 1 : 0, - ext: {} - }, - user: { - ext: {} - }, - source: { - ext: { - schain: bidRequest.schain - } - }, - ext: { - client: SMAATO_CLIENT - } - }; - - let ortb2 = bidderRequest.ortb2 || {}; - Object.assign(requestTemplate.user, ortb2.user); - Object.assign(requestTemplate.site, ortb2.site); - - deepSetValue(requestTemplate, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); - - if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { - deepSetValue(requestTemplate, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - deepSetValue(requestTemplate, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent !== undefined) { - deepSetValue(requestTemplate, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (ortb2.regs?.gpp !== undefined) { - deepSetValue(requestTemplate, 'regs.ext.gpp', ortb2.regs.gpp); - deepSetValue(requestTemplate, 'regs.ext.gpp_sid', ortb2.regs.gpp_sid); - } - - if (ortb2.device?.ifa !== undefined) { - deepSetValue(requestTemplate, 'device.ifa', ortb2.device.ifa); - } - - if (ortb2.device?.geo !== undefined) { - deepSetValue(requestTemplate, 'device.geo', ortb2.device.geo); - } - - if (deepAccess(bidRequest, 'params.app')) { - if (!deepAccess(requestTemplate, 'device.geo')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(requestTemplate, 'device.geo', geo); - } - if (!deepAccess(requestTemplate, 'device.ifa')) { - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(requestTemplate, 'device.ifa', ifa); - } - } - - const eids = deepAccess(bidRequest, 'userIdAsEids'); - if (eids && eids.length) { - deepSetValue(requestTemplate, 'user.ext.eids', eids); - } - - let requests = []; - - if (deepAccess(bidRequest, 'mediaTypes.banner')) { - const bannerRequest = Object.assign({}, requestTemplate, createBannerImp(bidRequest)); - requests.push(bannerRequest); - } - - const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video'); - if (videoMediaType) { - if (videoMediaType.context === ADPOD) { - const adPodRequest = Object.assign({}, requestTemplate, createAdPodImp(bidRequest, videoMediaType)); - addOptionalAdpodParameters(adPodRequest, videoMediaType); - requests.push(adPodRequest); - } else { - const videoRequest = Object.assign({}, requestTemplate, createVideoImp(bidRequest, videoMediaType)); - requests.push(videoRequest); - } - } - - const nativeOrtbRequest = bidRequest.nativeOrtbRequest; - if (nativeOrtbRequest) { - const nativeRequest = Object.assign({}, requestTemplate, createNativeImp(bidRequest, nativeOrtbRequest)); - requests.push(nativeRequest); - } - - return requests; -} - -const buildServerRequest = (validBidRequest, data) => { - logInfo('[SMAATO] OpenRTB Request:', data); - return { - method: 'POST', - url: validBidRequest.params.endpoint || SMAATO_ENDPOINT, - data: JSON.stringify(data), - options: { - withCredentials: true, - crossOrigin: true, - } - }; -} +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, gvlid: 82, /** @@ -195,13 +76,30 @@ export const spec = { return true; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (bidRequests, bidderRequest) => { logInfo('[SMAATO] Client version:', SMAATO_CLIENT); - return validBidRequests.map((validBidRequest) => { - const openRtbBidRequests = buildOpenRtbBidRequest(validBidRequest, bidderRequest); - return openRtbBidRequests.map((openRtbBidRequest) => buildServerRequest(validBidRequest, openRtbBidRequest)); - }).reduce((acc, item) => item != null && acc.concat(item), []); + let requests = []; + bidRequests.forEach(bid => { + // separate requests per mediaType + SUPPORTED_MEDIA_TYPES.forEach(mediaType => { + if ((bid.mediaTypes && bid.mediaTypes[mediaType]) || (mediaType === NATIVE && bid.nativeOrtbRequest)) { + const data = converter.toORTB({bidderRequest, bidRequests: [bid], context: {mediaType}}); + requests.push({ + method: 'POST', + url: bid.params.endpoint || SMAATO_ENDPOINT, + data: JSON.stringify(data), + options: { + withCredentials: true, + crossOrigin: true, + }, + bidderRequest + }) + } + }); + }); + + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -239,7 +137,7 @@ export const spec = { creativeId: bid.crid, dealId: bid.dealid || null, netRevenue: deepAccess(bid, 'ext.net', true), - currency: response.cur, + currency: CURRENCY, meta: { advertiserDomains: bid.adomain, networkName: bid.bidderName, @@ -306,6 +204,172 @@ export const spec = { } registerBidder(spec); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: TTL, + currency: CURRENCY + }, + request(buildRequest, imps, bidderRequest, context) { + function isGdprApplicable() { + return bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies; + } + + const request = buildRequest(imps, bidderRequest, context); + const bidRequest = context.bidRequests[0]; + let siteContent; + const mediaType = context.mediaType; + if (mediaType === VIDEO) { + const videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams.context === ADPOD) { + request.imp = createAdPodImp(request.imp[0], videoParams); + siteContent = addOptionalAdpodParameters(videoParams); + } + } + + request.at = 1; + + if (request.user) { + if (isGdprApplicable()) { + deepSetValue(request.user, 'ext.consent', bidderRequest.gdprConsent.consentString); + } + } else { + const eids = deepAccess(bidRequest, 'userIdAsEids'); + request.user = { + ext: { + consent: isGdprApplicable() ? bidderRequest.gdprConsent.consentString : null, + eids: (eids && eids.length) ? eids : null + } + } + } + + if (request.site) { + request.site.id = window.location.hostname + if (siteContent) { + request.site.content = siteContent; + } + } else { + request.site = { + id: window.location.hostname, + domain: bidderRequest.refererInfo.domain || window.location.hostname, + page: bidderRequest.refererInfo.page || window.location.href, + ref: bidderRequest.refererInfo.ref, + content: siteContent || null + } + } + deepSetValue(request.site, 'publisher.id', bidRequest.params.publisherId); + + if (request.regs) { + if (isGdprApplicable()) { + deepSetValue(request.regs, 'ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + if (bidderRequest.uspConsent !== undefined) { + deepSetValue(request.regs, 'ext.us_privacy', bidderRequest.uspConsent); + } + if (request.regs?.gpp) { + deepSetValue(request.regs, 'ext.gpp', request.regs.gpp); + deepSetValue(request.regs, 'ext.gpp_sid', request.regs.gpp_sid); + } + } else { + request.regs = { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: { + gdpr: isGdprApplicable() ? bidderRequest.gdprConsent.gdprApplies ? 1 : 0 : null, + us_privacy: bidderRequest.uspConsent + } + } + } + + if (request.device) { + if (bidRequest.params.app) { + if (!deepAccess(request.device, 'geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(request.device, 'geo', geo); + } + if (!deepAccess(request.device, 'ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(request.device, 'ifa', ifa); + } + } + } else { + request.device = { + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + ua: navigator.userAgent, + dnt: getDNT() ? 1 : 0, + h: screen.height, + w: screen.width + } + if (!deepAccess(request.device, 'geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(request.device, 'geo', geo); + } + if (!deepAccess(request.device, 'ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(request.device, 'ifa', ifa); + } + } + + request.source = { + ext: { + schain: bidRequest.schain + } + }; + request.ext = { + client: SMAATO_CLIENT + } + return request; + }, + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'tagid', bidRequest.params.adbreakId || bidRequest.params.adspaceId); + if (imp.bidfloorcur && imp.bidfloorcur !== CURRENCY) { + delete imp.bidfloor; + delete imp.bidfloorcur; + } + return imp; + }, + + overrides: { + imp: { + banner(orig, imp, bidRequest, context) { + const mediaType = context.mediaType; + + if (mediaType === BANNER) { + imp.bidfloor = getBidFloor(bidRequest, BANNER, getAdUnitSizes(bidRequest)); + } + + orig(imp, bidRequest, context); + }, + + video(orig, imp, bidRequest, context) { + const mediaType = context.mediaType; + if (mediaType === VIDEO) { + const videoParams = bidRequest.mediaTypes[VIDEO]; + imp.bidfloor = getBidFloor(bidRequest, VIDEO, videoParams.playerSize); + if (videoParams.context !== ADPOD) { + deepSetValue(imp, 'video.ext', { + rewarded: videoParams.ext && videoParams.ext.rewarded ? videoParams.ext.rewarded : 0 + }) + } + } + + orig(imp, bidRequest, context); + }, + + native(orig, imp, bidRequest, context) { + const mediaType = context.mediaType; + + if (mediaType === NATIVE) { + imp.bidfloor = getBidFloor(bidRequest, NATIVE, getNativeMainImageSize(bidRequest.nativeOrtbRequest)); + } + + orig(imp, bidRequest, context); + } + }, + } +}); + const createImgAd = (adm) => { const image = JSON.parse(adm).image; @@ -346,65 +410,6 @@ const createNativeAd = (adm) => { } }; -function createBannerImp(bidRequest) { - const adUnitSizes = getAdUnitSizes(bidRequest); - const sizes = adUnitSizes.map((size) => ({w: size[0], h: size[1]})); - return { - imp: [{ - id: bidRequest.bidId, - tagid: deepAccess(bidRequest, 'params.adspaceId'), - bidfloor: getBidFloor(bidRequest, BANNER, adUnitSizes), - instl: deepAccess(bidRequest.ortb2Imp, 'instl'), - banner: { - w: sizes[0].w, - h: sizes[0].h, - format: sizes - } - }] - }; -} - -function createVideoImp(bidRequest, videoMediaType) { - return { - imp: [{ - id: bidRequest.bidId, - tagid: deepAccess(bidRequest, 'params.adspaceId'), - bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), - instl: deepAccess(bidRequest.ortb2Imp, 'instl'), - video: { - mimes: videoMediaType.mimes, - minduration: videoMediaType.minduration, - startdelay: videoMediaType.startdelay, - linearity: videoMediaType.linearity, - w: videoMediaType.playerSize[0][0], - h: videoMediaType.playerSize[0][1], - maxduration: videoMediaType.maxduration, - skip: videoMediaType.skip, - protocols: videoMediaType.protocols, - ext: { - rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 - }, - skipmin: videoMediaType.skipmin, - api: videoMediaType.api - } - }] - }; -} - -function createNativeImp(bidRequest, nativeRequest) { - return { - imp: [{ - id: bidRequest.bidId, - tagid: deepAccess(bidRequest, 'params.adspaceId'), - bidfloor: getBidFloor(bidRequest, NATIVE, getNativeMainImageSize(nativeRequest)), - native: { - request: JSON.stringify(nativeRequest), - ver: '1.2' - } - }] - }; -} - function getNativeMainImageSize(nativeRequest) { const mainImage = find(nativeRequest.assets, asset => asset.hasOwnProperty('img') && asset.img.type === NATIVE_IMAGE_TYPES.MAIN) if (mainImage) { @@ -418,30 +423,12 @@ function getNativeMainImageSize(nativeRequest) { return [] } -function createAdPodImp(bidRequest, videoMediaType) { - const tagid = deepAccess(bidRequest, 'params.adbreakId') +function createAdPodImp(imp, videoMediaType) { const bce = config.getConfig('adpod.brandCategoryExclusion') - let imp = { - id: bidRequest.bidId, - tagid: tagid, - bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), - instl: deepAccess(bidRequest.ortb2Imp, 'instl'), - video: { - w: videoMediaType.playerSize[0][0], - h: videoMediaType.playerSize[0][1], - mimes: videoMediaType.mimes, - startdelay: videoMediaType.startdelay, - linearity: videoMediaType.linearity, - skip: videoMediaType.skip, - protocols: videoMediaType.protocols, - skipmin: videoMediaType.skipmin, - api: videoMediaType.api, - ext: { - context: ADPOD, - brandcategoryexclusion: bce !== undefined && bce - } - } - } + imp.video.ext = { + context: ADPOD, + brandcategoryexclusion: bce !== undefined && bce + }; const numberOfPlacements = getAdPodNumberOfPlacements(videoMediaType) let imps = fill(imp, numberOfPlacements) @@ -471,9 +458,7 @@ function createAdPodImp(bidRequest, videoMediaType) { }); } - return { - imp: imps - } + return imps } function getAdPodNumberOfPlacements(videoMediaType) { @@ -486,7 +471,7 @@ function getAdPodNumberOfPlacements(videoMediaType) { : numberOfPlacements } -const addOptionalAdpodParameters = (request, videoMediaType) => { +const addOptionalAdpodParameters = (videoMediaType) => { const content = {} if (videoMediaType.tvSeriesName) { @@ -509,7 +494,7 @@ const addOptionalAdpodParameters = (request, videoMediaType) => { } if (!isEmpty(content)) { - request.site.content = content + return content } } diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 185dee2430f..28fd06c4b8d 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -1,7 +1,16 @@ import {spec} from 'modules/smaatoBidAdapter.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; -import {createEidsArray} from 'modules/userId/eids.js'; + +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; @@ -112,14 +121,13 @@ describe('smaatoBidAdapterTest', () => { describe('buildRequests', () => { const BANNER_OPENRTB_IMP = { - w: 300, - h: 50, format: [ { h: 50, w: 300 } - ] + ], + topframe: 0, } describe('common', () => { @@ -145,13 +153,6 @@ describe('smaatoBidAdapterTest', () => { expect(req.at).to.be.equal(1); }) - it('currency is US dollar', () => { - const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.cur).to.be.deep.equal(['USD']); - }) - it('can override endpoint', () => { const overridenEndpoint = 'https://prebid/bidder'; const updatedBidRequest = utils.deepClone(singleBannerBidRequest); @@ -178,7 +179,7 @@ describe('smaatoBidAdapterTest', () => { it('sends bidfloor when configured', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function(arg) { + singleBannerBidRequestWithFloor.getFloor = function (arg) { if (arg.currency === 'USD' && arg.mediaType === 'banner' && JSON.stringify(arg.size) === JSON.stringify([300, 50])) { @@ -202,7 +203,7 @@ describe('smaatoBidAdapterTest', () => { } } }); - singleBannerMultipleSizesBidRequestWithFloor.getFloor = function(arg) { + singleBannerMultipleSizesBidRequestWithFloor.getFloor = function (arg) { if (arg.size === '*') { return { currency: 'USD', @@ -228,7 +229,7 @@ describe('smaatoBidAdapterTest', () => { it('sends undefined bidfloor when invalid', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function() { + singleBannerBidRequestWithFloor.getFloor = function () { return undefined; } const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); @@ -239,7 +240,7 @@ describe('smaatoBidAdapterTest', () => { it('sends undefined bidfloor when not a number', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function() { + singleBannerBidRequestWithFloor.getFloor = function () { return { currency: 'USD', } @@ -252,7 +253,7 @@ describe('smaatoBidAdapterTest', () => { it('sends undefined bidfloor when wrong currency', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function() { + singleBannerBidRequestWithFloor.getFloor = function () { return { currency: 'EUR', floor: 0.123 @@ -275,6 +276,58 @@ describe('smaatoBidAdapterTest', () => { expect(req.site.publisher.id).to.equal('publisherId'); }) + it('sends correct site from ortb2', () => { + const domain = 'domain'; + const page = 'page'; + const ref = 'ref'; + const ortb2 = { + site: { + name: 'example', + domain: domain, + page: page, + ref: ref + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.id).to.exist.and.to.be.a('string'); + expect(req.site.domain).to.equal(domain); + expect(req.site.page).to.equal(page); + expect(req.site.ref).to.equal(ref); + expect(req.site.publisher.id).to.equal('publisherId'); + }) + + it('sends correct device from ortb2', () => { + const language = 'language' + const ua = 'ua' + const sua = 'sua' + const dnt = 1 + const w = 2 + const h = 3 + const ortb2 = { + device: { + language: language, + ua: ua, + sua: sua, + dnt: dnt, + w: w, + h: h + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.language).to.equal(language); + expect(req.device.ua).to.equal(ua); + expect(req.device.sua).to.equal(sua); + expect(req.device.dnt).to.equal(dnt); + expect(req.device.w).to.equal(w); + expect(req.device.h).to.equal(h); + }) + it('sends gdpr applies if exists', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -283,6 +336,19 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.consent).to.equal(CONSENT_STRING); }); + it('sends correct coppa from ortb2', () => { + const ortb2 = { + regs: { + coppa: 1 + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.coppa).to.equal(1); + }) + it('sends no gdpr applies if no gdpr exists', () => { const reqs = spec.buildRequests([singleBannerBidRequest], MINIMAL_BIDDER_REQUEST); @@ -321,7 +387,7 @@ describe('smaatoBidAdapterTest', () => { }); it('sends instl if instl exists', () => { - const instl = { instl: 1 }; + const instl = {instl: 1}; const bidRequestWithInstl = Object.assign({}, singleBannerBidRequest, {ortb2Imp: instl}); const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); @@ -381,6 +447,16 @@ describe('smaatoBidAdapterTest', () => { expect(req.device.geo.lon).to.equal(9.9872); }); + it('sends user first party data when user is not defined', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.user.gender).be.undefined; + expect(req.user.yob).to.be.undefined; + expect(req.user.keywords).to.be.undefined; + expect(req.user.ext.consent).to.equal(CONSENT_STRING); + }); + it('has no user ids', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -456,531 +532,534 @@ describe('smaatoBidAdapterTest', () => { bidderWinsCount: 0 }; - it('sends correct video imps', () => { - const reqs = spec.buildRequests([singleVideoBidRequest], defaultBidderRequest); + if (FEATURES.VIDEO) { + it('sends correct video imps', () => { + const reqs = spec.buildRequests([singleVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].id).to.be.equal('bidId'); - expect(req.imp[0].tagid).to.be.equal('adspaceId'); - expect(req.imp[0].bidfloor).to.be.undefined; - expect(req.imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].id).to.be.equal('bidId'); + expect(req.imp[0].tagid).to.be.equal('adspaceId'); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); + }); - it('sends bidfloor when configured', () => { - const singleVideoBidRequestWithFloor = Object.assign({}, singleVideoBidRequest); - singleVideoBidRequestWithFloor.getFloor = function(arg) { - if (arg.currency === 'USD' && - arg.mediaType === 'video' && - JSON.stringify(arg.size) === JSON.stringify([768, 1024])) { - return { - currency: 'USD', - floor: 0.456 + it('sends bidfloor when configured', () => { + const singleVideoBidRequestWithFloor = Object.assign({}, singleVideoBidRequest); + singleVideoBidRequestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'video' && + JSON.stringify(arg.size) === JSON.stringify([768, 1024])) { + return { + currency: 'USD', + floor: 0.456 + } } } - } - const reqs = spec.buildRequests([singleVideoBidRequestWithFloor], defaultBidderRequest); + const reqs = spec.buildRequests([singleVideoBidRequestWithFloor], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].bidfloor).to.be.equal(0.456); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.456); + }); - it('sends instl if instl exists', () => { - const instl = { instl: 1 }; - const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); + it('sends instl if instl exists', () => { + const instl = {instl: 1}; + const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); - const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].instl).to.equal(1); - }); - - it('splits multi format bid requests', () => { - const combinedBannerAndVideoBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - mediaTypes: { - banner: BANNER_PREBID_MEDIATYPE, - video: VIDEO_OUTSTREAM_PREBID_MEDIATYPE - }, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - sizes: [[300, 50]], - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }; - - const reqs = spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest); - - expect(reqs).to.have.length(2); - expect(JSON.parse(reqs[0].data).imp[0].banner).to.deep.equal(BANNER_OPENRTB_IMP); - expect(JSON.parse(reqs[0].data).imp[0].video).to.not.exist; - expect(JSON.parse(reqs[1].data).imp[0].banner).to.not.exist; - expect(JSON.parse(reqs[1].data).imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); - describe('ad pod / long form video', () => { - describe('required parameters with requireExactDuration false', () => { - const ADBREAK_ID = 'adbreakId'; - const ADPOD = 'adpod'; - const BID_ID = '4331'; - const W = 640; - const H = 480; - const ADPOD_DURATION = 300; - const DURATION_RANGE = [15, 30]; - const longFormVideoBidRequest = { + it('splits multi format bid requests', () => { + const combinedBannerAndVideoBidRequest = { + bidder: 'smaato', params: { publisherId: 'publisherId', - adbreakId: ADBREAK_ID, + adspaceId: 'adspaceId' }, mediaTypes: { - video: { - context: ADPOD, - playerSize: [[W, H]], - adPodDurationSec: ADPOD_DURATION, - durationRangeSec: DURATION_RANGE, - requireExactDuration: false - } + banner: BANNER_PREBID_MEDIATYPE, + video: VIDEO_OUTSTREAM_PREBID_MEDIATYPE }, - bidId: BID_ID + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 }; - it('sends required fields', () => { - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.id).to.exist; - expect(req.imp.length).to.be.equal(ADPOD_DURATION / DURATION_RANGE[0]); - expect(req.imp[0].id).to.be.equal(BID_ID); - expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[0].bidfloor).to.be.undefined; - expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[0].video.w).to.be.equal(W); - expect(req.imp[0].video.h).to.be.equal(H); - expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[0].video.sequence).to.be.equal(1); - expect(req.imp[1].id).to.be.equal(BID_ID); - expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[1].bidfloor).to.be.undefined; - expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[1].video.w).to.be.equal(W); - expect(req.imp[1].video.h).to.be.equal(H); - expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[1].video.sequence).to.be.equal(2); - }); + const reqs = spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest); - it('sends instl if instl exists', () => { - const instl = { instl: 1 }; - const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); + expect(reqs).to.have.length(2); + expect(JSON.parse(reqs[0].data).imp[0].banner).to.deep.equal(BANNER_OPENRTB_IMP); + expect(JSON.parse(reqs[0].data).imp[0].video).to.not.exist; + expect(JSON.parse(reqs[1].data).imp[0].banner).to.not.exist; + expect(JSON.parse(reqs[1].data).imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); + }); - const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + describe('ad pod / long form video', () => { + describe('required parameters with requireExactDuration false', () => { + const ADBREAK_ID = 'adbreakId'; + const ADPOD = 'adpod'; + const BID_ID = '4331'; + const W = 640; + const H = 480; + const ADPOD_DURATION = 300; + const DURATION_RANGE = [15, 30]; + const longFormVideoBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: ADBREAK_ID, + }, + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: false + } + }, + bidId: BID_ID + }; + + it('sends required fields', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.id).to.exist; + expect(req.imp.length).to.be.equal(ADPOD_DURATION / DURATION_RANGE[0]); + expect(req.imp[0].id).to.be.equal(BID_ID); + expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[0].video.w).to.be.equal(W); + expect(req.imp[0].video.h).to.be.equal(H); + expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[0].video.sequence).to.be.equal(1); + expect(req.imp[1].id).to.be.equal(BID_ID); + expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[1].bidfloor).to.be.undefined; + expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[1].video.w).to.be.equal(W); + expect(req.imp[1].video.h).to.be.equal(H); + expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.sequence).to.be.equal(2); + }); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].instl).to.equal(1); - expect(req.imp[1].instl).to.equal(1); - }); + it('sends instl if instl exists', () => { + const instl = {instl: 1}; + const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); - it('sends bidfloor when configured', () => { - const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); - longFormVideoBidRequestWithFloor.getFloor = function(arg) { - if (arg.currency === 'USD' && - arg.mediaType === 'video' && - JSON.stringify(arg.size) === JSON.stringify([640, 480])) { - return { - currency: 'USD', - floor: 0.789 + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + expect(req.imp[1].instl).to.equal(1); + }); + + it('sends bidfloor when configured', () => { + const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); + longFormVideoBidRequestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'video' && + JSON.stringify(arg.size) === JSON.stringify([640, 480])) { + return { + currency: 'USD', + floor: 0.789 + } } } - } - const reqs = spec.buildRequests([longFormVideoBidRequestWithFloor], defaultBidderRequest); + const reqs = spec.buildRequests([longFormVideoBidRequestWithFloor], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].bidfloor).to.be.equal(0.789); - expect(req.imp[1].bidfloor).to.be.equal(0.789); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.789); + expect(req.imp[1].bidfloor).to.be.equal(0.789); + }); - it('sends brand category exclusion as true when config is set to true', () => { - config.setConfig({adpod: {brandCategoryExclusion: true}}); + it('sends brand category exclusion as true when config is set to true', () => { + config.setConfig({adpod: {brandCategoryExclusion: true}}); - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(true); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(true); + }); - it('sends brand category exclusion as false when config is set to false', () => { - config.setConfig({adpod: {brandCategoryExclusion: false}}); + it('sends brand category exclusion as false when config is set to false', () => { + config.setConfig({adpod: {brandCategoryExclusion: false}}); - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + }); - it('sends brand category exclusion as false when config is not set', () => { - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + it('sends brand category exclusion as false when config is not set', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + }); }); - }); - describe('required parameters with requireExactDuration true', () => { - const ADBREAK_ID = 'adbreakId'; - const ADPOD = 'adpod'; - const BID_ID = '4331'; - const W = 640; - const H = 480; - const ADPOD_DURATION = 5; - const DURATION_RANGE = [5, 15, 25]; - const longFormVideoBidRequest = { - params: { - publisherId: 'publisherId', - adbreakId: ADBREAK_ID, - }, - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[W, H]], - adPodDurationSec: ADPOD_DURATION, - durationRangeSec: DURATION_RANGE, - requireExactDuration: true - } - }, - bidId: BID_ID - }; - - it('sends required fields', () => { - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.id).to.exist; - expect(req.imp.length).to.be.equal(DURATION_RANGE.length); - expect(req.imp[0].id).to.be.equal(BID_ID); - expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[0].video.w).to.be.equal(W); - expect(req.imp[0].video.h).to.be.equal(H); - expect(req.imp[0].video.minduration).to.be.equal(DURATION_RANGE[0]); - expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[0]); - expect(req.imp[0].video.sequence).to.be.equal(1); - expect(req.imp[1].id).to.be.equal(BID_ID); - expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[1].video.w).to.be.equal(W); - expect(req.imp[1].video.h).to.be.equal(H); - expect(req.imp[1].video.minduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[1].video.sequence).to.be.equal(2); - expect(req.imp[2].id).to.be.equal(BID_ID); - expect(req.imp[2].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[2].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[2].video.w).to.be.equal(W); - expect(req.imp[2].video.h).to.be.equal(H); - expect(req.imp[2].video.minduration).to.be.equal(DURATION_RANGE[2]); - expect(req.imp[2].video.maxduration).to.be.equal(DURATION_RANGE[2]); - expect(req.imp[2].video.sequence).to.be.equal(3); + describe('required parameters with requireExactDuration true', () => { + const ADBREAK_ID = 'adbreakId'; + const ADPOD = 'adpod'; + const BID_ID = '4331'; + const W = 640; + const H = 480; + const ADPOD_DURATION = 5; + const DURATION_RANGE = [5, 15, 25]; + const longFormVideoBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: ADBREAK_ID, + }, + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: true + } + }, + bidId: BID_ID + }; + + it('sends required fields', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.id).to.exist; + expect(req.imp.length).to.be.equal(DURATION_RANGE.length); + expect(req.imp[0].id).to.be.equal(BID_ID); + expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[0].video.w).to.be.equal(W); + expect(req.imp[0].video.h).to.be.equal(H); + expect(req.imp[0].video.minduration).to.be.equal(DURATION_RANGE[0]); + expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[0]); + expect(req.imp[0].video.sequence).to.be.equal(1); + expect(req.imp[1].id).to.be.equal(BID_ID); + expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[1].video.w).to.be.equal(W); + expect(req.imp[1].video.h).to.be.equal(H); + expect(req.imp[1].video.minduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.sequence).to.be.equal(2); + expect(req.imp[2].id).to.be.equal(BID_ID); + expect(req.imp[2].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[2].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[2].video.w).to.be.equal(W); + expect(req.imp[2].video.h).to.be.equal(H); + expect(req.imp[2].video.minduration).to.be.equal(DURATION_RANGE[2]); + expect(req.imp[2].video.maxduration).to.be.equal(DURATION_RANGE[2]); + expect(req.imp[2].video.sequence).to.be.equal(3); + }); }); - }); - - describe('forwarding of optional parameters', () => { - const MIMES = ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v']; - const STARTDELAY = 0; - const LINEARITY = 1; - const SKIP = 1; - const PROTOCOLS = [7]; - const SKIPMIN = 5; - const API = [7]; - const validBasicAdpodBidRequest = { - params: { - publisherId: 'publisherId', - adbreakId: 'adbreakId', - }, - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - mimes: MIMES, - startdelay: STARTDELAY, - linearity: LINEARITY, - skip: SKIP, - protocols: PROTOCOLS, - skipmin: SKIPMIN, - api: API - } - }, - bidId: 'bidId' - }; - it('sends general video fields when they are present', () => { - const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.mimes).to.eql(MIMES); - expect(req.imp[0].video.startdelay).to.be.equal(STARTDELAY); - expect(req.imp[0].video.linearity).to.be.equal(LINEARITY); - expect(req.imp[0].video.skip).to.be.equal(SKIP); - expect(req.imp[0].video.protocols).to.eql(PROTOCOLS); - expect(req.imp[0].video.skipmin).to.be.equal(SKIPMIN); - expect(req.imp[0].video.api).to.eql(API); - }); + describe('forwarding of optional parameters', () => { + const MIMES = ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v']; + const STARTDELAY = 0; + const LINEARITY = 1; + const SKIP = 1; + const PROTOCOLS = [7]; + const SKIPMIN = 5; + const API = [7]; + const validBasicAdpodBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: 'adbreakId', + }, + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + mimes: MIMES, + startdelay: STARTDELAY, + linearity: LINEARITY, + skip: SKIP, + protocols: PROTOCOLS, + skipmin: SKIPMIN, + api: API + } + }, + bidId: 'bidId' + }; + + it('sends general video fields when they are present', () => { + const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.mimes).to.eql(MIMES); + expect(req.imp[0].video.startdelay).to.be.equal(STARTDELAY); + expect(req.imp[0].video.linearity).to.be.equal(LINEARITY); + expect(req.imp[0].video.skip).to.be.equal(SKIP); + expect(req.imp[0].video.protocols).to.eql(PROTOCOLS); + expect(req.imp[0].video.skipmin).to.be.equal(SKIPMIN); + expect(req.imp[0].video.api).to.eql(API); + }); - it('sends series name when parameter is present', () => { - const SERIES_NAME = 'foo' - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvSeriesName = SERIES_NAME; + it('sends series name when parameter is present', () => { + const SERIES_NAME = 'foo' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvSeriesName = SERIES_NAME; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.series).to.be.equal(SERIES_NAME); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.series).to.be.equal(SERIES_NAME); + }); - it('sends episode name when parameter is present', () => { - const EPISODE_NAME = 'foo' - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvEpisodeName = EPISODE_NAME; + it('sends episode name when parameter is present', () => { + const EPISODE_NAME = 'foo' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvEpisodeName = EPISODE_NAME; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.title).to.be.equal(EPISODE_NAME); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.title).to.be.equal(EPISODE_NAME); + }); - it('sends season number as string when parameter is present', () => { - const SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST = 42 - const SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST = '42' - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvSeasonNumber = SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST; + it('sends season number as string when parameter is present', () => { + const SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST = 42 + const SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST = '42' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvSeasonNumber = SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.season).to.be.equal(SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.season).to.be.equal(SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST); + }); - it('sends episode number when parameter is present', () => { - const EPISODE_NUMBER = 42 - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvEpisodeNumber = EPISODE_NUMBER; + it('sends episode number when parameter is present', () => { + const EPISODE_NUMBER = 42 + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvEpisodeNumber = EPISODE_NUMBER; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.episode).to.be.equal(EPISODE_NUMBER); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.episode).to.be.equal(EPISODE_NUMBER); + }); - it('sends content length when parameter is present', () => { - const LENGTH = 42 - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.contentLengthSec = LENGTH; + it('sends content length when parameter is present', () => { + const LENGTH = 42 + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentLengthSec = LENGTH; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.len).to.be.equal(LENGTH); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.len).to.be.equal(LENGTH); + }); - it('sends livestream as 1 when content mode parameter is live', () => { - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.contentMode = 'live'; + it('sends livestream as 1 when content mode parameter is live', () => { + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentMode = 'live'; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.livestream).to.be.equal(1); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.livestream).to.be.equal(1); + }); - it('sends livestream as 0 when content mode parameter is on-demand', () => { - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.contentMode = 'on-demand'; + it('sends livestream as 0 when content mode parameter is on-demand', () => { + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentMode = 'on-demand'; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.livestream).to.be.equal(0); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.livestream).to.be.equal(0); + }); - it("doesn't send any optional parameters when none are present", () => { - const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); + it('doesn\'t send any optional parameters when none are present', () => { + const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.requireExactDuration).to.not.exist; - expect(req.site.content).to.not.exist; + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.requireExactDuration).to.not.exist; + expect(req.site.content).to.not.exist; + }); }); }); - }); + } }); - describe('buildRequests for native imps', () => { - const NATIVE_OPENRTB_REQUEST = { - ver: '1.2', - assets: [ - { - id: 4, - required: 1, - img: { - type: 3, - w: 150, - h: 50, - } - }, - { - id: 2, - required: 1, - img: { - type: 2, - w: 50, - h: 50 - } - }, - { - id: 0, - required: 1, - title: { - len: 80 - } - }, - { - id: 1, - required: 1, - data: { - type: 1 - } - }, - { - id: 3, - required: 1, - data: { - type: 2 - } - }, - { - id: 5, - required: 1, - data: { - type: 3 - } - }, - { - id: 6, - required: 1, - data: { - type: 4 - } - }, - { - id: 7, - required: 1, - data: { - type: 5 - } - }, - { - id: 8, - required: 1, - data: { - type: 6 - } - }, - { - id: 9, - required: 1, - data: { - type: 7 - } - }, - { - id: 10, - required: 0, - data: { - type: 8 - } - }, - { - id: 11, - required: 1, - data: { - type: 9 - } - }, - { - id: 12, - require: 0, - data: { - type: 10 - } - }, - { - id: 13, - required: 0, - data: { - type: 11 - } - }, - { - id: 14, - required: 1, - data: { - type: 12 + if (FEATURES.NATIVE) { + describe('buildRequests for native imps', () => { + const NATIVE_OPENRTB_REQUEST = { + ver: '1.2', + assets: [ + { + id: 4, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }, + { + id: 2, + required: 1, + img: { + type: 2, + w: 50, + h: 50 + } + }, + { + id: 0, + required: 1, + title: { + len: 80 + } + }, + { + id: 1, + required: 1, + data: { + type: 1 + } + }, + { + id: 3, + required: 1, + data: { + type: 2 + } + }, + { + id: 5, + required: 1, + data: { + type: 3 + } + }, + { + id: 6, + required: 1, + data: { + type: 4 + } + }, + { + id: 7, + required: 1, + data: { + type: 5 + } + }, + { + id: 8, + required: 1, + data: { + type: 6 + } + }, + { + id: 9, + required: 1, + data: { + type: 7 + } + }, + { + id: 10, + required: 0, + data: { + type: 8 + } + }, + { + id: 11, + required: 1, + data: { + type: 9 + } + }, + { + id: 12, + require: 0, + data: { + type: 10 + } + }, + { + id: 13, + required: 0, + data: { + type: 11 + } + }, + { + id: 14, + required: 1, + data: { + type: 12 + } } - } - ] - }; + ] + }; - const singleNativeBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - nativeOrtbRequest: NATIVE_OPENRTB_REQUEST, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }; + const singleNativeBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + nativeOrtbRequest: NATIVE_OPENRTB_REQUEST, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; - it('sends correct native imps', () => { - const reqs = spec.buildRequests([singleNativeBidRequest], defaultBidderRequest); + it('sends correct native imps', () => { + const reqs = spec.buildRequests([singleNativeBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].id).to.be.equal('bidId'); - expect(req.imp[0].tagid).to.be.equal('adspaceId'); - expect(req.imp[0].bidfloor).to.be.undefined; - expect(req.imp[0].native.request).to.deep.equal(JSON.stringify(NATIVE_OPENRTB_REQUEST)); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].id).to.be.equal('bidId'); + expect(req.imp[0].tagid).to.be.equal('adspaceId'); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].native.request).to.deep.equal(JSON.stringify(NATIVE_OPENRTB_REQUEST)); + }); - it('sends bidfloor when configured', () => { - const singleNativeBidRequestWithFloor = Object.assign({}, singleNativeBidRequest); - singleNativeBidRequestWithFloor.getFloor = function(arg) { - if (arg.currency === 'USD' && - arg.mediaType === 'native' && - JSON.stringify(arg.size) === JSON.stringify([150, 50])) { - return { - currency: 'USD', - floor: 0.123 + it('sends bidfloor when configured', () => { + const singleNativeBidRequestWithFloor = Object.assign({}, singleNativeBidRequest); + singleNativeBidRequestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'native' && + JSON.stringify(arg.size) === JSON.stringify([150, 50])) { + return { + currency: 'USD', + floor: 0.123 + } } } - } - const reqs = spec.buildRequests([singleNativeBidRequestWithFloor], defaultBidderRequest); + const reqs = spec.buildRequests([singleNativeBidRequestWithFloor], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].bidfloor).to.be.equal(0.123); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.123); + }); }); - }); - + } describe('in-app requests', () => { const LOCATION = { lat: 33.3, @@ -1263,7 +1342,7 @@ describe('smaatoBidAdapterTest', () => { adm = ''; break; case ADTYPE_NATIVE: - adm = JSON.stringify({ native: NATIVE_RESPONSE }) + adm = JSON.stringify({native: NATIVE_RESPONSE}) break; default: throw Error('Invalid AdType'); From 3a1dff0f635139eca591585ebcde178271ae7265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kowalski?= <112544015+pkowalski-id5@users.noreply.github.com> Date: Mon, 6 May 2024 20:16:11 +0200 Subject: [PATCH 0009/1097] ID5 ID module: config call as bounce (#11424) --- modules/id5IdSystem.js | 44 +++--- test/spec/modules/id5IdSystem_spec.js | 203 ++++++++++++++------------ 2 files changed, 131 insertions(+), 116 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 42f0044edc3..e7a7ec7f037 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -148,7 +148,7 @@ export const id5IdSubmodule = { id5id: { uid: universalUid, ext: ext - }, + } }; if (isPlainObject(ext.euid)) { @@ -156,7 +156,7 @@ export const id5IdSubmodule = { uid: ext.euid.uids[0].id, source: ext.euid.source, ext: {provider: ID5_DOMAIN} - } + }; } const abTestingResult = deepAccess(value, 'ab_testing.result'); @@ -196,7 +196,7 @@ export const id5IdSubmodule = { } if (!hasWriteConsentToLocalStorage(consentData)) { - logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.') + logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.'); return undefined; } @@ -204,7 +204,7 @@ export const id5IdSubmodule = { const fetchFlow = new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData(), gppDataHandler.getConsentData()); fetchFlow.execute() .then(response => { - cbFunction(response) + cbFunction(response); }) .catch(error => { logError(LOG_PREFIX + 'getId fetch encountered an error', error); @@ -227,7 +227,7 @@ export const id5IdSubmodule = { */ extendId(config, consentData, cacheIdObj) { if (!hasWriteConsentToLocalStorage(consentData)) { - logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.') + logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.'); return cacheIdObj; } @@ -239,12 +239,12 @@ export const id5IdSubmodule = { }, eids: { 'id5id': { - getValue: function(data) { - return data.uid + getValue: function (data) { + return data.uid; }, source: ID5_DOMAIN, atype: 1, - getUidExt: function(data) { + getUidExt: function (data) { if (data.ext) { return data.ext; } @@ -264,16 +264,16 @@ export const id5IdSubmodule = { } } } - }, + } }; export class IdFetchFlow { constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData, gppData) { - this.submoduleConfig = submoduleConfig - this.gdprConsentData = gdprConsentData - this.cacheIdObj = cacheIdObj - this.usPrivacyData = usPrivacyData - this.gppData = gppData + this.submoduleConfig = submoduleConfig; + this.gdprConsentData = gdprConsentData; + this.cacheIdObj = cacheIdObj; + this.usPrivacyData = usPrivacyData; + this.gppData = gppData; } /** @@ -324,7 +324,11 @@ export class IdFetchFlow { let url = this.submoduleConfig.params.configUrl || ID5_API_CONFIG_URL; // override for debug/test purposes only const response = await fetch(url, { method: 'POST', - body: JSON.stringify(this.submoduleConfig) + body: JSON.stringify({ + ...this.submoduleConfig, + bounce: true + }), + credentials: 'include' }); if (!response.ok) { throw new Error('Error while calling config endpoint: ', response); @@ -342,7 +346,7 @@ export class IdFetchFlow { const extensionsUrl = extensionsCallConfig.url; const method = extensionsCallConfig.method || 'GET'; const body = method === 'GET' ? undefined : JSON.stringify(extensionsCallConfig.body || {}); - const response = await fetch(extensionsUrl, { method, body }); + const response = await fetch(extensionsUrl, {method, body}); if (!response.ok) { throw new Error('Error while calling extensions endpoint: ', response); } @@ -360,7 +364,7 @@ export class IdFetchFlow { ...additionalData, extensions: extensionsData }); - const response = await fetch(fetchUrl, { method: 'POST', body, credentials: 'include' }); + const response = await fetch(fetchUrl, {method: 'POST', body, credentials: 'include'}); if (!response.ok) { throw new Error('Error while calling fetch endpoint: ', response); } @@ -456,7 +460,7 @@ function validateConfig(config) { return false; } - const partner = config.params.partner + const partner = config.params.partner; if (typeof partner === 'string' || partner instanceof String) { let parsedPartnerId = parseInt(partner); if (isNaN(parsedPartnerId) || parsedPartnerId < 0) { @@ -566,8 +570,8 @@ export function storeInLocalStorage(key, value, expDays) { */ function hasWriteConsentToLocalStorage(consentData) { const hasGdpr = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`) - const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`) + const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`); + const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`); if (hasGdpr && (!localstorageConsent || !id5VendorConsent)) { return false; } diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index fd5d24295f5..1da862cc007 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -8,7 +8,7 @@ import { } from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; import * as events from '../../../src/events.js'; -import { EVENTS } from '../../../src/constants.js'; +import {EVENTS} from '../../../src/constants.js'; import * as utils from '../../../src/utils.js'; import {uspDataHandler, gppDataHandler} from '../../../src/adapterManager.js'; import '../../../src/prebid.js'; @@ -81,11 +81,11 @@ describe('ID5 ID System', function () { 131: true } } - } + }; const HEADERS_CONTENT_TYPE_JSON = { 'Content-Type': 'application/json' - } + }; function getId5FetchConfig(partner = ID5_TEST_PARTNER_ID, storageName = id5System.ID5_STORAGE_NAME, storageType = 'html5') { return { @@ -98,7 +98,7 @@ describe('ID5 ID System', function () { type: storageType, expires: 90 } - } + }; } function getId5ValueConfig(value) { @@ -109,7 +109,7 @@ describe('ID5 ID System', function () { uid: value } } - } + }; } function getUserSyncConfig(userIds) { @@ -118,7 +118,7 @@ describe('ID5 ID System', function () { userIds: userIds, syncDelay: 0 } - } + }; } function getFetchLocalStorageConfig() { @@ -167,6 +167,7 @@ describe('ID5 ID System', function () { const configRequest = await this.expectFirstRequest(); expect(configRequest.url).is.eq(ID5_API_CONFIG_URL); expect(configRequest.method).is.eq('POST'); + expect(configRequest.withCredentials).is.eq(true); return configRequest; } @@ -184,7 +185,7 @@ describe('ID5 ID System', function () { } async #waitOnRequest(index) { - const server = this.server + const server = this.server; return new GreedyPromise((resolve) => { const waitForCondition = () => { if (server.requests && server.requests.length > index) { @@ -214,31 +215,37 @@ describe('ID5 ID System', function () { expect(id5System.id5IdSubmodule.getId({})).is.eq(undefined); // valid params, invalid id5System.storage - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: {} })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: { name: '' } })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: { type: '' } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}, storage: {}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}, storage: {name: ''}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}, storage: {type: ''}})).to.be.eq(undefined); // valid id5System.storage, invalid params - expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { } })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({storage: {name: 'name', type: 'html5'}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({storage: {name: 'name', type: 'html5'}, params: {}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ + storage: {name: 'name', type: 'html5'}, + params: {partner: 'abc'} + })).to.be.eq(undefined); }); it('should warn with non-recommended id5System.storage params', function () { const logWarnStub = sinon.stub(utils, 'logWarn'); - id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); + id5System.id5IdSubmodule.getId({storage: {name: 'name', type: 'html5'}, params: {partner: 123}}); expect(logWarnStub.calledOnce).to.be.true; logWarnStub.restore(); - id5System.id5IdSubmodule.getId({ storage: { name: id5System.ID5_STORAGE_NAME, type: 'cookie', }, params: { partner: 123 } }); + id5System.id5IdSubmodule.getId({ + storage: {name: id5System.ID5_STORAGE_NAME, type: 'cookie'}, + params: {partner: 123} + }); expect(logWarnStub.calledOnce).to.be.true; logWarnStub.restore(); }); }); - describe('Check for valid consent', function() { + describe('Check for valid consent', function () { const dataConsentVals = [ [{purpose: {consents: {1: false}}}, {vendor: {consents: {131: true}}}, ' no purpose consent'], [{purpose: {consents: {1: true}}}, {vendor: {consents: {131: false}}}, ' no vendor consent'], @@ -250,15 +257,15 @@ describe('ID5 ID System', function () { [{purpose: {consents: {1: true}}}, {vendor: {consents: {31: true}}}, ' incorrect vendor consent'] ]; - dataConsentVals.forEach(function([purposeConsent, vendorConsent, caseName]) { - it('should fail with invalid consent because of ' + caseName, function() { + dataConsentVals.forEach(function ([purposeConsent, vendorConsent, caseName]) { + it('should fail with invalid consent because of ' + caseName, function () { const dataConsent = { gdprApplies: true, consentString: 'consentString', vendorData: { purposeConsent, vendorConsent } - } + }; expect(id5System.id5IdSubmodule.getId(config)).is.eq(undefined); expect(id5System.id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); @@ -270,25 +277,25 @@ describe('ID5 ID System', function () { }); describe('Xhr Requests from getId()', function () { - const responseHeader = HEADERS_CONTENT_TYPE_JSON - let gppStub + const responseHeader = HEADERS_CONTENT_TYPE_JSON; + let gppStub; beforeEach(function () { }); afterEach(function () { - uspDataHandler.reset() - gppStub?.restore() + uspDataHandler.reset(); + gppStub?.restore(); }); it('should call the ID5 server and handle a valid response', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const config = getId5FetchConfig(); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(config, undefined, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); expect(fetchRequest.url).to.contain(ID5_ENDPOINT); expect(fetchRequest.withCredentials).is.true; @@ -298,12 +305,12 @@ describe('ID5 ID System', function () { expect(requestBody.o).is.eq('pbjs'); expect(requestBody.pd).is.undefined; expect(requestBody.s).is.undefined; - expect(requestBody.provider).is.undefined + expect(requestBody.provider).is.undefined; expect(requestBody.v).is.eq('$prebid.version$'); expect(requestBody.gdpr).is.eq(0); expect(requestBody.gdpr_consent).is.undefined; expect(requestBody.us_privacy).is.undefined; - expect(requestBody.storage).is.deep.eq(config.storage) + expect(requestBody.storage).is.deep.eq(config.storage); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); @@ -312,17 +319,17 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with gdpr data ', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: true, consentString: 'consentString', vendorData: ALLOWED_ID5_VENDOR_DATA - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); expect(requestBody.gdpr).to.eq(1); @@ -335,19 +342,19 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server without gdpr data when gdpr not applies ', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: false, consentString: 'consentString' - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.gdpr).to.eq(0); - expect(requestBody.gdpr_consent).is.undefined + expect(requestBody.gdpr_consent).is.undefined; fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); @@ -357,18 +364,18 @@ describe('ID5 ID System', function () { it('should call the ID5 server with us privacy consent', async function () { const usPrivacyString = '1YN-'; - uspDataHandler.setConsentData(usPrivacyString) - const xhrServerMock = new XhrServerMock(server) + uspDataHandler.setConsentData(usPrivacyString); + const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: true, consentString: 'consentString', vendorData: ALLOWED_ID5_VENDOR_DATA - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); expect(requestBody.us_privacy).to.eq(usPrivacyString); @@ -380,12 +387,12 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with no signature field when no stored object', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.s).is.undefined; @@ -394,7 +401,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server for config with submodule config object', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.extraParam = { x: 'X', @@ -402,22 +409,25 @@ describe('ID5 ID System', function () { a: 1, b: '3' } - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); const configRequest = await xhrServerMock.expectConfigRequest(); const requestBody = JSON.parse(configRequest.requestBody); - expect(requestBody).is.deep.eq(id5FetchConfig) + expect(requestBody).is.deep.eq({ + ...id5FetchConfig, + bounce: true + }); - const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server for config with partner id being a string', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.partner = '173'; @@ -425,18 +435,18 @@ describe('ID5 ID System', function () { const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); const configRequest = await xhrServerMock.expectConfigRequest(); - const requestBody = JSON.parse(configRequest.requestBody) - expect(requestBody.params.partner).is.eq(173) + const requestBody = JSON.parse(configRequest.requestBody); + expect(requestBody.params.partner).is.eq(173); - const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server for config with overridden url', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5FetchConfig = getId5FetchConfig(); - id5FetchConfig.params.configUrl = 'http://localhost/x/y/z' + id5FetchConfig.params.configUrl = 'http://localhost/x/y/z'; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); @@ -444,13 +454,13 @@ describe('ID5 ID System', function () { const configRequest = await xhrServerMock.expectFirstRequest(); expect(configRequest.url).is.eq('http://localhost/x/y/z'); - const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server with additional data when provided', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); @@ -472,18 +482,18 @@ describe('ID5 ID System', function () { expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); expect(requestBody.o).is.eq('pbjs'); expect(requestBody.v).is.eq('$prebid.version$'); - expect(requestBody.arg1).is.eq('123') + expect(requestBody.arg1).is.eq('123'); expect(requestBody.arg2).is.deep.eq({ x: '1', y: 2 - }) + }); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server with extensions', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); @@ -498,8 +508,8 @@ describe('ID5 ID System', function () { method: 'GET' } }); - expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) - expect(extensionsRequest.method).is.eq('GET') + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT); + expect(extensionsRequest.method).is.eq('GET'); extensionsRequest.respond(200, responseHeader, JSON.stringify({ lb: 'ex' @@ -511,14 +521,14 @@ describe('ID5 ID System', function () { expect(requestBody.v).is.eq('$prebid.version$'); expect(requestBody.extensions).is.deep.eq({ lb: 'ex' - }) + }); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server with extensions fetched using method POST', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); @@ -537,15 +547,15 @@ describe('ID5 ID System', function () { } } }); - expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) - expect(extensionsRequest.method).is.eq('POST') - const extRequestBody = JSON.parse(extensionsRequest.requestBody) + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT); + expect(extensionsRequest.method).is.eq('POST'); + const extRequestBody = JSON.parse(extensionsRequest.requestBody); expect(extRequestBody).is.deep.eq({ x: '1', y: 2 - }) + }); extensionsRequest.respond(200, responseHeader, JSON.stringify({ - lb: 'post', + lb: 'post' })); const fetchRequest = await xhrServerMock.expectNextRequest(); @@ -562,12 +572,12 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with signature field from stored object', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); @@ -576,7 +586,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with pd field when pd config is set', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; const id5Config = getId5FetchConfig(); @@ -594,7 +604,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with no pd field when pd config is not set', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); id5Config.params.pd = undefined; @@ -610,7 +620,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with nb=1 when no stored value exists and reset after', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const TEST_PARTNER_ID = 189; coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(TEST_PARTNER_ID)); @@ -647,9 +657,9 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with ab_testing object when abTesting is turned on', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); - id5Config.params.abTesting = {enabled: true, controlGroupPct: 0.234} + id5Config.params.abTesting = {enabled: true, controlGroupPct: 0.234}; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); @@ -664,9 +674,9 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server without ab_testing object when abTesting is turned off', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); - id5Config.params.abTesting = {enabled: false, controlGroupPct: 0.55} + id5Config.params.abTesting = {enabled: false, controlGroupPct: 0.55}; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); @@ -680,7 +690,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server without ab_testing when when abTesting is not set', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); // Trigger the fetch but we await on it later @@ -695,7 +705,7 @@ describe('ID5 ID System', function () { }); it('should store the privacy object from the ID5 server response', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); @@ -733,7 +743,7 @@ describe('ID5 ID System', function () { expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.null; }); - describe('with successful external module call', function() { + describe('with successful external module call', function () { const MOCK_RESPONSE = { ...ID5_JSON_RESPONSE, universal_uid: 'my_mock_reponse' @@ -743,7 +753,8 @@ describe('ID5 ID System', function () { beforeEach(() => { window.id5Prebid = { integration: { - fetchId5Id: function() {} + fetchId5Id: function () { + } } }; mockId5ExternalModule = sinon.stub(window.id5Prebid.integration, 'fetchId5Id') @@ -755,7 +766,7 @@ describe('ID5 ID System', function () { delete window.id5Prebid; }); - it('should retrieve the response from the external module interface', async function() { + it('should retrieve the response from the external module interface', async function () { const xhrServerMock = new XhrServerMock(server); const config = getId5FetchConfig(); config.params.externalModuleUrl = 'https://test-me.test'; @@ -772,8 +783,8 @@ describe('ID5 ID System', function () { }); }); - describe('with failing external module loading', function() { - it('should fallback to regular logic if external module fails to load', async function() { + describe('with failing external module loading', function () { + it('should fallback to regular logic if external module fails to load', async function () { const xhrServerMock = new XhrServerMock(server); const config = getId5FetchConfig(); config.params.externalModuleUrl = 'https://test-me.test'; // Fails by loading this fake URL @@ -791,7 +802,7 @@ describe('ID5 ID System', function () { }); it('should pass gpp_string and gpp_sid to ID5 server', function () { - let xhrServerMock = new XhrServerMock(server) + let xhrServerMock = new XhrServerMock(server); gppStub = sinon.stub(gppDataHandler, 'getConsentData'); gppStub.returns({ ready: true, @@ -806,7 +817,7 @@ describe('ID5 ID System', function () { expect(requestBody.gpp_string).is.equal('GPP_STRING'); expect(requestBody.gpp_sid).contains(2); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse + return submoduleResponse; }); }); @@ -823,7 +834,7 @@ describe('ID5 ID System', function () { id5System.storage.getCookie.callsFake(() => ' Not JSON '); id5System.id5IdSubmodule.getId(getId5FetchConfig()); }); - }) + }); }); describe('Local storage', () => { @@ -839,9 +850,9 @@ describe('ID5 ID System', function () { [true, 1], [false, 0] ].forEach(([isEnabled, expectedValue]) => { - it(`should check localStorage availability and log in request. Available=${isEnabled}`, async function() { - const xhrServerMock = new XhrServerMock(server) - id5System.storage.localStorageIsEnabled.callsFake(() => isEnabled) + it(`should check localStorage availability and log in request. Available=${isEnabled}`, async function () { + const xhrServerMock = new XhrServerMock(server); + id5System.storage.localStorageIsEnabled.callsFake(() => isEnabled); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); @@ -868,7 +879,7 @@ describe('ID5 ID System', function () { coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); - coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()) + coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()); adUnits = [getAdUnitMock()]; }); afterEach(function () { @@ -876,7 +887,7 @@ describe('ID5 ID System', function () { coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); - coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst') + coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst'); sandbox.restore(); }); @@ -930,7 +941,7 @@ describe('ID5 ID System', function () { provider: ID5_SOURCE } }] - }) + }); }); }); done(); @@ -967,7 +978,7 @@ describe('ID5 ID System', function () { requestBidsHook((adUnitConfig) => { expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); - done() + done(); }, {adUnits}); }); @@ -981,12 +992,12 @@ describe('ID5 ID System', function () { requestBidsHook(() => { expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); - done() + done(); }, {adUnits}); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); id5System.storeInLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`, id5System.expDaysStr(-1), 1); @@ -1000,7 +1011,7 @@ describe('ID5 ID System', function () { return new Promise((resolve) => { requestBidsHook(() => { - resolve() + resolve(); }, {adUnits}); }).then(() => { expect(xhrServerMock.hasReceivedAnyRequest()).is.false; @@ -1018,11 +1029,11 @@ describe('ID5 ID System', function () { if (id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); setTimeout(waitForCondition, 30); })(); - }) + }); }).then(() => { expect(decodeURIComponent(id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); - }) + }); }); }); From 2bb9f9759da25d53203b716f225adebf063ef09a Mon Sep 17 00:00:00 2001 From: Parth Shah Date: Tue, 7 May 2024 02:05:31 +0530 Subject: [PATCH 0010/1097] DeepIntent Id Module : fix user ids not being passed on page reload due to getValue func miss (#11383) * fix user ids not being passed on page reload due to extendId func miss * remove extendId and add function to read value for eids * handle inconsistent localstorage values in deepintent id * remove unused imports * revert to getValue changes * revert version in package-lock * update test suite for deepintent id system --- modules/deepintentDpesIdSystem.js | 9 ++- .../modules/deepintentDpesIdsystem_spec.js | 60 +++++++------------ 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 2d3eae980cd..a1f1e29a4ce 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.js @@ -8,6 +8,7 @@ import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {isPlainObject} from '../src/utils.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -49,7 +50,13 @@ export const deepintentDpesSubmodule = { eids: { 'deepintentId': { source: 'deepintent.com', - atype: 3 + atype: 3, + getValue: (userIdData) => { + if (isPlainObject(userIdData) && userIdData?.id) { + return userIdData.id; + } + return userIdData; + } }, }, }; diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js index 4c26b118a98..3bd1e71d8f6 100644 --- a/test/spec/modules/deepintentDpesIdsystem_spec.js +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -1,12 +1,8 @@ import { expect } from 'chai'; -import {find} from 'src/polyfill.js'; -import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; -import { config } from 'src/config.js'; +import { deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; -const DI_COOKIE_NAME = '_dpes_id'; -const DI_COOKIE_STORED = '{"id":"2cf40748c4f7f60d343336e08f80dc99"}'; const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; +const DI_UPDATED_STORAGE = '2cf40748c4f7f60d343336e08f80dc99'; const cookieConfig = { name: 'deepintentId', @@ -27,50 +23,40 @@ const html5Config = { } describe('Deepintent DPES System', () => { - let getDataFromLocalStorageStub, localStorageIsEnabledStub; - let getCookieStub, cookiesAreEnabledStub; - - beforeEach(() => { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); - getCookieStub = sinon.stub(storage, 'getCookie'); - cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); - }); - - afterEach(() => { - getDataFromLocalStorageStub.restore(); - localStorageIsEnabledStub.restore(); - getCookieStub.restore(); - cookiesAreEnabledStub.restore(); - }); - describe('Deepintent Dpes Sytsem: test "getId" method', () => { - it('Wrong config should fail the tests', () => { - // no config - expect(deepintentDpesSubmodule.getId()).to.be.eq(undefined); - expect(deepintentDpesSubmodule.getId({ })).to.be.eq(undefined); - - expect(deepintentDpesSubmodule.getId({params: {}, storage: {}})).to.be.eq(undefined); - expect(deepintentDpesSubmodule.getId({params: {}, storage: {type: 'cookie'}})).to.be.eq(undefined); - expect(deepintentDpesSubmodule.getId({params: {}, storage: {name: '_dpes_id'}})).to.be.eq(undefined); + it('If nothing is found in cache, return undefined', () => { + let diId = deepintentDpesSubmodule.getId({}, undefined, undefined); + expect(diId).to.be.eq(undefined); }); it('Get value stored in cookie for getId', () => { - getCookieStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); let diId = deepintentDpesSubmodule.getId(cookieConfig, undefined, DI_COOKIE_OBJECT); expect(diId).to.deep.equal(DI_COOKIE_OBJECT); }); it('provides the stored deepintentId if cookie is absent but present in local storage', () => { - getDataFromLocalStorageStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); - let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_COOKIE_OBJECT); - expect(idx).to.deep.equal(DI_COOKIE_OBJECT); + let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_UPDATED_STORAGE); + expect(idx).to.be.eq(DI_UPDATED_STORAGE); }); }); describe('Deepintent Dpes System : test "decode" method', () => { - it('Get the correct decoded value for dpes id', () => { - expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': {'id': '2cf40748c4f7f60d343336e08f80dc99'}}); + it('Get the correct decoded value for dpes id, if an object is set return object', () => { + expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': DI_COOKIE_OBJECT}); + }); + + it('Get the correct decoded value for dpes id, if a string is set return string', () => { + expect(deepintentDpesSubmodule.decode(DI_UPDATED_STORAGE, {})).to.deep.equal({'deepintentId': DI_UPDATED_STORAGE}); + }); + }); + + describe('Deepintent Dpes System : test "getValue" method in eids', () => { + it('Get the correct string value for dpes id, if an object is set in local storage', () => { + expect(deepintentDpesSubmodule.eids.deepintentId.getValue(DI_COOKIE_OBJECT)).to.be.equal(DI_UPDATED_STORAGE); + }); + + it('Get the correct string value for dpes id, if a string is set in local storage', () => { + expect(deepintentDpesSubmodule.eids.deepintentId.getValue(DI_UPDATED_STORAGE)).to.be.eq(DI_UPDATED_STORAGE); }); }); }); From 55d008a5934e035f3cb3adb36ef0376e4483264f Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Tue, 7 May 2024 04:58:58 +0800 Subject: [PATCH 0011/1097] Discovery Bid Adapter : get UTM tag data (#11380) * feat(isBidRequestValid): just filter token once. not filter publisher and tagid * feat(isBidRequestValid): add unit test * feat(spec): fix eslint * feat(spec): fix unit test * feat(spec): fix unit test * feat(ext): change default value * feat(utm): build UTMTag data * feat(spec): fix utm test * feat(utm): get UTMTag * feat(utm): add unit tests * feat(utm): fix unit tests * feat(utm): fix unit tests * feat(utm): fix unit tests --------- Co-authored-by: yubei01 --- modules/discoveryBidAdapter.js | 3 +- test/spec/modules/discoveryBidAdapter_spec.js | 53 +++++++------------ 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 7493dcb9af4..3ac905ef6b5 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -414,7 +414,7 @@ function getItems(validBidRequests, bidderRequest) { export const buildUTMTagData = (url) => { if (!storage.cookiesAreEnabled()) return; - const urlParams = utils.parseUrl(url).search; + const urlParams = utils.parseUrl(url).search || {}; const UTMParams = {}; Object.keys(urlParams).forEach(key => { if (/^utm_/.test(key)) { @@ -486,6 +486,7 @@ function getParam(validBidRequests, bidderRequest) { ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, pmguid: getPmgUID(), tpData, + utm: storage.getCookie(UTM_KEY), page: { title: title ? title.slice(0, 100) : undefined, desc: desc ? desc.slice(0, 300) : undefined, diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index d148d5062a4..42cc6ff68eb 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -15,6 +15,25 @@ import { import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(utils, 'generateUUID').returns('new-uuid'); + sandbox.stub(utils, 'parseUrl').returns({ + search: { + utm_source: 'example.com' + } + }); + sandbox.stub(storage, 'cookiesAreEnabled'); + }) + + afterEach(() => { + sandbox.restore(); + }); + let bidRequestData = { bidderCode: 'discovery', auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', @@ -200,8 +219,8 @@ describe('discovery:BidAdapterTests', function () { }) ).to.equal(true); }); - it('discovery:validate_generated_params', function () { + storage.getCookie.withArgs('_ss_pp_utm').callsFake(() => '{"utm_source":"example.com","utm_medium":"123","utm_campaign":"456"}'); request = spec.buildRequests(bidRequestData.bids, bidRequestData); let req_data = JSON.parse(request.data); expect(req_data.imp).to.have.lengthOf(1); @@ -216,20 +235,6 @@ describe('discovery:BidAdapterTests', function () { describe('discovery: buildRequests', function() { describe('getPmgUID function', function() { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getCookie'); - sandbox.stub(storage, 'setCookie'); - sandbox.stub(utils, 'generateUUID').returns('new-uuid'); - sandbox.stub(storage, 'cookiesAreEnabled'); - }) - - afterEach(() => { - sandbox.restore(); - }); - it('should generate new UUID and set cookie if not exists', () => { storage.cookiesAreEnabled.callsFake(() => true); storage.getCookie.callsFake(() => null); @@ -254,24 +259,6 @@ describe('discovery:BidAdapterTests', function () { }); }) describe('buildUTMTagData function', function() { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getCookie'); - sandbox.stub(storage, 'setCookie'); - sandbox.stub(utils, 'parseUrl').returns({ - search: { - utm_source: 'example.com' - } - }); - sandbox.stub(storage, 'cookiesAreEnabled'); - }) - - afterEach(() => { - sandbox.restore(); - }); - it('should set UTM cookie', () => { storage.cookiesAreEnabled.callsFake(() => true); storage.getCookie.callsFake(() => null); From 3ccef438e439c962fa68675c7cace9101c6cf350 Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 6 May 2024 17:08:21 -0400 Subject: [PATCH 0012/1097] README: note about build-bundle-dev (#11448) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 70e058d122b..f890f055104 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,16 @@ Or, if you are consuming Prebid through npm, with the `disableFeatures` option i **Note**: this is still a work in progress - at the moment, `NATIVE` is the only feature that can be disabled this way, resulting in a minimal decrease in size (but you can expect that to improve over time). +## Unminified code + +You can get a version of the code that's unminified for debugging with `build-bundle-dev`: + +```bash +gulp build-bundle-dev --modules=bidderA,module1,... +``` + +The results will be in build/dev/prebid.js. + ## Test locally To lint the code: From b6c25e18be3e70bc3e50eb47c3367483848b9f8a Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Mon, 6 May 2024 14:41:16 -0700 Subject: [PATCH 0013/1097] adding the domain when calling home (#11440) --- modules/hadronIdSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 66cb5624a38..bdb8e634de6 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -115,7 +115,7 @@ export const hadronIdSubmodule = { // config.params.url and config.params.urlArg are not documented // since their use is for debugging purposes only paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid&t=1&src=id` // src=id => the backend was called from getId + `partner_id=${partnerId}&_it=prebid&t=1&src=id&domain=${document.location.hostname}` // src=id => the backend was called from getId ); if (isDebug) { url += '&debug=1' From 93533276030e4748503a20b2028845a8d5fbf6fb Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 6 May 2024 18:44:14 -0400 Subject: [PATCH 0014/1097] Update PULL_REQUEST_TEMPLATE.md (#11449) Updating the notes: - new adapters are pointed to the docs rather than the repo - updated client-side adapters are asked to consider whether PBS adapter update is needed --- .github/PULL_REQUEST_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 09ef5c445f2..b225be162a8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,7 +16,8 @@ For any user facing change, submit a link to a PR on the docs repo at https://gi - [ ] Bugfix - [ ] Feature -- [ ] New bidder adapter +- [ ] New bidder adapter +- [ ] Updated bidder adapter - [ ] Code style update (formatting, local variables) - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes From 14d2a0e844ed9f75ea8478011c0e3e886ea5eb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Millet?= Date: Tue, 7 May 2024 16:39:23 +0200 Subject: [PATCH 0015/1097] Dailymotion Bid Adapter: accept ortb2 field (#11366) * Dailymotion Bid Adaptor: initial release * .md file lint issue resolved * Dailymotion Bid Adaptor: build bidder request based on param with fallbacks * Dailymotion Bid Adaptor: support video metadata * Dailymotion Bid Adaptor: add support for sending adUnitCode * Dailymotion Bid Adaptor: add support for sending startDelay * feat(LEO-528): Allow multiple IAB categories in video metadata The same way as we can have an array of IAB categories level 1 in the ORTB request, this PR introduces an array for the IAB categories level 2. To be forward compatible with level [2.2](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.2.tsv) and [3.0](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%203.0.tsv) specifications, the category IDs should be sent as strings. * Dailymotion bid adapter: Clarify the video metadata to provide in each player context * Dailymotion bid adapter: Move API key to bid params * Dailymotion bid adapter: Verify API key is string Co-authored-by: Rumesh * Dailymotion bid adapter: Move API key to bid params (fix tests) * Dailymotion Bid Adaptor: add gpp support and get coppa from request * Dailymotion Bid Adaptor: fix lint error * Dailymotion Bid Adaptor: add iabcat1 and fallback to ortb2 for iabcat2 * Dailymotion Bid Adaptor: get iabcats from ortb2.site.content.data * Dailymotion Bid Adaptor: get content data from ortb2.site.content * Dailymotion Bid Adaptor: add support for iabcat1 in videoParams and document mapping of ortb2 fpd to video metadata * Dailymotion Bid Adaptor: add support for Object: App * Dailymotion Bid Adaptor: only support video adunits in context instream * Dailymotion bid adapter: Add standard ORTB video parameters Note: I changed the case of `startdelay` to be consistent with the other parameters, but it won't have any impact on current deployments as this parameter is not used onsite. * Dailymotion Bid Adaptor: add support for livestream and app * Dailymotion Bid Adaptor: drop support for non standard fields in mediaTypes.video * Dailymotion Bid Adaptor: update docs examples --------- Co-authored-by: Kevin Siow Co-authored-by: Aditi Chaudhary Co-authored-by: Kevin Siow Co-authored-by: Rumesh --- modules/dailymotionBidAdapter.js | 105 +++++--- modules/dailymotionBidAdapter.md | 83 ++++-- .../modules/dailymotionBidAdapter_spec.js | 255 +++++++++++++++--- 3 files changed, 354 insertions(+), 89 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 2be5edad78e..afded538fd0 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -9,27 +9,33 @@ import { deepAccess } from '../src/utils.js'; * @return video metadata */ function getVideoMetadata(bidRequest, bidderRequest) { - const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); - const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + const videoParams = deepAccess(bidRequest, 'params.video', {}); - const videoParams = { - ...videoAdUnit, - ...videoBidderParams, // Bidder Specific overrides - }; + // As per oRTB 2.5 spec, "A bid request must not contain both an App and a Site object." + // See section 3.2.14 + // Content object is either from Object: Site or Object: App + const contentObj = deepAccess(bidderRequest, 'ortb2.site') + ? deepAccess(bidderRequest, 'ortb2.site.content') + : deepAccess(bidderRequest, 'ortb2.app.content'); - // Store as object keys to ensure uniqueness - const iabcat1 = {}; - const iabcat2 = {}; + const parsedContentData = { + // Store as object keys to ensure uniqueness + iabcat1: {}, + iabcat2: {}, + }; - deepAccess(bidderRequest, 'ortb2.site.content.data', []).forEach((data) => { + deepAccess(contentObj, 'data', []).forEach((data) => { if ([4, 5, 6, 7].includes(data?.ext?.segtax)) { (Array.isArray(data.segment) ? data.segment : []).forEach((segment) => { if (typeof segment.id === 'string') { // See https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy // Only take IAB cats of taxonomy V1 - if (data.ext.segtax === 4) iabcat1[segment.id] = 1; - // Only take IAB cats of taxonomy V2 or higher - if ([5, 6, 7].includes(data.ext.segtax)) iabcat2[segment.id] = 1; + if (data.ext.segtax === 4) { + parsedContentData.iabcat1[segment.id] = 1; + } else { + // Only take IAB cats of taxonomy V2 or higher + parsedContentData.iabcat2[segment.id] = 1; + } } }); } @@ -37,18 +43,25 @@ function getVideoMetadata(bidRequest, bidderRequest) { const videoMetadata = { description: videoParams.description || '', - duration: videoParams.duration || 0, - iabcat1: Object.keys(iabcat1), + duration: videoParams.duration || deepAccess(contentObj, 'len', 0), + iabcat1: Array.isArray(videoParams.iabcat1) + ? videoParams.iabcat1 + : Array.isArray(deepAccess(contentObj, 'cat')) + ? contentObj.cat + : Object.keys(parsedContentData.iabcat1), iabcat2: Array.isArray(videoParams.iabcat2) ? videoParams.iabcat2 - : Object.keys(iabcat2), - id: videoParams.id || '', - lang: videoParams.lang || '', + : Object.keys(parsedContentData.iabcat2), + id: videoParams.id || deepAccess(contentObj, 'id', ''), + lang: videoParams.lang || deepAccess(contentObj, 'language', ''), private: videoParams.private || false, - tags: videoParams.tags || '', - title: videoParams.title || '', + tags: videoParams.tags || deepAccess(contentObj, 'keywords', ''), + title: videoParams.title || deepAccess(contentObj, 'title', ''), topics: videoParams.topics || '', xid: videoParams.xid || '', + livestream: typeof videoParams.livestream === 'number' + ? !!videoParams.livestream + : !!deepAccess(contentObj, 'livestream', 0), }; return videoMetadata; @@ -67,7 +80,24 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return typeof bid?.params?.apiKey === 'string' && bid.params.apiKey.length > 10; + if (bid?.params) { + // We only accept video adUnits + if (!bid?.mediaTypes?.[VIDEO]) return false; + + // As `context`, `placement` & `plcmt` are optional (although recommended) + // values, we check the 3 of them to see if we are in an instream video context + const isInstream = bid.mediaTypes[VIDEO].context === 'instream' || + bid.mediaTypes[VIDEO].placement === 1 || + bid.mediaTypes[VIDEO].plcmt === 1; + + // We only accept instream video context + if (!isInstream) return false; + + // We need API key + return typeof bid.params.apiKey === 'string' && bid.params.apiKey.length > 10; + } + + return false; }, /** @@ -83,15 +113,15 @@ export const spec = { data: { bidder_request: { gdprConsent: { - apiVersion: bidderRequest?.gdprConsent?.apiVersion || 1, - consentString: bidderRequest?.gdprConsent?.consentString || '', + apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), + consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), // Cast boolean in any case (eg: if value is int) to ensure type - gdprApplies: !!bidderRequest?.gdprConsent?.gdprApplies, + gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), }, refererInfo: { - page: bidderRequest?.refererInfo?.page || '', + page: deepAccess(bidderRequest, 'refererInfo.page', ''), }, - uspConsent: bidderRequest?.uspConsent || '', + uspConsent: deepAccess(bidderRequest, 'uspConsent', ''), gppConsent: { gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), @@ -104,15 +134,28 @@ export const spec = { }, // Cast boolean in any case (value should be 0 or 1) to ensure type coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), + // In app context, we need to retrieve additional informations + ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { + appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), + appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), + } : {}), request: { - adUnitCode: bid.adUnitCode || '', - auctionId: bid.auctionId || '', - bidId: bid.bidId || '', + adUnitCode: deepAccess(bid, 'adUnitCode', ''), + auctionId: deepAccess(bid, 'auctionId', ''), + bidId: deepAccess(bid, 'bidId', ''), mediaTypes: { video: { - playerSize: bid.mediaTypes?.[VIDEO]?.playerSize || [], api: bid.mediaTypes?.[VIDEO]?.api || [], - startDelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, + mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], + minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, + maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, + protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], + skip: bid.mediaTypes?.[VIDEO]?.skip || 0, + skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, + skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0, + startdelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, + w: bid.mediaTypes?.[VIDEO]?.w || 0, + h: bid.mediaTypes?.[VIDEO]?.h || 0, }, }, sizes: bid.sizes || [], diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 795273c9229..7c871b0d536 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -9,10 +9,11 @@ Maintainer: ad-leo-engineering@dailymotion.com # Description Dailymotion prebid adapter. +Supports video ad units in instream context. # Configuration options -Before calling this adapter, you need to set at least the API key in the bid parameters: +Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters: ```javascript const adUnits = [ @@ -21,8 +22,14 @@ const adUnits = [ bidder: 'dailymotion', params: { apiKey: 'fake_api_key' - } - }] + }, + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + context: 'instream', + }, + }, } ]; ``` @@ -39,9 +46,15 @@ const adUnits = [ bids: [{ bidder: 'dailymotion', params: { - apiKey: 'dailymotion-testing' - } - }] + apiKey: 'dailymotion-testing', + }, + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + context: 'instream', + }, + }, } ]; ``` @@ -51,7 +64,7 @@ Please note that failing to set these will result in the adapter not bidding at # Sample video AdUnit To allow better targeting, you should provide as much context about the video as possible. -There are two ways of doing this depending on if you're using Dailymotion player or a third party one. +There are three ways of doing this depending on if you're using Dailymotion player or a third party one. If you are using the Dailymotion player, you should only provide the video `xid` in your ad unit, example: @@ -61,7 +74,10 @@ const adUnits = [ bids: [{ bidder: 'dailymotion', params: { - apiKey: 'dailymotion-testing' + apiKey: 'dailymotion-testing', + video: { + xid: 'x123456' // Dailymotion infrastructure unique video ID + }, } }], code: 'test-ad-unit', @@ -69,9 +85,9 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', - playerSize: [ [1280, 720] ], - startDelay: 0, - xid: 'x123456' // Dailymotion infrastructure unique video ID + startdelay: 0, + w: 1280, + h: 720, }, } } @@ -91,7 +107,17 @@ const adUnits = [ params: { apiKey: 'dailymotion-testing', video: { - description: 'overriden video description' + description: 'this is a video description', + duration: 556, + iabcat1: ['IAB-2'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + livestream: 0, + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', } } }], @@ -100,37 +126,42 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', - description: 'this is a video description', - duration: 556, - iabcat2: ['6', '17'], - id: '54321', - lang: 'FR', - playerSize: [ [1280, 720] ], - private: false, - startDelay: 0, - tags: 'tag_1,tag_2,tag_3', - title: 'test video', - topics: 'topic_1, topic_2', + startdelay: 0, + w: 1280, + h: 720, }, } } ]; ``` -Each of the following video metadata fields can be added in mediaTypes.video or bids.params.video. -If a field exists in both places, it will be overridden by bids.params.video. +Each of the following video metadata fields can be added in bids.params.video. * `description` - Video description * `duration` - Video duration in seconds -* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv) +* `iabcat1` - List of IAB category IDs from the [1.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%201.0.tsv) +* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv) and above * `id` - Video unique ID in host video infrastructure * `lang` - ISO 639-1 code for main language used in the video +* `livestream` - 0 = not live, 1 = content is live * `private` - True if video is not publicly available * `tags` - Tags for the video, comma separated * `title` - Video title * `topics` - Main topics for the video, comma separated * `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) +If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will fallback to those values when possible. See the mapping below. + +| From ortb2 | Metadata fields | +|---------------------------------------------------------------------------------|-----------------| +| `ortb2.site.content.cat` OR `ortb2.site.content.data` where `ext.segtax` is `4` | `iabcat1` | +| `ortb2.site.content.data` where `ext.segtax` is `5`, `6` or `7` | `iabcat2` | +| `ortb2.site.content.id` | `id` | +| `ortb2.site.content.language` | `lang` | +| `ortb2.site.content.livestream` | `livestream` | +| `ortb2.site.content.keywords` | `tags` | +| `ortb2.site.content.title` | `title` | + # Integrating the adapter To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index a102c26dca2..fe9484b2814 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -1,7 +1,7 @@ import { config } from 'src/config.js'; import { expect } from 'chai'; import { spec } from 'modules/dailymotionBidAdapter.js'; -import { VIDEO } from '../../../src/mediaTypes'; +import { BANNER, VIDEO } from '../../../src/mediaTypes'; describe('dailymotionBidAdapterTests', () => { // Validate that isBidRequestValid only validates requests with apiKey @@ -12,6 +12,11 @@ describe('dailymotionBidAdapterTests', () => { params: { apiKey: '', }, + mediaTypes: { + [VIDEO]: { + context: 'instream', + }, + }, }; expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyApi))).to.be.false; @@ -20,9 +25,57 @@ describe('dailymotionBidAdapterTests', () => { params: { apiKey: 'test_api_key', }, + mediaTypes: { + [VIDEO]: { + context: 'instream', + }, + }, }; expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithApi))).to.be.true; + + const bidWithEmptyMediaTypes = { + params: { + apiKey: '', + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyMediaTypes))).to.be.false; + + const bidWithEmptyVideoAdUnit = { + params: { + apiKey: '', + }, + mediaTypes: { + [VIDEO]: {}, + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyVideoAdUnit))).to.be.false; + + const bidWithBannerMediaType = { + params: { + apiKey: 'test_api_key', + }, + mediaTypes: { + [BANNER]: {}, + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithBannerMediaType))).to.be.false; + + const bidWithOutstreamContext = { + params: { + apiKey: 'test_api_key', + }, + mediaTypes: { + video: { + context: 'outstream', + }, + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithOutstreamContext))).to.be.false; }); // Validate request generation @@ -33,20 +86,27 @@ describe('dailymotionBidAdapterTests', () => { adUnitCode: 'preroll', mediaTypes: { video: { - playerSize: [[1280, 720]], api: [2, 7], - description: 'this is a test video', - duration: 300, - iabcat2: ['6', '17'], - lang: 'ENG', + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, startdelay: 0, + w: 1280, + h: 720 }, }, sizes: [[1920, 1080]], params: { apiKey: 'test_api_key', video: { + description: 'this is a test video', duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], id: '54321', lang: 'FR', private: false, @@ -54,6 +114,7 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', topics: 'topic_1, topic_2', xid: 'x123456', + livestream: 1, }, }, }]; @@ -78,6 +139,117 @@ describe('dailymotionBidAdapterTests', () => { }, site: { content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + const { data: reqData } = request; + + expect(request.url).to.equal('https://pb.dmxleo.com'); + + expect(reqData.bidder_request).to.eql({ + refererInfo: bidderRequestData.refererInfo, + uspConsent: bidderRequestData.uspConsent, + gdprConsent: bidderRequestData.gdprConsent, + gppConsent: bidderRequestData.gppConsent, + }); + expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); + expect(reqData.coppa).to.be.true; + expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); + expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); + expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); + expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); + expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.video_metadata).to.eql({ + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-1'], + iabcat2: bidRequestData[0].params.video.iabcat2, + id: bidRequestData[0].params.video.id, + lang: bidRequestData[0].params.video.lang, + private: bidRequestData[0].params.video.private, + tags: bidRequestData[0].params.video.tags, + title: bidRequestData[0].params.video.title, + topics: bidRequestData[0].params.video.topics, + xid: bidRequestData[0].params.video.xid, + duration: bidRequestData[0].params.video.duration, + livestream: !!bidRequestData[0].params.video.livestream, + }); + }); + + it('validates buildRequests with content values from App', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + app: { + bundle: 'app-bundle', + storeurl: 'https://play.google.com/store/apps/details?id=app-bundle', + content: { + len: 556, data: [ { name: 'dataprovider.com', @@ -112,15 +284,25 @@ describe('dailymotionBidAdapterTests', () => { }); expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); expect(reqData.coppa).to.be.true; + expect(reqData.appBundle).to.eql(bidderRequestData.ortb2.app.bundle); + expect(reqData.appStoreUrl).to.eql(bidderRequestData.ortb2.app.storeurl); expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startDelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.request.mediaTypes.video.mimes).to.eql(bidRequestData[0].mediaTypes.video.mimes); + expect(reqData.request.mediaTypes.video.minduration).to.eql(bidRequestData[0].mediaTypes.video.minduration); + expect(reqData.request.mediaTypes.video.maxduration).to.eql(bidRequestData[0].mediaTypes.video.maxduration); + expect(reqData.request.mediaTypes.video.protocols).to.eql(bidRequestData[0].mediaTypes.video.protocols); + expect(reqData.request.mediaTypes.video.skip).to.eql(bidRequestData[0].mediaTypes.video.skip); + expect(reqData.request.mediaTypes.video.skipafter).to.eql(bidRequestData[0].mediaTypes.video.skipafter); + expect(reqData.request.mediaTypes.video.skipmin).to.eql(bidRequestData[0].mediaTypes.video.skipmin); + expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.request.mediaTypes.video.w).to.eql(bidRequestData[0].mediaTypes.video.w); + expect(reqData.request.mediaTypes.video.h).to.eql(bidRequestData[0].mediaTypes.video.h); expect(reqData.video_metadata).to.eql({ - description: bidRequestData[0].mediaTypes.video.description, - iabcat1: ['IAB-1'], // Taxonomy v2 or higher is excluded - iabcat2: bidRequestData[0].mediaTypes.video.iabcat2, + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-1'], + iabcat2: bidRequestData[0].params.video.iabcat2, id: bidRequestData[0].params.video.id, lang: bidRequestData[0].params.video.lang, private: bidRequestData[0].params.video.private, @@ -129,22 +311,19 @@ describe('dailymotionBidAdapterTests', () => { topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, // Overriden through bidder params - duration: bidRequestData[0].params.video.duration, + duration: bidderRequestData.ortb2.app.content.len, + livestream: !!bidRequestData[0].params.video.livestream, }); }); - it('validates buildRequests with fallback values on ortb2 for gpp & iabcat', () => { + it('validates buildRequests with fallback values on ortb2 (gpp, iabcat2, id...)', () => { const bidRequestData = [{ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId: 123456, adUnitCode: 'preroll', mediaTypes: { video: { - playerSize: [[1280, 720]], api: [2, 7], - description: 'this is a test video', - duration: 300, - lang: 'ENG', startdelay: 0, }, }, @@ -152,11 +331,9 @@ describe('dailymotionBidAdapterTests', () => { params: { apiKey: 'test_api_key', video: { + description: 'this is a test video', duration: 556, - id: '54321', - lang: 'FR', private: false, - tags: 'tag_1,tag_2,tag_3', title: 'test video', topics: 'topic_1, topic_2', xid: 'x123456', @@ -182,6 +359,12 @@ describe('dailymotionBidAdapterTests', () => { }, site: { content: { + id: '54321', + language: 'FR', + keywords: 'tag_1,tag_2,tag_3', + title: 'test video', + livestream: 1, + cat: ['IAB-2'], data: [ undefined, // Undefined to check proper handling of edge cases {}, // Empty object to check proper handling of edge cases @@ -203,7 +386,7 @@ describe('dailymotionBidAdapterTests', () => { }, { name: 'dataprovider.com', - ext: { segtax: 4 }, + ext: { segtax: 5 }, segment: [{ id: 2222 }], // Invalid segment id to check proper handling of edge cases }, { @@ -251,21 +434,20 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startDelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); expect(reqData.video_metadata).to.eql({ - description: bidRequestData[0].mediaTypes.video.description, - // No iabcat1 here because nothing matches taxonomy - iabcat1: [], + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-2'], iabcat2: ['6', '17', '20'], - id: bidRequestData[0].params.video.id, - lang: bidRequestData[0].params.video.lang, + id: bidderRequestData.ortb2.site.content.id, + lang: bidderRequestData.ortb2.site.content.language, private: bidRequestData[0].params.video.private, - tags: bidRequestData[0].params.video.tags, - title: bidRequestData[0].params.video.title, + tags: bidderRequestData.ortb2.site.content.keywords, + title: bidderRequestData.ortb2.site.content.title, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, - // Overriden through bidder params duration: bidRequestData[0].params.video.duration, + livestream: !!bidderRequestData.ortb2.site.content.livestream, }); }); @@ -310,9 +492,17 @@ describe('dailymotionBidAdapterTests', () => { adUnitCode: '', mediaTypes: { video: { - playerSize: [], - startDelay: 0, api: [], + mimes: [], + minduration: 0, + maxduration: 0, + protocols: [], + skip: 0, + skipafter: 0, + skipmin: 0, + startdelay: 0, + w: 0, + h: 0, }, }, sizes: [], @@ -330,6 +520,7 @@ describe('dailymotionBidAdapterTests', () => { title: '', topics: '', xid: '', + livestream: false, }); }); From 685d72c50472b3e99dbe33c809b3d14fa8ec97ee Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Tue, 7 May 2024 18:41:12 +0400 Subject: [PATCH 0016/1097] enhance fledge (#11455) --- modules/sovrnBidAdapter.js | 38 ++++++- test/spec/modules/sovrnBidAdapter_spec.js | 132 +++++++++++++++++----- 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 3df025b1619..b6563cac4c5 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -6,7 +6,7 @@ import { logError, deepAccess, isInteger, - logWarn, getBidIdParameter + logWarn, getBidIdParameter, isEmptyStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { @@ -254,16 +254,38 @@ export const spec = { })) .flat() - let fledgeAuctionConfigs = deepAccess(ext, 'fledge_auction_configs'); + let fledgeAuctionConfigs = null; + if (isArray(ext?.igbid)) { + const seller = ext.seller + const decisionLogicUrl = ext.decisionLogicUrl + const sellerTimeout = ext.sellerTimeout + ext.igbid.filter(item => isValidIgBid(item)).forEach((igbid) => { + const perBuyerSignals = {} + igbid.igbuyer.filter(item => isValidIgBuyer(item)).forEach(buyerItem => { + perBuyerSignals[buyerItem.igdomain] = buyerItem.buyerdata + }) + const interestGroupBuyers = [...Object.keys(perBuyerSignals)] + if (interestGroupBuyers.length) { + fledgeAuctionConfigs = fledgeAuctionConfigs || {} + fledgeAuctionConfigs[igbid.impid] = { + seller, + decisionLogicUrl, + sellerTimeout, + interestGroupBuyers: interestGroupBuyers, + perBuyerSignals, + } + } + }) + } if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { return { bidId, config: Object.assign({ - auctionSignals: {}, + auctionSignals: {} }, cfg) } - }); + }) return { bids, fledgeAuctionConfigs, @@ -367,4 +389,12 @@ function _getBidFloors(bid) { return !isNaN(paramValue) ? paramValue : undefined } +function isValidIgBid(igBid) { + return !isEmptyStr(igBid.impid) && isArray(igBid.igbuyer) && igBid.igbuyer.length +} + +function isValidIgBuyer(igBuyer) { + return !isEmptyStr(igBuyer.igdomain) +} + registerBidder(spec) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 9c7f433ef96..10f5ab8e89d 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -844,26 +844,56 @@ describe('sovrnBidAdapter', function() { }] }], ext: { - fledge_auction_configs: { - 'test_bid_id': { - seller: 'ap.lijit.com', - interestGroupBuyers: ['dsp1.com'], - sellerTimeout: 0, - perBuyerSignals: { - 'dsp1.com': { - bid_macros: 0.1, - disallowed_adv_ids: [ - '8765', - '4321' - ], - } + seller: 'seller.lijit.com', + decisionLogicUrl: 'https://decision.lijit.com', + igbid: [{ + impid: 'test_imp_id', + igbuyer: [{ + igdomain: 'ap.lijit.com', + buyerdata: { + base_bid_micros: 0.1, + use_bid_multiplier: true, + multiplier: '1.3' } - } - } + }, { + igdomain: 'buyer2.com', + buyerdata: {} + }, { + igdomain: 'buyer3.com', + buyerdata: {} + }] + }, { + impid: 'test_imp_id_2', + igbuyer: [{ + igdomain: 'ap2.lijit.com', + buyerdata: { + base_bid_micros: '0.2', + } + }] + }, { + impid: '', + igbuyer: [{ + igdomain: 'ap3.lijit.com', + buyerdata: { + base_bid_micros: '0.3', + } + }] + }, { + impid: 'test_imp_id_3', + igbuyer: [{ + igdomain: '', + buyerdata: { + base_bid_micros: '0.3', + } + }] + }, { + impid: 'test_imp_id_4', + igbuyer: [] + }] } } } - let invalidFledgeResponse = { + let emptyFledgeResponse = { body: { id: '37386aade21a71', seatbid: [{ @@ -879,25 +909,73 @@ describe('sovrnBidAdapter', function() { }] }], ext: { - fledge_auction_configs: { + igbid: { } } } } - it('should return fledge auction configs alongside bids', function () { + let expectedResponse = { + 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(`>`) + } + let expectedFledgeResponse = [ + { + bidId: 'test_imp_id', + config: { + seller: 'seller.lijit.com', + decisionLogicUrl: 'https://decision.lijit.com', + sellerTimeout: undefined, + auctionSignals: {}, + interestGroupBuyers: ['ap.lijit.com', 'buyer2.com', 'buyer3.com'], + perBuyerSignals: { + 'ap.lijit.com': { + base_bid_micros: 0.1, + use_bid_multiplier: true, + multiplier: '1.3' + }, + 'buyer2.com': {}, + 'buyer3.com': {} + } + } + }, + { + bidId: 'test_imp_id_2', + config: { + seller: 'seller.lijit.com', + decisionLogicUrl: 'https://decision.lijit.com', + sellerTimeout: undefined, + auctionSignals: {}, + interestGroupBuyers: ['ap2.lijit.com'], + perBuyerSignals: { + 'ap2.lijit.com': { + base_bid_micros: '0.2', + } + } + } + } + ] + + it('should return valid fledge auction configs alongside bids', function () { const result = spec.interpretResponse(fledgeResponse) expect(result).to.have.property('bids') expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(1) - expect(result.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id') - expect(result.fledgeAuctionConfigs[0].config).to.not.be.undefined - expect(result.fledgeAuctionConfigs[0].config).to.contain.keys('seller', 'interestGroupBuyers', 'sellerTimeout', 'perBuyerSignals') + expect(result.fledgeAuctionConfigs.length).to.equal(2) + expect(result.fledgeAuctionConfigs).to.deep.equal(expectedFledgeResponse) }) - it('should ignore invalid fledge auction configs', function () { - const result = spec.interpretResponse(invalidFledgeResponse) - expect(result).to.have.property('bids') - expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(0) + it('should ignore empty fledge auction configs array', function () { + const result = spec.interpretResponse(emptyFledgeResponse) + expect(result.length).to.equal(1) + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) }) }) From 80f627d33c164e680fa78b37dfe4395cacfea3b0 Mon Sep 17 00:00:00 2001 From: ecdrsvc <82906140+ecdrsvc@users.noreply.github.com> Date: Tue, 7 May 2024 10:46:29 -0400 Subject: [PATCH 0017/1097] Mabidder Bid Adapter : use ortbConverter facility to pass ortb2 (#11447) * Add lmpIdSystem userId submodule * Use ortbConverter facility in mabidder --------- Co-authored-by: Antoine Niek --- modules/mabidderBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 632403c6643..8d97f48f604 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,9 +1,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'mabidder'; export const baseUrl = 'https://prebid.ecdrsvc.com/bid'; +const converter = ortbConverter({}) + export const spec = { supportedMediaTypes: [BANNER], code: BIDDER_CODE, @@ -14,7 +17,8 @@ export const spec = { return !!(bid.params.ppid && bid.sizes && Array.isArray(bid.sizes) && Array.isArray(bid.sizes[0])) }, buildRequests: function(validBidRequests, bidderRequest) { - const fpd = bidderRequest.ortb2; + const fpd = converter.toORTB({ bidRequests: validBidRequests, bidderRequest: bidderRequest }); + const bids = []; validBidRequests.forEach(bidRequest => { const sizes = []; From 8ebc22d462fe40f51ab2c5e5cfb0231ed6c9807e Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Tue, 7 May 2024 10:47:22 -0400 Subject: [PATCH 0018/1097] Update sharethroughBidAdapter.js (#11451) * Update the logic to determine how `gpid` is applied to a bid request. --- modules/sharethroughBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 2264bc37ebb..590fddca079 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -103,7 +103,7 @@ export const sharethroughAdapterSpec = { // mergeDeep(impression, bidReq.ortb2Imp); // leaving this out for now as we may want to leave stuff out on purpose const tid = deepAccess(bidReq, 'ortb2Imp.ext.tid'); if (tid) impression.ext.tid = tid; - const gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid', deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot')); + const gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid') || deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot'); if (gpid) impression.ext.gpid = gpid; const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); From ff458c5d1feac0df3a2f654406bb9ee1bb1fee80 Mon Sep 17 00:00:00 2001 From: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Date: Tue, 7 May 2024 23:49:21 +0900 Subject: [PATCH 0019/1097] remove format guard (#11452) --- modules/ajaBidAdapter.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index e02ab920707..699dfd6fa04 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -55,11 +55,6 @@ export const spec = { for (let i = 0, len = validBidRequests.length; i < len; i++) { const bidRequest = validBidRequests[i]; - if ( - (bidRequest.mediaTypes?.native || bidRequest.mediaTypes?.video) && - bidRequest.mediaTypes?.banner) { - continue - } let queryString = ''; From 10830eed32b5412c20839a9b8e4931b430ddd69d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 7 May 2024 11:13:24 -0700 Subject: [PATCH 0020/1097] EXADS bid adapter: replace broken logic with merely bad logic (#11456) --- modules/exadsBidAdapter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/exadsBidAdapter.js b/modules/exadsBidAdapter.js index 31d75db470d..20ead7a7e6b 100644 --- a/modules/exadsBidAdapter.js +++ b/modules/exadsBidAdapter.js @@ -319,24 +319,25 @@ function getEnvParams() { language: '' }; + // TODO: all of this is already in first party data envParams.domain = window.location.hostname; envParams.page = window.location.protocol + '//' + window.location.host + window.location.pathname; envParams.lang = navigator.language.indexOf('-') > -1 ? navigator.language.split('-')[0] : navigator.language; envParams.userAgent = navigator.userAgent; - if (envParams.userAgent.match(/Windows/i)) { envParams.osName = 'Windows'; } else if (envParams.userAgent.match(/Mac OS|Macintosh/i)) { envParams.osName = 'MacOS'; } else if (envParams.userAgent.match(/Unix/i)) { envParams.osName = 'Unix'; - } else if (envParams.userAgent.userAgent.match(/Android/i)) { + // TODO: what is userAgent.userAgent supposed to be? + } else if (envParams.userAgent.userAgent?.match(/Android/i)) { envParams.osName = 'Android'; - } else if (envParams.userAgent.userAgent.match(/iPhone|iPad|iPod/i)) { + } else if (envParams.userAgent.userAgent?.match(/iPhone|iPad|iPod/i)) { envParams.osName = 'iOS'; - } else if (envParams.userAgent.userAgent.match(/Linux/i)) { + } else if (envParams.userAgent.userAgent?.match(/Linux/i)) { envParams.osName = 'Linux'; } else { envParams.osName = 'Unknown'; From 4a4ada816117cc8c085ca3966af12121283ac68e Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Tue, 7 May 2024 14:59:29 -0400 Subject: [PATCH 0021/1097] look for gpid in the ortb2Imp.ext.gpid (#11460) --- modules/sonobiBidAdapter.js | 2 +- test/spec/modules/sonobiBidAdapter_spec.js | 24 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index e1b51affd09..edc4f255d18 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -329,7 +329,7 @@ function _validateFloor(bid) { } function _validateGPID(bid) { - const gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') || deepAccess(getGptSlotInfoForAdUnitCode(bid.adUnitCode), 'gptSlot') || bid.params.ad_unit; + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') || deepAccess(getGptSlotInfoForAdUnitCode(bid.adUnitCode), 'gptSlot') || bid.params.ad_unit; if (gpid) { return `gpid=${gpid},` diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index c7f954cfdcf..75da1983f0c 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -302,6 +302,29 @@ describe('SonobiBidAdapter', function () { } } }, + { + + 'bidder': 'sonobi', + 'params': { + 'keywords': 'sports,news,some_other_keyword', + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1.25', + }, + 'adUnitCode': 'adunit-code-42', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1g', + ortb2Imp: { + ext: { + gpid: '/123123/gpt_publisher/adunit-code-42' + } + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }, { 'bidder': 'sonobi', 'params': { @@ -343,6 +366,7 @@ describe('SonobiBidAdapter', function () { let keyMakerData = { '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,pm=1:2:3,p=2,pl=3,', + '30b31c1838de1g': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-42,c=d,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; From eb5ae983c8bc958f27b6ca6d353a245aa6796cbb Mon Sep 17 00:00:00 2001 From: lukashakl <107847079+lukashakl@users.noreply.github.com> Date: Tue, 7 May 2024 21:01:03 +0200 Subject: [PATCH 0022/1097] Performax Bid Adapter: New bidder adapter (#11325) --- modules/performaxBidAdapter.js | 77 ++++++++ modules/performaxBidAdapter.md | 36 ++++ test/spec/modules/performaxBidAdapter_spec.js | 175 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 modules/performaxBidAdapter.js create mode 100644 modules/performaxBidAdapter.md create mode 100644 test/spec/modules/performaxBidAdapter_spec.js diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js new file mode 100644 index 00000000000..a765c4d9d78 --- /dev/null +++ b/modules/performaxBidAdapter.js @@ -0,0 +1,77 @@ +import { deepSetValue, deepAccess } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'performax'; +const BIDDER_SHORT_CODE = 'px'; +const GVLID = 732 +const ENDPOINT = 'https://dale.performax.cz/ortb' +export const converter = ortbConverter({ + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'tagid', bidRequest.params.tagid); + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + context.netRevenue = deepAccess(bid, 'netRevenue'); + context.mediaType = deepAccess(bid, 'mediaType'); + context.currency = deepAccess(bid, 'currency'); + + return buildBidResponse(bid, context) + }, + + context: { + ttl: 360, + } +}) + +export const spec = { + code: BIDDER_CODE, + aliases: [BIDDER_SHORT_CODE], + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.tagid; + }, + + buildRequests: function (bidRequests, bidderRequest) { + let data = converter.toORTB({bidderRequest, bidRequests}) + return [{ + method: 'POST', + url: ENDPOINT, + options: {'contentType': 'application/json'}, + data: data + }] + }, + + interpretResponse: function (bidderResponse, request) { + if (!bidderResponse.body) return []; + const response = bidderResponse.body + const data = { + + seatbid: response.seatbid.map(seatbid => ({ + seat: seatbid.seat, + bid: seatbid.bid.map(bid => ({ + impid: bid.imp_id, + w: bid.w, + h: bid.h, + requestId: request.data.id, + price: bid.price, + currency: response.cur, + adm: bid.adm, + crid: bid.id, + netRevenue: true, + mediaType: BANNER, + })) + })) + }; + return converter.fromORTB({ response: data, request: request.data }).bids + }, + +} + +registerBidder(spec); diff --git a/modules/performaxBidAdapter.md b/modules/performaxBidAdapter.md new file mode 100644 index 00000000000..8b5b702a8e6 --- /dev/null +++ b/modules/performaxBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Performax Bid Adapter +Module Type: Bidder Adapter +Maintainer: development@performax.cz +``` + +# Description + +Connects to Performax exchange for bids. + +Performax bid adapter supports Banner. + + +# Sample Banner Ad Unit: For Publishers + +```javascript + var adUnits = [ + { + code: 'performax-div', + mediaTypes: { + banner: {sizes: [[300, 300]]}, + }, + bids: [ + { + bidder: "performax", + params: { + tagid: "sample" // required + } + } + ] + }, + ]; +``` + diff --git a/test/spec/modules/performaxBidAdapter_spec.js b/test/spec/modules/performaxBidAdapter_spec.js new file mode 100644 index 00000000000..49a6a83e29d --- /dev/null +++ b/test/spec/modules/performaxBidAdapter_spec.js @@ -0,0 +1,175 @@ +import { expect } from 'chai'; +import { spec, converter } from 'modules/performaxBidAdapter.js'; + +describe('Performax adapter', function () { + let bids = [{ + bidder: 'performax', + params: { + tagid: 'sample' + }, + ortb2Imp: { + ext: {} + }, + mediaTypes: { + banner: { + sizes: [ + [300, 300], + ]}}, + adUnitCode: 'postbid_iframe', + transactionId: '84deda92-e9ba-4b0d-a797-43be5e522430', + adUnitId: '4ee4643b-931f-4a17-a571-ccba57886dc8', + sizes: [ + [300, 300], + ], + bidId: '2bc545c347dbbe', + bidderRequestId: '1534dec005b9a', + auctionId: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + source: {}, + site: {}, + device: {} + }, + }, + + { + bidder: 'performax', + params: { + tagid: '1545' + }, + ortb2Imp: { + ext: {} + }, + mediaTypes: { + banner: { + sizes: [ + [300, 600], + ]}}, + adUnitCode: 'postbid_halfpage_iframe', + transactionId: '84deda92-e9ba-4b0d-a797-43be5e522430', + adUnitId: '4ee4643b-931f-4a17-a571-ccba57886dc8', + sizes: [ + [300, 600], + ], + bidId: '3dd53d30c691fe', + bidderRequestId: '1534dec005b9a', + auctionId: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + source: {}, + site: {}, + device: {} + }}]; + + let bidderRequest = { + bidderCode: 'performax2', + auctionId: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + id: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + bidderRequestId: '1534dec005b9a', + bids: bids, + ortb2: { + regs: { + ext: { + gdpr: 1 + }}, + user: { + ext: { + consent: 'consent-string' + } + }, + site: {}, + device: {} + }}; + + let serverResponse = { + body: { + cur: 'CZK', + seatbid: [ + { + seat: 'performax', + bid: [ + { + id: 'sample', + price: 20, + w: 300, + h: 300, + adm: 'My ad' + } + ]}]}, + } + + describe('isBidRequestValid', function () { + let bid = {}; + it('should return false when missing "tagid" param', function() { + bid.params = {slotId: 'param'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when tagid is correct', function() { + bid.params = {tagid: 'sample'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }) + + describe('buildRequests', function () { + it('should set correct request method and url', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + expect(requests).to.be.an('array').that.has.lengthOf(1); + let request = requests[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://dale.performax.cz/ortb'); + expect(request.data).to.be.an('object'); + }); + + it('should pass correct imp', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(1); + expect(imp[0]).to.be.an('object'); + let bid = imp[0]; + expect(bid.id).to.equal('2bc545c347dbbe'); + expect(bid.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 300}]}); + }); + + it('should process multiple bids', function () { + let requests = spec.buildRequests(bids, bidderRequest); + expect(requests).to.be.an('array').that.has.lengthOf(1); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(bids.length); + let bid1 = imp[0]; + expect(bid1.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 300}]}); + let bid2 = imp[1]; + expect(bid2.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 600}]}); + }); + }); + + describe('interpretResponse', function () { + it('should map params correctly', function () { + let ortbRequest = {data: converter.toORTB({bidderRequest, bids})}; + serverResponse.body.id = ortbRequest.data.id; + serverResponse.body.seatbid[0].bid[0].imp_id = ortbRequest.data.imp[0].id; + + let result = spec.interpretResponse(serverResponse, ortbRequest); + expect(result).to.be.an('array').that.has.lengthOf(1); + let bid = result[0]; + + expect(bid.cpm).to.equal(20); + expect(bid.ad).to.equal('My ad'); + expect(bid.currency).to.equal('CZK'); + expect(bid.mediaType).to.equal('banner'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + expect(bid.creativeId).to.equal('sample'); + }); + }); +}); From 0b574b3adfaecf756ccb7233229541e74241f3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=BEidar=20Bare=C5=A1i=C4=87?= <153560835+bbaresic@users.noreply.github.com> Date: Tue, 7 May 2024 15:20:27 -0500 Subject: [PATCH 0023/1097] faster-deep-clone (#11418) --- allowedModules.js | 2 +- modules/browsiRtdProvider.js | 2 +- package-lock.json | 25 ++++++++++++++----------- package.json | 2 +- src/utils.js | 4 ++-- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/allowedModules.js b/allowedModules.js index 75ad4141a6c..bc9ada39571 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -7,7 +7,7 @@ module.exports = { ], 'src': [ 'fun-hooks/no-eval', - 'just-clone', + 'klona', 'dlv', 'dset' ], diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index ad4019ed03c..01a38c63b69 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -57,7 +57,7 @@ export function addBrowsiTag(data) { script.setAttribute('prebidbpt', 'true'); script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); - script.prebidData = deepClone(data); + script.prebidData = deepClone(typeof data === 'string' ? Object(data) : data) if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } diff --git a/package-lock.json b/package-lock.json index 261cbdb1fb7..9a373144ebd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", - "just-clone": "^1.0.2", + "klona": "^2.0.6", "live-connect-js": "^6.3.4" }, "devDependencies": { @@ -19198,11 +19198,6 @@ "node": ">=0.6.0" } }, - "node_modules/just-clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", - "integrity": "sha512-p93GINPwrve0w3HUzpXmpTl7MyzzWz1B5ag44KEtq/hP1mtK8lA2b9Q0VQaPlnY87352osJcE6uBmN0e8kuFMw==" - }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -19677,6 +19672,14 @@ "node": ">=6" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/konan": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", @@ -43994,11 +43997,6 @@ "verror": "1.10.0" } }, - "just-clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", - "integrity": "sha512-p93GINPwrve0w3HUzpXmpTl7MyzzWz1B5ag44KEtq/hP1mtK8lA2b9Q0VQaPlnY87352osJcE6uBmN0e8kuFMw==" - }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -44382,6 +44380,11 @@ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true }, + "klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==" + }, "konan": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", diff --git a/package.json b/package.json index acac0a99da7..28ce27dce05 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", - "just-clone": "^1.0.2", + "klona": "^2.0.6", "live-connect-js": "^6.3.4" }, "optionalDependencies": { diff --git a/src/utils.js b/src/utils.js index abb69209020..3225eac162e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,5 @@ import {config} from './config.js'; -import clone from 'just-clone'; +import {klona} from 'klona/json'; import {includes} from './polyfill.js'; import { EVENTS, S2S } from './constants.js'; import {GreedyPromise} from './utils/promise.js'; @@ -609,7 +609,7 @@ export function shuffle(array) { } export function deepClone(obj) { - return clone(obj); + return klona(obj) || {}; } export function inIframe() { From 3870124588e92a2728a4577ea73f5772223b4da3 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Wed, 8 May 2024 15:57:04 +0200 Subject: [PATCH 0024/1097] LiveIntent User ID Module: Stabilize Tests (#11463) * Filter for relevant events in tests * Fix expectation --- test/spec/modules/liveIntentIdSystem_spec.js | 83 +++++++++++--------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 084b4de212a..373142db82e 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -10,6 +10,18 @@ const PUBLISHER_ID = '89899'; const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; const responseHeader = {'Content-Type': 'application/json'} +function requests(...urlRegExps) { + return server.requests.filter((request) => urlRegExps.some((regExp) => request.url.match(regExp))) +} + +function rpRequests() { + return requests(/https:\/\/rp.liadm.com.*/) +} + +function idxRequests() { + return requests(/https:\/\/idx.liadm.com.*/) +} + describe('LiveIntentId', function() { let logErrorStub; let uspConsentDataStub; @@ -57,7 +69,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1%2C2.*/); const response = { unifiedId: 'a_unified_id', @@ -83,7 +95,7 @@ describe('LiveIntentId', function() { }) liveIntentIdSubmodule.getId(defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 300); }); @@ -94,7 +106,7 @@ describe('LiveIntentId', function() { emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) done(); }, 300); }); @@ -102,7 +114,7 @@ describe('LiveIntentId', function() { it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.contain('tv=$prebid.version$') + expect(rpRequests()[0].url).to.contain('tv=$prebid.version$') done(); }, 300); }); @@ -119,7 +131,7 @@ describe('LiveIntentId', function() { } }}); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(requests(/https:\/\/collector.liveintent.com.*/)[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); done(); }, 300); }); @@ -127,7 +139,7 @@ describe('LiveIntentId', function() { it('should fire an event with the provided distributorId', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111' } }); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); done(); }, 300); }); @@ -135,8 +147,9 @@ describe('LiveIntentId', function() { it('should fire an event without the provided distributorId when appId is provided', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); - expect(server.requests[0].url).to.not.match(/.*did=*/); + const request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(request.url).to.not.match(/.*did=*/); done(); }, 300); }); @@ -153,7 +166,7 @@ describe('LiveIntentId', function() { }) liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); + expect(rpRequests()[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 300); }); @@ -164,7 +177,7 @@ describe('LiveIntentId', function() { emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); done(); }, 300); }); @@ -177,7 +190,7 @@ describe('LiveIntentId', function() { it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.be.not.null + expect(rpRequests()[0].url).to.be.not.null done(); }, 300); }); @@ -188,7 +201,7 @@ describe('LiveIntentId', function() { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests.length).to.be.eq(1); + expect(rpRequests().length).to.be.eq(1); done(); }, 300); }); @@ -198,7 +211,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 204, @@ -212,7 +225,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&cd=.localhost&resolve=nonId'); request.respond( 204, @@ -226,7 +239,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?cd=.localhost&resolve=nonId'); request.respond( 204, @@ -246,7 +259,7 @@ describe('LiveIntentId', function() { } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?cd=.localhost&resolve=nonId'); request.respond( 200, @@ -261,7 +274,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 200, @@ -276,7 +289,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 503, @@ -293,7 +306,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&resolve=nonId`); request.respond( 200, @@ -316,7 +329,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, @@ -338,7 +351,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, @@ -355,12 +368,12 @@ describe('LiveIntentId', function() { }); it('should decode a unifiedId to lipbId and remove it', function() { - const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }); + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); }); it('should decode a nonId to lipbId', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'data' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); }); @@ -371,7 +384,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId&resolve=foo`); request.respond( 200, @@ -382,54 +395,54 @@ describe('LiveIntentId', function() { }); it('should decode a uid2 to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with uid2 but no nonId', function() { - const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a medianet id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a sovrn id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a magnite id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an index id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an openx id to a separate object when present', function () { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an pubmatic id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a thetradedesk id to a separate object when present', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); @@ -440,7 +453,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=uid2`); request.respond( 200, From 5438945e9d8de671a2b15db2f5fee1b9aa5b885d Mon Sep 17 00:00:00 2001 From: Ruslan S Date: Wed, 8 May 2024 22:29:22 +0800 Subject: [PATCH 0025/1097] Smaato: Change server response type (#11450) --- modules/smaatoBidAdapter.js | 46 ++++---------- test/spec/modules/smaatoBidAdapter_spec.js | 74 ++-------------------- 2 files changed, 18 insertions(+), 102 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 9a0a0f75623..8f325aa13c9 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -19,7 +19,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_2.0' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.0' const TTL = 300; const CURRENCY = 'USD'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; @@ -160,12 +160,8 @@ export const spec = { } else { switch (smtAdType) { case 'Img': - resultingBid.ad = createImgAd(bid.adm); - resultingBid.mediaType = BANNER; - bids.push(resultingBid); - break; case 'Richmedia': - resultingBid.ad = createRichmediaAd(bid.adm); + resultingBid.ad = createBannerAd(bid); resultingBid.mediaType = BANNER; bids.push(resultingBid); break; @@ -370,37 +366,17 @@ const converter = ortbConverter({ } }); -const createImgAd = (adm) => { - const image = JSON.parse(adm).image; - +const createBannerAd = (bid) => { let clickEvent = ''; - image.clicktrackers.forEach(src => { - clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; - }) - - let markup = `
`; - - image.impressiontrackers.forEach(src => { - markup += ``; - }); - - return markup + '
'; -}; - -const createRichmediaAd = (adm) => { - const rich = JSON.parse(adm).richmedia; - let clickEvent = ''; - rich.clicktrackers.forEach(src => { - clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; - }) - - let markup = `
${rich.mediadata.content}`; - - rich.impressiontrackers.forEach(src => { - markup += ``; - }); + if (bid.ext && bid.ext.curls) { + let clicks = '' + bid.ext.curls.forEach(src => { + clicks += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + clickEvent = `onclick="${clicks}"` + } - return markup + '
'; + return `
${bid.adm}
`; }; const createNativeAd = (adm) => { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 28fd06c4b8d..2ac2a1e5c33 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -13,7 +13,6 @@ import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; const ADTYPE_IMG = 'Img'; -const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; const ADTYPE_NATIVE = 'Native'; @@ -1300,43 +1299,7 @@ describe('smaatoBidAdapterTest', () => { switch (adType) { case ADTYPE_IMG: - adm = JSON.stringify( - { - image: { - img: { - url: 'https://prebid/static/ad.jpg', - w: 320, - h: 50, - ctaurl: 'https://prebid/track/ctaurl' - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } - }); - break; - case ADTYPE_RICHMEDIA: - adm = JSON.stringify( - { - richmedia: { - mediadata: { - content: '

RICHMEDIA CONTENT

', - w: 800, - h: 600 - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } - }); + adm = '' break; case ADTYPE_VIDEO: adm = ''; @@ -1372,7 +1335,10 @@ describe('smaatoBidAdapterTest', () => { 'nurl': 'https://prebid/nurl', 'price': 0.01, 'w': 350, - 'h': 50 + 'h': 50, + 'ext': { + curls: ['https://prebid/track/click/1'] + } } ], seat: 'CM6523' @@ -1398,7 +1364,7 @@ describe('smaatoBidAdapterTest', () => { }); describe('non ad pod', () => { - it('single image response', () => { + it('single banner response', () => { const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_IMG), buildBidRequest()); expect(bids).to.deep.equal([ @@ -1407,33 +1373,7 @@ describe('smaatoBidAdapterTest', () => { cpm: 0.01, width: 350, height: 50, - ad: '
', - ttl: 300, - creativeId: 'CR69381', - dealId: '12345', - netRevenue: true, - currency: 'USD', - mediaType: 'banner', - meta: { - advertiserDomains: ['smaato.com'], - agencyId: 'CM6523', - networkName: 'smaato', - mediaType: 'banner' - } - } - ]); - }); - - it('single richmedia response', () => { - const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_RICHMEDIA), buildBidRequest()); - - expect(bids).to.deep.equal([ - { - requestId: '226416e6e6bf41', - cpm: 0.01, - width: 350, - height: 50, - ad: '

RICHMEDIA CONTENT

', + ad: '
', ttl: 300, creativeId: 'CR69381', dealId: '12345', From 8c5c9d52ef996d90958b41b02808677e0ff7f4bb Mon Sep 17 00:00:00 2001 From: Giuseppe Cera <117671343+giuseppe-exads@users.noreply.github.com> Date: Wed, 8 May 2024 17:10:03 +0100 Subject: [PATCH 0026/1097] EXADS Bid Adapter : update bidder code before adapter is published (#11464) * First commit * fix: readme.md * fix: changed exads urls * fix: Tools and suggestions related to the doc * fix: from code review * fix: from code review * fix: from code review * fix: error from code review - native example * fox: from code review * fix: from code review * fix: from code review * fix: native img set as mandatory * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: bidfloor and bidfloorcur set as optional * fix: dsa * fix: mananing multiple responses * fix: unit test after code review * fix: fixing native snippet code * fix: from code review * fix: video events after code review * fix: video module into documentation * fix: impression tracker for native * fix: afeter code review * fix: unit tests * fix: added badv and bcat * fix: video -> mimes and protocols * fix * fix: removed image_output and video_output params, forcing always html for rtb banner * fix: gulp * fix: added site.name * fix: removed EXADS dir * fix: after linting * fix: unit tests * fix: final dsa solution * fix: dsa * fix: fix instream example * fix: doc media type context * fix: documented the endpoint param into native section * fix: related to markdown lint validation (#2) * fix: from CR (#3) * fix: changed bidder code to exads * fix: userAgent --------- Co-authored-by: tfoliveira --- modules/exadsBidAdapter.js | 9 ++++----- modules/exadsBidAdapter.md | 8 ++++---- test/spec/modules/exadsBidAdapter_spec.js | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/exadsBidAdapter.js b/modules/exadsBidAdapter.js index 20ead7a7e6b..e50c141f4b0 100644 --- a/modules/exadsBidAdapter.js +++ b/modules/exadsBidAdapter.js @@ -2,7 +2,7 @@ import * as utils from '../src/utils.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -const BIDDER = 'exadsadserver'; +const BIDDER = 'exads'; const PARTNERS = { ORTB_2_4: 'ortb_2_4' @@ -332,12 +332,11 @@ function getEnvParams() { envParams.osName = 'MacOS'; } else if (envParams.userAgent.match(/Unix/i)) { envParams.osName = 'Unix'; - // TODO: what is userAgent.userAgent supposed to be? - } else if (envParams.userAgent.userAgent?.match(/Android/i)) { + } else if (envParams.userAgent.match(/Android/i)) { envParams.osName = 'Android'; - } else if (envParams.userAgent.userAgent?.match(/iPhone|iPad|iPod/i)) { + } else if (envParams.userAgent.match(/iPhone|iPad|iPod/i)) { envParams.osName = 'iOS'; - } else if (envParams.userAgent.userAgent?.match(/Linux/i)) { + } else if (envParams.userAgent.match(/Linux/i)) { envParams.osName = 'Linux'; } else { envParams.osName = 'Unknown'; diff --git a/modules/exadsBidAdapter.md b/modules/exadsBidAdapter.md index 06b873d8da8..4c8eedffdd0 100644 --- a/modules/exadsBidAdapter.md +++ b/modules/exadsBidAdapter.md @@ -123,7 +123,7 @@ adUnits = } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', @@ -162,7 +162,7 @@ adUnits = } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', @@ -237,7 +237,7 @@ adUnits = [{ } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', @@ -365,7 +365,7 @@ adUnits = [{ } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', diff --git a/test/spec/modules/exadsBidAdapter_spec.js b/test/spec/modules/exadsBidAdapter_spec.js index 9253f21ddf1..ca24dad3959 100644 --- a/test/spec/modules/exadsBidAdapter_spec.js +++ b/test/spec/modules/exadsBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec, imps } from 'modules/exadsBidAdapter.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('exadsBidAdapterTest', function () { - const bidder = 'exadsadserver'; + const bidder = 'exads'; const partners = { ORTB_2_4: 'ortb_2_4' From 85fd44e1e29939bbda2614553fa9ff787579cdbc Mon Sep 17 00:00:00 2001 From: Harry King-Riches <109534328+harrykingriches@users.noreply.github.com> Date: Wed, 8 May 2024 18:49:47 +0100 Subject: [PATCH 0027/1097] Rubicon Bid Adapter: Provide backwards compatibility for transparency object (#11426) * Provide backwards compatibility for transparency object When the property `transparency.params` gets passed in the `ortb2` object, the Rubicon adapter throws an error. This change will provide backwards compatibility for the `params` property by adding it to the `dsaparams` property. If `params` or `dsaparams` does not exist, the value will be turned into an empty string. The same is done for the `domain` property. This ensures that the Bid Adapter will not error if these properties do not exist. * Refactor RubiconBidAdapter to fix transparency object compatibility * Fix transparency object compatibility in RubiconBidAdapter * Fix transparency object compatibility in RubiconBidAdapter * Refactor dsatransparency check --------- Co-authored-by: Harry King-Riches <109534328+Strife9224@users.noreply.github.com> --- modules/rubiconBidAdapter.js | 18 ++++++- test/spec/modules/rubiconBidAdapter_spec.js | 58 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index c03065cd5a5..9e47807bdc0 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -982,11 +982,25 @@ function applyFPD(bidRequest, mediaType, data) { 'transparency', (transparency) => { if (Array.isArray(transparency) && transparency.length) { data['dsatransparency'] = transparency.reduce((param, transp) => { + // make sure domain is there, otherwise skip entry + const domain = transp.domain || ''; + if (!domain) { + return param; + } + + // make sure dsaParam array is there (try both 'dsaparams' and 'params', but prefer dsaparams) + const dsaParamArray = transp.dsaparams || transp.params; + if (!Array.isArray(dsaParamArray) || dsaParamArray.length === 0) { + return param; + } + + // finally we will add this one, if param has been added already, add our seperator if (param) { param += '~~' } - return param += `${transp.domain}~${transp.dsaparams.join('_')}` - }, '') + + return param += `${domain}~${dsaParamArray.join('_')}`; + }, ''); } } ]) diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 55e8909f6c8..494943f9f7d 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1735,6 +1735,64 @@ describe('the rubicon adapter', function () { } } } + it('should send valid dsaparams but filter out invalid ones', function () { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + ortb2Clone.regs.ext.dsa.transparency = [ + { + domain: 'testdomain.com', + dsaparams: [1], + }, + { + domain: '', + dsaparams: [2], + } + ]; + + const expectedTransparency = 'testdomain.com~1'; + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({ ...b, ortb2: ortb2Clone })), bidderRequest); + const data = parseQuery(request.data); + + expect(data['dsatransparency']).to.equal(expectedTransparency); + }) + it('should send dsaparams if \"ortb2.regs.ext.dsa.transparancy[0].params\"', function() { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + + ortb2Clone.regs.ext.dsa.transparency = [{ + domain: 'testdomain.com', + dsaparams: [1], + }]; + + const expectedTransparency = 'testdomain.com~1'; + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); + const data = parseQuery(request.data); + + expect(data['dsatransparency']).to.equal(expectedTransparency); + }) + it('should pass an empty transparency param if \"ortb2.regs.ext.dsa.transparency[0].params\" is empty', function() { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + + ortb2Clone.regs.ext.dsa.transparency = [{ + domain: 'testdomain.com', + params: [], + }]; + + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); + const data = parseQuery(request.data); + expect(data['dsatransparency']).to.be.undefined + }) + it('should send an empty transparency if \"ortb2.regs.ext.dsa.transparency[0].domain\" is empty', function() { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + + ortb2Clone.regs.ext.dsa.transparency = [{ + domain: '', + dsaparams: [1], + }]; + + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); + const data = parseQuery(request.data); + + expect(data['dsatransparency']).to.be.undefined + }) it('should send dsa signals if \"ortb2.regs.ext.dsa\"', function() { const expectedTransparency = 'testdomain.com~1~~testdomain2.com~1_2' const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest) From b76cddda19812501f67772296e4936cc216ec3f3 Mon Sep 17 00:00:00 2001 From: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com> Date: Wed, 8 May 2024 22:36:40 +0300 Subject: [PATCH 0028/1097] Playdigo: new adapter (#11378) * init adapter * add gpp support * upd --- modules/playdigoBidAdapter.js | 199 +++++++++ modules/playdigoBidAdapter.md | 78 ++++ test/spec/modules/playdigoBidAdapter_spec.js | 446 +++++++++++++++++++ 3 files changed, 723 insertions(+) create mode 100644 modules/playdigoBidAdapter.js create mode 100644 modules/playdigoBidAdapter.md create mode 100644 test/spec/modules/playdigoBidAdapter_spec.js diff --git a/modules/playdigoBidAdapter.js b/modules/playdigoBidAdapter.js new file mode 100644 index 00000000000..6c4ea6492d9 --- /dev/null +++ b/modules/playdigoBidAdapter.js @@ -0,0 +1,199 @@ +import { logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'playdigo'; +const AD_URL = 'https://server.playdigo.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!bid.getFloor || typeof bid.getFloor !== 'function') { + return 0; + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + } +}; + +registerBidder(spec); diff --git a/modules/playdigoBidAdapter.md b/modules/playdigoBidAdapter.md new file mode 100644 index 00000000000..1c63cce79a1 --- /dev/null +++ b/modules/playdigoBidAdapter.md @@ -0,0 +1,78 @@ +# Overview + +``` +Module Name: Playdigo Bidder Adapter +Module Type: Playdigo Bidder Adapter +Maintainer: yr@playdigo.com +``` + +# Description + +One of the easiest way to gain access to Playdigo demand sources - Playdigo header bidding adapter. +Playdigo header bidding adapter connects with Playdigo demand sources to fetch bids for display placements + +# Test Parameters +``` + var adUnits = [ + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'playdigo', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'playdigo', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'playdigo', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js new file mode 100644 index 00000000000..80fc3c96e81 --- /dev/null +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -0,0 +1,446 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/playdigoBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'playdigo' + +describe('PlaydigoBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + } + } + ] + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 3c814105d86e66dcde8353754493840f953ebd8d Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 8 May 2024 22:38:07 +0300 Subject: [PATCH 0029/1097] Twist Digital Bid Adapter: initial release (#11370) * Added twist digital bid adapter. * Add maintainer email to twistDigitalBidAdapter * Update default subdomain in twistDigitalBidAdapter. * remove unneeded dealId. --- modules/twistDigitalBidAdapter.js | 463 +++++++++ modules/twistDigitalBidAdapter.md | 42 + .../modules/twistDigitalBidAdapter_spec.js | 937 ++++++++++++++++++ 3 files changed, 1442 insertions(+) create mode 100644 modules/twistDigitalBidAdapter.js create mode 100644 modules/twistDigitalBidAdapter.md create mode 100644 test/spec/modules/twistDigitalBidAdapter_spec.js diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js new file mode 100644 index 00000000000..f509e68f9a2 --- /dev/null +++ b/modules/twistDigitalBidAdapter.js @@ -0,0 +1,463 @@ +import { + _each, + deepAccess, + isFn, + parseSizesInput, + parseUrl, + uniques, + isArray, + formatQS, + triggerPixel +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; +import {config} from '../src/config.js'; +import {chunk} from '../libraries/chunk/chunk.js'; + +const GVLID = 1292; +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'twistdigital'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; + +export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.twist.win`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const pId = extractPID(params); + const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); + const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); + const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); + const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + isStorageAllowed: isStorageAllowed, + gpid: gpid, + cat: cat, + contentData, + userData: userData, + pagecat: pagecat, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout, + webSessionId: webSessionId + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (bidderRequest.fledgeEnabled) { + const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); + if (fledge) { + data.fledge = fledge; + } + } + + _each(ext, (value, key) => { + data['ext.' + key] = value; + }); + + return data; +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const {params} = bid; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout); + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + return dto; +} + +function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { + const {params} = bidRequests[0]; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = bidRequests.map(bid => { + const sizes = parseSizesInput(bid.sizes); + return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) + }); + const chunkSize = Math.min(20, config.getConfig('twistdigital.chunkSize') || 10); + + const chunkedData = chunk(data, chunkSize); + return chunkedData.map(chunk => { + return { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: { + bids: chunk + } + }; + }); +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + + const singleRequestMode = config.getConfig('twistdigital.singleRequest'); + + const requests = []; + + if (singleRequestMode) { + // banner bids are sent as a single request + const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); + if (bannerBidRequests.length > 0) { + const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); + requests.push(...singleRequests); + } + + // video bids are sent as a single request for each bid + + const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); + videoBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } else { + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + + const singleRequestMode = config.getConfig('twistdigital.singleRequest'); + const reqBidId = deepAccess(request, 'data.bidId'); + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach((result, i) => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + bidId, + nurl, + advertiserDomains, + metaData, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: (singleRequestMode && bidId) ? bidId : reqBidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (nurl) { + response.nurl = nurl; + } + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + const {gppString, applicableSections} = gppConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; + + if (gppString && applicableSections?.length) { + params += '&gpp=' + encodeURIComponent(gppString); + params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); + } + + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.twist.win/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.twist.win/api/sync/image/${params}` + }); + } + return syncs; +} + +/** + * @param {Bid} bid + */ +function onBidWon(bid) { + if (!bid.nurl) { + return; + } + const wonBid = { + adId: bid.adId, + creativeId: bid.creativeId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + netRevenue: bid.netRevenue, + mediaType: bid.mediaType, + timeToRespond: bid.timeToRespond, + status: bid.status, + }; + const qs = formatQS(wonBid); + const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; + triggerPixel(url); +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon +}; + +registerBidder(spec); diff --git a/modules/twistDigitalBidAdapter.md b/modules/twistDigitalBidAdapter.md new file mode 100644 index 00000000000..8722608c5dd --- /dev/null +++ b/modules/twistDigitalBidAdapter.md @@ -0,0 +1,42 @@ +# Overview + +**Module Name:** Twist Digital Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** yoni@twist.win + + + + + + + + +# Description + +Module that connects to Twist Digital demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'twistdigital', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js new file mode 100644 index 00000000000..7d263f6d4f0 --- /dev/null +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -0,0 +1,937 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, + webSessionId +} from 'modules/twistDigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import {deepSetValue} from 'src/utils.js'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'openrtb'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'gpid': '1234567890' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + ortb2Imp: { + ext: { + tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + } + }, + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'site': { + 'cat': ['IAB2'], + 'pagecat': ['IAB2-2'], + 'content': { + 'data': [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }] + } + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + }, + user: { + data: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], + } + }, +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'bidId': '2d52001cabd527-response', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['twistdigital.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +} + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('TwistDigitalBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true, + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cat: ['IAB2'], + pagecat: ['IAB2-2'], + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', + gpid: '', + prebidVersion: version, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + contentData: [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }], + userData: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + isStorageAllowed: true, + webSessionId: webSessionId, + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }) + ; + }); + + it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + isStorageAllowed: true, + gpid: '1234567890', + cat: ['IAB2'], + pagecat: ['IAB2-2'], + contentData: [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }], + userData: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], + webSessionId: webSessionId + } + }); + }); + + it('should build single banner request for multiple bids', function () { + config.setConfig({ + bidderTimeout: 3000, + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + + const BID2 = utils.deepClone(BID); + BID2.bidId = '2d52001cabd528'; + BID2.adUnitCode = 'div-gpt-ad-12345-1'; + BID2.sizes = [[300, 250]]; + + const REQUEST_DATA = { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + isStorageAllowed: true, + gpid: '1234567890', + cat: ['IAB2'], + pagecat: ['IAB2-2'], + contentData: [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }], + userData: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], + webSessionId: webSessionId + }; + + const REQUEST_DATA2 = utils.deepClone(REQUEST_DATA); + REQUEST_DATA2.bidId = '2d52001cabd528'; + REQUEST_DATA2.adUnitCode = 'div-gpt-ad-12345-1'; + REQUEST_DATA2.sizes = ['300x250']; + + const requests = adapter.buildRequests([BID, BID2], BIDDER_REQUEST); + expect(requests).to.have.length(1); + + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: {bids: [REQUEST_DATA, REQUEST_DATA2]} + }); + }); + + it('should return seperated requests for video and banner if singleRequest is true', function () { + config.setConfig({ + bidderTimeout: 3000, + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const requests = adapter.buildRequests([BID, VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(2); + }); + + it('should chunk requests if requests exceed chunkSize and singleRequest is true', function () { + config.setConfig({ + bidderTimeout: 3000, + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const requests = adapter.buildRequests([BID, BID, BID, BID], BIDDER_REQUEST); + expect(requests).to.have.length(2); + }); + + it('should set fledge correctly if enabled', function () { + config.resetConfig(); + const bidderRequest = utils.deepClone(BIDDER_REQUEST); + bidderRequest.fledgeEnabled = true; + deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); + const requests = adapter.buildRequests([BID], bidderRequest); + expect(requests[0].data.fledge).to.equal(1); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + config.resetConfig(); + sandbox.restore(); + }); + }); + + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.twist.win/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['twistdigital.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['twistdigital.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['twistdigital.com'] + } + }); + }); + + it('should populate requestId from response in case of singleRequest true', function () { + config.setConfig({ + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].requestId).to.equal('2d52001cabd527-response'); + + config.resetConfig(); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + + it('should add nurl if exists on response', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].nurl = 'https://test.com/win-notice?test=123'; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].nurl).to.equal('https://test.com/win-notice?test=123'); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); + + describe('validate onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('should call triggerPixel if nurl exists', function () { + const bid = { + adUnitCode: 'div-gpt-ad-12345-0', + adId: '2d52001cabd527', + auctionId: '1fdb5ff1b6eaa7', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + status: 'rendered', + timeToRespond: 100, + cpm: 0.8, + originalCpm: 0.8, + creativeId: '12610997325162499419', + currency: 'USD', + originalCurrency: 'USD', + height: 250, + mediaType: 'banner', + nurl: 'https://test.com/win-notice?test=123', + netRevenue: true, + requestId: '2d52001cabd527', + ttl: 30, + width: 300 + }; + adapter.onBidWon(bid); + expect(utils.triggerPixel.called).to.be.true; + + const url = utils.triggerPixel.args[0]; + + expect(url[0]).to.be.equal('https://test.com/win-notice?test=123&adId=2d52001cabd527&creativeId=12610997325162499419&auctionId=1fdb5ff1b6eaa7&transactionId=c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf&adUnitCode=div-gpt-ad-12345-0&cpm=0.8¤cy=USD&originalCpm=0.8&originalCurrency=USD&netRevenue=true&mediaType=banner&timeToRespond=100&status=rendered'); + }); + + it('should not call triggerPixel if nurl does not exist', function () { + const bid = { + adUnitCode: 'div-gpt-ad-12345-0', + adId: '2d52001cabd527', + auctionId: '1fdb5ff1b6eaa7', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + status: 'rendered', + timeToRespond: 100, + cpm: 0.8, + originalCpm: 0.8, + creativeId: '12610997325162499419', + currency: 'USD', + originalCurrency: 'USD', + height: 250, + mediaType: 'banner', + netRevenue: true, + requestId: '2d52001cabd527', + ttl: 30, + width: 300 + }; + adapter.onBidWon(bid); + expect(utils.triggerPixel.called).to.be.false; + }); + }); +}); From 3c4ebab30c514d5f20b9ceca175e20f6cae66668 Mon Sep 17 00:00:00 2001 From: Jorge Algaba Aranda Date: Thu, 9 May 2024 16:11:31 +0200 Subject: [PATCH 0030/1097] Telaria bid adapter typo (#11471) --- modules/telariaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/telariaBidAdapter.md b/modules/telariaBidAdapter.md index 6a5e24e9a5e..3a6c86ec2f6 100644 --- a/modules/telariaBidAdapter.md +++ b/modules/telariaBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: github@telaria.com Connects to Telaria's exchange. -Telaria bid adapter supports insteream Video. +Telaria bid adapter supports instream Video. # Test Parameters ``` From c902d6ccf58b8979bc7502a0095df1438136745c Mon Sep 17 00:00:00 2001 From: Love Sharma Date: Thu, 9 May 2024 10:23:21 -0400 Subject: [PATCH 0031/1097] chore: code cleanup [PB-2828] (#11468) Co-authored-by: Love Sharma --- modules/ixBidAdapter.js | 153 +--------------------- test/spec/modules/ixBidAdapter_spec.js | 174 +------------------------ 2 files changed, 8 insertions(+), 319 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 248dbea0046..f56e2790ad6 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -18,15 +18,12 @@ import { } from '../src/utils.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import { EVENTS } from '../src/constants.js'; import { getStorageManager } from '../src/storageManager.js'; -import * as events from '../src/events.js'; import { find } from '../src/polyfill.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; @@ -49,17 +46,6 @@ const PRICE_TO_DOLLAR_FACTOR = { const IFRAME_USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; const IMG_USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid'; -export const ERROR_CODES = { - BID_SIZE_INVALID_FORMAT: 1, - BID_SIZE_NOT_INCLUDED: 2, - PROPERTY_NOT_INCLUDED: 3, - SITE_ID_INVALID_VALUE: 4, - BID_FLOOR_INVALID_FORMAT: 5, - IX_FPD_EXCEEDS_MAX_SIZE: 6, - EXCEEDS_MAX_SIZE: 7, - PB_FPD_EXCEEDS_MAX_SIZE: 8, - VIDEO_DURATION_INVALID: 9 -}; const FIRST_PARTY_DATA = { SITE: [ 'id', 'name', 'domain', 'cat', 'sectioncat', 'pagecat', 'page', 'ref', 'search', 'mobile', @@ -110,7 +96,6 @@ const VIDEO_PARAMS_ALLOW_LIST = [ ]; const LOCAL_STORAGE_KEY = 'ixdiag'; export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; -let hasRegisteredHandler = false; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { // Update with list of CFTs to be requested from Exchange @@ -262,8 +247,7 @@ export function bidToVideoImp(bid) { if (imp.video.minduration > imp.video.maxduration) { logError( - `IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`, - { bidder: BIDDER_CODE, code: ERROR_CODES.VIDEO_DURATION_INVALID } + `IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]` ); return {}; } @@ -883,13 +867,6 @@ function enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids r.ext.ixdiag.syncsPerBidder = config.getConfig('userSync').syncsPerBidder; } - // Get cached errors stored in LocalStorage - const cachedErrors = getCachedErrors(); - - if (!isEmpty(cachedErrors)) { - r.ext.ixdiag.err = cachedErrors; - } - // Add number of available imps to ixDiag. r.ext.ixdiag.imps = Object.keys(impressions).length; @@ -1546,104 +1523,6 @@ function createMissingBannerImp(bid, imp, newSize) { return newImp; } -/** - * @typedef {Array[message: string, err: Object]} ErrorData - * @property {string} message - The error message. - * @property {object} err - The error object. - * @property {string} err.bidder - The bidder of the error. - * @property {string} err.code - The error code. - */ - -/** - * Error Event handler that receives type and arguments in a data object. - * - * @param {ErrorData} data - */ -function storeErrorEventData(data) { - if (!storage.localStorageIsEnabled()) { - return; - } - - let currentStorage; - - try { - currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); - } catch (e) { - logWarn('ix can not read ixdiag from localStorage.'); - } - - const todayDate = new Date(); - - Object.keys(currentStorage).map((errorDate) => { - const date = new Date(errorDate); - - if (date.setDate(date.getDate() + 7) - todayDate < 0) { - delete currentStorage[errorDate]; - } - }); - - if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { - const todayString = todayDate.toISOString().slice(0, 10); - - const errorCode = data.arguments[1].code; - - if (errorCode) { - currentStorage[todayString] = currentStorage[todayString] || {}; - - if (!Number(currentStorage[todayString][errorCode])) { - currentStorage[todayString][errorCode] = 0; - } - - currentStorage[todayString][errorCode]++; - }; - } - - storage.setDataInLocalStorage(LOCAL_STORAGE_KEY, JSON.stringify(currentStorage)); -} - -/** - * Event handler for storing data into local storage. It will only store data if - * local storage premissions are avaliable - */ -function localStorageHandler(data) { - if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { - storeErrorEventData(data); - } -} - -/** - * Get ixdiag stored in LocalStorage and format to be added to request payload - * - * @returns {Object} Object with error codes and counts - */ -function getCachedErrors() { - if (!storage.localStorageIsEnabled()) { - return; - } - - const errors = {}; - let currentStorage; - - try { - currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); - } catch (e) { - logError('ix can not read ixdiag from localStorage.'); - return null; - } - - Object.keys(currentStorage).forEach((date) => { - Object.keys(currentStorage[date]).forEach((code) => { - if (typeof currentStorage[date][code] === 'number') { - errors[code] = errors[code] - ? errors[code] + currentStorage[date][code] - : currentStorage[date][code]; - } - }); - }); - - return errors; -} - /** * * Initialize IX Outstream Renderer @@ -1738,12 +1617,6 @@ export const spec = { * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (!hasRegisteredHandler) { - events.on(EVENTS.AUCTION_DEBUG, localStorageHandler); - events.on(EVENTS.AD_RENDER_FAILED, localStorageHandler); - hasRegisteredHandler = true; - } - const paramsVideoRef = deepAccess(bid, 'params.video'); const paramsSize = deepAccess(bid, 'params.size'); const mediaTypeBannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); @@ -1765,14 +1638,14 @@ export const spec = { // since there is an ix bidder level size, make sure its valid const ixSize = getFirstSize(paramsSize); if (!ixSize) { - logError('IX Bid Adapter: size has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_INVALID_FORMAT }); + logError('IX Bid Adapter: size has invalid format.'); return false; } // check if the ix bidder level size, is present in ad unit level if (!includesSize(bid.sizes, ixSize) && !(includesSize(mediaTypeVideoPlayerSize, ixSize)) && !(includesSize(mediaTypeBannerSizes, ixSize))) { - logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_NOT_INCLUDED }); + logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.'); return false; } } @@ -1784,19 +1657,19 @@ export const spec = { if (bid.params.siteId !== undefined) { if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { - logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + logError('IX Bid Adapter: siteId must be string or number type.'); return false; } if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) { - logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + logError('IX Bid Adapter: siteId must valid value'); return false; } } if (hasBidFloor || hasBidFloorCur) { if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { - logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_FLOOR_INVALID_FORMAT }); + logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.'); return false; } } @@ -1815,7 +1688,7 @@ export const spec = { if (errorList.length) { errorList.forEach((err) => { - logError(err, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); + logError(err); }); return false; } @@ -1997,18 +1870,6 @@ export const spec = { } }, - /** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ - transformBidParams: function (params, isOpenRtb) { - return convertTypes({ - 'siteID': 'number' - }, params); - }, - /** * Determine which user syncs should occur * @param {object} syncOptions diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 7655868ffc3..88cfb93850f 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,7 +2,7 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo } from '../../../modules/ixBidAdapter.js'; +import { spec, storage, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo } from '../../../modules/ixBidAdapter.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { @@ -4867,178 +4867,6 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('LocalStorage error codes', () => { - let TODAY = new Date().toISOString().slice(0, 10); - const key = 'ixdiag'; - - let sandbox; - let setDataInLocalStorageStub; - let getDataFromLocalStorageStub; - let removeDataFromLocalStorageStub; - let localStorageValues = {}; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - setDataInLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage').callsFake((key, value) => localStorageValues[key] = value) - getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => localStorageValues[key]) - removeDataFromLocalStorageStub = sandbox.stub(storage, 'removeDataFromLocalStorage').callsFake((key) => delete localStorageValues[key]) - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - }); - - afterEach(() => { - setDataInLocalStorageStub.restore(); - getDataFromLocalStorageStub.restore(); - removeDataFromLocalStorageStub.restore(); - localStorageValues = {}; - sandbox.restore(); - - config.setConfig({ - ortb2: {}, - ix: {}, - }) - }); - - it('should not log error in LocalStorage when there is no logError called.', () => { - const bid = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; - expect(spec.isBidRequestValid(bid)).to.be.true; - expect(localStorageValues[key]).to.be.undefined; - }); - - it('should log ERROR_CODES.BID_SIZE_INVALID_FORMAT in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); - }); - - it('should log ERROR_CODES.BID_SIZE_NOT_INCLUDED in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = [407, 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_NOT_INCLUDED]: 1 } }); - }); - - it('should log ERROR_CODES.PROPERTY_NOT_INCLUDED in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video = {}; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); - }); - - it('should log ERROR_CODES.SITE_ID_INVALID_VALUE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.siteId = false; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 } }); - }); - - it('should log ERROR_CODES.BID_FLOOR_INVALID_FORMAT in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.bidFloor = true; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); - }); - - it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video.minduration = 1; - bid.params.video.maxduration = 0; - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid]); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 2 } }); - }); - - it('should increment errors for errorCode', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video = {}; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 8 } }); - }); - - it('should add new errorCode to ixdiag.', () => { - let bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); - - bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.siteId = false; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ - [TODAY]: { - [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1, - [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 - } - }); - }); - - it('should clear errors with successful response', () => { - const ixdiag = { [TODAY]: { '1': 1, '3': 8, '4': 1 } }; - setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); - - expect(JSON.parse(localStorageValues[key])).to.deep.equal(ixdiag); - - const request = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; - expect(spec.isBidRequestValid(request)).to.be.true; - - const data = { - id: '345', - imp: [ - { - id: '1a2b3c4e', - } - ], - ext: { - ixdiag: { - err: { - '4': 8 - } - } - } - }; - - const validBidRequest = DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0]; - - spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data, validBidRequest }); - - expect(localStorageValues[key]).to.be.undefined; - }); - - it('should clear errors after 7 day expiry errorCode', () => { - const EXPIRED_DATE = '2019-12-12'; - - const ixdiag = { [EXPIRED_DATE]: { '1': 1, '3': 8, '4': 1 }, [TODAY]: { '3': 8, '4': 1 } }; - setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); - - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])[EXPIRED_DATE]).to.be.undefined; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { '1': 1, '3': 8, '4': 1 } }) - }); - - it('should not save error data into localstorage if consent is not given', () => { - config.setConfig({ deviceAccess: false }); - storage.localStorageIsEnabled.restore(); // let core manage device access - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(localStorageValues[key]).to.be.undefined; - }); - }); describe('combine imps test', function () { it('base test', function () { const imps = [ From 0a5e9de3edfb781c49adf11bbebdba391cac93ba Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 May 2024 18:46:59 +0000 Subject: [PATCH 0032/1097] Prebid 8.48.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 9a373144ebd..15543c5ecc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0-pre", + "version": "8.48.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 28ce27dce05..b642b5dff4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0-pre", + "version": "8.48.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ef42addac923de9464a4180cd32e585e470382e9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 May 2024 18:47:00 +0000 Subject: [PATCH 0033/1097] Increment version to 8.49.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 15543c5ecc3..5198c143442 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0", + "version": "8.49.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b642b5dff4d..6e668a9cad9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0", + "version": "8.49.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 37ab555c840803dde2ef4971ee53955afbdecea6 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Fri, 10 May 2024 14:32:51 +0200 Subject: [PATCH 0034/1097] 9789 tmax/timeout set to value from bidderRequest (#11469) Co-authored-by: mkomorski Co-authored-by: Demetrio Girardi --- modules/advangelistsBidAdapter.js | 4 ++-- modules/axonixBidAdapter.js | 2 +- modules/bluebillywigBidAdapter.js | 2 +- modules/brightMountainMediaBidAdapter.js | 2 +- modules/lkqdBidAdapter.js | 2 +- modules/mgidXBidAdapter.js | 2 +- modules/oguryBidAdapter.js | 2 +- modules/rhythmoneBidAdapter.js | 2 +- modules/visxBidAdapter.js | 3 +-- test/spec/modules/advangelistsBidAdapter_spec.js | 6 +++--- test/spec/modules/bluebillywigBidAdapter_spec.js | 2 +- test/spec/modules/lkqdBidAdapter_spec.js | 2 +- test/spec/modules/mgidXBidAdapter_spec.js | 3 ++- test/spec/modules/oguryBidAdapter_spec.js | 1 + 14 files changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index 8e5be83f166..a916e07a963 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -236,7 +236,7 @@ function createVideoRequestData(bid, bidderRequest) { }, 'at': 2, 'site': {}, - 'tmax': 3000, + 'tmax': Math.min(3000, bidderRequest.timeout), 'cur': ['USD'], 'id': bid.bidId, 'imp': [], @@ -322,7 +322,7 @@ function createBannerRequestData(bid, bidderRequest) { }, 'at': 2, 'site': {}, - 'tmax': 3000, + 'tmax': Math.min(3000, bidderRequest.timeout), 'cur': ['USD'], 'id': bid.bidId, 'imp': [], diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 87c3aff444a..b1ccef155de 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -120,7 +120,7 @@ export const spec = { prebidVersion: '$prebid.version$', screenHeight: screen.height, screenWidth: screen.width, - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, ua: navigator.userAgent, }; diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index d4bde9b3f2c..0718f512cdd 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -278,7 +278,7 @@ export const spec = { const request = { id: bidderRequest.bidderRequestId, source: {tid: bidderRequest.ortb2?.source?.tid}, - tmax: BB_CONSTANTS.DEFAULT_TIMEOUT, + tmax: Math.min(BB_CONSTANTS.DEFAULT_TIMEOUT, bidderRequest.timeout), imp: imps, test: DEV_MODE ? 1 : 0, ext: { diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index 6db06744c24..98b97286631 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -31,7 +31,7 @@ export const spec = { site: buildSite(bidderRequest), device: buildDevice(), cur: [CURRENCY], - tmax: 1000, + tmax: Math.min(1000, bidderRequest.timeout), regs: buildRegs(bidderRequest), user: {}, source: {}, diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 1dbe89f5a49..d4b1cdea0d1 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -67,7 +67,7 @@ export const spec = { }, test: 0, at: 2, - tmax: bid.params.timeout || config.getConfig('bidderTimeout') || 100, + tmax: bidderRequest.timeout, cur: ['USD'], regs: { ext: { diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index ac25a419de1..f073fb4c576 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -166,7 +166,7 @@ export const spec = { placements, coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; if (bidderRequest.gdprConsent) { diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 9937391f6e7..3cf78da4e3a 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -81,7 +81,7 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { function buildRequests(validBidRequests, bidderRequest) { const openRtbBidRequestBanner = { id: bidderRequest.bidderRequestId, - tmax: DEFAULT_TIMEOUT, + tmax: Math.min(DEFAULT_TIMEOUT, bidderRequest.timeout), at: 1, regs: { ext: { diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index 749ab92c0dc..3ab8b79df81 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -161,7 +161,7 @@ function RhythmOneBidAdapter() { } }, at: 1, - tmax: 1000, + tmax: Math.min(1000, bidderRequest.timeout), regs: { ext: { gdpr: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index a86c958392e..beffcf5da95 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -114,8 +114,7 @@ export const spec = { } } - const bidderTimeout = Number(config.getConfig('bidderTimeout')) || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + const tmax = timeout; const source = { ext: { wrapperType: 'Prebid_js', diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js index 143d85a1ab6..57ad2d0e898 100755 --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -44,19 +44,19 @@ describe('advangelistsBidAdapter', function () { describe('spec.buildRequests', function () { it('should create a POST request for each bid', function () { const bidRequest = bidRequests[0]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], { timeout: 1000 }); expect(requests[0].method).to.equal('POST'); }); it('should create a POST request for each bid in video request', function () { const bidRequest = bidRequestsVid[0]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], { timeout: 1000 }); expect(requests[0].method).to.equal('POST'); }); it('should have domain in request', function () { const bidRequest = bidRequests[0]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], { timeout: 1000 }); expect(requests[0].data.site.domain).length !== 0; }); }); diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 4b58e3507db..b6fb11c4750 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -298,7 +298,7 @@ describe('BlueBillywigAdapter', () => { expect(payload.id).to.exist; expect(payload.source).to.be.an('object'); expect(payload.source.tid).to.equal(validBidderRequest.ortb2.source.tid); - expect(payload.tmax).to.equal(BB_CONSTANTS.DEFAULT_TIMEOUT); + expect(payload.tmax).to.equal(3000); expect(payload.imp).to.be.an('array'); expect(payload.test).to.be.a('number'); expect(payload).to.have.nested.property('ext.prebid.targeting'); diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 7fee9bf6e41..4ff69ce5e2a 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -140,7 +140,7 @@ describe('lkqdBidAdapter', () => { }); it('should not populate unspecified parameters', () => { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, { timeout: 1000 }); const serverRequestObject = requests[0]; expect(serverRequestObject.data.device.dnt).to.be.a('undefined'); diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index e0b1e1a84e9..9efaf94c954 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -83,7 +83,8 @@ describe('MGIDXBidAdapter', function () { }, refererInfo: { referer: 'https://test.com' - } + }, + timeout: 1000 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index aad753571a8..ea923a18e57 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -80,6 +80,7 @@ describe('OguryBidAdapter', function () { bidderRequestId: 'mock-uuid', auctionId: bidRequests[0].auctionId, gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, + timeout: 1000 }; describe('isBidRequestValid', function () { From b6159c056c5cdd4f7857679785ce2e07392acb36 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 13 May 2024 03:13:42 -0400 Subject: [PATCH 0035/1097] Various Adapters: Delete s2s transform bid params for adapters with no server equivalent (#11402) * Brightcom adapter: remove adapters (#10925) * Update adagioBidAdapter.js * Update adrelevantisBidAdapter.js * Update big-richmediaBidAdapter.js * Update craftBidAdapter.js * Update goldbachBidAdapter.js * Update winrBidAdapter.js * Update vibrantmediaBidAdapter.js * Update winrBidAdapter.js * Update goldbachBidAdapter.js * Update craftBidAdapter.js * Update adrelevantisBidAdapter.js * Revert "Brightcom adapter: remove adapters (#10925)" (#11404) This reverts commit 878f73773bc50f4c258b3c66f4bb41c6de926866. * Update goldbachBidAdapter.js * Update goldbachBidAdapter.js * Update adagioBidAdapter_spec.js * Update big-richmediaBidAdapter_spec.js * Update vibrantmediaBidAdapter_spec.js * Update vibrantmediaBidAdapter_spec.js --------- Co-authored-by: Alexandru --- modules/adagioBidAdapter.js | 14 -------- modules/adrelevantisBidAdapter.js | 26 +------------- modules/big-richmediaBidAdapter.js | 5 --- modules/craftBidAdapter.js | 22 +----------- modules/goldbachBidAdapter.js | 28 +-------------- modules/vibrantmediaBidAdapter.js | 11 ------ modules/winrBidAdapter.js | 36 +------------------ test/spec/modules/adagioBidAdapter_spec.js | 27 -------------- .../modules/big-richmediaBidAdapter_spec.js | 25 ------------- .../modules/vibrantmediaBidAdapter_spec.js | 19 +++------- 10 files changed, 8 insertions(+), 205 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6e3c38e4e85..86f0b0e09e3 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1275,20 +1275,6 @@ export const spec = { return syncs; }, - - /** - * Handle custom logic in s2s context - * - * @param {*} params - * @param {boolean} isOrtb Is an s2s context - * @param {*} adUnit - * @param {*} bidRequests - * @returns {object} updated params - */ - transformBidParams(params, isOrtb, adUnit, bidRequests) { - // We do not have a prebid server adapter. So let's return unchanged params. - return params; - } }; initAdagio(); diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 68cd859e24e..7d18135f2e8 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -18,9 +18,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -187,28 +185,6 @@ export const spec = { } return bids; - }, - - transformBidParams: function(params, isOpenRtb) { - params = convertTypes({ - 'placementId': 'number', - 'keywords': transformBidderParamKeywords - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; } }; diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index ecb1724c2a1..858dad2ffde 100644 --- a/modules/big-richmediaBidAdapter.js +++ b/modules/big-richmediaBidAdapter.js @@ -112,11 +112,6 @@ export const spec = { return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); }, - transformBidParams: function (params, isOpenRtb) { - if (!baseAdapter.transformBidParams) { return params; } - return baseAdapter.transformBidParams(params, isOpenRtb); - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 74e732d313f..c751a18cc84 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -6,9 +6,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -101,24 +99,6 @@ export const spec = { } }, - transformBidParams: function(params, isOpenRtb) { - params = convertTypes({ - 'sitekey': 'string', - 'placementId': 'string', - 'keywords': transformBidderParamKeywords, - }, params); - if (isOpenRtb) { - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - return params; - }, - onBidWon: function(bid) { ajax(bid._prebidWon, null, null, { method: 'POST', diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 9f9913b7023..df0336c6cd4 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -24,9 +24,8 @@ import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -386,31 +385,6 @@ export const spec = { } }, - transformBidParams: function (params, isOpenRtb) { - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': transformBidderParamKeywords, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js index 8809aae32bd..348d17d2395 100644 --- a/modules/vibrantmediaBidAdapter.js +++ b/modules/vibrantmediaBidAdapter.js @@ -97,17 +97,6 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - /** - * Transforms the 'raw' bid params into ones that this adapter can use, prior to creating the bid request. - * - * @param {object} bidParams the params to transform. - * - * @returns {object} the bid params. - */ - transformBidParams: function(bidParams) { - return bidParams; - }, - /** * Determines whether or not the given bid request is valid. For all bid requests passed to the buildRequests * function, each will have been passed to this function and this function will have returned true. diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index cf1158474b4..6cde0412071 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -14,9 +14,8 @@ import {BANNER} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -312,39 +311,6 @@ export const spec = { ]; } }, - - transformBidParams: function (params, isOpenRtb) { - params = convertTypes( - { - member: 'string', - invCode: 'string', - placementId: 'number', - keywords: transformBidderParamKeywords, - publisherId: 'number', - }, - params - ); - - if (isOpenRtb) { - params.use_pmt_rule = - typeof params.usePaymentRule === 'boolean' - ? params.usePaymentRule - : false; - if (params.usePaymentRule) { - delete params.usePaymentRule; - } - - Object.keys(params).forEach((paramKey) => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, }; function formatRequest(payload, bidderRequest) { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 13c02cc9bae..19ccb49eea1 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1465,33 +1465,6 @@ describe('Adagio bid adapter', () => { }); }); - describe('transformBidParams', function() { - it('Compute additional params in s2s mode', function() { - const adUnit = { - code: 'adunit-code', - params: { - organizationId: '1000' - } - }; - const bid01 = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] }, - video: { - context: 'outstream', - playerSize: [300, 250], - renderer: { - url: 'https://url.tld', - render: () => true - } - } - } - }).withParams().build(); - - const params = spec.transformBidParams({ param01: 'test' }, true, adUnit, [{ bidderCode: 'adagio', auctionId: bid01.auctionId, bids: [bid01] }]); - expect(params.param01).eq('test'); - }); - }); - describe('Adagio features when prebid in top.window', function() { it('should return all expected features when all expected bidder params are available', function() { sandbox.stub(window.top.document, 'getElementById').returns( diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index c3a9a8ef6c1..f37975c3bc1 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -271,31 +271,6 @@ describe('bigRichMediaAdapterTests', function () { }); }); - describe('transformBidParams', function() { - it('cast placementId to number', function() { - const adUnit = { - code: 'adunit-code', - params: { - placementId: '456' - } - }; - const bid = { - params: { - placementId: '456' - }, - sizes: [[300, 250]], - mediaTypes: { - banner: { sizes: [[300, 250]] } - } - }; - - const params = spec.transformBidParams({ placementId: '456' }, true, adUnit, [{ bidderCode: 'bigRichmedia', auctionId: bid.auctionId, bids: [bid] }]); - - expect(params.placement_id).to.exist; - expect(params.placement_id).to.be.a('number'); - }); - }); - describe('onBidWon', function() { it('Should not have any error', function() { const result = spec.onBidWon({}); diff --git a/test/spec/modules/vibrantmediaBidAdapter_spec.js b/test/spec/modules/vibrantmediaBidAdapter_spec.js index c6ce7d52fb3..cf9487ebf25 100644 --- a/test/spec/modules/vibrantmediaBidAdapter_spec.js +++ b/test/spec/modules/vibrantmediaBidAdapter_spec.js @@ -62,12 +62,6 @@ describe('VibrantMediaBidAdapter', function () { }); }); - describe('transformBidParams', function () { - it('transforms bid params correctly', function () { - expect(spec.transformBidParams(VALID_VIDEO_BID_PARAMS)).to.deep.equal(VALID_VIDEO_BID_PARAMS); - }); - }) - let bidRequest; beforeEach(function () { @@ -1077,13 +1071,9 @@ describe('VibrantMediaBidAdapter', function () { describe('Flow tests', function () { describe('For successive API calls to the public functions', function () { it('should succeed with one media type per bid', function () { - const transformedBannerBidParams = spec.transformBidParams(VALID_BANNER_BID_PARAMS); - const transformedVideoBidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); - const transformedNativeBidParams = spec.transformBidParams(VALID_NATIVE_BID_PARAMS); - const bannerBid = { bidder: 'vibrantmedia', - params: transformedBannerBidParams, + params: VALID_BANNER_BID_PARAMS, mediaTypes: { banner: { sizes: DEFAULT_BID_SIZES, @@ -1097,7 +1087,7 @@ describe('VibrantMediaBidAdapter', function () { }; const videoBid = { bidder: 'vibrantmedia', - params: transformedVideoBidParams, + params: VALID_VIDEO_BID_PARAMS, mediaTypes: { video: { context: OUTSTREAM, @@ -1112,7 +1102,7 @@ describe('VibrantMediaBidAdapter', function () { }; const nativeBid = { bidder: 'vibrantmedia', - params: transformedNativeBidParams, + params: VALID_NATIVE_BID_PARAMS, mediaTypes: { native: { image: { @@ -1178,10 +1168,9 @@ describe('VibrantMediaBidAdapter', function () { }); it('should succeed with multiple media types for a single bid', function () { - const bidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); const bid = { bidder: 'vibrantmedia', - params: bidParams, + params: VALID_VIDEO_BID_PARAMS, mediaTypes: { banner: { sizes: DEFAULT_BID_SIZES From 3a5f38618e112c059706e063329217fe83032701 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Mon, 13 May 2024 10:46:56 -0400 Subject: [PATCH 0036/1097] KRAK-4688: Adds support for PAAPI module (#34) (#11480) * adds support for paapi module * slight revision --- modules/kargoBidAdapter.js | 17 +++++++- test/spec/modules/kargoBidAdapter_spec.js | 49 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index f3b3166ccad..cafbbd982fa 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -196,6 +196,7 @@ function buildRequests(validBidRequests, bidderRequest) { function interpretResponse(response, bidRequest) { const bids = response.body; + const fledgeAuctionConfigs = []; const bidResponses = []; if (isEmpty(bids) || typeof bids !== 'object') { @@ -237,9 +238,23 @@ function interpretResponse(response, bidRequest) { } bidResponses.push(bidResponse); + + if (adUnit.auctionConfig) { + fledgeAuctionConfigs.push({ + bidId: bidID, + config: adUnit.auctionConfig + }) + } } - return bidResponses; + if (fledgeAuctionConfigs.length > 0) { + return { + bids: bidResponses, + fledgeAuctionConfigs + } + } else { + return bidResponses; + } } function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 7aa853ad902..ee89d6468a5 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1807,6 +1807,55 @@ describe('kargo adapter tests', function() { advertiserDomains: [ 'https://foo.com', 'https://bar.com' ] }); }); + + it('should return fledgeAuctionConfigs if provided in bid response', function () { + const auctionConfig = { + seller: 'https://kargo.com', + decisionLogicUrl: 'https://kargo.com/decision_logic.js', + interestGroupBuyers: ['https://some_buyer.com'], + perBuyerSignals: { + 'https://some_buyer.com': { a: 1 } + } + } + + const body = response.body; + for (const key in body) { + if (body.hasOwnProperty(key)) { + if (key % 2 !== 0) { // Add auctionConfig to every other object + body[key].auctionConfig = auctionConfig; + } + } + } + + let result = spec.interpretResponse(response, bidderRequest); + + // Test properties of bidResponses + result.bids.forEach(bid => { + expect(bid).to.have.property('requestId'); + expect(bid).to.have.property('cpm'); + expect(bid).to.have.property('width'); + expect(bid).to.have.property('height'); + expect(bid).to.have.property('ttl'); + expect(bid).to.have.property('creativeId'); + expect(bid.netRevenue).to.be.true; + expect(bid).to.have.property('meta').that.is.an('object'); + }); + + // Test properties of fledgeAuctionConfigs + expect(result.fledgeAuctionConfigs).to.have.lengthOf(3); + + const expectedBidIds = ['1', '3', '5']; // Expected bidIDs + result.fledgeAuctionConfigs.forEach(config => { + expect(config).to.have.property('bidId'); + expect(expectedBidIds).to.include(config.bidId); + + expect(config).to.have.property('config').that.is.an('object'); + expect(config.config).to.have.property('seller', 'https://kargo.com'); + expect(config.config).to.have.property('decisionLogicUrl', 'https://kargo.com/decision_logic.js'); + expect(config.config.interestGroupBuyers).to.be.an('array').that.includes('https://some_buyer.com'); + expect(config.config.perBuyerSignals).to.have.property('https://some_buyer.com').that.deep.equals({ a: 1 }); + }); + }); }); describe('getUserSyncs', function() { From 1604431acfcd64ceebb6b7fab7c02d55a24cb53a Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Mon, 13 May 2024 20:32:22 +0200 Subject: [PATCH 0037/1097] Site Ext Data to kvs. (#11465) --- modules/adnuntiusBidAdapter.js | 17 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 161 +++++++++++------- 2 files changed, 113 insertions(+), 65 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 9c8aa5dd5c4..060f87c0f9c 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -146,6 +146,15 @@ const storageTool = (function () { return segments } + const getKvsFromOrtb = function (ortb2) { + const siteData = deepAccess(ortb2, 'site.ext.data'); + if (siteData) { + return siteData + } else { + return null + } + } + return { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; @@ -163,18 +172,19 @@ const storageTool = (function () { }); } metaInternal.segments = getSegmentsFromOrtb(ortb2); + metaInternal.kv = getKvsFromOrtb(ortb2); }, saveToStorage: function (serverData, network) { setMetaInternal(serverData, network); }, getUrlRelatedData: function () { // getting the URL information is theoretically not network-specific - const { segments, usi, voidAuIdsArray } = metaInternal; - return { segments, usi, voidAuIdsArray }; + const { segments, kv, usi, voidAuIdsArray } = metaInternal; + return { segments, kv, usi, voidAuIdsArray }; }, getPayloadRelatedData: function (network) { // getting the payload data should be network-specific - const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({...a, [entry.key]: entry.value}), {}); + const { segments, kv, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); return payloadRelatedData; } }; @@ -252,6 +262,7 @@ export const spec = { } const targeting = bid.params.targeting || {}; + if (urlRelatedMetaData.kv) targeting.kv = urlRelatedMetaData.kv; const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index cad5e9ea1cf..c288bfb4f12 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -1,18 +1,18 @@ // import or require modules necessary for the test, e.g.: -import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' -import {misc, spec} from 'modules/adnuntiusBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {config} from 'src/config.js'; +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { misc, spec } from 'modules/adnuntiusBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; -import {getStorageManager} from 'src/storageManager.js'; -import {getGlobal} from '../../../src/prebidGlobal'; +import { getStorageManager } from 'src/storageManager.js'; +import { getGlobal } from '../../../src/prebidGlobal'; -describe('adnuntiusBidAdapter', function() { +describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const usi = utils.generateUUID() - const meta = [{key: 'valueless'}, {value: 'keyless'}, {key: 'voidAuIds'}, {key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp()}, {exp: misc.getUnixTimestamp(1)}]}, {key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1)}, {key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1)}, {key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp()}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius'}, {key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius'}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}] + const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp() }, { exp: misc.getUnixTimestamp(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1) }, { key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1) }, { key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp() }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp() }] let storage; // need this to make the restore work correctly -- something to do with stubbing static prototype methods @@ -24,7 +24,7 @@ describe('adnuntiusBidAdapter', function() { storageAllowed: true } }; - storage = getStorageManager({bidderCode: 'adnuntius'}); + storage = getStorageManager({ bidderCode: 'adnuntius' }); }); beforeEach(() => { @@ -35,7 +35,7 @@ describe('adnuntiusBidAdapter', function() { getGlobal().bidderSettings = {}; }); - afterEach(function() { + afterEach(function () { config.resetConfig(); if (stub1.restore) { @@ -453,20 +453,20 @@ describe('adnuntiusBidAdapter', function() { } } - describe('inherited functions', function() { - it('exists and is a function', function() { + describe('inherited functions', function () { + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function() { - it('should return true when required params found', function() { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bidderRequests[0])).to.equal(true); }); }); - describe('buildRequests', function() { - it('Test requests', function() { + describe('buildRequests', function () { + it('Test requests', function () { stub1 = sinon.stub(URLSearchParams.prototype, 'has').callsFake(() => { return true; }); @@ -485,7 +485,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}]}'); }); - it('Test requests with no local storage', function() { + it('Test requests with no local storage', function () { storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{}])); const request = spec.buildRequests(bidderRequests, {}); expect(request.length).to.equal(1); @@ -504,8 +504,8 @@ describe('adnuntiusBidAdapter', function() { expect(request2[0].url).to.equal(ENDPOINT_URL_BASE); }); - it('Test request changes for voided au ids', function() { - storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp(1)}, {auId: '0000000000000023', exp: misc.getUnixTimestamp(1)}]}])); + it('Test request changes for voided au ids', function () { + storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp(1) }, { auId: '0000000000000023', exp: misc.getUnixTimestamp(1) }] }])); const bRequests = bidderRequests.concat([{ bidId: 'adn-11118b6bc', bidder: 'adnuntius', @@ -556,7 +556,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]},{"auId":"13","targetId":"adn-13","dimensions":[[164,140],[10,1400]]}]}'); }); - it('Test Video requests', function() { + it('Test Video requests', function () { const request = spec.buildRequests(videoBidderRequest, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); @@ -566,12 +566,12 @@ describe('adnuntiusBidAdapter', function() { expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); - it('should pass segments if available in config', function() { + it('should pass segments if available in config', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}, {invalidSegment: 'invalid'}, {id: 123}, {id: ['3332']}] + segment: [{ id: 'segment1' }, { id: 'segment2' }, { invalidSegment: 'invalid' }, { id: 123 }, { id: ['3332'] }] }, { name: 'other', @@ -580,18 +580,55 @@ describe('adnuntiusBidAdapter', function() { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should skip segments in config if not either id or array of strings', function() { + it('should pass site data ext as key values to ad server', function () { + const ortb2 = { + site: { + ext: { + data: { + '12345': 'true', + '45678': 'true' + } + } + } + }; + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + const data = JSON.parse(request[0].data); + expect(data.adUnits[0].kv).to.have.property('12345'); + expect(data.adUnits[0].kv['12345']).to.equal('true'); + expect(data.adUnits[0].kv).to.have.property('45678'); + expect(data.adUnits[0].kv['45678']).to.equal('true'); + }); + + it('should skip passing site data ext if missing', function () { + const ortb2 = { + site: { + ext: { + } + } + }; + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + const data = JSON.parse(request[0].data); + expect(data.adUnits[0]).to.not.have.property('kv'); + }); + + it('should skip segments in config if not either id or array of strings', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}, {id: 'segment3'}] + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] }, { name: 'other', @@ -602,34 +639,34 @@ describe('adnuntiusBidAdapter', function() { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); }); - describe('user privacy', function() { - it('should send GDPR Consent data if gdprApplies', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: true, consentString: 'consentString'}}); + describe('user privacy', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: undefined, consentString: 'consentString'}}); + it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); - it('should pass segments if available in config', function() { + it('should pass segments if available in config', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}] + segment: [{ id: 'segment1' }, { id: 'segment2' }] }, { name: 'other', @@ -638,18 +675,18 @@ describe('adnuntiusBidAdapter', function() { } } - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should skip segments in config if not either id or array of strings', function() { + it('should skip segments in config if not either id or array of strings', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}, {id: 'segment3'}] + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] }, { name: 'other', @@ -660,20 +697,20 @@ describe('adnuntiusBidAdapter', function() { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should user user ID if present in ortb2.user.id field', function() { + it('should user user ID if present in ortb2.user.id field', function () { const ortb2 = { user: { id: usi } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); @@ -721,24 +758,24 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('user privacy', function() { - it('should send GDPR Consent data if gdprApplies', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: true, consentString: 'consentString'}}); + describe('user privacy', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: undefined, consentString: 'consentString'}}); + it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); }); - describe('use cookie', function() { - it('should send noCookie in url if set to false.', function() { + describe('use cookie', function () { + it('should send noCookie in url if set to false.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -753,8 +790,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('validate auId', function() { - it('should fail when auId is not hexadecimal', function() { + describe('validate auId', function () { + it('should fail when auId is not hexadecimal', function () { const invalidRequest = { bidId: 'adn-000000000008b6bc', bidder: 'adnuntius', @@ -766,7 +803,7 @@ describe('adnuntiusBidAdapter', function() { expect(valid).to.equal(false); }); - it('should pass when auId is hexadecimal', function() { + it('should pass when auId is hexadecimal', function () { const invalidRequest = { bidId: 'adn-000000000008b6bc', bidder: 'adnuntius', @@ -779,8 +816,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('request deals', function() { - it('Should set max deals.', function() { + describe('request deals', function () { + it('Should set max deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'] }); @@ -797,7 +834,7 @@ describe('adnuntiusBidAdapter', function() { expect(bidderRequests[1].params).to.not.have.property('maxBids'); expect(data.adUnits[1].maxDeals).to.equal(undefined); }); - it('Should allow a maximum of 5 deals.', function() { + it('Should allow a maximum of 5 deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -820,7 +857,7 @@ describe('adnuntiusBidAdapter', function() { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(5); }); - it('Should allow a minumum of 0 deals.', function() { + it('Should allow a minumum of 0 deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -843,7 +880,7 @@ describe('adnuntiusBidAdapter', function() { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(undefined); }); - it('Should set max deals using bidder config.', function() { + it('Should set max deals using bidder config.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -856,7 +893,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL + '&ds=2'); }); - it('Should allow a maximum of 5 deals when using bidder config.', function() { + it('Should allow a maximum of 5 deals when using bidder config.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -869,7 +906,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL + '&ds=5'); }); - it('Should allow a minimum of 0 deals when using bidder config.', function() { + it('Should allow a minimum of 0 deals when using bidder config.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -885,8 +922,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('interpretResponse', function() { - it('should return valid response when passed valid server response', function() { + describe('interpretResponse', function () { + it('should return valid response when passed valid server response', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -960,7 +997,7 @@ describe('adnuntiusBidAdapter', function() { expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); }); - it('should not process valid response when passed alt bidder that is an adndeal', function() { + it('should not process valid response when passed alt bidder that is an adndeal', function () { const altBidder = { bid: [ { @@ -978,7 +1015,7 @@ describe('adnuntiusBidAdapter', function() { serverResponse.body.adUnits[0].deals = deals; }); - it('should return valid response when passed alt bidder', function() { + it('should return valid response when passed alt bidder', function () { const altBidder = { bid: [ { @@ -1015,8 +1052,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('interpretVideoResponse', function() { - it('should return valid response when passed valid server response', function() { + describe('interpretVideoResponse', function () { + it('should return valid response when passed valid server response', function () { const interpretedResponse = spec.interpretResponse(serverVideoResponse, videoBidRequest); const ad = serverVideoResponse.body.adUnits[0].ads[0] const deal = serverVideoResponse.body.adUnits[0].deals[0] From 48c884c7ded1ac420b8bd125df4477cc8aa246de Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 14 May 2024 11:19:30 -0600 Subject: [PATCH 0038/1097] Remove Email (#11484) --- modules/waardexBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/waardexBidAdapter.md b/modules/waardexBidAdapter.md index 6978281d40e..165321c2057 100644 --- a/modules/waardexBidAdapter.md +++ b/modules/waardexBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Waardex Bid Adapter Module Type: Bidder Adapter -Maintainer: info@prebid.org +Maintainer: ``` # Description From ddb66fd245265214e6333c4807046f4facc11517 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 14 May 2024 19:31:26 +0200 Subject: [PATCH 0039/1097] AdagioBidAdapter: validate `plcmt` video param () (#11487) --- modules/adagioBidAdapter.js | 7 ++++++- test/spec/modules/adagioBidAdapter_spec.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 86f0b0e09e3..ac2e578de5c 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -64,7 +64,11 @@ export const ORTB_VIDEO_PARAMS = { 'w': (value) => isInteger(value), 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), - 'placement': (value) => isInteger(value), + 'placement': (value) => { + logWarn(LOG_PREFIX, 'The OpenRTB video param `placement` is deprecated and should not be used anymore.'); + return isInteger(value) + }, + 'plcmt': (value) => isInteger(value), 'linearity': (value) => isInteger(value), 'skip': (value) => [1, 0].includes(value), 'skipmin': (value) => isInteger(value), @@ -964,6 +968,7 @@ export const spec = { autoFillParams(bid); + // Note: `bid.params.placement` is not related to the video param `placement`. if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { logWarn(`${LOG_PREFIX} at least one required param is missing.`); // internal.enqueue(debugData()); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 19ccb49eea1..b11c0ab4a8e 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -493,7 +493,7 @@ describe('Adagio bid adapter', () => { skipafter: 4, minduration: 10, maxduration: 30, - placement: 3, + plcmt: 4, protocols: [8] } }).build(); @@ -508,7 +508,7 @@ describe('Adagio bid adapter', () => { skipafter: 4, minduration: 10, maxduration: 30, - placement: 3, + plcmt: 4, protocols: [8], w: 300, h: 250 From 0612325a0db1991a62aa92304959ee0cb6e3092f Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Wed, 15 May 2024 01:03:05 +0400 Subject: [PATCH 0040/1097] AdsYield Bid Adapter: move to limelight (#11483) --- modules/admixerBidAdapter.js | 1 - modules/limelightDigitalBidAdapter.js | 2 +- test/spec/modules/admixerBidAdapter_spec.js | 6 ------ 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index f5f0b5bf665..de7b941d614 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -10,7 +10,6 @@ const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ALIASES = [ {code: 'go2net', endpoint: 'https://ads.go2net.com.ua/prebid.1.2.aspx'}, 'adblender', - {code: 'adsyield', endpoint: 'https://ads.adsyield.com/prebid.1.2.aspx'}, {code: 'futureads', endpoint: 'https://ads.futureads.io/prebid.1.2.aspx'}, {code: 'smn', endpoint: 'https://ads.smn.rs/prebid.1.2.aspx'}, {code: 'admixeradx', endpoint: 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'}, diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index c816f800fda..6c589f536c2 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -32,7 +32,7 @@ function isBidResponseValid(bid) { export const spec = { code: BIDDER_CODE, - aliases: ['pll', 'iionads', 'apester'], + aliases: ['pll', 'iionads', 'apester', 'adsyield'], supportedMediaTypes: [BANNER, VIDEO], /** diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 85538efc957..e254d2f2ff7 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -175,12 +175,6 @@ describe('AdmixerAdapter', function () { expect(request.url).to.equal('https://inv-nets.admixer.net/prebid.1.2.aspx'); expect(request.method).to.equal('POST'); }); - it('build request for adsyield', function () { - const requestParams = requestParamsFor('adsyield'); - const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); - expect(request.url).to.equal('https://ads.adsyield.com/prebid.1.2.aspx'); - expect(request.method).to.equal('POST'); - }); it('build request for futureads', function () { const requestParams = requestParamsFor('futureads'); const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); From f47540b629f265c2ad5214162c46c1df851a25ac Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 15 May 2024 14:07:24 +0300 Subject: [PATCH 0041/1097] Vidazoo Bid Adapter : rector gpid value assignment (#11491) * Refactor gpid value assignment in vidazooBidAdapter. * Refactor gpid value assignment in vidazooBidAdapter. --- modules/vidazooBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index ea1555e5327..c5e35c6b138 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -83,7 +83,7 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout const ptrace = getCacheOpt(); const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); From 7a507b7601153d14731d574a9d45e6cc3f814166 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 15 May 2024 14:15:44 +0300 Subject: [PATCH 0042/1097] Refactor gpid extraction logic. (#11492) --- modules/kueezRtbBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 264592cd7d6..ba33f3ade9a 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -65,7 +65,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const pId = extractPID(params); const subDomain = extractSubDomain(params); - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ From 85cee26c5f9723cac86c73493dfa952939f0663d Mon Sep 17 00:00:00 2001 From: sangarbe Date: Wed, 15 May 2024 14:13:54 +0200 Subject: [PATCH 0043/1097] Seedtag Bid Adapter : allows sending bcat and badv ortb2 params in request payload (#11490) * sends bcat and badv ortb2 params in request payload * adds tests for bcat and badv --- modules/seedtagBidAdapter.js | 8 +++++ test/spec/modules/seedtagBidAdapter_spec.js | 36 +++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index e17ff1301a3..284e62e70fe 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -324,6 +324,14 @@ export const spec = { payload.user.eids = validBidRequests[0].userIdAsEids } + if (bidderRequest.ortb2?.bcat) { + payload.bcat = bidderRequest.ortb2?.bcat + } + + if (bidderRequest.ortb2?.badv) { + payload.badv = bidderRequest.ortb2?.badv + } + const payloadString = JSON.stringify(payload); return { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 2012c78d239..ed3e2c9be0b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -590,6 +590,42 @@ describe('Seedtag Adapter', function () { expect(data.user.eids).to.deep.equal(userIdAsEids); }) }); + + describe('Blocking params', function () { + it('should add bcat param to payload when bidderRequest has ortb2 bcat info', function () { + const blockedCategories = ['IAB1', 'IAB2'] + var ortb2 = { + bcat: blockedCategories + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(blockedCategories); + }); + + it('should add badv param to payload when bidderRequest has ortb2 badv info', function () { + const blockedAdvertisers = ['blocked.com'] + var ortb2 = { + badv: blockedAdvertisers + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.badv).to.deep.equal(blockedAdvertisers); + }); + + it('should not add bcat and badv params to payload when bidderRequest does not have ortb2 badv and bcat info', function () { + var ortb2 = {} + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.be.undefined; + expect(data.badv).to.be.undefined; + }); + }); }) describe('interpret response method', function () { it('should return a void array, when the server response are not correct.', function () { From 542694585269e3796d65205283783915a8cf9f70 Mon Sep 17 00:00:00 2001 From: rcheptanariu <35690143+rcheptanariu@users.noreply.github.com> Date: Wed, 15 May 2024 15:31:45 +0300 Subject: [PATCH 0044/1097] Invibes Bid Adapter : reading page referer and cookie handlid (#11477) * InvibesBidAdapter - added cookie and referer read * InvibesBidAdapter - unit tests * InvibesBidAdapter - tab fix * InvibesBidAdapter - null checks * InvibesBidAdapter - fix { after if --- modules/invibesBidAdapter.js | 16 ++++++-- test/spec/modules/invibesBidAdapter_spec.js | 44 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 2c37c0edad9..7ba2b8225b0 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -14,7 +14,7 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 11, + PREBID_VERSION: 12, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], @@ -40,7 +40,7 @@ export const spec = { buildRequests: buildRequest, /** * @param {*} responseObj - * @param {requestParams} bidRequests + * @param {*} requestParams * @return {Bid[]} An array of bids which */ interpretResponse: function (responseObj, requestParams) { @@ -131,7 +131,6 @@ function buildRequest(bidRequests, bidderRequest) { window.invibes.placementIds.push(bidRequest.params.placementId); - _placementIds.push(bidRequest.params.placementId); _placementIds.push(bidRequest.params.placementId); _adUnitCodes.push(bidRequest.adUnitCode); _domainId = _domainId || bidRequest.params.domainId; @@ -180,9 +179,18 @@ function buildRequest(bidRequests, bidderRequest) { isLocalStorageEnabled: storage.hasLocalStorage(), preventPageViewEvent: preventPageViewEvent, isPlacementRefresh: isPlacementRefresh, - isInfiniteScrollPage: isInfiniteScrollPage, + isInfiniteScrollPage: isInfiniteScrollPage }; + if (bidderRequest.refererInfo && bidderRequest.refererInfo.ref) { + data.pageReferrer = bidderRequest.refererInfo.ref.substring(0, 300); + } + + let hid = invibes.getCookie('handIid'); + if (hid) { + data.handIid = hid; + } + let lid = readFromLocalStorage('ivbsdid'); if (!lid) { let str = invibes.getCookie('ivbsdid'); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 056255c7738..ba2d8f5255a 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -560,6 +560,50 @@ describe('invibesBidAdapter:', function () { expect(request.data.lId).to.exist; }); + it('does not send handIid when it doesnt exist in cookie', function () { + top.window.invibes.optIn = 1; + top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; + global.document.cookie = ''; + let bidderRequest = { + gdprConsent: { + vendorData: { + vendorConsents: { + 436: true + } + } + }, + refererInfo: { + page: 'https://randomWeb.com?someFakePara=fakeValue&secondParam=secondValue' + } + }; + SetBidderAccess(); + + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.handIid).to.not.exist; + }); + + it('sends handIid when comes on cookie', function () { + top.window.invibes.optIn = 1; + top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; + global.document.cookie = 'handIid=abcdefghijkk'; + let bidderRequest = { + gdprConsent: { + vendorData: { + vendorConsents: { + 436: true + } + } + }, + refererInfo: { + page: 'https://randomWeb.com?someFakePara=fakeValue&secondParam=secondValue' + } + }; + SetBidderAccess(); + + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.handIid).to.equal('abcdefghijkk'); + }); + it('should send purpose 1', function () { let bidderRequest = { gdprConsent: { From dd398f171c77b72801798fd1b92bd7ea9bf8c03e Mon Sep 17 00:00:00 2001 From: Dejan Grbavcic Date: Thu, 16 May 2024 12:48:05 +0200 Subject: [PATCH 0045/1097] Brid Bid Adapter: switching to plcmt (#11502) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * Add Brid bid adapter * Brid adapter requested changes * BridBidAdapter: switching to plcmt --- modules/bridBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js index e784ea517ac..f3fe1541886 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -15,7 +15,7 @@ const ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; const GVLID = 934; const TIME_TO_LIVE = 300; const VIDEO_PARAMS = [ - 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', + 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'plcmt', 'playbackmethod', 'protocols', 'startdelay' ]; From 2c55a6a080c1981d74e85bb7e0d9709a620f97ec Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Thu, 16 May 2024 12:59:09 +0200 Subject: [PATCH 0046/1097] Tappx Bid Adapter : change to plcmt (#11504) * Tappx Refactor: Optimizing and adding more checkers and tests * Fix: fixed site referrer for iframes using external sites error #13231 * Adapt new placement params --------- Co-authored-by: Jordi Arnau Co-authored-by: Felipe N --- modules/tappxBidAdapter.js | 6 +++--- modules/tappxBidAdapter.md | 2 +- test/spec/modules/tappxBidAdapter_spec.js | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index f0c275acfb6..b2939bffedf 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -5,8 +5,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; -import {parseDomain} from '../src/refererDetection.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import { parseDomain } from '../src/refererDetection.js'; +import { getGlobal } from '../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -39,7 +39,7 @@ const VIDEO_CUSTOM_PARAMS = { 'h': DATA_TYPES.NUMBER, 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, - 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER, 'skip': DATA_TYPES.NUMBER diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md index 55f18531f28..e0d1816cf6a 100644 --- a/modules/tappxBidAdapter.md +++ b/modules/tappxBidAdapter.md @@ -80,7 +80,7 @@ Ads sizes available: [300,250], [320,50], [320,480], [480,320], [728,90], [768,1 protocols: [ 2, 3 ], // Optional battr: [ 13, 14 ], // Optional linearity: 1, // Optional - placement: 2, // Optional + plcmt: 2, // Optional minbitrate: 10, // Optional maxbitrate: 10 // Optional }, diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index 46fac8de1e2..1dd3f1b3c50 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -124,8 +124,8 @@ const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; const c_BIDDERREQUEST_B = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'page': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_CONSENTSTRING, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; -const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"placement":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'placement': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} -const c_BIDDERREQUEST_VOutstream = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"context": "outstream","playerSize":[640, 480],"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"placement":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'placement': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"plcmt":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'plcmt': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +const c_BIDDERREQUEST_VOutstream = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"context": "outstream","playerSize":[640, 480],"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"plcmt":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'plcmt': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} describe('Tappx bid adapter', function () { /** @@ -323,14 +323,14 @@ describe('Tappx bid adapter', function () { * INTERPRET RESPONSE TESTS */ describe('interpretResponse', function () { - it('receive banner reponse with single placement', function () { + it('receive banner reponse with single plcmt', function () { const bids = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); const bid = bids[0]; expect(bid.cpm).to.exist; expect(bid.ad).to.match(/^ +``` + +# Protected Audience API (FLEDGE) + +In order to enable PAAPI auctions follow the instructions below: + +1. Add the fledgeForGpt and paapi modules to your prebid bundle. +2. Add the following configuration for the module +``` +pbjs.que.push(function() { + pbjs.setConfig({ + fledgeForGpt: { + enabled: true, + bidders: ['medianet'], + defaultForSlots: 1 + } + }); +}); +``` +For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 4a221e97444..cc1a15fd733 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -174,6 +174,37 @@ let VALID_BID_REQUEST = [{ }, 'bidRequestsCount': 1 }], + // Protected Audience API Request + VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'ortb2Imp': { + 'ext': { + 'tid': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ae': 1 + } + }, + 'bidRequestsCount': 1 + }], + VALID_BID_REQUEST_WITH_USERID = [{ 'bidder': 'medianet', 'params': { @@ -875,6 +906,75 @@ let VALID_BID_REQUEST = [{ }], 'tmax': config.getConfig('bidderTimeout') }, + // Protected Audience API Valid Payload + VALID_PAYLOAD_PAAPI = { + 'site': { + 'domain': 'media.net', + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest', + 'topMostLocation': 'http://media.net/topmost', + 'isTop': true + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false, + 'usp_applies': false, + 'coppa_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + }, + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [ + { + 'id': '28f8f8130a583e', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ext': { + 'ae': 1, + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'display_count': 1, + 'coordinates': { + 'top_left': { + 'x': 50, + 'y': 50 + }, + 'bottom_right': { + 'x': 100, + 'y': 100 + } + }, + 'viewability': 1, + 'visibility': 1 + }, + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'domain': 'media.net', + 'isTop': true, + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest' + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ae': 1 + } + }, + 'banner': [ + { + 'w': 300, + 'h': 250 + } + ], + 'tagid': 'crid' + } + ], + 'tmax': 3000 + }, VALID_VIDEO_BID_REQUEST = [{ 'bidder': 'medianet', @@ -1104,6 +1204,126 @@ let VALID_BID_REQUEST = [{ } } }, + // Protected Audience API Response + SERVER_RESPONSE_PAAPI = { + body: { + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidList': [{ + 'no_bid': false, + 'requestId': '28f8f8130a583e', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': 'crid', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'paApiAuctionConfigs': [ + { + 'bidId': '28f8f8130a583e', + 'config': { + 'seller': 'https://hbx.test.media.net', + 'decisionLogicUrl': 'https://hbx.test.media.net/decision-logic.js', + 'interestGroupBuyers': ['https://buyer.test.media.net'], + 'auctionSignals': { + 'logging_params': { + 'cid': 'customer_id', + 'crid': 'crid', + 'bid_uuid': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'browser_id': 2, + 'dfpid': 'div-gpt-ad-1460505748561-0' + }, + 'pvidLookup': { + 'https://buyer.test.media.net': { + 'pvid': '172', + 'seat': 'quantcast-qc1' + } + }, + 'bidFlr': 0.0 + }, + 'sellerTimout': 1000, + 'sellerSignals': { + 'callbackURL': 'https://test.com/paapi/v1/abcd' + }, + 'perBuyerSignals': { + 'https://buyer.test.media.net': [ 'test_buyer_signals' ] + }, + 'perBuyerTimeouts': { + '*': 200 + } + } + } + ], + 'csUrl': [{ + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + // Protected Audience API OpenRTB Response + SERVER_RESPONSE_PAAPI_ORTB = { + body: { + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidList': [{ + 'no_bid': false, + 'requestId': '28f8f8130a583e', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': 'crid', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'igi': [{ + 'igs': [ + { + 'impid': '28f8f8130a583e', + 'bidId': '28f8f8130a583e', + 'config': { + 'seller': 'https://hbx.test.media.net', + 'decisionLogicUrl': 'https://hbx.test.media.net/decision-logic.js', + 'interestGroupBuyers': ['https://buyer.test.media.net'], + 'auctionSignals': { + 'logging_params': { + 'cid': 'customer_id', + 'crid': 'crid', + 'bid_uuid': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'browser_id': 2, + 'dfpid': 'div-gpt-ad-1460505748561-0' + }, + 'pvidLookup': { + 'https://buyer.test.media.net': { + 'pvid': '172', + 'seat': 'quantcast-qc1' + } + }, + 'bidFlr': 0.0 + }, + 'sellerTimout': 1000, + 'sellerSignals': { + 'callbackURL': 'https://test.com/paapi/v1/abcd' + }, + 'perBuyerSignals': { + 'https://buyer.test.media.net': [ 'test_buyer_signals' ] + }, + 'perBuyerTimeouts': { + '*': 200 + } + } + } + ], + }], + 'csUrl': [{ + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID = { body: { 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', @@ -1547,6 +1767,19 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERID); }); + it('should have valid payload when PAAPI is enabled', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAAPI); + }); + + it('should send whatever is set in ortb2imp.ext.ae in all bid requests when PAAPI is enabled', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let data = JSON.parse(bidReq.data); + expect(data).to.deep.equal(VALID_PAYLOAD_PAAPI); + expect(data.imp[0].ext).to.have.property('ae'); + expect(data.imp[0].ext.ae).to.equal(1); + }); + describe('build requests: when page meta-data is available', () => { beforeEach(() => { spec.clearMnData(); @@ -1721,6 +1954,32 @@ describe('Media.net bid adapter', function () { let bids = spec.interpretResponse(SERVER_RESPONSE_EMPTY_BIDLIST, []); expect(bids).to.deep.equal(validBids); }); + + it('should return fledgeAuctionConfigs if PAAPI response is received', function() { + let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); + }); + + it('should return fledgeAuctionConfigs if openRTB PAAPI response received', function () { + let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) + }); + + it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); + expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + }); + + it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id for openRTB response', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); + expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + }); }); describe('onTimeout', function () { From b4d6a8280416a42a9a81c48986e7ef231b7396f0 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 21 May 2024 17:55:54 +0200 Subject: [PATCH 0064/1097] ZetaGlobalSsp Analytics Adapter: refactoring (#11479) * ZetaGlobalSsp Analytics Adapter: refactoring * test * improve test * - * BidTimeout event * bidTimeoutHandler * - * ortb2 to only device --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 151 ++++++------------ .../zeta_global_sspAnalyticsAdapter_spec.js | 116 ++++++++------ 2 files changed, 116 insertions(+), 151 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index f0933d2f62f..2ba119b4d35 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -1,7 +1,7 @@ -import {logInfo, logError} from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import {logError} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; +import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -10,13 +10,9 @@ const ADAPTER_CODE = 'zeta_global_ssp'; const BASE_URL = 'https://ssp.disqus.com/prebid/event'; const LOG_PREFIX = 'ZetaGlobalSsp-Analytics: '; -const cache = { - auctions: {} -}; - /// /////////// VARIABLES //////////////////////////////////// -let publisherId; // int +let zetaParams; /// /////////// HELPER FUNCTIONS ///////////////////////////// @@ -28,154 +24,96 @@ function sendEvent(eventType, event) { ); } -function getZetaParams(event) { - if (event.adUnits) { - for (const i in event.adUnits) { - const unit = event.adUnits[i]; - if (unit.bids) { - for (const j in unit.bids) { - const bid = unit.bids[j]; - if (bid.bidder === ADAPTER_CODE && bid.params) { - return bid.params; - } - } - } - } - } - return null; -} - /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// function adRenderSucceededHandler(args) { - let eventType = EVENTS.AD_RENDER_SUCCEEDED - logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - const event = { - adId: args.adId, + zetaParams: zetaParams, + domain: args.doc?.location?.host, + page: args.doc?.location?.host + args.doc?.location?.pathname, bid: { adId: args.bid?.adId, - auctionId: args.bid?.auctionId, - adUnitCode: args.bid?.adUnitCode, - bidId: args.bid?.bidId, requestId: args.bid?.requestId, - bidderCode: args.bid?.bidderCode, - mediaTypes: args.bid?.mediaTypes, - sizes: args.bid?.sizes, - adserverTargeting: args.bid?.adserverTargeting, - cpm: args.bid?.cpm, + auctionId: args.bid?.auctionId, creativeId: args.bid?.creativeId, + bidder: args.bid?.bidderCode, mediaType: args.bid?.mediaType, - renderer: args.bid?.renderer, size: args.bid?.size, + adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, - params: args.bid?.params - }, - doc: { - location: args.doc?.location + cpm: args.bid?.cpm } } - - // set zetaParams from cache - if (event.bid && event.bid.auctionId) { - const zetaParams = cache.auctions[event.bid.auctionId]; - if (zetaParams) { - event.bid.params = [ zetaParams ]; - } - } - - sendEvent(eventType, event); + sendEvent(EVENTS.AD_RENDER_SUCCEEDED, event); } function auctionEndHandler(args) { - let eventType = EVENTS.AUCTION_END; - logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - const event = { - auctionId: args.auctionId, - adUnits: args.adUnits, + zetaParams: zetaParams, bidderRequests: args.bidderRequests?.map(br => ({ bidderCode: br?.bidderCode, - refererInfo: br?.refererInfo, + domain: br?.refererInfo?.domain, + page: br?.refererInfo?.page, bids: br?.bids?.map(b => ({ - adUnitCode: b?.adUnitCode, - auctionId: b?.auctionId, bidId: b?.bidId, - requestId: b?.requestId, - bidderCode: b?.bidderCode, - mediaTypes: b?.mediaTypes, - sizes: b?.sizes, + auctionId: b?.auctionId, bidder: b?.bidder, - params: b?.params + mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), + size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s) })) })), bidsReceived: args.bidsReceived?.map(br => ({ adId: br?.adId, - adserverTargeting: { - hb_adomain: br?.adserverTargeting?.hb_adomain - }, - cpm: br?.cpm, + requestId: br?.requestId, creativeId: br?.creativeId, + bidder: br?.bidder, mediaType: br?.mediaType, - renderer: br?.renderer, size: br?.size, + adomain: br?.adserverTargeting?.hb_adomain, timeToRespond: br?.timeToRespond, - adUnitCode: br?.adUnitCode, - auctionId: br?.auctionId, - bidId: br?.bidId, - requestId: br?.requestId, - bidderCode: br?.bidderCode, - mediaTypes: br?.mediaTypes, - sizes: br?.sizes, - bidder: br?.bidder, - params: br?.params + cpm: br?.cpm })) } + sendEvent(EVENTS.AUCTION_END, event); +} - // save zetaParams to cache - const zetaParams = getZetaParams(event); - if (zetaParams && event.auctionId) { - cache.auctions[event.auctionId] = zetaParams; +function bidTimeoutHandler(args) { + const event = { + zetaParams: zetaParams, + timeouts: args.map(t => ({ + bidId: t?.bidId, + auctionId: t?.auctionId, + bidder: t?.bidder, + mediaType: t?.mediaTypes?.video ? 'VIDEO' : (t?.mediaTypes?.banner ? 'BANNER' : undefined), + size: t?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), + timeout: t?.timeout, + device: t?.ortb2?.device + })) } - - sendEvent(eventType, event); + sendEvent(EVENTS.BID_TIMEOUT, event); } /// /////////// ADAPTER DEFINITION /////////////////////////// -let baseAdapter = adapter({ analyticsType: 'endpoint' }); +let baseAdapter = adapter({analyticsType: 'endpoint'}); let zetaAdapter = Object.assign({}, baseAdapter, { enableAnalytics(config = {}) { - let error = false; - - if (typeof config.options === 'object') { - if (config.options.sid) { - publisherId = Number(config.options.sid); - } + if (config.options && config.options.sid) { + zetaParams = config.options; + baseAdapter.enableAnalytics.call(this, config); } else { logError(LOG_PREFIX + 'Config not found'); - error = true; - } - - if (!publisherId) { - logError(LOG_PREFIX + 'Missing sid (publisher id)'); - error = true; - } - - if (error) { logError(LOG_PREFIX + 'Analytics is disabled due to error(s)'); - } else { - baseAdapter.enableAnalytics.call(this, config); } }, disableAnalytics() { - publisherId = undefined; + zetaParams = undefined; baseAdapter.disableAnalytics.apply(this, arguments); }, - track({ eventType, args }) { + track({eventType, args}) { switch (eventType) { case EVENTS.AD_RENDER_SUCCEEDED: adRenderSucceededHandler(args); @@ -183,6 +121,9 @@ let zetaAdapter = Object.assign({}, baseAdapter, { case EVENTS.AUCTION_END: auctionEndHandler(args); break; + case EVENTS.BID_TIMEOUT: + bidTimeoutHandler(args); + break; } } }); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index fa4b50d7693..604bc780d6b 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; import {config} from 'src/config'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {server} from '../../mocks/xhr.js'; import {logError} from '../../../src/utils'; @@ -113,6 +113,8 @@ const SAMPLE_EVENTS = { 'auctionStart': 1638441234544, 'timeout': 400, 'refererInfo': { + 'page': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'domain': 'test-zeta-ssp.net:63342', 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', 'reachedTop': true, 'isAmp': false, @@ -172,6 +174,8 @@ const SAMPLE_EVENTS = { 'auctionStart': 1638441234544, 'timeout': 400, 'refererInfo': { + 'page': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'domain': 'test-zeta-ssp.net:63342', 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', 'reachedTop': true, 'isAmp': false, @@ -280,7 +284,7 @@ const SAMPLE_EVENTS = { 'location': { 'href': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', 'protocol': 'http:', - 'host': 'localhost:63342', + 'host': 'test-zeta-ssp.net', 'hostname': 'localhost', 'port': '63342', 'pathname': '/zeta-ssp/ssp/_dev/examples/page_banner.html', @@ -322,12 +326,6 @@ const SAMPLE_EVENTS = { 'bidder': 'zeta_global_ssp', 'adUnitCode': '/19968336/header-bid-tag-0', 'timeToRespond': 123, - 'pbLg': '2.00', - 'pbMg': '2.20', - 'pbHg': '2.25', - 'pbAg': '2.25', - 'pbDg': '2.25', - 'pbCg': '', 'size': '480x320', 'adserverTargeting': { 'hb_bidder': 'zeta_global_ssp', @@ -349,11 +347,11 @@ const SAMPLE_EVENTS = { } } -describe('Zeta Global SSP Analytics Adapter', function() { +describe('Zeta Global SSP Analytics Adapter', function () { let sandbox; let requests; - beforeEach(function() { + beforeEach(function () { sandbox = sinon.sandbox.create(); requests = server.requests; sandbox.stub(events, 'getEvents').returns([]); @@ -372,11 +370,15 @@ describe('Zeta Global SSP Analytics Adapter', function() { expect(utils.logError.called).to.equal(true); }); - describe('handle events', function() { - beforeEach(function() { + describe('handle events', function () { + beforeEach(function () { zetaAnalyticsAdapter.enableAnalytics({ options: { - sid: 111 + sid: 111, + tags: { + position: 'top', + shortname: 'name' + } } }); }); @@ -385,21 +387,7 @@ describe('Zeta Global SSP Analytics Adapter', function() { zetaAnalyticsAdapter.disableAnalytics(); }); - it('Move ZetaParams through analytics events', function() { - this.timeout(3000); - - events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); - events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); - - expect(requests.length).to.equal(2); - const auctionEnd = JSON.parse(requests[0].requestBody); - const auctionSucceeded = JSON.parse(requests[1].requestBody); - - expect(auctionSucceeded.bid.params[0]).to.be.deep.equal(SAMPLE_EVENTS.AUCTION_END.adUnits[0].bids[0].params); - expect(SAMPLE_EVENTS.AUCTION_END.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); - }); - - it('Keep only needed fields', function() { + it('Handle events', function () { this.timeout(3000); events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); @@ -409,24 +397,60 @@ describe('Zeta Global SSP Analytics Adapter', function() { const auctionEnd = JSON.parse(requests[0].requestBody); const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionEnd.adUnitCodes).to.be.undefined; - expect(auctionEnd.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.auctionEnd).to.be.undefined; - expect(auctionEnd.auctionId).to.be.equal('75e394d9'); - expect(auctionEnd.bidderRequests[0].bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.bidderRequests[0].bids[0].bidId).to.be.equal('206be9a13236af'); - expect(auctionEnd.bidderRequests[0].bids[0].adUnitCode).to.be.equal('/19968336/header-bid-tag-0'); - expect(auctionEnd.bidsReceived[0].bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.bidsReceived[0].adserverTargeting.hb_adomain).to.be.equal('example.adomain'); - expect(auctionEnd.bidsReceived[0].auctionId).to.be.equal('75e394d9'); - - expect(auctionSucceeded.adId).to.be.equal('5759bb3ef7be1e8'); - expect(auctionSucceeded.bid.auctionId).to.be.equal('75e394d9'); - expect(auctionSucceeded.bid.requestId).to.be.equal('206be9a13236af'); - expect(auctionSucceeded.bid.bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionSucceeded.bid.creativeId).to.be.equal('456456456'); - expect(auctionSucceeded.bid.size).to.be.equal('480x320'); - expect(auctionSucceeded.doc.location.hostname).to.be.equal('localhost'); + expect(auctionEnd).to.be.deep.equal({ + zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, + bidderRequests: [{ + bidderCode: 'zeta_global_ssp', + domain: 'test-zeta-ssp.net:63342', + page: 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + bids: [{ + bidId: '206be9a13236af', + auctionId: '75e394d9', + bidder: 'zeta_global_ssp', + mediaType: 'BANNER', + size: '300x250' + }] + }, { + bidderCode: 'appnexus', + domain: 'test-zeta-ssp.net:63342', + page: 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + bids: [{ + bidId: '41badc0e164c758', + auctionId: '75e394d9', + bidder: 'appnexus', + mediaType: 'BANNER', + size: '300x250' + }] + }], + bidsReceived: [{ + adId: '5759bb3ef7be1e8', + requestId: '206be9a13236af', + creativeId: '456456456', + bidder: 'zeta_global_ssp', + mediaType: 'banner', + size: '480x320', + adomain: 'example.adomain', + timeToRespond: 123, + cpm: 2.258302852806723 + }] + }); + expect(auctionSucceeded).to.be.deep.equal({ + zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, + domain: 'test-zeta-ssp.net', + page: 'test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html', + bid: { + adId: '5759bb3ef7be1e8', + requestId: '206be9a13236af', + auctionId: '75e394d9', + creativeId: '456456456', + bidder: 'zeta_global_ssp', + mediaType: 'banner', + size: '480x320', + adomain: 'example.adomain', + timeToRespond: 123, + cpm: 2.258302852806723 + } + }); }); }); }); From 39787dc1c67c4ccbe2c2aaf0b4be69102e7b2c69 Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Wed, 22 May 2024 00:09:21 +0800 Subject: [PATCH 0065/1097] FreeWheel Bid Adapter: remove .innerText for PrebidJS 9.0 (#11532) * freewheel add schain in the request * FreeWheel-SSP-Adapter: remove the innerText for 9.0 release * update test --- modules/freewheel-sspBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index e11aa3f8fb7..0ac848bf62a 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -76,7 +76,7 @@ function getPricing(xmlNode) { var priceNode = pricingExtNode.querySelector('Price'); princingData = { currency: priceNode.getAttribute('currency'), - price: priceNode.textContent || priceNode.innerText + price: priceNode.textContent }; } else { logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.'); @@ -110,7 +110,7 @@ function getAdvertiserDomain(xmlNode) { // Currently we only return one Domain if (brandExtNode) { var domainNode = brandExtNode.querySelector('Domain'); - domain.push(domainNode.textContent || domainNode.innerText); + domain.push(domainNode.textContent); } else { logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing StickyBrand extension.'); } From 8590378e9e46c0f159bd38f26723fa3f8b4823e0 Mon Sep 17 00:00:00 2001 From: balajimediafuse <87535823+balajimediafuse@users.noreply.github.com> Date: Tue, 21 May 2024 22:03:07 +0530 Subject: [PATCH 0066/1097] Mediafuse Bid Adapter : remove transformBidParams function (#11534) * removed transformBidParams function * fix lint issues --------- Co-authored-by: Chris Huie --- modules/mediafuseBidAdapter.js | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 5e31f60d3b5..d969314f406 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -29,11 +29,9 @@ import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; import { getANKewyordParamFromMaps, - getANKeywordParam, - transformBidderParamKeywords + getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -351,31 +349,6 @@ export const spec = { } }, - transformBidParams: function (params, isOpenRtb) { - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': transformBidderParamKeywords, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid From 040dc1c49d8e0690440a4760f9e01c3a0b64dadf Mon Sep 17 00:00:00 2001 From: decemberWP <155962474+decemberWP@users.noreply.github.com> Date: Wed, 22 May 2024 03:19:34 +0200 Subject: [PATCH 0067/1097] cee Id System : initial ID module release (#11510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * add pirIdSystem * pirIdSystem * piridSystem - preCR * fix after CR * name change --------- Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: Wojciech Biały --- modules/ceeIdSystem.js | 62 +++++++++++++++++++++ modules/ceeIdSystem.md | 27 ++++++++++ test/spec/modules/ceeIdSystem_spec.js | 77 +++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 modules/ceeIdSystem.js create mode 100644 modules/ceeIdSystem.md create mode 100644 test/spec/modules/ceeIdSystem_spec.js diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js new file mode 100644 index 00000000000..9c8f1409fd3 --- /dev/null +++ b/modules/ceeIdSystem.js @@ -0,0 +1,62 @@ +/** + * This module adds ceeId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/ceeId + * @requires module:modules/userId + */ + +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { submodule } from '../src/hook.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const MODULE_NAME = 'ceeId'; +const ID_TOKEN = 'WPxid'; +export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); + +/** + * Reads the ID token from local storage or cookies. + * @returns {string|undefined} The ID token, or undefined if not found. + */ +export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); + +/** @type {Submodule} */ +export const ceeIdSubmodule = { + name: MODULE_NAME, + gvlid: 676, + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {string} value + * @returns {(Object|undefined)} + */ + decode(value) { + return typeof value === 'string' ? { 'ceeId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value + * @function + * @returns {(IdResponse|undefined)} + */ + getId() { + const ceeIdToken = readId(); + + return ceeIdToken ? { id: ceeIdToken } : undefined; + }, + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME), + eids: { + 'ceeId': { + source: 'ceeid.eu', + atype: 1 + }, + }, +}; + +submodule('userId', ceeIdSubmodule); diff --git a/modules/ceeIdSystem.md b/modules/ceeIdSystem.md new file mode 100644 index 00000000000..811efe08069 --- /dev/null +++ b/modules/ceeIdSystem.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: ceeIdSystem +Module Type: UserID Module +Maintainer: pawel.grudzien@grupawp.pl + +# Description + +User identification system for WPM + +### Prebid Params example + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360 + } + }] + } +}); +``` diff --git a/test/spec/modules/ceeIdSystem_spec.js b/test/spec/modules/ceeIdSystem_spec.js new file mode 100644 index 00000000000..049e596ad15 --- /dev/null +++ b/test/spec/modules/ceeIdSystem_spec.js @@ -0,0 +1,77 @@ +import { ceeIdSubmodule, storage, readId } from 'modules/ceeIdSystem.js'; +import sinon from 'sinon'; + +describe('ceeIdSystem', () => { + let sandbox; + let getCookieStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + getCookieStub = sandbox.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId', () => { + it('should return an object with id when ceeIdToken is found', () => { + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = ceeIdSubmodule.getId(); + + expect(result).to.deep.equal({ id: 'testToken' }); + }); + + it('should return undefined when ceeIdToken is not found', () => { + const result = ceeIdSubmodule.getId(); + + expect(result).to.be.undefined; + }); + }); + + describe('decode', () => { + it('should return an object with ceeId when value is a string', () => { + const result = ceeIdSubmodule.decode('testId'); + + expect(result).to.deep.equal({ ceeId: 'testId' }); + }); + + it('should return undefined when value is not a string', () => { + const result = ceeIdSubmodule.decode({}); + + expect(result).to.be.undefined; + }); + }); + + describe('readId', () => { + it('should return data from local storage when it exists', () => { + getDataFromLocalStorageStub.returns('local_storage_data'); + + const result = readId(); + + expect(result).to.equal('local_storage_data'); + }); + + it('should return data from cookie when local storage data does not exist', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns('cookie_data'); + + const result = readId(); + + expect(result).to.equal('cookie_data'); + }); + + it('should return null when neither local storage data nor cookie data exists', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns(null); + + const result = readId(); + + expect(result).to.be.null; + }); + }); +}); From adb339981bdbac7a0fa12913ca6005b2502dc6a4 Mon Sep 17 00:00:00 2001 From: Omer Dotan <54346241+omerDotan@users.noreply.github.com> Date: Wed, 22 May 2024 09:42:50 +0300 Subject: [PATCH 0068/1097] Browsi RTD : add split key (#11445) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * browsi-rtd-kv * browsi-rtd-kv (lint) * unit tests * lint fix --- modules/browsiRtdProvider.js | 13 ++++++++ test/spec/modules/browsiRtdProvider_spec.js | 33 ++++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 01a38c63b69..46393b37a05 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,6 +13,7 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName + * @property {?string} splitKey */ import {deepClone, deepSetValue, isFn, isGptPubadsDefined, isNumber, logError, logInfo, generateUUID} from '../src/utils.js'; @@ -33,6 +34,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'browsi'; const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); +const RANDOM = Math.floor(Math.random() * 10) + 1; /** @type {ModuleParams} */ let _moduleParams = {}; @@ -58,12 +60,22 @@ export function addBrowsiTag(data) { script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); script.prebidData = deepClone(typeof data === 'string' ? Object(data) : data) + script.brwRandom = RANDOM; if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } return script; } +export function setKeyValue(key) { + if (!key || typeof key !== 'string') return false; + window.googletag = window.googletag || {cmd: []}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting(key, RANDOM.toString()); + }); +} + export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { @@ -380,6 +392,7 @@ function init(moduleConfig) { _moduleParams = moduleConfig.params; if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { collectData(); + setKeyValue(_moduleParams.splitKey); } else { logError('missing params for Browsi provider'); } diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 5fcc78f4322..92a08c8fbef 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -1,9 +1,9 @@ import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; -import {makeSlot} from '../integration/faker/googletag.js'; import * as utils from '../../../src/utils' import * as events from '../../../src/events'; import * as sinon from 'sinon'; import {sendPageviewEvent} from '../../../modules/browsiRtdProvider.js'; +import * as mockGpt from 'test/spec/integration/faker/googletag.js'; describe('browsi Real time data sub module', function () { const conf = { @@ -55,7 +55,7 @@ describe('browsi Real time data sub module', function () { }); it('should match placement with ad unit', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc']); // true const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc', '/456/def']); // true @@ -69,7 +69,7 @@ describe('browsi Real time data sub module', function () { }); it('should return correct macro values', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); slot.setTargeting('test', ['test', 'value']); // slot getTargeting doesn't act like GPT so we can't expect real value @@ -90,7 +90,7 @@ describe('browsi Real time data sub module', function () { }); it('should return prediction from server', function () { - makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); + mockGpt.makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); const data = { p: {'hasPrediction': {ps: {0: 0.234}}}, kn: 'bv', @@ -266,4 +266,29 @@ describe('browsi Real time data sub module', function () { expect(eventsEmitSpy.callCount).to.equal(0); }) }) + + describe('set targeting - invalid params', function () { + it('should return false if key is undefined', function () { + expect(browsiRTD.setKeyValue()).to.equal(false); + }) + it('should return false if key is not string', function () { + expect(browsiRTD.setKeyValue(1)).to.equal(false); + }) + }) + describe('set targeting - valid params', function () { + let slot; + const splitKey = 'splitTest'; + before(() => { + mockGpt.reset(); + window.googletag.pubads().clearTargeting(); + slot = mockGpt.makeSlot({code: '/123/split', divId: 'split'}); + browsiRTD.setKeyValue(splitKey); + window.googletag.cmd.forEach(cmd => cmd()); + }) + it('should place numeric key value on all slots', function () { + const targetingValue = window.googletag.pubads().getTargeting(splitKey); + expect(targetingValue).to.be.an('array').that.is.not.empty; + expect(targetingValue[0]).to.be.a('string'); + }) + }) }); From 1400fbed357cf58d8341e7d3728928d18347f40d Mon Sep 17 00:00:00 2001 From: Baptiste Haudegand <89531368+github-baptiste-haudegand@users.noreply.github.com> Date: Wed, 22 May 2024 12:25:48 +0200 Subject: [PATCH 0069/1097] Add GPP support in Teads adapter (#11535) --- modules/teadsBidAdapter.js | 12 +++++ test/spec/modules/teadsBidAdapter_spec.js | 53 +++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index a3c8d3e24dc..8f775f2580d 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -81,6 +81,18 @@ export const spec = { payload.schain = firstBidRequest.schain; } + let gpp = bidderRequest.gppConsent; + if (bidderRequest && gpp) { + let isValidConsentString = typeof gpp.gppString === 'string'; + let validateApplicableSections = + Array.isArray(gpp.applicableSections) && + gpp.applicableSections.every((section) => typeof (section) === 'number') + payload.gpp = { + consentString: isValidConsentString ? gpp.gppString : '', + applicableSectionIds: validateApplicableSections ? gpp.applicableSections : [], + }; + } + let gdpr = bidderRequest.gdprConsent; if (bidderRequest && gdpr) { let isCmp = typeof gdpr.gdprApplies === 'boolean'; diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 81e09b09d08..40011367ac0 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -142,6 +142,59 @@ describe('teadsBidAdapter', () => { expect(payload.us_privacy).to.equal(usPrivacy); }); + it('should send GPP values to endpoint when available and valid', function () { + let consentString = 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'; + let applicableSectionIds = [7, 8]; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': applicableSectionIds + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(consentString); + expect(payload.gpp.applicableSectionIds).to.have.members(applicableSectionIds); + }); + + it('should send default GPP values to endpoint when available but invalid', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': undefined, + 'applicableSections': ['a'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(''); + expect(payload.gpp.applicableSectionIds).to.have.members([]); + }); + + it('should not set the GPP object in the request sent to the endpoint when not present', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.not.exist; + }); + it('should send GDPR to endpoint', function() { let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { From 4d0d0b79fe0ca5af5ef857ea22de08c5c66c8d66 Mon Sep 17 00:00:00 2001 From: fliccione <165936219+fliccione@users.noreply.github.com> Date: Wed, 22 May 2024 14:10:53 +0200 Subject: [PATCH 0070/1097] Onetag Bid Adapter: add reading of ortb2Imp field (#11539) --- modules/onetagBidAdapter.js | 1 + test/spec/modules/onetagBidAdapter_spec.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index d8423253aaf..62bee5c2aeb 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -291,6 +291,7 @@ function setGeneralInfo(bidRequest) { this['gpid'] = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); this['pubId'] = params.pubId; this['ext'] = params.ext; + this['ortb2Imp'] = deepAccess(bidRequest, 'ortb2Imp'); if (params.pubClick) { this['click'] = params.pubClick; } diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 93db5ffc57f..3ceaec13cd5 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -226,6 +226,7 @@ describe('onetag', function () { 'bidId', 'bidderRequestId', 'pubId', + 'ortb2Imp', 'transactionId', 'context', 'playerSize', @@ -240,6 +241,7 @@ describe('onetag', function () { 'bidId', 'bidderRequestId', 'pubId', + 'ortb2Imp', 'transactionId', 'mediaTypeInfo', 'sizes', @@ -270,6 +272,7 @@ describe('onetag', function () { expect(payload.bids).to.exist.and.to.have.length(1); expect(payload.bids[0].auctionId).to.equal(bannerBid.ortb2.source.tid); expect(payload.bids[0].transactionId).to.equal(bannerBid.ortb2Imp.ext.tid); + expect(payload.bids[0].ortb2Imp).to.deep.equal(bannerBid.ortb2Imp); }); it('should send GDPR consent data', function () { let consentString = 'consentString'; From e55a64b33c44d136d54ed71e347ce840ad023c70 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Wed, 22 May 2024 22:18:34 +1000 Subject: [PATCH 0071/1097] Handle kvs and segments better from multiple sources (#11508) --- modules/adnuntiusBidAdapter.js | 86 ++++++++++++------- test/spec/modules/adnuntiusBidAdapter_spec.js | 26 ++++-- 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 060f87c0f9c..189e2287571 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -130,31 +130,6 @@ const storageTool = (function () { return (meta && meta.usi) ? meta.usi : false } - const getSegmentsFromOrtb = function (ortb2) { - const userData = deepAccess(ortb2, 'user.data'); - let segments = []; - if (userData) { - userData.forEach(userdat => { - if (userdat.segment) { - segments.push(...userdat.segment.map((segment) => { - if (isStr(segment)) return segment; - if (isStr(segment.id)) return segment.id; - }).filter((seg) => !!seg)); - } - }); - } - return segments - } - - const getKvsFromOrtb = function (ortb2) { - const siteData = deepAccess(ortb2, 'site.ext.data'); - if (siteData) { - return siteData - } else { - return null - } - } - return { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; @@ -171,25 +146,69 @@ const storageTool = (function () { return voidAuId.auId; }); } - metaInternal.segments = getSegmentsFromOrtb(ortb2); - metaInternal.kv = getKvsFromOrtb(ortb2); }, saveToStorage: function (serverData, network) { setMetaInternal(serverData, network); }, getUrlRelatedData: function () { // getting the URL information is theoretically not network-specific - const { segments, kv, usi, voidAuIdsArray } = metaInternal; - return { segments, kv, usi, voidAuIdsArray }; + const { usi, voidAuIdsArray } = metaInternal; + return { usi, voidAuIdsArray }; }, getPayloadRelatedData: function (network) { // getting the payload data should be network-specific - const { segments, kv, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); + const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); return payloadRelatedData; } }; })(); +const targetingTool = (function() { + const getSegmentsFromOrtb = function(bidderRequest) { + const userData = deepAccess(bidderRequest.ortb2 || {}, 'user.data'); + let segments = []; + if (userData) { + userData.forEach(userdat => { + if (userdat.segment) { + segments.push(...userdat.segment.map((segment) => { + if (isStr(segment)) return segment; + if (isStr(segment.id)) return segment.id; + }).filter((seg) => !!seg)); + } + }); + } + return segments + }; + + const getKvsFromOrtb = function(bidderRequest) { + return deepAccess(bidderRequest.ortb2 || {}, 'site.ext.data'); + }; + + return { + addSegmentsToUrlData: function (validBids, bidderRequest, existingUrlRelatedData) { + let segments = getSegmentsFromOrtb(bidderRequest || {}); + + for (let i = 0; i < validBids.length; i++) { + const bid = validBids[i]; + const targeting = bid.params.targeting || {}; + if (Array.isArray(targeting.segments)) { + segments = segments.concat(targeting.segments); + delete bid.params.targeting.segments; + } + } + + existingUrlRelatedData.segments = segments; + }, + mergeKvsFromOrtb: function(bidTargeting, bidderRequest) { + const kv = getKvsFromOrtb(bidderRequest || {}); + if (!kv) { + return; + } + bidTargeting.kv = {...kv, ...bidTargeting.kv}; + } + } +})(); + const validateBidType = function (bidTypeOption) { return VALID_BID_TYPES.indexOf(bidTypeOption || '') > -1 ? bidTypeOption : 'bid'; } @@ -227,6 +246,7 @@ export const spec = { storageTool.refreshStorage(bidderRequest); const urlRelatedMetaData = storageTool.getUrlRelatedData(); + targetingTool.addSegmentsToUrlData(validBidRequests, bidderRequest, urlRelatedMetaData); if (urlRelatedMetaData.segments.length > 0) queryParamsAndValues.push('segments=' + urlRelatedMetaData.segments.join(',')); if (urlRelatedMetaData.usi) queryParamsAndValues.push('userId=' + urlRelatedMetaData.usi); @@ -261,9 +281,9 @@ export const spec = { networks[network].metaData = payloadRelatedData; } - const targeting = bid.params.targeting || {}; - if (urlRelatedMetaData.kv) targeting.kv = urlRelatedMetaData.kv; - const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; + const bidTargeting = {...bid.params.targeting || {}}; + targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest); + const adUnit = { ...bidTargeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { adUnit.maxDeals = maxDeals; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index c288bfb4f12..4044e62280a 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -566,7 +566,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); - it('should pass segments if available in config', function () { + it('should pass segments if available in config and merge from targeting', function () { const ortb2 = { user: { data: [{ @@ -580,10 +580,16 @@ describe('adnuntiusBidAdapter', function () { } }; + bidderRequests[0].params.targeting = { + segments: ['merge-this', 'and-this'] + }; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS.replace('segment3', 'segment3,merge-this,and-this')); + + delete bidderRequests[0].params.targeting; }); it('should pass site data ext as key values to ad server', function () { @@ -592,20 +598,28 @@ describe('adnuntiusBidAdapter', function () { ext: { data: { '12345': 'true', - '45678': 'true' + '45678': 'true', + '9090': 'should-be-overwritten' } } } }; - + bidderRequests[0].params.targeting = { + kv: { + 'merge': ['this'], + '9090': ['take it over'] + } + }; const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); - expect(data.adUnits[0].kv).to.have.property('12345'); expect(data.adUnits[0].kv['12345']).to.equal('true'); - expect(data.adUnits[0].kv).to.have.property('45678'); expect(data.adUnits[0].kv['45678']).to.equal('true'); + expect(data.adUnits[0].kv['9090'][0]).to.equal('take it over'); + expect(data.adUnits[0].kv['merge'][0]).to.equal('this'); + + delete bidderRequests[0].params.targeting; }); it('should skip passing site data ext if missing', function () { From 178ef64b11bd73638c8098c9270bb5b84b52cd11 Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Thu, 23 May 2024 01:09:23 +0530 Subject: [PATCH 0072/1097] PubMatic Bid Adapter : start sending displaymanager & displaymanagerver (#11530) * Added support for displaymanager & version to pubmatic adapter * Added comments --- modules/pubmaticBidAdapter.js | 4 +++- test/spec/modules/pubmaticBidAdapter_spec.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 7ff86c5c46f..a0e117a2e1b 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -673,7 +673,9 @@ function _createImpressionObject(bid, bidderRequest) { ext: { pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) }, - bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY + bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY, + displaymanager: 'Prebid.js', + displaymanagerver: '$prebid.version$' // prebid version }; _addPMPDealsInImpression(impObj, bid); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index ebda4b1767d..e47d301fae4 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1187,6 +1187,8 @@ describe('PubMatic adapter', function () { expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); expect(data.ext.epoch).to.exist; + expect(data.imp[0].displaymanager).to.equal('Prebid.js'); + expect(data.imp[0].displaymanagerver).to.equal('$prebid.version$'); }); it('Set tmax from global config if not set by requestBids method', function() { From 565825f9d558fbdf370162e545de358e8b70c80f Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Thu, 23 May 2024 00:16:22 +0200 Subject: [PATCH 0073/1097] Forward extended parameters (#11527) --- modules/livewrappedAnalyticsAdapter.js | 10 +--- .../livewrappedAnalyticsAdapter_spec.js | 51 +++++++++++++------ 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 2797664e954..413147b4fa4 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { timestamp, logInfo, getWindowTop } from '../src/utils.js'; +import { timestamp, logInfo } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS, STATUS } from '../src/constants.js'; @@ -171,7 +171,7 @@ livewrappedAnalyticsAdapter.sendEvents = function() { timeouts: getTimeouts(sentRequests.gdpr, sentRequests.auctionIds), bidAdUnits: getbidAdUnits(), rf: getAdRenderFailed(sentRequests.auctionIds), - rcv: getAdblockerRecovered() + ext: initOptions.ext }; if (events.requests.length == 0 && @@ -185,12 +185,6 @@ livewrappedAnalyticsAdapter.sendEvents = function() { ajax(initOptions.endpoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); }; -function getAdblockerRecovered() { - try { - return getWindowTop().I12C && getWindowTop().I12C.Morph === 1; - } catch (e) {} -} - function getSentRequests() { var sentRequests = []; var gdpr = []; diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index f82cc4c4f52..1e5f089ca53 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -341,7 +341,6 @@ describe('Livewrapped analytics adapter', function () { }); it('should build a batched message from prebid events', function () { - sandbox.stub(utils, 'getWindowTop').returns({}); performStandardAuction(); clock.tick(BID_WON_TIMEOUT + 1000); @@ -403,20 +402,6 @@ describe('Livewrapped analytics adapter', function () { expect(message.timeouts[0].adUnit).to.equal('panorama_d_1'); }); - it('should detect adblocker recovered request', function () { - sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); - performStandardAuction(); - - clock.tick(BID_WON_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - let message = JSON.parse(request.requestBody); - - expect(message.rcv).to.equal(true); - }); - it('should forward GDPR data', function () { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, { @@ -623,4 +608,40 @@ describe('Livewrapped analytics adapter', function () { expect(request.url).to.equal('https://whitelabeled.com/analytics/10'); }); }); + + describe('when given extended options', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'livewrapped', + adapter: livewrappedAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'livewrapped', + options: { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + ext: { + testparam: 123 + } + } + }); + }); + + afterEach(function () { + livewrappedAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward the extended options', function () { + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.ext).to.not.equal(null); + expect(message.ext.testparam).to.equal(123); + }); + }); }); From e8c68911a5894490b70b0833ed9350a1bc581a54 Mon Sep 17 00:00:00 2001 From: Eugene Dorfman Date: Thu, 23 May 2024 16:55:56 +0200 Subject: [PATCH 0074/1097] 51d: remove specific API request limits from doc (#11546) API limits should not be hardcoded in the module doc, as they might change - the interested use should check the pricing plan web page that contains actual information. --- modules/51DegreesRtdProvider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md index 991e756a0d7..424a559797d 100644 --- a/modules/51DegreesRtdProvider.md +++ b/modules/51DegreesRtdProvider.md @@ -49,7 +49,7 @@ In order to use the module please first obtain a Resource Key using the [Configu * ScreenInchesWidth * PixelRatio (optional) -PixelRatio is desirable, but it's a paid property requiring a paid license. Also free API service is limited to 500,000 requests per month - consider picking a [51Degrees pricing plan](https://51degrees.com/pricing) that fits your needs. +PixelRatio is desirable, but it's a paid property requiring a paid license. Free API service is limited. Please check [51Degrees pricing](https://51degrees.com/pricing) to choose a plan that suits your needs. #### User Agent Client Hint (UA-CH) Permissions From e07451212d8b708fe662031f8a4a1e159b469b49 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Thu, 23 May 2024 17:21:43 +0200 Subject: [PATCH 0075/1097] LiveIntent Identity Module: Introduce First Party ID (#11437) * fpid * lint * Adjust tests * Fix test expectation * live-connect v6.7.3 --- modules/liveIntentIdSystem.js | 103 ++++++---- modules/userId/eids.md | 17 +- package-lock.json | 30 +-- package.json | 2 +- test/spec/modules/eids_spec.js | 16 +- test/spec/modules/liveIntentIdSystem_spec.js | 192 ++++++++++++------- 6 files changed, 230 insertions(+), 130 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 786feeb8052..6925f5fd4a0 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,12 +7,12 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports -import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { LiveConnect } from 'live-connect-js/prebid'; // eslint-disable-line prebid/validate-imports +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { UID2_EIDS } from '../libraries/uid2Eids/uid2Eids.js'; import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; -import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; import { getRefererInfo } from '../src/refererDetection.js'; /** @@ -21,12 +21,12 @@ import { getRefererInfo } from '../src/refererDetection.js'; * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ -const DEFAULT_AJAX_TIMEOUT = 5000 -const EVENTS_TOPIC = 'pre_lips' +const DEFAULT_AJAX_TIMEOUT = 5000; +const EVENTS_TOPIC = 'pre_lips'; const MODULE_NAME = 'liveIntentId'; const LI_PROVIDER_DOMAIN = 'liveintent.com'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); -const defaultRequestedAttributes = {'nonId': true} +const defaultRequestedAttributes = {'nonId': true}; const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { ajaxBuilder(timeout)( @@ -53,10 +53,10 @@ let liveConnect = null; */ export function reset() { if (window && window.liQ_instances) { - window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)); window.liQ_instances = []; } - liveIntentIdSubmodule.setModuleMode(null) + liveIntentIdSubmodule.setModuleMode(null); eventFired = false; liveConnect = null; } @@ -70,7 +70,7 @@ export function setEventFiredFlag() { function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; - collectConfig = collectConfig || {} + collectConfig = collectConfig || {}; collectConfig.appId && (config.appId = collectConfig.appId); collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); @@ -86,30 +86,39 @@ function parseLiveIntentCollectorConfig(collectConfig) { * @returns {Array} */ function parseRequestedAttributes(overrides) { + function renameAttribute(attribute) { + if (attribute === 'fpid') { + return 'idCookie'; + } else { + return attribute; + }; + } function createParameterArray(config) { - return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [k] : []); + return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [renameAttribute(k)] : []); } if (typeof overrides === 'object') { - return createParameterArray({...defaultRequestedAttributes, ...overrides}) + return createParameterArray({...defaultRequestedAttributes, ...overrides}); } else { return createParameterArray(defaultRequestedAttributes); } } function initializeLiveConnect(configParams) { - configParams = configParams || {}; if (liveConnect) { return liveConnect; } + configParams = configParams || {}; + const fpidConfig = configParams.fpid || {}; + const publisherId = configParams.publisherId || 'any'; const identityResolutionConfig = { publisherId: publisherId, requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) }; if (configParams.url) { - identityResolutionConfig.url = configParams.url - } + identityResolutionConfig.url = configParams.url; + }; identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; @@ -119,7 +128,7 @@ function initializeLiveConnect(configParams) { liveConnectConfig.distributorId = configParams.distributorId; identityResolutionConfig.source = configParams.distributorId; } else { - identityResolutionConfig.source = configParams.partner || 'prebid' + identityResolutionConfig.source = configParams.partner || 'prebid'; } liveConnectConfig.wrapperName = 'prebid'; @@ -127,11 +136,16 @@ function initializeLiveConnect(configParams) { liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; liveConnectConfig.fireEventDelay = configParams.fireEventDelay; + + liveConnectConfig.idCookie = {}; + liveConnectConfig.idCookie.name = fpidConfig.name; + liveConnectConfig.idCookie.strategy = fpidConfig.strategy == 'html5' ? 'localStorage' : fpidConfig.strategy; + const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; } - const gdprConsent = gdprDataHandler.getConsentData() + const gdprConsent = gdprDataHandler.getConsentData(); if (gdprConsent) { liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; liveConnectConfig.gdprConsent = gdprConsent.consentString; @@ -145,21 +159,21 @@ function initializeLiveConnect(configParams) { // The third param is the ajax and pixel object, the ajax and pixel use PBJS liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); if (configParams.emailHash) { - liveConnect.push({ hash: configParams.emailHash }) + liveConnect.push({ hash: configParams.emailHash }); } return liveConnect; } function tryFireEvent() { if (!eventFired && liveConnect) { - const eventDelay = liveConnect.config.fireEventDelay || 500 + const eventDelay = liveConnect.config.fireEventDelay || 500; setTimeout(() => { - const instances = window.liQ_instances - instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) + const instances = window.liQ_instances; + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)); if (!eventFired && liveConnect) { liveConnect.fire(); } - }, eventDelay) + }, eventDelay); } } @@ -173,10 +187,10 @@ export const liveIntentIdSubmodule = { name: MODULE_NAME, setModuleMode(mode) { - this.moduleMode = mode + this.moduleMode = mode; }, getInitializer() { - return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode) + return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode); }, /** @@ -194,46 +208,54 @@ export const liveIntentIdSubmodule = { const result = {}; // old versions stored lipbid in unifiedId. Ensure that we can still read the data. - const lipbid = value.nonId || value.unifiedId + const lipbid = value.nonId || value.unifiedId; if (lipbid) { - value.lipbid = lipbid - delete value.unifiedId - result.lipb = value + const lipb = { ...value, lipbid }; + delete lipb.unifiedId; + result.lipb = lipb; } // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. // As adapters are applied in lexicographical order, we will always // be overwritten by the 'proper' uid2 module if it is present. if (value.uid2) { - result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } } + result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.bidswitch) { - result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } + result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.medianet) { - result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } + result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.magnite) { - result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } } + result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.index) { - result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } + result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.openx) { - result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } + result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.pubmatic) { - result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } + result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.sovrn) { - result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } + result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } }; + } + + if (value.idCookie) { + if (!coppaDataHandler.getCoppa()) { + result.lipb = { ...result.lipb, fpid: value.idCookie }; + result.fpid = { 'id': value.idCookie }; + } + delete result.lipb.idCookie; } if (value.thetradedesk) { @@ -380,6 +402,13 @@ export const liveIntentIdSubmodule = { return data.ext; } } + }, + 'fpid': { + source: 'fpid.liveintent.com', + atype: 1, + getValue: function(data) { + return data.id; + } } } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index c10ecde9c30..aa1601e95e3 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -107,7 +107,7 @@ userIdAsEids = [ segments: ['s1', 's2'] } }, - + { source: 'bidswitch.net', uids: [{ @@ -118,7 +118,7 @@ userIdAsEids = [ } }] }, - + { source: 'liveintent.indexexchange.com', uids: [{ @@ -161,7 +161,7 @@ userIdAsEids = [ provider: 'liveintent.com' } }] - }, + }, { source: 'media.net', @@ -185,6 +185,17 @@ userIdAsEids = [ }] }, + { + source: 'fpid.liveintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + provider: 'liveintent.com' + } + }] + }, + { source: 'merkleinc.com', uids: [{ diff --git a/package-lock.json b/package-lock.json index 198eb105ed5..ab475b57fcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^6.3.4" + "live-connect-js": "^6.7.3" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -19842,19 +19842,19 @@ "dev": true }, "node_modules/live-connect-common": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.3.tgz", - "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz", + "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==", "engines": { "node": ">=18" } }, "node_modules/live-connect-js": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.4.tgz", - "integrity": "sha512-lg2XeCaj/eEbK66QGGDEdz9IdT/K3ExZ83Qo6xGVLdP5XJ33xAUCk/gds34rRTmpIwUfAnboOpyj3UoYtS3QUQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz", + "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==", "dependencies": { - "live-connect-common": "^v3.0.3", + "live-connect-common": "^v3.1.4", "tiny-hashes": "1.0.1" }, "engines": { @@ -44521,16 +44521,16 @@ "dev": true }, "live-connect-common": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.3.tgz", - "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==" + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz", + "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==" }, "live-connect-js": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.4.tgz", - "integrity": "sha512-lg2XeCaj/eEbK66QGGDEdz9IdT/K3ExZ83Qo6xGVLdP5XJ33xAUCk/gds34rRTmpIwUfAnboOpyj3UoYtS3QUQ==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz", + "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==", "requires": { - "live-connect-common": "^v3.0.3", + "live-connect-common": "^v3.1.4", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index 087cab55d7c..3ae934df3b5 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^6.3.4" + "live-connect-js": "^6.7.3" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index e1f2394ab27..260864dd1a2 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -1,7 +1,7 @@ import {createEidsArray} from 'modules/userId/eids.js'; import {expect} from 'chai'; -// Note: In unit tets cases for bidders, call the createEidsArray function over userId object that is used for calling fetchBids +// Note: In unit test cases for bidders, call the createEidsArray function over userId object that is used for calling fetchBids // this way the request will stay consistent and unit test cases will not need lots of changes. describe('eids array generation for known sub-modules', function() { @@ -184,6 +184,20 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('fpid; getValue call', function() { + const userId = { + fpid: { + id: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'fpid.liveintent.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + it('bidswitch', function() { const userId = { bidswitch: {'id': 'sample_id'} diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 373142db82e..e5caacd1547 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -1,13 +1,13 @@ import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; import * as utils from 'src/utils.js'; -import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; import * as refererDetection from '../../../src/refererDetection.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; -const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; +const defaultConfigParams = {publisherId: PUBLISHER_ID, fireEventDelay: 1}; const responseHeader = {'Content-Type': 'application/json'} function requests(...urlRegExps) { @@ -30,6 +30,7 @@ describe('LiveIntentId', function() { let getCookieStub; let getDataFromLocalStorageStub; let imgStub; + let coppaConsentDataStub; let refererInfoStub; beforeEach(function() { @@ -41,6 +42,7 @@ describe('LiveIntentId', function() { uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + coppaConsentDataStub = sinon.stub(coppaDataHandler, 'getCoppa'); refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); }); @@ -52,11 +54,12 @@ describe('LiveIntentId', function() { uspConsentDataStub.restore(); gdprConsentDataStub.restore(); gppConsentDataStub.restore(); + coppaConsentDataStub.restore(); refererInfoStub.restore(); resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { + it('should initialize LiveConnect with a privacy string when getId but not send request', function (done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -67,61 +70,58 @@ describe('LiveIntentId', function() { applicableSections: [1, 2] }) let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); - let request = idxRequests()[0]; - expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1%2C2.*/); - const response = { - unifiedId: 'a_unified_id', - segments: [123, 234] - } - request.respond( - 200, - responseHeader, - JSON.stringify(response) - ); - expect(callBackSpy.calledOnceWith(response)).to.be.true; + setTimeout(() => { + let requests = idxRequests().concat(rpRequests()); + expect(requests).to.be.empty; + expect(callBackSpy.notCalled).to.be.true; + done(); + }, 300) }); - it('should fire an event when getId', function(done) { + it('should fire an event without privacy setting when getId', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ - gdprApplies: true, + gdprApplies: false, consentString: 'consentDataString' }) gppConsentDataStub.returns({ gppString: 'gppConsentDataString', applicableSections: [1] }) - liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=0.*&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString.*&gpp_as=1.*/); done(); }, 300); }); it('should fire an event when getId and a hash is provided', function(done) { liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) done(); }, 300); }); it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) { - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.contain('tv=$prebid.version$') + let request = rpRequests()[0]; + expect(request.url).to.contain('tv=$prebid.version$') done(); }, 300); }); it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ url: 'https://dummy.liveintent.com', liCollectConfig: { @@ -131,7 +131,8 @@ describe('LiveIntentId', function() { } }}); setTimeout(() => { - expect(requests(/https:\/\/collector.liveintent.com.*/)[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + let request = requests(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(request.length).to.be.greaterThan(0); done(); }, 300); }); @@ -139,7 +140,8 @@ describe('LiveIntentId', function() { it('should fire an event with the provided distributorId', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111' } }); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); done(); }, 300); }); @@ -147,7 +149,7 @@ describe('LiveIntentId', function() { it('should fire an event without the provided distributorId when appId is provided', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }); setTimeout(() => { - const request = rpRequests()[0]; + let request = rpRequests()[0]; expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); expect(request.url).to.not.match(/.*did=*/); done(); @@ -164,42 +166,44 @@ describe('LiveIntentId', function() { gppString: 'gppConsentDataString', applicableSections: [1] }) - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); + let request = rpRequests()[0]; + expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 300); }); it('should fire an event when decode and a hash is provided', function(done) { liveIntentIdSubmodule.decode({}, { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); done(); }, 300); }); it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); + const result = liveIntentIdSubmodule.decode({ params: { fireEventDelay: 1, additionalData: 'data' } }); expect(result).to.be.eql({}); }); it('should fire an event when decode', function(done) { - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.be.not.null + expect(rpRequests().length).to.be.eq(1); done(); }, 300); }); it('should initialize LiveConnect and send data only once', function(done) { - liveIntentIdSubmodule.getId(defaultConfigParams); - liveIntentIdSubmodule.decode({}, defaultConfigParams); - liveIntentIdSubmodule.getId(defaultConfigParams); - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { expect(rpRequests().length).to.be.eq(1); done(); @@ -209,10 +213,10 @@ describe('LiveIntentId', function() { it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/dummy.liveintent.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 204, responseHeader @@ -226,7 +230,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/did-1111\/any\?.*did=did-1111.*&cd=.localhost.*&resolve=nonId.*/); request.respond( 204, responseHeader @@ -240,7 +244,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/any\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 204, responseHeader @@ -252,7 +256,7 @@ describe('LiveIntentId', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ 'url': 'https://dummy.liveintent.com/idex', 'partner': 'rubicon' @@ -260,7 +264,7 @@ describe('LiveIntentId', function() { } }).callback; submoduleCallback(callBackSpy); let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/dummy.liveintent.com\/idex\/rubicon\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 200, responseHeader, @@ -272,10 +276,10 @@ describe('LiveIntentId', function() { it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 200, responseHeader, @@ -287,10 +291,10 @@ describe('LiveIntentId', function() { it('should log an error and continue to callback if ajax request errors', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 503, responseHeader, @@ -304,10 +308,11 @@ describe('LiveIntentId', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' getCookieStub.withArgs('_lc2_fpi').returns(oldCookie) let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&resolve=nonId`); + const expected = new RegExp('https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*duid=' + oldCookie + '.*&cd=.localhost.*&resolve=nonId.*'); + expect(request.url).to.match(expected); request.respond( 200, responseHeader, @@ -321,7 +326,7 @@ describe('LiveIntentId', function() { getCookieStub.withArgs('_lc2_fpi').returns(oldCookie); getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); const configParams = { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ 'identifiersToResolve': ['_thirdPC'] } @@ -330,7 +335,8 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&_thirdPC=third-pc&resolve=nonId`); + const expected = new RegExp('https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*duid=' + oldCookie + '.*&cd=.localhost.*&_thirdPC=third-pc.*&resolve=nonId.*'); + expect(request.url).to.match(expected); request.respond( 200, responseHeader, @@ -343,7 +349,7 @@ describe('LiveIntentId', function() { getCookieStub.returns(null); getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'}); const configParams = { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ 'identifiersToResolve': ['_thirdPC'] } @@ -352,7 +358,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&_thirdPC=%7B%22key%22%3A%22value%22%7D.*&resolve=nonId.*/); request.respond( 200, responseHeader, @@ -363,29 +369,29 @@ describe('LiveIntentId', function() { it('should send an error when the cookie jar throws an unexpected error', function() { getCookieStub.throws('CookieError', 'A message'); - liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); expect(imgStub.getCall(0).args[0]).to.match(/.*ae=.+/); }); it('should decode a unifiedId to lipbId and remove it', function() { - const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); }); it('should decode a nonId to lipbId', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); }); it('should resolve extra attributes', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId&resolve=foo`); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*&resolve=foo.*/); request.respond( 200, responseHeader, @@ -395,66 +401,66 @@ describe('LiveIntentId', function() { }); it('should decode a uid2 to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with uid2 but no nonId', function() { - const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a medianet id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a sovrn id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a magnite id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an index id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an openx id to a separate object when present', function () { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an pubmatic id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a thetradedesk id to a separate object when present', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=uid2`); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=uid2.*/); request.respond( 200, responseHeader, @@ -462,4 +468,44 @@ describe('LiveIntentId', function() { ); expect(callBackSpy.calledOnce).to.be.true; }); -}); + + it('should decode a idCookie as fpid if it exists and coppa is false', function() { + coppaConsentDataStub.returns(false) + const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, { params: defaultConfigParams }) + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'fpid': 'bar'}, 'fpid': {'id': 'bar'}}) + }); + + it('should not decode a idCookie as fpid if it exists and coppa is true', function() { + coppaConsentDataStub.returns(true) + const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, { params: defaultConfigParams }) + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo'}}) + }); + + it('should resolve fpid from cookie', async function() { + const expectedValue = 'someValue' + const cookieName = 'testcookie' + getCookieStub.withArgs(cookieName).returns(expectedValue) + const config = { params: { + ...defaultConfigParams, + fpid: { 'strategy': 'cookie', 'name': cookieName }, + requestedAttributesOverrides: { 'fpid': true } } + } + const submoduleCallback = liveIntentIdSubmodule.getId(config).callback; + const decodedResult = new Promise(resolve => { + submoduleCallback((x) => resolve(liveIntentIdSubmodule.decode(x, config))); + }); + const request = idxRequests()[0]; + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&ic=someValue.*&resolve=nonId.*/); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + + const result = await decodedResult + expect(result).to.be.eql({ + lipb: { 'fpid': expectedValue }, + fpid: { id: expectedValue } + }); + }); +}) From f4d8ed3970a1a59c54f7155eda7a144a25a94b11 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 May 2024 18:51:54 +0000 Subject: [PATCH 0076/1097] Prebid 8.50.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 ab475b57fcd..c37ef8b8b72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.50.0-pre", + "version": "8.50.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 3ae934df3b5..00c739fcf22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.50.0-pre", + "version": "8.50.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 1ebf1a668ff3a44a2d4cf06caeea9752944ef512 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 May 2024 18:51:54 +0000 Subject: [PATCH 0077/1097] Increment version to 8.51.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 c37ef8b8b72..50d72367d6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.50.0", + "version": "8.51.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 00c739fcf22..c6d19acbe1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.50.0", + "version": "8.51.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b1d5237007165c162944a0fe462a4f89347d1d1a Mon Sep 17 00:00:00 2001 From: Cadent Aperture MX <43830380+EMXDigital@users.noreply.github.com> Date: Thu, 23 May 2024 18:31:02 -0400 Subject: [PATCH 0078/1097] CadentApertureMX Bid Adapter : update gpid support (#11557) Co-authored-by: Michael Denton --- modules/cadentApertureMXBidAdapter.js | 11 +++++----- .../cadentApertureMXBidAdapter_spec.js | 21 +++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/modules/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js index e73564dacdb..97283952888 100644 --- a/modules/cadentApertureMXBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -282,12 +282,13 @@ export const spec = { }; // adding gpid support - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); - if (!gpid) { - gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - } + let gpid = + deepAccess(bid, 'ortb2Imp.ext.gpid') || + deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot') || + deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { - data.ext = {gpid: gpid.toString()}; + data.ext = { gpid: gpid.toString() }; } let typeSpecifics = isVideo ? { video: cadentAdapter.buildVideo(bid) } : { banner: cadentAdapter.buildBanner(bid) }; let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; diff --git a/test/spec/modules/cadentApertureMXBidAdapter_spec.js b/test/spec/modules/cadentApertureMXBidAdapter_spec.js index 3ccb5405552..4f0f2cf8f20 100644 --- a/test/spec/modules/cadentApertureMXBidAdapter_spec.js +++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js @@ -451,10 +451,27 @@ describe('cadent_aperture_mx Adapter', function () { }); }); - it('should add gpid to request if present', () => { + it('should add gpid to request if present in ext.gpid', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { gpid, data: { adserver: { adslot: gpid + '1' }, pbadslot: gpid + '2' } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + + it('should add gpid to request if present in ext.data.adserver.adslot', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid }, pbadslot: gpid + '1' } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + + it('should add gpid to request if present in ext.data.pbadslot', () => { const gpid = '/12345/my-gpt-tag-0'; let bid = utils.deepClone(bidderRequest.bids[0]); - bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid } } } }; bid.ortb2Imp = { ext: { data: { pbadslot: gpid } } }; let requestWithGPID = spec.buildRequests([bid], bidderRequest); requestWithGPID = JSON.parse(requestWithGPID.data); From 8c4955bcfad5e6811e311629e38136cf872061b3 Mon Sep 17 00:00:00 2001 From: Andrius Versockas Date: Fri, 24 May 2024 02:11:12 +0300 Subject: [PATCH 0079/1097] Eskimi Bid Adapter: switching to plcmt (#11543) Co-authored-by: Andrius Versockas --- modules/eskimiBidAdapter.js | 5 ++--- test/spec/modules/eskimiBidAdapter_spec.js | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index ce01abb9e71..02568c01014 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -9,7 +9,6 @@ import {getBidIdParameter} from '../src/utils.js'; */ const BIDDER_CODE = 'eskimi'; -// const ENDPOINT = 'https://hb.eskimi.com/bids' const ENDPOINT = 'https://sspback.eskimi.com/bid-request' const DEFAULT_BID_TTL = 30; @@ -21,7 +20,7 @@ const VIDEO_ORTB_PARAMS = [ 'mimes', 'minduration', 'maxduration', - 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -142,7 +141,7 @@ function buildVideoImp(bidRequest, imp) { }); if (imp.video && videoParams?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; + imp.video.plcmt = imp.video.plcmt || 4; } return { ...imp }; diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js index d01240c86ab..c00a22a5aac 100644 --- a/test/spec/modules/eskimiBidAdapter_spec.js +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -33,7 +33,7 @@ const VIDEO_BID = { playbackmethod: [2, 4, 6], playerSize: [[1024, 768]], protocols: [3, 4, 7, 8, 10], - placement: 1, + plcmt: 1, minduration: 0, maxduration: 60, startdelay: 0 @@ -222,7 +222,7 @@ describe('Eskimi bid adapter', function () { mimes: ['video/mp4', 'video/x-flv'], playbackmethod: [3, 4], protocols: [5, 6], - placement: 1, + plcmt: 1, minduration: 0, maxduration: 60, w: 1024, From 8efc3be414f1d98bf18c2d06743b60198e2cafc5 Mon Sep 17 00:00:00 2001 From: adtech-colombia Date: Fri, 24 May 2024 05:32:09 +0530 Subject: [PATCH 0080/1097] Colombia Bid Adapter : initial release (#11478) * new Colombia bid Adapter implementation * new Colombia bid Adapter implementation * new Colombia bid Adapter implementation * new Colombia bid Adapter implementation- 'lint' errored Fixed * new Colombia bid Adapter implementation- Resolved test case issue * new Colombia bid Adapter implementation- Resolved test case issue * new Colombia bid Adapter implementation- Resolved test case issue --------- Co-authored-by: subhashish.singh --- modules/colombiaBidAdapter.js | 107 +++++++++++++ modules/colombiaBidAdapter.md | 31 ++++ package-lock.json | 2 +- test/spec/modules/colombiaBidAdapter_spec.js | 155 +++++++++++++++++++ 4 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 modules/colombiaBidAdapter.js create mode 100644 modules/colombiaBidAdapter.md create mode 100644 test/spec/modules/colombiaBidAdapter_spec.js diff --git a/modules/colombiaBidAdapter.js b/modules/colombiaBidAdapter.js new file mode 100644 index 00000000000..0d25ca6cb60 --- /dev/null +++ b/modules/colombiaBidAdapter.js @@ -0,0 +1,107 @@ +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'colombia'; +const ENDPOINT_URL = 'https://ade.clmbtech.com/cde/prebid.htm'; +const HOST_NAME = document.location.protocol + '//' + window.location.host; + +export const spec = { + code: BIDDER_CODE, + aliases: ['clmb'], + supportedMediaTypes: [BANNER], + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + buildRequests: function(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + let payloadArr = [] + let ctr = 1; + validBidRequests = validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const placementId = params.placementId; + const cb = Math.floor(Math.random() * 99999999999); + const bidId = bidRequest.bidId; + const referrer = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : ''; + let mediaTypes = {} + let payload = { + v: 'hb1', + p: placementId, + pos: '~' + ctr, + w: width, + h: height, + cb: cb, + r: referrer, + uid: bidId, + t: 'i', + d: HOST_NAME, + fpc: params.fpc, + _u: window.location.href, + mediaTypes: Object.assign({}, mediaTypes, bidRequest.mediaTypes) + }; + if (params.keywords) payload.keywords = params.keywords; + if (params.category) payload.cat = params.category; + if (params.pageType) payload.pgt = params.pageType; + if (params.incognito) payload.ic = params.incognito; + if (params.dsmi) payload.smi = params.dsmi; + if (params.optout) payload.out = params.optout; + if (bidRequest && bidRequest.hasOwnProperty('ortb2Imp') && bidRequest.ortb2Imp.hasOwnProperty('ext')) { + payload.ext = bidRequest.ortb2Imp.ext; + if (bidRequest.ortb2Imp.ext.hasOwnProperty('gpid')) payload.pubAdCode = bidRequest.ortb2Imp.ext.gpid.split('#')[0]; + } + payloadArr.push(payload); + ctr++; + }); + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: payloadArr, + }] + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const res = serverResponse.body || serverResponse; + if (!res || res.length === 0) { + return bidResponses; + } + try { + res.forEach(response => { + const crid = response.creativeId || 0; + const width = response.width || 0; + const height = response.height || 0; + let cpm = response.cpm || 0; + if (cpm <= 0) { + return bidResponses; + } + if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'USD'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const bidResponse = { + requestId: response.requestId, + cpm: cpm.toFixed(2), + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout') || 300, + referrer: bidRequest.data.r, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + }); + } catch (error) { + utils.logError(error); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/colombiaBidAdapter.md b/modules/colombiaBidAdapter.md new file mode 100644 index 00000000000..c6ef5e6b749 --- /dev/null +++ b/modules/colombiaBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: COLOMBIA Bidder Adapter +Module Type: Bidder Adapter +Maintainer: colombiaonline@timesinteret.in +``` + +# Description + +Connect to COLOMBIA for bids. + +COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. + +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250],[728,90],[320,50]] + } + }, + bids: [{ + bidder: 'colombia', + params: { + placementId: '540799' + } + }] + }]; +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 50d72367d6e..7c28a82a9f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.48.0-pre", + "version": "8.49.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js new file mode 100644 index 00000000000..b7256545c5e --- /dev/null +++ b/test/spec/modules/colombiaBidAdapter_spec.js @@ -0,0 +1,155 @@ +import { expect } from 'chai'; +import { spec } from 'modules/colombiaBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const HOST_NAME = document.location.protocol + '//' + window.location.host; +const ENDPOINT = 'https://ade.clmbtech.com/cde/prebid.htm'; + +describe('colombiaBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', function () { + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [ + [300, 250] + ], + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de2511"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests); + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': 'https://ade.clmbtech.com/cde/prebid.htm', + 'data': { + 'v': 'hb1', + 'p': '307466', + 'w': '300', + 'h': '250', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i', + } + } + ]; + + let serverResponse = [{ + 'ad': '
This is test case for colombia adapter
', + 'cpm': 3.14, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'currency': 'USD', + 'requestId': '23beaa6af6cdde', + 'width': 728, + 'height': 90, + 'netRevenue': true, + 'ttl': 600, + 'dealid': '', + 'referrer': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836' + }]; + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 3.14, + 'width': 728, + 'height': 90, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'dealId': '', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'referrer': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'ad': '
This is test case for colombia adapter
' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'uid': '23beaa6af6cdde', + 'height': 0, + 'creativeId': '', + 'statusMessage': 'Bid returned empty or error response', + 'width': 0, + 'cpm': 0 + } + }; + let result = spec.interpretResponse(response, bidRequest[0]); + expect(result.length).to.equal(0); + }); + }); +}); From dd34052c65c05e405739f6057b8e89897a5f9f81 Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Fri, 24 May 2024 05:43:13 +0530 Subject: [PATCH 0081/1097] Insticator Bid Adapter: Add support for BidFloors (#11472) * support bidfloor from params * update test case for bidder bidfloor * prioritize module floor * fix * update bidfloorcur * add USD currency for module floor * add test cases for bidfloors and fix module floor scopes * add logwarn for non usd floors --- modules/insticatorBidAdapter.js | 58 +++++++++++- .../spec/modules/insticatorBidAdapter_spec.js | 94 +++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 617ce49f171..bff74f0755b 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue} from '../src/utils.js'; +import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue, isFn, logWarn} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {find} from '../src/polyfill.js'; @@ -170,6 +170,19 @@ function buildImpression(bidRequest) { }, } + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + + if (!isNaN(bidFloor)) { + imp.bidfloor = deepAccess(bidRequest, 'params.floor'); + imp.bidfloorcur = 'USD'; + const bidfloorcur = deepAccess(bidRequest, 'params.bidfloorcur') + if (bidfloorcur && bidfloorcur !== 'USD') { + delete imp.bidfloor; + delete imp.bidfloorcur; + logWarn('insticator: bidfloorcur supported by insticator is USD only. ignoring bidfloor and bidfloorcur params'); + } + } + if (deepAccess(bidRequest, 'mediaTypes.banner')) { imp.banner = buildBanner(bidRequest); } @@ -178,6 +191,49 @@ function buildImpression(bidRequest) { imp.video = buildVideo(bidRequest); } + if (isFn(bidRequest.getFloor)) { + let moduleBidFloor; + + const mediaType = deepAccess(bidRequest, 'mediaTypes.banner') ? 'banner' : deepAccess(bidRequest, 'mediaTypes.video') ? 'video' : undefined; + + let _mediaType = mediaType; + let _size = '*'; + + if (mediaType && ['banner', 'video'].includes(mediaType)) { + if (mediaType === 'banner') { + const { w: width, h: height } = imp[mediaType]; + if (width && height) { + _size = [width, height]; + } else { + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.format'); + if (sizes && sizes.length > 0) { + const {w: width, h: height} = sizes[0]; + _size = [width, height]; + } + } + } else if (mediaType === 'video') { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + } + try { + moduleBidFloor = bidRequest.getFloor({ + currency: 'USD', + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + logWarn('priceFloors module call getFloor failed, error : ', err); + } + + if (moduleBidFloor) { + imp.bidfloor = moduleBidFloor.floor; + imp.bidfloorcur = moduleBidFloor.currency; + } + } + return imp; } diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 5e41cd6d7aa..489e213b0fa 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -569,6 +569,100 @@ describe('InsticatorBidAdapter', function () { expect(data.imp[0].video.h).to.equal(480); }); + it('should have bidder bidfloor from the request', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + floor: 0.5, + }, + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(0.5); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('should have bidder bidfloorcur from the request', function () { + const expectedFloor = 1.5; + const currency = 'USD'; + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + floor: 0.5, + currency: 'USD', + }, + } + tempBiddRequest.getFloor = () => ({ floor: expectedFloor, currency }) + + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(1.5); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('should have 1 floor for banner 300x250 and 1.5 for 300x600', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + format: [{ w: 300, h: 250 }] + }, + }, + } + tempBiddRequest.getFloor = (params) => { + return { floor: params.size[1] === 250 ? 1 : 1.5, currency: 'USD' } + } + + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(1); + + tempBiddRequest.mediaTypes.banner.format = [ { w: 300, h: 600 }, + ]; + const request2 = spec.buildRequests([tempBiddRequest], bidderRequest); + const data2 = JSON.parse(request2[0].data); + expect(data2.imp[0].bidfloor).to.equal(1.5); + }); + + it('should have 4 floor for video 300x250 and 4.5 for 300x600', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + }, + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + w: 300, + h: 250, + placement: 2, + }, + }, + } + tempBiddRequest.getFloor = (params) => { + return { floor: params.size[1] === 250 ? 4 : 4.5, currency: 'USD' } + } + + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(4); + + tempBiddRequest.mediaTypes.video.w = 300; + tempBiddRequest.mediaTypes.video.h = 600; + const request2 = spec.buildRequests([tempBiddRequest], bidderRequest); + const data2 = JSON.parse(request2[0].data); + expect(data2.imp[0].bidfloor).to.equal(4.5); + }); + it('should have sites first party data if present in bidderRequest ortb2', function () { bidderRequest = { ...bidderRequest, From caa2b8b2302ac6cb82bd4a70617d1549df470b22 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Thu, 23 May 2024 19:35:04 -0500 Subject: [PATCH 0082/1097] User id module: Ability to store user IDs in cookie and localStorage simultaneously (#11482) * Refactoring - break functions that are handling multiple storage types. * user id: introduce the concept of enabled storage types * refactor the way enabled storage types are populated --- modules/userId/index.js | 232 +++++++++++++++++++++---------- test/spec/modules/userId_spec.js | 122 ++++++++++++++-- 2 files changed, 273 insertions(+), 81 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 90d377a816e..977af127534 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -239,6 +239,29 @@ function cookieSetter(submodule, storageMgr) { } } +function setValueInCookie(submodule, valueStr, expiresStr) { + const storage = submodule.config.storage; + const setCookie = cookieSetter(submodule); + + setCookie(null, valueStr, expiresStr); + setCookie('_cst', getConsentHash(), expiresStr); + if (typeof storage.refreshInSeconds === 'number') { + setCookie('_last', new Date().toUTCString(), expiresStr); + } +} + +function setValueInLocalStorage(submodule, valueStr, expiresStr) { + const storage = submodule.config.storage; + const mgr = submodule.storageMgr; + + mgr.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); + mgr.setDataInLocalStorage(`${storage.name}_cst`, getConsentHash()); + mgr.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); + if (typeof storage.refreshInSeconds === 'number') { + mgr.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); + } +} + /** * @param {SubmoduleContainer} submodule * @param {(Object|string)} value @@ -248,54 +271,62 @@ export function setStoredValue(submodule, value) { * @type {SubmoduleStorage} */ const storage = submodule.config.storage; - const mgr = submodule.storageMgr; try { const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); const valueStr = isPlainObject(value) ? JSON.stringify(value) : value; - if (storage.type === COOKIE) { - const setCookie = cookieSetter(submodule); - setCookie(null, valueStr, expiresStr); - setCookie('_cst', getConsentHash(), expiresStr); - if (typeof storage.refreshInSeconds === 'number') { - setCookie('_last', new Date().toUTCString(), expiresStr); - } - } else if (storage.type === LOCAL_STORAGE) { - mgr.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); - mgr.setDataInLocalStorage(`${storage.name}_cst`, getConsentHash()); - mgr.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); - if (typeof storage.refreshInSeconds === 'number') { - mgr.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); + + submodule.enabledStorageTypes.forEach(storageType => { + switch (storageType) { + case COOKIE: + setValueInCookie(submodule, valueStr, expiresStr); + break; + case LOCAL_STORAGE: + setValueInLocalStorage(submodule, valueStr, expiresStr); + break; } - } + }); } catch (error) { logError(error); } } +function deleteValueFromCookie(submodule) { + const setCookie = cookieSetter(submodule, coreStorage); + const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); + + ['', '_last', '_cst'].forEach(suffix => { + try { + setCookie(suffix, '', expiry); + } catch (e) { + logError(e); + } + }) +} + +function deleteValueFromLocalStorage(submodule) { + ['', '_last', '_exp', '_cst'].forEach(suffix => { + try { + coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix); + } catch (e) { + logError(e); + } + }); +} + export function deleteStoredValue(submodule) { - let deleter, suffixes; - switch (submodule.config?.storage?.type) { - case COOKIE: - const setCookie = cookieSetter(submodule, coreStorage); - const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); - deleter = (suffix) => setCookie(suffix, '', expiry) - suffixes = ['', '_last', '_cst']; - break; - case LOCAL_STORAGE: - deleter = (suffix) => coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix) - suffixes = ['', '_last', '_exp', '_cst']; - break; - } - if (deleter) { - suffixes.forEach(suffix => { - try { - deleter(suffix) - } catch (e) { - logError(e); - } - }); - } + populateEnabledStorageTypes(submodule); + + submodule.enabledStorageTypes.forEach(storageType => { + switch (storageType) { + case COOKIE: + deleteValueFromCookie(submodule); + break; + case LOCAL_STORAGE: + deleteValueFromLocalStorage(submodule); + break; + } + }); } function setPrebidServerEidPermissions(initializedSubmodules) { @@ -305,30 +336,46 @@ function setPrebidServerEidPermissions(initializedSubmodules) { } } +function getValueFromCookie(submodule, storedKey) { + return submodule.storageMgr.getCookie(storedKey) +} + +function getValueFromLocalStorage(submodule, storedKey) { + const mgr = submodule.storageMgr; + const storage = submodule.config.storage; + const storedValueExp = mgr.getDataFromLocalStorage(`${storage.name}_exp`); + + // empty string means no expiration set + if (storedValueExp === '') { + return mgr.getDataFromLocalStorage(storedKey); + } else if (storedValueExp && ((new Date(storedValueExp)).getTime() - Date.now() > 0)) { + return decodeURIComponent(mgr.getDataFromLocalStorage(storedKey)); + } +} + /** * @param {SubmoduleContainer} submodule * @param {String|undefined} key optional key of the value * @returns {string} */ function getStoredValue(submodule, key = undefined) { - const mgr = submodule.storageMgr; const storage = submodule.config.storage; const storedKey = key ? `${storage.name}_${key}` : storage.name; let storedValue; try { - if (storage.type === COOKIE) { - storedValue = mgr.getCookie(storedKey); - } else if (storage.type === LOCAL_STORAGE) { - const storedValueExp = mgr.getDataFromLocalStorage(`${storage.name}_exp`); - // empty string means no expiration set - if (storedValueExp === '') { - storedValue = mgr.getDataFromLocalStorage(storedKey); - } else if (storedValueExp) { - if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { - storedValue = decodeURIComponent(mgr.getDataFromLocalStorage(storedKey)); - } + submodule.enabledStorageTypes.find(storageType => { + switch (storageType) { + case COOKIE: + storedValue = getValueFromCookie(submodule, storedKey); + break; + case LOCAL_STORAGE: + storedValue = getValueFromLocalStorage(submodule, storedKey); + break; } - } + + return !!storedValue; + }); + // support storing a string or a stringified object if (typeof storedValue === 'string' && storedValue.trim().charAt(0) === '{') { storedValue = JSON.parse(storedValue); @@ -776,8 +823,10 @@ function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { } if (!storedId || refreshNeeded || forceRefresh || consentChanged(submodule)) { + const extendedConfig = Object.assign({ enabledStorageTypes: submodule.enabledStorageTypes }, submodule.config); + // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. - response = submodule.submodule.getId(submodule.config, gdprConsent, storedId); + response = submodule.submodule.getId(extendedConfig, gdprConsent, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken response = submodule.submodule.extendId(submodule.config, gdprConsent, storedId); @@ -834,6 +883,8 @@ function initSubmodules(dest, submodules, forceRefresh = false) { return uidMetrics().fork().measureTime('userId.init.modules', function () { if (!submodules.length) return []; // to simplify log messages from here on + submodules.forEach(submod => populateEnabledStorageTypes(submod)); + /** * filter out submodules that: * @@ -884,6 +935,16 @@ function updateInitializedSubmodules(dest, submodule) { } } +function getConfiguredStorageTypes(config) { + return config?.storage?.type?.trim().split(/\s*&\s*/) || []; +} + +function hasValidStorageTypes(config) { + const storageTypes = getConfiguredStorageTypes(config); + + return storageTypes.every(storageType => ALL_STORAGE_TYPES.has(storageType)); +} + /** * list of submodule configurations with valid 'storage' or 'value' obj definitions * storage config: contains values for storing/retrieving User ID data in browser storage @@ -905,7 +966,7 @@ function getValidSubmoduleConfigs(configRegistry) { if (config.storage && !isEmptyStr(config.storage.type) && !isEmptyStr(config.storage.name) && - ALL_STORAGE_TYPES.has(config.storage.type)) { + hasValidStorageTypes(config)) { carry.push(config); } else if (isPlainObject(config.value)) { carry.push(config); @@ -918,28 +979,53 @@ function getValidSubmoduleConfigs(configRegistry) { const ALL_STORAGE_TYPES = new Set([LOCAL_STORAGE, COOKIE]); -function canUseStorage(submodule) { - switch (submodule.config?.storage?.type) { - case LOCAL_STORAGE: - if (submodule.storageMgr.localStorageIsEnabled()) { - if (coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { - logInfo(`${MODULE_NAME} - opt-out localStorage found, storage disabled`); - return false - } - return true; - } - break; - case COOKIE: - if (submodule.storageMgr.cookiesAreEnabled()) { - if (coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { - logInfo(`${MODULE_NAME} - opt-out cookie found, storage disabled`); - return false; - } - return true - } - break; +function canUseLocalStorage(submodule) { + if (!submodule.storageMgr.localStorageIsEnabled()) { + return false; + } + + if (coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { + logInfo(`${MODULE_NAME} - opt-out localStorage found, storage disabled`); + return false + } + + return true; +} + +function canUseCookies(submodule) { + if (!submodule.storageMgr.cookiesAreEnabled()) { + return false; + } + + if (coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { + logInfo(`${MODULE_NAME} - opt-out cookie found, storage disabled`); + return false; + } + + return true +} + +function populateEnabledStorageTypes(submodule) { + if (submodule.enabledStorageTypes) { + return; } - return false; + + const storageTypes = getConfiguredStorageTypes(submodule.config); + + submodule.enabledStorageTypes = storageTypes.filter(type => { + switch (type) { + case LOCAL_STORAGE: + return canUseLocalStorage(submodule); + case COOKIE: + return canUseCookies(submodule); + } + + return false; + }); +} + +function canUseStorage(submodule) { + return !!submodule.enabledStorageTypes.length; } function updateEIDConfig(submodules) { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 2ff19424e09..0f7e9cec6ce 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1837,6 +1837,88 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from pubcommonid cookie&html5', function (done) { + const expiration = new Date(Date.now() + 100000).toUTCString(); + coreStorage.setCookie('pubcid', 'testpubcid', expiration); + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', expiration); + + init(config); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] + }); + }); + }); + + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('pubcid'); + localStorage.removeItem('pubcid_exp'); + + done(); + }, {adUnits}); + }); + + it('test hook from pubcommonid cookie&html5, no cookie present', function (done) { + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', new Date(Date.now() + 100000).toUTCString()); + + init(config); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] + }); + }); + }); + + localStorage.removeItem('pubcid'); + localStorage.removeItem('pubcid_exp'); + + done(); + }, {adUnits}); + }); + + it('test hook from pubcommonid cookie&html5, no local storage entry', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); + + init(config); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] + }); + }); + }); + + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + + done(); + }, {adUnits}); + }); + it('test hook from pubcommonid config value object', function (done) { init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); @@ -3197,7 +3279,8 @@ describe('User ID', function () { }, storageMgr: { setCookie: sinon.stub() - } + }, + enabledStorageTypes: [ 'cookie' ] } setStoredValue(submodule, 'bar'); expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal('foo.com'); @@ -3214,7 +3297,8 @@ describe('User ID', function () { }, storageMgr: { setCookie: sinon.stub() - } + }, + enabledStorageTypes: [ 'cookie' ] } setStoredValue(submodule, 'bar'); expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal(null); @@ -3361,6 +3445,20 @@ describe('User ID', function () { sinon.assert.calledOnce(mockExtendId); }); }); + + it('calls getId with the list of enabled storage types', function() { + setStorage({lastDelta: 1000}); + config.setConfig(userIdConfig); + + let innerAdUnits; + return runBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}).then(() => { + sinon.assert.calledOnce(mockGetId); + + expect(mockGetId.getCall(0).args[0].enabledStorageTypes).to.deep.equal([ userIdConfig.userSync.userIds[0].storage.type ]); + }); + }); }); describe('requestDataDeletion', () => { @@ -3376,21 +3474,23 @@ describe('User ID', function () { onDataDeletionRequest: sinon.stub() } } - let mod1, mod2, mod3, cfg1, cfg2, cfg3; + let mod1, mod2, mod3, mod4, cfg1, cfg2, cfg3, cfg4; beforeEach(() => { init(config); mod1 = idMod('id1', 'val1'); mod2 = idMod('id2', 'val2'); mod3 = idMod('id3', 'val3'); + mod4 = idMod('id4', 'val4'); cfg1 = getStorageMock('id1', 'id1', 'cookie'); cfg2 = getStorageMock('id2', 'id2', 'html5'); - cfg3 = {name: 'id3', value: {id3: 'val3'}}; - setSubmoduleRegistry([mod1, mod2, mod3]); + cfg3 = getStorageMock('id3', 'id3', 'cookie&html5'); + cfg4 = {name: 'id4', value: {id4: 'val4'}}; + setSubmoduleRegistry([mod1, mod2, mod3, mod4]); config.setConfig({ auctionDelay: 1, userSync: { - userIds: [cfg1, cfg2, cfg3] + userIds: [cfg1, cfg2, cfg3, cfg4] } }); return getGlobal().refreshUserIds(); @@ -3399,16 +3499,21 @@ describe('User ID', function () { it('deletes stored IDs', () => { expect(coreStorage.getCookie('id1')).to.exist; expect(coreStorage.getDataFromLocalStorage('id2')).to.exist; + expect(coreStorage.getCookie('id3')).to.exist; + expect(coreStorage.getDataFromLocalStorage('id3')).to.exist; requestDataDeletion(sinon.stub()); expect(coreStorage.getCookie('id1')).to.not.exist; expect(coreStorage.getDataFromLocalStorage('id2')).to.not.exist; + expect(coreStorage.getCookie('id3')).to.not.exist; + expect(coreStorage.getDataFromLocalStorage('id3')).to.not.exist; }); it('invokes onDataDeletionRequest', () => { requestDataDeletion(sinon.stub()); sinon.assert.calledWith(mod1.onDataDeletionRequest, cfg1, {id1: 'val1'}); - sinon.assert.calledWith(mod2.onDataDeletionRequest, cfg2, {id2: 'val2'}) - sinon.assert.calledWith(mod3.onDataDeletionRequest, cfg3, {id3: 'val3'}) + sinon.assert.calledWith(mod2.onDataDeletionRequest, cfg2, {id2: 'val2'}); + sinon.assert.calledWith(mod3.onDataDeletionRequest, cfg3, {id3: 'val3'}); + sinon.assert.calledWith(mod4.onDataDeletionRequest, cfg4, {id4: 'val4'}); }); describe('does not choke when onDataDeletionRequest', () => { @@ -3423,6 +3528,7 @@ describe('User ID', function () { requestDataDeletion(next, arg); sinon.assert.calledOnce(mod2.onDataDeletionRequest); sinon.assert.calledOnce(mod3.onDataDeletionRequest); + sinon.assert.calledOnce(mod4.onDataDeletionRequest); sinon.assert.calledWith(next, arg); }) }) From abf0fd07ac0b537d7d98e4bfdbd157a121f89fab Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Fri, 24 May 2024 14:17:02 +0300 Subject: [PATCH 0083/1097] Taboola Bid Adapter: support DChain (#11545) * cookie-look-up-logic-fix-gpp-fix * support dchain --------- Co-authored-by: aleskanderl <109285067+aleskanderl@users.noreply.github.com> --- modules/taboolaBidAdapter.js | 3 ++ test/spec/modules/taboolaBidAdapter_spec.js | 59 +++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 6e6d89dc921..74a614bc6b0 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -113,6 +113,9 @@ const converter = ortbConverter({ const bidResponse = buildBidResponse(bid, context); bidResponse.nurl = bid.nurl; bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + if (bid.ext && bid.ext.dchain) { + deepSetValue(bidResponse, 'meta.dchain', bid.ext.dchain); + } return bidResponse } }); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index ca09fbbbcc9..2ea8c325989 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -692,6 +692,50 @@ describe('Taboola Adapter', function () { } }; + const serverResponseWithDchain = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': request.data.imp[0].id, + '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, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + 'ext': { + 'dchain': { + 'complete': 1, + 'ver': '1.0', + 'nodes': [ + { + 'asi': 'taboola.com', + 'bsid': '1495' + } + ] + } + } + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + const serverResponseWithPa = { body: { 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', @@ -1023,6 +1067,21 @@ describe('Taboola Adapter', function () { expect(res).to.deep.equal(expectedRes) }); + it('should interpret display response with dchain', function () { + const expectedDchainRes = { + 'complete': 1, + 'ver': '1.0', + 'nodes': [ + { + 'asi': 'taboola.com', + 'bsid': '1495' + } + ] + } + const res = spec.interpretResponse(serverResponseWithDchain, request) + expect(res[0].meta.dchain).to.deep.equal(expectedDchainRes) + }); + it('should interpret display response with PA', function () { const [bid] = serverResponse.body.seatbid[0].bid; From 29a5b40507284d234488795d5be61ccd433eef6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bendeg=C3=BAz=20=C3=81cs?= <30595431+acsbendi@users.noreply.github.com> Date: Fri, 24 May 2024 15:56:51 +0200 Subject: [PATCH 0084/1097] Kobler adapter: Fix to avoid bidding with third-party creatives requiring consent or legitimate interest. (#11559) * Kobler adapter: Fix to avoid bidding with third-party creatives requiring consent or legitimate interest. * Fixed tests. --- modules/koblerBidAdapter.js | 38 ++++++++---- test/spec/modules/koblerBidAdapter_spec.js | 67 ++++++++++++---------- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 596e5b2695f..3ef40c8a921 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -90,8 +90,6 @@ export const onTimeout = function (timeoutDataArray) { timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, page_url: pageUrl, @@ -103,13 +101,6 @@ export const onTimeout = function (timeoutDataArray) { }; 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')) { - // TODO: it's not clear what the intent is here - but all adapters should always respect pageUrl. - // With prebid 7, using `refererInfo.page` will do that automatically. - return config.getConfig('pageUrl'); - } - return (bidderRequest.refererInfo && bidderRequest.refererInfo.page) ? bidderRequest.refererInfo.page : window.location.href; @@ -125,7 +116,26 @@ function getPageUrlFromRefererInfo() { function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); const timeout = bidderRequest.timeout; - const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest); + // Kobler, a contextual advertising provider, does not process any personal data itself, so it is not part of TCF/GVL. + // However, it supports using select third-party creatives in its platform, some of which require certain permissions + // in order to be shown. Kobler's bidder checks if necessary permissions are present to avoid bidding + // with ineligible creatives. + let purpose2Given; + let purpose3Given; + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.vendorData) { + const vendorData = bidderRequest.gdprConsent.vendorData + const purposeData = vendorData.purpose; + const restrictions = vendorData.publisher ? vendorData.publisher.restrictions : null; + const restrictionForPurpose2 = restrictions ? (restrictions[2] ? Object.values(restrictions[2])[0] : null) : null; + purpose2Given = restrictionForPurpose2 === 1 ? ( + purposeData && purposeData.consents && purposeData.consents[2] + ) : ( + restrictionForPurpose2 === 0 + ? false : (purposeData && purposeData.legitimateInterests && purposeData.legitimateInterests[2]) + ); + purpose3Given = purposeData && purposeData.consents && purposeData.consents[3]; + } const request = { id: bidderRequest.bidderRequestId, at: 1, @@ -138,7 +148,13 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { site: { page: pageUrl, }, - test: getTestAsNumber(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]), + ext: { + kobler: { + tcf_purpose_2_given: purpose2Given, + tcf_purpose_3_given: purpose3Given + } + } }; return JSON.stringify(request); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 2b5830f68d2..74c0a1f5967 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -5,14 +5,37 @@ import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {getRefererInfo} from 'src/refererDetection.js'; -function createBidderRequest(auctionId, timeout, pageUrl) { +function createBidderRequest(auctionId, timeout, pageUrl, addGdprConsent) { + const gdprConsent = addGdprConsent ? { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false, + 2: true, + 3: false + } + }, + publisher: { + restrictions: { + '2': { + // require consent + '11': 1 + } + } + } + }, + gdprApplies: true + } : {}; return { bidderRequestId: 'mock-uuid', auctionId: auctionId || 'c1243d83-0bed-4fdb-8c76-42b456be17d0', timeout: timeout || 2000, refererInfo: { page: pageUrl || 'example.com' - } + }, + gdprConsent: gdprConsent }; } @@ -289,27 +312,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.test).to.be.equal(1); }); - it('should read pageUrl from config when testing', function () { - config.setConfig({ - pageUrl: 'https://testing-url.com' - }); - const validBidRequests = [ - createValidBidRequest( - { - test: true - } - ) - ]; - const bidderRequest = createBidderRequest(); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - 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 not read pageUrl from config when not testing', function () { config.setConfig({ pageUrl: 'https://testing-url.com' @@ -439,7 +441,8 @@ describe('KoblerAdapter', function () { const bidderRequest = createBidderRequest( '9ff580cf-e10e-4b66-add7-40ac0c804e21', 4500, - 'bid.kobler.no' + 'bid.kobler.no', + true ); const result = spec.buildRequests(validBidRequests, bidderRequest); @@ -529,7 +532,13 @@ describe('KoblerAdapter', function () { site: { page: 'bid.kobler.no' }, - test: 0 + test: 0, + ext: { + kobler: { + tcf_purpose_2_given: true, + tcf_purpose_3_given: false + } + } }; expect(openRtbRequest).to.deep.equal(expectedOpenRtbRequest); @@ -702,14 +711,12 @@ describe('KoblerAdapter', function () { spec.onTimeout([ { adUnitCode: 'adunit-code', - auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', timeout: 100, params: [], }, { adUnitCode: 'adunit-code-2', - auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', timeout: 100, params: [], @@ -719,13 +726,11 @@ describe('KoblerAdapter', function () { expect(utils.triggerPixel.callCount).to.be.equal(2); 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&' + - 'page_url=' + encodeURIComponent(getRefererInfo().page) + 'bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&page_url=' + encodeURIComponent(getRefererInfo().page) ); 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&' + - 'page_url=' + encodeURIComponent(getRefererInfo().page) + 'bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&page_url=' + encodeURIComponent(getRefererInfo().page) ); }); }); From 1fc58448a42c191dc4e2365fb2146e0ffefbe467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdravko=20Kosanovi=C4=87?= <41286499+zkosanovic@users.noreply.github.com> Date: Sat, 25 May 2024 14:02:08 +0200 Subject: [PATCH 0085/1097] MinuteMedia: Respond with the correct creativeId (#11565) --- modules/minutemediaBidAdapter.js | 2 +- test/spec/modules/minutemediaBidAdapter_spec.js | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index d14af07210e..a724a0446a4 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -76,7 +76,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index cf50ad2cd0a..f2bdd3b6c9d 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -440,6 +440,8 @@ describe('minutemediaAdapter', function () { width: 640, height: 480, requestId: '21e12606d47ba7', + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: VIDEO }, @@ -449,6 +451,8 @@ describe('minutemediaAdapter', function () { width: 300, height: 250, requestId: '21e12606d47ba7', + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER }] @@ -461,7 +465,7 @@ describe('minutemediaAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -476,10 +480,10 @@ describe('minutemediaAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -492,8 +496,8 @@ describe('minutemediaAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { From f6a214b259a2f232f589fbdb404b3f0709451f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdravko=20Kosanovi=C4=87?= <41286499+zkosanovic@users.noreply.github.com> Date: Sat, 25 May 2024 14:34:48 +0200 Subject: [PATCH 0086/1097] Rise: Respond with the correct creativeId (#11564) --- modules/riseBidAdapter.js | 2 +- test/spec/modules/riseBidAdapter_spec.js | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index b176ab08aaf..72170706fc0 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -82,7 +82,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 3cb5cb5c154..cb879231237 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -466,6 +466,8 @@ describe('riseAdapter', function () { height: 480, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: VIDEO }, { @@ -475,6 +477,8 @@ describe('riseAdapter', function () { height: 250, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: BANNER }] }; @@ -486,7 +490,7 @@ describe('riseAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -501,10 +505,10 @@ describe('riseAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -517,8 +521,8 @@ describe('riseAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { From 229c968d3410e793d4ddc88cdad2a3ae017c2b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdravko=20Kosanovi=C4=87?= <41286499+zkosanovic@users.noreply.github.com> Date: Sun, 26 May 2024 20:13:18 +0200 Subject: [PATCH 0087/1097] STN: Respond with the correct creativeId (#11566) --- modules/stnBidAdapter.js | 2 +- test/spec/modules/stnBidAdapter_spec.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index 42b69ee7c2b..af4f4c45d38 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -75,7 +75,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js index 95cab32e41d..de851158ed0 100644 --- a/test/spec/modules/stnBidAdapter_spec.js +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -440,8 +440,10 @@ describe('stnAdapter', function () { width: 640, height: 480, requestId: '21e12606d47ba7', + creativeId: 'creative-id-1', adomain: ['abc.com'], - mediaType: VIDEO + mediaType: VIDEO, + nurl: 'http://example.com/win/1234', }, { cpm: 12.5, @@ -449,8 +451,10 @@ describe('stnAdapter', function () { width: 300, height: 250, requestId: '21e12606d47ba7', + creativeId: 'creative-id-2', adomain: ['abc.com'], - mediaType: BANNER + mediaType: BANNER, + nurl: 'http://example.com/win/1234', }] }; @@ -461,7 +465,7 @@ describe('stnAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-1', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -476,10 +480,10 @@ describe('stnAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-2', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -492,8 +496,8 @@ describe('stnAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { From e0e177f6921c67a01cd836580099c03980beed11 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Mon, 27 May 2024 14:23:55 +0300 Subject: [PATCH 0088/1097] Adkernel Bid Adapter: add hyperbrainz alias (#11544) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 0c353e4332a..151e08bd2bb 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -94,7 +94,8 @@ export const spec = { {code: 'adpluto'}, {code: 'headbidder'}, {code: 'digiad'}, - {code: 'monetix'} + {code: 'monetix'}, + {code: 'hyperbrainz'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 187e0d8e9b72cf2453b0daad9f50419edbb357da Mon Sep 17 00:00:00 2001 From: mkomorski Date: Mon, 27 May 2024 19:54:56 +0200 Subject: [PATCH 0089/1097] Prebid Core: Configurable maxbid (#11519) * 11252 Configurable maxbid * lint fixes & add docs * removing unnecessary logic --------- Co-authored-by: mkomorski Co-authored-by: Marcin Komorski --- src/auction.js | 22 ++++++++++++++++++++-- src/config.js | 4 ++++ src/constants.js | 3 ++- test/spec/auctionmanager_spec.js | 13 +++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/auction.js b/src/auction.js index 26845936797..881dee9f2de 100644 --- a/src/auction.js +++ b/src/auction.js @@ -94,7 +94,7 @@ import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; -import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, S2S, TARGETING_KEYS } from './constants.js'; +import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS } from './constants.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; @@ -421,7 +421,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a * @param {function(String): void} reject a function that, when called, rejects `bid` with the given reason. */ export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { - this.dispatch.call(null, adUnitCode, bid); + if (!isValidPrice(bid)) { + reject(REJECTION_REASON.PRICE_TOO_HIGH) + } else { + this.dispatch.call(null, adUnitCode, bid); + } }, 'addBidResponse'); /** @@ -974,3 +978,17 @@ function groupByPlacement(bidsByPlacement, bid) { bidsByPlacement[bid.adUnitCode].bids.push(bid); return bidsByPlacement; } + +/** + * isValidPrice is price validation function + * which checks if price from bid response + * is not higher than top limit set in config + * @type {Function} + * @param bid + * @returns {boolean} + */ +function isValidPrice(bid) { + const maxBidValue = config.getConfig('maxBid'); + if (!maxBidValue || !bid.cpm) return true; + return maxBidValue >= Number(bid.cpm); +} diff --git a/src/config.js b/src/config.js index f4dd0de9612..21c34cf34d2 100644 --- a/src/config.js +++ b/src/config.js @@ -36,6 +36,7 @@ const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; const DEFAULT_DEVICE_ACCESS = true; const DEFAULT_MAX_NESTED_IFRAMES = 10; +const DEFAULT_MAXBID_VALUE = 5000 const DEFAULT_TIMEOUTBUFFER = 400; @@ -160,6 +161,9 @@ export function newConfig() { // default max nested iframes for referer detection maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, + + // default max bid + maxBid: DEFAULT_MAXBID_VALUE }; Object.defineProperties(newConfig, diff --git a/src/constants.js b/src/constants.js index b40b7ddb9b0..bb76083862b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -135,7 +135,8 @@ export const REJECTION_REASON = { FLOOR_NOT_MET: 'Bid does not meet price floor', CANNOT_CONVERT_CURRENCY: 'Unable to convert currency', DSA_REQUIRED: 'Bid does not provide required DSA transparency info', - DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method' + DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method', + PRICE_TOO_HIGH: 'Bid price exceeds maximum value' }; export const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 65c6256acdc..e5cdb66e75f 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -24,6 +24,9 @@ import {expect} from 'chai'; import {deepClone} from '../../src/utils.js'; import { IMAGE as ortbNativeRequest } from 'src/native.js'; import {PrebidServer} from '../../modules/prebidServerBidAdapter/index.js'; +import '../../modules/currency.js' +import { setConfig as setCurrencyConfig } from '../../modules/currency.js'; +import { REJECTION_REASON } from '../../src/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -1467,6 +1470,16 @@ describe('auctionmanager.js', function () { config.getConfig.restore(); store.store.restore(); }); + + it('should reject bid for price higher than limit for the same currency', () => { + sinon.stub(auction, 'addBidRejected'); + config.setConfig({ + maxBid: 1 + }); + + auction.callBids(); + sinon.assert.calledWith(auction.addBidRejected, sinon.match({rejectionReason: REJECTION_REASON.PRICE_TOO_HIGH})); + }) }); describe('addBidRequests', function () { From af5502030eedf31d49ccc3b61748a807f4286702 Mon Sep 17 00:00:00 2001 From: vishal-dw <109065778+vishal-dw@users.noreply.github.com> Date: Tue, 28 May 2024 04:13:54 +0530 Subject: [PATCH 0090/1097] remove use of deprecated video.placement (#11573) --- modules/datawrkzBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index b5a096521a1..2f8e397d959 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -227,7 +227,6 @@ function buildVideoRequest(bidRequest, bidderRequest) { maxbitrate: deepAccess(bidRequest, 'mediaTypes.video.maxbitrate'), delivery: deepAccess(bidRequest, 'mediaTypes.video.delivery'), linearity: deepAccess(bidRequest, 'mediaTypes.video.linearity'), - placement: deepAccess(bidRequest, 'mediaTypes.video.placement'), skip: deepAccess(bidRequest, 'mediaTypes.video.skip'), skipafter: deepAccess(bidRequest, 'mediaTypes.video.skipafter') }; From e093b2c875a4a7bb366e7bc81874d57253c0fefb Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 28 May 2024 12:37:50 +0200 Subject: [PATCH 0091/1097] ZetaGlobalSpp adapter: remove onTimeout (#11576) --- modules/zeta_global_sspBidAdapter.js | 21 ------------------- .../modules/zeta_global_sspBidAdapter_spec.js | 5 ----- 2 files changed, 26 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index f3e2e12c143..918d03861ae 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -3,7 +3,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {parseDomain} from '../src/refererDetection.js'; -import {ajax} from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -15,7 +14,6 @@ import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'zeta_global_ssp'; const ENDPOINT_URL = 'https://ssp.disqus.com/bid/prebid'; -const TIMEOUT_URL = 'https://ssp.disqus.com/timeout/prebid'; const USER_SYNC_URL_IFRAME = 'https://ssp.disqus.com/sync?type=iframe'; const USER_SYNC_URL_IMAGE = 'https://ssp.disqus.com/sync?type=image'; const DEFAULT_CUR = 'USD'; @@ -268,25 +266,6 @@ export const spec = { url: USER_SYNC_URL_IMAGE + syncurl }]; } - }, - - onTimeout: function(timeoutData) { - if (timeoutData) { - const payload = timeoutData.map(d => ({ - bidder: d?.bidder, - shortname: d?.params?.map(p => p?.tags?.shortname).find(p => p), - sid: d?.params?.map(p => p?.sid).find(p => p), - country: d?.ortb2?.device?.geo?.country, - devicetype: d?.ortb2?.device?.devicetype - })); - ajax(TIMEOUT_URL, null, JSON.stringify(payload), { - method: 'POST', - options: { - withCredentials: false, - contentType: 'application/json' - } - }); - } } } diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 7b5c0278019..f6079f08460 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -542,11 +542,6 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].bidfloor).to.eql(params.bidfloor); }); - it('Timeout should exists and be a function', function () { - expect(spec.onTimeout).to.exist.and.to.be.a('function'); - expect(spec.onTimeout([{bidder: '1'}])).to.be.undefined; - }); - it('Test schain provided', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); From a5eaf635859ce387c8207f48cf5b7811e29edd28 Mon Sep 17 00:00:00 2001 From: AvivOpenWeb Date: Tue, 28 May 2024 15:21:46 +0300 Subject: [PATCH 0092/1097] OpenWeb: Respond with the correct creativeId (#11574) Co-authored-by: Zdravko Kosanovic --- modules/openwebBidAdapter.js | 2 +- test/spec/modules/openwebBidAdapter_spec.js | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index cf0334b7f29..5bc74ac6465 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -76,7 +76,7 @@ export const spec = { width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, + creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, mediaType: adUnit.mediaType, diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index 4586ce78135..34f92a76c42 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -440,6 +440,8 @@ describe('openwebAdapter', function () { width: 640, height: 480, requestId: '21e12606d47ba7', + creativeId: 'creative-id-1', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: VIDEO }, @@ -449,6 +451,8 @@ describe('openwebAdapter', function () { width: 300, height: 250, requestId: '21e12606d47ba7', + creativeId: 'creative-id-2', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER }] @@ -461,7 +465,7 @@ describe('openwebAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-1', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -476,10 +480,10 @@ describe('openwebAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-2', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -492,8 +496,8 @@ describe('openwebAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { From 69578436c07a9c90a054eea44824fdd135e16c98 Mon Sep 17 00:00:00 2001 From: John Salis Date: Tue, 28 May 2024 08:27:23 -0400 Subject: [PATCH 0093/1097] Beachfront Bid Adapter : add plcmt support (#11558) * change placement to plcmt * add placement param --------- Co-authored-by: John Salis --- modules/beachfrontBidAdapter.js | 4 ++-- test/spec/modules/beachfrontBidAdapter_spec.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 658fc30b43b..8f4a9e3e46d 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -15,7 +15,7 @@ import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; -const ADAPTER_VERSION = '1.20'; +const ADAPTER_VERSION = '1.21'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; const CURRENCY = 'USD'; @@ -26,7 +26,7 @@ export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/l export const SYNC_IFRAME_ENDPOINT = 'https://sync.bfmio.com/sync_iframe'; export const SYNC_IMAGE_ENDPOINT = 'https://sync.bfmio.com/syncb'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'plcmt', 'skip', 'skipmin', 'skipafter']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; export const SUPPORTED_USER_IDS = [ diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index c0994985aae..2e766487951 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -245,14 +245,14 @@ describe('BeachfrontAdapter', function () { const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; - const placement = 4; + const plcmt = 4; const skip = 1; bidRequest.mediaTypes = { - video: { mimes, playbackmethod, maxduration, placement, skip } + video: { mimes, playbackmethod, maxduration, plcmt, skip } }; const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, plcmt, skip }); }); it('must override video params from the bidder object', function () { @@ -260,13 +260,13 @@ describe('BeachfrontAdapter', function () { const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; - const placement = 4; + const plcmt = 4; const skip = 1; - bidRequest.mediaTypes = { video: { placement: 3, skip: 0 } }; - bidRequest.params.video = { mimes, playbackmethod, maxduration, placement, skip }; + bidRequest.mediaTypes = { video: { plcmt: 3, skip: 0 } }; + bidRequest.params.video = { mimes, playbackmethod, maxduration, plcmt, skip }; const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, plcmt, skip }); }); it('must add US privacy data to the request', function () { From 646426f940c2b293a3cb90cf793b49cded8bef93 Mon Sep 17 00:00:00 2001 From: Nikhil <137479857+NikhilGopalChennissery@users.noreply.github.com> Date: Tue, 28 May 2024 18:31:18 +0530 Subject: [PATCH 0094/1097] Preciso Bid Adapter : update on valid request checks (#11161) * test logs added * Added precisoExample.html in integrationExamples * updated request Validation check * bid request data updated * bid request data updated * trailing spaces removed * precisoBidAdapter_spec.js updated * Auction_price macro replacing from response * Auction_price macro replacing from response * Auction_price macro replacing from response * Test logs removed * Bid Request valid check modified * Test logs removed * userid updated in usersync call * add back blank line * add blank line to end * bidFloor correction --------- Co-authored-by: Chris Huie --- integrationExamples/gpt/precisoExample.html | 168 +++++++++++++++++++ modules/precisoBidAdapter.js | 170 ++++++++++---------- test/spec/modules/precisoBidAdapter_spec.js | 73 ++++----- 3 files changed, 288 insertions(+), 123 deletions(-) create mode 100644 integrationExamples/gpt/precisoExample.html diff --git a/integrationExamples/gpt/precisoExample.html b/integrationExamples/gpt/precisoExample.html new file mode 100644 index 00000000000..1c4fa661edd --- /dev/null +++ b/integrationExamples/gpt/precisoExample.html @@ -0,0 +1,168 @@ + + + + + + + + + + + + + +

Basic Prebid.js Example with Preciso Bidder

+

Adslot-1

+
+ +
+ +
+

Adslot-2

+ +
+ + + + diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 9125f6f3911..370591bfe91 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,15 +1,24 @@ -import { logMessage, isFn, deepAccess, logInfo } from '../src/utils.js'; +import { isFn, deepAccess, logInfo, replaceAuctionPrice } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +// import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const BIDDER_CODE = 'preciso'; +const COOKIE_NAME = '_sharedid'; const AD_URL = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; +// const AD_URL = 'http://localhost:80/bid_request/openrtb'; const URL_SYNC = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const GVLID = 874; let userId = 'NA'; +let precisoId = 'NA'; +let sharedId = 'NA' + +export const storage2 = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER_CODE }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: 'sharedId' }); export const spec = { code: BIDDER_CODE, @@ -17,44 +26,40 @@ export const spec = { gvlid: GVLID, isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.publisherId) && bid.params.host == 'prebid'); + sharedId = readFromAllStorages(COOKIE_NAME); + let precisoBid = true; + const preCall = 'https://ssp-usersync.mndtrk.com/getUUID?sharedId=' + sharedId; + precisoId = window.localStorage.getItem('_pre|id'); + + if (Object.is(precisoId, 'NA') || Object.is(precisoId, null) || Object.is(precisoId, undefined)) { + if (!bid.precisoBid) { + precisoBid = false; + getapi(preCall); + } + } + + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.publisherId) && precisoBid); }, buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - // userId = validBidRequests[0].userId.pubcid; - let winTop = window; - let location; - var offset = new Date().getTimezoneOffset(); - logInfo('timezone ' + offset); + if (validBidRequests !== 'undefined' && validBidRequests.length > 0) { + userId = validBidRequests[0].userId.pubcid; + } + // let winTop = window; + // let location; var city = Intl.DateTimeFormat().resolvedOptions().timeZone; - logInfo('location test' + city) - - const countryCode = getCountryCodeByTimezone(city); - logInfo(`The country code for ${city} is ${countryCode}`); - - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let request = { - id: validBidRequests[0].bidderRequestId, - + // bidRequest: bidderRequest, + id: validBidRequests[0].auctionId, + cur: validBidRequests[0].params.currency || ['USD'], imp: validBidRequests.map(request => { - const { bidId, sizes, mediaType, ortb2 } = request + const { bidId, sizes } = request const item = { id: bidId, - region: request.params.region, - traffic: mediaType, bidFloor: getBidFloor(request), - ortb2: ortb2 - + bidfloorcur: request.params.currency } if (request.mediaTypes.banner) { @@ -62,38 +67,28 @@ export const spec = { format: (request.mediaTypes.banner.sizes || sizes).map(size => { return { w: size[0], h: size[1] } }), - } - } - - if (request.schain) { - item.schain = request.schain; - } - if (request.floorData) { - item.bidFloor = request.floorData.floorMin; + } } return item }), - auctionId: validBidRequests[0].auctionId, - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - geo: navigator.geolocation.getCurrentPosition(position => { - const { latitude, longitude } = position.coords; - return { - latitude: latitude, - longitude: longitude - } - // Show a map centered at latitude / longitude. - }) || { utcoffset: new Date().getTimezoneOffset() }, - city: city, - 'host': location.host, - 'page': location.pathname, - 'coppa': config.getConfig('coppa') === true ? 1 : 0 - // userId: validBidRequests[0].userId + user: { + id: validBidRequests[0].userId.pubcid || '', + buyeruid: window.localStorage.getItem('_pre|id'), + geo: { + region: validBidRequests[0].params.region || city, + }, + + }, + device: validBidRequests[0].ortb2.device, + site: validBidRequests[0].ortb2.site, + source: validBidRequests[0].ortb2.source, + bcat: validBidRequests[0].ortb2.bcat || validBidRequests[0].params.bcat, + badv: validBidRequests[0].ortb2.badv || validBidRequests[0].params.badv, + wlang: validBidRequests[0].ortb2.wlang || validBidRequests[0].params.wlang }; - request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) + // request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) if (bidderRequest) { if (bidderRequest.uspConsent) { request.ccpa = bidderRequest.uspConsent; @@ -127,21 +122,21 @@ export const spec = { width: bid.w, height: bid.h, creativeId: bid.crid, - ad: bid.adm, + ad: macroReplace(bid.adm, bid.price), currency: 'USD', netRevenue: true, ttl: 300, meta: { - advertiserDomains: bid.adomain || [], + advertiserDomains: bid.adomain || '', }, }) }) }) - return bids }, getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '', gppConsent = '') => { + userId = sharedId; let syncs = []; let { gdprApplies, consentString = '' } = gdprConsent; @@ -165,31 +160,10 @@ export const spec = { }; -function getCountryCodeByTimezone(city) { - try { - const now = new Date(); - const options = { - timeZone: city, - timeZoneName: 'long', - }; - const [timeZoneName] = new Intl.DateTimeFormat('en-US', options) - .formatToParts(now) - .filter((part) => part.type === 'timeZoneName'); - - if (timeZoneName) { - // Extract the country code from the timezone name - const parts = timeZoneName.value.split('-'); - if (parts.length >= 2) { - return parts[1]; - } - } - } catch (error) { - // Handle errors, such as an invalid timezone city - logInfo(error); - } - - // Handle the case where the city is not found or an error occurred - return 'Unknown'; +/* replacing auction_price macro from adm */ +function macroReplace(adm, cpm) { + let replacedadm = replaceAuctionPrice(adm, cpm); + return replacedadm; } function getBidFloor(bid) { @@ -209,4 +183,34 @@ function getBidFloor(bid) { } } +async function getapi(url) { + try { + // Storing response + const response = await fetch(url); + + // Storing data in form of JSON + var data = await response.json(); + + const dataMap = new Map(Object.entries(data)); + + const uuidValue = dataMap.get('UUID'); + + if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) { + storage2.setDataInLocalStorage('_pre|id', uuidValue); + logInfo('DEBUG nonNull uuidValue:' + uuidValue); + } + + return data; + } catch (error) { + logInfo('Error in preciso precall' + error); + } +} + +function readFromAllStorages(name) { + const fromCookie = storage.getCookie(name); + const fromLocalStorage = storage.getDataFromLocalStorage(name); + + return fromCookie || fromLocalStorage || undefined; +} + registerBidder(spec); diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 78a1615a02e..4ac1c479bb9 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/precisoBidAdapter.js'; -import { config } from '../../../src/config.js'; +// simport { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 const DEFAULT_CURRENCY = 'USD' @@ -10,6 +10,7 @@ const BIDDER_CODE = 'preciso'; describe('PrecisoAdapter', function () { let bid = { + precisoBid: true, bidId: '23fhj33i987f', bidder: 'preciso', mediaTypes: { @@ -22,15 +23,33 @@ describe('PrecisoAdapter', function () { sourceid: '0', publisherId: '0', mediaType: 'banner', - region: 'prebid-eu' + region: 'IND' }, userId: { pubcid: '12355454test' }, - geo: 'NA', - city: 'Asia,delhi' + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + } + }; describe('isBidRequestValid', function () { @@ -59,43 +78,17 @@ describe('PrecisoAdapter', function () { }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; - // expect(data).to.be.an('object'); - - // expect(data).to.have.all.keys('bidId', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); - - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.coppa).to.be.a('number'); - expect(data.language).to.be.a('string'); - // expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - - expect(data.city).to.be.a('string'); - expect(data.geo).to.be.a('object'); - // expect(data.userId).to.be.a('string'); - // expect(data.imp).to.be.a('object'); - }); - // it('Returns empty data if no valid requests are passed', function () { - /// serverRequest = spec.buildRequests([]); - // let data = serverRequest.data; - // expect(data.imp).to.be.an('array').that.is.empty; - // }); - }); - - describe('with COPPA', function () { - beforeEach(function () { - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + expect(data).to.be.an('object'); + expect(data.device).to.be.a('object'); + expect(data.user).to.be.a('object'); + expect(data.source).to.be.a('object'); + expect(data.site).to.be.a('object'); }); - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send the Coppa "required" flag set to "1" in the request', function () { - let serverRequest = spec.buildRequests([bid]); - expect(serverRequest.data.coppa).to.equal(1); + it('Returns empty data if no valid requests are passed', function () { + delete bid.ortb2.device; + serverRequest = spec.buildRequests([bid]); + let data = serverRequest.data; + expect(data.device).to.be.undefined; }); }); From 7851caf9171ec1e7108023744015dd65d6b2706d Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 28 May 2024 07:48:55 -0600 Subject: [PATCH 0095/1097] Revert "Preciso Bid Adapter : update on valid request checks (#11161)" (#11578) This reverts commit 646426f940c2b293a3cb90cf793b49cded8bef93. --- integrationExamples/gpt/precisoExample.html | 168 ------------------- modules/precisoBidAdapter.js | 170 ++++++++++---------- test/spec/modules/precisoBidAdapter_spec.js | 73 +++++---- 3 files changed, 123 insertions(+), 288 deletions(-) delete mode 100644 integrationExamples/gpt/precisoExample.html diff --git a/integrationExamples/gpt/precisoExample.html b/integrationExamples/gpt/precisoExample.html deleted file mode 100644 index 1c4fa661edd..00000000000 --- a/integrationExamples/gpt/precisoExample.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - - -

Basic Prebid.js Example with Preciso Bidder

-

Adslot-1

-
- -
- -
-

Adslot-2

- -
- - - - diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 370591bfe91..9125f6f3911 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,24 +1,15 @@ -import { isFn, deepAccess, logInfo, replaceAuctionPrice } from '../src/utils.js'; +import { logMessage, isFn, deepAccess, logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -// import { config } from '../src/config.js'; +import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const BIDDER_CODE = 'preciso'; -const COOKIE_NAME = '_sharedid'; const AD_URL = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; -// const AD_URL = 'http://localhost:80/bid_request/openrtb'; const URL_SYNC = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const GVLID = 874; let userId = 'NA'; -let precisoId = 'NA'; -let sharedId = 'NA' - -export const storage2 = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER_CODE }); -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: 'sharedId' }); export const spec = { code: BIDDER_CODE, @@ -26,40 +17,44 @@ export const spec = { gvlid: GVLID, isBidRequestValid: (bid) => { - sharedId = readFromAllStorages(COOKIE_NAME); - let precisoBid = true; - const preCall = 'https://ssp-usersync.mndtrk.com/getUUID?sharedId=' + sharedId; - precisoId = window.localStorage.getItem('_pre|id'); - - if (Object.is(precisoId, 'NA') || Object.is(precisoId, null) || Object.is(precisoId, undefined)) { - if (!bid.precisoBid) { - precisoBid = false; - getapi(preCall); - } - } - - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.publisherId) && precisoBid); + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.publisherId) && bid.params.host == 'prebid'); }, buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - if (validBidRequests !== 'undefined' && validBidRequests.length > 0) { - userId = validBidRequests[0].userId.pubcid; - } - // let winTop = window; - // let location; + // userId = validBidRequests[0].userId.pubcid; + let winTop = window; + let location; + var offset = new Date().getTimezoneOffset(); + logInfo('timezone ' + offset); var city = Intl.DateTimeFormat().resolvedOptions().timeZone; + logInfo('location test' + city) + + const countryCode = getCountryCodeByTimezone(city); + logInfo(`The country code for ${city} is ${countryCode}`); + + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin + try { + location = new URL(bidderRequest.refererInfo.page) + winTop = window.top; + } catch (e) { + location = winTop.location; + logMessage(e); + }; + let request = { - // bidRequest: bidderRequest, - id: validBidRequests[0].auctionId, - cur: validBidRequests[0].params.currency || ['USD'], + id: validBidRequests[0].bidderRequestId, + imp: validBidRequests.map(request => { - const { bidId, sizes } = request + const { bidId, sizes, mediaType, ortb2 } = request const item = { id: bidId, + region: request.params.region, + traffic: mediaType, bidFloor: getBidFloor(request), - bidfloorcur: request.params.currency + ortb2: ortb2 + } if (request.mediaTypes.banner) { @@ -67,28 +62,38 @@ export const spec = { format: (request.mediaTypes.banner.sizes || sizes).map(size => { return { w: size[0], h: size[1] } }), - } } + + if (request.schain) { + item.schain = request.schain; + } + + if (request.floorData) { + item.bidFloor = request.floorData.floorMin; + } return item }), - user: { - id: validBidRequests[0].userId.pubcid || '', - buyeruid: window.localStorage.getItem('_pre|id'), - geo: { - region: validBidRequests[0].params.region || city, - }, - - }, - device: validBidRequests[0].ortb2.device, - site: validBidRequests[0].ortb2.site, - source: validBidRequests[0].ortb2.source, - bcat: validBidRequests[0].ortb2.bcat || validBidRequests[0].params.bcat, - badv: validBidRequests[0].ortb2.badv || validBidRequests[0].params.badv, - wlang: validBidRequests[0].ortb2.wlang || validBidRequests[0].params.wlang + auctionId: validBidRequests[0].auctionId, + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language : '', + geo: navigator.geolocation.getCurrentPosition(position => { + const { latitude, longitude } = position.coords; + return { + latitude: latitude, + longitude: longitude + } + // Show a map centered at latitude / longitude. + }) || { utcoffset: new Date().getTimezoneOffset() }, + city: city, + 'host': location.host, + 'page': location.pathname, + 'coppa': config.getConfig('coppa') === true ? 1 : 0 + // userId: validBidRequests[0].userId }; - // request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) + request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) if (bidderRequest) { if (bidderRequest.uspConsent) { request.ccpa = bidderRequest.uspConsent; @@ -122,21 +127,21 @@ export const spec = { width: bid.w, height: bid.h, creativeId: bid.crid, - ad: macroReplace(bid.adm, bid.price), + ad: bid.adm, currency: 'USD', netRevenue: true, ttl: 300, meta: { - advertiserDomains: bid.adomain || '', + advertiserDomains: bid.adomain || [], }, }) }) }) + return bids }, getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '', gppConsent = '') => { - userId = sharedId; let syncs = []; let { gdprApplies, consentString = '' } = gdprConsent; @@ -160,10 +165,31 @@ export const spec = { }; -/* replacing auction_price macro from adm */ -function macroReplace(adm, cpm) { - let replacedadm = replaceAuctionPrice(adm, cpm); - return replacedadm; +function getCountryCodeByTimezone(city) { + try { + const now = new Date(); + const options = { + timeZone: city, + timeZoneName: 'long', + }; + const [timeZoneName] = new Intl.DateTimeFormat('en-US', options) + .formatToParts(now) + .filter((part) => part.type === 'timeZoneName'); + + if (timeZoneName) { + // Extract the country code from the timezone name + const parts = timeZoneName.value.split('-'); + if (parts.length >= 2) { + return parts[1]; + } + } + } catch (error) { + // Handle errors, such as an invalid timezone city + logInfo(error); + } + + // Handle the case where the city is not found or an error occurred + return 'Unknown'; } function getBidFloor(bid) { @@ -183,34 +209,4 @@ function getBidFloor(bid) { } } -async function getapi(url) { - try { - // Storing response - const response = await fetch(url); - - // Storing data in form of JSON - var data = await response.json(); - - const dataMap = new Map(Object.entries(data)); - - const uuidValue = dataMap.get('UUID'); - - if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) { - storage2.setDataInLocalStorage('_pre|id', uuidValue); - logInfo('DEBUG nonNull uuidValue:' + uuidValue); - } - - return data; - } catch (error) { - logInfo('Error in preciso precall' + error); - } -} - -function readFromAllStorages(name) { - const fromCookie = storage.getCookie(name); - const fromLocalStorage = storage.getDataFromLocalStorage(name); - - return fromCookie || fromLocalStorage || undefined; -} - registerBidder(spec); diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 4ac1c479bb9..78a1615a02e 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/precisoBidAdapter.js'; -// simport { config } from '../../../src/config.js'; +import { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 const DEFAULT_CURRENCY = 'USD' @@ -10,7 +10,6 @@ const BIDDER_CODE = 'preciso'; describe('PrecisoAdapter', function () { let bid = { - precisoBid: true, bidId: '23fhj33i987f', bidder: 'preciso', mediaTypes: { @@ -23,33 +22,15 @@ describe('PrecisoAdapter', function () { sourceid: '0', publisherId: '0', mediaType: 'banner', - region: 'IND' + region: 'prebid-eu' }, userId: { pubcid: '12355454test' }, - user: { - geo: { - region: 'IND', - } - }, - ortb2: { - device: { - w: 1920, - h: 166, - dnt: 0, - }, - site: { - domain: 'localHost' - }, - source: { - tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' - } - - } - + geo: 'NA', + city: 'Asia,delhi' }; describe('isBidRequestValid', function () { @@ -78,17 +59,43 @@ describe('PrecisoAdapter', function () { }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data.device).to.be.a('object'); - expect(data.user).to.be.a('object'); - expect(data.source).to.be.a('object'); - expect(data.site).to.be.a('object'); + // expect(data).to.be.an('object'); + + // expect(data).to.have.all.keys('bidId', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); + + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.coppa).to.be.a('number'); + expect(data.language).to.be.a('string'); + // expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + + expect(data.city).to.be.a('string'); + expect(data.geo).to.be.a('object'); + // expect(data.userId).to.be.a('string'); + // expect(data.imp).to.be.a('object'); }); - it('Returns empty data if no valid requests are passed', function () { - delete bid.ortb2.device; - serverRequest = spec.buildRequests([bid]); - let data = serverRequest.data; - expect(data.device).to.be.undefined; + // it('Returns empty data if no valid requests are passed', function () { + /// serverRequest = spec.buildRequests([]); + // let data = serverRequest.data; + // expect(data.imp).to.be.an('array').that.is.empty; + // }); + }); + + describe('with COPPA', function () { + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([bid]); + expect(serverRequest.data.coppa).to.equal(1); }); }); From 8620ae4464bd0a7bbe4e112d5b636715cc2d8021 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue, 28 May 2024 16:51:16 +0300 Subject: [PATCH 0096/1097] update adapter SmartHub: add aliases (#11553) --- modules/smarthubBidAdapter.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 0be3e6831e7..ea2b62c95c9 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -5,10 +5,16 @@ import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'smarthub'; -const ALIASES = [{code: 'markapp', skipPbsAliasing: true}]; +const ALIASES = [ + {code: 'markapp', skipPbsAliasing: true}, + {code: 'jdpmedia', skipPbsAliasing: true}, + {code: 'tredio', skipPbsAliasing: true}, +]; const BASE_URLS = { smarthub: 'https://prebid.smart-hub.io/pbjs', - markapp: 'https://markapp-prebid.smart-hub.io/pbjs' + markapp: 'https://markapp-prebid.smart-hub.io/pbjs', + jdpmedia: 'https://jdpmedia-prebid.smart-hub.io/pbjs', + tredio: 'https://tredio-prebid.smart-hub.io/pbjs' }; function getUrl(partnerName) { From b44deb8944a10465c16b1df19634420f04929e3c Mon Sep 17 00:00:00 2001 From: Andrea Tumbarello Date: Tue, 28 May 2024 16:46:56 +0200 Subject: [PATCH 0097/1097] AIDEM Bid Adapter : fixed getConfig cleanup of consent management (#11575) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object * PlacementId is now a required parameter Added optional rateLimit parameter Added publisherId, siteId, placementId in win notice payload Added unit tests * Revert to optional placementId parameter Added missing semicolons * Extended win notice * Added arbitrary ext field to win notice * Moved aidemBidAdapter implementation to comply with ortbConverter * disabled video-specific tests * Fixed getConfig cleanup of consent management (Issue #10658) * Fixed getConfig cleanup of consent management (Issue #10658) * Fixed getConfig cleanup of consent management (Issue #10658) * Fixed getConfig cleanup of consent management (Issue #10658) --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> --- modules/aidemBidAdapter.js | 18 ++-- test/spec/modules/aidemBidAdapter_spec.js | 120 +++++++++++++++------- 2 files changed, 93 insertions(+), 45 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index c6a5cd96fb6..0730149e909 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -59,7 +59,7 @@ const converter = ortbConverter({ const request = buildRequest(imps, bidderRequest, context); deepSetValue(request, 'at', 1); setPrebidRequestEnvironment(request); - deepSetValue(request, 'regs', getRegs()); + deepSetValue(request, 'regs', getRegs(bidderRequest)); deepSetValue(request, 'site.publisher.id', bidderRequest.bids[0].params.publisherId); deepSetValue(request, 'site.id', bidderRequest.bids[0].params.siteId); return request; @@ -106,22 +106,22 @@ function recur(obj) { return result; } -function getRegs() { +function getRegs(bidderRequest) { let regs = {}; - const consentManagement = config.getConfig('consentManagement'); + const euConsentManagement = bidderRequest.gdprConsent; + const usConsentManagement = bidderRequest.uspConsent; const coppa = config.getConfig('coppa'); - if (consentManagement && !!(consentManagement.gdpr)) { - deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); + if (euConsentManagement && euConsentManagement.consentString) { + deepSetValue(regs, 'gdpr_applies', !!euConsentManagement.consentString); } else { deepSetValue(regs, 'gdpr_applies', false); } - if (consentManagement && deepAccess(consentManagement, 'usp.cmpApi') === 'static') { - deepSetValue(regs, 'usp_applies', !!deepAccess(consentManagement, 'usp')); - deepSetValue(regs, 'us_privacy', deepAccess(consentManagement, 'usp.consentData.getUSPData.uspString')); + if (usConsentManagement) { + deepSetValue(regs, 'usp_applies', true); + deepSetValue(regs, 'us_privacy', bidderRequest.uspConsent); } else { deepSetValue(regs, 'usp_applies', false); } - if (isBoolean(coppa)) { deepSetValue(regs, 'coppa_applies', !!coppa); } else { diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 3de348197b2..c9d29ff09dd 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -168,7 +168,7 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ }, params: { siteId: '1', - placementId: '13144370' + placementId: '13144370', }, src: 'client', transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' @@ -193,7 +193,7 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ }, params: { siteId: '1', - placementId: '13144370' + placementId: '13144370', }, src: 'client', transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' @@ -209,7 +209,50 @@ const VALID_BIDDER_REQUEST = { params: { placementId: '13144370', siteId: '23434', - publisherId: '7689670753' + publisherId: '7689670753', + }, + } + ], + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, +} + +const VALID_GDPR_BIDDER_REQUEST = { + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + bidderCode: 'aidem', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753', + }, + } + ], + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, + gdprConsent: { + consentString: 'test-gdpr-string' + } +} + +const VALID_USP_BIDDER_REQUEST = { + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + bidderCode: 'aidem', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753', }, } ], @@ -218,6 +261,7 @@ const VALID_BIDDER_REQUEST = { domain: 'test-domain', ref: 'test-referer' }, + uspConsent: '1YYY' } const SERVER_RESPONSE_BANNER = { @@ -601,47 +645,51 @@ describe('Aidem adapter', () => { }); it(`should set gdpr to true`, function () { - config.setConfig({ - consentManagement: { - gdpr: { - // any data here set gdpr to true - }, - } - }); - const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + // config.setConfig({ + // consentManagement: { + // gdpr: { + // consentData: { + // getTCData: { + // tcString: 'test-gdpr-string' + // } + // } + // }, + // } + // }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_GDPR_BIDDER_REQUEST); expect(data.regs.gdpr_applies).to.equal(true) }); it(`should set usp_consent string`, function () { - config.setConfig({ - consentManagement: { - usp: { - cmpApi: 'static', - consentData: { - getUSPData: { - uspString: '1YYY' - } - } - } - } - }); - const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + // config.setConfig({ + // consentManagement: { + // usp: { + // cmpApi: 'static', + // consentData: { + // getUSPData: { + // uspString: '1YYY' + // } + // } + // } + // } + // }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_USP_BIDDER_REQUEST); expect(data.regs.us_privacy).to.equal('1YYY') }); it(`should not set usp_consent string`, function () { - config.setConfig({ - consentManagement: { - usp: { - cmpApi: 'iab', - consentData: { - getUSPData: { - uspString: '1YYY' - } - } - } - } - }); + // config.setConfig({ + // consentManagement: { + // usp: { + // cmpApi: 'iab', + // consentData: { + // getUSPData: { + // uspString: '1YYY' + // } + // } + // } + // } + // }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); expect(data.regs.us_privacy).to.undefined }); From f6040c445428071cf4547b5663fd2d2e86bc74c6 Mon Sep 17 00:00:00 2001 From: Parth Shah Date: Tue, 28 May 2024 20:36:50 +0530 Subject: [PATCH 0098/1097] DeepIntent Bid Adapter : update video.placement to video.plcmt (#11577) * fix user ids not being passed on page reload due to extendId func miss * remove extendId and add function to read value for eids * handle inconsistent localstorage values in deepintent id * remove unused imports * revert to getValue changes * revert version in package-lock * update test suite for deepintent id system * fix - video.placement was deprecated in favor of video.plcmt --- modules/deepintentBidAdapter.js | 2 +- modules/deepintentBidAdapter.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 0a64ed88ca5..f0c76ae557b 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -14,7 +14,7 @@ export const ORTB_VIDEO_PARAMS = { 'w': (value) => isInteger(value), 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), - 'placement': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), + 'plcmt': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), 'linearity': (value) => [1, 2].indexOf(value) !== -1, 'skip': (value) => [0, 1].indexOf(value) !== -1, 'skipmin': (value) => isInteger(value), diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md index 84c375d69a4..0f017402d1d 100644 --- a/modules/deepintentBidAdapter.md +++ b/modules/deepintentBidAdapter.md @@ -66,7 +66,7 @@ var adVideoAdUnits = [ protocols: [ 2, 3 ], // optional battr: [ 13, 14 ], // optional linearity: 1, // optional - placement: 2, // optional + plcmt: 2, // optional minbitrate: 10, // optional maxbitrate: 10 // optional } From 6b446e602d300e7bf949259e29ef1ec5b3d0144a Mon Sep 17 00:00:00 2001 From: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Date: Tue, 28 May 2024 18:22:04 +0300 Subject: [PATCH 0099/1097] IQzone Bid Adapter : update placement to plcmt and move coppa from getConfig (#11562) * add IQZone adapter * add endpointId param * add user sync * added gpp support * added support of transanctionId and eids * updated tests * changed placement to plcmt --- modules/iqzoneBidAdapter.js | 58 ++++++++++++----- test/spec/modules/iqzoneBidAdapter_spec.js | 75 ++++++++++++++++++++-- 2 files changed, 111 insertions(+), 22 deletions(-) diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 52f3be7e4b4..3ce622dba10 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -1,4 +1,4 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { logMessage, logError, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -9,8 +9,7 @@ const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; const SYNC_URL = 'https://cs.smartssp.iqzone.com'; function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { return false; } @@ -27,7 +26,7 @@ function isBidResponseValid(bid) { } function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; + const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; const schain = bid.schain || {}; const { placementId, endpointId } = params; const bidfloor = getBidFloor(bid); @@ -57,7 +56,7 @@ function getPlacementReqData(bid) { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; @@ -71,14 +70,19 @@ function getPlacementReqData(bid) { placement.adFormat = NATIVE; } + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; + } + return placement; } function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - try { const bidFloor = bid.getFloor({ currency: 'USD', @@ -86,8 +90,9 @@ function getBidFloor(bid) { size: '*', }); return bidFloor.floor; - } catch (_) { - return 0 + } catch (err) { + logError(err); + return 0; } } @@ -151,12 +156,28 @@ export const spec = { host, page, placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, tmax: bidderRequest.timeout }; + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; @@ -184,9 +205,10 @@ export const spec = { return response; }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; @@ -194,10 +216,16 @@ export const spec = { syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; } } + if (uspConsent && uspConsent.consentString) { syncUrl += `&ccpa_consent=${uspConsent.consentString}`; } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl += '&gpp=' + gppConsent.gppString; + syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + const coppa = config.getConfig('coppa') ? 1 : 0; syncUrl += `&coppa=${coppa}`; diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index 2e920d3b769..9d012e526e2 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -6,6 +6,16 @@ import { getUniqueIdentifierStr } from '../../../src/utils.js'; const bidder = 'iqzone' describe('IQZoneBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -17,7 +27,8 @@ describe('IQZoneBidAdapter', function () { }, params: { placementId: 'testBanner', - } + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -31,7 +42,8 @@ describe('IQZoneBidAdapter', function () { }, params: { placementId: 'testVideo', - } + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -54,7 +66,8 @@ describe('IQZoneBidAdapter', function () { }, params: { placementId: 'testNative', - } + }, + userIdAsEids } ]; @@ -73,7 +86,9 @@ describe('IQZoneBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + }, refererInfo: { referer: 'https://test.com' }, @@ -129,7 +144,7 @@ describe('IQZoneBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +160,7 @@ describe('IQZoneBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +186,8 @@ describe('IQZoneBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -194,6 +210,38 @@ describe('IQZoneBidAdapter', function () { }); }); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -370,6 +418,7 @@ describe('IQZoneBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -394,5 +443,17 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.smartssp.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.smartssp.iqzone.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); From 78f93cd29ee0955c6ddf2cf20f5251ac8e8c66e1 Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Tue, 28 May 2024 09:51:41 -0600 Subject: [PATCH 0100/1097] Concert Bid Adapter : add dealId Property to Bid Responses (#11582) * collect EIDs for bid request * add ad slot positioning to payload * RPO-2012: Update local storage name-spacing for c_uid (#8) * Updates c_uid namespacing to be more specific for concert * fixes unit tests * remove console.log * RPO-2012: Add check for shared id (#9) * Adds check for sharedId * Updates cookie name * remove trailing comma * [RPO-3152] Enable Support for GPP Consent (#12) * Adds gpp consent integration to concert bid adapter * Update tests to check for gpp consent string param * removes user sync endpoint and tests * updates comment * cleans up consentAllowsPpid function * comment fix * rename variables for clarity * fixes conditional logic for consent allows function (#13) * [RPO-3262] Update getUid function to check for pubcid and sharedid (#14) * Update getUid function to check for pubcid and sharedid * updates adapter version * [RPO-3405] Add browserLanguage to request meta object * ConcertBidAdapter: Add TDID (#20) * Add tdid to meta object * Fix null handling and add tests * Concert Bid Adapter: Add dealId Property to Bid Responses (#22) * adds dealid property to bid responses * updates tests * use first bid for tests * adds dealid at the correct level --------- Co-authored-by: antoin Co-authored-by: Antoin Co-authored-by: Sam Ghitelman Co-authored-by: Sam Ghitelman --- modules/concertBidAdapter.js | 3 ++- test/spec/modules/concertBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index bd738a39bba..12dba194844 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -128,7 +128,8 @@ export const spec = { meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, creativeId: bid.creativeId, netRevenue: bid.netRevenue, - currency: bid.currency + currency: bid.currency, + ...(bid.dealid && { dealId: bid.dealid }), }; }); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 0a76ed3e62d..2fb43236081 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -249,6 +249,22 @@ describe('ConcertAdapter', function () { }); }); + it('should include dealId when present in bidResponse', function() { + const bids = spec.interpretResponse({ + body: { + bids: [ + { ...bidResponse.body.bids[0], dealid: 'CON-123' } + ] + } + }, bidRequest); + expect(bids[0]).to.have.property('dealId'); + }); + + it('should exclude dealId when absent in bidResponse', function() { + const bids = spec.interpretResponse(bidResponse, bidRequest); + expect(bids[0]).to.not.have.property('dealId'); + }); + it('should return empty bids if there is no response from server', function() { const bids = spec.interpretResponse({ body: null }, bidRequest); expect(bids).to.have.lengthOf(0); From 8309f29c8691f1dde45f688b88d683842a00fca7 Mon Sep 17 00:00:00 2001 From: nouchy <33549554+nouchy@users.noreply.github.com> Date: Tue, 28 May 2024 20:17:50 +0200 Subject: [PATCH 0101/1097] Sirdata RTD Module : add eids and post content support and get ready for PBJS 9.0 (#11524) * Add eids and post content suport * multi-character sanitization Modify regular expression to match comments containing newlines. * recursively removing elements to avoid an HTML element injection vulnerability * fix expression vs expected assignment * fix return statement for loop * run completeSanitization instead of sanitizeContent * fix multi-character sanitization * Update sirdataRtdProvider.js * fix missing moduleConfig param * Add filtering eids based on whitelist * fixe default Eids --- modules/sirdataRtdProvider.js | 779 ++++++++++++++----- modules/sirdataRtdProvider.md | 216 +++-- test/spec/modules/sirdataRtdProvider_spec.js | 155 +++- 3 files changed, 827 insertions(+), 323 deletions(-) diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 29d10a3a071..0ad3b891c40 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -7,21 +7,35 @@ * @module modules/sirdataRtdProvider * @requires module:modules/realTimeData */ -import {deepAccess, deepSetValue, isEmpty, logError, logInfo, mergeDeep} from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import {findIndex} from '../src/polyfill.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {config} from '../src/config.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import adapterManager from '../src/adapterManager.js'; +import { ajax } from '../src/ajax.js'; +import { + deepAccess, checkCookieSupport, deepSetValue, hasDeviceAccess, inIframe, isEmpty, + logError, logInfo, mergeDeep +} from '../src/utils.js'; +import { findIndex } from '../src/polyfill.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'SirdataRTDModule'; const ORTB2_NAME = 'sirdata.com'; const LOG_PREFIX = 'Sirdata RTD: '; +const EUIDS_STORAGE_NAME = 'SDDAN'; +// Get the cookie domain from the referer info or fallback to window location hostname +const cookieDomain = getRefererInfo().domain || window.location.hostname; -const partnerIds = { +/** @type {number} */ +const GVLID = 53; + +/** @type {object} */ +const STORAGE = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); +const bidderAliasRegistry = adapterManager.aliasRegistry || {}; +const biddersId = { // Partner IDs mapping for different SSPs and DSPs 'criteo': 27443, 'openx': 30342, 'pubmatic': 30345, @@ -30,25 +44,9 @@ const partnerIds = { 'yahoossp': 30339, 'rubicon': 27452, 'appnexus': 27446, - 'appnexusAst': 27446, - 'brealtime': 27446, - 'emetriq': 27446, - 'emxdigital': 27446, - 'pagescience': 27446, 'gourmetads': 33394, - 'matomy': 27446, - 'featureforward': 27446, - 'oftmedia': 27446, - 'districtm': 27446, - 'adasta': 27446, - 'beintoo': 27446, - 'gravity': 27446, - 'msq_classic': 27878, - 'msq_max': 27878, - '366_apx': 27878, 'mediasquare': 27878, 'smartadserver': 27440, - 'smart': 27440, 'proxistore': 27484, 'ix': 27248, 'sdRtdForGpt': 27449, @@ -65,57 +63,370 @@ const partnerIds = { 'zeta_global_ssp': 33385, }; -export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { - logInfo(LOG_PREFIX, 'init'); - moduleConfig.params = moduleConfig.params || {}; - - let tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : ''); - let gdprApplies = (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies ? userConsent.gdpr.gdprApplies : ''); - - moduleConfig.params.partnerId = moduleConfig.params.partnerId ? moduleConfig.params.partnerId : 1; - moduleConfig.params.key = moduleConfig.params.key ? moduleConfig.params.key : 1; - moduleConfig.params.actualUrl = moduleConfig.params.actualUrl || null; - - let sirdataDomain; - let sendWithCredentials; - - if (userConsent.coppa || (userConsent.usp && (userConsent.usp[0] === '1' && (userConsent.usp[1] === 'N' || userConsent.usp[2] === 'Y')))) { - // if children or "Do not Sell" management in California, no segments, page categories only whatever TCF signal - sirdataDomain = 'cookieless-data.com'; - sendWithCredentials = false; - gdprApplies = null; - tcString = ''; - } else if (config.getConfig('consentManagement.gdpr')) { - // Default endpoint for Contextual results only is cookieless if gdpr management is set. Needed because the cookie-based endpoint will fail and return error if user is located in Europe and no consent has been given - sirdataDomain = 'cookieless-data.com'; - sendWithCredentials = false; +const eidsProvidersMap = { + 'id5': 'id5-sync.com', + 'id5id': 'id5-sync.com', + 'id5_id': 'id5-sync.com', + 'pubprovided_id': 'pubProvidedId', + 'ppid': 'pubProvidedId', + 'first-id.fr': 'pubProvidedId', + 'sharedid': 'pubcid.org', + 'publishercommonid': 'pubcid.org', + 'pubcid.org': 'pubcid.org', +} + +// params +let params = { + partnerId: 1, + key: 1, + actualUrl: getRefererInfo().stack.pop() || getRefererInfo().page, + cookieAccessGranted: false, + setGptKeyValues: true, + contextualMinRelevancyScore: 30, + preprod: false, + authorizedEids: ['pubProvidedId', 'id5-sync.com', 'pubcid.org'], + avoidPostContent: false, + sirdataDomain: 'cookieless-data.com', + bidders: [] +}; + +/** + * Sets a cookie on the top-level domain + * @param {string} key - The cookie name + * @param {string} value - The cookie value + * @param {string} hostname - The hostname for setting the cookie + * @param {boolean} deleteCookie - The cookie must be deleted + * @returns {boolean} - True if the cookie was successfully set, otherwise false + */ +export function setCookieOnTopDomain(key, value, hostname, deleteCookie) { + const subDomains = hostname.split('.'); + let expTime = new Date(); + expTime.setTime(expTime.getTime() + (deleteCookie ? -1 : 365 * 24 * 60 * 60 * 1000)); // Set expiration time + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1).join('.'); + try { + STORAGE.setCookie(key, value, expTime.toUTCString(), 'Lax', '.' + domain); + // Try to read the cookie to check if we wrote it + if (STORAGE.getCookie(key, null) === value) return true; // Check if the cookie was set, and if so top domain was found. If deletion with expire date -1 will parse until complete host + } catch (e) { + logError(LOG_PREFIX, e); + } } + return false; +} - // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply +/** + * Retrieves the UID from storage (cookies or local storage) + * @returns {Array|null} - Array of UID objects or null if no UID found + */ +export function getUidFromStorage() { + let cUid = STORAGE.getCookie(EUIDS_STORAGE_NAME, null); + let lsUid = STORAGE.getDataFromLocalStorage(EUIDS_STORAGE_NAME, null); + if (cUid && (!lsUid || cUid !== lsUid)) { + STORAGE.setDataInLocalStorage(EUIDS_STORAGE_NAME, cUid, null); + } else if (lsUid && !cUid) { + setCookieOnTopDomain(EUIDS_STORAGE_NAME, lsUid, cookieDomain, false); + cUid = lsUid; + } + return cUid ? [{ source: 'sddan.com', uids: [{ id: cUid, atype: 1 }] }] : null; +} - if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { - sirdataDomain = 'sddan.com'; - sendWithCredentials = true; +/** + * Sets the UID in storage (cookies and local storage) + * @param {string} sddanId - The UID to be set + * @returns {boolean} - True if the UID was successfully set, otherwise false + */ +export function setUidInStorage(sddanId) { + if (!sddanId) return false; + sddanId = encodeURI(sddanId.toString()); + setCookieOnTopDomain(EUIDS_STORAGE_NAME, sddanId, cookieDomain, false); + STORAGE.setDataInLocalStorage(EUIDS_STORAGE_NAME, sddanId, null); + return true; +} + +/** + * Merges and cleans objects from two eids arrays based on the 'source' and unique 'id' within the 'uids' array. + * Processes each array to add unique items or merge uids if the source already exists. + * @param {Array} euids1 - The first array to process and merge. + * @param {Array} euids2 - The second array to process and merge. + * @returns {Array} The merged array with unique sources and uid ids. + */ +export function mergeEuidsArrays(euids1, euids2) { + if (isEmpty(euids1)) return euids2; + if (isEmpty(euids2)) return euids1; + const mergedArray = []; + // Helper function to process each array + const processArray = (array) => { + array.forEach(item => { + if (item.uids) { + const foundIndex = findIndex(mergedArray, function (x) { + return x.source === item.source; + }); + if (foundIndex !== -1) { + // Merge uids if the source is found + item.uids.forEach(uid => { + if (!mergedArray[foundIndex].uids.some(u => u.id === uid.id)) { + mergedArray[foundIndex].uids.push(uid); + } + }); + } else { + // Add the entire item if the source does not exist + mergedArray.push({ ...item, uids: [...item.uids] }); + } + } + }); + }; + // Process both euids1 and euids2 + processArray(euids1); + processArray(euids2); + return mergedArray; +} + +/** + * Handles data deletion request by removing stored EU IDs + * @param {Object} moduleConfig - The module configuration + * @returns {boolean} - True if data was deleted successfully + */ +export function onDataDeletionRequest(moduleConfig) { + if (moduleConfig && moduleConfig.params) { + setCookieOnTopDomain(EUIDS_STORAGE_NAME, '', window.location.hostname, true); + STORAGE.removeDataFromLocalStorage(EUIDS_STORAGE_NAME, null); } + return !getUidFromStorage(); +} - let actualUrl = moduleConfig.params.actualUrl || getRefererInfo().stack.pop() || getRefererInfo().page; +/** + * Sends the page content for semantic analysis to Sirdata's server. + * @param {string} postContentToken - The token required to post content. + * @param {string} actualUrl - The actual URL of the current page. + * @returns {boolean} - True if the content was sent successfully + */ +export function postContentForSemanticAnalysis(postContentToken, actualUrl) { + if (!postContentToken || !actualUrl) return false; - const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + encodeURIComponent(actualUrl) : ''); + try { + let content = document.implementation.createHTMLDocument(''); + // Clone the current document content to avoid altering the original page content + content.documentElement.innerHTML = document.documentElement.innerHTML; + // Sanitize the cloned content to remove unnecessary elements and PII + content = sanitizeContent(content); + // Serialize the sanitized content to a string + const payload = new XMLSerializer().serializeToString(content.documentElement); + + if (payload && payload.length > 300 && payload.length < 300000) { + const url = `https://contextual.sirdata.io/api/v1/push/contextual?post_content_token=${postContentToken}&url=${encodeURIComponent(actualUrl)}`; + + // Use the Beacon API if supported to send the payload + if ('sendBeacon' in navigator) { + navigator.sendBeacon(url, payload); + } else { + // Fallback to using AJAX if Beacon API is not supported + ajax(url, {}, payload, { + contentType: 'text/plain', + method: 'POST', + withCredentials: false, // No user-specific data is tied to the request + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); + } + } + } catch (e) { + logError(LOG_PREFIX, e); + return false; + } + return true; +} + +/** + * Executes a callback function when the document is fully loaded. + * @param {function} callback - The function to execute when the document is ready. + */ +export function onDocumentReady(callback) { + if (typeof callback !== 'function') return false; + try { + if (document.readyState && document.readyState !== 'loading') { + callback(); + } else if (typeof document.addEventListener === 'function') { + document.addEventListener('DOMContentLoaded', callback); + } + } catch (e) { + callback(); + } + return true; +} + +/** + * Removes Personally Identifiable Information (PII) from the content + * @param {string} content - The content to be sanitized + * @returns {string} - The sanitized content + */ +export function removePII(content) { + const patterns = [ + /\b(?:\d{4}[ -]?){3}\d{4}\b/g, // Credit card numbers + /\b\d{10,12}\b/g, // US bank account numbers + /\b\d{5}\d{5}\d{11}\d{2}\b/g, // EU bank account numbers + /\b(\d{3}-\d{2}-\d{4}|\d{9}|\d{13}|\d{2} \d{2} \d{2} \d{3} \d{3} \d{3})\b/g, // SSN + /\b[A-Z]{1,2}\d{6,9}\b/g, // Passport numbers + /\b(\d{8,10}|\d{3}-\d{3}-\d{3}-\d{3}|\d{2} \d{2} \d{2} \d{3} \d{3})\b/g, // ID card numbers + /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email addresses + /(\+?\d{1,3}[-.\s]?)?(\(?\d{2,3}\)?[-.\s]?)(\d{2}[-.\s]?){3,4}\d{2}/g // Phone numbers + ]; + patterns.forEach(pattern => { + content = content.replace(pattern, ''); + }); + return content; +} - ajax(url, - { +/** + * Sanitizes the content by removing unnecessary elements and PII + * @param {Object} content - The content to be sanitized + * @returns {Object} - The sanitized content + */ +export function sanitizeContent(content) { + if (content && content.documentElement.innerText && content.documentElement.innerText.length > 500) { + // Reduce size by removing useless content + // Allowed tags + const allowedTags = [ + 'div', 'span', 'a', 'article', 'section', 'p', 'h1', 'h2', 'body', 'b', 'u', 'i', 'big', 'mark', 'ol', 'small', 'strong', 'blockquote', + 'nav', 'menu', 'li', 'ul', 'ins', 'head', 'title', 'main', 'var', 'table', 'caption', 'colgroup', 'col', 'tr', 'td', 'th', + 'summary', 'details', 'dl', 'dt', 'dd' + ]; + + const processElement = (element) => { + Array.from(element.childNodes).reverse().forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE) { + processElement(child); + Array.from(child.attributes).forEach(attr => { + // keeps only id attributes and class attribute if useful for contextualisation + if (attr.name === 'class' && !/^(main|article|product)/.test(attr.value)) { + child.removeAttribute(attr.name); + } else if (attr.name !== 'id') { + child.removeAttribute(attr.name); + } + }); + if (!child.innerHTML.trim() || !allowedTags.includes(child.tagName.toLowerCase())) { // Keeps only allowed Tags (allowedTags) + child.remove(); + } + } + }); + }; + + const removeEmpty = (element) => { // remove empty tags + Array.from(element.childNodes).reverse().forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE) { + removeEmpty(child); + if (!child.innerHTML.trim()) { + child.remove(); + } + } else if (child.nodeType === Node.TEXT_NODE && !child.textContent.trim()) { + child.remove(); + } + }); + }; + + processElement(content.documentElement); + removeEmpty(content.documentElement); + + // Clean any potential PII + content.documentElement.innerHTML = removePII(content.documentElement.innerHTML); + + let htmlContent = content.documentElement.innerHTML; + // Remove HTML comments + // This regex removes HTML comments, including those that might not be properly closed + htmlContent = htmlContent.replace(/|$)/g, ''); + // Remove multiple spaces + htmlContent = htmlContent.replace(/\s+/g, ' '); + // Remove spaces between tags + htmlContent = htmlContent.replace(/>\s+<'); + // Assign the cleaned content + content.documentElement.innerHTML = htmlContent; + } + return content; +} + +/** + * Fetches segments and categories from Sirdata server and processes the response + * @param {Object} reqBidsConfigObj - The bids configuration object + * @param {function} onDone - The callback function to be called upon completion + * @param {Object} moduleConfig - The module Config + * @param {Object} userConsent - The user consent information + */ +export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + logInfo(LOG_PREFIX, 'get Segments And Categories'); + const adUnits = (reqBidsConfigObj && reqBidsConfigObj.adUnits) || getGlobal().adUnits; + if (!adUnits) { + logInfo(LOG_PREFIX, 'no ad unit, RTD processing is useless'); + onDone(); + return; + } + + const gdprApplies = deepAccess(userConsent, 'gdpr.gdprApplies') ? userConsent.gdpr.gdprApplies : false; + const sirdataSubDomain = params.preprod ? 'kvt-preprod' : 'kvt'; + + let euids; // empty if no right to access device (publisher or user reject) + let privacySignals = ''; + + // Default global endpoint is cookie-based only if no rules falls into cookieless or consent has been given or GDPR doesn't apply + if (hasDeviceAccess() && !userConsent.coppa && (isEmpty(userConsent.usp) || userConsent.usp === -1 || (userConsent.usp[0] === '1' && (userConsent.usp[1] !== 'N' && userConsent.usp[2] !== 'Y'))) && (!gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[GVLID] && deepAccess(userConsent, 'gdpr.vendorData.purpose.consents') && userConsent.gdpr.vendorData.purpose.consents[1] && (userConsent.gdpr.vendorData.purpose.consents[2] || userConsent.gdpr.vendorData.purpose.consents[3]) && userConsent.gdpr.vendorData.purpose.consents[4])) && (isEmpty(userConsent.gpp) || userConsent.gpp.gppString) && checkCookieSupport()) { + params.sirdataDomain = 'sddan.com'; // cookie based domain + params.cookieAccessGranted = true; // cookies sent in request + + if (gdprApplies && deepAccess(userConsent, 'gdpr.consentString')) { + privacySignals = `&gdpr=${gdprApplies}&gdpr_consent=${userConsent.gdpr.consentString}`; + } else if (!isEmpty(userConsent.usp)) { + privacySignals = `&ccpa_consent=${userConsent.usp.toString()}`; + } else if (deepAccess(userConsent, 'gpp.gppString')) { + const sid = deepAccess(userConsent, 'gpp.applicableSections') ? `&gpp_sid=${userConsent.gpp.applicableSections.join(',')}` : ''; + privacySignals = `&gpp=${userConsent.gpp.gppString}${sid}`; + } + + // Authorized EUIDS from storage and sync global for graph + euids = getUidFromStorage(); // Sirdata Id + + if (!isEmpty(params.authorizedEids) && typeof getGlobal().getUserIds === 'function') { + let filteredEids = {}; + const authorizedEids = params.authorizedEids; + const globalUserIds = getGlobal().getUserIds(); + const globalUserIdsAsEids = getGlobal().getUserIdsAsEids(); + + const hasPubProvidedId = authorizedEids.indexOf('pubProvidedId') !== -1; + + if (hasPubProvidedId && !isEmpty(globalUserIds.pubProvidedId)) { // Publisher allows pubProvidedId + filteredEids = mergeEuidsArrays(filteredEids, globalUserIds.pubProvidedId); + } + + if (!hasPubProvidedId || authorizedEids.length > 1) { // Publisher allows other Id providers + const filteredGlobalEids = globalUserIdsAsEids.filter(entry => authorizedEids.includes(entry.source)); + if (!isEmpty(filteredGlobalEids)) { + filteredEids = mergeEuidsArrays(filteredEids, filteredGlobalEids); + } + } + + if (!isEmpty(filteredEids)) { + euids = mergeEuidsArrays(euids, filteredEids); // merge ids for graph id + } + } + } + + const url = `https://${sirdataSubDomain}.${params.sirdataDomain}/api/v1/public/p/${params.partnerId.toString()}/d/${params.key.toString()}/s?callback=&allowed_post_content=${!params.avoidPostContent}${privacySignals}${params.actualUrl ? `&url=${encodeURIComponent(params.actualUrl)}` : ''}`; + const method = isEmpty(euids) ? 'GET' : 'POST'; + const payload = isEmpty(euids) ? null : JSON.stringify({ external_ids: euids }); + + try { + ajax(url, { success: function (response, req) { if (req.status === 200) { try { const data = JSON.parse(response); if (data && data.segments) { - addSegmentData(reqBidsConfigObj, data, moduleConfig, onDone); + addSegmentData(reqBidsConfigObj, data, adUnits, onDone); } else { onDone(); } } catch (e) { onDone(); - logError('unable to parse Sirdata data' + e); + logError(LOG_PREFIX, 'unable to parse Sirdata data' + e); } } else if (req.status === 204) { onDone(); @@ -123,104 +434,136 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }, error: function () { onDone(); - logError('unable to get Sirdata data'); + logError(LOG_PREFIX, 'unable to get Sirdata data'); } - }, - null, - { + }, payload, { contentType: 'text/plain', - method: 'GET', - withCredentials: sendWithCredentials, + method: method, + withCredentials: params.cookieAccessGranted, referrerPolicy: 'unsafe-url', crossOrigin: true }); + } catch (e) { + logError(LOG_PREFIX, e); + } } +/** + * Pushes data to OpenRTB 2.5 fragments for the specified bidder + * @param {Object} ortb2Fragments - The OpenRTB 2.5 fragments + * @param {string} bidder - The bidder name + * @param {Object} data - The data to be pushed + * @param {number} segtaxid - The segment taxonomy ID + * @param {number} cattaxid - The category taxonomy ID + * @returns {boolean} - True if data was pushed successfully + */ export function pushToOrtb2(ortb2Fragments, bidder, data, segtaxid, cattaxid) { try { if (!isEmpty(data.segments)) { if (segtaxid) { setOrtb2Sda(ortb2Fragments, bidder, 'user', data.segments, segtaxid); } else { - setOrtb2(ortb2Fragments, bidder, 'user.ext.data', {sd_rtd: {segments: data.segments}}); + setOrtb2(ortb2Fragments, bidder, 'user.ext.data', { sd_rtd: { segments: data.segments } }); } } if (!isEmpty(data.categories)) { if (cattaxid) { setOrtb2Sda(ortb2Fragments, bidder, 'site', data.categories, cattaxid); } else { - setOrtb2(ortb2Fragments, bidder, 'site.ext.data', {sd_rtd: {categories: data.categories}}); + setOrtb2(ortb2Fragments, bidder, 'site.ext.data', { sd_rtd: { categories: data.categories } }); } } if (!isEmpty(data.categories_score) && !cattaxid) { - setOrtb2(ortb2Fragments, bidder, 'site.ext.data', {sd_rtd: {categories_score: data.categories_score}}); + setOrtb2(ortb2Fragments, bidder, 'site.ext.data', { sd_rtd: { categories_score: data.categories_score } }); } } catch (e) { - logError(e) + logError(LOG_PREFIX, e); } return true; } +/** + * Sets OpenRTB 2.5 Seller Defined Audiences (SDA) data + * @param {Object} ortb2Fragments - The OpenRTB 2.5 fragments + * @param {string} bidder - The bidder name + * @param {string} type - The type of data ('user' or 'site') + * @param {Array} segments - The segments to be set + * @param {number} segtaxValue - The segment taxonomy value + * @returns {boolean} - True if data was set successfully + */ export function setOrtb2Sda(ortb2Fragments, bidder, type, segments, segtaxValue) { try { - let ortb2Data = [{ - name: ORTB2_NAME, - segment: segments.map((segmentId) => ({ id: segmentId })), - }]; - if (segtaxValue) { - ortb2Data[0].ext = { segtax: segtaxValue }; - } - let ortb2Conf = (type === 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); - if (bidder) { - ortb2Conf = {[bidder]: ortb2Conf}; - } + let ortb2Data = [{ name: ORTB2_NAME, segment: segments.map(segmentId => ({ id: segmentId })) }]; + if (segtaxValue) ortb2Data[0].ext = { segtax: segtaxValue }; + let ortb2Conf = (type === 'site') ? { site: { content: { data: ortb2Data } } } : { user: { data: ortb2Data } }; + if (bidder) ortb2Conf = { [bidder]: ortb2Conf }; mergeDeep(ortb2Fragments, ortb2Conf); } catch (e) { - logError(e) + logError(LOG_PREFIX, e); } return true; } +/** + * Sets OpenRTB 2.5 data at the specified path + * @param {Object} ortb2Fragments - The OpenRTB 2.5 fragments + * @param {string} bidder - The bidder name + * @param {string} path - The path to set the data at + * @param {Object} segments - The segments to be set + * @returns {boolean} - True if data was set successfully + */ export function setOrtb2(ortb2Fragments, bidder, path, segments) { try { - if (isEmpty(segments)) { return false; } + if (isEmpty(segments)) return false; let ortb2Conf = {}; - deepSetValue(ortb2Conf, path, segments || {}); - if (bidder) { - ortb2Conf = {[bidder]: ortb2Conf}; - } + deepSetValue(ortb2Conf, path, segments); + if (bidder) ortb2Conf = { [bidder]: ortb2Conf }; mergeDeep(ortb2Fragments, ortb2Conf); } catch (e) { - logError(e) + logError(LOG_PREFIX, e); } - return true; } +/** + * Loads a custom function for processing ad unit data + * @param {function} todo - The custom function to be executed + * @param {Object} adUnit - The ad unit object + * @param {Object} list - The list of data + * @param {Object} data - The data object + * @param {Object} bid - The bid object + * @returns {boolean} - True if the function was executed successfully + */ export function loadCustomFunction(todo, adUnit, list, data, bid) { try { - if (typeof todo == 'function') { - todo(adUnit, list, data, bid); - } + if (typeof todo === 'function') todo(adUnit, list, data, bid); } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } return true; } +/** + * Gets segments and categories array from the data object + * @param {Object} data - The data object + * @param {number} minScore - The minimum score threshold for contextual relevancy + * @param {string} pid - The partner ID (attributed by Sirdata to bidder) + * @returns {Object} - The segments and categories data + */ export function getSegAndCatsArray(data, minScore, pid) { - let sirdataData = {'segments': [], 'categories': [], 'categories_score': {}}; - minScore = minScore && typeof minScore == 'number' ? minScore : 30; - let cattaxid = data.cattaxid || null; - let segtaxid = data.segtaxid || null; + let sirdataData = { segments: [], categories: [], categories_score: {} }; + minScore = typeof minScore === 'number' ? minScore : 30; + const { cattaxid, segtaxid, segments } = data; + const contextualCategories = data.contextual_categories || {}; + // parses contextual categories try { - if (data && data.contextual_categories) { - for (let catId in data.contextual_categories) { - if (data.contextual_categories.hasOwnProperty(catId) && data.contextual_categories[catId]) { - let value = data.contextual_categories[catId]; - if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { - if (pid && pid === '27440' && cattaxid) { // Equativ only - sirdataData.categories.push(pid.toString() + 'cc' + catId.toString()); + if (contextualCategories) { + for (let catId in contextualCategories) { + if (contextualCategories.hasOwnProperty(catId) && contextualCategories[catId]) { + let value = contextualCategories[catId]; + if (value >= minScore && !sirdataData.categories.includes(catId)) { + if (pid === '27440' && cattaxid) { // Equativ only + sirdataData.categories.push(`${pid}cc${catId}`); } else { sirdataData.categories.push(catId.toString()); sirdataData.categories_score[catId] = value; @@ -230,15 +573,16 @@ export function getSegAndCatsArray(data, minScore, pid) { } } } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } + // parses user-centric segments (empty if no right to access device/process PII) try { - if (data && data.segments) { - for (let segId in data.segments) { - if (data.segments.hasOwnProperty(segId) && data.segments[segId]) { - let id = data.segments[segId].toString(); - if (pid && pid === '27440' && segtaxid) { // Equativ only - sirdataData.segments.push(pid.toString() + 'us' + id); + if (segments) { + for (let segId in segments) { + if (segments.hasOwnProperty(segId) && segments[segId]) { + let id = segments[segId].toString(); + if (pid === '27440' && segtaxid) { // Equativ only + sirdataData.segments.push(`${pid}us${id}`); } else { sirdataData.segments.push(id); } @@ -246,150 +590,177 @@ export function getSegAndCatsArray(data, minScore, pid) { } } } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } return sirdataData; } -export function applySdaGetSpecificData(data, sirdataData, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { - // only share SDA data if whitelisted - if (!biddersParamsExist || indexFound) { +/** + * Applies Seller Defined Audience (SDA) data and specific data for the bidder + * @param {Object} data - The data object + * @param {Object} sirdataData - The Sirdata data object + * @param {boolean} biddersParamsExist - Flag indicating if bidder parameters exist + * @param {Object} reqBids - The request bids object + * @param {Object} bid - The bid object + * @param {number} bidderIndex - The bidder index + * @param {Object} adUnit - The ad unit object + * @param {string} aliasActualBidder - The bidder Alias + * @returns {Object} - The modified Sirdata data + */ +export function applySdaGetSpecificData(data, sirdataData, biddersParamsExist, reqBids, bid, bidderIndex, adUnit, aliasActualBidder) { + // Apply custom function or return Bidder Specific Data if publisher is ok + if (bidderIndex && params.bidders[bidderIndex]?.customFunction && typeof (params.bidders[bidderIndex]?.customFunction) === 'function') { + return loadCustomFunction(params.bidders[bidderIndex].customFunction, adUnit, sirdataData, data, bid); + } + + // Only share Publisher SDA data if whitelisted + if (!biddersParamsExist || bidderIndex) { // SDA Publisher - let sirdataDataForSDA = getSegAndCatsArray(data, minScore, moduleConfig.params.partnerId.toString()); + let sirdataDataForSDA = getSegAndCatsArray(data, params.contextualMinRelevancyScore, params.partnerId.toString()); pushToOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, sirdataDataForSDA, data.segtaxid, data.cattaxid); } - // always share SDA for curation - let curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); - if (curationId) { - // seller defined audience & bidder specific data - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - // Get Bidder Specific Data - let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, curationId.toString()); - pushToOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, curationData, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + // Always share SDA for curation + if (!isEmpty(data.shared_taxonomy)) { + let curationId = (bidderIndex && params.bidders[bidderIndex]?.curationId) || biddersId[aliasActualBidder]; + if (curationId && data.shared_taxonomy[curationId]) { + // Seller defined audience & bidder specific data + let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], params.contextualMinRelevancyScore, curationId.toString()); + if (!isEmpty(curationData)) { + pushToOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, curationData, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + mergeDeep(sirdataData, curationData); + } } } - // Apply custom function or return Bidder Specific Data if publisher is ok - if (!biddersParamsExist || indexFound) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - return loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataData, data, bid); - } else { - return sirdataData; - } - } + return sirdataData; } -export function addSegmentData(reqBids, data, moduleConfig, onDone) { - const adUnits = (reqBids && reqBids.adUnits) || getGlobal().adUnits; - if (!adUnits) { - onDone(); - return; - } - - moduleConfig = moduleConfig || {}; - moduleConfig.params = moduleConfig.params || {}; - const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; - let sirdataData = getSegAndCatsArray(data, globalMinScore, null); +/** + * Adds segment data to the request bids object and processes the data + * @param {Object} reqBids - The request bids object + * @param {Object} data - The data object + * @param {Array} adUnits - The ad units array + * @param {function} onDone - The callback function to be called upon completion + * @returns {Array} - The ad units array + */ +export function addSegmentData(reqBids, data, adUnits, onDone) { + logInfo(LOG_PREFIX, 'Dispatch Segments And Categories'); + const minScore = params.contextualMinRelevancyScore || 30; + let sirdataData = getSegAndCatsArray(data, minScore, ''); - const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders && moduleConfig.params.bidders.length > 0)); + const biddersParamsExist = params.bidders.length > 0; // Global ortb2 SDA - if (data.global_taxonomy && !isEmpty(data.global_taxonomy)) { - let globalData = {'segments': [], 'categories': [], 'categories_score': []}; + if (!isEmpty(data.global_taxonomy)) { for (let i in data.global_taxonomy) { + let globalData; if (!isEmpty(data.global_taxonomy[i])) { - globalData = getSegAndCatsArray(data.global_taxonomy[i], globalMinScore, null); - pushToOrtb2(reqBids.ortb2Fragments?.global, null, globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + globalData = getSegAndCatsArray(data.global_taxonomy[i], params.contextualMinRelevancyScore, ''); + if (!isEmpty(globalData)) { + pushToOrtb2(reqBids.ortb2Fragments?.global, '', globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + } } } } // Google targeting - if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { + if (typeof window.googletag !== 'undefined' && params.setGptKeyValues) { try { - let gptCurationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : (partnerIds['sdRtdForGpt'] ? partnerIds['sdRtdForGpt'] : null)); - let sirdataMergedList = []; - if (gptCurationId && data.shared_taxonomy && data.shared_taxonomy[gptCurationId]) { - let gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], globalMinScore, null); - sirdataMergedList = sirdataMergedList.concat(gamCurationData.segments).concat(gamCurationData.categories); - } else { - sirdataMergedList = sirdataData.segments.concat(sirdataData.categories); + const gptCurationId = params.gptCurationId || biddersId.sdRtdForGpt; + let sirdataMergedList = [...sirdataData.segments, ...sirdataData.categories]; + + if (gptCurationId && data.shared_taxonomy?.[gptCurationId]) { + const gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], params.contextualMinRelevancyScore, ''); + sirdataMergedList = [...sirdataMergedList, ...gamCurationData.segments, ...gamCurationData.categories]; } - window.googletag.cmd.push(function() { - window.googletag.pubads().getSlots().forEach(function (n) { - if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { - n.setTargeting('sd_rtd', sirdataMergedList); + + window.googletag.cmd.push(() => { + window.googletag.pubads().getSlots().forEach(slot => { + if (typeof slot.setTargeting !== 'undefined' && sirdataMergedList.length > 0) { + slot.setTargeting('sd_rtd', sirdataMergedList); } }); }); } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } } - // Bid targeting level for FPD non-generic biders - let bidderIndex = ''; - let indexFound = false; - adUnits.forEach(adUnit => { - adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { - bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function (i) { - return i.bidder === bid.bidder; - }) : false); - indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); + return adUnit.bids?.forEach(bid => { + const bidderIndex = findIndex(params.bidders, function (i) { return i.bidder === bid.bidder; }); try { - let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore); - switch (bid.bidder) { - case 'appnexus': - case 'appnexusAst': - case 'brealtime': - case 'emetriq': - case 'emxdigital': - case 'pagescience': - case 'gourmetads': - case 'matomy': - case 'featureforward': - case 'oftmedia': - case 'districtm': - case 'adasta': - case 'beintoo': - case 'gravity': - case 'msq_classic': - case 'msq_max': - case '366_apx': - sirdataData = applySdaGetSpecificData(data, sirdataData, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); - if (sirdataData.segments && sirdataData.segments.length > 0) { - setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.keywords', 'sd_rtd=' + sirdataData.segments.join(',sd_rtd=')); - } - if (sirdataData.categories && sirdataData.categories.length > 0) { - setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'site.content.keywords', 'sd_rtd=' + sirdataData.categories.join(',sd_rtd=')); - } - break; - - default: - if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { - applySdaGetSpecificData(data, sirdataData, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); - } + const aliasActualBidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + if (aliasActualBidder === 'appnexus') { + let xandrData = applySdaGetSpecificData(data, sirdataData, biddersParamsExist, reqBids, bid, bidderIndex, adUnit, aliasActualBidder); + // Surprisingly, to date Xandr doesn't support SDA, we need to set specific 'keywords' entries + if (xandrData.segments.length > 0) { + setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.keywords', `sd_rtd=${xandrData.segments.join(',sd_rtd=')}`); + } + if (xandrData.categories.length > 0) { + setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'site.content.keywords', `sd_rtd=${xandrData.categories.join(',sd_rtd=')}`); + } + } else { + applySdaGetSpecificData(data, sirdataData, biddersParamsExist, reqBids, bid, bidderIndex, adUnit, aliasActualBidder); } } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } - }) + }); }); + + // Trigger onDone onDone(); + + // Postprocessing: should we send async content to categorize content and/or store Sirdata ID (stored in 3PC) within local scope ? + if (params.sirdataDomain === 'sddan.com') { // Means consent has been given for device and content access + if (!isEmpty(data.sddan_id) && params.cookieAccessGranted) { // Device access allowed by publisher and cookies are supported + setUidInStorage(data.sddan_id); // Save Sirdata user ID + } + if (!params.avoidPostContent && params.actualUrl && !inIframe() && !isEmpty(data.post_content_token)) { + onDocumentReady(() => postContentForSemanticAnalysis(data.post_content_token, params.actualUrl)); + } + } return adUnits; } -export function init(config) { - logInfo(LOG_PREFIX, config); +/** + * Initializes the module with the given configuration + * @param {Object} moduleConfig - The module configuration + * @returns {boolean} - True if the initialization was successful + */ +export function init(moduleConfig) { + logInfo(LOG_PREFIX, moduleConfig); + if (typeof (moduleConfig.params) !== 'object' || !moduleConfig.params.key) return false; + if (typeof (moduleConfig.params.authorizedEids) !== 'object' || !Array.isArray(moduleConfig.params.authorizedEids)) { + delete (moduleConfig.params.authorizedEids); // must be array of strings + } else { + // we need it if the publishers uses user Id module name instead of key or source + const resultSet = new Set( + moduleConfig.params.authorizedEids.map(item => { + const formattedItem = item.toLowerCase().replace(/\s+/g, '_'); // Normalize + return eidsProvidersMap[formattedItem] || formattedItem; + }) + ); + moduleConfig.params.authorizedEids = Array.from(resultSet); + } + if (typeof (moduleConfig.params.bidders) !== 'object' || !Array.isArray(moduleConfig.params.bidders)) delete (moduleConfig.params.bidders); // must be array of objects + delete (moduleConfig.params.sirdataDomain); // delete cookieless domain if specified => shouldn't be overridden by publisher + params = Object.assign({}, params, moduleConfig.params); return true; } +/** + * The Sirdata submodule definition + * @type {Object} + */ export const sirdataSubmodule = { name: SUBMODULE_NAME, + gvlid: GVLID, init: init, getBidRequestData: getSegmentsAndCategories }; +// Register the Sirdata submodule with the real time data module submodule(MODULE_NAME, sirdataSubmodule); diff --git a/modules/sirdataRtdProvider.md b/modules/sirdataRtdProvider.md index f67e34db43a..9e712e5fbd6 100644 --- a/modules/sirdataRtdProvider.md +++ b/modules/sirdataRtdProvider.md @@ -1,30 +1,28 @@ -# Sirdata Real-Time Data Submodule +# Sirdata RTD/SDA Module -Module Name: Sirdata Rtd Provider +Module Name: Sirdata Real-time SDA Module Module Type: Rtd Provider Maintainer: bob@sirdata.com # Description -Sirdata provides a disruptive API that allows its partners to leverage its -cutting-edge contextualization technology and its audience segments based on -cookies and consent or without cookies nor consent! +Sirdata provides a disruptive API that allows publishers and SDA compliant SSPs/DSPs to leverage its cutting-edge contextualization technology and its audience segments! -User-based segments and page-level automatic contextual categories will be -attached to bid request objects sent to different SSPs in order to optimize -targeting. +User-based segments and page-level automatic contextual categories will be attached to bid request objects sent to different bidders in order to optimize targeting. -Automatic integration with Google Ad Manager and major bidders like Xandr/Appnexus, -Smartadserver, Index Exchange, Proxistore, Magnite/Rubicon or Triplelift ! +Automatic or custom integration with Google Ad Manager and major bidders like Xandr's, Equativ's, Index Exchange's, Proxistore's, Magnite's or Triplelift's ! -User's country and choice management are included in the module, so it's 100% -compliant with local and regional laws like GDPR and CCPA/CPRA. +User's country and choice management are included in the module, so it's 100% compliant with local and regional laws like GDPR and CCPA/CPRA. ORTB2 compliant and FPD support for Prebid versions < 4.29 -Contact bob@sirdata.com for information. +Fully supports Seller Defined Audience ! Please find the full SDA taxonomy ids list here. -### Publisher Usage +Please contact for more information. + +## Publisher Usage + +### Configure Prebid.js Compile the Sirdata RTD module into your Prebid build: @@ -32,15 +30,39 @@ Compile the Sirdata RTD module into your Prebid build: Add the Sirdata RTD provider to your Prebid config. -Segments ids (user-centric) and category ids (page-centric) will be provided -salted and hashed : you can use them with a dedicated and private matching table. -Should you want to allow a SSP or a partner to curate your media and operate -cross-publishers campaigns with our data, please ask Sirdata (bob@sirdata.com) to -open it for you account. +`actualUrl` MUST be set with actual location of parent page if prebid.js is loaded in an iframe (e.g. hosted). It can be left blank ('') or removed otherwise. + +`partnerId` and `key` should be provided by your partnering SSP or get one and your dedicated taxonomy from Sirdata (). Segments ids (user-centric) and category ids (page-centric) will be provided salted and hashed : you can use them with a dedicated and private matching table. + +Should you want to allow any SSP or a partner to curate your media and operate cross-publishers campaigns with our data, please ask Sirdata () to whitelist him it in your account. + +#### Typical configuration +```javascript +pbjs.setConfig({ + // ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + } + } + ] + } + // ... +}); ``` -pbjs.setConfig( - ... + +#### Advanced configuration + +```javascript +pbjs.setConfig({ + // ... realTimeData: { auctionDelay: 1000, dataProviders: [ @@ -48,85 +70,114 @@ pbjs.setConfig( name: "SirdataRTDModule", waitForIt: true, params: { - partnerId: 1, + partnerId: 1, key: 1, - setGptKeyValues: true, - contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale) - actualUrl: actual_url, //top location url, for contextual categories - bidders: [{ - bidder: 'appnexus', - adUnitCodes: ['adUnit-1','adUnit-2'], - customFunction: overrideAppnexus, - curationId: '111', - },{ - bidder: 'ix', - sizeLimit: 1200 //specific to Index Exchange, - contextualMinRelevancyScore: 50, //Min score to filter contextual category for curation in the bidder (0-100 scale) - }] + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale) + actualUrl: '' //top location url, for contextual categories } } ] } - ... -} + // ... +}); ``` ### Parameter Descriptions for the Sirdata Configuration Section -| Name |Type | Description | Notes | -| :------------ | :------------ | :------------ |:------------ | -| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' | -| waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | -| params | Object | | Optional | -| params.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | -| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. | -| params.setGptKeyValues | Boolean | This parameter Sirdata to set Targeting for GPT/GAM | Optional. Defaults to true. | -| params.contextualMinRelevancyScore | Integer | Min score to keep filter category in the bidders (0-100 scale). Optional. Defaults to 30. | -| params.bidders | Object | Dictionary of bidders you would like to supply Sirdata data for. | Optional. In case no bidder is specified Sirdata will atend to ad data custom and ortb2 to all bidders, adUnits & Globalconfig | +| Name | Type | Description | Notes | +|:-----------------------------------|:--------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------| +| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Default to false but recommended to true | +| params | Object | Settings | Optional | +| params.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and request your Id at | Mandatory. Default 1 | +| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and request your key at | Mandatory. Default 1 | +| params.setGptKeyValues | Boolean | Sets Targeting for GPT/GAM | Optional. Default to true | +| params.authorizedEids | Array of String | List of authorised Eids for graph. Set [] to prevent all Eids usage | Optional. Default to : ID5, pubProvidedId and sharedId | +| params.avoidPostContent | Boolean | Block contextual data POST from user's device (a crawler is use instead) | Optional. Default to false, and setting it to true results in your content downloaded by Sirdata crawler | +| params.contextualMinRelevancyScore | Integer | Min relevancy score to filter categories sent to the bidders (0-100 scale). | Optional. Defaults to 30. | +| params.bidders | Array of Object | Bidders you want to supply your own data to (works only with your private data bought to Sirdata) | Optional | Bidders can receive common setting : -| Name |Type | Description | Notes | -| :------------ | :------------ | :------------ |:------------ | -| bidder | String | Bidder name | Mandatory if params.bidders are specified | -| adUnitCodes | Array of String | Use if you want to limit data injection to specified adUnits for the bidder | Optional. Default is false and data shared with the bidder isn't filtered | -| customFunction | Function | Use it to override the way data is shared with a bidder | Optional. Default is false | -| curationId | String | Specify the curation ID of the bidder. Provided by Sirdata, request it at bob@sirdata.com | Optional. Default curation ids are specified for main bidders | -| contextualMinRelevancyScore | Integer | Min score to filter contextual categories for curation in the bidder (0-100 scale). Optional. Defaults to 30 or global params.contextualMinRelevancyScore if exits. | -| sizeLimit | Integer | used only for bidder 'ix' to limit the size of the get parameter in Index Exchange ad call | Optional. Default is 1000 | +| Name | Type | Description | Notes | +|:---------------|:----------------|:-----------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------| +| bidder | String | Bidder name | Mandatory if params.bidders are specified | +| adUnitCodes | Array of String | Use if you want to limit data injection to specified adUnits for the bidder | Optional. Default is false and data shared with the bidder isn't filtered | +| customFunction | Function | Use it to override the way data is shared with a bidder | Optional. Default is false | +| curationId | String | Specify the curation ID of the bidder. Provided by Sirdata, request it at | Optional. Default curation ids are specified for main bidders | ### Overriding data sharing function -As indicated above, it is possible to provide your own bid augmentation -functions. This is useful if you know a bid adapter's API supports segment -fields which aren't specifically being added to request objects in the Prebid -bid adapter. -Please see the following example, which provides a function to modify bids for -a bid adapter called ix and overrides the appnexus. +As indicated above, it is possible to provide your own bid augmentation functions. This is useful if you know a bid adapter's API supports segment fields which aren't specifically being added to request objects in the Prebid bid adapter. +Please see the following example, which provides a function to modify bids for a bid adapter called ix and overrides the appnexus. data Object format for usage in this kind of function : + +```json { - "segments":[111111,222222], - "contextual_categories":{"333333":100}, - "shared_taxonomy":{ - "27446":{ //CurationId - "segments":[444444,555555], - "contextual_categories":{"666666":100} - } - } + "segments": [ + 111111, + 222222 + ], + "segtaxid": null, + "cattaxid": null, + "contextual_categories": { + "333333": 100 + }, + "shared_taxonomy": { + "27440": { + "segments": [ + 444444, + 555555 + ], + "segtaxid": 552, + "cattaxid": 553, + "contextual_categories": { + "666666": 100 + } + } + }, + "global_taxonomy": { + "9998": { + "segments": [ + 123, + 234 + ], + "segtaxid": 4, + "cattaxid": 7, + "contextual_categories": { + "345": 100, + "456": 100 + } + }, + "9999": { + "segments": [ + 12345, + 23456 + ], + "segtaxid": 550, + "cattaxid": 551, + "contextual_categories": { + "34567": 100, + "45678": 100 + } + } + } } - ``` + +```javascript function overrideAppnexus (adUnit, segmentsArray, dataObject, bid) { - for (var i = 0; i < segmentsArray.length; i++) { + for (var i = 0; i < segmentsArray.length; i++) { if (segmentsArray[i]) { bid.params.user.segments.push(segmentsArray[i]); } } } -pbjs.setConfig( - ... +pbjs.setConfig({ + // ... realTimeData: { auctionDelay: 1000, dataProviders: [ @@ -134,18 +185,17 @@ pbjs.setConfig( name: "SirdataRTDModule", waitForIt: true, params: { - partnerId: 1, + partnerId: 1, key: 1, - setGptKeyValues: true, - contextualMinRelevancyScore: 50, //Min score to keep contextual category in the bidders (0-100 scale) - actualUrl: actual_url, //top location url, for contextual categories + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to keep contextual category in the bidders (0-100 scale) + actualUrl: actual_url, //top location url, for contextual categories bidders: [{ bidder: 'appnexus', customFunction: overrideAppnexus, curationId: '111' },{ bidder: 'ix', - sizeLimit: 1200, //specific to Index Exchange customFunction: function(adUnit, segmentsArray, dataObject, bid) { bid.params.contextual.push(dataObject.contextual_categories); }, @@ -153,17 +203,19 @@ pbjs.setConfig( } } ] - } + }, ... -} +}); ``` ### Testing To view an example of available segments returned by Sirdata's backends: -`gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter` +```bash +gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter +``` and then point your browser at: -`http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html` \ No newline at end of file +[http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html] diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js index fbb5967bc20..9f6bb30e0b0 100644 --- a/test/spec/modules/sirdataRtdProvider_spec.js +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -1,21 +1,123 @@ -import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule, setOrtb2} from 'modules/sirdataRtdProvider.js'; +import { + addSegmentData, + getSegmentsAndCategories, + getUidFromStorage, + loadCustomFunction, + mergeEuidsArrays, + onDataDeletionRequest, + onDocumentReady, + postContentForSemanticAnalysis, + removePII, + sanitizeContent, + setOrtb2, + setUidInStorage, + sirdataSubmodule +} from 'modules/sirdataRtdProvider.js'; +import {expect} from 'chai'; +import {deepSetValue} from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('sirdataRtdProvider', function () { - describe('sirdataSubmodule', function () { + describe('sirdata Submodule init', function () { it('exists', function () { expect(sirdataSubmodule.init).to.be.a('function'); }); it('successfully instantiates', function () { - expect(sirdataSubmodule.init()).to.equal(true); + const moduleConfig = { + params: { + partnerId: 1, + key: 1, + } + }; + expect(sirdataSubmodule.init(moduleConfig)).to.equal(true); }); it('has the correct module name', function () { expect(sirdataSubmodule.name).to.equal('SirdataRTDModule'); }); }); + describe('Sanitize content', function () { + it('removes PII from content', function () { + let doc = document.implementation.createHTMLDocument(''); + let div = doc.createElement('div'); + div.className = 'test'; + div.setAttribute('test', 'test'); + div.textContent = 'My email is test@test.com, My bank account number is 123456789012, my SSN is 123-45-6789, and my credit card number is 1234 5678 9101 1121.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + let div2 = doc.createElement('div'); + let div3 = doc.createElement('div'); + div3.innerText = 'hello'; + div2.appendChild(div3); + div.appendChild(div2); + doc.body.appendChild(div); + const cleanedDom = removePII(doc.documentElement.innerHTML); + const sanitizedDom = sanitizeContent(doc); + expect(cleanedDom).to.equal('
My email is , My bank account number is , my SSN is , and my credit card number is .Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
hello
'); + expect(sanitizedDom.documentElement.innerHTML).to.equal('
My email is , My bank account number is , my SSN is , and my credit card number is .Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
hello
'); + }); + }); + + describe('setUidInStorage', function () { + it('sets Id in Storage', function () { + setUidInStorage('123456789'); + let val = getUidFromStorage(); + expect(val).to.deep.equal([{source: 'sddan.com', uids: [{id: '123456789', atype: 1}]}]); + }); + }); + + describe('mergeEuidsArrays', function () { + it('merges Euids Arrays', function () { + const object1 = [{source: 'sddan.com', uids: [{id: '123456789', atype: 1}]}]; + const object2 = [{source: 'sddan.com', uids: [{id: '987654321', atype: 1}]}]; + const object3 = mergeEuidsArrays(object1, object2); + expect(object3).to.deep.equal([{source: 'sddan.com', uids: [{id: '123456789', atype: 1}, {id: '987654321', atype: 1}]}]); + }); + }); + + describe('onDocumentReady', function () { + it('on Document Ready function execution', function () { + const testString = ''; + const testFunction = function() { return true; }; + let resString; + try { + resString = onDocumentReady(testString); + } catch (e) {} + expect(resString).to.be.false; + let resFunction = onDocumentReady(testFunction); + expect(resFunction).to.be.true; + }); + }); + + describe('postContentForSemanticAnalysis', function () { + it('gets content for analysis', function () { + let res = postContentForSemanticAnalysis('1223456', 'https://www.sirdata.com/'); + let resEmpty = postContentForSemanticAnalysis('1223456', ''); + expect(res).to.be.true; + expect(resEmpty).to.be.false; + }); + }); + + describe('loadCustomFunction', function () { + it('load function', function () { + const res = loadCustomFunction(function(...args) { return true; }, {}, {}, {}, {}); + expect(res).to.be.true; + }); + }); + + describe('onDataDeletionRequest', function () { + it('destroy id', function () { + const moduleConfig = { + params: { + partnerId: 1, + key: 1, + } + }; + const res = onDataDeletionRequest(moduleConfig); + expect(res).to.be.true; + }); + }); + describe('Add Segment Data', function () { it('adds segment data', function () { const firstConfig = { @@ -25,9 +127,12 @@ describe('sirdataRtdProvider', function () { setGptKeyValues: true, gptCurationId: 27449, contextualMinRelevancyScore: 50, + actualUrl: 'https://www.sirdata.com/', + cookieAccessGranted: true, bidders: [] } }; + sirdataSubmodule.init(firstConfig); let adUnits = [ { @@ -68,13 +173,12 @@ describe('sirdataRtdProvider', function () { 'segtaxid': 4, 'cattaxid': 7, 'contextual_categories': {'345': 100, '456': 100} - } + }, + 'sddan_id': '123456789', + 'post_content_token': '987654321' } - }; - - addSegmentData(firstReqBidsConfigObj, firstData, firstConfig, () => { - }); - + } + addSegmentData(firstReqBidsConfigObj, firstData, adUnits, function() { return true; }); expect(firstReqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); @@ -109,6 +213,7 @@ describe('sirdataRtdProvider', function () { }] } }; + sirdataSubmodule.init(config); let reqBidsConfigObj = { adUnits: [{ @@ -197,11 +302,13 @@ describe('sirdataRtdProvider', function () { 'cattaxid': 7, 'contextual_categories': {'345': 100, '456': 100} } - } + }, + 'sddan_id': '123456789', + 'post_content_token': '987654321' }; getSegmentsAndCategories(reqBidsConfigObj, () => { - }, config, {}); + }, {}, {}); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(data)); @@ -228,16 +335,6 @@ describe('sirdataRtdProvider', function () { describe('Set ortb2 for bidder', function () { it('set ortb2 for a givent bidder', function () { - const config = { - params: { - setGptKeyValues: false, - contextualMinRelevancyScore: 50, - bidders: [{ - bidder: 'appnexus', - }] - } - }; - let reqBidsConfigObj = { adUnits: [{ bids: [{ @@ -252,22 +349,6 @@ describe('sirdataRtdProvider', function () { } }; - let data = { - 'segments': [111111, 222222], - 'segtaxid': null, - 'cattaxid': null, - 'contextual_categories': {'333333': 100}, - 'shared_taxonomy': { - '27440': { - 'segments': [444444, 555555], - 'segtaxid': null, - 'cattaxid': null, - 'contextual_categories': {'666666': 100} - } - }, - 'global_taxonomy': {} - }; - window.googletag = window.googletag || {}; window.googletag.cmd = window.googletag.cmd || []; From 0f91affebac045efd2470fafb76a0f138ea15f20 Mon Sep 17 00:00:00 2001 From: Aymeric Le Corre Date: Wed, 29 May 2024 00:13:02 +0200 Subject: [PATCH 0102/1097] Lucead Bid Adapter: Update (#11488) * Lucead Bid Adapter: Support Single Request Architecture mode + enhanced reporting + updated PAAPI auction config * PB9 compliance * Added TCF Global Vendor List ID * Add GVLID to Lucead RTD Provider * Add domain in impression tracking * remove RTD related files * remove useless code * remove lucead from adloader --- modules/luceadBidAdapter.js | 167 ++++++++++++--------- modules/luceadBidAdapter.md | 54 ++++--- src/adloader.js | 1 - test/spec/modules/luceadBidAdapter_spec.js | 97 ++++++------ 4 files changed, 186 insertions(+), 133 deletions(-) mode change 100644 => 100755 modules/luceadBidAdapter.js mode change 100644 => 100755 modules/luceadBidAdapter.md mode change 100644 => 100755 test/spec/modules/luceadBidAdapter_spec.js diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js old mode 100644 new mode 100755 index ab7f96c4e60..b95dfc08732 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -1,40 +1,42 @@ +/** + * @module modules/luceadBidAdapter + */ + import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -import {loadExternalScript} from '../src/adloader.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getUniqueIdentifierStr, logInfo, deepSetValue} from '../src/utils.js'; +import {getUniqueIdentifierStr, deepSetValue, logInfo} from '../src/utils.js'; import {fetch} from '../src/ajax.js'; +const gvlid = 1309; const bidderCode = 'lucead'; -const bidderName = 'Lucead'; -let baseUrl = 'https://lucead.com'; -let staticUrl = 'https://s.lucead.com'; -let companionUrl = 'https://cdn.jsdelivr.net/gh/lucead/prebid-js-external-js-lucead@master/dist/prod.min.js'; -let endpointUrl = 'https://prebid.lucead.com/go'; const defaultCurrency = 'EUR'; const defaultTtl = 500; const aliases = ['adliveplus']; +const defaultRegion = 'eu'; +const domain = 'lucead.com' +let baseUrl = `https://${domain}`; +let staticUrl = `https://s.${domain}`; +let endpointUrl = baseUrl; function isDevEnv() { - return location.hash.includes('prebid-dev') || location.href.startsWith('https://ayads.io/test'); + return location.hash.includes('prebid-dev'); } function isBidRequestValid(bidRequest) { return !!bidRequest?.params?.placementId; } -export function log(msg, obj) { - logInfo(`${bidderName} - ${msg}`, obj); -} - function buildRequests(bidRequests, bidderRequest) { + const region = bidRequests[0]?.params?.region || defaultRegion; + endpointUrl = `https://${region}.${domain}`; + if (isDevEnv()) { baseUrl = location.origin; staticUrl = baseUrl; - companionUrl = `${staticUrl}/dist/prebid-companion.js`; - endpointUrl = `${baseUrl}/go`; + endpointUrl = `${baseUrl}`; } - log('buildRequests', { + logInfo('buildRequests', { bidRequests, bidderRequest, }); @@ -50,107 +52,134 @@ function buildRequests(bidRequests, bidderRequest) { getUniqueIdentifierStr, ortbConverter, deepSetValue, + is_sra: true, + region, }; - loadExternalScript(companionUrl, bidderCode, () => window.ayads_prebid && window.ayads_prebid(companionData)); + window.lucead_prebid_data = companionData; + const fn = window.lucead_prebid; + + if (fn && typeof fn === 'function') { + fn(companionData); + } - return bidRequests.map(bidRequest => ({ + return { method: 'POST', - url: `${endpointUrl}/prebid/sub`, + url: `${endpointUrl}/go/prebid/sra`, data: JSON.stringify({ request_id: bidderRequest.bidderRequestId, domain: location.hostname, - bid_id: bidRequest.bidId, - sizes: bidRequest.sizes, - media_types: bidRequest.mediaTypes, - fledge_enabled: bidderRequest.fledgeEnabled, - enable_contextual: bidRequest?.params?.enableContextual !== false, - enable_pa: bidRequest?.params?.enablePA !== false, - params: bidRequest.params, + bid_requests: bidRequests.map(bidRequest => { + return { + bid_id: bidRequest.bidId, + sizes: bidRequest.sizes, + media_types: bidRequest.mediaTypes, + placement_id: bidRequest.params.placementId, + schain: bidRequest.schain, + }; + }), }), options: { contentType: 'text/plain', withCredentials: false }, - })); + }; } function interpretResponse(serverResponse, bidRequest) { // @see required fields https://docs.prebid.org/dev-docs/bidder-adaptor.html - const response = serverResponse.body; - const bidRequestData = JSON.parse(bidRequest.data); - - const bids = response.enable_contextual !== false ? [{ - requestId: response?.bid_id || '1', // bid request id, the bid id - cpm: response?.cpm || 0, - width: (response?.size && response?.size?.width) || 300, - height: (response?.size && response?.size?.height) || 250, - currency: response?.currency || defaultCurrency, - ttl: response?.ttl || defaultTtl, - creativeId: response.ssp ? `ssp:${response.ssp}` : (response?.ad_id || '0'), - netRevenue: response?.netRevenue || true, - ad: response?.ad || '', + const response = serverResponse?.body; + const bidRequestData = JSON.parse(bidRequest?.data); + + const bids = (response?.bids || []).map(bid => ({ + requestId: bid?.bid_id || '1', // bid request id, the bid id + cpm: bid?.cpm || 0, + width: (bid?.size && bid?.size?.width) || 300, + height: (bid?.size && bid?.size?.height) || 250, + currency: bid?.currency || defaultCurrency, + ttl: bid?.ttl || defaultTtl, + creativeId: bid?.ssp ? `ssp:${bid.ssp}` : `${bid?.ad_id || 0}:${bid?.ig_id || 0}`, + netRevenue: bid?.net_revenue || true, + ad: bid?.ad || '', meta: { - advertiserDomains: response?.advertiserDomains || [], + advertiserDomains: bid?.advertiser_domains || [], }, - }] : null; - - log('interpretResponse', {serverResponse, bidRequest, bidRequestData, bids}); - - if (response.enable_pa === false) { return bids; } - - const fledgeAuctionConfig = { - seller: baseUrl, - decisionLogicUrl: `${baseUrl}/js/ssp.js`, - interestGroupBuyers: [baseUrl], - perBuyerSignals: {}, - auctionSignals: { - size: bidRequestData.sizes ? {width: bidRequestData?.sizes[0][0] || 300, height: bidRequestData?.sizes[0][1] || 250} : null, - }, - }; + })); - const fledgeAuctionConfigs = [{bidId: response.bid_id, config: fledgeAuctionConfig}]; + logInfo('interpretResponse', {serverResponse, bidRequest, bidRequestData, bids}); + + if (response?.enable_pa === false) { return bids; } + + const fledgeAuctionConfigs = (response.bids || []).map(bid => ({ + bidId: bid?.bid_id, + config: { + seller: baseUrl, + decisionLogicUrl: `${baseUrl}/js/ssp.js`, + interestGroupBuyers: [baseUrl], + requestedSize: bid?.size, + auctionSignals: { + size: bid?.size, + }, + perBuyerSignals: { + [baseUrl]: { + prebid_paapi: true, + prebid_bid_id: bid?.bid_id, + prebid_request_id: bidRequestData.request_id, + placement_id: bid.placement_id, + // floor, + is_sra: true, + endpoint_url: endpointUrl, + }, + } + } + })); return {bids, fledgeAuctionConfigs}; } -function report(type = 'impression', data = {}) { +function report(type, data) { // noinspection JSCheckFunctionSignatures - return fetch(`${endpointUrl}/report/${type}`, { - body: JSON.stringify(data), + return fetch(`${endpointUrl}/go/report/${type}`, { + body: JSON.stringify({ + ...data, + domain: location.hostname, + }), method: 'POST', - contentType: 'text/plain' + contentType: 'text/plain', }); } function onBidWon(bid) { - log('Bid won', bid); + logInfo('Bid won', bid); let data = { bid_id: bid?.bidId, - placement_id: bid?.params ? bid?.params[0]?.placementId : 0, + placement_id: bid.params ? (bid?.params[0]?.placementId || '0') : '0', spent: bid?.cpm, currency: bid?.currency, }; - if (bid.creativeId) { - if (bid.creativeId.toString().startsWith('ssp:')) { - data.ssp = bid.creativeId.split(':')[1]; + if (bid?.creativeId) { + const parts = bid.creativeId.toString().split(':'); + + if (parts[0] === 'ssp') { + data.ssp = parts[1]; } else { - data.ad_id = bid.creativeId; + data.ad_id = parts[0] + data.ig_id = parts[1] } } - return report(`impression`, data); + return report('impression', data); } function onTimeout(timeoutData) { - log('Timeout from adapter', timeoutData); + logInfo('Timeout from adapter', timeoutData); } export const spec = { code: bidderCode, - // gvlid: BIDDER_GVLID, + gvlid, aliases, isBidRequestValid, buildRequests, diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md old mode 100644 new mode 100755 index 953c911cd2b..41e3730897a --- a/modules/luceadBidAdapter.md +++ b/modules/luceadBidAdapter.md @@ -1,29 +1,41 @@ -# Overview +# Lucead Bid Adapter -Module Name: Lucead Bidder Adapter +- Module Name: Lucead Bidder Adapter +- Module Type: Bidder Adapter +- Maintainer: prebid@lucead.com -Module Type: Bidder Adapter +## Description -Maintainer: prebid@lucead.com +Module that connects to Lucead demand source. -# Description +## Adapter configuration -Module that connects to Lucead demand source to fetch bids. +## Ad units parameters -# Test Parameters +### Type definition + +```typescript +type Params = { + placementId: string; + region?: 'eu' | 'us' | 'ap'; +}; ``` -const adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'lucead', - params: { - placementId: '2', - } - } - ] - } - ]; + +### Example code +```javascript +const adUnits=[ + { + code:'test-div', + sizes:[[300,250]], + bids:[ + { + bidder: 'lucead', + params:{ + placementId: '1', + region: 'us', // optional: 'eu', 'us', 'ap' + } + } + ] + } +]; ``` diff --git a/src/adloader.js b/src/adloader.js index 30693560133..b746c59a1cc 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -33,7 +33,6 @@ const _approvedLoadExternalJSList = [ 'dynamicAdBoost', 'contxtful', 'id5', - 'lucead', '51Degrees', ]; diff --git a/test/spec/modules/luceadBidAdapter_spec.js b/test/spec/modules/luceadBidAdapter_spec.js old mode 100644 new mode 100755 index 72bc7cc2d6e..6f61071b653 --- a/test/spec/modules/luceadBidAdapter_spec.js +++ b/test/spec/modules/luceadBidAdapter_spec.js @@ -28,6 +28,7 @@ describe('Lucead Adapter', () => { bidder: 'lucead', params: { placementId: '1', + region: 'eu', }, }; }); @@ -39,7 +40,10 @@ describe('Lucead Adapter', () => { describe('onBidWon', function () { let sandbox; - const bid = { foo: 'bar', creativeId: 'ssp:improve' }; + const bids = [ + { foo: 'bar', creativeId: 'ssp:improve' }, + { foo: 'bar', creativeId: '123:456' }, + ]; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -47,8 +51,11 @@ describe('Lucead Adapter', () => { it('should trigger impression pixel', function () { sandbox.spy(ajax, 'fetch'); - spec.onBidWon(bid); - expect(ajax.fetch.args[0][0]).to.match(/report\/impression$/); + + for (const bid of bids) { + spec.onBidWon(bid); + expect(ajax?.fetch?.args[0][0]).to.match(/report\/impression$/); + } }); afterEach(function () { @@ -77,49 +84,62 @@ describe('Lucead Adapter', () => { it('should have a post method', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].method).to.equal('POST'); + expect(request.method).to.equal('POST'); }); it('should contains a request id equals to the bid id', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(JSON.parse(request[0].data).bid_id).to.equal(bidRequests[0].bidId); + expect(JSON.parse(request.data).bid_requests[0].bid_id).to.equal(bidRequests[0].bidId); }); - it('should have an url that contains sub keyword', function () { + it('should have an url that contains sra keyword', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].url).to.match(/sub/); + expect(request.url).to.contain('/prebid/sra'); }); }); describe('interpretResponse', function () { - const serverResponse = { - body: { - 'bid_id': '2daf899fbe4c52', - 'request_id': '13aaa3df18bfe4', - 'ad': 'Ad', - 'ad_id': '3890677904', - 'cpm': 3.02, - 'currency': 'USD', - 'time': 1707257712095, - 'size': {'width': 300, 'height': 250}, - } + const serverResponseBody = { + 'request_id': '17548f887fb722', + 'bids': [ + { + 'bid_id': '2d663fdd390b49', + 'ad': '\u003chtml lang="en"\u003e\u003cbody style="margin:0;background-color:#FFF"\u003e\u003ciframe src="urn:uuid:fb81a0f9-b83a-4f27-8676-26760d090f1c" style="width:300px;height:250px;border:none" seamless \u003e\u003c/iframe\u003e\u003c/body\u003e\u003c/html\u003e', + 'size': { + 'width': 300, + 'height': 250 + }, + 'ad_id': '1', + 'ig_id': '1', + 'cpm': 1, + 'currency': 'EUR', + 'time': 0, + 'ssp': '', + 'placement_id': '1', + 'is_pa': true + } + ] }; - const bidRequest = {data: JSON.stringify({ - 'request_id': '13aaa3df18bfe4', - 'domain': '7cdb-2a02-8429-e4a0-1701-bc69-d51c-86e-b279.ngrok-free.app', - 'bid_id': '2daf899fbe4c52', - 'sizes': [[300, 250]], - 'media_types': {'banner': {'sizes': [[300, 250]]}}, - 'fledge_enabled': true, - 'enable_contextual': true, - 'enable_pa': true, - 'params': {'placementId': '1'}, - })}; + const serverResponse = {body: serverResponseBody}; + + const bidRequest = { + data: JSON.stringify({ + 'request_id': '17548f887fb722', + 'domain': 'lucead.com', + 'bid_requests': [{ + 'bid_id': '2d663fdd390b49', + 'sizes': [[300, 250], [300, 150]], + 'media_types': {'banner': {'sizes': [[300, 250], [300, 150]]}}, + 'placement_id': '1' + }], + }), + }; it('should get correct bid response', function () { const result = spec.interpretResponse(serverResponse, bidRequest); + // noinspection JSCheckFunctionSignatures expect(Object.keys(result.bids[0])).to.have.members([ 'requestId', 'cpm', @@ -135,7 +155,7 @@ describe('Lucead Adapter', () => { }); it('should return bid empty response', function () { - const serverResponse = {body: {cpm: 0}}; + const serverResponse = {body: {bids: [{cpm: 0}]}}; const bidRequest = {data: '{}'}; const result = spec.interpretResponse(serverResponse, bidRequest); expect(result.bids[0].ad).to.be.equal(''); @@ -154,18 +174,11 @@ describe('Lucead Adapter', () => { expect(Object.keys(result.bids[0].meta)).to.include.members(['advertiserDomains']); }); - it('should support disabled contextual bids', function () { - const serverResponseWithDisabledContectual = deepClone(serverResponse); - serverResponseWithDisabledContectual.body.enable_contextual = false; - const result = spec.interpretResponse(serverResponseWithDisabledContectual, bidRequest); - expect(result.bids).to.be.null; - }); - - it('should support disabled Protected Audience', function () { - const serverResponseWithEnablePaFalse = deepClone(serverResponse); - serverResponseWithEnablePaFalse.body.enable_pa = false; - const result = spec.interpretResponse(serverResponseWithEnablePaFalse, bidRequest); - expect(result.fledgeAuctionConfigs).to.be.undefined; + it('should support enable_pa = false', function () { + serverResponse.body.enable_pa = false; + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.be.an('array'); + expect(result[0].cpm).to.be.greaterThan(0); }); }); }); From e1b028fd270192f2176e20a77728902a6ad9898b Mon Sep 17 00:00:00 2001 From: nouchy <33549554+nouchy@users.noreply.github.com> Date: Wed, 29 May 2024 14:30:24 +0200 Subject: [PATCH 0103/1097] new lint rule for Prebid 9 fix : use textContent instead of innerText (#11598) --- modules/sirdataRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 0ad3b891c40..9a222e20f9d 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -284,7 +284,7 @@ export function removePII(content) { * @returns {Object} - The sanitized content */ export function sanitizeContent(content) { - if (content && content.documentElement.innerText && content.documentElement.innerText.length > 500) { + if (content && content.documentElement.textContent && content.documentElement.textContent.length > 500) { // Reduce size by removing useless content // Allowed tags const allowedTags = [ From deeeb9e7c116a3c4d74d2c0b7e429c68ff3528aa Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Wed, 29 May 2024 19:09:11 +0530 Subject: [PATCH 0104/1097] update placement logic, deprecated in 9.0 prebid version (#11600) --- modules/insticatorBidAdapter.js | 14 ++++------- .../spec/modules/insticatorBidAdapter_spec.js | 25 +++---------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index bff74f0755b..636c0162a02 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -102,7 +102,7 @@ function buildVideo(bidRequest) { let w = deepAccess(bidRequest, 'mediaTypes.video.w'); let h = deepAccess(bidRequest, 'mediaTypes.video.h'); const mimes = deepAccess(bidRequest, 'mediaTypes.video.mimes'); - const placement = deepAccess(bidRequest, 'mediaTypes.video.placement') || 3; + const placement = deepAccess(bidRequest, 'mediaTypes.video.placement'); const plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt') || undefined; const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); const context = deepAccess(bidRequest, 'mediaTypes.video.context'); @@ -136,6 +136,10 @@ function buildVideo(bidRequest) { } } + if (placement && typeof placement !== 'undefined' && typeof placement === 'number') { + optionalParams['placement'] = placement; + } + if (plcmt) { optionalParams['plcmt'] = plcmt; } @@ -145,7 +149,6 @@ function buildVideo(bidRequest) { } let videoObj = { - placement, mimes, w, h, @@ -595,13 +598,6 @@ function validateVideo(bid) { return false; } - const placement = deepAccess(bid, 'mediaTypes.video.placement'); - - if (typeof placement !== 'undefined' && typeof placement !== 'number') { - logError('insticator: video placement is not a number'); - return false; - } - const plcmt = deepAccess(bid, 'mediaTypes.video.plcmt'); if (typeof plcmt !== 'undefined' && typeof plcmt !== 'number') { diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 489e213b0fa..72ee132f6f6 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -175,25 +175,6 @@ describe('InsticatorBidAdapter', function () { })).to.be.true; }) - it('should return false if video placement is not a number', () => { - expect(spec.isBidRequestValid({ - ...bidRequest, - ...{ - mediaTypes: { - video: { - mimes: [ - 'video/mp4', - 'video/mpeg', - ], - w: 250, - h: 300, - placement: 'NaN', - }, - } - } - })).to.be.false; - }); - it('should return false if video plcmt is not a number', () => { expect(spec.isBidRequestValid({ ...bidRequest, @@ -224,7 +205,7 @@ describe('InsticatorBidAdapter', function () { 'video/mpeg', ], playerSize: [250, 300], - placement: 1, + plcmt: 1, }, } } @@ -293,7 +274,7 @@ describe('InsticatorBidAdapter', function () { 'video/mpeg', ], playerSize: [250, 300], - placement: 1, + plcmt: 1, }, } }, @@ -306,7 +287,7 @@ describe('InsticatorBidAdapter', function () { 'video/x-flv', 'video/webm', ], - placement: 2, + plcmt: 2, }, } })).to.be.true; From 1cad24dddebc08947391b2cb901f4a9aa675ce7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+krzysztofequativ@users.noreply.github.com> Date: Wed, 29 May 2024 15:51:56 +0200 Subject: [PATCH 0105/1097] Smartadserver Bid Adapter : refactor gpid logic as part of Prebid 9.0 (#11602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching * Add gpid support * Support additional video params * vpmt as array of numbers * Fix comment * Update default startDelay * Include videoMediaType's startdelay * Handle specified midroll * add smartadserver topics iframe * gpid first, then pbadslot as fallback * drop topics iframe --------- Co-authored-by: Meven Courouble Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> Co-authored-by: Dariusz O --- modules/smartadserverBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 7edaaa36957..415061d0eda 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -196,7 +196,7 @@ export const spec = { sdc: sellerDefinedContext }; - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { payload.gpid = gpid; } From 2fef9c29351d2b4ada4a3ba6b008e616a15a0af8 Mon Sep 17 00:00:00 2001 From: Dmitry Sinev Date: Wed, 29 May 2024 18:36:48 +0300 Subject: [PATCH 0106/1097] Appnexus Bid Adapter: parse the currency from the bid if specified (#11581) * Appnexus Bid Adapter: parse the currency from the bid if specified * Appnexus Bid Adapter: parse the currency from the bid if specified, change code to codename --- modules/appnexusBidAdapter.js | 2 +- test/spec/modules/appnexusBidAdapter_spec.js | 33 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 5a81f272db5..b0c91a14a46 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -597,7 +597,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { cpm: rtbBid.cpm, creativeId: rtbBid.creative_id, dealId: rtbBid.deal_id, - currency: 'USD', + currency: rtbBid.publisher_currency_codename || 'USD', netRevenue: true, ttl: 300, adUnitCode: bidRequest.adUnitCode, diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c2da2f36223..393768c3063 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1748,6 +1748,7 @@ describe('AppNexusAdapter', function () { 'cpm': 0.5, 'cpm_publisher_currency': 0.5, 'publisher_currency_code': '$', + 'publisher_currency_codename': 'USD', 'client_initiated_ad_counting': true, 'viewability': { 'config': '' @@ -1832,6 +1833,38 @@ describe('AppNexusAdapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); + it('should parse non-default currency', function () { + let eurCpmResponse = deepClone(response); + eurCpmResponse.tags[0].ads[0].publisher_currency_codename = 'EUR'; + + let bidderRequest = { + bidderCode: 'appnexus', + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + + let result = spec.interpretResponse({ body: eurCpmResponse }, { bidderRequest }); + expect(result[0].currency).to.equal('EUR'); + }); + + it('should parse default currency', function () { + let defaultCpmResponse = deepClone(response); + delete defaultCpmResponse.tags[0].ads[0].publisher_currency_codename; + + let bidderRequest = { + bidderCode: 'appnexus', + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + + let result = spec.interpretResponse({ body: defaultCpmResponse }, { bidderRequest }); + expect(result[0].currency).to.equal('USD'); + }); + it('should reject 0 cpm bids', function () { let zeroCpmResponse = deepClone(response); zeroCpmResponse.tags[0].ads[0].cpm = 0; From 45a77ef37480c621268e73ed66d6dcf612fd87c4 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 29 May 2024 11:32:27 -0700 Subject: [PATCH 0107/1097] PBS adapter: do not set bidder schain in source.ext.schain (#11467) --- .../prebidServerBidAdapter/ortbConverter.js | 11 +----- .../modules/prebidServerBidAdapter_spec.js | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index e0f038767c2..7f4ffc84070 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -197,9 +197,7 @@ const PBS_CONVERTER = ortbConverter({ context.actualBidderRequests.forEach(req => orig(ortbRequest, req, context)); }, sourceExtSchain(orig, ortbRequest, proxyBidderRequest, context) { - // pass schains in ext.prebid.schains, with the most commonly used one in source.ext.schain - let mainChain; - + // pass schains in ext.prebid.schains let chains = (deepAccess(ortbRequest, 'ext.prebid.schains') || []); const chainBidders = new Set(chains.flatMap((item) => item.bidders)); @@ -218,17 +216,10 @@ const PBS_CONVERTER = ortbConverter({ chains[key] = {bidders: new Set(), schain}; } bidders.forEach((bidder) => chains[key].bidders.add(bidder)); - if (mainChain == null || chains[key].bidders.size > mainChain.bidders.size) { - mainChain = chains[key] - } return chains; }, {}) ).map(({bidders, schain}) => ({bidders: Array.from(bidders), schain})); - if (mainChain != null) { - deepSetValue(ortbRequest, 'source.ext.schain', mainChain.schain); - } - if (chains.length) { deepSetValue(ortbRequest, 'ext.prebid.schains', chains); } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9c2ac8a23a9..8e92cecd36b 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2377,23 +2377,28 @@ describe('S2S Adapter', function () { ]); }); - it('should "promote" the most reused bidder schain to source.ext.schain', () => { - const bidderReqs = [ - {...deepClone(BID_REQUESTS[0]), bidderCode: 'A'}, - {...deepClone(BID_REQUESTS[0]), bidderCode: 'B'}, - {...deepClone(BID_REQUESTS[0]), bidderCode: 'C'} - ]; - const chain1 = {chain: 1}; - const chain2 = {chain: 2}; + Object.entries({ + 'set': {}, + 'override': {source: {ext: {schain: 'pub-provided'}}} + }).forEach(([t, fpd]) => { + it(`should not ${t} source.ext.schain`, () => { + const bidderReqs = [ + {...deepClone(BID_REQUESTS[0]), bidderCode: 'A'}, + {...deepClone(BID_REQUESTS[0]), bidderCode: 'B'}, + {...deepClone(BID_REQUESTS[0]), bidderCode: 'C'} + ]; + const chain1 = {chain: 1}; + const chain2 = {chain: 2}; - bidderReqs[0].bids[0].schain = chain1; - bidderReqs[1].bids[0].schain = chain2; - bidderReqs[2].bids[0].schain = chain2; + bidderReqs[0].bids[0].schain = chain1; + bidderReqs[1].bids[0].schain = chain2; + bidderReqs[2].bids[0].schain = chain2; - adapter.callBids(REQUEST, bidderReqs, addBidResponse, done, ajax); - const req = JSON.parse(server.requests[0].requestBody); - expect(req.source.ext.schain).to.eql(chain2); - }); + adapter.callBids({...REQUEST, ortb2Fragments: {global: fpd}}, bidderReqs, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.source?.ext?.schain).to.eql(fpd?.source?.ext?.schain); + }) + }) it('passes multibid array in request', function () { const bidRequests = utils.deepClone(BID_REQUESTS); From 2928473698507417b8ee4098060ae85d386ecdd9 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Wed, 29 May 2024 20:42:59 +0200 Subject: [PATCH 0108/1097] Nexx360 Bid Adapter: Additional localStorage information (#11466) * ext.vastxml to adm * amxId added * test fix --------- Co-authored-by: Gabriel Chicoye --- modules/nexx360BidAdapter.js | 36 ++++++++++++++++++-- test/spec/modules/nexx360BidAdapter_spec.js | 37 +++++++++++++++++++-- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index c31c3d81aeb..b4f7cf50ffe 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -22,7 +22,7 @@ const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstre const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '4.0'; +const BIDDER_VERSION = '4.1'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; @@ -69,6 +69,19 @@ function getAdContainer(container) { } } +/** + * Get the AMX ID + * @return {string | false } false if localstorageNotEnabled + */ +export function getAmxId() { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for Nexx360`); + return false; + } + const amxId = storage.getDataFromLocalStorage('__amuidpb'); + return amxId || false; +} + const converter = ortbConverter({ context: { netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false @@ -107,13 +120,30 @@ const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); const nexx360LocalStorage = getNexx360LocalStorage(); - if (nexx360LocalStorage) deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + if (nexx360LocalStorage) { + deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + deepSetValue(request, 'ext.localStorage.nexx360Id', nexx360LocalStorage.nexx360Id); + } + const amxId = getAmxId(); + if (amxId) deepSetValue(request, 'ext.localStorage.amxId', amxId()); deepSetValue(request, 'ext.version', '$prebid.version$'); deepSetValue(request, 'ext.source', 'prebid.js'); deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION); deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']); - if (!request.user) deepSetValue(request, 'user', {}); + if (!request.user) request.user = {}; + if (getAmxId()) { + if (!request.user.ext) request.user.ext = {}; + if (!request.user.ext.eids) request.user.ext.eids = []; + request.user.ext.eids.push({ + source: 'amxdt.net', + uids: [{ + id: `${getAmxId()}`, + atype: 1 + }] + }); + } + return request; }, }); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index f18e0365226..06cbec347ff 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec, storage, getNexx360LocalStorage, } from 'modules/nexx360BidAdapter.js'; import { sandbox } from 'sinon'; +import { getAmxId } from '../../../modules/nexx360BidAdapter'; const instreamResponse = { 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', @@ -220,7 +221,7 @@ describe('Nexx360 bid adapter tests', function () { after(function () { sandbox.restore() }); - }) + }); describe('getNexx360LocalStorage enabled', function () { before(function () { @@ -235,7 +236,37 @@ describe('Nexx360 bid adapter tests', function () { after(function () { sandbox.restore() }); - }) + }); + + describe('getAmxId() with localStorage enabled and data not set', function() { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); + }); + it('We test if we get the amxId', function() { + const output = getAmxId(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data set', function() { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'abcdef'); + }); + it('We test if we get the amxId', function() { + const output = getAmxId(); + expect(output).to.be.eql('abcdef'); + }); + after(function () { + sandbox.restore() + }); + }); describe('buildRequests()', function() { before(function () { @@ -374,7 +405,7 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); - expect(requestContent.ext.bidderVersion).to.be.eql('4.0'); + expect(requestContent.ext.bidderVersion).to.be.eql('4.1'); expect(requestContent.ext.source).to.be.eql('prebid.js'); }); From 6b7c86ef41f20b3fefda87484d7270fdf22239fb Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 29 May 2024 21:54:33 +0200 Subject: [PATCH 0109/1097] Adagio Rtd Provider: initial release (#11509) * AdagioRtdProvider: add Adagio Rtd Provider * AdagioRtdProvider: missing callback() call * AdagioRtdProvider: set signals for all --- modules/adagioRtdProvider.js | 690 ++++++++++++++++++++ modules/adagioRtdProvider.md | 37 ++ test/spec/modules/adagioRtdProvider_spec.js | 532 +++++++++++++++ 3 files changed, 1259 insertions(+) create mode 100644 modules/adagioRtdProvider.js create mode 100644 modules/adagioRtdProvider.md create mode 100644 test/spec/modules/adagioRtdProvider_spec.js diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js new file mode 100644 index 00000000000..a901c2c489d --- /dev/null +++ b/modules/adagioRtdProvider.js @@ -0,0 +1,690 @@ +/** + * This module adds the adagio provider to the Real Time Data module (rtdModule). + * The {@link module:modules/realTimeData} module is required. + * @module modules/adagioRtdProvider + * @requires module:modules/realTimeData + */ +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import adapterManager from '../src/adapterManager.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { submodule } from '../src/hook.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + canAccessWindowTop, + deepAccess, + deepSetValue, + generateUUID, + getUniqueIdentifierStr, + getWindowSelf, + getWindowTop, + inIframe, + isNumber, + isSafeFrameWindow, + isStr, + prefixLog +} from '../src/utils.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').adUnit} adUnit + */ +const SUBMODULE_NAME = 'adagio'; +const ADAGIO_BIDDER_CODE = 'adagio'; +const GVLID = 617; +const SCRIPT_URL = 'https://script.4dex.io/a/latest/adagio.js'; +const SESS_DURATION = 30 * 60 * 1000; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); + +const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); + +// Guard to avoid storing the same bid data several times. +const guard = new Set(); + +/** + * Returns the window.ADAGIO global object used to store Adagio data. + * This object is created in window.top if possible, otherwise in window.self. + */ +const _ADAGIO = (function() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.windows = w.ADAGIO.windows || []; + + return w.ADAGIO; +})(); + +/** + * Store the sampling data. + * This data is used to determine if beacons should be sent to adagio. + * The sampling data + */ +const _SESSION = (function() { + /** + * @type {SessionData} + */ + const data = { + session: {} + }; + + return { + init: () => { + // helper function to determine if the session is new. + const isNewSession = (lastActivity) => { + const now = Date.now(); + return (!isNumber(lastActivity) || (now - lastActivity) > SESS_DURATION); + }; + + storage.getDataFromLocalStorage('adagio', (storageValue) => { + // session can be an empty object + const { rnd, new: isNew, vwSmplg, vwSmplgNxt, lastActivityTime } = _internal.getSessionFromLocalStorage(storageValue); + + data.session = { + rnd, + new: isNew || false, // legacy: `new` was used but the choosen name is not good. + // Don't use values if they are not defined. + ...(vwSmplg !== undefined && { vwSmplg }), + ...(vwSmplgNxt !== undefined && { vwSmplgNxt }), + ...(lastActivityTime !== undefined && { lastActivityTime }) + }; + + if (isNewSession(lastActivityTime)) { + data.session.new = true; + data.session.rnd = Math.random(); + } + + _internal.getAdagioNs().queue.push({ + action: 'session', + ts: Date.now(), + data: { + session: { + ...data.session + } + } + }); + }); + }, + get: function() { + return data.session; + } + }; +})(); + +const _FEATURES = (function() { + /** + * @type {Features} + */ + const features = { + initialized: false, + data: {}, + }; + + return { + // reset is used for testing purpose + reset: function() { + features.initialized = false; + features.data = {}; + }, + get: function() { + if (!features.initialized) { + features.data = { + page_dimensions: getPageDimensions().toString(), + viewport_dimensions: getViewPortDimensions().toString(), + user_timestamp: getTimestampUTC().toString(), + dom_loading: getDomLoadingDuration().toString(), + }; + features.initialized = true; + } + + return { ...features.data }; + } + }; +})(); + +export const _internal = { + getAdagioNs: function() { + return _ADAGIO; + }, + + getSession: function() { + return _SESSION; + }, + + getFeatures: function() { + return _FEATURES; + }, + + getGuard: function() { + return guard; + }, + + /** + * Ensure that the bidder is Adagio. + * + * @param {string} alias + * @returns {boolean} + */ + isAdagioBidder: function (alias) { + if (!alias) { + return false; + } + return (alias + adapterManager.aliasRegistry[alias]).toLowerCase().includes(ADAGIO_BIDDER_CODE); + }, + + /** + * Returns the session data from the localStorage. + * + * @param {string} storageValue - The value stored in the localStorage. + * @returns {Session} + */ + getSessionFromLocalStorage: function(storageValue) { + const _default = { + new: true, + rnd: Math.random() + }; + + const obj = JSON.parse(storageValue, function(name, value) { + if (name.charAt(0) !== '_' || name === '') { + return value; + } + }); + + return (!obj || !obj.session) ? _default : obj.session; + } +}; + +function loadAdagioScript(config) { + storage.localStorageIsEnabled(isValid => { + if (!isValid) { + return; + } + + loadExternalScript(SCRIPT_URL, SUBMODULE_NAME, undefined, undefined, { id: `adagiojs-${getUniqueIdentifierStr()}`, 'data-pid': config.params.organizationId }); + }); +} + +/** + * Initialize the Adagio RTD Module. + * @param {Object} config + * @param {Object} _userConsent + * @returns {boolean} + */ +function init(config, _userConsent) { + if (!isStr(config.params?.organizationId) || !isStr(config.params?.site)) { + logError('organizationId is required and must be a string.'); + return false; + } + + _internal.getAdagioNs().hasRtd = true; + + _internal.getSession().init(); + + registerEventsForAdServers(config); + + loadAdagioScript(config); + + return true; +} + +/** + * onBidRequest is called for each bidder during an auction and contains the bids for that bidder. + * + * @param {*} bidderRequest + * @param {*} config + * @param {*} _userConsent + */ +function onBidRequest(bidderRequest, config, _userConsent) { + // setTimeout trick to ensure that the `bidderRequest.params` values updated by a bidder adapter are taken into account. + // @todo: Check why we have to do it like this, and if there is a better way. Check how the event is dispatched in rtdModule/index.js + setTimeout(() => { + bidderRequest.bids.forEach(bid => { + const uid = deepAccess(bid, 'ortb2.site.ext.data.adg_rtd.uid'); + if (!uid) { + logError('The `uid` is required to store the request in the ADAGIO namespace.'); + return; + } + + // No need to store the same info several times. + // `uid` is unique as it is generated by the RTD module itself for each auction. + const key = `${bid.adUnitCode}-${uid}`; + if (_internal.getGuard().has(key)) { + return; + } + + _internal.getGuard().add(key); + storeRequestInAdagioNS(bid, config); + }); + }, 1); +} + +/** + * onGetBidRequestData is called once per auction. + * Update both the `ortb2Fragments` and `ortb2Imp` objects with features computed for Adagio. + * + * @param {*} bidReqConfig + * @param {*} callback + * @param {*} config + */ +function onGetBidRequestData(bidReqConfig, callback, config) { + const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + const features = _internal.getFeatures().get(); + const ext = { + uid: generateUUID(), + features: { ...features }, + session: { ..._SESSION.get() } + }; + + deepSetValue(ortb2Site, `ext.data.adg_rtd`, ext); + + const adUnits = bidReqConfig.adUnits || getGlobal().adUnits || []; + adUnits.forEach(adUnit => { + const ortb2Imp = deepAccess(adUnit, 'ortb2Imp'); + // A divId is required to compute the slot position and later to track viewability. + // If nothing has been explicitly set, we try to get the divId from the GPT slot and fallback to the adUnit code in last resort. + if (!deepAccess(ortb2Imp, 'ext.data.divId')) { + const divId = getGptSlotInfoForAdUnitCode(adUnit.code).divId; + deepSetValue(ortb2Imp, `ext.data.divId`, divId || adUnit.code); + } + + const slotPosition = getSlotPosition(adUnit); + deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition); + + // We expect `pagetype` `category` are defined in FPD `ortb2.site.ext.data` object. + // `placement` is expected in FPD `adUnits[].ortb2Imp.ext.data` object. (Please note that this `placement` is not related to the oRTB video property.) + // Btw, we have to ensure compatibility with publishers that use the "legacy" adagio params at the adUnit.params level. + const adagioBid = adUnit.bids.find(bid => _internal.isAdagioBidder(bid.bidder)); + if (adagioBid) { + // ortb2 level + let mustWarnOrtb2 = false; + if (!deepAccess(ortb2Site, 'ext.data.pagetype') && adagioBid.params.pagetype) { + deepSetValue(ortb2Site, 'ext.data.pagetype', adagioBid.params.pagetype); + mustWarnOrtb2 = true; + } + if (!deepAccess(ortb2Site, 'ext.data.category') && adagioBid.params.category) { + deepSetValue(ortb2Site, 'ext.data.category', adagioBid.params.category); + mustWarnOrtb2 = true; + } + + // ortb2Imp level + let mustWarnOrtb2Imp = false; + if (!deepAccess(ortb2Imp, 'ext.data.placement')) { + if (adagioBid.params.placement) { + deepSetValue(ortb2Imp, 'ext.data.placement', adagioBid.params.placement); + mustWarnOrtb2Imp = true; + } else { + // If the placement is not defined, we fallback to the adUnit code. + deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); + } + } + + if (mustWarnOrtb2) { + logWarn('`pagetype` and `category` must be defined in the FPD `ortb2.site.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.'); + } + if (mustWarnOrtb2Imp) { + logWarn('`placement` must be defined in the FPD `adUnits[].ortb2Imp.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.'); + } + } + }); + + callback(); +} + +export const adagioRtdSubmodule = { + name: SUBMODULE_NAME, + gvlid: GVLID, + init: init, + getBidRequestData: onGetBidRequestData, + onBidRequestEvent: onBidRequest, +}; + +submodule('realTimeData', adagioRtdSubmodule); + +// --- +// +// internal functions moved from adagioBidAdapter.js to adagioRtdProvider.js. +// +// Several of these functions could be redistribued in Prebid.js core or in a library +// +// --- + +/** + * storeRequestInAdagioNS store ad-units in the ADAGIO namespace for further usage. + * Not all the properties are stored, only the ones that are useful for adagio.js. + * + * @param {*} bid - The bid object. Correspond to the bidRequest.bids[i] object. + * @param {*} config - The RTD module configuration. + * @returns {void} + */ +function storeRequestInAdagioNS(bid, config) { + try { + const { bidder, adUnitCode, mediaTypes, params, auctionId, bidderRequestsCount, ortb2, ortb2Imp } = bid; + + const { organizationId, site } = config.params; + + const ortb2Data = deepAccess(ortb2, 'site.ext.data', {}); + const ortb2ImpData = deepAccess(ortb2Imp, 'ext.data', {}); + + // TODO: `bidderRequestsCount` must be incremented with s2s context, actually works only for `client` context + // see: https://github.com/prebid/Prebid.js/pull/11295/files#diff-d5c9b255c545e5097d1cd2f49e7dad309b731e34d788f9c28432ad43ebcd7785L114 + const data = { + bidder, + adUnitCode, + mediaTypes, + params, + auctionId, + bidderRequestsCount, + ortb2: ortb2Data, + ortb2Imp: ortb2ImpData, + localPbjs: '$$PREBID_GLOBAL$$', + localPbjsRef: getGlobal(), + organizationId, + site + }; + + _internal.getAdagioNs().queue.push({ + action: 'store', + ts: Date.now(), + data + }); + } catch (e) { + logError(e); + } +} + +function getElementFromTopWindow(element, currentWindow) { + try { + if (getWindowTop() === currentWindow) { + if (!element.getAttribute('id')) { + element.setAttribute('id', `adg-${getUniqueIdentifierStr()}`); + } + return element; + } else { + const frame = currentWindow.frameElement; + const frameClientRect = frame.getBoundingClientRect(); + const elementClientRect = element.getBoundingClientRect(); + + if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) { + return false; + } + + return getElementFromTopWindow(frame, currentWindow.parent); + } + } catch (err) { + logWarn(err); + return false; + } +}; + +function getSlotPosition(adUnit) { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { + return ''; + } + + const position = { x: 0, y: 0 }; + + if (isSafeFrameWindow()) { + const ws = getWindowSelf(); + + const sfGeom = (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : null; + + if (!sfGeom || !sfGeom.self) { + return ''; + } + + position.x = Math.round(sfGeom.self.t); + position.y = Math.round(sfGeom.self.l); + } else { + try { + // window.top based computing + const wt = getWindowTop(); + const d = wt.document; + const adUnitElementId = deepAccess(adUnit, 'ortb2Imp.ext.data.divId'); + + let domElement; + + if (inIframe() === true) { + const ws = getWindowSelf(); + const currentElement = ws.document.getElementById(adUnitElementId); + domElement = getElementFromTopWindow(currentElement, ws); + } else { + domElement = wt.document.getElementById(adUnitElementId); + } + + if (!domElement) { + return ''; + } + + let box = domElement.getBoundingClientRect(); + + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = wt.getComputedStyle(domElement, null); + const mustDisplayElement = elComputedStyle.display === 'none'; + + if (mustDisplayElement) { + logWarn('The element is hidden. The slot position cannot be computed.'); + } + + position.x = Math.round(box.left + scrollLeft - clientLeft); + position.y = Math.round(box.top + scrollTop - clientTop); + } catch (err) { + logError(err); + return ''; + } + } + + return `${position.x}x${position.y}`; +} + +function getPageDimensions() { + if (isSafeFrameWindow() || !canAccessWindowTop()) { + return ''; + } + + // the page dimension can be computed on window.top only. + const wt = getWindowTop(); + const body = wt.document.querySelector('body'); + + if (!body) { + return ''; + } + const html = wt.document.documentElement; + const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); + const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); + + return `${pageWidth}x${pageHeight}`; +} + +function getViewPortDimensions() { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { + return ''; + } + + const viewportDims = { w: 0, h: 0 }; + + if (isSafeFrameWindow()) { + const ws = getWindowSelf(); + + const sfGeom = (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : null; + + if (!sfGeom || !sfGeom.win) { + return ''; + } + + viewportDims.w = Math.round(sfGeom.win.w); + viewportDims.h = Math.round(sfGeom.win.h); + } else { + // window.top based computing + const wt = getWindowTop(); + viewportDims.w = wt.innerWidth; + viewportDims.h = wt.innerHeight; + } + + return `${viewportDims.w}x${viewportDims.h}`; +} + +function getTimestampUTC() { + // timestamp returned in seconds + return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; +} + +function getDomLoadingDuration() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + const performance = w.performance; + + let domLoadingDuration = -1; + + if (performance && performance.timing && performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) { + domLoadingDuration = val; + } + } + + return domLoadingDuration; +} + +/** + * registerEventsForAdServers bind adagio listeners to ad-server events. + * Theses events are used to track the viewability and attention. + * + * @param {*} config + * @returns {void} + */ +function registerEventsForAdServers(config) { + const GPT_EVENTS = new Set([ + 'impressionViewable', + 'slotRenderEnded', + 'slotVisibilityChanged', + ]); + + const SAS_EVENTS = new Set([ + 'noad', + 'setHeaderBiddingWinner', + ]); + + const AST_EVENTS = new Set([ + 'adLoaded', + ]); + + // Listen to ad-server events in current window + // as we can be safe in a Post-Bid scenario. + const ws = getWindowSelf(); + + // Keep a reference to the window on which the listener is attached. + // this is used to avoid to bind event several times. + if (!Array.isArray(_internal.getAdagioNs().windows)) { + _internal.getAdagioNs().windows = []; + } + + let selfStoredWindow = _internal.getAdagioNs().windows.find(_w => _w.self === ws); + if (!selfStoredWindow) { + selfStoredWindow = { self: ws }; + _internal.getAdagioNs().windows.push(selfStoredWindow); + } + + const register = (namespace, command, selfWindow, adserver, cb) => { + try { + if (selfWindow.adserver === adserver) { + return; + } + ws[namespace] = ws[namespace] || {}; + ws[namespace][command] = ws[namespace][command] || []; + cb(); + } catch (e) { + logError(e); + } + }; + + register('googletag', 'cmd', ws, 'gpt', () => { + ws.googletag.cmd.push(() => { + GPT_EVENTS.forEach(eventName => { + ws.googletag.pubads().addEventListener(eventName, (args) => { + _internal.getAdagioNs().queue.push({ + action: 'gpt-event', + data: { eventName, args, _window: ws }, + ts: Date.now(), + }); + }); + }); + selfStoredWindow.adserver = 'gpt'; + }); + }); + + register('sas', 'cmd', ws, 'sas', () => { + ws.sas.cmd.push(() => { + SAS_EVENTS.forEach(eventName => { + ws.sas.events.on(eventName, (args) => { + _internal.getAdagioNs().queue.push({ + action: 'sas-event', + data: { eventName, args, _window: ws }, + ts: Date.now(), + }); + }); + }); + selfStoredWindow.adserver = 'sas'; + }); + }); + + // https://learn.microsoft.com/en-us/xandr/seller-tag/on-event + register('apntag', 'anq', ws, 'ast', () => { + ws.apntag.anq.push(() => { + AST_EVENTS.forEach(eventName => { + ws.apntag.onEvent(eventName, () => { + _internal.getAdagioNs().queue.push({ + action: 'ast-event', + data: { eventName, args: arguments, _window: ws }, + ts: Date.now(), + }); + }); + }); + selfStoredWindow.adserver = 'ast'; + }); + }); +}; + +// --- end of internal functions ----- // + +/** + * @typedef {Object} AdagioWindow + * @property {Window} self + * @property {string} adserver - 'gpt', 'sas', 'ast' + */ + +/** + * @typedef {Object} AdagioGlobal + * @property {Object} adUnits + * @property {Array} pbjsAdUnits + * @property {Array} queue + * @property {Array} windows + */ + +/** + * @typedef {Object} Session + * @property {boolean} new - True if the session is new. + * @property {number} rnd - Random number used to determine if the session is new. + * @property {number} vwSmplg - View sampling rate. + * @property {number} vwSmplgNxt - Next view sampling rate. + * @property {number} lastActivityTime - Last activity time. + */ + +/** + * @typedef {Object} SessionData + * @property {Session} session - the session data. + */ + +/** + * @typedef {Object} Features + * @property {boolean} initialized - True if the features are initialized. + * @property {Object} data - the features data. + */ diff --git a/modules/adagioRtdProvider.md b/modules/adagioRtdProvider.md new file mode 100644 index 00000000000..f05521ec54a --- /dev/null +++ b/modules/adagioRtdProvider.md @@ -0,0 +1,37 @@ +# Overview + +Module Name: Adagio Rtd Provider +Module Type: Rtd Provider +Maintainer: dev@adagio.io + +# Description + +This module is exclusively used in combination with Adagio Bidder Adapter (SSP) and/or with Adagio prebid server endpoint, and mandatory for Adagio customers. +It computes and collects data required to leverage Adagio viewability and attention prediction engine. + +Features are computed for the Adagio bidder only and placed into `ortb2.ext` and `AdUnit.ortb2Imp.ext.data`. + +To collect data, an external script is loaded by the provider. +It relies on the listening of ad-server events. +Supported ad-servers are GAM, Smart Ad Server, Xandr. Custom ad-server can also be used, +please contact [contact@adagio.io](contact@adagio.io) for more information. + +# Integration + +```bash +gulp build --modules=adagioBidAdapter,rtdModule,adagioRtdProvider +``` + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders:[{ + name: 'adagio', + params: { + organizationId: '1000' // Required. Provided by Adagio + site: 'my-site' // Required. Provided by Adagio + } + }] + } +}); +``` diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js new file mode 100644 index 00000000000..2c1612f2e83 --- /dev/null +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -0,0 +1,532 @@ +import { adagioRtdSubmodule, _internal, storage } from 'modules/adagioRtdProvider.js'; +import * as utils from 'src/utils.js'; +import { loadExternalScript } from '../../../src/adloader.js'; +import { expect } from 'chai'; +import { getGlobal } from '../../../src/prebidGlobal.js'; + +describe('Adagio Rtd Provider', function () { + const SUBMODULE_NAME = 'adagio'; + + function getElementByIdMock(width, height, x, y) { + const obj = { + x: x || 800, + y: y || 300, + width: width || 300, + height: height || 250, + }; + + return { + ...obj, + getBoundingClientRect: () => { + return { + width: obj.width, + height: obj.height, + left: obj.x, + top: obj.y, + right: obj.x + obj.width, + bottom: obj.y + obj.height + }; + } + }; + } + + let sandbox; + let clock; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + sandbox.restore(); + }); + + describe('submodule `init`', function () { + const config = { + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 'mysite' + } + }; + + it('exists', function () { + expect(adagioRtdSubmodule.init).to.be.a('function'); + }); + + it('returns false missing config params', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + }); + expect(value).to.equal(false); + }); + + it('returns false if missing providers param', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + params: {} + }); + expect(value).to.equal(false); + }); + + it('returns false if organizationId param is not a string', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + params: { + organizationId: 1000, + site: 'mysite' + } + }); + expect(value).to.equal(false); + }); + + it('returns false if `site` param is not a string', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 123 + } + }); + expect(value).to.equal(false); + }); + + it('returns true if `organizationId` and `site` params included', function () { + const value = adagioRtdSubmodule.init(config); + expect(value).to.equal(true); + }); + + it('load an external script if localStorageIsEnabled is enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true) + adagioRtdSubmodule.init(config); + expect(loadExternalScript.called).to.be.true; + }); + + it('do not load an external script if localStorageIsEnabled is disabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false) + adagioRtdSubmodule.init(config); + expect(loadExternalScript.called).to.be.false; + }); + + describe('store session data in localStorage', function () { + const session = { + lastActivityTime: 1714116520700, + rnd: 0.5697, + vwSmplg: 0.1, + vwSmplgNxt: 0.1 + }; + + it('store new session data for further usage', function () { + const storageValue = null; + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + new: true, + rnd: Math.random() + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + + it('store existing session data for further usage', function () { + const storageValue = JSON.stringify({session: session}); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + ...session, + new: false, + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + + it('store new session if old session has expired data for further usage', function () { + const storageValue = JSON.stringify({session: session}); + sandbox.stub(Date, 'now').returns(1715679344351); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Math, 'random').returns(0.8); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + ...session, + new: true, + rnd: Math.random(), + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); + }); + + describe('submodule `getBidRequestData`', function () { + const bidReqConfig = { + 'timeout': 700, + 'adUnits': [ + { + 'code': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'ortb2Imp': {}, + 'bids': [ + { + 'bidder': 'adagio', + 'params': { + 'organizationId': '1004', + 'site': 'maville', + 'useAdUnitCodeAsPlacement': true, + 'adUnitElementId': 'div-gpt-ad-1460505748561-0', + 'pagetype': 'article', + } + }, + { + 'bidder': 'another', + 'params': { + 'pubid': 'xxx', + } + } + ] + } + ], + 'adUnitCodes': [ + 'div-gpt-ad-1460505748561-0' + ], + 'ortb2Fragments': { + 'global': { + 'regs': { + 'ext': { + 'gdpr': 1 + } + }, + 'site': { + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + }, + 'page': 'http://example.com/page.html', + }, + 'device': { + 'w': 1359, + 'h': 1253, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'fr' + } + }, + 'bidder': {} + } + }; + + function cb() {} + + beforeEach(function() { + _internal.getFeatures().reset(); + }); + + it('exists', function () { + expect(adagioRtdSubmodule.getBidRequestData).to.be.a('function'); + }); + + it('update the ortb2Fragments object with adg_rtd signals', function() { + const bidRequest = utils.deepClone(bidReqConfig); + + sandbox.stub(window.top.document, 'getElementById').returns(getElementByIdMock()); + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + sandbox.stub(utils, 'inIframe').returns(false); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + const signals = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(signals).to.have.property('features'); + expect(signals).to.have.property('session'); + expect(signals).to.have.property('uid'); + expect(signals.features.viewport_dimensions).to.match(/\d+x\d+/); + expect(signals.features.page_dimensions).to.match(/\d+x\d+/); + + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.adunit_position).to.match(/\d+x\d+/); + }); + + describe('update the ortb2Fragments object a SafeFrame context', function() { + it('update', function() { + sandbox.stub(utils, 'isSafeFrameWindow').returns(true); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + + window.$sf = { + ext: { + geom() { + return { + win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, + self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, + } + } + } + }; + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const fragmentExt = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(fragmentExt.features.viewport_dimensions).equal('1920x1177'); + expect(fragmentExt.features.page_dimensions).equal(''); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal('210x859'); + + window.$sf = undefined; + }); + + it('handle missformated $sf object and update', function() { + sandbox.stub(utils, 'isSafeFrameWindow').returns(true); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + + window.$sf = { + ext: { + geom: '' + } + }; + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const fragmentExt = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(fragmentExt.features.viewport_dimensions).equal(''); + expect(fragmentExt.features.page_dimensions).equal(''); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + + window.$sf = undefined; + }); + }); + + describe('update the ortb2Fragments object in a "inIframe" context', function() { + it('update when window.top is accessible', function() { + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + sandbox.stub(utils, 'inIframe').returns(true); + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + }); + + it('catch error when window.top is accessible', function() { + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + sandbox.stub(window.document, 'getElementById').throws(); + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + }); + }); + + it('update the ortb2Fragments object when window.top is not accessible', function() { + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const fragmentExt = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(fragmentExt.features.viewport_dimensions).equal(''); + expect(fragmentExt.features.page_dimensions).equal(''); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + }); + }); + + describe('submodule `onBidRequestEvent`', function() { + const bidderRequest = { + 'bidderCode': 'adagio', + 'auctionId': '3de10dc0-fe75-480f-95cc-f15f2c4929fe', + 'bidderRequestId': '4ecd1f17cf829b', + 'bids': [ + { + 'bidder': 'adagio', + 'params': { + 'organizationId': '1000', + 'site': 'example', + 'adUnitElementId': 'div-gpt-ad-1460505748561-0', + 'pagetype': 'article', + 'environment': 'desktop', + 'placement': 'div-gpt-ad-1460505748561-0', + 'adagioAuctionId': '4c259968-0158-443d-af93-551bac594b6c', + 'pageviewId': 'dfb9b067-e5c4-4212-97bb-c67d6313ecaf' + }, + 'ortb2Imp': { + 'ext': { + 'tid': '235c991e-fcc4-416b-95d3-f60e53575bee', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/19968336/header-bid-tag-0' + }, + 'pbadslot': '/19968336/header-bid-tag-0', + 'adunit_position': '8x95' + }, + 'gpid': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '235c991e-fcc4-416b-95d3-f60e53575bee', + 'adUnitId': '79ab5904-0b21-4235-965a-f4905af072b7', + 'bidId': '534aa529a44e0e', + 'bidderRequestId': '4ecd1f17cf829b', + 'auctionId': '3de10dc0-fe75-480f-95cc-f15f2c4929fe', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + 'uid': 'dfb9b067-e5c4-4212-97bb-c67d6313ecaf', + 'features': { + 'page_dimensions': '1359x1353', + 'viewport_dimensions': '1359x1253', + 'user_timestamp': '1715621032', + 'dom_loading': '28' + }, + 'session': { + 'new': true, + 'rnd': 0.020644826280300954, + 'vwSmplg': 0.1, + 'vwSmplgNxt': 0.1 + } + } + } + } + } + }, + }, + ], + 'auctionStart': 1715613832791, + 'timeout': 700, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + 'features': { + 'page_dimensions': '1359x1353', + 'viewport_dimensions': '1359x1253', + 'user_timestamp': '1715621032', + 'dom_loading': '28' + }, + 'session': { + 'new': true, + 'rnd': 0.020644826280300954, + 'vwSmplg': 0.1, + 'vwSmplgNxt': 0.1 + } + } + } + } + } + }, + 'start': 1715613832796 + } + + it('store a copy of computed property', function() { + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + sandbox.stub(Date, 'now').returns(12345); + + _internal.getGuard().clear(); + + const config = { + params: { + organizationId: '1000', + site: 'example' + } + }; + const bidderRequestCopy = utils.deepClone(bidderRequest); + adagioRtdSubmodule.onBidRequestEvent(bidderRequestCopy, config); + + clock.tick(1); + + const { + bidder, + adUnitCode, + mediaTypes, + params, + auctionId, + bidderRequestsCount } = bidderRequestCopy.bids[0]; + + const expected = { + bidder, + adUnitCode, + mediaTypes, + ortb2: bidderRequestCopy.bids[0].ortb2.site.ext.data, + ortb2Imp: bidderRequestCopy.bids[0].ortb2Imp.ext.data, + params, + auctionId, + bidderRequestsCount, + organizationId: config.params.organizationId, + site: config.params.site, + localPbjs: 'pbjs', + localPbjsRef: getGlobal() + } + + expect(spy.withArgs({ + action: 'store', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); +}); From 8681fb9da3eee495da8bcfb012e84d000a6b9267 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 29 May 2024 21:55:34 +0200 Subject: [PATCH 0110/1097] AdagioBidAdapter: update preparation for Rtd module and Prebid.js 9 (#11580) --- modules/adagioBidAdapter.js | 15 +++++----- test/spec/modules/adagioBidAdapter_spec.js | 33 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index e3b25773061..b6ffc9b8d0d 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -667,13 +667,14 @@ function autoFillParams(bid) { bid.params.site = adgGlobalConf.siteId.split(':')[1]; } - // Edge case. Useful when Prebid Manager cannot handle properly params setting… - if (adgGlobalConf.useAdUnitCodeAsPlacement === true || bid.params.useAdUnitCodeAsPlacement === true) { + // `useAdUnitCodeAsPlacement` is an edge case. Useful when a Prebid Manager cannot handle properly params setting. + // In Prebid.js 9, `placement` should be defined in ortb2Imp and the `useAdUnitCodeAsPlacement` param should be removed + bid.params.placement = deepAccess(bid, 'ortb2Imp.ext.data.placement', bid.params.placement); + if (!bid.params.placement && (adgGlobalConf.useAdUnitCodeAsPlacement === true || bid.params.useAdUnitCodeAsPlacement === true)) { bid.params.placement = bid.adUnitCode; } - bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.elementId', null) || bid.params.adUnitElementId; - + bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.divId', bid.params.adUnitElementId); if (!bid.params.adUnitElementId) { if (adgGlobalConf.useAdUnitCodeAsAdUnitElementId === true || bid.params.useAdUnitCodeAsAdUnitElementId === true) { bid.params.adUnitElementId = bid.adUnitCode; @@ -959,14 +960,14 @@ const OUTSTREAM_RENDERER = { * @returns */ const _getFeatures = (bidRequest) => { - const f = { ...deepAccess(bidRequest, 'ortb2.ext.features', GlobalExchange.getOrSetGlobalFeatures()) } || {}; + const f = { ...deepAccess(bidRequest, 'ortb2.site.ext.data.adg_rtd.features', GlobalExchange.getOrSetGlobalFeatures()) } || {}; f.print_number = deepAccess(bidRequest, 'bidderRequestsCount', 1).toString(); if (f.type === 'bidAdapter') { f.adunit_position = getSlotPosition(bidRequest.params.adUnitElementId) } else { - f.adunit_position = deepAccess(bidRequest, 'ortb2Imp.ext.data.adunit_position'); + f.adunit_position = deepAccess(bidRequest, 'ortb2Imp.ext.data.adg_rtd.adunit_position'); } Object.keys(f).forEach((prop) => { @@ -1019,7 +1020,7 @@ export const spec = { // We don't validate the dsa object in adapter and let our server do it. const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); - let rtdSamplingSession = deepAccess(bidderRequest, 'ortb2.ext.session'); + let rtdSamplingSession = deepAccess(bidderRequest, 'ortb2.site.ext.data.adg_rtd.session'); const dataExchange = (rtdSamplingSession) ? { session: rtdSamplingSession } : GlobalExchange.getExchangeData(); const aucId = generateUUID() diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 1371c97dddf..ec8486f62ad 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -349,6 +349,39 @@ describe('Adagio bid adapter', () => { adagioMock.verify(); }); + describe('with Adagio Rtd Provider', function() { + it('it dont enqueue features from the bidder adapter', function() { + sandbox.stub(adagio, 'hasRtd').returns(true); + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + spec.buildRequests([bid01], bidderRequest); + adagioMock.expects('enqueue').withArgs(sinon.match({ action: 'features' })).never(); + adagioMock.verify(); + }); + + it('get feature from ortb2', function() { + sandbox.stub(adagio, 'hasRtd').returns(true); + const bid01 = new BidRequestBuilder().withParams().build(); + bid01.ortb2Imp = { + ext: { data: {adg_rtd: {adunit_position: '1x1'}} } + }; + bid01.ortb2 = { + site: { + ext: + { + data: { + adg_rtd: { features: {} } + } + } + } + }; + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.adUnits[0].features).to.exist; + expect(requests[0].data.adUnits[0].features.adunit_position).to.equal('1x1'); + }); + }); + it('should filter some props in case refererDetection.reachedTop is false', function() { const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder({ From b15af76cdcb443bc59de4a2770aff34ccf038d5a Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Wed, 29 May 2024 15:58:03 -0400 Subject: [PATCH 0111/1097] Contxtful RTD Provider : add ORTB2 support (#11497) * contxtfulRtdProvider: update the Contxtful Rtd module * contxtfulRtdProvider: update the GPT example * contxtfulRtdProvider: update the tests * contxtfulRtdProvider: refresh the documentation * contxtfulRtdProvider: fix getTargetingData unit test * contxtfulRtdProvider: revert out-of-scope changes * contxtfulRtdProvider: revert out-of-scope changes * contxtfulRtdProvider: sync docs with content on prebid.github.io * contxtfulRtdProvider: improve header style * contxtfulRtdProvider: improve sentences * contxtfulRtdProvider: remove spurious text * contxtfulRtdProvider: trigger build * contxtfulRtdProvider: fix multi-line row in .md file --------- Co-authored-by: Sebastien Boisvert --- .../gpt/contxtfulRtdProvider_example.html | 261 +++++++--- modules/contxtfulRtdProvider.js | 221 +++++++-- modules/contxtfulRtdProvider.md | 73 ++- .../spec/modules/contxtfulRtdProvider_spec.js | 453 ++++++++++++++++-- 4 files changed, 825 insertions(+), 183 deletions(-) diff --git a/integrationExamples/gpt/contxtfulRtdProvider_example.html b/integrationExamples/gpt/contxtfulRtdProvider_example.html index 29284de81a2..e47bd4142f9 100644 --- a/integrationExamples/gpt/contxtfulRtdProvider_example.html +++ b/integrationExamples/gpt/contxtfulRtdProvider_example.html @@ -1,91 +1,212 @@ + - - - - + +Contxtful Rtd Provider Example + - +googletag.cmd.push(function () { + googletag + .defineSlot('/19968336/header-bid-tag-1', + div2Sizes, 'div-2') + .addService(googletag.pubads()); + googletag.pubads().enableSingleRequest(); + googletag.enableServices(); +}); + + -

Contxtful RTD Provider

-
- - +

Contxtful Rtd Provider Example

+ +

+ +
Div-1
+
+ +
+ +
+ +
Div-2
+
+ +
+ + diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 6d4b2a2ce29..e2bb1f3b909 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -1,8 +1,8 @@ /** - * Contxtful Technologies Inc - * This RTD module provides receptivity feature that can be accessed using the - * getReceptivity() function. The value returned by this function enriches the ad-units - * that are passed within the `getTargetingData` functions and GAM. + * Contxtful Technologies Inc. + * This RTD module provides receptivity that can be accessed using the + * getTargetingData and getBidRequestData functions. The receptivity enriches ad units + * and bid requests. */ import { submodule } from '../src/hook.js'; @@ -10,8 +10,11 @@ import { logInfo, logError, isStr, + mergeDeep, isEmptyStr, + isEmpty, buildUrl, + isArray, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; @@ -20,8 +23,55 @@ const MODULE = `${MODULE_NAME}RtdProvider`; const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io'; -let initialReceptivity = null; -let contxtfulModule = null; +let rxApi = null; +let isFirstBidRequestCall = true; + +/** + * Return current receptivity value for the requester. + * @param { String } requester + * @return { { Object } } + */ +function getRxEngineReceptivity(requester) { + return rxApi?.receptivity(requester); +} + +function loadSessionReceptivity(requester) { + let sessionStorageValue = sessionStorage.getItem(requester); + if (!sessionStorageValue) { + return null; + } + + try { + // Check expiration of the cached value + let sessionStorageReceptivity = JSON.parse(sessionStorageValue); + let expiration = parseInt(sessionStorageReceptivity?.exp); + if (expiration < new Date().getTime()) { + return null; + } + + let rx = sessionStorageReceptivity?.rx; + return rx; + } catch { + return null; + } +}; + +/** + * Prepare a receptivity batch + * @param {Array.} requesters + * @param {Function} method + * @returns A batch + */ +function prepareBatch(requesters, method) { + return requesters.reduce((acc, requester) => { + const receptivity = method(requester); + if (!isEmpty(receptivity)) { + return { ...acc, [requester]: receptivity }; + } else { + return acc; + } + }, {}); +} /** * Init function used to start sub module @@ -30,11 +80,10 @@ let contxtfulModule = null; */ function init(config) { logInfo(MODULE, 'init', config); - initialReceptivity = null; - contxtfulModule = null; + rxApi = null; try { - const {version, customer, hostname} = extractParameters(config); + const { version, customer, hostname } = extractParameters(config); initCustomer(version, customer, hostname); return true; } catch (error) { @@ -51,7 +100,7 @@ function init(config) { * @return { { version: String, customer: String, hostname: String } } * @throws params.{name} should be a non-empty string */ -function extractParameters(config) { +export function extractParameters(config) { const version = config?.params?.version; if (!isStr(version) || isEmptyStr(version)) { throw Error(`${MODULE}: params.version should be a non-empty string`); @@ -64,7 +113,7 @@ function extractParameters(config) { const hostname = config?.params?.hostname || CONTXTFUL_RECEPTIVITY_DOMAIN; - return {version, customer, hostname}; + return { version, customer, hostname }; } /** @@ -78,73 +127,145 @@ function initCustomer(version, customer, hostname) { const CONNECTOR_URL = buildUrl({ protocol: 'https', host: hostname, - pathname: `/${version}/prebid/${customer}/connector/p.js`, + pathname: `/${version}/prebid/${customer}/connector/rxConnector.js`, }); const externalScript = loadExternalScript(CONNECTOR_URL, MODULE_NAME); - addExternalScriptEventListener(externalScript); + addExternalScriptEventListener(externalScript, customer); } /** * Add event listener to the script tag for the expected events from the external script. * @param { HTMLScriptElement } script */ -function addExternalScriptEventListener(script) { - if (!script) { - return; - } - - script.addEventListener('initialReceptivity', ({ detail }) => { - let receptivityState = detail?.ReceptivityState; - if (isStr(receptivityState) && !isEmptyStr(receptivityState)) { - initialReceptivity = receptivityState; +function addExternalScriptEventListener(script, tagId) { + script.addEventListener( + 'rxConnectorIsReady', + async ({ detail: rxConnector }) => { + // Fetch the customer configuration + const { rxApiBuilder, fetchConfig } = rxConnector; + let config = await fetchConfig(tagId); + if (!config) { + return; + } + rxApi = await rxApiBuilder(config); } - }); - - script.addEventListener('rxEngineIsReady', ({ detail: api }) => { - contxtfulModule = api; - }); -} - -/** - * Return current receptivity. - * @return { { ReceptivityState: String } } - */ -function getReceptivity() { - return { - ReceptivityState: contxtfulModule?.GetReceptivity()?.ReceptivityState || initialReceptivity - }; + ); } /** * Set targeting data for ad server * @param { [String] } adUnits - * @param {*} _config + * @param {*} config * @param {*} _userConsent - * @return {{ code: { ReceptivityState: String } }} + * @return {{ code: { ReceptivityState: String } }} */ -function getTargetingData(adUnits, _config, _userConsent) { - logInfo(MODULE, 'getTargetingData'); - if (!adUnits) { +function getTargetingData(adUnits, config, _userConsent) { + try { + if (String(config?.params?.adServerTargeting) === 'false') { + return {}; + } + logInfo(MODULE, 'getTargetingData'); + + const requester = config?.params?.customer; + const rx = getRxEngineReceptivity(requester) || + loadSessionReceptivity(requester) || {}; + if (isEmpty(rx)) { + return {}; + } + + return adUnits.reduce((targets, code) => { + targets[code] = rx; + return targets; + }, {}); + } catch (error) { + logError(MODULE, error); return {}; } +} + +/** + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} onDone Called on completion + * @param {Object} config Configuration for Contxtful RTD module + * @param {Object} userConsent + */ +function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { + function onReturn() { + if (isFirstBidRequestCall) { + isFirstBidRequestCall = false; + }; + onDone(); + } + logInfo(MODULE, 'getBidRequestData'); + const bidders = config?.params?.bidders || []; + if (isEmpty(bidders) || !isArray(bidders)) { + onReturn(); + return; + } - const receptivity = getReceptivity(); - if (!receptivity?.ReceptivityState) { + let fromApiBatched = () => rxApi?.receptivityBatched?.(bidders); + let fromApiSingle = () => prepareBatch(bidders, getRxEngineReceptivity); + let fromStorage = () => prepareBatch(bidders, loadSessionReceptivity); + + function tryMethods(methods) { + for (let method of methods) { + try { + let batch = method(); + if (!isEmpty(batch)) { + return batch; + } + } catch (error) { } + } return {}; } + let rxBatch = {}; + try { + if (isFirstBidRequestCall) { + rxBatch = tryMethods([fromStorage, fromApiBatched, fromApiSingle]); + } else { + rxBatch = tryMethods([fromApiBatched, fromApiSingle, fromStorage]) + } + } catch (error) { } - return adUnits.reduce((targets, code) => { - targets[code] = receptivity; - return targets; - }, {}); -} + if (isEmpty(rxBatch)) { + onReturn(); + return; + } + + bidders + .map((bidderCode) => ({ bidderCode, rx: rxBatch[bidderCode] })) + .filter(({ rx }) => !isEmpty(rx)) + .forEach(({ bidderCode, rx }) => { + const ortb2 = { + user: { + data: [ + { + name: MODULE_NAME, + ext: { + rx, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments?.bidder, { + [bidderCode]: ortb2, + }); + }); + + onReturn(); +}; export const contxtfulSubmodule = { name: MODULE_NAME, init, - extractParameters, getTargetingData, + getBidRequestData, }; submodule('realTimeData', contxtfulSubmodule); diff --git a/modules/contxtfulRtdProvider.md b/modules/contxtfulRtdProvider.md index dfefca2067a..71a641db4ad 100644 --- a/modules/contxtfulRtdProvider.md +++ b/modules/contxtfulRtdProvider.md @@ -2,25 +2,42 @@ **Module Name:** Contxtful RTD Provider **Module Type:** RTD Provider -**Maintainer:** [prebid@contxtful.com](mailto:prebid@contxtful.com) +**Maintainer:** [contact@contxtful.com](mailto:contact@contxtful.com) # Description The Contxtful RTD module offers a unique feature—Receptivity. Receptivity is an efficiency metric, enabling the qualification of any instant in a session in real time based on attention. The core idea is straightforward: the likelihood of an ad’s success increases when it grabs attention and is presented in the right context at the right time. -To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please contact [prebid@contxtful.com](mailto:prebid@contxtful.com). - -# Configuration +To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please contact [contact@contxtful.com](mailto:contact@contxtful.com). ## Build Instructions To incorporate this module into your `prebid.js`, compile the module using the following command: ```sh -gulp build --modules=contxtfulRtdProvider, +gulp build --modules=rtdModule,contxtfulRtdProvider, +``` + +## Testing + +To run the test server locally: +```sh +gulp serve --modules=rtdModule,contxtfulRtdProvider, --fix --nolint --notest +chrome http://localhost:9999/integrationExamples/gpt/contxtfulRtdProvider_example.html ``` -## Module Configuration +To run the unit tests: + +```bash +gulp test +``` + +To run the unit tests for a particular file: +```bash +gulp test --file "test/spec/modules/contxtfulRtdProvider_spec.js" --nolint +``` + +## Configuration Configure the `contxtfulRtdProvider` by passing the required settings through the `setConfig` function in `prebid.js`. @@ -35,31 +52,53 @@ pbjs.setConfig({ "name": "contxtful", "waitForIt": true, "params": { - "version": "", - "customer": "" + "version": "Contact contact@contxtful.com for the API version", + "customer": "Contact contact@contxtful.com for the customer ID", + "hostname": "api.receptivity.io", // Optional, default: "api.receptivity.io" + "bidders": ["bidderCode1", "bidderCode", "..."], // list of bidders + "adServerTargeting": true, // Optional, default: true } } ] } }); ``` +## Parameters -### Configuration Parameters - -| Name | Type | Scope | Description | -|------------|----------|----------|-------------------------------------------| -| `version` | `string` | Required | Specifies the API version of Contxtful. | -| `customer` | `string` | Required | Your unique customer identifier. | +| Name | Type | Scope | Description | +|---------------------|----------|----------|--------------------------------------------| +| `version` | `String` | Required | Specifies the version of the Contxtful Receptivity API. | +| `customer` | `String` | Required | Your unique customer identifier. | +| `hostname` | `String` | Optional | Target URL for CONTXTFUL external JavaScript file. Default is "api.receptivity.io". Changing default behaviour is not recommended. Please reach out to contact@contxtful.com if you experience issues. | +| `adServerTargeting` | `Boolean`| Optional | Enables the `getTargetingData` to inject targeting value in ad units. Setting to true enables the feature, false disables the feature. Default is true | +| `bidders` | `Array` | Optional | Setting this array enables Receptivity in the `ortb2` object through `getBidRequestData` for all the listed `bidders`. Default is `[]` (an empty array). RECOMMENDED : Add all the bidders active like this `["bidderCode1", "bidderCode", "..."]` | -# Usage +## Usage: Injection in Ad Servers The `contxtfulRtdProvider` module loads an external JavaScript file and authenticates with Contxtful APIs. The `getTargetingData` function then adds a `ReceptivityState` to each ad slot, which can have one of two values: `Receptive` or `NonReceptive`. ```json { "adUnitCode1": { "ReceptivityState": "Receptive" }, - "adUnitCode2": { "ReceptivityState": "NonReceptive" } + "adUnitCode2": { "ReceptivityState": "Receptive" } } ``` -This module also integrates seamlessly with Google Ad Manager, ensuring that the `ReceptivityState` is available as early as possible in the ad serving process. \ No newline at end of file +This module also integrates seamlessly with Google Ad Manager, ensuring that the `ReceptivityState` is available as early as possible in the ad serving process. + +## Usage: Injection in ortb2 for bidders + +Setting the `bidders` field in the configuration parameters enables Receptivity in the `ortb2` object through `getBidRequestData` for all the listed bidders. +On a Bid Request Event, all bidders in the configuration will inherit the Receptivity data through `ortb2` +Default is `[]` (an empty array) + +RECOMMENDED : Add all the bidders active like this `["bidderCode1", "bidderCode", "..."]` + +## Links + +- [Basic Prebid.js Example](https://docs.prebid.org/dev-docs/examples/basic-example.html) +- [How Bid Adapters Should Read First Party Data](https://docs.prebid.org/features/firstPartyData.html#how-bid-adapters-should-read-first-party-data) +- [getBidRequestData](https://docs.prebid.org/dev-docs/add-rtd-submodule.html#getbidrequestdata) +- [getTargetingData](https://docs.prebid.org/dev-docs/add-rtd-submodule.html#gettargetingdata) +- [Contxtful Documentation](https://documentation.contxtful.com/) + diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index 541c0e6e6dd..01e7a242d19 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -1,25 +1,40 @@ -import { contxtfulSubmodule } from '../../../modules/contxtfulRtdProvider.js'; +import { contxtfulSubmodule, extractParameters } from '../../../modules/contxtfulRtdProvider.js'; import { expect } from 'chai'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; - import * as events from '../../../src/events'; -const _ = null; const VERSION = 'v1'; const CUSTOMER = 'CUSTOMER'; -const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/p.js`; -const INITIAL_RECEPTIVITY = { ReceptivityState: 'INITIAL_RECEPTIVITY' }; -const INITIAL_RECEPTIVITY_EVENT = new CustomEvent('initialReceptivity', { detail: INITIAL_RECEPTIVITY }); +const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/rxConnector.js`; + +const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_from_session_storage' }; +const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' }; + +const RX_API_MOCK = { receptivity: sinon.stub(), }; +const RX_CONNECTOR_MOCK = { + fetchConfig: sinon.stub(), + rxApiBuilder: sinon.stub(), +}; -const CONTXTFUL_API = { GetReceptivity: sinon.stub() } -const RX_ENGINE_IS_READY_EVENT = new CustomEvent('rxEngineIsReady', {detail: CONTXTFUL_API}); +const TIMEOUT = 10; +const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: RX_CONNECTOR_MOCK }); + +function writeToStorage(requester, timeDiff) { + let rx = RX_FROM_SESSION_STORAGE; + let exp = new Date().getTime() + timeDiff; + let item = { rx, exp, }; + sessionStorage.setItem(requester, JSON.stringify(item),); +} function buildInitConfig(version, customer) { return { name: 'contxtful', params: { - version, - customer, + version: version, + customer: customer, + hostname: 'api.receptivity.io', + bidders: ['mock-bidder-code'], + adServerTargeting: true, }, }; } @@ -33,9 +48,19 @@ describe('contxtfulRtdProvider', function () { loadExternalScriptTag = document.createElement('script'); loadExternalScriptStub.callsFake((_url, _moduleName) => loadExternalScriptTag); - CONTXTFUL_API.GetReceptivity.reset(); + RX_API_MOCK.receptivity.reset(); + RX_API_MOCK.receptivity.callsFake((tagId) => RX_FROM_API); + + RX_CONNECTOR_MOCK.fetchConfig.reset(); + RX_CONNECTOR_MOCK.fetchConfig.callsFake((tagId) => new Promise((resolve, reject) => resolve({ tag_id: tagId }))); + + RX_CONNECTOR_MOCK.rxApiBuilder.reset(); + RX_CONNECTOR_MOCK.rxApiBuilder.callsFake((_config) => new Promise((resolve, reject) => resolve(RX_API_MOCK))); eventsEmitSpy = sandbox.spy(events, ['emit']); + + let tagId = CUSTOMER; + sessionStorage.clear(); }); afterEach(function () { @@ -43,7 +68,7 @@ describe('contxtfulRtdProvider', function () { sandbox.restore(); }); - describe('extractParameters with invalid configuration', () => { + describe('extractParameters', () => { const { params: { customer, version }, } = buildInitConfig(VERSION, CUSTOMER); @@ -87,27 +112,27 @@ describe('contxtfulRtdProvider', function () { theories.forEach(([params, expectedErrorMessage, _description]) => { const config = { name: 'contxtful', params }; - it('throws the expected error', () => { - expect(() => contxtfulSubmodule.extractParameters(config)).to.throw( + it('detects invalid configuration and throws the expected error', () => { + expect(() => extractParameters(config)).to.throw( expectedErrorMessage ); }); }); }); - describe('initialization with invalid config', function () { - it('returns false', () => { + describe('extractParameters', function () { + it('detects invalid configuration and returns false', () => { expect(contxtfulSubmodule.init({})).to.be.false; }); }); - describe('initialization with valid config', function () { - it('returns true when initializing', () => { + describe('init', function () { + it('uses a valid configuration and returns true when initializing', () => { const config = buildInitConfig(VERSION, CUSTOMER); expect(contxtfulSubmodule.init(config)).to.be.true; }); - it('loads contxtful module script asynchronously', (done) => { + it('loads a RX connector script asynchronously', (done) => { contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); setTimeout(() => { @@ -115,54 +140,84 @@ describe('contxtfulRtdProvider', function () { expect(loadExternalScriptStub.args[0][0]).to.equal( CONTXTFUL_CONNECTOR_ENDPOINT ); + done(); - }, 10); + }, TIMEOUT); }); }); - describe('load external script return falsy', function () { + describe('init', function () { it('returns true when initializing', () => { - loadExternalScriptStub.callsFake(() => {}); + loadExternalScriptStub.callsFake((url, moduleCode, callback, doc, attributes) => { + return { addEventListener: (type, listener) => { } }; + }); const config = buildInitConfig(VERSION, CUSTOMER); expect(contxtfulSubmodule.init(config)).to.be.true; }); }); - describe('rxEngine from external script', function () { - it('use rxEngine api to get receptivity', () => { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); - loadExternalScriptTag.dispatchEvent(RX_ENGINE_IS_READY_EVENT); + describe('init', function () { + it('gets the RX API returned by an external script', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); - contxtfulSubmodule.getTargetingData(['ad-slot']); + setTimeout(() => { + contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(RX_CONNECTOR_MOCK.fetchConfig.callCount, 'fetchConfig').to.be.equal(1); + expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount, 'rxApiBuilder').to.be.equal(1); + done(); + }, TIMEOUT); + }); + }); + + describe('init', function () { + it('uses the RX API to get receptivity', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); - expect(CONTXTFUL_API.GetReceptivity.calledOnce).to.be.true; + setTimeout(() => { + contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(RX_API_MOCK.receptivity.callCount, 'receptivity 42').to.be.equal(1); + expect(RX_API_MOCK.receptivity.firstCall.returnValue, 'receptivity').to.be.equal(RX_FROM_API); + done(); + }, TIMEOUT); }); }); - describe('initial receptivity is not dispatched', function () { - it('does not initialize receptivity value', () => { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + describe('init', function () { + it('detect that initial receptivity is not dispatched and it does not initialize receptivity value', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); - let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot']); - expect(targetingData).to.deep.equal({}); + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(targetingData).to.deep.equal({}); + done(); + }, TIMEOUT); }); }); - describe('initial receptivity is invalid', function () { + describe('init', function () { const theories = [ [new Event('initialReceptivity'), 'event without details'], - [new CustomEvent('initialReceptivity', { }), 'custom event without details'], + [new CustomEvent('initialReceptivity', {}), 'custom event without details'], [new CustomEvent('initialReceptivity', { detail: {} }), 'custom event with invalid details'], [new CustomEvent('initialReceptivity', { detail: { ReceptivityState: '' } }), 'custom event with details without ReceptivityState'], ]; theories.forEach(([initialReceptivityEvent, _description]) => { - it('does not initialize receptivity value', () => { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + it('figures out that initial receptivity is invalid and it does not initialize receptivity value', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); loadExternalScriptTag.dispatchEvent(initialReceptivityEvent); - let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot']); - expect(targetingData).to.deep.equal({}); + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(targetingData).to.deep.equal({}); + done(); + }, TIMEOUT); }); }) }); @@ -173,28 +228,334 @@ describe('contxtfulRtdProvider', function () { [[], {}, 'empty ad-slots'], [ ['ad-slot'], - { 'ad-slot': { ReceptivityState: 'INITIAL_RECEPTIVITY' } }, + { 'ad-slot': RX_FROM_API }, 'single ad-slot', ], [ ['ad-slot-1', 'ad-slot-2'], { - 'ad-slot-1': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, - 'ad-slot-2': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, + 'ad-slot-1': RX_FROM_API, + 'ad-slot-2': RX_FROM_API, + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, description]) => { + it('adds receptivity to the ad units using the RX API', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(targetingData, description).to.deep.equal(expected); + done(); + }, TIMEOUT); + }); + }); + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + {}, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, description]) => { + it('honours "adServerTargeting" and the RX API is not called', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + config.params.adServerTargeting = false; + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + let _ = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(RX_API_MOCK.receptivity.callCount).to.be.equal(0); + done(); + }, TIMEOUT); + }); + + it('honours adServerTargeting and it does not add receptivity to the ad units', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + config.params.adServerTargeting = false; + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(targetingData, description).to.deep.equal(expected); + done(); + }, TIMEOUT); + }); + }); + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + { 'ad-slot': RX_FROM_SESSION_STORAGE }, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + 'ad-slot-1': RX_FROM_SESSION_STORAGE, + 'ad-slot-2': RX_FROM_SESSION_STORAGE, }, 'many ad-slots', ], ]; theories.forEach(([adUnits, expected, _description]) => { - it('adds "ReceptivityState" to the adUnits', function () { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); - loadExternalScriptTag.dispatchEvent(INITIAL_RECEPTIVITY_EVENT); + it('uses non-expired info from session storage and adds receptivity to the ad units using session storage', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + // Simulate that there was a write to sessionStorage in the past. + writeToStorage(config.params.customer, +100); + contxtfulSubmodule.init(config); - expect(contxtfulSubmodule.getTargetingData(adUnits)).to.deep.equal( + setTimeout(() => { + expect(contxtfulSubmodule.getTargetingData(adUnits, config)).to.deep.equal( + expected + ); + done(); + }, TIMEOUT); + }); + }); + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + {}, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, _description]) => { + it('ignores expired info from session storage and does not forward the info to ad units', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + // Simulate that there was a write to sessionStorage in the past. + writeToStorage(config.params.customer, -100); + contxtfulSubmodule.init(config); + expect(contxtfulSubmodule.getTargetingData(adUnits, config)).to.deep.equal( expected ); + done(); }); }); }); + + describe('getBidRequestData', function () { + it('calls once the onDone callback', function (done) { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, buildInitConfig(VERSION, CUSTOMER)); + expect(onDoneSpy.calledOnce).to.be.true; + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('does not write receptivity to the global OpenRTB 2 fragment', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDone = () => 42; + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDone, config); + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal({}); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('writes receptivity to the configured bidder OpenRTB 2 fragments', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let expectedOrtb2 = { + user: { + data: [ + { + name: 'contxtful', + ext: { + rx: RX_FROM_API, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + + setTimeout(() => { + const onDone = () => undefined; + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDone, config); + let actualOrtb2 = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]; + expect(actualOrtb2).to.deep.equal(expectedOrtb2); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('uses non-expired info from session storage and adds receptivity to the reqBidsConfigObj', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + // Simulate that there was a write to sessionStorage in the past. + writeToStorage(config.params.bidders[0], +100); + + contxtfulSubmodule.init(config); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let expectedOrtb2 = { + user: { + data: [ + { + name: 'contxtful', + ext: { + rx: RX_FROM_SESSION_STORAGE, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + + // Since the RX_CONNECTOR_IS_READY_EVENT event was not dispatched, the RX engine is not loaded. + setTimeout(() => { + const noOp = () => undefined; + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, noOp, buildInitConfig(VERSION, CUSTOMER)); + let actualOrtb2 = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]; + expect(actualOrtb2).to.deep.equal(expectedOrtb2); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('uses the RX API', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + expect(RX_CONNECTOR_MOCK.fetchConfig.callCount).to.equal(1); + expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount).to.equal(1); + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + expect(onDoneSpy.callCount).to.equal(1); + expect(RX_API_MOCK.receptivity.callCount).to.equal(1); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('adds receptivity to the reqBidsConfigObj', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let ortb2 = { + user: { + data: [ + { + name: 'contxtful', + ext: { + rx: RX_FROM_API, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + expect(reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]).to.deep.equal(ortb2); + done(); + }, TIMEOUT); + }); + }); }); From 38ca7c230e5b58174d3583ceab541b7b6078b11b Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 29 May 2024 23:04:34 +0300 Subject: [PATCH 0112/1097] Add vidazoo bidder to topicsFpdModule. (#11283) --- modules/topicsFpdModule.js | 3 +++ modules/topicsFpdModule.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 72f4068af7f..523c0db326a 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -47,6 +47,9 @@ const bidderIframeList = { }, { bidder: 'undertone', iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html' + }, { + bidder: 'vidazoo', + iframeURL: 'https://static.vidazoo.com/topics_api/topics_frame.html' }] } diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index d187645f520..1f299a9eabe 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -68,6 +68,10 @@ pbjs.setConfig({ bidder: 'undertone', iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html', expiry: 7 // Configurable expiry days + },{ + bidder: 'vidazoo', + iframeURL: 'https://static.vidazoo.com/topics_api/topics_frame.html', + expiry: 7 // Configurable expiry days }] } .... From 84e4359543eb582b8821e0163078f8db701af84e Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Wed, 29 May 2024 23:26:33 +0300 Subject: [PATCH 0113/1097] Taboola Bid Adapter: fix ortb2 user override issue (#11516) * pass-user-fields * pass-user-fields * tests added --- modules/taboolaBidAdapter.js | 49 +++++++++--------- test/spec/modules/taboolaBidAdapter_spec.js | 57 ++++++++++++++++++--- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 74a614bc6b0..ab5d5fef139 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -64,6 +64,7 @@ export const userData = { return tblaId; } } + return undefined; }, getCookieDataByKey(cookieData, key) { if (!cookieData) { @@ -78,6 +79,7 @@ export const userData = { if (hasLocalStorage() && localStorageIsEnabled()) { return getDataFromLocalStorage(STORAGE_KEY); } + return undefined; }, getFromTRC() { return window.TRC ? window.TRC.user_id : 0; @@ -274,35 +276,38 @@ function getSiteProperties({publisherId}, refererInfo, ortb2) { function fillTaboolaReqData(bidderRequest, bidRequest, data) { const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); - const device = {ua: navigator.userAgent}; - let user = { - buyeruid: userData.getUserId(gdprConsent, uspConsent), - ext: {} - }; - if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.user) { - user.data = bidderRequest.ortb2.user.data; + deepSetValue(data, 'device.ua', navigator.userAgent); + const extractedUserId = userData.getUserId(gdprConsent, uspConsent); + if (data.user == undefined) { + data.user = { + buyeruid: 0, + ext: {} + } } - const regs = { - coppa: 0, - ext: {} - }; - + if (extractedUserId && extractedUserId !== 0) { + deepSetValue(data, 'user.buyeruid', extractedUserId); + } + if (data.regs?.ext == undefined) { + data.regs = { + ext: {} + } + } + deepSetValue(data, 'regs.coppa', 0); if (gdprConsent.gdprApplies) { - user.ext.consent = bidderRequest.gdprConsent.consentString; - regs.ext.gdpr = 1; + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(data, 'regs.ext.gdpr', 1); } - if (uspConsent) { - regs.ext.us_privacy = uspConsent; + deepSetValue(data, 'regs.ext.us_privacy', uspConsent); } if (bidderRequest.ortb2?.regs?.gpp) { - regs.ext.gpp = bidderRequest.ortb2.regs.gpp; - regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + deepSetValue(data, 'regs.ext.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(data, 'regs.ext.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); } if (config.getConfig('coppa')) { - regs.coppa = 1; + deepSetValue(data, 'regs.coppa', 1); } const ortb2 = bidderRequest.ortb2 || { @@ -311,16 +316,14 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data) { wlang: [] }; + deepSetValue(data, 'source.fd', 1); + data.id = bidderRequest.bidderRequestId; data.site = site; - data.device = device; - data.source = {fd: 1}; data.tmax = (bidderRequest.timeout == undefined) ? undefined : parseInt(bidderRequest.timeout); data.bcat = ortb2.bcat || bidRequest.params.bcat || []; data.badv = ortb2.badv || bidRequest.params.badv || []; data.wlang = ortb2.wlang || bidRequest.params.wlang || []; - data.user = user; - data.regs = regs; deepSetValue(data, 'ext.pageType', ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType); deepSetValue(data, 'ext.prebid.version', '$prebid.version$'); } diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 2ea8c325989..55d0731ec21 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -199,6 +199,13 @@ describe('Taboola Adapter', function () { }], id: 'mock-uuid', 'test': 0, + 'device': {'ua': navigator.userAgent}, + 'user': { + 'buyeruid': 0, + 'ext': {}, + }, + 'regs': {'ext': {}, 'coppa': 0}, + 'source': {'fd': 1}, 'site': { 'id': commonBidRequest.params.publisherId, 'name': commonBidRequest.params.publisherId, @@ -208,16 +215,9 @@ describe('Taboola Adapter', function () { 'publisher': {'id': commonBidRequest.params.publisherId}, 'content': {'language': navigator.language} }, - 'device': {'ua': navigator.userAgent}, - 'source': {'fd': 1}, 'bcat': [], 'badv': [], 'wlang': [], - 'user': { - 'buyeruid': 0, - 'ext': {}, - }, - 'regs': {'coppa': 0, 'ext': {}}, 'ext': { 'prebid': { 'version': '$prebid.version$' @@ -363,12 +363,33 @@ describe('Taboola Adapter', function () { bcat: ['EX1', 'EX2', 'EX3'], badv: ['site.com'], wlang: ['de'], + user: { + id: 'externalUserIdPassed' + } } } const res = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.bcat).to.deep.equal(bidderRequest.ortb2.bcat) expect(res.data.badv).to.deep.equal(bidderRequest.ortb2.badv) expect(res.data.wlang).to.deep.equal(bidderRequest.ortb2.wlang) + expect(res.data.user.id).to.deep.equal(bidderRequest.ortb2.user.id) + }); + + it('should pass user entities', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + user: { + id: 'userid', + buyeruid: 'buyeruid', + yob: 1990 + } + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.user.id).to.deep.equal(bidderRequest.ortb2.user.id) + expect(res.data.user.buyeruid).to.deep.equal(bidderRequest.ortb2.user.buyeruid) + expect(res.data.user.yob).to.deep.equal(bidderRequest.ortb2.user.yob) }); it('should pass pageType if exists in ortb2', function () { @@ -503,6 +524,28 @@ describe('Taboola Adapter', function () { expect(res.data.user.buyeruid).to.equal('12121212'); }); + it('should get buyeruid from cookie as priority and external user id from ortb2 object', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.returns('taboola%20global%3Auser-id=12121212'); + + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + user: { + id: 'userid', + buyeruid: 'buyeruid', + yob: 1990 + } + } + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.user.id).to.deep.equal('userid') + expect(res.data.user.buyeruid).to.equal('12121212'); + }); + it('should get user id from cookie if local storage isn`t defined, only TGID_COOKIE_KEY exists', function () { getDataFromLocalStorage.returns(51525152); hasLocalStorage.returns(false); From 5a62b3863a22dcdac2bebe4c8d0d4fb6f129699f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 29 May 2024 17:22:35 -0700 Subject: [PATCH 0114/1097] Currency module: fix bug where changing currency configs causes currency logic to run multiple times (#11613) --- modules/currency.js | 53 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/modules/currency.js b/modules/currency.js index c26e64a9400..e2da8b519fa 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -155,36 +155,35 @@ function loadRates() { function initCurrency() { conversionCache = {}; - currencySupportEnabled = true; - - logInfo('Installing addBidResponse decorator for currency module', arguments); - - // Adding conversion function to prebid global for external module and on page use - getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); - getHook('addBidResponse').before(addBidResponseHook, 100); - getHook('responsesReady').before(responsesReadyHook); - onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); - onEvent(EVENTS.AUCTION_INIT, loadRates); - loadRates(); + if (!currencySupportEnabled) { + currencySupportEnabled = true; + // Adding conversion function to prebid global for external module and on page use + getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); + getHook('addBidResponse').before(addBidResponseHook, 100); + getHook('responsesReady').before(responsesReadyHook); + onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + onEvent(EVENTS.AUCTION_INIT, loadRates); + loadRates(); + } } function resetCurrency() { - logInfo('Uninstalling addBidResponse decorator for currency module', arguments); - - getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); - getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); - offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); - offEvent(EVENTS.AUCTION_INIT, loadRates); - delete getGlobal().convertCurrency; - - adServerCurrency = 'USD'; - conversionCache = {}; - currencySupportEnabled = false; - currencyRatesLoaded = false; - needToCallForCurrencyFile = true; - currencyRates = {}; - bidderCurrencyDefault = {}; - responseReady = defer(); + if (currencySupportEnabled) { + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); + offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + offEvent(EVENTS.AUCTION_INIT, loadRates); + delete getGlobal().convertCurrency; + + adServerCurrency = 'USD'; + conversionCache = {}; + currencySupportEnabled = false; + currencyRatesLoaded = false; + needToCallForCurrencyFile = true; + currencyRates = {}; + bidderCurrencyDefault = {}; + responseReady = defer(); + } } function responsesReadyHook(next, ready) { From c73147ea3044b72c2c426445b7f3eae853428738 Mon Sep 17 00:00:00 2001 From: Mark Kuhar Date: Thu, 30 May 2024 12:42:09 +0200 Subject: [PATCH 0115/1097] also read plcmt video param (#11618) --- modules/outbrainBidAdapter.js | 2 +- test/spec/modules/outbrainBidAdapter_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 6015ff37e08..9690b79fea5 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -354,7 +354,7 @@ function getVideoAsset(bid) { minduration: bid.mediaTypes.video.minduration, maxduration: bid.mediaTypes.video.maxduration, startdelay: bid.mediaTypes.video.startdelay, - placement: bid.mediaTypes.video.placement, + placement: bid.mediaTypes.video.plcmt ?? bid.mediaTypes.video.placement, linearity: bid.mediaTypes.video.linearity }; } diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index e6abb5e9caa..9825eb3ee79 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -58,7 +58,8 @@ describe('Outbrain Adapter', function () { minduration: 3, maxduration: 10, startdelay: 2, - placement: 4, + plcmt: 4, + placement: 5, linearity: 1 } } From ec7b909980d0827bc0de0bb624d3b3ad42be93af Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Thu, 30 May 2024 13:45:13 +0300 Subject: [PATCH 0116/1097] Adform adapter: ortb2Imp first party data (#11616) --- modules/adfBidAdapter.js | 2 ++ test/spec/modules/adfBidAdapter_spec.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index 881b1adfcc4..27fb36a225d 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -78,6 +78,7 @@ export const spec = { const bidfloor = floorInfo.floor; const bidfloorcur = floorInfo.currency; const { mid, inv, mname } = bid.params; + const impExtData = bid.ortb2Imp?.ext?.data; const imp = { id: id + 1, @@ -85,6 +86,7 @@ export const spec = { bidfloor, bidfloorcur, ext: { + data: impExtData, bidder: { inv, mname diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index d4c5f5c3c38..2ec1cdf719c 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -429,6 +429,18 @@ describe('Adf adapter', function () { } }); + it('should add first party data', function () { + let validBidRequests = [ + { bidId: 'bidId', params: { mid: 1000 }, mediaTypes: { video: {} }, ortb2Imp: { ext: { data: { some: 'value' } } } }, + { bidId: 'bidId2', params: { mid: 1001 }, mediaTypes: { video: {} }, ortb2Imp: { ext: { data: { some: 'value', another: 1 } } } }, + { bidId: 'bidId3', params: { mid: 1002 }, mediaTypes: { video: {} }, ortb2Imp: { ext: {} } } + ]; + let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp; + for (let i = 0; i < 3; i++) { + assert.deepEqual(imps[i].ext.data, validBidRequests[i].ortb2Imp.ext.data); + } + }); + describe('dynamic placement tag', function () { it('should add imp parameters correctly', function () { const validBidRequests = [ From 1e88c2f3d433428aaeda9e21ef784d4af6437a5c Mon Sep 17 00:00:00 2001 From: Scott Sundahl <37344964+ssundahlTTD@users.noreply.github.com> Date: Thu, 30 May 2024 04:59:35 -0600 Subject: [PATCH 0117/1097] Uid2 and friends: Cleanup issues around optout in UID2 and EUID (#11505) * handle optout in UID2 decodeImpl() * better log message when CSTG params are not set * better log message when CSTG params are not set --- integrationExamples/gpt/cstg_example.html | 4 ++-- modules/uid2IdSystem.js | 4 ++++ modules/uid2IdSystem_shared.js | 6 +++++- test/spec/modules/uid2IdSystem_spec.js | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/integrationExamples/gpt/cstg_example.html b/integrationExamples/gpt/cstg_example.html index 8ca049a0ed0..2ca8b43b9de 100644 --- a/integrationExamples/gpt/cstg_example.html +++ b/integrationExamples/gpt/cstg_example.html @@ -29,8 +29,8 @@ function updateUID2GuiElements() { console.log('Updating displayed values.'); const uid2 = pbjs.getUserIds().uid2; - document.querySelector('#uid2TargetedAdvertisingReady').innerText = uid2 ? 'yes' : 'no'; - document.querySelector('#uid2AdvertisingToken').innerText = uid2 ? String(uid2.id) : ''; + document.querySelector('#uid2TargetedAdvertisingReady').innerText = uid2 && !uid2.optout ? 'yes' : 'no'; + document.querySelector('#uid2AdvertisingToken').innerText = uid2 && !uid2.optout ? String(uid2.id) : uid2 && uid2.optout ? 'Optout' : ''; if (!uid2) { document.querySelector('#uid2LoginForm').style['display'] = 'block'; document.querySelector('#uid2ClearStorageForm').style['display'] = 'none';; diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 32d2322e9bd..061f0f981e5 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -109,6 +109,10 @@ function decodeImpl(value) { const result = { uid2: { id: value } }; return result; } + if (value.latestToken === 'optout') { + _logInfo('Found optout token. Refresh is unavailable for this token.'); + return { uid2: { optout: true } }; + } if (Date.now() < value.latestToken.identity_expires) { return { uid2: { id: value.latestToken.advertising_token } }; } diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index f3702069d10..808a61cb388 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -187,11 +187,15 @@ if (FEATURES.UID2_CSTG) { clientSideTokenGenerator = { isCSTGOptionsValid(maybeOpts, _logWarn) { if (typeof maybeOpts !== 'object' || maybeOpts === null) { - _logWarn('CSTG opts must be an object'); + _logWarn('CSTG is not being used, but is included in the Prebid.js bundle. You can reduce the bundle size by passing "--disable UID2_CSTG" to the Prebid.js build.'); return false; } const opts = maybeOpts; + if (!opts.serverPublicKey && !opts.subscriptionId) { + _logWarn('CSTG has been enabled but its parameters have not been set.'); + return false; + } if (typeof opts.serverPublicKey !== 'string') { _logWarn('CSTG opts.serverPublicKey must be a string'); return false; diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 901e0c57e32..6e5011164cb 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -25,6 +25,7 @@ const initialToken = `initial-advertising-token`; const legacyToken = 'legacy-advertising-token'; const refreshedToken = 'refreshed-advertising-token'; const clientSideGeneratedToken = 'client-side-generated-advertising-token'; +const optoutToken = 'optout-token'; const legacyConfigParams = {storage: null}; const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName }; @@ -32,6 +33,7 @@ const newServerCookieConfigParams = { uid2Cookie: publisherCookieName }; const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' } const makeUid2IdentityContainer = (token) => ({uid2: {id: token}}); +const makeUid2OptoutContainer = (token) => ({uid2: {optout: true}}); let useLocalStorage = false; const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings @@ -49,6 +51,7 @@ const getFromAppropriateStorage = () => { const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2IdentityContainer(token)); const expectLegacyToken = (bid) => expect(bid.userId).to.deep.include(makeUid2IdentityContainer(legacyToken)); const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); +const expectOptout = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2OptoutContainer(token)); const expectGlobalToHaveToken = (token) => expect(getGlobal().getUserIds()).to.deep.include(makeUid2IdentityContainer(token)); const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2'); const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makeUid2IdentityContainer(legacyToken)); @@ -64,6 +67,7 @@ const apiUrl = 'https://prod.uidapi.com/v2/token' const refreshApiUrl = `${apiUrl}/refresh`; const headers = { 'Content-Type': 'application/json' }; const makeSuccessResponseBody = (responseToken) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: responseToken } })); +const makeOptoutResponseBody = (token) => btoa(JSON.stringify({ status: 'optout', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } })); const cstgApiUrl = `${apiUrl}/client-generate`; const testCookieAndLocalStorage = (description, test, only = false) => { @@ -120,6 +124,7 @@ describe(`UID2 module`, function () { const configureUid2Response = (apiUrl, httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); const configureUid2ApiSuccessResponse = (apiUrl, responseToken) => configureUid2Response(apiUrl, 200, makeSuccessResponseBody(responseToken)); const configureUid2ApiFailResponse = (apiUrl) => configureUid2Response(apiUrl, 500, 'Error'); + const configureUid2CstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); // Runs the provided test twice - once with a successful API mock, once with one which returns a server error const testApiSuccessAndFailure = (act, apiUrl, testDescription, failTestDescription, only = false, responseToken = refreshedToken) => { const testFn = only ? it.only : it; @@ -442,6 +447,15 @@ describe(`UID2 module`, function () { }); }); }); + it('Should receive an optout response when the user has opted out.', async function() { + const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); + configureUid2CstgResponse(200, makeOptoutResponseBody(optoutToken)); + config.setConfig(makePrebidConfig({ uid2Token, ...cstgConfigParams, email: 'optout@test.com' })); + apiHelpers.respondAfterDelay(1, server); + + const bid = await runAuction(); + expectOptout(bid, optoutToken); + }); describe(`when the response doesn't arrive before the auction timer`, function() { testApiSuccessAndFailure(async function() { config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); From 42fd6f214adc02449f52b526332ad65b9f3c5249 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 30 May 2024 07:40:51 -0400 Subject: [PATCH 0118/1097] Update autoplay.js (#11622) --- libraries/autoplayDetection/autoplay.js | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/autoplayDetection/autoplay.js b/libraries/autoplayDetection/autoplay.js index b598e46cbd1..99922da7b49 100644 --- a/libraries/autoplayDetection/autoplay.js +++ b/libraries/autoplayDetection/autoplay.js @@ -1,6 +1,7 @@ let autoplayEnabled = null; /** + * DEVELOPER WARNING: IMPORTING THIS LIBRARY MAY MAKE YOUR ADAPTER NO LONGER COMPATIBLE WITH APP PUBLISHERS USING WKWEBVIEW * Note: this function returns true if detection is not done yet. This is by design: if autoplay is not allowed, * the call to video.play() will fail immediately, otherwise it may not terminate. * @returns true if autoplay is not forbidden From 7defcab7953e38c8afd3478631202116956d3340 Mon Sep 17 00:00:00 2001 From: pm-nitin-shirsat <107102698+pm-nitin-shirsat@users.noreply.github.com> Date: Thu, 30 May 2024 18:51:33 +0530 Subject: [PATCH 0119/1097] PubmaticBidAdapter: Using plcmt instead of placement (#11623) * Implement functionality for deal priority * Update test cases * kick off test manually * Added support of GPP to PubMatic adapter * gpp_sid in user syncs supposed to encode as a string, not an array * Remove extra space * Remove trailing spaces * Remove the placement parameter and update test cases accordingly, Add plcmt parameter. * Supporting placement parameter and logging warning message, for the plcmt parameter, if it is missing. * Remove commented code --------- Co-authored-by: Chris Huie --- modules/pubmaticBidAdapter.js | 6 +++--- test/spec/modules/pubmaticBidAdapter_spec.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a0e117a2e1b..5b470fdc34a 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -25,7 +25,7 @@ const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application -const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; +const MSG_VIDEO_PLCMT_MISSING = 'Video.plcmt param missing'; const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url @@ -560,8 +560,8 @@ function _createBannerRequest(bid) { export function checkVideoPlacement(videoData, adUnitCode) { // Check for video.placement property. If property is missing display log message. - if (FEATURES.VIDEO && !deepAccess(videoData, 'placement')) { - logWarn(MSG_VIDEO_PLACEMENT_MISSING + ' for ' + adUnitCode); + if (FEATURES.VIDEO && !deepAccess(videoData, 'plcmt')) { + logWarn(MSG_VIDEO_PLCMT_MISSING + ' for ' + adUnitCode); }; } diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index e47d301fae4..745def57f4e 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -4046,10 +4046,10 @@ describe('PubMatic adapter', function () { }); if (FEATURES.VIDEO) { - describe('Checking for Video.Placement property', function() { + describe('Checking for Video.plcmt property', function() { let sandbox, utilsMock; const adUnit = 'Div1'; - const msg_placement_missing = 'Video.Placement param missing for Div1'; + const msg_placement_missing = 'Video.plcmt param missing for Div1'; let videoData = { battr: [6, 7], skipafter: 15, @@ -4073,12 +4073,12 @@ describe('PubMatic adapter', function () { sandbox.restore(); }) - it('should log Video.Placement param missing', function() { + it('should log Video.plcmt param missing', function() { checkVideoPlacement(videoData, adUnit); sinon.assert.calledWith(utils.logWarn, msg_placement_missing); }) - it('shoud not log Video.Placement param missing', function() { - videoData['placement'] = 1; + it('shoud not log Video.plcmt param missing', function() { + videoData['plcmt'] = 1; checkVideoPlacement(videoData, adUnit); sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); }) From d9dbb423769c4798995d3f2fcb888dfebce8bf10 Mon Sep 17 00:00:00 2001 From: adxpremium <55161519+adxpremium@users.noreply.github.com> Date: Thu, 30 May 2024 15:32:26 +0200 Subject: [PATCH 0120/1097] Fix (9.0 fixes): Remove discontinued fpd.context, remove unused onBidWon event (#11619) Co-authored-by: Lupon Media --- modules/luponmediaBidAdapter.js | 18 +------- .../spec/modules/luponmediaBidAdapter_spec.js | 45 ------------------- 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 20fa601bade..2c08ca3a435 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -14,7 +14,6 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER} from '../src/mediaTypes.js'; -import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'luponmedia'; const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; @@ -220,19 +219,6 @@ export const spec = { hasSynced = true; return allUserSyncs; - }, - onBidWon: bid => { - const bidString = JSON.stringify(bid); - spec.sendWinningsToServer(bidString); - }, - sendWinningsToServer: data => { - let mutation = `mutation {createWin(input: {win: {eventData: "${window.btoa(data)}"}}) {win {createTime } } }`; - let dataToSend = JSON.stringify({ query: mutation }); - - ajax('https://analytics.adxpremium.services/graphql', null, dataToSend, { - contentType: 'application/json', - method: 'POST' - }); } }; @@ -469,9 +455,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - // TODO: bidRequest.fpd is not the right place for pbadslot - who's filling that in, if anyone? - // is this meant to be bidRequest.ortb2Imp.ext.data.pbadslot? - const pbAdSlot = deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 1441abc0fe8..064bad74835 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -385,49 +385,4 @@ describe('luponmediaBidAdapter', function () { expect(checkSchain).to.equal(false); }); }); - - describe('onBidWon', function () { - const bidWonEvent = { - 'bidderCode': 'luponmedia', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '105bbf8c54453ff', - 'requestId': '934b8752185955', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.364, - 'creativeId': '443801010', - 'currency': 'USD', - 'netRevenue': false, - 'ttl': 300, - 'referrer': '', - 'ad': '', - 'auctionId': '926a8ea3-3dd4-4bf2-95ab-c85c2ce7e99b', - 'responseTimestamp': 1598527728026, - 'requestTimestamp': 1598527727629, - 'bidder': 'luponmedia', - 'adUnitCode': 'div-gpt-ad-1533155193780-5', - 'timeToRespond': 397, - 'size': '300x250', - 'status': 'rendered' - }; - - let ajaxStub; - - beforeEach(() => { - ajaxStub = sinon.stub(spec, 'sendWinningsToServer') - }) - - afterEach(() => { - ajaxStub.restore() - }) - - it('calls luponmedia\'s callback endpoint', () => { - const result = spec.onBidWon(bidWonEvent); - expect(result).to.equal(undefined); - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.deep.equal(JSON.stringify(bidWonEvent)); - }); - }); }); From 012a83e76ff810d07ac095a0de46b50e9c797dfd Mon Sep 17 00:00:00 2001 From: qt-io <104574052+qt-io@users.noreply.github.com> Date: Thu, 30 May 2024 16:44:16 +0300 Subject: [PATCH 0121/1097] QT Bid Adapter : initial release (#11529) * New Adapter: QT * changed coppa retrieving --- modules/qtBidAdapter.js | 239 ++++++++++++ modules/qtBidAdapter.md | 79 ++++ test/spec/modules/qtBidAdapter_spec.js | 501 +++++++++++++++++++++++++ 3 files changed, 819 insertions(+) create mode 100644 modules/qtBidAdapter.js create mode 100644 modules/qtBidAdapter.md create mode 100644 test/spec/modules/qtBidAdapter_spec.js diff --git a/modules/qtBidAdapter.js b/modules/qtBidAdapter.js new file mode 100644 index 00000000000..e26aad8f9ec --- /dev/null +++ b/modules/qtBidAdapter.js @@ -0,0 +1,239 @@ +import { logMessage, logError, deepAccess } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'qt'; +const AD_URL = 'https://endpoint1.qt.io/pbjs'; +const SYNC_URL = 'https://cs.qt.io'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.plcmt = mediaTypes[VIDEO].plcmt; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; + } + + return placement; +} + +function getBidFloor(bid) { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, + tmax: bidderRequest.timeout + }; + + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl += '&gpp=' + gppConsent.gppString; + syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/qtBidAdapter.md b/modules/qtBidAdapter.md new file mode 100644 index 00000000000..6d505b38379 --- /dev/null +++ b/modules/qtBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: QT Bidder Adapter +Module Type: QT Bidder Adapter +Maintainer: octavian.cimpu@qt.io +``` + +# Description + +Connects to QT exchange for bids. +QT bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'qt', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'qt', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'qt', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js new file mode 100644 index 00000000000..e573a6ae0e9 --- /dev/null +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -0,0 +1,501 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/qtBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'qt'; + +describe('QTBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com' + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.qt.io/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.qt.io/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.qt.io/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); From 5625bcc6a7a9847a2ab6d9d150f9aca01854904c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 30 May 2024 15:12:36 +0000 Subject: [PATCH 0122/1097] Prebid 8.51.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 7c28a82a9f5..ecf188da606 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.51.0-pre", + "version": "8.51.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c6d19acbe1e..545cf3c1ccc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.51.0-pre", + "version": "8.51.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 646f884115882641601c2df1a3e468a6745ad632 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 30 May 2024 15:12:36 +0000 Subject: [PATCH 0123/1097] Increment version to 8.52.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 ecf188da606..dd9e3ad9d27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.51.0", + "version": "8.52.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 545cf3c1ccc..34d1eb4a593 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.51.0", + "version": "8.52.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 3f20fa3a0e962fa70ddead50493146ea2c5ae845 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 30 May 2024 13:48:23 -0400 Subject: [PATCH 0124/1097] Update tncIdSystem.md (#11627) --- modules/tncIdSystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/tncIdSystem.md b/modules/tncIdSystem.md index f0f98e9098f..b94d03c8e85 100644 --- a/modules/tncIdSystem.md +++ b/modules/tncIdSystem.md @@ -10,6 +10,8 @@ gulp build --modules=tncIdSystem,userId ### TNCIDIdSystem module Configuration +Disclosure: This module loads external script unreviewed by the prebid.js community + You can configure this submodule in your `userSync.userIds[]` configuration: ```javascript From b82d1954eab1622ddbf5f84418fea0d6ef489373 Mon Sep 17 00:00:00 2001 From: Moshe Moses Date: Sun, 2 Jun 2024 19:16:31 +0300 Subject: [PATCH 0125/1097] Gamoshi Bid Adapter : fixes use of `placement` and not `plcmt` (#11647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Playground XYZ adapter - iframe usersync bug fix (#4141) * corrected user sync type * removed support for iframe usersync * added unit tests for getUserSyncs * update nvmrc file (#4162) * update gulp-footer package (#4160) * Datablocks bid/analytics adapter (#4128) * add datablocks Analytics and Bidder Adapters * remove preload param * remove preloadid * better coverage of tests * better coverage * IE doesn't support array.find * lint test * update example host * native asset id should be integer * update logic of ad_types field in appnexusBidAdapter (#4065) * Shorten SomoAudience to just Somo (#4163) * Shorten SomoAudience to just Somo * Make package-lock return * Quantcast: Fix for empty video parameters (#4145) * Copy params from bid.params.video. * Added test for missing video parameters. * Include mimes from adunit. * One Video adding Rewarded Video Feature (#4142) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * Module to pass User Ids to DFP (#4140) * first commit * renamed * minor doc change * documentation * small change * EB * removed unused imports * minor changes * reanmaed a const * adding more methods to test shareUserIds module * unit tets cases for shareUserIds * indentation * renamed DFP to GAM * renamed shareUserIds to userIdTargeting * Update userIdTargeting.md * trying to restart CI * digitrust userId case handled * minor comment change * using auctionEnd event instead of requestBids.before * using events.on * Buzzoola bid adapter (#4127) * initial commit for buzzoola adapter * leave only banners for now * fix bid validation * change endpoint url * add video type * restore renderer * fix renderer * add fixed player sizes * switch bids * convert dimentions to strings * write tests * 100% tests * remove new DOM element creation in tests * handle empty response from server * change description * E2e tests for Native and Outstream video Ad formats. (#4116) * reorganize e2e/ tests into separate directories * new test page for e2e-banner testing * add test to check if Banner Ad is getting loaded * change location of the spec files to reflect change in test/e2e directory structure * add test case to check for generation of valid targeting keys * create Native Ad test page * add test case to check validity of the targeting keys and correct rendering of the Ad * update old browser versions to new * update browser version * update title * remove console.log statements * add basic functional test for e2e outstream video ad format * Update LockerDome adUnitId bid param (#4176) This is not a breaking change * fix several issues in appnexus video bids (#4154) * S2s testing disable client side (#4123) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * New testServerOnly flag * Tests and a bug fix * Removed dead code * Fixes requested in review * Check each adUnit * isTestingServerOnly changes per Eric * Fixed IE 11 bug * More tests * improved test case names * New option to Include deal KVPs when enableSendAllBids === false (#4136) * new option to include KVPs which have deals when enableSendAllBids === false * updating tests to be more realistic * Prebid 2.32.0 Release * increment pre version * Rubicon doc: changing video test zone (#4187) * added schain support to sonobi adapter (#4173) * if schain config is not defined then error should not be thrown (#4165) * if schain config is not defiend then error should not be thrown * relaxed mode nodes param not defined error handled * added test cases for config validation * a curly bracket was missing in the example * Rubicon: updating test params (#4190) * myTargetBidAdapter: support currency config (#4188) * Update README.md (#4193) * Update README.md * Update README.md * cedato bid adapter instream video support (#4153) * Added adxpremium prebid analytics adapter (#4181) * feat(OAFLO-186): added support for schain (#4194) * Sonobi - send entire userid payload (#4196) * added userid param to pass the entire userId payload to sonobis bid request endpoint * removed console log git p * fixed lint * OpenX Adapter fix: updating outdated video examples (#4198) * userId - Add support for refreshing the cached user id (#4082) * [userId] Added support for refreshing the cached user id: refreshInSeconds storage parameter, related tests and implementation in id5 module * [userId] Added support for refreshing the cached user id: refreshInSeconds storage parameter, related tests and implementation in id5 module * UserId - ID5 - Updated doc with new contact point for partners * UserId - Merged getStoredValue and getStoredDate * [UserId] - ID5 - Moved back ID5 in ./modules * UserId - ID5 - Fixed incorrect GDPR condition * [UserId] - Doc update and test cleanup * Prebid 2.33.0 Release * Increment pre version * SupplyChainObject support and fires a pixel onTimeout (#4152) * - Implemented the 'onTimeout' callback to fire a pixel when there's a timeout. - Added the ability to serialize an schain object according to the description provided here: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md * some mods to the schain tag generation * - added tests for schain param checking. * - fixed a malformed url for timeouts * - Removed a trailing ',' while generating a schain param. * - Using the schain object from validBidRequest if present. Reverting to checking if params has it if not. * - reverting changes to merge with master * - Resolving merge issues * Feature/add profile parameter (#4185) * Add optional profile parameter * EMXDigital Bid Adapter: Add video dimensions in request (#4174) * addressed feedback from #3731 ticket * removed commented code from emx test spec * logging removed from spec * flip h & w values from playerSize for video requests * adding Outstream mediaType to EMX Digital * adding device info. update to grab video param. styling changes. * add video dimensions from playerSize * fix test for video dimensions * Added keywords parameter support in TrustX Bid Adapter (#4183) * Add trustx adapter and tests for it * update integration example * Update trustx adapter * Post-review fixes of Trustx adapter * Code improvement for trustx adapter: changed default price type from gross to net * Update TrustX adapter to support the 1.0 version * Make requested changes for TrustX adapter * Updated markdown file for TrustX adapter * Fix TrustX adapter and spec file * Update TrustX adapter: r parameter was added to ad request as cache buster * Add support of gdpr to Trustx Bid Adapter * Add wtimeout to ad request params for TrustX Bid Adapter * TrustX Bid Adapter: remove last ampersand in the ad request * Update TrustX Bid Adapter to support identical uids in parameters * Update TrustX Bid Adapter to ignore bids that sizes do not match the size of the request * Update TrustX Bid Adapter to support instream and outstream video * Added wrapperType and wrapperVersion parameters in ad request for TrustX Bid Adapter * Update TrustX Bid Adapter to use refererInfo instead depricated function utils.getTopWindowUrl * HOTFIX for referrer encodind in TrustX Bid Adapter * Fix test for TrustX Bid Adapter * TrustX Bid Adapter: added keywords passing support * rubicon: avoid passing unknown position (#4207) * rubicon: not passing pos if not specified * added comment * not sending pos for video when undefined * cleaning up test * fixed unit test * correctly reference bidrequest and determine mediatype of bidresponse (#4204) * GumGum: only send gdprConsent when found (#4205) * adds digitrust module, mods gdpr from bool to int * update unit test * only send gdprconsent if present * LKQD: Use refererInfo.referer as fallback pageurl (#4210) * Refactored URL query parameter passthrough for additional values, changed SSP endpoint to v.lkqd.net, and updated associated unit tests * Use refererInfo.referer as fallback pageurl * Removed logs and testing values * [UserId] - ID5 - Fixed case when consentData is undefined (No CMP) (#4215) * create stubs for localStorage in widespaceBidAdapter test file (#4208) * added adId property to adRenderFailed event (#4097) When no bid (therefore no adUnitCode) is available in the adRenderFailed event it can be difficult to identify the erroring slot.But in almost all cases the given slot still has the adId targeting. * OpenX Adapter: Forcing https requests and adding UserID module support for LiveRamp and TTD (#4182) * OpenX Adapter: Updated requests to force https * OpenX Adapter: Added support for TTD's UnifiedID and LiveRamp's IDL * PubMatic to support userId sub-modules (#4191) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * TripleLift support for UnifiedId and IdentityLink (#4197) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * Added lemma adapter (#4126) * lemmaBidAdapter.js Added lemma bid adapter file * lemmaBidAdapter.md Added lemma bid adapter md file * lemmaBidAdapter_spec.js Added lemma bid adapter test spec file * Update lemmaBidAdapter.js Fixed automated code review alert comparison between inconvertible types * Update lemmaBidAdapter.js Fixed review changes * Update lemmaBidAdapter.md Correct parameter value. * Adkernel adapter new alias (#4221) * Force https scheme for Criteo Bidder (#4227) * assign adapter version number * Ensure that Criteo's bidder is always called through https * Add Video Support for Datablocks Bid Adapter (#4195) * add datablocks Analytics and Bidder Adapters * remove preload param * remove preloadid * better coverage of tests * better coverage * IE doesn't support array.find * lint test * update example host * native asset id should be integer * add datablocks Video * remove isInteger * skip if empty * update adUnit, bidRequest and bidResponse object (#4180) * update adUnit, bidRequest and bidResponse object * add test for mediaTypes object * 3 display banner and video vast support for rads (#4209) * add stv adapter * remove comments from adapter file * start rads adapter * fix adapter and tests * fixes * fix adapter and doc * fix adapter * fix tests * little fix * add ip param * fix dev url * #3 radsBidAdapter.md * #3 radsBidAdapter.md: cleanup * fix code and doc * UserId - Add SameSite and server-side pubcid support (#3869) * Add SameSite and server-side pubcid support * Fix emoteevBidAdapter unit test * added schain to appnexus bid adapter (#4229) * added schain to appnexus bid adapter * semicolon * update doubleclick url (#4179) * Prebid 2.34.0 release * increment pre version * Rubi Analytics handles > 1 bidResponse per bidRequest (#4224) * videoNow bid adapter (#4088) * -- first commit * -- cors and bidder's name fixed * -- almost ready * -- added docs * -- added nurl tracking * -- bid params * -- tests added * -- test fixed * -- replace placeholder in the onBidWon pixel's url * -- commit for restart tests * -- change response data format for display ad * -- tests updated * -- 100% tests coverage * -- a few clean the test's code * -- custom urls from localStorage * -- tests updated * -- a few clean the test's code * -- new init model * -- spec for new init model * -- fix for new init model * -- code cleaned * -- 100% tests coverage * -- 100% tests coverage * -- fixed test * -- commit for restart tests * djax new bidder adapter (#4192) * djax bidder adapter * djax bidder adapter * Update hello_world.html * Added Turk Telekom Bid Adapter (#4203) * Added Turk Telekom Bid Adapter * Fix md file for Turk Telekom Bid Adapter * MicroAd: Use HTTPS in all requests (#4220) * Always use HTTPS endpoint in MicroAd * Update code * Fixed a broken test in MicroAd * Schain: avoiding Object.values as it is breaking on IE11 (#4238) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * avoiding use of Object.values * 3952 delay auction for ids (#4115) * 3952 delay auction for user ids * 3952 add integration example * 3952 add tests * 3952 fix html example * add todos * 3952 continue auction if ids received * 3952 add tests for auction delay * increase test coverage * set config for test * remove todo * add a few more checks to tests * add comment, force tests to rerun * Feature: adUnitBidLimit (#3906) * added new feature to config to limit bids when sendallbids is enabled * cleaned up code. removed extra spaces etc * removed trailing spaces in config * remove .flat() and replaced with spread operator * removed flat function and instead pushing using spread operator * updated to use sendBidsControl instead * updated targeting_spec to test bidLimit * removed trailing spaces from targeting_spec * Update Rubicon Adapter netRevenue default (#4242) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Removed AdastaMadia from alias (#4255) * Update appnexusBidAdapter.js (#4251) * IdentityLink - change expiration time to 30 days (#4239) * Add coppa support for AppNexus adapter (#4253) * Add coppa support for AppNexus adapter * test name * add new longform e2e tests (#4206) * Konduit module (#4184) * Adding Konduit module * Removed superfluous arguments passed to obtainVastUrl function * Removed superfluous arguments passed to obtainVastUrl function. * Build trigger (empty commit) * Module documentation updated according to the comments * Logic in obtainVastUrl function updated according to the review comment. * Removed hook, enabled eslint * Circle CI runs e2e tests on every push (#4200) * run functional tests on circle ci on push to any remote branch * remove extraneous key from config file * add test.localhost as alias to 127.0.0.1 * check 0: execute circle-ci * move /etc/config to a separate command * change bid partner to rubicon * test appnexus bid adapter in ci * comment browserstack command * remove console.log statement * test1: circle-ci * change reference dev -> prod while loading prebid * add console.log statement * check-2: circle-ci * comment browserstack testing * change bid adapter * change bid adapter * remove test case for checking targeting keys * remove the ci flag * uncomment test for checking correct generation of targeting keys * swap AN -> Rubicon for testing targeting keys * Outcon bid adapter. (#4161) * Outcon bid adapter. * Fix identation * Fixes * Fixes * Fixes * Spec fixes * Fixes * Fix urls * Fix * Fix parameters * Fix space operators * Fix bidder timeout * Update * Fix whitespace * no message * Outcon unit test * no message * no message * no message * no message * Fixes * Fixes * Change url * no message * no message * no message * Added bidId * no message * no message * no message * no message * Wrapping url with html * no message * no message * no message * Adding workflow to run end to end tests (#4230) * Adding workflow to run end to end tests * trying self branch * Update to run at 12 every day * cleanup config using aliases * update branch and cron time * add command * update prebid path for e2e test pages (#4274) * Prebid 2.35.0 release * Increment pre version * Add usersync to adpone adapter (#4245) * add user sync to adpone adapter * move adpone usersync to global variable * added withcredentials to http request * fix http request options * fix http request options * add withCredentials: true * add withCredentials: true * added test coverage to usersync * update sync function * add test coverage * adpone adapter * package lock * add more testing * add more testing * testing for onBidWon fucntion * test onbidwon function * trigger build * Revert GumGum Adapter 2.28 resizing changes (#4277) * changed resizing unit tests to return the first size dimensions in the sizes array * added some changes * reverted adapter changes * SpotX Bid Adapter: Support schain, ID5 object, Google consent object, and hide_skin (#4281) * Add SpotXBidAdapter * Minor updates * Undo testing changes to shared files * Fix relative imports * Remove superfluous imports and write a few more tests * Formatting, ID5 object, Google consent objects - Added ID5 object support - Added Google Consent object - Reformatted indentaiton on spec file * Revert content_width and content_height changes in docs - not sure how these got moved, lets put them back * Remove click_to_replay flag in example - no reason to use this one in the example * Spotx adapter - Add schain support and update unit tests * Update schain path in ORTB 2.3 request body - schain object is now added to ortb request body at request.ext.source.ext.schain * Add hide_skin to documentation - whoops, this got removed, let's add it back * Update Rubicon Analytics Adapter `bidId` to match PBS (#4156) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update for rubicon analytics to send seat[].bid.id for PBS video and banner * fixed conditional for server and video or banner * updated with optimized value test for bidid * update changed default value of netRevenue to true * remove var declaration for rightSlot to correct lgtm error for unused variable * update defineSlot div id to match div id defined in html body * update test ad unit test props * revert lock to match remote master * add seatBidId to bidObj in rpBidAdapter interpretResponse * update setTargeting to execute in the bids back handler * remove dev integration test page * meaningless commit to get lgtm to re-run * SmartRTB adapter update (#4246) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. * Remove debugs, update doc * Update test for video support * Handle missing syncs. Add video to media types in sample ad unit * Add null response check, update primary endpoint * Note smrtb video requires renderer * Support Vast Track (#4276) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Add parameters if config.cache.vasttrack is true * Use requestId instead of adId * Test new vasttrack payload params * Removed commented out code * Relaxed conditional check per review * Removed commented out line * Added 1000x250 size (#4295) * prepare vidazoo adapter for v3.0 (#4291) * Improve Digital adapter: support schain (#4286) * LiveIntent Identity Module. (#4178) * LiveIntentIdSystem. Initial implementation. * LiveIntentIdSystem. Removed whitespace. * Fixed typo * Renamed variables, cookiesm added md. * Changed the default identity url. * Composite id, with having more than just the lipbid passed around. * Composite id. * Merge conflict resolution. * Changed docs and param description. * Added typedoc & mentioned liveIntentIdSystem in submodule.json. * Extracted the LiveIntentIdSystem under modules, removed it from default userId modules. * Fixing the 204 + no body scenario. * Added liveIntent to submodule.json * Fixing docs indentation. * Updated prebidServer & specs. * Minor specs update. * updating liveintent eids source (#4300) * updating liveintent eids source these are supposed to be domains * updating unit test * fix appnexusBidAdapter view-script regex (#4289) * fix an view script regex * minor syntax update * 33Across adding bidder specific extension field (#4298) * - add 33across specific ext field for statedAt * - fix unit test for 33Across adapter * PubMatic to support LiveIntent User Id sub-module (#4306) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * supporting LiveIntent Id in PubMatic adapter * updated source for liveintent * Finteza Analytics Adapter: fix cookies (#4292) * fix reading and sending cookies * fix lint errors * clear comments * add unit tests * fix calling of setCookies for IE * clear cookies after test * use own setCookie method inside tests * Update LockerDome adapter to support Prebid 3.0 (#4301) * Returning the `IdResponse` type with an obj + callback. Fix for 4304 (#4305) * Returning the `IdResponse` type with an obj + callback. * Renamed resp -> result. * Removed whitespace. * ShowHeroes adapter - expanded outstream support (#4222) * add ShowHeroes Adapter * ShowHeroes adapter - expanded outstream support * Revert "ShowHeroes adapter - expanded outstream support" This reverts commit bfcdb913b52012b5afbf95a84956b906518a4b51. * ShowHeroes adapter - expanded outstream support * ShowHeroes adapter - fixes (#4222) * ShowHeroes adapter - banner and outstream fixes (#4222) * ShowHeroes adapter - description and outstream changes (#4222) * ShowHeroes adapter - increase test coverage and small fix * [Orbidder-Adapter] Add bidRequestCount and remove bid.params.keyValues (#4264) * initial orbidder version in personal github repo * use adUnits from orbidder_example.html * replace obsolete functions * forgot to commit the test * check if bidderRequest object is available * try to fix weird safari/ie issue * ebayK: add more params * update orbidderBidAdapter.md * use spec. instead of this. for consistency reasons * add bidfloor parameter to params object * fix gdpr object handling * default to consentRequired: false when not explicitly given * wip - use onSetTargeting callback * add tests for onSetTargeting callback * fix params and respective tests * remove not used bid.params.keyValues * add bidRequestCount to orbidder.otto.de/bid Post request * add bidRequestCount to test object defaultBidRequest * PulsePoint: remove usage of deprecated utils method / prep for 3.0 (#4257) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * Removing usage of deprecated utils method * minor refactor * Use isArray method (#4288) * Add Parrable ID submodule (#4266) * add parrable id submodule * fix integration test config * fix var name * always refresh sotredId for parrable * add submodulesThatAlwaysRefresh concept * remove comment * add parrable url as one string * add parrable prod endpoint * use .indexOf instead of .includes * add params to test config * comment failing test * uncomment failing assertion * add parrable ID to prebid server adapter * add parrableIdSystem to .submodules.json * extract parrableId unit tests from userId spec * remove breakline between imports * remove unused param * remove userId generic feature from parrableId module * remove trailing space * fix failing test due to none merged conflict * Prebid 2.36.0 Release * Increment pre version * Support schain module and send bidfloor param in Sharethrough adapter (#4271) * Add support for supply chain object module Story: [#168742394](https://www.pivotaltracker.com/story/show/168742394) Co-authored-by: Josh Becker * Add bidfloor parameter to bid request sent to STX Story: [#168742573](https://www.pivotaltracker.com/story/show/168742573) * Platform One Analytics Adapter (#4233) * Added Y1 Analytics Adapter * rename y1AnalyticsAdapter in yieldoneAnalyticsAdapter * Yieldone Bid Adapter: fixes from lint check * Yieldone Analytics Adapter: fix endpoint protocol * Added spec file for yieldone Analytics Adapter * Fix parrable id integration example (#4317) * fix parrableId integration example * add parentheses * Improve Digital adapter: support for video (#4318) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * Improve Digital support for video * Improve Digital adapter: video * adapter version -> 6.0.0 * Gamoshi: Update aliases list. Add support for userSync. (#4319) * Add support for multi-format ad units. Add favoredMediaType property to params. * Add tests for gdpr consent. * Add adId to outbids * Modify media type resolving * Refactor multi-format ad units handler. * Modify the way of sending GDPR data. Update aliases. * Add new consent fields. Add unit test. * Add new consent fields. Add unit test. * Add support for id5 and unified id cookie sync. * Add support for id5 and unified id cookie sync. * Add restricted check for gdpr consent. * fix for userSync endpoint getting called with bidder alias names, instead of actual bidder names (#4265) * modify ixBidAdapater to always use the secure endpoint (#4323) * PubMatic to support Parrable User Id sub-module (#4324) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * PubMatic to support parrable id * VISX: currency validation & fix double escape of referer (#4299) * PubMatic to support coppa (#4336) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * added coppa compliance * vuble: outstream has fullscreen option (#4320) * Mod: vuble oustream has fullscreen option * Add: vuble test for outstream scenario * EMXDigital: hotfix to resolve URIError from decodeURIComponent (#4333) * hotfix to resolve URIError from decodeURIComponent * added unit for decoding adm * Specify second parameter for parseInt for pubmaticBidAdapter (#4347) * Remove usage of getTopWindowUrl in Prebid Adapter (#4341) * Conversant Bid Adapter update for 3.0 (#4284) * Add cpmDistribution function for Google Analytics adapter (#4240) * Add cpmDistribution function for Google Analytics adapter * Add test for the cpmDistribution function * Remove half written comment * fixing SRA p_pos (#4337) * In Sonobi Adapter, only read sizes from bid.mediaTypes (#4311) * Fix mediaTypes (#4332) * Outcon bid adapter. * Fix identation * Fixes * Fixes * Fixes * Spec fixes * Fixes * Fix urls * Fix * Fix parameters * Fix space operators * Fix bidder timeout * Update * Fix whitespace * no message * Outcon unit test * no message * no message * no message * no message * Fixes * Fixes * Change url * no message * no message * no message * Added bidId * no message * no message * no message * no message * Wrapping url with html * no message * no message * no message * Fix mediaTypes * no message * Update outconBidAdapter_spec.js * Adding VAS response * no message * no message * no message * Fix * Changed ttl * no message * supportedMediaTypes * no message * no message * Prebid 2.37.0 release * increment pre version * Add vast xml support and other minor changes to Beachfront adapter (#4350) * Add support for vast xml in the bid response * add secure protocol to outstream player url * add device connection type * add player setting for poster color * add new value for creative Id * Update smartrtbBidAdapter (#4362) * modules: Implement SmartRTB adapter and spec. * Fix for-loop syntax to support IE; refactor getDomain out of exported set. * Remove debugs, update doc * Update test for video support * Handle missing syncs. Add video to media types in sample ad unit * Add null response check, update primary endpoint * Note smrtb video requires renderer * Remove old params checks, fix documentation playerSize field name * Revert "Update smartrtbBidAdapter (#4362)" (#4368) This reverts commit be6704bcec65a28d80b6d09a8d1c51ef9a8ba824. * Add userSync in onetagBidAdapter (#4358) * Minor bug fixing in onetagBidAdapter.js Fixed a minor bug. Updated TTL in response to align the correct specifications. * Update onetagBidAdapter Added additional page info and user sync function. * Update onetagBidAdapter_spec.js Added the test for getUserSyncs function. * Fix about userSync * getUserSyncs: test update with gdpr params * Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4335) * schain and digitrust * pixel beacons * unit tests and fixes from testing * Prebid 3.0 updates * review fix * Add bid adapter for ablida (#4256) * Add ablida adapter * rename category parameter, add documentation * AdKernel: added waardex_ak alias (#4290) * added alias Added a new alias * fixing unit test * Revert "Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4335)" (#4376) This reverts commit 6114a3dba93815dcfb535707d7b4d84f1adb2bc7. * Vrtcal Markets Inc. Bid Adapter Addition (#4259) * Added 3 key Vrtcal Adapter files: adapter,markdown,unit tests * Removed unused getUserSyncs;Added mediaTypes.banner.sizes support;Raised test coverage to 85% * lint formatting errors corrected * Update schain path in ORTB path for spotxBidAdapter (#4377) - Move schain object from request.ext.source.ext.schain to request.source.ext.schain * Update Grid Bid Adapter (#4379) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * Add video support in Grid Bid Adapter * Added test parameter for video slot * update Grid Bid Adapter to set size in response bid * Update Grid Bid Adapter to support identical uids in parameters * Fix typo in test file for Grid Bid Adapter * Update The Grid Media Bidder Adapter to send refererInfo.referer as 'u' parameter in ad request * Hotfix for referrer in Grid Bid Adapter * Grid Bid Adapter: added wrapperType and wrappweVersion to the ad request * TripleLift: Sending schain (#4375) * Add IdentityLink support and fix UnifiedId. It appears we've been looking for UnifiedId userIds on the bidderRequest object, when they are found on bidRequests. This commit fixes that error, and adds support for IdentityLink. * change maintainer email to group * TripleLift: Sending schain (#1) * Sending schain * null -> undefined * DistrictmDMX: adding support for schain and remove content type to default to prebid selection (#4366) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * Support for ID5 + receive meta data (#4352) * Livewrapped bid and analytics adapter * Fixed some tests for browser compatibility * Fixed some tests for browser compatibility * Changed analytics adapter code name * Fix double quote in debug message * modified how gdpr is being passed * Added support for Publisher Common ID Module * Corrections for ttr in analytics * ANalytics updates * Auction start time stamp changed * Detect recovered ad blocked requests Make it possible to pass dynamic parameters to adapter * Collect info on ad units receiving any valid bid * Support for ID5 Pass metadata from adapter * Typo in test + eids on wrong level * Rubicon Adapter: Always make requests using HTTPS (#4380) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Always make bids requests using https * rp_secure and imp.secure should always be 1 * 7xbid adapter (#4328) * 7xbid adapter * fix error when cli build * - update 33across adapter cookie sync end point (#4345) - update unit test for 33across adapter * Adform adapter: add renderer for outstream bids (#4363) * Prebid 2.38.0 Release * Increment pre version * Adagio: update with external js (#4217) * Add external loader in AdagioBidAdapter * Change adagioAnalyticsAdapter to "endpoint" type * Change _setPredictions for a generic method * Improve AdagioBidAdapter test coverage * Add features detection in Adagio adapter * Fix adagioBidAdapter tests * Add featuresVersion field to bidRequest * Refacto adagio.queue * Expose versions in ADAGIO namespace * Generate a ADAGIO.pageviewId if missing * Move ad-server events tracking to adagioBidAdapter * Store adUnitCodes in ADAGIO namespace * Update documentation Better description of test parameters. * Add internal array to prevent empty pbjs.adUnits * Be sure to access to window.top - does not work in safe-frame env * Add PrintNumber feature * Be sure to compute features on window.top * Bump versions * Add Post-Bid support - ad-server events are listen in current window (instead of window.top) - a new "outerAdUnitElementId" property is set to ADAGIO.pbjsAdUnits array in case of Post-Bid scenario. This property is the 1st parent element id attribute of the iframe in window.top. * Set pagetype param as optional * Add AdThink ad-server support * Improve internal `pbjsAdUnits.sizes` detection Use the adUnit `mediaTypes.banner.sizes` property if exists to build the `ADAGIO.pbjsAdUnits.sizes`. The use of the `sizes` root property is deprecated. * adagioAnalyticsAdapter: add and improve tests * adagioBidAdapter: add and improve tests # Conflicts: # modules/adagioBidAdapter.js # test/spec/modules/adagioBidAdapter_spec.js * adagioBidAdapter: Bump version 1.5 * Adagio: fix import path * PostBid: insure window.top is accessible for specifics functions * Consistency: use Prebid.js utils and fix deprecated * PostBid: do not build a request if in safeframe * Bump version 2.0.0 * Try to fix tests without UA stubing * Try to fix adagioAnalytics failling tests on CI * Consistency: use Prebid loadExternalScript() * Add "adagio" to Prebid.js adloader vendor whitelist * Remove proprietary ad-server listeners * Add RSA validation to adagio external script * add viewdeosDX whitelabel (#4231) * add viewdeosDX hitelabel * Fixed tests and support for sizes * Fix strings * Fix strings * remove only * Fix tests * fix codereview * Fix test + Code review * code review + tests * One video display ad (#4344) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * testing display ad * adding banner * validating banner object * display=1 changes * checking whether diplsy == 1 * html page change * reverting video.html * adding more test cases * spaces * md file change * updated working oneVideoBidAdapter.md file * Update oneVideoBidAdapter.md * Update oneVideoBidAdapter.md * updated the file with both video params and banner * Update video.html * fix double-urlecoded referrer (#4386) * fix double-urlecoded referer (#4388) * PulsePoint Adapter - update for ttl logic (#4400) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * Using the TTL from the bid.ext * Minor refactor * IdentityLink - add logic for sending consent string (#4346) * Fix adagio analytics adapter circleci (#4409) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * update to skip broken circleci tests * skip all * Feature/7xbid remove unneeded params (#4402) * 7xbid adapter * fix error when cli build * remove unneeded params * Empty commit * Empty commit * Remove none ssl (#4406) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * remove none ssl element from all request] * fixed reference to global object (#4412) * ucfunnel adapter support supply chain (#4383) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel adapter support supply chain * LiveIntent support in RP Adapter and PBS Adapter update to pass segments (#4303) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * added semi-colon * update eid source to use domain * update video oRTB with liveintent segments * update pbs adapter with liveintent segments support * update rp adapter liveintent support for fastlane * reverted package lock, fix for unintentional update * added unit tests for fastlane.json and ortb, fix to join segments with commas * fix obj property path data.tpid * update remove unnecessary function call * re-ordering query string params * Rubicon Adapter: Add multiple sizes to sizeMap (#4407) * Add Utils to remove item in LocalStorage (#4355) * Making originalCpm and originalCurrency fields in bid object always available (#4396) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * moving originalCurrency declaration from currency to bidderFactory * added a comment * trying to re-run the CI job * added unit test case * trying to re-run the CI job * Placement and inventory (#4353) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * inventory_id and placement * removed unnecessary file * lint error * Update oneVideoBidAdapter.js * lint error fix * Fixes for Platform One Analytics Adapter (#4359) * Added Y1 Analytics Adapter * rename y1AnalyticsAdapter in yieldoneAnalyticsAdapter * Yieldone Bid Adapter: fixes from lint check * Yieldone Analytics Adapter: fix endpoint protocol * Added spec file for yieldone Analytics Adapter * Add adUnitName to analytics data for Yieldone Analytics Adapter * Fix yieldone Analytics Adapter to log only id from adUnitPath * Fix bug with timeout event in Yieldone Analytics Adapter * Added protocol to url (#4395) * initial commit * updated contact and tag details * changes ti support the renderers * changes to pass dimId * fixed names of internal mapping * added comment * added gdpr param to request and other fixes * modified api url * fix * fixed the secure api call * rolled back video event callback till we support it * updated doc with video details * added bid won and timeout pixel * added testcase for bid events * modified testcase * fixed the url logged * tag param values passed ot renderer * added a conditioal check * changes to support new param to adserver for purpose of tracking * passed param to renderer * missing variable defined * added protocol to url * fixed test for protocol * changed urls to secure only * Update emoteev endpoints (#4329) * JustPremium: Update to Prebid 3.0 (#4410) * Update underdogmedia adapter for pbjs 3.0 (#4390) * Update underdogmedia adapter for pbjs 3.0 * Ensure request to endpoint is secure * Update prebid version * Lint fix * Update Consumable adapter for Prebid.js 3.0 (#4401) * Consumable: Clean up tests. * Consumable: Update use of deprecated function. * Consumable: Read sizes from mediaTypes.banner.sizes. * Consumable: Fix lint violation. * CriteoId User Module (#4287) * Add CriteoId module * Update the return type of getId in Criteo Id module Changes: - Use of url parsing function from url lib - Update the return type of getId() - Update the jsdoc to reflect the real return types * Fix failing tests for Criteo user module * Add CriteoIdSystem submodule to .submodule.json. * 2019/10/18 Create Mobsmart bidder adapter (#4339) * Adpod deal support (#4389) * Adpod deal support * Replacing filterBids with minTier * fix potential issue * remove querystringify package (#4422) * Browsi real time data module (#4114) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * Prebid 2.39.0 Release * increment pre version * OpenX Adapter: Prebid 3.0 Compatibility Update (#4413) * Removed usage of deprecated functions * Removed beacons * Banner sizes now reads from bidRequest.mediaTypes.banner.sizes instead of bidRequest.sizes * Updated tests to reflect changes. * GumGum: use mediaTypes.banner.sizes (#4416) * adds digitrust module, mods gdpr from bool to int * update unit test * only send gdprconsent if present * uses mediaTypes before trying bidRequest sizes * removes use of deprecated method * RTBhouse Bid Adapter update for 3.0 (#4428) * add viewable rendering format (#4201) * Feature/adapter (#4219) * feat(bidrequest): code for making bidrequest * feat(bidresponse): format and return the response * feat(tests): added tests for adapter * feat(docs): added docs for the adapter * refactor(url): changed adserver url * test(user sync): added unit tests for the user syncs * refactor(endpoint): changed endpoint for prebid * refactor(endpoint): changed endpoint for prebid * doc(tagid): mandatory param definition added * fix(imp id): fix for correct impression id * fix(width/height): fix for correct width and height sequence * PulsePoint Bid Adapter: Support for schain (#4433) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * ET-5938 SupplyChain Object Support * Formatting * Code review * Code review * Fix to currency parsing on response * Add supply chain support for Teads adapter (#4420) * Rubicon: support SupplyChain (schain) (#4315) * Add microadBidAdapter * Remove unnecessary encodeURIComponent from microadBidAdapter * Submit Advangelists Prebid Adapter * Submit Advangelists Prebid Adapter 1.1 * Correct procudtion endpoint for prebid * analytics update with wrapper name * reverted error merge * update changed default value of netRevenue to true * Starting schain * More tests for banner schain support * Video tests * Encoding tweaks, required fields and comments * Removed .only() from tests * Change requests per Bret * Add 1ad4good bidder (#4081) * adding bidder code and A bidder for non-profit free ads. more info about this bidder project can be found on project site http://1ad4good.org * removed unused code test coverage is improved to >80% tested for instream video support * removed some legacy code, unused params * hardcoding https to endpoint * Improve Digital adapter fix: don't send sizes for instream video (#4427) * Bid floor, https, native ad update * Update the ad server protocol module * Adding referrer * Improve Digital support for video * Improve Digital adapter: video * adapter version -> 6.0.0 * Improve Digital adapter: don't send sizes for video * Fix a typo in code comment (#4450) * Inventory id and schain support for display (#4426) * supporting schain * Update coinzillaBidAdapter.js (#4438) Update sizes const. * Support schain in ZEDO adapter (#4441) * changes to pass schain * PubMatic supporting updated Criteo User Id module (#4431) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * PubMatic supporting updated Criteo User Id module * added a comment to re-start CI * Remove duplicate param to fix unit tests (#4459) * Brightcom Bid Adapter update for 3.0 (#4343) * add support for min_height field in pbs native requests (#4434) * Supporting Alias via Video Requests (#4460) * New adapter Proxistore (#4365) * add test adapter and documentation * integration test with hello_world * reset package-lock.json * delete useless conditionnal * make integrate test work * revert hello-world * revert hello_world * fix descriptor * change adUnits for integration test * remove proxistore widget * uncomment file * change sizes * remove useless script tag * Implementation of setBidderConfig and bidder-specific data (#4334) * initial implementation of setBidderConfig * fix ie11 test errors * Support new setBidderConfig format. Include props from both config and bidderConfig in _getConfig * Use core-js Set to avoid issues with IE * Fix tests in IE * put registerSyncs back on bidderFactory * run bidder event methods with bidder config enabled * Prebid 2.40.0 Release * Increment pre version * Conversant Bid Adapter checks pubcid directly (#4430) * Cookie Sync functionality (#4457) * changing PID param value for testing * cookie sync integration * merge from upstream * Staq Adapter: update with meta envelope (#4372) * initial dev * fix staq adapter name * fix hello world staq call * get hello world working again * add user agent collection * fix some unite tests * Add STAQ Analytics Adapter doc * clean up hello world * fix tests to play nice with browserstack * fix around issues with browserstack and deep equals of objects * dump variable env testing since we can't mod user agent stuff in browserstack * Update STAQ adapter to stop using deprecated utils for referrer * remove package-lock.json changes via master rebase * improve call frequency for ref util * change ajax content type * adjust ajax request to not expect whitelisting * remove superflous commented-out code * update event package to use meta information in envelope rather than per event basis * fix formatting * more formatting fixes * more formatting! * Rhythmone Adapter - schain support (#4414) Circle CI failing tests are not related to this PR. * Media.net Adapter: Support Prebid 3.0 (#4378) * Media.net Adapter: Support Prebid 3.0 * Media.net Adapter: add tests to increase code coverage * Vi Adapter: Passes additional param in the bid request (#4134) * Add focus check (cherry picked from commit 9d6d6dfb83580d6a5ffed8faa5762db48f8fd44d) * Pass focus as numeric value (cherry picked from commit 9fae56a637f87b0d39cc1d24eeb1f9ff9df88f64) * Add unit test (cherry picked from commit 946710f2e9960b3839613d4bdf730e57ba38a964) * Sovrn adapter updates: schain, digitrust, pixel syncing, and 3.0 upgrades (#4385) * schain and digitrust * pixel beacons * unit tests and fixes from testing * Prebid 3.0 updates * review fix * use backwards compatible flatMap impl * update pixel tests * unit test fix * update one more url to ssl * fixed test * review updates * TheMediaGrid Bid Adapter update (#4447) * Added Grid Bid Adapter * remove priceType from TheMediaGrid Bid Adapter * Add video support in Grid Bid Adapter * Added test parameter for video slot * update Grid Bid Adapter to set size in response bid * Update Grid Bid Adapter to support identical uids in parameters * Fix typo in test file for Grid Bid Adapter * Update The Grid Media Bidder Adapter to send refererInfo.referer as 'u' parameter in ad request * Hotfix for referrer in Grid Bid Adapter * Grid Bid Adapter: added wrapperType and wrappweVersion to the ad request * TheMediaGrid Bid Adapter: added sync url * TheMediaGrid Bid Adapter: added GDPR params to sync url * TheMediaGrid Bid Adapter: added tests for getUserSyncs function * Conversant Bid Adapter adds support for extended ids (#4462) * Adkernel 3.0 compatibility (#4477) * Rubicon Adapter pchain support (#4480) * rubicon pchain support * removed describe.only * Implemented changes required to provide support for video in the IX bidding adapter for Instream and Outstream contexts. (#4424) * Default size filter & KVP support (#4452) * adding DMX test @97%, two files added one updated * Update districtm_spec.js * Update districtmDMX.js * adding all districtm needed file * remove legacy file * remove typo || 0 in the test method * force default to return a valid width and height * update unit test code for failing test * changed class for an object * remove package-lock.json * change file name for dmx adapter * renamed files * restaure package-lock.json * update to last package-lock state * update gdpr user consent * fix sizes issue * Documentation updates Adding the readme.md info * update file name and update unit testing import file location * current machine state * lint correction * remove variable assigment duplicate * adding logic upto5 * adding support for removing and shuffle sizes * adding array split test * re-assign none standard size to the request * resolve duplicate format inside format array * update .md and adaptor file for KVP support * remove array helper includes * inforce two digit after decimal * RUn error check nothing on my side but error form another adapter * add id5id to prebid server bid adapter (#4468) * Added _pbjsGlobals for tracking renames. Resolves #4254 (#4419) * Feature/smart video (#4367) * Adding outstream video support. * Fixing unit test. * Adding video instream support. * Handling video startDelay parameter. * Improving unit tests. * Fixing indent. * Handling the request when videoMediaType context is not supported. * Changing maintainer mail address. * Remove video outstream specific code. * Unit test updated. * do not select element that gets removed after dfp render (#4423) * add smms adapter (#4439) * add smms adapter * re-run ci, why adigo adapter failed?? * review comments fix, remove deprecated functions, fix unit test * Prebid 2.41.0 release * Increment pre version * adds schain param (#4442) * Create newborntownWeb adapter (#4455) * Create newborntownWeb adapter * only https protocol * Provide criteoId to server by user.ext.eids (#4478) * ucfunnel adapter fix error message in debug mode (#4338) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel fix error message in debug mode * explicitly check undefined to allow falsey values in getConfig (#4486) * Conversant Bid Adapter handles vast xml (#4492) * [feature] Add a config list of submodules that require refreshing the stored ID after each bid request (#4325) * add a feature to always refresh stored id on each bid request for submodules that require that * update test comments * Prebid 2.42.0 Release * Increment pre version * Make adhese adapter prebid 3.0 compatible (#4507) * Added 'adhese' attribute to bid that contains meta data - Jira AD-2642 * added DALE to adhese determination * extra config option: no format, but use size array as format string * Read sizes from mediaTypes.banner.sizes + Apply Eslint suggestions * Use map and join, add originData to response * properly use originData obj * Remove duplicated ids * Update tests * BugFix: Site id missing (#4467) * outstream changes * removing global filtet * reverting page * message * adapter change * remove space * testcases * testpage * spaces for test page * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * added unit test case * adding site id * adding placement and siteis * site id param test case * removing deprecated functions * correcting test cases * indentation * test cases fix * change placement to plcmt * fix extra space --------- Co-authored-by: Jonathan Mullins Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: htang555 Co-authored-by: Bryan DeLong Co-authored-by: dpapworth-qc <50959025+dpapworth-qc@users.noreply.github.com> Co-authored-by: DeepthiNeeladri Co-authored-by: Harshad Mane Co-authored-by: Roman Co-authored-by: Neelanjan Sen <14229985+Fawke@users.noreply.github.com> Co-authored-by: Margaret Liu Co-authored-by: TJ Eastmond Co-authored-by: Robert Ray Martinez III Co-authored-by: Jason Snellbaker Co-authored-by: bretg Co-authored-by: JonGoSonobi Co-authored-by: Vladimir Fedoseev Co-authored-by: DJ Rosenbaum Co-authored-by: Alex Khmelnitsky Co-authored-by: adxpremium <55161519+adxpremium@users.noreply.github.com> Co-authored-by: Jimmy Tu Co-authored-by: Pierre-Antoine Durgeat Co-authored-by: Eric Harper Co-authored-by: Telaria Engineering <36203956+telariaEng@users.noreply.github.com> Co-authored-by: ujuettner Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: PWyrembak Co-authored-by: susyt Co-authored-by: Max Crawford Co-authored-by: Pascal S Co-authored-by: Will Chapin Co-authored-by: Lemma Dev <54662130+lemmadev@users.noreply.github.com> Co-authored-by: Denis Logachov Co-authored-by: Léonard Labat Co-authored-by: onlsol <48312668+onlsol@users.noreply.github.com> Co-authored-by: Paul Yang Co-authored-by: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Co-authored-by: Mike Sperone Co-authored-by: sdbaron Co-authored-by: djaxbidder <55269794+djaxbidder@users.noreply.github.com> Co-authored-by: turktelssp <54801433+turktelssp@users.noreply.github.com> Co-authored-by: nkmt <45026101+strong-zero@users.noreply.github.com> Co-authored-by: Mutasem Aldmour Co-authored-by: r-schweitzer <50628828+r-schweitzer@users.noreply.github.com> Co-authored-by: Isaac A. Dettman Co-authored-by: Adasta Media <55529969+Adasta2019@users.noreply.github.com> Co-authored-by: mamatic <52153441+mamatic@users.noreply.github.com> Co-authored-by: Konduit <55142865+konduit-dev@users.noreply.github.com> Co-authored-by: TinchoF <50110327+TinchoF@users.noreply.github.com> Co-authored-by: Jaimin Panchal <7393273+jaiminpanchal27@users.noreply.github.com> Co-authored-by: Jaimin Panchal Co-authored-by: Sergio Co-authored-by: Wayne Yang Co-authored-by: Cody Bonney Co-authored-by: evanmsmrtb Co-authored-by: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Co-authored-by: Oz Weiss Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Janko Ulaga Co-authored-by: thomas-33across <44033452+thomas-33across@users.noreply.github.com> Co-authored-by: Finteza Analytics <45741245+finteza@users.noreply.github.com> Co-authored-by: Vadim Mazzherin Co-authored-by: Hendrik Iseke <39734979+hiseke@users.noreply.github.com> Co-authored-by: Anand Venkatraman Co-authored-by: Eyas Ranjous Co-authored-by: Bret Gorsline Co-authored-by: Michael Co-authored-by: hbanalytics <55453525+hbanalytics@users.noreply.github.com> Co-authored-by: Salomon Rada Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: Michael Kuryshev Co-authored-by: Roffray Co-authored-by: rumesh Co-authored-by: oasis <2394426+bmwcmw@users.noreply.github.com> Co-authored-by: Nepomuk Seiler Co-authored-by: John Salis Co-authored-by: OneTagDevOps <38786435+OneTagDevOps@users.noreply.github.com> Co-authored-by: Ankit Prakash Co-authored-by: Dan Co-authored-by: romanantropov <45817046+romanantropov@users.noreply.github.com> Co-authored-by: msm0504 <51493331+msm0504@users.noreply.github.com> Co-authored-by: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: colbertk <50499465+colbertk@users.noreply.github.com> Co-authored-by: Steve Alliance Co-authored-by: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Co-authored-by: 7XBID00 <52267720+7XBID00@users.noreply.github.com> Co-authored-by: Tomas Kovtun Co-authored-by: Olivier Co-authored-by: Gena Co-authored-by: Jonathan Mullins Co-authored-by: ucfunnel <39581136+ucfunnel@users.noreply.github.com> Co-authored-by: ACannuniRP <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Hugo Duthil Co-authored-by: skazedo Co-authored-by: 胡雨軒 Петр Co-authored-by: Konrad Dulemba Co-authored-by: Mariya Mego <31904600+mash-a@users.noreply.github.com> Co-authored-by: Daniel Cassidy Co-authored-by: kpis-msa <50609476+kpis-msa@users.noreply.github.com> Co-authored-by: omerBrowsi <54346241+omerBrowsi@users.noreply.github.com> Co-authored-by: Marcian123 Co-authored-by: koji-eguchi <50477903+koji-eguchi@users.noreply.github.com> Co-authored-by: sourabhg Co-authored-by: Alexis Andrieu Co-authored-by: Vlad Gurgov Co-authored-by: Richard Lee <14349+dlackty@users.noreply.github.com> Co-authored-by: Alex Co-authored-by: Vladislav Yatsun Co-authored-by: vincentproxistore <56686565+vincentproxistore@users.noreply.github.com> Co-authored-by: Rich Snapp Co-authored-by: Mike Chowla Co-authored-by: Rade Popovic <32302052+nanointeractive@users.noreply.github.com> Co-authored-by: Matt Quirion Co-authored-by: rhythmonebhaines <49991465+rhythmonebhaines@users.noreply.github.com> Co-authored-by: binoy-chitale Co-authored-by: Alex Pashkov Co-authored-by: harpere Co-authored-by: Scott Co-authored-by: tadam75 Co-authored-by: Veronica Kim <43146383+vkimcm@users.noreply.github.com> Co-authored-by: songtungmtp <57524426+songtungmtp@users.noreply.github.com> Co-authored-by: z-sunshine <33084773+z-sunshine@users.noreply.github.com> Co-authored-by: Sander --- modules/gamoshiBidAdapter.js | 2 +- test/spec/modules/gamoshiBidAdapter_spec.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 1c279cdb9b8..6681dc328d0 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { maxduration: bidRequest.mediaTypes.video.maxduration, api: bidRequest.mediaTypes.video.api, skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, - placement: bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement, + placement: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index 984830f67d4..f41750cfe71 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -379,7 +379,7 @@ describe('GamoshiAdapter', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.params.video = { - placement: 1, + plcmt: 1, minduration: 1, } @@ -408,7 +408,7 @@ describe('GamoshiAdapter', () => { playerSize: [302, 252], mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -431,7 +431,7 @@ describe('GamoshiAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -440,6 +440,7 @@ describe('GamoshiAdapter', () => { let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; expect(response.data.imp[0].video.ext.context).to.equal('instream'); bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; @@ -460,7 +461,7 @@ describe('GamoshiAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, From 2bacdeb1d49ab7271ae8358b9cfcb435ef6010fc Mon Sep 17 00:00:00 2001 From: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:26:03 +0530 Subject: [PATCH 0126/1097] Colossus SSp Bid Adapter : switching placement to plcmt (#11652) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data * gpp support * accepting eids from request * fixing lint errors * resolving a conflict * fixing a failed test case related to tid * fixing karma version for conflict resolution * reverting package json files to original version * switching placement to plcmt --------- Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally --- modules/colossussspBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 5fe78ff932d..3bc6c772bc6 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -173,7 +173,7 @@ export const spec = { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; + placement.placement = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; From 0ee4efdfde777045bdf5e00539efcfb878ff22d6 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 3 Jun 2024 05:43:25 -0700 Subject: [PATCH 0127/1097] PAAPI: add support for protected audience extensions and "direct" buyers (`igb`) (#11277) * set ortb2Imp.ext.igs * PAAPI: add ortbConverter support for request ext.igi and response ext.igi.igs * Update paapi hook to accept auction config as one field * mergeBuyers * partitionBuyers * add fpd to perBuyerSignals * complete igb treatment * improve names * parse ext.igi.igb * naming improvement * update debugging mod to work with igb * fix mergeBuyers to work with actual URL origins * rename componentBuyers to componentSeller * signal componentSeller config to bid adapters --- modules/debugging/bidInterceptor.js | 11 +- modules/debugging/debugging.js | 2 +- modules/debugging/pbsInterceptor.js | 2 +- modules/paapi.js | 241 +++++- modules/prebidServerBidAdapter/index.js | 4 +- .../prebidServerBidAdapter/ortbConverter.js | 2 +- src/adapters/bidderFactory.js | 17 +- test/spec/modules/debugging_mod_spec.js | 32 +- test/spec/modules/paapi_spec.js | 693 +++++++++++++++--- .../modules/prebidServerBidAdapter_spec.js | 12 +- test/spec/unit/core/bidderFactory_spec.js | 62 +- 11 files changed, 887 insertions(+), 191 deletions(-) diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 3afaacaeb81..67b24b3f222 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -152,10 +152,17 @@ Object.assign(BidInterceptor.prototype, { }, paapiReplacer(paapiDef, ruleNo) { + function wrap(configs = []) { + return configs.map(config => { + return Object.keys(config).some(k => !['config', 'igb'].includes(k)) + ? {config} + : config + }); + } if (Array.isArray(paapiDef)) { - return () => paapiDef; + return () => wrap(paapiDef); } else if (typeof paapiDef === 'function') { - return paapiDef + return (...args) => wrap(paapiDef(...args)) } else { this.logger.logError(`Invalid 'paapi' definition for debug bid interceptor (in rule #${ruleNo})`); } diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js index 2fd1731dc4e..63887074f04 100644 --- a/modules/debugging/debugging.js +++ b/modules/debugging/debugging.js @@ -103,7 +103,7 @@ export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest bids, bidRequest, addBid: cbs.onBid, - addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, config}), + addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, ...config}), done })); if (bids.length === 0) { diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 5c2eb458b18..c514031ad0e 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -29,7 +29,7 @@ export function makePbsInterceptor({createBid}) { adUnitCode: bidRequest.adUnitCode, ortb2: bidderRequest.ortb2, ortb2Imp: bidRequest.ortb2Imp, - config + ...config }) }, done diff --git a/modules/paapi.js b/modules/paapi.js index 9122ecce1a0..28252d0bb7a 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -3,7 +3,7 @@ */ import {config} from '../src/config.js'; import {getHook, module} from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {deepSetValue, logInfo, logWarn, mergeDeep, deepEqual, parseSizesInput, deepAccess} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; @@ -24,7 +24,7 @@ export function registerSubmodule(submod) { module('paapi', registerSubmodule); -function auctionConfigs() { +function auctionStore() { const store = new WeakMap(); return function (auctionId, init = {}) { const auction = auctionManager.index.getAuction({auctionId}); @@ -36,8 +36,10 @@ function auctionConfigs() { }; } -const pendingForAuction = auctionConfigs(); -const configsForAuction = auctionConfigs(); +const pendingConfigsForAuction = auctionStore(); +const configsForAuction = auctionStore(); +const pendingBuyersForAuction = auctionStore(); + let latestAuctionForAdUnit = {}; let moduleConfig = {}; @@ -65,7 +67,7 @@ export function init(cfg, configNamespace) { } } -getHook('addComponentAuction').before(addComponentAuctionHook); +getHook('addPaapiConfig').before(addPaapiConfigHook); getHook('makeBidRequests').after(markForFledge); events.on(EVENTS.AUCTION_END, onAuctionEnd); @@ -89,6 +91,23 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) { return cfg; } +export function buyersToAuctionConfigs(igbRequests, merge = mergeBuyers, config = moduleConfig?.componentSeller ?? {}, partitioners = { + compact: (igbRequests) => partitionBuyers(igbRequests.map(req => req[1])).map(part => [{}, part]), + expand: partitionBuyersByBidder +}) { + if (!config.auctionConfig) { + logWarn(MODULE, 'Cannot use IG buyers: paapi.componentSeller.auctionConfig not set', igbRequests.map(req => req[1])); + return []; + } + const partition = partitioners[config.separateAuctions ? 'expand' : 'compact']; + return partition(igbRequests) + .map(([request, igbs]) => { + const auctionConfig = mergeDeep(merge(igbs), config.auctionConfig); + auctionConfig.auctionSignals = setFPD(auctionConfig.auctionSignals || {}, request); + return auctionConfig; + }); +} + function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) { const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []) const allReqs = bidderRequests?.flatMap(br => br.bids); @@ -97,7 +116,14 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adU paapiConfigs[au] = null; !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); }); - Object.entries(pendingForAuction(auctionId) || {}).forEach(([adUnitCode, auctionConfigs]) => { + const pendingConfigs = pendingConfigsForAuction(auctionId); + const pendingBuyers = pendingBuyersForAuction(auctionId); + if (pendingConfigs && pendingBuyers) { + Object.entries(pendingBuyers).forEach(([adUnitCode, igbRequests]) => { + buyersToAuctionConfigs(igbRequests).forEach(auctionConfig => append(pendingConfigs, adUnitCode, auctionConfig)) + }) + } + Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); paapiConfigs[adUnitCode] = { @@ -126,25 +152,117 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adU ); } -function setFPDSignals(auctionConfig, fpd) { - auctionConfig.auctionSignals = mergeDeep({}, {prebid: fpd}, auctionConfig.auctionSignals); +function append(target, key, value) { + !target.hasOwnProperty(key) && (target[key] = []); + target[key].push(value); } -export function addComponentAuctionHook(next, request, componentAuctionConfig) { +function setFPD(target, {ortb2, ortb2Imp}) { + ortb2 != null && deepSetValue(target, 'prebid.ortb2', mergeDeep({}, ortb2, target.prebid?.ortb2)); + ortb2Imp != null && deepSetValue(target, 'prebid.ortb2Imp', mergeDeep({}, ortb2Imp, target.prebid?.ortb2Imp)); + return target; +} + +export function addPaapiConfigHook(next, request, paapiConfig) { if (getFledgeConfig().enabled) { - const {adUnitCode, auctionId, ortb2, ortb2Imp} = request; - const configs = pendingForAuction(auctionId); - if (configs != null) { - setFPDSignals(componentAuctionConfig, {ortb2, ortb2Imp}); - !configs.hasOwnProperty(adUnitCode) && (configs[adUnitCode] = []); - configs[adUnitCode].push(componentAuctionConfig); - } else { - logWarn(MODULE, `Received component auction config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, componentAuctionConfig); + const {adUnitCode, auctionId} = request; + + // eslint-disable-next-line no-inner-declarations + function storePendingData(store, data) { + const target = store(auctionId); + if (target != null) { + append(target, adUnitCode, data) + } else { + logWarn(MODULE, `Received PAAPI config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, data); + } + } + + const {config, igb} = paapiConfig; + if (config) { + config.auctionSignals = setFPD(config.auctionSignals || {}, request); + (config.interestGroupBuyers || []).forEach(buyer => { + deepSetValue(config, `perBuyerSignals.${buyer}`, setFPD(config.perBuyerSignals?.[buyer] || {}, request)); + }) + storePendingData(pendingConfigsForAuction, config); + } + if (igb && checkOrigin(igb)) { + igb.pbs = setFPD(igb.pbs || {}, request); + storePendingData(pendingBuyersForAuction, [request, igb]) } } - next(request, componentAuctionConfig); + next(request, paapiConfig); +} + +export const IGB_TO_CONFIG = { + cur: 'perBuyerCurrencies', + pbs: 'perBuyerSignals', + ps: 'perBuyerPrioritySignals', + maxbid: 'auctionSignals.prebid.perBuyerMaxbid', +} + +function checkOrigin(igb) { + if (igb.origin) return true; + logWarn('PAAPI buyer does not specify origin and will be ignored', igb); } +/** + * Convert a list of InterestGroupBuyer (igb) objects into a partial auction config. + * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + */ +export function mergeBuyers(igbs) { + const buyers = new Set(); + return Object.assign( + igbs.reduce((config, igb) => { + if (checkOrigin(igb)) { + if (!buyers.has(igb.origin)) { + buyers.add(igb.origin); + Object.entries(IGB_TO_CONFIG).forEach(([igbField, configField]) => { + if (igb[igbField] != null) { + const entry = deepAccess(config, configField) || {} + entry[igb.origin] = igb[igbField]; + deepSetValue(config, configField, entry); + } + }); + } else { + logWarn(MODULE, `Duplicate buyer: ${igb.origin}. All but the first will be ignored`, igbs); + } + } + return config; + }, {}), + { + interestGroupBuyers: Array.from(buyers.keys()) + } + ); +} + +/** + * Partition a list of InterestGroupBuyer (igb) object into sets that can each be merged into a single auction. + * If the same buyer (origin) appears more than once, it will be split across different partition unless the igb objects + * are identical. + */ +export function partitionBuyers(igbs) { + return igbs.reduce((partitions, igb) => { + if (checkOrigin(igb)) { + let partition = partitions.find(part => !part.hasOwnProperty(igb.origin) || deepEqual(part[igb.origin], igb)); + if (!partition) { + partition = {}; + partitions.push(partition); + } + partition[igb.origin] = igb; + } + return partitions; + }, []).map(part => Object.values(part)); +} + +export function partitionBuyersByBidder(igbRequests) { + const requests = {}; + const igbs = {}; + igbRequests.forEach(([request, igb]) => { + !requests.hasOwnProperty(request.bidder) && (requests[request.bidder] = request); + append(igbs, request.bidder, igb); + }) + return Object.entries(igbs).map(([bidder, igbs]) => [requests[bidder], igbs]) +} /** * Get PAAPI auction configuration. * @@ -197,9 +315,30 @@ export function markForFledge(next, bidderRequests) { bidderRequests.forEach((bidderReq) => { config.runWithBidder(bidderReq.bidderCode, () => { const {enabled, ae} = getFledgeConfig(); - Object.assign(bidderReq, {fledgeEnabled: enabled}); + Object.assign(bidderReq, { + fledgeEnabled: enabled, + paapi: { + enabled, + componentSeller: !!moduleConfig.componentSeller?.auctionConfig + } + }); bidderReq.bids.forEach(bidReq => { - deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidReq.ortb2Imp?.ext?.ae ?? ae); + // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + const igsAe = bidReq.ortb2Imp?.ext?.igs != null + ? bidReq.ortb2Imp.ext.igs.ae || 1 + : null + const extAe = bidReq.ortb2Imp?.ext?.ae; + if (igsAe !== extAe && igsAe != null && extAe != null) { + logWarn(MODULE, `Bid request defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, bidReq); + } + const bidAe = igsAe ?? extAe ?? ae; + if (bidAe) { + deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidAe); + bidReq.ortb2Imp.ext.igs = Object.assign({ + ae: bidAe, + biddable: 1 + }, bidReq.ortb2Imp.ext.igs) + } }); }); }); @@ -208,41 +347,72 @@ export function markForFledge(next, bidderRequests) { } export function setImpExtAe(imp, bidRequest, context) { - if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { + if (!context.bidderRequest.fledgeEnabled) { delete imp.ext?.ae; + delete imp.ext?.igs; } } registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); -// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up -// fledge response processing in two steps: first aggregate all the auction configs by their imp... - -export function parseExtPrebidFledge(response, ortbResponse, context) { - (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { - const impCtx = context.impContext[cfg.impid]; +function paapiResponseParser(configs, response, context) { + configs.forEach((config) => { + const impCtx = context.impContext[config.impid]; if (!impCtx?.imp?.ext?.ae) { - logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); + logWarn(MODULE, 'Received auction configuration for an impression that was not in the request or did not ask for it', config, impCtx?.imp); } else { - impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; - impCtx.fledgeConfigs.push(cfg); + impCtx.paapiConfigs = impCtx.paapiConfigs || []; + impCtx.paapiConfigs.push(config); } }); } +export function parseExtIgi(response, ortbResponse, context) { + paapiResponseParser( + (ortbResponse.ext?.igi || []).flatMap(igi => { + return (igi?.igs || []).map(igs => { + if (igs.impid !== igi.impid && igs.impid != null && igi.impid != null) { + logWarn(MODULE, 'ORTB response ext.igi.igs.impid conflicts with parent\'s impid', igi); + } + return { + config: igs.config, + impid: igs.impid ?? igi.impid + } + }).concat((igi?.igb || []).map(igb => ({ + igb, + impid: igi.impid + }))) + }), + response, + context + ) +} + +// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up +// fledge response processing in two steps: first aggregate all the auction configs by their imp... + +export function parseExtPrebidFledge(response, ortbResponse, context) { + paapiResponseParser( + (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []), + response, + context + ) +} + registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); +registerOrtbProcessor({type: RESPONSE, name: 'extIgiIgs', fn: parseExtIgi}); // ...then, make them available in the adapter's response. This is the client side version, for which the // interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} -export function setResponseFledgeConfigs(response, ortbResponse, context) { +export function setResponsePaapiConfigs(response, ortbResponse, context) { const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({ + .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => ({ bidId: impCtx.bidRequest.bidId, - config: cfg.config + ...cfg }))); if (configs.length > 0) { - response.fledgeAuctionConfigs = configs; + response.paapi = configs; } } @@ -250,6 +420,5 @@ registerOrtbProcessor({ type: RESPONSE, name: 'fledgeAuctionConfigs', priority: -1, - fn: setResponseFledgeConfigs, - dialects: [PBS] + fn: setResponsePaapiConfigs, }); diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index d95b8d3ecfc..8c5b89b5794 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -19,7 +19,7 @@ import { import { EVENTS, REJECTION_REASON, S2S } from '../../src/constants.js'; import adapterManager, {s2sActivityParams} from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; -import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js'; +import {addPaapiConfig, isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import {includes} from '../../src/polyfill.js'; import {S2S_VENDORS} from './config.js'; @@ -509,7 +509,7 @@ export function PrebidServer() { } }, onFledge: (params) => { - addComponentAuction({auctionId: bidRequests[0].auctionId, ...params}, params.config); + addPaapiConfig({auctionId: bidRequests[0].auctionId, ...params}, {config: params.config}); } }) } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 7f4ffc84070..1ec3eb0ccd0 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -232,7 +232,7 @@ const PBS_CONVERTER = ortbConverter({ }, fledgeAuctionConfigs(orig, response, ortbResponse, context) { const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => { + .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => { const bidderReq = impCtx.actualBidderRequests.find(br => br.bidderCode === cfg.bidder); const bidReq = impCtx.actualBidRequests.get(cfg.bidder); return { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 1d10c3161e5..dae0902d4cc 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -296,7 +296,7 @@ export function newBidder(spec) { onPaapi: (paapiConfig) => { const bidRequest = bidRequestMap[paapiConfig.bidId]; if (bidRequest) { - addComponentAuction(bidRequest, paapiConfig.config); + addPaapiConfig(bidRequest, paapiConfig); } else { logWarn('Received fledge auction configuration for an unknown bidId', paapiConfig); } @@ -363,12 +363,13 @@ export function newBidder(spec) { // Transition from 'fledge' to 'paapi' // TODO: remove this in prebid 9 -const PAAPI_RESPONSE_PROPS = ['paapiAuctionConfigs', 'fledgeAuctionConfigs']; -const RESPONSE_PROPS = ['bids'].concat(PAAPI_RESPONSE_PROPS); +const PAAPI_PROPS = ['paapi', 'fledgeAuctionConfigs']; +const RESPONSE_PROPS = ['bids'].concat(PAAPI_PROPS); + function getPaapiConfigs(adapterResponse) { - const [paapi, fledge] = PAAPI_RESPONSE_PROPS.map(prop => adapterResponse[prop]); + const [paapi, fledge] = PAAPI_PROPS.map(prop => adapterResponse[prop]); if (paapi != null && fledge != null) { - throw new Error(`Adapter response should use ${PAAPI_RESPONSE_PROPS[0]} over ${PAAPI_RESPONSE_PROPS[1]}, not both`); + throw new Error(`Adapter response should use ${PAAPI_PROPS[0]} over ${PAAPI_PROPS[1]}, not both`); } return paapi ?? fledge; } @@ -437,7 +438,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe // adapters can reply with: // a single bid // an array of bids - // a BidderAuctionResponse object ({bids: [*], paapiAuctionConfigs: [*]}) + // a BidderAuctionResponse object let bids, paapiConfigs; if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { @@ -548,8 +549,8 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon } }, 'registerSyncs') -export const addComponentAuction = hook('sync', (request, fledgeAuctionConfig) => { -}, 'addComponentAuction'); +export const addPaapiConfig = hook('sync', (request, paapiConfig) => { +}, 'addPaapiConfig'); // check that the bid has a width and height set function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) { diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index ab99ba2aa0c..4de51cc02a4 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -167,8 +167,8 @@ describe('bid interceptor', () => { describe('paapi', () => { it('should accept literals', () => { const mockConfig = [ - {paapi: 1}, - {paapi: 2} + {config: {paapi: 1}}, + {config: {paapi: 2}} ] const paapi = matchingRule({paapi: mockConfig}).paapi({}); expect(paapi).to.eql(mockConfig); @@ -179,6 +179,30 @@ describe('bid interceptor', () => { const args = [{}, {}, {}]; matchingRule({paapi: paapiDef}).paapi(...args); expect(paapiDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + + Object.entries({ + 'literal': (cfg) => [cfg], + 'function': (cfg) => () => [cfg] + }).forEach(([t, makeConfigs]) => { + describe(`when paapi is defined as a ${t}`, () => { + it('should wrap top-level configs in "config"', () => { + const cfg = {decisionLogicURL: 'example'}; + expect(matchingRule({paapi: makeConfigs(cfg)}).paapi({})).to.eql([{ + config: cfg + }]) + }); + + Object.entries({ + 'config': {config: 1}, + 'igb': {igb: 1}, + 'config and igb': {config: 1, igb: 2} + }).forEach(([t, cfg]) => { + it(`should not wrap configs that define top-level ${t}`, () => { + expect(matchingRule({paapi: makeConfigs(cfg)}).paapi({})).to.eql([cfg]); + }) + }) + }) }) }) @@ -274,8 +298,8 @@ describe('bid interceptor', () => { it('should call addPaapiConfigs when provided', () => { const mockPaapiConfigs = [ - {paapi: 1}, - {paapi: 2} + {config: {paapi: 1}}, + {config: {paapi: 2}} ] setRules({ when: {id: 2}, diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index c7d6d88bd12..bc1faa23833 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -6,13 +6,19 @@ import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import { - addComponentAuctionHook, + addPaapiConfigHook, getPAAPIConfig, parseExtPrebidFledge, registerSubmodule, setImpExtAe, - setResponseFledgeConfigs, - reset + setResponsePaapiConfigs, + reset, + parseExtIgi, + mergeBuyers, + IGB_TO_CONFIG, + partitionBuyers, + partitionBuyersByBidder, + buyersToAuctionConfigs } from 'modules/paapi.js'; import * as events from 'src/events.js'; import { EVENTS } from 'src/constants.js'; @@ -20,6 +26,7 @@ import {getGlobal} from '../../../src/prebidGlobal.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {AuctionIndex} from '../../../src/auctionIndex.js'; +import {deepAccess, deepClone} from '../../../src/utils.js'; describe('paapi module', () => { let sandbox; @@ -38,15 +45,18 @@ describe('paapi module', () => { ].forEach(configNS => { describe(`using ${configNS} for configuration`, () => { describe('getPAAPIConfig', function () { - let nextFnSpy, fledgeAuctionConfig; + let nextFnSpy, auctionConfig, paapiConfig; before(() => { config.setConfig({[configNS]: {enabled: true}}); }); beforeEach(() => { - fledgeAuctionConfig = { + auctionConfig = { seller: 'bidder', mock: 'config' }; + paapiConfig = { + config: auctionConfig + } nextFnSpy = sinon.spy(); }); @@ -58,18 +68,93 @@ describe('paapi module', () => { it('should call next()', function () { const request = {auctionId, adUnitCode: 'auc'}; - addComponentAuctionHook(nextFnSpy, request, fledgeAuctionConfig); - sinon.assert.calledWith(nextFnSpy, request, fledgeAuctionConfig); + addPaapiConfigHook(nextFnSpy, request, paapiConfig); + sinon.assert.calledWith(nextFnSpy, request, paapiConfig); + }); + + describe('igb', () => { + let igb1, igb2, buyerAuctionConfig; + beforeEach(() => { + igb1 = { + origin: 'buyer1' + }; + igb2 = { + origin: 'buyer2' + } + buyerAuctionConfig = { + seller: 'seller', + decisionLogicURL: 'seller-decision-logic' + } + config.mergeConfig({ + [configNS]: { + componentSeller: { + auctionConfig: buyerAuctionConfig + } + } + }); + }); + + function addIgb(request, igb) { + addPaapiConfigHook(nextFnSpy, Object.assign({auctionId}, request), {igb}); + } + + it('should be collected into an auction config', () => { + addIgb({adUnitCode: 'au1'}, igb1); + addIgb({adUnitCode: 'au1'}, igb2); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + const buyerConfig = getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + sinon.assert.match(buyerConfig, { + interestGroupBuyers: [igb1.origin, igb2.origin], + ...buyerAuctionConfig + }) + }); + + describe('FPD', () => { + let ortb2, ortb2Imp; + beforeEach(() => { + ortb2 = {'fpd': 1}; + ortb2Imp = {'fpd': 2} + }); + + function getBuyerAuctionConfig() { + addIgb({adUnitCode: 'au1', ortb2, ortb2Imp}, igb1); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } + + it('should be added to auction config', () => { + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: { + ortb2, + ortb2Imp + } + }) + }); + + it('should not override existing perBuyerSignals', () => { + const original = { + ortb2: { + fpd: 'original' + } + }; + igb1.pbs = { + prebid: deepClone(original) + } + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: original + }) + }) + }); }); describe('should collect auction configs', () => { let cf1, cf2; beforeEach(() => { - cf1 = {...fledgeAuctionConfig, id: 1, seller: 'b1'}; - cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, cf1); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, cf2); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); + cf1 = {...auctionConfig, id: 1, seller: 'b1'}; + cf2 = {...auctionConfig, id: 2, seller: 'b2'}; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, {config: cf1}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, {config: cf2}); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); }); it('and make them available at end of auction', () => { @@ -124,16 +209,16 @@ describe('paapi module', () => { it('should drop auction configs after end of auction', () => { events.emit(EVENTS.AUCTION_END, { auctionId }); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); events.emit(EVENTS.AUCTION_END, { auctionId }); expect(getPAAPIConfig({auctionId})).to.eql({}); }); it('should use first size as requestedSize', () => { - addComponentAuctionHook(nextFnSpy, { + addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au1', - }, fledgeAuctionConfig); + }, paapiConfig); events.emit(EVENTS.AUCTION_END, { auctionId, adUnits: [ @@ -153,28 +238,63 @@ describe('paapi module', () => { }) }) - it('should augment auctionSignals with FPD', () => { - addComponentAuctionHook(nextFnSpy, { - auctionId, - adUnitCode: 'au1', - ortb2: {fpd: 1}, - ortb2Imp: {fpd: 2} - }, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId }); - sinon.assert.match(getPAAPIConfig({auctionId}), { - au1: { - componentAuctions: [{ - ...fledgeAuctionConfig, - auctionSignals: { - prebid: { - ortb2: {fpd: 1}, - ortb2Imp: {fpd: 2} - } + describe('FPD', () => { + let ortb2, ortb2Imp; + beforeEach(() => { + ortb2 = {fpd: 1}; + ortb2Imp = {fpd: 2}; + }); + + function getComponentAuctionConfig() { + addPaapiConfigHook(nextFnSpy, { + auctionId, + adUnitCode: 'au1', + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + }, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } + + it('should be added to auctionSignals', () => { + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: {ortb2, ortb2Imp} + }) + }); + it('should not override existing auctionSignals', () => { + auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}} + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: { + ortb2: {fpd: 'original'}, + ortb2Imp + } + }) + }) + + it('should be added to perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; + const pbs = getComponentAuctionConfig().perBuyerSignals; + sinon.assert.match(pbs, { + buyer1: {prebid: {ortb2, ortb2Imp}}, + buyer2: {prebid: {ortb2, ortb2Imp}} + }) + }); + + it('should not override existing perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer']; + const original = { + prebid: { + ortb2: { + fpd: 'original' } - }] + } + } + auctionConfig.perBuyerSignals = { + buyer: deepClone(original) } + sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); }); - }); + }) describe('submodules', () => { let submods; @@ -193,13 +313,13 @@ describe('paapi module', () => { submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); }); it('is invoked with relevant configs', () => { - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); submods.forEach(submod => { sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { - au1: {componentAuctions: [fledgeAuctionConfig]}, - au2: {componentAuctions: [fledgeAuctionConfig]}, + au1: {componentAuctions: [auctionConfig]}, + au2: {componentAuctions: [auctionConfig]}, au3: null }) }); @@ -208,13 +328,13 @@ describe('paapi module', () => { submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { markAsUsed('au1'); }); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); expect(getPAAPIConfig()).to.eql({}); }); it('keeps them available if they do not', () => { - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); expect(getPAAPIConfig()).to.not.be.empty; }) }); @@ -317,7 +437,7 @@ describe('paapi module', () => { }); it('should populate bidfloor/bidfloorcur', () => { - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); events.emit(EVENTS.AUCTION_END, payload); const cfg = getPAAPIConfig({auctionId}).au; const signals = cfg.auctionSignals; @@ -361,9 +481,9 @@ describe('paapi module', () => { [AUCTION2]: [['au2', 'au3'], []], }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { adUnitCodes.forEach(adUnitCode => { - const cfg = {...fledgeAuctionConfig, auctionId, adUnitCode}; + const cfg = {...auctionConfig, auctionId, adUnitCode}; configs[auctionId][adUnitCode] = cfg; - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode}, cfg); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); }); events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes) }); }); @@ -478,8 +598,8 @@ describe('paapi module', () => { ] }]; - function expectFledgeFlags(...enableFlags) { - const bidRequests = Object.fromEntries( + function mark() { + return Object.fromEntries( adapterManager.makeBidRequests( adUnits, Date.now(), @@ -489,12 +609,26 @@ describe('paapi module', () => { [] ).map(b => [b.bidderCode, b]) ); + } + function expectFledgeFlags(...enableFlags) { + const bidRequests = mark(); expect(bidRequests.appnexus.fledgeEnabled).to.eql(enableFlags[0].enabled); + expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); expect(bidRequests.rubicon.fledgeEnabled).to.eql(enableFlags[1].enabled); + expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); + + Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { + if (bid.ortb2Imp?.ext?.ae) { + sinon.assert.match(bid.ortb2Imp.ext.igs, { + ae: bid.ortb2Imp.ext.ae, + biddable: 1 + }); + } + }) } describe('with setBidderConfig()', () => { @@ -534,6 +668,34 @@ describe('paapi module', () => { expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); }); + Object.entries({ + 'not set': { + cfg: {}, + componentSeller: false + }, + 'set': { + cfg: { + componentSeller: { + auctionConfig: { + decisionLogicURL: 'publisher.example' + } + } + }, + componentSeller: true + } + }).forEach(([t, {cfg, componentSeller}]) => { + it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { + config.setConfig({ + [configNS]: { + enabled: true, + defaultForSlots: 1, + ...cfg + } + }); + Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); + }) + }) + it('should not override pub-defined ext.ae', () => { config.setConfig({ bidderSequence: 'fixed', @@ -545,104 +707,437 @@ describe('paapi module', () => { Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); }); + + it('should populate ext.igs when request has ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + [configNS]: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); + expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); + }) + + it('should not override pub-defined ext.igs', () => { + config.setConfig({ + [configNS]: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); + const bidReqs = mark(); + Object.values(bidReqs).flatMap(req => req.bids).forEach(bid => { + sinon.assert.match(bid.ortb2Imp.ext, { + ae: 1, + igs: { + ae: 1, + biddable: 0 + } + }) + }) + }); + + it('should fill ext.ae from ext.igs, if defined', () => { + config.setConfig({ + [configNS]: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}) + }); }); }); }); }); + describe('igb', () => { + let igb1, igb2; + const buyer1 = 'https://buyer1.example'; + const buyer2 = 'https://buyer2.example'; + beforeEach(() => { + igb1 = { + origin: buyer1, + cur: 'EUR', + maxbid: 1, + pbs: { + signal: 1 + }, + ps: { + priority: 1 + } + }; + igb2 = { + origin: buyer2, + cur: 'USD', + maxbid: 2, + pbs: { + signal: 2 + }, + ps: { + priority: 2 + } + } + }); + + describe('mergeBuyers', () => { + it('should merge multiple igb into a partial auction config', () => { + sinon.assert.match(mergeBuyers([igb1, igb2]), { + interestGroupBuyers: [buyer1, buyer2], + perBuyerCurrencies: { + [buyer1]: 'EUR', + [buyer2]: 'USD' + }, + perBuyerSignals: { + [buyer1]: { + signal: 1 + }, + [buyer2]: { + signal: 2 + } + }, + perBuyerPrioritySignals: { + [buyer1]: { + priority: 1 + }, + [buyer2]: { + priority: 2 + } + }, + auctionSignals: { + prebid: { + perBuyerMaxbid: { + [buyer1]: 1, + [buyer2]: 2 + } + } + } + }); + }); + + Object.entries(IGB_TO_CONFIG).forEach(([igbField, configField]) => { + it(`should not set ${configField} if ${igbField} is undefined`, () => { + delete igb1[igbField]; + expect(deepAccess(mergeBuyers([igb1, igb2]), configField)[buyer1]).to.not.exist; + }); + }); + + it('ignores igbs that have no origin', () => { + delete igb1.origin; + expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb2])); + }); + + it('ignores igbs with duplicate origin', () => { + igb2.origin = igb1.origin; + expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb1])); + }) + }); + + describe('partitionBuyers', () => { + it('should return a single partition when there are no duplicates', () => { + expect(partitionBuyers([igb1, igb2])).to.eql([[igb1, igb2]]); + }); + it('should ignore igbs that have no origin', () => { + delete igb1.origin; + expect(partitionBuyers([igb1, igb2])).to.eql([[igb2]]); + }) + it('should return a single partition when duplicates exist, but do not conflict', () => { + expect(partitionBuyers([igb1, igb2, deepClone(igb1)])).to.eql([[igb1, igb2]]); + }); + it('should return multiple partitions when there are conflicts', () => { + const igb3 = deepClone(igb1); + const igb4 = deepClone(igb1); + igb3.pbs.signal = 'conflict'; + igb4.ps.signal = 'conflict'; + expect(partitionBuyers([igb1, igb2, igb3, igb4])).to.eql([ + [igb1, igb2], + [igb3], + [igb4] + ]); + }) + }); + + describe('partitionBuyersByBidder', () => { + it('should split requests by bidder', () => { + expect(partitionBuyersByBidder([[{bidder: 'a'}, igb1], [{bidder: 'b'}, igb2]])).to.eql([ + [{bidder: 'a'}, [igb1]], + [{bidder: 'b'}, [igb2]] + ]); + }); + + it('accepts repeated buyers, if from different bidders', () => { + expect(partitionBuyersByBidder([ + [{bidder: 'a', extra: 'data'}, igb1], + [{bidder: 'b', more: 'data'}, igb1], + [{bidder: 'a'}, igb2], + [{bidder: 'b'}, igb2] + ])).to.eql([ + [{bidder: 'a', extra: 'data'}, [igb1, igb2]], + [{bidder: 'b', more: 'data'}, [igb1, igb2]] + ]) + }) + describe('buyersToAuctionConfig', () => { + let config, partitioners, merge, igbRequests; + beforeEach(() => { + config = { + auctionConfig: { + decisionLogicURL: 'mock-decision-logic' + } + } + partitioners = { + compact: sinon.stub(), + expand: sinon.stub(), + } + let i = 0; + merge = sinon.stub().callsFake(() => ({config: i++})); + igbRequests = [ + [{}, igb1], + [{}, igb2] + ]; + }); + + function toAuctionConfig(reqs = igbRequests) { + return buyersToAuctionConfigs(reqs, merge, config, partitioners); + } + + it('uses compact partitions by default, and returns an auction config for each one', () => { + partitioners.compact.returns([[{}, 1], [{}, 2]]); + const [cf1, cf2] = toAuctionConfig(); + sinon.assert.match(cf1, { + ...config.auctionConfig, + config: 0 + }); + sinon.assert.match(cf2, { + ...config.auctionConfig, + config: 1 + }) + sinon.assert.calledWith(partitioners.compact, igbRequests); + [1, 2].forEach(mockPart => sinon.assert.calledWith(merge, mockPart)); + }); + + it('uses per-bidder partition when config has separateAuctions', () => { + config.separateAuctions = true; + partitioners.expand.returns([]); + toAuctionConfig(); + sinon.assert.called(partitioners.expand); + }); + + it('does not return any auction config when configuration does not specify auctionConfig', () => { + delete config.auctionConfig; + expect(toAuctionConfig()).to.eql([]); + Object.values(partitioners).forEach(part => sinon.assert.notCalled(part)); + }); + + it('sets FPD in auction signals when partitioner returns it', () => { + const fpd = { + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + } + partitioners.compact.returns([[{}], [fpd]]); + const [cf1, cf2] = toAuctionConfig(); + expect(cf1.auctionSignals?.prebid).to.not.exist; + expect(cf2.auctionSignals.prebid).to.eql(fpd); + }) + }) + }); + }); + describe('ortb processors for fledge', () => { it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = {ext: {ae: 1}}; + const imp = {ext: {ae: 1, igs: {}}}; setImpExtAe(imp, {}, {bidderRequest: {}}); expect(imp.ext.ae).to.not.exist; + expect(imp.ext.igs).to.not.exist; }); it('imp.ext.ae should be left intact if fledge is enabled', () => { - const imp = {ext: {ae: 2}}; + const imp = {ext: {ae: 2, igs: {biddable: 0}}}; setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); - expect(imp.ext.ae).to.equal(2); + expect(imp.ext).to.eql({ + ae: 2, + igs: { + biddable: 0 + } + }) }); - describe('parseExtPrebidFledge', () => { - function packageConfigs(configs) { - return { - ext: { - prebid: { - fledge: { - auctionconfigs: configs - } - } - } - }; - } + describe('response parsing', () => { function generateImpCtx(fledgeFlags) { return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); } - function generateCfg(impid, ...ids) { - return ids.map((id) => ({impid, config: {id}})); - } - - function extractResult(ctx) { + function extractResult(type, ctx) { return Object.fromEntries( Object.entries(ctx) - .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .map(([impid, ctx]) => [impid, ctx.paapiConfigs?.map(cfg => cfg[type].id)]) .filter(([_, val]) => val != null) ); } - it('should collect fledge configs by imp', () => { - const ctx = { - impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) - }; - const resp = packageConfigs( - generateCfg('e1', 1, 2, 3) - .concat(generateCfg('e2', 4) - .concat(generateCfg('d1', 5, 6))) - ); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({ - e1: [1, 2, 3], - e2: [4], - }); - }); - it('should not choke if fledge config references unknown imp', () => { - const ctx = {impContext: generateImpCtx({i: 1})}; - const resp = packageConfigs(generateCfg('unknown', 1)); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({}); + Object.entries({ + 'parseExtPrebidFledge': { + parser: parseExtPrebidFledge, + responses: { + 'ext.prebid.fledge'(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + }; + }, + } + }, + 'parseExtIgi': { + parser: parseExtIgi, + responses: { + 'ext.igi.igs'(configs) { + return { + ext: { + igi: [{ + igs: configs + }] + } + } + }, + 'ext.igi.igs with impid on igi'(configs) { + return { + ext: { + igi: configs.map(cfg => { + const impid = cfg.impid; + delete cfg.impid; + return { + impid, + igs: [cfg] + } + }) + } + } + }, + 'ext.igi.igs with conflicting impid'(configs) { + return { + ext: { + igi: [{ + impid: 'conflict', + igs: configs + }] + } + } + } + } + } + }).forEach(([t, {parser, responses}]) => { + describe(t, () => { + Object.entries(responses).forEach(([t, packageConfigs]) => { + describe(`when response uses ${t}`, () => { + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + it('should collect auction configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parser({}, resp, ctx); + expect(extractResult('config', ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parser({}, resp, ctx); + expect(extractResult('config', ctx.impContext)).to.eql({}); + }); + }) + }) + }) }); - }); - describe('setResponseFledgeConfigs', () => { - it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + + describe('response ext.igi.igb', () => { + it('should collect igb by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = { + ext: { + igi: [ + { + impid: 'e1', + igb: [ + {id: 1}, + {id: 2} + ] + }, + { + impid: 'e2', + igb: [ + {id: 3} + ] + }, + { + impid: 'd1', + igb: [ + {id: 4} + ] + } + ] + } + } + parseExtIgi({}, resp, ctx); + expect(extractResult('igb', ctx.impContext)).to.eql({ + e1: [1, 2], + e2: [3], + }); + }) + }) + }) + + describe('setResponsePaapiConfigs', () => { + it('should set paapi configs/igb paired with their corresponding bid id', () => { const ctx = { impContext: { 1: { bidRequest: {bidId: 'bid1'}, - fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + paapiConfigs: [{config: {id: 1}}, {config: {id: 2}}] }, 2: { bidRequest: {bidId: 'bid2'}, - fledgeConfigs: [{config: {id: 3}}] + paapiConfigs: [{config: {id: 3}}] }, 3: { bidRequest: {bidId: 'bid3'} + }, + 4: { + bidRequest: {bidId: 'bid1'}, + paapiConfigs: [{igb: {id: 4}}] } } }; const resp = {}; - setResponseFledgeConfigs(resp, {}, ctx); - expect(resp.fledgeAuctionConfigs).to.eql([ + setResponsePaapiConfigs(resp, {}, ctx); + expect(resp.paapi).to.eql([ {bidId: 'bid1', config: {id: 1}}, {bidId: 'bid1', config: {id: 2}}, {bidId: 'bid2', config: {id: 3}}, + {bidId: 'bid1', igb: {id: 4}} ]); }); - it('should not set fledgeAuctionConfigs if none exist', () => { + it('should not set paapi if no config or igb exists', () => { const resp = {}; - setResponseFledgeConfigs(resp, {}, { + setResponsePaapiConfigs(resp, {}, { impContext: { 1: { - fledgeConfigs: [] + paapiConfigs: [] }, 2: {} } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 8e92cecd36b..c6335227202 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -24,14 +24,14 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; -import 'modules/fledgeForGpt.js'; +import 'modules/paapi.js'; import * as redactor from 'src/activities/redactor.js'; import * as activityRules from 'src/activities/rules.js'; import {hook} from '../../../src/hook.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; -import {addComponentAuction, registerBidder} from 'src/adapters/bidderFactory.js'; +import {addPaapiConfig, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepSetValue} from '../../../src/utils.js'; @@ -3487,11 +3487,11 @@ describe('S2S Adapter', function () { } before(() => { - addComponentAuction.before(fledgeHook); + addPaapiConfig.before(fledgeHook); }); after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); + addPaapiConfig.getHooks({hook: fledgeHook}).remove(); }) beforeEach(function () { @@ -3520,8 +3520,8 @@ describe('S2S Adapter', function () { function expectFledgeCalls() { const auctionId = bidderRequests[0].auctionId; - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), {id: 1}) - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), {id: 2}) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), {config: {id: 1}}) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), {config: {id: 2}}) } it('calls addComponentAuction alongside addBidResponse', function () { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 1e70c938a57..ef6d1de0b30 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,4 +1,4 @@ -import {addComponentAuction, isValid, newBidder, registerBidder} from 'src/adapters/bidderFactory.js'; +import {addPaapiConfig, addIGBuyer, isValid, newBidder, registerBidder} from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; import {expect} from 'chai'; @@ -1460,6 +1460,9 @@ describe('bidderFactory', () => { bidId: '1', config: { foo: 'bar' + }, + igb: { + foo: 'bar' } } @@ -1482,72 +1485,69 @@ describe('bidderFactory', () => { sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); }) - describe('when response has PAAPI auction config', function() { + describe('when response has PAAPI config', function() { let paapiStub; function paapiHook(next, ...args) { paapiStub(...args); } + function runBidder(response) { + const bidder = newBidder(spec); + spec.interpretResponse.returns(response); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + } + before(() => { - addComponentAuction.before(paapiHook); + addPaapiConfig.before(paapiHook); }); after(() => { - addComponentAuction.getHooks({hook: paapiHook}).remove(); + addPaapiConfig.getHooks({hook: paapiHook}).remove(); }) beforeEach(function () { paapiStub = sinon.stub(); }); - const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapiAuctionConfigs']; + const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapi']; it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => { - const bidder = newBidder(spec); - spec.interpretResponse.returns(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) expect(() => { - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - }).to.throw; + runBidder(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) + }).to.throw(); }) PAAPI_PROPS.forEach(paapiProp => { describe(`using ${paapiProp}`, () => { - it('should call paapi hook with PAAPI configs', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ + it('should call paapi config hook with auction configs', function() { + runBidder({ bids: bids, [paapiProp]: [paapiConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - + }) expect(paapiStub.calledOnce).to.equal(true); - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); }) Object.entries({ 'missing': undefined, 'an empty array': [] }).forEach(([t, bids]) => { - it(`should call paapi hook with PAAPI configs even when bids is ${t}`, function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ + it(`should call paapi config hook with PAAPI configs even when bids is ${t}`, function() { + runBidder({ bids, [paapiProp]: [paapiConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - + }) expect(paapiStub.calledOnce).to.be.true; - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); expect(addBidResponseStub.calledOnce).to.equal(false); - }) - }) - }) - }) - }) - }) + }); + }); + }); + }); + }); + }); }); describe('bid response isValid', () => { From df32b86a9480c0b46710749afa4a1bc577b2edf7 Mon Sep 17 00:00:00 2001 From: lusitdev <70213175+lusitdev@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:24:12 +0200 Subject: [PATCH 0128/1097] Etarget Bid Adapter: Add GVL ID (GDPR Enforcement) (#11649) --- modules/etargetBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index cced180e061..6f2d217993f 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'etarget'; +const GVL_ID = 29; const countryMap = { 1: 'sk', 2: 'cz', @@ -19,6 +20,7 @@ const countryMap = { } export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid: function (bid) { return !!(bid.params.refid && bid.params.country); From 96754999bb7625ab76f7b91b749bec08c60abc95 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Mon, 3 Jun 2024 08:35:57 -0500 Subject: [PATCH 0129/1097] 33across - use video.plcmt instead of video.placement (#11641) --- modules/33acrossBidAdapter.js | 28 ++- test/spec/modules/33acrossBidAdapter_spec.js | 221 +++++++++++++------ 2 files changed, 176 insertions(+), 73 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 0e9beb22013..d8a32d75afc 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -36,6 +36,7 @@ const VIDEO_ORTB_PARAMS = [ 'minduration', 'maxduration', 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -140,10 +141,10 @@ function _validateVideo(bid) { } // If placement if defined, it must be a number - if ( - typeof videoParams.placement !== 'undefined' && - typeof videoParams.placement !== 'number' - ) { + if ([ videoParams.placement, videoParams.plcmt ].some(value => ( + typeof value !== 'undefined' && + typeof value !== 'number' + ))) { return false; } @@ -490,12 +491,27 @@ function _buildVideoORTB(bidRequest) { // Placement Inference Rules: // - If no placement is defined then default to 2 (In Banner) + // - If the old deprecated field is defined, use its value for the recent placement field // - If product is instream (for instream context) then override placement to 1 - video.placement = video.placement || 2; + + const calculatePlacementValue = () => { + const IN_BANNER_PLACEMENT_VALUE = 2; + + if (video.placement) { + logWarn('[33Across Adapter] The ORTB field `placement` is deprecated, please use `plcmt` instead'); + + return video.placement; + } + + return IN_BANNER_PLACEMENT_VALUE; + } + + video.plcmt ??= calculatePlacementValue(); if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; - video.placement = 1; + video.plcmt = 1; + video.placement &&= 1; } // bidfloors diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 9cc038428bc..936e7cee074 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -121,7 +121,7 @@ describe('33acrossBidAdapter:', function () { video: { w: 300, h: 250, - placement: 2, + plcmt: 2, ...params } }); @@ -733,6 +733,11 @@ describe('33acrossBidAdapter:', function () { 'foo' ]; + invalidPlacement.forEach((placement) => { + this.bid.mediaTypes.video.plcmt = placement; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + invalidPlacement.forEach((placement) => { this.bid.mediaTypes.video.placement = placement; expect(spec.isBidRequestValid(this.bid)).to.be.false; @@ -1520,89 +1525,171 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when mediaType has video only and context is instream', function() { - it('builds instream request with default params', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({context: 'instream'}) - .build() - ); + context('when mediaType has video only', function() { + context('and context is instream', function() { + it('builds instream request with default params', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream'}) + .build() + ); - const ttxRequest = new TtxRequestBuilder() - .withVideo() - .withProduct('instream') - .build(); + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') + .build(); - ttxRequest.imp[0].video.placement = 1; - ttxRequest.imp[0].video.startdelay = 0; + ttxRequest.imp[0].video.plcmt = 1; + ttxRequest.imp[0].video.startdelay = 0; - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); - }); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); - it('builds instream request with params passed', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({context: 'instream', startdelay: -2}) - .build() - ); + it('builds instream request with params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'instream', startdelay: -2}) + .build() + ); - const ttxRequest = new TtxRequestBuilder() - .withVideo({startdelay: -2, placement: 1}) - .withProduct('instream') - .build(); + const ttxRequest = new TtxRequestBuilder() + .withVideo({startdelay: -2, plcmt: 1}) + .withProduct('instream') + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); - }); - }); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); - context('when mediaType has video only and context is outstream', function() { - it('builds siab request with video only with default params', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({context: 'outstream'}) - .build() - ); + it('overrides the placement value', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({ + plcmt: 2, // Incorrect placement value for an instream video + placement: 2, // Placement specified in the DEPRECATED field. + context: 'instream' + }) + .build() + ); - const ttxRequest = new TtxRequestBuilder() - .withVideo() - .withProduct('siab') - .build(); + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') + .build(); - ttxRequest.imp[0].video.placement = 2; + ttxRequest.imp[0].video.plcmt = 1; + ttxRequest.imp[0].video.placement = 1; + ttxRequest.imp[0].video.startdelay = 0; - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + + context('when the placement is still specified in the DEPRECATED `placement` field', function() { + it('overwrites its value and sets it in the recent `plcmt` field as well', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({ + placement: 2, // Incorrect placement for an instream video + context: 'instream' + }) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') + .build(); + + ttxRequest.imp[0].video.plcmt = 1; + ttxRequest.imp[0].video.placement = 1; + ttxRequest.imp[0].video.startdelay = 0; + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + }); }); - it('builds siab request with video params passed', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) - .build() - ); + context('and context is outstream', function() { + it('builds siab request with video only with default params', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream'}) + .build() + ); - const ttxRequest = new TtxRequestBuilder() - .withVideo({placement: 3, playbackmethod: [2]}) - .withProduct('siab') - .build(); + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('siab') + .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + // No placement specified, final value should default to 2. + ttxRequest.imp[0].video.plcmt = 2; - validateBuiltServerRequest(builtServerRequest, serverRequest); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + it('builds siab request with video params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', plcmt: 3, playbackmethod: [2]}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({plcmt: 3, playbackmethod: [2]}) + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + context('and the placement is specified in the DEPRECATED `placement` field', function() { + it('sets the recent `plcmt` field', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({plcmt: 3, placement: 3, playbackmethod: [2]}) + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); }); }); @@ -1686,7 +1773,7 @@ describe('33acrossBidAdapter:', function () { .withProduct('siab') .build(); - ttxRequest.imp[0].video.placement = 2; + ttxRequest.imp[0].video.plcmt = 2; const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) From d406b913fbb790e8fc9849f3aee76399d42d3a43 Mon Sep 17 00:00:00 2001 From: pm-nitin-shirsat <107102698+pm-nitin-shirsat@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:25:04 +0530 Subject: [PATCH 0130/1097] Pubmatic Bid Adapter : update markdown (#11634) * Implement functionality for deal priority * Update test cases * kick off test manually * Added support of GPP to PubMatic adapter * gpp_sid in user syncs supposed to encode as a string, not an array * Remove extra space * Remove trailing spaces * Remove the placement parameter and update test cases accordingly, Add plcmt parameter. * Supporting placement parameter and logging warning message, for the plcmt parameter, if it is missing. * Remove commented code * Added plcmt in the pubmaticBidAdapter.md file --------- Co-authored-by: Chris Huie --- modules/pubmaticBidAdapter.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index baf58177505..e472b30a916 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -70,6 +70,7 @@ var adVideoAdUnits = [ protocols: [ 2, 3 ], // optional battr: [ 13, 14 ], // optional linearity: 1, // optional + plcmt: 1, // optional placement: 2, // optional minbitrate: 10, // optional maxbitrate: 10 // optional @@ -169,6 +170,7 @@ var adUnits = [ protocols: [ 2, 3 ], // optional battr: [ 13, 14 ], // optional linearity: 1, // optional + plcmt: 1, // optional placement: 2, // optional minbitrate: 10, // optional maxbitrate: 10 // optional From ccf98137c3476e432e599a05aab10522795b3650 Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Mon, 3 Jun 2024 10:10:24 -0400 Subject: [PATCH 0131/1097] MPY-161: Video logic update (#11644) --- modules/tripleliftBidAdapter.js | 15 +--------- .../spec/modules/tripleliftBidAdapter_spec.js | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index fc0d73dc44b..056ab2b9d19 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -236,20 +236,7 @@ function _getORTBVideo(bidRequest) { } catch (err) { logWarn('Video size not defined', err); } - // honor existing publisher settings - if (video.context === 'instream') { - if (!video.placement) { - video.placement = 1; - } - } - if (video.context === 'outstream') { - if (!video.placement) { - video.placement = 3 - } else if ([3, 4, 5].indexOf(video.placement) === -1) { - logMessage(`video.placement value of ${video.placement} is invalid for outstream context. Setting placement to 3`) - video.placement = 3 - } - } + if (video.playbackmethod && Number.isInteger(video.playbackmethod)) { video.playbackmethod = Array.from(String(video.playbackmethod), Number); } diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 851425574d0..09e57e29a12 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -167,7 +167,8 @@ describe('triplelift adapter', function () { video: { context: 'instream', playerSize: [640, 480], - playbackmethod: 5 + playbackmethod: 5, + plcmt: 1 } }, adUnitCode: 'adunit-code-instream', @@ -307,7 +308,8 @@ describe('triplelift adapter', function () { video: { context: 'instream', playerSize: [640, 480], - playbackmethod: [1, 2, 3] + playbackmethod: [1, 2, 3], + plcmt: 1 }, banner: { sizes: [ @@ -524,7 +526,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'outstream', - playerSize: [640, 480] + playerSize: [640, 480], + plcmt: 4 } }, adUnitCode: 'adunit-code-instream', @@ -553,7 +556,7 @@ describe('triplelift adapter', function () { video: { context: 'outstream', playerSize: [640, 480], - placement: 6 + plcmt: 3 } }, adUnitCode: 'adunit-code-instream', @@ -637,12 +640,12 @@ describe('triplelift adapter', function () { expect(payload.imp[1].tagid).to.equal('insteam_test'); expect(payload.imp[1].floor).to.equal(1.0); expect(payload.imp[1].video).to.exist.and.to.be.a('object'); - expect(payload.imp[1].video.placement).to.equal(1); + expect(payload.imp[1].video.plcmt).to.equal(1); // banner and outstream video expect(payload.imp[2]).to.have.property('video'); expect(payload.imp[2]).to.have.property('banner'); expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - expect(payload.imp[2].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[2].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream'}); // banner and incomplete video expect(payload.imp[3]).to.not.have.property('video'); expect(payload.imp[3]).to.have.property('banner'); @@ -655,21 +658,24 @@ describe('triplelift adapter', function () { expect(payload.imp[5]).to.not.have.property('banner'); expect(payload.imp[5]).to.have.property('video'); expect(payload.imp[5].video).to.exist.and.to.be.a('object'); - expect(payload.imp[5].video.placement).to.equal(1); + expect(payload.imp[5].video.plcmt).to.equal(1); // banner and outream video and native expect(payload.imp[6]).to.have.property('video'); expect(payload.imp[6]).to.have.property('banner'); expect(payload.imp[6].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - expect(payload.imp[6].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[6].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream'}); // outstream video only expect(payload.imp[7]).to.have.property('video'); expect(payload.imp[7]).to.not.have.property('banner'); - expect(payload.imp[7].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[7].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream'}); // banner and incomplete outstream (missing size); video request is permitted so banner can still monetize expect(payload.imp[8]).to.have.property('video'); expect(payload.imp[8]).to.have.property('banner'); expect(payload.imp[8].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - expect(payload.imp[8].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[8].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'context': 'outstream'}); + // outstream new plcmt value + expect(payload.imp[13]).to.have.property('video'); + expect(payload.imp[13].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'plcmt': 3}); }); it('should check for valid outstream placement values', function () { @@ -694,12 +700,12 @@ describe('triplelift adapter', function () { expect(payload.imp[12]).to.not.have.property('banner'); expect(payload.imp[12]).to.have.property('video'); expect(payload.imp[12].video).to.exist.and.to.be.a('object'); - expect(payload.imp[12].video.placement).to.equal(3); + expect(payload.imp[12].video.plcmt).to.equal(4); // outstream video; invalid placement expect(payload.imp[13]).to.not.have.property('banner'); expect(payload.imp[13]).to.have.property('video'); expect(payload.imp[13].video).to.exist.and.to.be.a('object'); - expect(payload.imp[13].video.placement).to.equal(3); + expect(payload.imp[13].video.plcmt).to.equal(3); }); it('should add tid to imp.ext if transactionId exists', function() { From 4d1692e8882ef9c6d37013313f3e0de2f379a3bb Mon Sep 17 00:00:00 2001 From: Tommy Callaway Date: Mon, 3 Jun 2024 07:27:48 -0700 Subject: [PATCH 0132/1097] IMDS Bid Adapter : add support for video.plcmt (#11615) * imds: Rename usersync from "pixel" to correct name of "image". * imds: Update documentation for "DFP Video Creative" to match prebid.github.io. * imds: Test modifications for renamed pixel->image usersync * imds: Remove warning for valid no-bid empty body 204 responses. * updates imds bidder for prebid9 with video.plcmt * newline --------- Co-authored-by: Timothy M. Ace --- modules/imdsBidAdapter.js | 2 +- test/spec/modules/imdsBidAdapter_spec.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/imdsBidAdapter.js b/modules/imdsBidAdapter.js index 4cad1d614c5..0a0514df205 100644 --- a/modules/imdsBidAdapter.js +++ b/modules/imdsBidAdapter.js @@ -11,7 +11,7 @@ const BID_SCHEME = 'https://'; const BID_DOMAIN = 'technoratimedia.com'; const USER_SYNC_IFRAME_URL = 'https://ad-cdn.technoratimedia.com/html/usersync.html'; const USER_SYNC_PIXEL_URL = 'https://sync.technoratimedia.com/services'; -const VIDEO_PARAMS = [ 'minduration', 'maxduration', 'startdelay', 'placement', 'linearity', 'mimes', 'protocols', 'api' ]; +const VIDEO_PARAMS = [ 'minduration', 'maxduration', 'startdelay', 'placement', 'plcmt', 'linearity', 'mimes', 'protocols', 'api' ]; const BLOCKED_AD_SIZES = [ '1x1', '1x2' diff --git a/test/spec/modules/imdsBidAdapter_spec.js b/test/spec/modules/imdsBidAdapter_spec.js index b71a0bc51d9..2911ee588c0 100644 --- a/test/spec/modules/imdsBidAdapter_spec.js +++ b/test/spec/modules/imdsBidAdapter_spec.js @@ -585,7 +585,7 @@ describe('imdsBidAdapter ', function () { maxduration: 45, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'], protocols: [1], api: 1 @@ -622,7 +622,7 @@ describe('imdsBidAdapter ', function () { maxduration: 45, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'], protocols: [1], api: 1 @@ -651,7 +651,7 @@ describe('imdsBidAdapter ', function () { playerSize: [[640, 480]], startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'] } }, @@ -680,7 +680,7 @@ describe('imdsBidAdapter ', function () { maxduration: 45, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'], protocols: [1], api: 1 @@ -703,7 +703,7 @@ describe('imdsBidAdapter ', function () { playerSize: [[ 640, 480 ]], startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'] } }, @@ -726,7 +726,7 @@ describe('imdsBidAdapter ', function () { w: 640, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'] }, id: 'v2624fabbb078e8-640x480', From 74c86448d5acdc44d7a7db4f5425339e4f65cf0a Mon Sep 17 00:00:00 2001 From: nalexand <35492736+nalexand@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:24:04 +0200 Subject: [PATCH 0133/1097] Mediaimpact Bid Adapter: move advertiserDomains, primaryCatId, secondaryCatIds to ad.meta (#11637) * Add mediaimpact bid adapter * Add mediaimpact bid adapter tests * Add custom sizes * Refactor response meta * mediaimpact adapter fix tests --------- Co-authored-by: koshe --- modules/mediaimpactBidAdapter.js | 10 +++++----- .../spec/modules/mediaimpactBidAdapter_spec.js | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/mediaimpactBidAdapter.js b/modules/mediaimpactBidAdapter.js index 4ce11201507..d62cb789ea4 100644 --- a/modules/mediaimpactBidAdapter.js +++ b/modules/mediaimpactBidAdapter.js @@ -115,12 +115,12 @@ export const spec = { creativeId: ad.creativeId, netRevenue: ad.netRevenue, currency: ad.currency, - winNotification: ad.winNotification - } + winNotification: ad.winNotification, + meta: {} + }; - bidObject.meta = {}; - if (ad.adomain && ad.adomain.length > 0) { - bidObject.meta.advertiserDomains = ad.adomain; + if (ad.meta) { + bidObject.meta = ad.meta; } return bidObject; diff --git a/test/spec/modules/mediaimpactBidAdapter_spec.js b/test/spec/modules/mediaimpactBidAdapter_spec.js index 3d706e59c3f..806f0adeabe 100644 --- a/test/spec/modules/mediaimpactBidAdapter_spec.js +++ b/test/spec/modules/mediaimpactBidAdapter_spec.js @@ -139,9 +139,9 @@ describe('MediaimpactAdapter', function () { 'width': 300, 'height': 250, 'creativeId': '8:123456', - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, 'syncs': [ {'type': 'image', 'url': 'https://test.domain/tracker_1.gif'}, {'type': 'image', 'url': 'https://test.domain/tracker_2.gif'}, @@ -200,9 +200,9 @@ describe('MediaimpactAdapter', function () { 'cpm': 0.01, 'currency': 'USD', 'netRevenue': true, - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, }; it('fill ad for response', function () { @@ -263,9 +263,9 @@ describe('MediaimpactAdapter', function () { 'width': 300, 'height': 250, 'creativeId': '8:123456', - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, 'syncs': [ {'type': 'image', 'link': 'https://test.domain/tracker_1.gif'}, {'type': 'image', 'link': 'https://test.domain/tracker_2.gif'}, From 07e6e301ba0035c9ee0cfbf64c17b6e1bb983945 Mon Sep 17 00:00:00 2001 From: Mike Lei Date: Mon, 3 Jun 2024 09:09:57 -0700 Subject: [PATCH 0134/1097] Flipp Bid Adapter: fix height parameter (#11633) * Flipp Bid Adapter: initial release * Added flippBidAdapter * OFF-372 Support DTX/Hero in flippBidAdapter (#2) * support creativeType * OFF-422 flippBidAdapter handle AdTypes --------- Co-authored-by: Jairo Panduro * OFF-465 Add getUserKey logic to prebid.js adapter (#3) * Support cookie sync and uid * address pr feedback * remove redundant check * OFF-500 Support "startCompact" param for Prebid.JS #4 * set startCompact default value (#5) * fix docs * use client bidding endpoint * update unit testing endpoint * OFF-876 [Prebid Adapter] Check userKey for empty string (#6) * add more checks to userKey * update document * add uuid format statement * modify docs * fix network id * use compactHeight and standardHeight in customData (#7) * OFF-1455 [Prebid.js] height should use the compactHeight and standardHeight fields in decisions response (#8) * Flipp Bid Adapter: initial release * Added flippBidAdapter * OFF-372 Support DTX/Hero in flippBidAdapter (#2) * support creativeType * OFF-422 flippBidAdapter handle AdTypes --------- Co-authored-by: Jairo Panduro * OFF-465 Add getUserKey logic to prebid.js adapter (#3) * Support cookie sync and uid * address pr feedback * remove redundant check * OFF-500 Support "startCompact" param for Prebid.JS #4 * set startCompact default value (#5) * fix docs * use client bidding endpoint * update unit testing endpoint * OFF-876 [Prebid Adapter] Check userKey for empty string (#6) * add more checks to userKey * update document * add uuid format statement * modify docs * fix network id * use compactHeight and standardHeight in customData --------- Co-authored-by: Jairo Panduro * Update flippBidAdapter.js * use compactHeight and standardHeight in customData (#7) * update docs * fix unit test --------- Co-authored-by: Jairo Panduro --- modules/flippBidAdapter.js | 6 +++++- test/spec/modules/flippBidAdapter_spec.js | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index f9c424d9da5..95fd67c779b 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -25,6 +25,7 @@ const DEFAULT_CREATIVE_TYPE = 'NativeX'; const VALID_CREATIVE_TYPES = ['DTX', 'NativeX']; const FLIPP_USER_KEY = 'flipp-uid'; const COMPACT_DEFAULT_HEIGHT = 600; +const STANDARD_DEFAULT_HEIGHT = 1800; let userKey = null; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -166,7 +167,10 @@ export const spec = { if (!isEmpty(res) && !isEmpty(res.decisions) && !isEmpty(res.decisions.inline)) { return res.decisions.inline.map(decision => { const placement = placements.find(p => p.prebid.requestId === decision.prebid?.requestId); - const height = placement.options?.startCompact ? COMPACT_DEFAULT_HEIGHT : decision.height; + const customData = decision.contents[0]?.data?.customData; + const height = placement.options?.startCompact + ? customData?.compactHeight ?? COMPACT_DEFAULT_HEIGHT + : customData?.standardHeight ?? STANDARD_DEFAULT_HEIGHT; return { bidderCode: BIDDER_CODE, requestId: decision.prebid?.requestId, diff --git a/test/spec/modules/flippBidAdapter_spec.js b/test/spec/modules/flippBidAdapter_spec.js index 518052ad91e..9602a156bed 100644 --- a/test/spec/modules/flippBidAdapter_spec.js +++ b/test/spec/modules/flippBidAdapter_spec.js @@ -99,6 +99,14 @@ describe('flippAdapter', function () { 'requestId': '237f4d1a293f99', 'cpm': 1.11, 'creative': 'Returned from server', + }, + 'contents': { + 'data': { + 'customData': { + 'compactHeight': 600, + 'standardHeight': 1800 + } + } } }] }, @@ -114,7 +122,7 @@ describe('flippAdapter', function () { cpm: 1.11, netRevenue: true, width: 300, - height: 600, + height: 1800, creativeId: 262838368, ttl: 30, ad: 'Returned from server', From 1e820bf0cafa488d087bd2a31c5e8affedf3e876 Mon Sep 17 00:00:00 2001 From: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:24:26 +0300 Subject: [PATCH 0135/1097] Krushmedia: changing traffic definition (#11631) * inital * fix * fix * fix * fix * fix * fix * add maintener to md * Added native support * add syncing * updates for prebid 5 compliance * changing traffic definition * upd --------- Co-authored-by: Aiholkin --- modules/krushmediaBidAdapter.js | 4 +++- test/spec/modules/krushmediaBidAdapter_spec.js | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index 876f0ebabc6..c4507201064 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -90,14 +90,15 @@ export const spec = { const placement = { key: bid.params.key, bidId: bid.bidId, - traffic: bid.params.traffic || BANNER, schain: bid.schain || {}, bidFloor: getBidFloor(bid) }; if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + placement.traffic = BANNER; placement.sizes = bid.mediaTypes[BANNER].sizes; } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { + placement.traffic = VIDEO; placement.wPlayer = bid.mediaTypes[VIDEO].playerSize[0]; placement.hPlayer = bid.mediaTypes[VIDEO].playerSize[1]; placement.minduration = bid.mediaTypes[VIDEO].minduration; @@ -115,6 +116,7 @@ export const spec = { placement.api = bid.mediaTypes[VIDEO].api; placement.linearity = bid.mediaTypes[VIDEO].linearity; } else if (bid.mediaTypes && bid.mediaTypes[NATIVE]) { + placement.traffic = NATIVE; placement.native = bid.mediaTypes[NATIVE]; } placements.push(placement); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index fcdcc942290..86437180e94 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -12,8 +12,7 @@ describe('KrushmediabBidAdapter', function () { } }, params: { - key: 783, - traffic: BANNER + key: 783 } }; From fca4691bcaf6cb0cd175774a531715df46379d54 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 3 Jun 2024 09:38:08 -0700 Subject: [PATCH 0136/1097] Multiple modules: clean up unit tests (#11630) * Test chunking * update some bidder eid tests * split eid tests into each userId submodule * cleanup userId_spec * add TEST_PAT config * fix idx, lmp * clean up userId_spec * fix double run, invibes, intentIq * small fixes * undo package-lock changes * update colors, remove empty test * 8pod analytics: clean up interval handler --- karma.conf.maker.js | 31 +- karmaRunner.js | 92 +- modules/ceeIdSystem.js | 2 +- modules/eightPodAnalyticsAdapter.js | 15 +- modules/invibesBidAdapter.js | 2 +- modules/userId/index.js | 2 +- test/helpers/cookies.js | 5 + test/helpers/karma-init.js | 6 - test/pipeline_setup.js | 21 + test/spec/modules/33acrossIdSystem_spec.js | 24 + test/spec/modules/adfBidAdapter_spec.js | 13 +- test/spec/modules/adqueryIdSystem_spec.js | 22 + test/spec/modules/amxIdSystem_spec.js | 312 +++-- test/spec/modules/britepoolIdSystem_spec.js | 19 + test/spec/modules/carodaBidAdapter_spec.js | 13 +- test/spec/modules/criteoIdSystem_spec.js | 19 + ...System_spec.js => czechAdIdSystem_spec.js} | 26 +- .../modules/deepintentDpesIdsystem_spec.js | 18 + test/spec/modules/dianomiBidAdapter_spec.js | 19 +- test/spec/modules/dsaControl_spec.js | 1 - test/spec/modules/eids_spec.js | 783 +---------- test/spec/modules/euidIdSystem_spec.js | 23 +- test/spec/modules/ftrackIdSystem_spec.js | 45 +- test/spec/modules/hadronIdSystem_spec.js | 23 + test/spec/modules/id5IdSystem_spec.js | 43 + .../spec/modules/identityLinkIdSystem_spec.js | 20 + test/spec/modules/idxIdSystem_spec.js | 1 + test/spec/modules/imuIdSystem_spec.js | 37 + test/spec/modules/intentIqIdSystem_spec.js | 3 + test/spec/modules/invibesBidAdapter_spec.js | 10 +- test/spec/modules/kinessoIdSystem_spec.js | 26 + test/spec/modules/liveIntentIdSystem_spec.js | 278 ++++ test/spec/modules/lmpIdSystem_spec.js | 1 + .../modules/lotamePanoramaIdSystem_spec.js | 19 +- test/spec/modules/merkleIdSystem_spec.js | 69 + test/spec/modules/netIdSystem_spec.js | 23 + test/spec/modules/operaadsIdSystem_spec.js | 93 +- test/spec/modules/parrableIdSystem_spec.js | 24 +- test/spec/modules/quantcastIdSystem_spec.js | 22 + test/spec/modules/sharedIdSystem_spec.js | 18 + test/spec/modules/tapadIdSystem_spec.js | 19 + test/spec/modules/tncIdSystem_spec.js | 22 + test/spec/modules/uid2IdSystem_spec.js | 42 +- test/spec/modules/unifiedIdSystem_spec.js | 46 + test/spec/modules/userId_spec.js | 1209 +---------------- .../spec/modules/zeotapIdPlusIdSystem_spec.js | 23 +- test/test_index.js | 23 +- 47 files changed, 1397 insertions(+), 2210 deletions(-) create mode 100644 test/helpers/cookies.js delete mode 100644 test/helpers/karma-init.js create mode 100644 test/pipeline_setup.js rename test/spec/modules/{cpexIdSystem_spec.js => czechAdIdSystem_spec.js} (54%) create mode 100644 test/spec/modules/kinessoIdSystem_spec.js create mode 100644 test/spec/modules/netIdSystem_spec.js create mode 100644 test/spec/modules/unifiedIdSystem_spec.js diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 7b8ca13b20b..fbef10ff567 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -108,14 +108,12 @@ function setBrowsers(karmaConf, browserstack) { module.exports = function(codeCoverage, browserstack, watchMode, file, disableFeatures) { var webpackConfig = newWebpackConfig(codeCoverage, disableFeatures); var plugins = newPluginsArray(browserstack); - - var files = file ? ['test/test_deps.js', file, 'test/helpers/hookSetup.js'].flatMap(f => f) : ['test/test_index.js']; - // This file opens the /debug.html tab automatically. - // It has no real value unless you're running --watch, and intend to do some debugging in the browser. - if (watchMode) { - files.push('test/helpers/karma-init.js'); + if (file) { + file = Array.isArray(file) ? ['test/pipeline_setup.js', ...file] : [file] } + var files = file ? ['test/test_deps.js', ...file, 'test/helpers/hookSetup.js'].flatMap(f => f) : ['test/test_index.js']; + var config = { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: './', @@ -127,15 +125,15 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe }, // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['es5-shim', 'mocha', 'chai', 'sinon'], + frameworks: ['es5-shim', 'mocha', 'chai', 'sinon', 'webpack'], - files: files, + // test files should not be watched or they'll run twice after an update + // (they are still, in fact, watched through autoWatch: true) + files: files.map(fn => ({pattern: fn, watched: false, served: true, included: true})), // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'test/test_index.js': ['webpack', 'sourcemap'] - }, + preprocessors: Object.fromEntries(files.map(f => [f, ['webpack', 'sourcemap']])), // web server port port: 9876, @@ -148,7 +146,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe logLevel: karmaConstants.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, + autoWatch: watchMode, reporters: ['mocha'], @@ -175,15 +173,6 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe plugins: plugins }; - // To ensure that, we are able to run single spec file - // here we are adding preprocessors, when file is passed - if (file) { - config.files.forEach((file) => { - config.preprocessors[file] = ['webpack', 'sourcemap']; - }); - delete config.preprocessors['test/test_index.js']; - } - setReporters(config, codeCoverage, browserstack); setBrowsers(config, browserstack); return config; diff --git a/karmaRunner.js b/karmaRunner.js index 96259069966..7239d2a2556 100644 --- a/karmaRunner.js +++ b/karmaRunner.js @@ -1,23 +1,97 @@ const karma = require('karma'); const process = require('process'); const karmaConfMaker = require('./karma.conf.maker.js'); +const glob = require('glob'); +/** + * Environment variables: + * + * TEST_CHUNKS: number of chunks to split tests into, or MAX to run each test suite in isolation + * TEST_CHUNK: run only this chunk (e.g. TEST_CHUNKS=4 TEST_CHUNK=2 gulp test) will run only the second quarter + * TEST_ALL: set to continue running remaining chunks after a previous chunk failed + * TEST_PAT: test file pattern (default is *_spec.js) + */ -process.on('message', function(options) { - try { - let cfg = karmaConfMaker(options.coverage, options.browserstack, options.watch, options.file, options.disableFeatures); +process.on('message', function (options) { + function info(msg) { + // eslint-disable-next-line no-console + console.log('\x1b[46m\x1b[30m%s\x1b[0m', msg); + } + + function error(msg) { + // eslint-disable-next-line no-console + console.log('\x1b[41m\x1b[37m%s\x1b[0m', msg); + } + + function chunkDesc(chunk) { + return chunk.length > 1 ? `From ${chunk[0]} to ${chunk[chunk.length - 1]}` : chunk[0]; + } + + const failures = []; + + function quit(fail) { + // eslint-disable-next-line no-console + console.log(''); + failures.forEach(([chunkNo, chunkTot, chunk]) => { + error(`Chunk ${chunkNo + 1} of ${chunkTot} failed: ${chunkDesc(chunk)}`); + fail = true; + }); + process.exit(fail ? 1 : 0); + } + + process.on('SIGINT', () => quit()); + + function runKarma(file) { + let cfg = karmaConfMaker(options.coverage, options.browserstack, options.watch, file, options.disableFeatures); if (options.browsers && options.browsers.length) { cfg.browsers = options.browsers; } if (options.oneBrowser) { - cfg.browsers = [cfg.browsers.find((b) => b.toLowerCase().includes(options.oneBrowser.toLowerCase())) || cfg.browsers[0]] + cfg.browsers = [cfg.browsers.find((b) => b.toLowerCase().includes(options.oneBrowser.toLowerCase())) || cfg.browsers[0]]; } cfg = karma.config.parseConfig(null, cfg); - new karma.Server(cfg, (exitCode) => { - process.exit(exitCode); - }).start(); + return new Promise((resolve, reject) => { + new karma.Server(cfg, (exitCode) => { + exitCode ? reject(exitCode) : resolve(exitCode); + }).start(); + }); + } + + try { + let chunks = []; + if (options.file) { + chunks.push([options.file]); + } else { + const chunkNum = process.env['TEST_CHUNKS'] ?? 1; + const pat = process.env['TEST_PAT'] ?? '*_spec.js' + const tests = glob.sync('test/**/' + pat).sort(); + const chunkLen = chunkNum === 'MAX' ? 0 : Math.floor(tests.length / Number(chunkNum)); + chunks.push([]); + tests.forEach((fn) => { + chunks[chunks.length - 1].push(fn); + if (chunks[chunks.length - 1].length > chunkLen) chunks.push([]); + }); + chunks = chunks.filter(chunk => chunk.length > 0); + if (chunks.length > 1) { + info(`Splitting tests into ${chunkNum} chunks, ${chunkLen + 1} suites each`); + } + } + let pm = Promise.resolve(); + chunks.forEach((chunk, i) => { + if (process.env['TEST_CHUNK'] && Number(process.env['TEST_CHUNK']) !== i + 1) return; + pm = pm.then(() => { + info(`Starting chunk ${i + 1} of ${chunks.length}: ${chunkDesc(chunk)}`); + return runKarma(chunk); + }).catch(() => { + failures.push([i, chunks.length, chunk]); + if (!process.env['TEST_ALL']) quit(); + }).finally(() => { + info(`Chunk ${i + 1} of ${chunks.length}: done`); + }); + }); + pm.then(() => quit()); } catch (e) { // eslint-disable-next-line - console.error(e); - process.exit(1); + error(e); + quit(true); } }); diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js index 9c8f1409fd3..5558e2c7d6f 100644 --- a/modules/ceeIdSystem.js +++ b/modules/ceeIdSystem.js @@ -44,7 +44,7 @@ export const ceeIdSubmodule = { * performs action to obtain id and return a value * @function * @returns {(IdResponse|undefined)} - */ + */ getId() { const ceeIdToken = readId(); diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js index 64ff845505d..a1ea76daed1 100644 --- a/modules/eightPodAnalyticsAdapter.js +++ b/modules/eightPodAnalyticsAdapter.js @@ -9,6 +9,7 @@ import {getStorageManager} from '../src/storageManager.js'; const analyticsType = 'endpoint'; const MODULE_NAME = `eightPod`; const MODULE = `${MODULE_NAME}AnalyticProvider`; +let interval; /** * Custom tracking server that gets internal events from EightPod's ad unit @@ -64,7 +65,9 @@ let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { }); // Send queue of event every 10 seconds - setInterval(sendEvents, 10_000); + if (!interval) { + interval = setInterval(sendEvents, 10_000); + } }, resetQueue() { queue = []; @@ -175,6 +178,16 @@ eightPodAnalytics.enableAnalytics = function (config) { logInfo(MODULE, 'init', config); }; +eightPodAnalytics.disableAnalytics = (function (orig) { + return function (...args) { + if (interval) { + clearInterval(interval); + interval = null; + } + return orig.apply(this, args); + }; +})(eightPodAnalytics.disableAnalytics); + /** * Register Analytics Adapter */ diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 7ba2b8225b0..a69311834b2 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -22,7 +22,7 @@ const CONSTANTS = { DISABLE_USER_SYNC: true }; -const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); export const spec = { code: CONSTANTS.BIDDER_CODE, diff --git a/modules/userId/index.js b/modules/userId/index.js index 977af127534..c2d7a9af2d8 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -1057,7 +1057,6 @@ function updateSubmodules() { const submoduleConfig = find(configs, j => j.name && (j.name.toLowerCase() === i.name.toLowerCase() || (i.aliasName && j.name.toLowerCase() === i.aliasName.toLowerCase()))); if (submoduleConfig && i.name !== submoduleConfig.name) submoduleConfig.name = i.name; - i.findRootDomain = findRootDomain; return submoduleConfig ? { submodule: i, config: submoduleConfig, @@ -1117,6 +1116,7 @@ export function requestDataDeletion(next, ...args) { * @param {Submodule} submodule */ export function attachIdSystem(submodule) { + submodule.findRootDomain = findRootDomain; if (!find(submoduleRegistry, i => i.name === submodule.name)) { submoduleRegistry.push(submodule); GDPR_GVLIDS.register(MODULE_TYPE_UID, submodule.name, submodule.gvlid) diff --git a/test/helpers/cookies.js b/test/helpers/cookies.js new file mode 100644 index 00000000000..a0d9da3c595 --- /dev/null +++ b/test/helpers/cookies.js @@ -0,0 +1,5 @@ +export function clearAllCookies() { + document.cookie.split(';').forEach(function (c) { + document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/'); + }); +} diff --git a/test/helpers/karma-init.js b/test/helpers/karma-init.js deleted file mode 100644 index 56e936aa741..00000000000 --- a/test/helpers/karma-init.js +++ /dev/null @@ -1,6 +0,0 @@ -(function (window) { - if (!window.parent.pbjsKarmaInitDone && window.location.pathname === '/context.html') { - window.parent.pbjsKarmaInitDone = true; - window.open('/debug.html', '_blank'); - } -})(window); diff --git a/test/pipeline_setup.js b/test/pipeline_setup.js new file mode 100644 index 00000000000..2790821888e --- /dev/null +++ b/test/pipeline_setup.js @@ -0,0 +1,21 @@ +[it, describe].forEach((ob) => { + ob.only = function () { + [ + 'describe.only and it.only are disabled unless you provide a single spec --file,', + 'because they can silently break the pipeline tests', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .only()') + }; +}); + +[it, describe].forEach((ob) => { + ob.skip = function () { + [ + 'describe.skip and it.skip are disabled,', + 'because they pollute the pipeline test output', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .skip()') + }; +}); diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 364f7d2845c..e2fb6f22cd2 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -3,6 +3,9 @@ import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from 'src/adapterManager.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; +import {attachIdSystem} from '../../../modules/userId/index.js'; describe('33acrossIdSystem', () => { describe('name', () => { @@ -817,4 +820,25 @@ describe('33acrossIdSystem', () => { }); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(thirthyThreeAcrossIdSubmodule); + }) + it('33acrossId', function() { + const userId = { + '33acrossId': { + envelope: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: '33across.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index 2ec1cdf719c..6f4395d548f 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -321,17 +321,14 @@ describe('Adf adapter', function () { let validBidRequests = [{ bidId: 'bidId', params: {}, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }) + userIdAsEids: [ + { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, + { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + ] }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); - assert.deepEqual(request.user.ext.eids, [ - { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, - { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } - ]); + assert.deepEqual(request.user.ext.eids, validBidRequests[0].userIdAsEids); }); it('should send currency if defined', function () { diff --git a/test/spec/modules/adqueryIdSystem_spec.js b/test/spec/modules/adqueryIdSystem_spec.js index 7952f23189e..9b7304d1984 100644 --- a/test/spec/modules/adqueryIdSystem_spec.js +++ b/test/spec/modules/adqueryIdSystem_spec.js @@ -1,6 +1,9 @@ import {adqueryIdSubmodule, storage} from 'modules/adqueryIdSystem.js'; import {server} from 'test/mocks/xhr.js'; import sinon from 'sinon'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const config = { storage: { @@ -58,4 +61,23 @@ describe('AdqueryIdSystem', function () { expect(callbackSpy.lastCall.lastArg).to.deep.equal('testqid'); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(adqueryIdSubmodule); + }); + it('qid', function() { + const userId = { + qid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/amxIdSystem_spec.js b/test/spec/modules/amxIdSystem_spec.js index c1ae2c791d5..b509ffe608b 100644 --- a/test/spec/modules/amxIdSystem_spec.js +++ b/test/spec/modules/amxIdSystem_spec.js @@ -1,6 +1,9 @@ import { amxIdSubmodule, storage } from 'modules/amxIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const TEST_ID = '51b561e3-0d82-4aea-8487-093fffca4a3a'; const ERROR_CODES = [404, 501, 500, 403]; @@ -13,177 +16,198 @@ const config = { type: 'html5', }, }; +describe('AMX ID', () => { + describe('amxid submodule', () => { + it('should expose a "name" property containing amxId', () => { + expect(amxIdSubmodule.name).to.equal('amxId'); + }); -describe('amxid submodule', () => { - it('should expose a "name" property containing amxId', () => { - expect(amxIdSubmodule.name).to.equal('amxId'); - }); - - it('should expose a "gvlid" property containing the GVL ID 737', () => { - expect(amxIdSubmodule.gvlid).to.equal(737); + it('should expose a "gvlid" property containing the GVL ID 737', () => { + expect(amxIdSubmodule.gvlid).to.equal(737); + }); }); -}); -describe('decode', () => { - it('should respond with an object with "amxId" key containing the value', () => { - expect(amxIdSubmodule.decode(TEST_ID)).to.deep.equal({ - amxId: TEST_ID + describe('decode', () => { + it('should respond with an object with "amxId" key containing the value', () => { + expect(amxIdSubmodule.decode(TEST_ID)).to.deep.equal({ + amxId: TEST_ID + }); }); - }); - it('should respond with undefined if the value is not a string', () => { - [1, null, undefined, NaN, [], {}].forEach((value) => { - expect(amxIdSubmodule.decode(value)).to.equal(undefined); + it('should respond with undefined if the value is not a string', () => { + [1, null, undefined, NaN, [], {}].forEach((value) => { + expect(amxIdSubmodule.decode(value)).to.equal(undefined); + }); }); }); -}); -describe('validateConfig', () => { - let logErrorSpy; + describe('validateConfig', () => { + let logErrorSpy; - beforeEach(() => { - logErrorSpy = sinon.spy(utils, 'logError'); - }); - afterEach(() => { - logErrorSpy.restore(); - }); + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + afterEach(() => { + logErrorSpy.restore(); + }); - it('should allow configuration with no storage', () => { - expect( - amxIdSubmodule.getId( - { - ...config, - storage: undefined - }, - null, - null - ) - ).to.not.equal(undefined); - }); + it('should allow configuration with no storage', () => { + expect( + amxIdSubmodule.getId( + { + ...config, + storage: undefined + }, + null, + null + ) + ).to.not.equal(undefined); + }); - it('should return undefined if expires > 30', () => { - const expires = Math.floor(Math.random() * 90) + 30.01; - expect( - amxIdSubmodule.getId( - { - ...config, - storage: { - type: 'html5', - expires, + it('should return undefined if expires > 30', () => { + const expires = Math.floor(Math.random() * 90) + 30.01; + expect( + amxIdSubmodule.getId( + { + ...config, + storage: { + type: 'html5', + expires, + }, }, - }, - null, - null - ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain(expires); + null, + null + ) + ).to.equal(undefined); + + expect(logErrorSpy.calledOnce).to.be.true; + expect(logErrorSpy.lastCall.lastArg).to.contain(expires); + }); }); -}); -describe('getId', () => { - const spy = sinon.spy(); + describe('getId', () => { + const spy = sinon.spy(); - beforeEach(() => { - spy.resetHistory(); - }); + beforeEach(() => { + spy.resetHistory(); + }); - it('should call the sync endpoint and accept a valid response', () => { - storage.setDataInLocalStorage('__amuidpb', TEST_ID); + it('should call the sync endpoint and accept a valid response', () => { + storage.setDataInLocalStorage('__amuidpb', TEST_ID); - const { callback } = amxIdSubmodule.getId(config, null, null); - callback(spy); + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); - const [request] = server.requests; - expect(request.withCredentials).to.be.true - expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) + const [request] = server.requests; + expect(request.withCredentials).to.be.true + expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) - const { search } = utils.parseUrl(request.url); - expect(search.av).to.equal(amxIdSubmodule.version); - expect(search.am).to.equal(TEST_ID); - expect(request.method).to.equal('GET'); + const { search } = utils.parseUrl(request.url); + expect(search.av).to.equal(amxIdSubmodule.version); + expect(search.am).to.equal(TEST_ID); + expect(request.method).to.equal('GET'); - request.respond( - 200, - {}, - JSON.stringify({ - id: TEST_ID, - v: '1.0a', - }) - ); + request.respond( + 200, + {}, + JSON.stringify({ + id: TEST_ID, + v: '1.0a', + }) + ); - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(TEST_ID); - }); + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(TEST_ID); + }); - it('should return undefined if the server has an error status code', () => { - const { callback } = amxIdSubmodule.getId(config, null, null); - callback(spy); + it('should return undefined if the server has an error status code', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); - const [request] = server.requests; - const responseCode = - ERROR_CODES[Math.floor(Math.random() * ERROR_CODES.length)]; - request.respond(responseCode, {}, ''); + const [request] = server.requests; + const responseCode = + ERROR_CODES[Math.floor(Math.random() * ERROR_CODES.length)]; + request.respond(responseCode, {}, ''); - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(undefined); - }); + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(undefined); + }); - it('should return undefined if the response has invalid keys', () => { - const { callback } = amxIdSubmodule.getId(config, null, null); - callback(spy); - - const [request] = server.requests; - request.respond( - 200, - {}, - JSON.stringify({ - test: TEST_ID, - }) - ); - - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(undefined); - }); + it('should return undefined if the response has invalid keys', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + request.respond( + 200, + {}, + JSON.stringify({ + test: TEST_ID, + }) + ); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(undefined); + }); - it('should returned undefined if the server JSON is invalid', () => { - const { callback } = amxIdSubmodule.getId(config, null, null); - callback(spy); + it('should returned undefined if the server JSON is invalid', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); - const [request] = server.requests; - request.respond(200, {}, '{,,}'); + const [request] = server.requests; + request.respond(200, {}, '{,,}'); - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(undefined); - }); + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(undefined); + }); - it('should use the intermediate value for the sync server', () => { - const { callback } = amxIdSubmodule.getId(config, null, null); - callback(spy); - - const [request] = server.requests; - const intermediateValue = 'https://example-publisher.com/api/sync'; - - request.respond( - 200, - {}, - JSON.stringify({ - u: intermediateValue, - }) - ); - - const [, secondRequest] = server.requests; - expect(secondRequest.url).to.match(new RegExp(`^${intermediateValue}\?`)); - secondRequest.respond( - 200, - {}, - JSON.stringify({ - id: TEST_ID, - }) - ); - - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(TEST_ID); + it('should use the intermediate value for the sync server', () => { + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); + + const [request] = server.requests; + const intermediateValue = 'https://example-publisher.com/api/sync'; + + request.respond( + 200, + {}, + JSON.stringify({ + u: intermediateValue, + }) + ); + + const [, secondRequest] = server.requests; + expect(secondRequest.url).to.match(new RegExp(`^${intermediateValue}\?`)); + secondRequest.respond( + 200, + {}, + JSON.stringify({ + id: TEST_ID, + }) + ); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(TEST_ID); + }); }); -}); + describe('eid', () => { + before(() => { + attachIdSystem(amxIdSubmodule); + }); + it('amxId', () => { + const id = 'c4bcadb0-124f-4468-a91a-d3d44cf311c5' + const userId = { + amxId: id + }; + + const [eid] = createEidsArray(userId); + expect(eid).to.deep.equal({ + source: 'amxdt.net', + uids: [{ + atype: 1, + id, + }] + }); + }); + }) +}) diff --git a/test/spec/modules/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js index ddb61806006..ab00c3015d4 100644 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ b/test/spec/modules/britepoolIdSystem_spec.js @@ -1,5 +1,8 @@ import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; import * as utils from '../../../src/utils.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('BritePool Submodule', () => { const api_key = '1111'; @@ -126,4 +129,20 @@ describe('BritePool Submodule', () => { done(); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(britepoolIdSubmodule); + }); + it('britepoolId', function() { + const userId = { + britepoolid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'britepool.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + }) }); diff --git a/test/spec/modules/carodaBidAdapter_spec.js b/test/spec/modules/carodaBidAdapter_spec.js index f575e31e85d..bf4557d7a8a 100644 --- a/test/spec/modules/carodaBidAdapter_spec.js +++ b/test/spec/modules/carodaBidAdapter_spec.js @@ -197,17 +197,14 @@ describe('Caroda adapter', function () { let validBidRequests = [{ bid_id: 'bidId', params: {}, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }) + userIdAsEids: [ + { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, + { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + ] }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } })[0].data); - assert.deepEqual(request.user.eids, [ - { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, - { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } - ]); + assert.deepEqual(request.user.eids, validBidRequests[0].userIdAsEids); }); describe('user privacy', function () { diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 975271738e5..eb1f54d7cd2 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -2,6 +2,9 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from '../../mocks/xhr'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const pastDateString = new Date(0).toString() @@ -358,4 +361,20 @@ describe('CriteoId module', function () { expect(callBackSpy.calledOnce).to.be.true; })); + describe('eid', () => { + before(() => { + attachIdSystem(criteoIdSubmodule); + }); + it('criteo', function() { + const userId = { + criteoId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'criteo.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }) }); diff --git a/test/spec/modules/cpexIdSystem_spec.js b/test/spec/modules/czechAdIdSystem_spec.js similarity index 54% rename from test/spec/modules/cpexIdSystem_spec.js rename to test/spec/modules/czechAdIdSystem_spec.js index 6e004c9f8ca..19b606b9237 100644 --- a/test/spec/modules/cpexIdSystem_spec.js +++ b/test/spec/modules/czechAdIdSystem_spec.js @@ -1,4 +1,7 @@ -import { czechAdIdSubmodule, storage } from 'modules/czechAdIdSystem.js'; +import {czechAdIdSubmodule, storage} from 'modules/czechAdIdSystem.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('czechAdId module', function () { let getCookieStub; @@ -12,13 +15,13 @@ describe('czechAdId module', function () { getCookieStub.restore(); }); - const cookieTestCasesForEmpty = [undefined, null, ''] + const cookieTestCasesForEmpty = [undefined, null, '']; describe('getId()', function () { it('should return the uid when it exists in cookie', function () { getCookieStub.withArgs('czaid').returns('czechAdIdTest'); const id = czechAdIdSubmodule.getId(); - expect(id).to.be.deep.equal({ id: 'czechAdIdTest' }); + expect(id).to.be.deep.equal({id: 'czechAdIdTest'}); }); cookieTestCasesForEmpty.forEach(testCase => it('should not return the uid when it doesnt exist in cookie', function () { @@ -32,7 +35,22 @@ describe('czechAdId module', function () { it('should return the uid when it exists in cookie', function () { getCookieStub.withArgs('czaid').returns('czechAdIdTest'); const decoded = czechAdIdSubmodule.decode(); - expect(decoded).to.be.deep.equal({ czechAdId: 'czechAdIdTest' }); + expect(decoded).to.be.deep.equal({czechAdId: 'czechAdIdTest'}); + }); + }); + describe('eid', () => { + before(() => { + attachIdSystem(czechAdIdSubmodule); + }); + + it('czechAdId', () => { + const id = 'some-random-id-value'; + const userId = {czechAdId: id}; + const [eid] = createEidsArray(userId); + expect(eid).to.deep.equal({ + source: 'czechadid.cz', + uids: [{id: 'some-random-id-value', atype: 1}] + }); }); }); }); diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js index 3bd1e71d8f6..252cb2f414c 100644 --- a/test/spec/modules/deepintentDpesIdsystem_spec.js +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -1,5 +1,7 @@ import { expect } from 'chai'; import { deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; const DI_UPDATED_STORAGE = '2cf40748c4f7f60d343336e08f80dc99'; @@ -59,4 +61,20 @@ describe('Deepintent DPES System', () => { expect(deepintentDpesSubmodule.eids.deepintentId.getValue(DI_UPDATED_STORAGE)).to.be.eq(DI_UPDATED_STORAGE); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(deepintentDpesSubmodule); + }) + it('deepintentId', function() { + const userId = { + deepintentId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'deepintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + }) }); diff --git a/test/spec/modules/dianomiBidAdapter_spec.js b/test/spec/modules/dianomiBidAdapter_spec.js index 0838762d750..b1ba5f60540 100644 --- a/test/spec/modules/dianomiBidAdapter_spec.js +++ b/test/spec/modules/dianomiBidAdapter_spec.js @@ -250,23 +250,20 @@ describe('Dianomi adapter', () => { { bidId: 'bidId', params: { smartadId: 1234 }, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE', - }), + userIdAsEids: [ + { + source: 'adserver.org', + uids: [{ id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } }], + }, + { source: 'pubcid.org', uids: [{ id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 }] }, + ], }, ]; let request = JSON.parse( spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data ); - assert.deepEqual(request.user.ext.eids, [ - { - source: 'adserver.org', - uids: [{ id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } }], - }, - { source: 'pubcid.org', uids: [{ id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 }] }, - ]); + assert.deepEqual(request.user.ext.eids, validBidRequests[0].userIdAsEids); }); it('should send currency if defined', () => { diff --git a/test/spec/modules/dsaControl_spec.js b/test/spec/modules/dsaControl_spec.js index 45392d58c04..1744d1d5bab 100644 --- a/test/spec/modules/dsaControl_spec.js +++ b/test/spec/modules/dsaControl_spec.js @@ -108,6 +108,5 @@ describe('DSA transparency', () => { }) }) }) - it('should accept bids regardless of dsa when "required" any other value') }); }); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 260864dd1a2..30bfabb6f50 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -1,644 +1,7 @@ import {createEidsArray} from 'modules/userId/eids.js'; -import {expect} from 'chai'; -// Note: In unit test cases for bidders, call the createEidsArray function over userId object that is used for calling fetchBids -// this way the request will stay consistent and unit test cases will not need lots of changes. - -describe('eids array generation for known sub-modules', function() { - it('pubCommonId', function() { - const userId = { - pubcid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'pubcid.org', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('unifiedId: ext generation', function() { - const userId = { - tdid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'some-random-id-value', atype: 1, ext: { rtiPartner: 'TDID' }}] - }); - }); - - it('unifiedId: ext generation with provider', function() { - const userId = { - tdid: {'id': 'some-sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'some-sample_id', atype: 1, ext: { rtiPartner: 'TDID', provider: 'some.provider.com' }}] - }); - }); - - describe('id5Id', function() { - it('does not include an ext if not provided', function() { - const userId = { - id5id: { - uid: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'id5-sync.com', - uids: [{ id: 'some-random-id-value', atype: 1 }] - }); - }); - - it('includes ext if provided', function() { - const userId = { - id5id: { - uid: 'some-random-id-value', - ext: { - linkType: 0 - } - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'id5-sync.com', - uids: [{ - id: 'some-random-id-value', - atype: 1, - ext: { - linkType: 0 - } - }] - }); - }); - }); - - it('parrableId', function() { - const userId = { - parrableId: { - eid: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'parrable.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('merkleId (legacy) - supports single id', function() { - const userId = { - merkleId: { - id: 'some-random-id-value', keyID: 1 - } - }; - const newEids = createEidsArray(userId); - - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'merkleinc.com', - uids: [{ - id: 'some-random-id-value', - atype: 3, - ext: { keyID: 1 } - }] - }); - }); - - it('merkleId supports multiple source providers', function() { - const userId = { - merkleId: [{ - id: 'some-random-id-value', ext: { enc: 1, keyID: 16, idName: 'pamId', ssp: 'ssp1' } - }, { - id: 'another-random-id-value', - ext: { - enc: 1, - idName: 'pamId', - third: 4, - ssp: 'ssp2' - } - }] - } - - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(2); - expect(newEids[0]).to.deep.equal({ - source: 'ssp1.merkleinc.com', - uids: [{id: 'some-random-id-value', - atype: 3, - ext: { - enc: 1, - keyID: 16, - idName: 'pamId', - ssp: 'ssp1' - } - }] - }); - expect(newEids[1]).to.deep.equal({ - source: 'ssp2.merkleinc.com', - uids: [{id: 'another-random-id-value', - atype: 3, - ext: { - third: 4, - enc: 1, - idName: 'pamId', - ssp: 'ssp2' - } - }] - }); - }); - - it('identityLink', function() { - const userId = { - idl_env: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('liveIntentId; getValue call and ext', function() { - const userId = { - lipb: { - lipbid: 'some-random-id-value', - segments: ['s1', 's2'] - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 3}], - ext: {segments: ['s1', 's2']} - }); - }); - - it('fpid; getValue call', function() { - const userId = { - fpid: { - id: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'fpid.liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('bidswitch', function() { - const userId = { - bidswitch: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'bidswitch.net', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('bidswitch with ext', function() { - const userId = { - bidswitch: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'bidswitch.net', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('medianet', function() { - const userId = { - medianet: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'media.net', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('medianet with ext', function() { - const userId = { - medianet: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'media.net', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('sovrn', function() { - const userId = { - sovrn: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.sovrn.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('sovrn with ext', function() { - const userId = { - sovrn: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.sovrn.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('magnite', function() { - const userId = { - magnite: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'rubiconproject.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('magnite with ext', function() { - const userId = { - magnite: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'rubiconproject.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('index', function() { - const userId = { - index: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.indexexchange.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('index with ext', function() { - const userId = { - index: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.indexexchange.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('openx', function () { - const userId = { - openx: { 'id': 'sample_id' } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'openx.net', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('openx with ext', function () { - const userId = { - openx: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'openx.net', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('pubmatic', function() { - const userId = { - pubmatic: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'pubmatic.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('pubmatic with ext', function() { - const userId = { - pubmatic: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'pubmatic.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('liveIntentId; getValue call and NO ext', function() { - const userId = { - lipb: { - lipbid: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('britepoolId', function() { - const userId = { - britepoolid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'britepool.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('lotamePanoramaId', function () { - const userId = { - lotamePanoramaId: 'some-random-id-value', - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'crwdcntrl.net', - uids: [{ id: 'some-random-id-value', atype: 1 }], - }); - }); - - it('criteo', function() { - const userId = { - criteoId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'criteo.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('tapadId', function() { - const userId = { - tapadId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'tapad.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('deepintentId', function() { - const userId = { - deepintentId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'deepintent.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('NetId', function() { - const userId = { - netId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'netid.de', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('zeotapIdPlus', function() { - const userId = { - IDP: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'zeotap.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('hadronId', function() { - const userId = { - hadronId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'audigent.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('quantcastId', function() { - const userId = { - quantcastId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'quantcast.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('uid2', function() { - const userId = { - uid2: {'id': 'Sample_AD_Token'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'uidapi.com', - uids: [{ - id: 'Sample_AD_Token', - atype: 3 - }] - }); - }); - - it('uid2 with ext', function() { - const userId = { - uid2: {'id': 'Sample_AD_Token', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'uidapi.com', - uids: [{ - id: 'Sample_AD_Token', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('euid', function() { - const userId = { - euid: {'id': 'Sample_AD_Token'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'euid.eu', - uids: [{ - id: 'Sample_AD_Token', - atype: 3 - }] - }); - }); - - it('kpuid', function() { - const userId = { - kpuid: 'Sample_Token' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'kpuid.com', - uids: [{ - id: 'Sample_Token', - atype: 3 - }] - }); - }); - - it('tncid', function() { - const userId = { - tncid: 'TEST_TNCID' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'thenewco.it', - uids: [{ - id: 'TEST_TNCID', - atype: 3 - }] - }); - }); - - it('pubProvidedId', function() { +describe('eids array generation for known sub-modules', function () { + it('pubProvidedId', function () { const userId = { pubProvidedId: [{ source: 'example.com', @@ -673,148 +36,10 @@ describe('eids array generation for known sub-modules', function() { }] }); }); - - it('amxId', () => { - const id = 'c4bcadb0-124f-4468-a91a-d3d44cf311c5' - const userId = { - amxId: id - }; - - const [eid] = createEidsArray(userId); - expect(eid).to.deep.equal({ - source: 'amxdt.net', - uids: [{ - atype: 1, - id, - }] - }); - }); - - it('qid', function() { - const userId = { - qid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adquery.io', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('operaId', function() { - const userId = { - operaId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 't.adx.opera.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('33acrossId', function() { - const userId = { - '33acrossId': { - envelope: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: '33across.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('czechAdId', () => { - const id = 'some-random-id-value' - const userId = { czechAdId: id }; - const [eid] = createEidsArray(userId); - expect(eid).to.deep.equal({ - source: 'czechadid.cz', - uids: [{ id: 'some-random-id-value', atype: 1 }] - }); - }); - - describe('ftrackId', () => { - it('should return the correct EID schema', () => { - // This is the schema returned from the ftrack decode() method - expect(createEidsArray({ - ftrackId: { - uid: 'test-device-id', - ext: { - DeviceID: 'test-device-id', - SingleDeviceID: 'test-single-device-id', - HHID: 'test-household-id' - } - }, - foo: { - bar: 'baz' - }, - lorem: { - ipsum: '' - } - })).to.deep.equal([{ - source: 'flashtalking.com', - uids: [{ - atype: 1, - id: 'test-device-id', - ext: { - DeviceID: 'test-device-id', - SingleDeviceID: 'test-single-device-id', - HHID: 'test-household-id' - } - }] - }]); - }); - }); - - describe('imuid', function() { - it('should return the correct EID schema with imuid', function() { - const userId = { - imuid: 'testimuid' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'intimatemerger.com', - uids: [{ - id: 'testimuid', - atype: 1 - }] - }); - }); - - it('should return the correct EID schema with imppid', function() { - const userId = { - imppid: 'imppid-value-imppid-value-imppid-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'ppid.intimatemerger.com', - uids: [{ - id: 'imppid-value-imppid-value-imppid-value', - atype: 1 - }] - }); - }); - }); }); describe('Negative case', function () { - it('eids array generation for UN-known sub-module', function() { + it('eids array generation for UN-known sub-module', function () { // UnknownCommonId const userId = { unknowncid: 'some-random-id-value' @@ -823,7 +48,7 @@ describe('Negative case', function () { expect(newEids.length).to.equal(0); }); - it('eids array generation for known sub-module with non-string value', function() { + it('eids array generation for known sub-module with non-string value', function () { // pubCommonId let userId = { pubcid: undefined diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index 9ad2b69e89c..f01f2a15f03 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -1,4 +1,4 @@ -import {coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; +import {attachIdSystem, coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import {euidIdSubmodule} from 'modules/euidIdSystem.js'; import 'modules/consentManagement.js'; @@ -8,6 +8,7 @@ import {apiHelpers, cookieHelpers, runAuction, setGdprApplies} from './uid2IdSys import {hook} from 'src/hook.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; import {server} from 'test/mocks/xhr'; +import {createEidsArray} from '../../../modules/userId/eids.js'; let expect = require('chai').expect; @@ -161,4 +162,24 @@ describe('EUID module', function() { expectOptout(bid, optoutToken); }); } + + describe('eid', () => { + before(() => { + attachIdSystem(euidIdSubmodule); + }); + it('euid', function() { + const userId = { + euid: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'euid.eu', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); + }) }); diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index ecd610a12fb..12e18ed5354 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -3,10 +3,10 @@ import * as utils from 'src/utils.js'; import { uspDataHandler } from 'src/adapterManager.js'; import { loadExternalScript } from 'src/adloader.js'; import { getGlobal } from 'src/prebidGlobal.js'; -import { init, setSubmoduleRegistry } from 'modules/userId/index.js'; +import {attachIdSystem, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; -let expect = require('chai').expect; +import 'src/prebid.js'; let server; @@ -380,10 +380,10 @@ describe('FTRACK ID System', () => { } }); - getGlobal().getUserIdsAsync().then(ids => { - expect(ids).to.deep.equal({ + return getGlobal().getUserIdsAsync().then(ids => { + expect(ids.ftrackId).to.deep.equal({ uid: 'device_test_id', - ftrackId: { + ext: { HHID: 'household_test_id', DeviceID: 'device_test_id', SingleDeviceID: 'single_device_test_id' @@ -558,5 +558,40 @@ describe('FTRACK ID System', () => { }); }); }); + }); + describe('eid', () => { + before(() => { + attachIdSystem(ftrackIdSubmodule); + }); + it('should return the correct EID schema', () => { + // This is the schema returned from the ftrack decode() method + expect(createEidsArray({ + ftrackId: { + uid: 'test-device-id', + ext: { + DeviceID: 'test-device-id', + SingleDeviceID: 'test-single-device-id', + HHID: 'test-household-id' + } + }, + foo: { + bar: 'baz' + }, + lorem: { + ipsum: '' + } + })).to.deep.equal([{ + source: 'flashtalking.com', + uids: [{ + atype: 1, + id: 'test-device-id', + ext: { + DeviceID: 'test-device-id', + SingleDeviceID: 'test-single-device-id', + HHID: 'test-household-id' + } + }] + }]); + }); }) }); diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index 85c8cc11c9e..899dc640dc1 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -1,6 +1,9 @@ import { hadronIdSubmodule, storage } from 'modules/hadronIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('HadronIdSystem', function () { describe('getId', function() { @@ -52,4 +55,24 @@ describe('HadronIdSystem', function () { expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); + + describe('eids', () => { + before(() => { + attachIdSystem(hadronIdSubmodule); + }); + it('hadronId', function() { + const userId = { + hadronId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'audigent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 1da862cc007..707560f2f4e 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,5 +1,6 @@ import * as id5System from '../../../modules/id5IdSystem.js'; import { + attachIdSystem, coreStorage, getConsentHash, init, @@ -17,6 +18,7 @@ import {mockGdprConsent} from '../../helpers/consentData.js'; import {server} from '../../mocks/xhr.js'; import {expect} from 'chai'; import {GreedyPromise} from '../../../src/utils/promise.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const IdFetchFlow = id5System.IdFetchFlow; @@ -1123,4 +1125,45 @@ describe('ID5 ID System', function () { }); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(id5System); + }); + it('does not include an ext if not provided', function() { + const userId = { + id5id: { + uid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{ id: 'some-random-id-value', atype: 1 }] + }); + }); + + it('includes ext if provided', function() { + const userId = { + id5id: { + uid: 'some-random-id-value', + ext: { + linkType: 0 + } + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 0 + } + }] + }); + }); + }) }); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 66d5a3edd00..5efe9794f92 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -4,6 +4,9 @@ import {server} from 'test/mocks/xhr.js'; import {getCoreStorageManager} from '../../../src/storageManager.js'; import {stub} from 'sinon'; import { gppDataHandler } from '../../../src/adapterManager.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const storage = getCoreStorageManager(); @@ -238,4 +241,21 @@ describe('IdentityLinkId tests', function () { expect(envelopeValueFromStorage).to.be.a('string'); expect(envelopeValueFromStorage).to.be.eq(testEnvelopeValue); }) + + describe('eid', () => { + before(() => { + attachIdSystem(identityLinkSubmodule); + }); + it('identityLink', function() { + const userId = { + idl_env: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + }) }); diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js index 56e1c709c8b..0d30808cc1f 100644 --- a/test/spec/modules/idxIdSystem_spec.js +++ b/test/spec/modules/idxIdSystem_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; +import 'src/prebid.js'; const IDX_COOKIE_NAME = '_idx'; const IDX_DUMMY_VALUE = 'idx value for testing'; diff --git a/test/spec/modules/imuIdSystem_spec.js b/test/spec/modules/imuIdSystem_spec.js index 3650302a2ed..1d6f79786a0 100644 --- a/test/spec/modules/imuIdSystem_spec.js +++ b/test/spec/modules/imuIdSystem_spec.js @@ -13,6 +13,9 @@ import { } from 'modules/imuIdSystem.js'; import * as utils from 'src/utils.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('imuId module', function () { // let setLocalStorageStub; @@ -181,4 +184,38 @@ describe('imuId module', function () { expect(res.success('error response')).to.equal(undefined); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(imuIdSubmodule); + }); + it('should return the correct EID schema with imuid', function() { + const userId = { + imuid: 'testimuid' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'intimatemerger.com', + uids: [{ + id: 'testimuid', + atype: 1 + }] + }); + }); + + it('should return the correct EID schema with imppid', function() { + const userId = { + imppid: 'imppid-value-imppid-value-imppid-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'ppid.intimatemerger.com', + uids: [{ + id: 'imppid-value-imppid-value-imppid-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index ef174af416b..4d013ba0a8e 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import {clearAllCookies} from '../../helpers/cookies.js'; const partner = 10; const pai = '11'; @@ -44,6 +45,8 @@ describe('IntentIQ tests', function () { afterEach(function () { logErrorStub.restore(); + clearAllCookies(); + localStorage.clear(); }); it('should log an error if no configParams were passed when getId', function () { diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index ba2d8f5255a..df789a7cca0 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import { config } from 'src/config.js'; -import {spec, resetInvibes, stubDomainOptions, readGdprConsent} from 'modules/invibesBidAdapter.js'; +import {spec, resetInvibes, stubDomainOptions, readGdprConsent, storage} from 'modules/invibesBidAdapter.js'; describe('invibesBidAdapter:', function () { const BIDDER_CODE = 'invibes'; @@ -187,6 +187,8 @@ describe('invibesBidAdapter:', function () { }; } + let sandbox; + beforeEach(function () { resetInvibes(); $$PREBID_GLOBAL$$.bidderSettings = { @@ -196,11 +198,13 @@ describe('invibesBidAdapter:', function () { }; document.cookie = ''; this.cStub1 = sinon.stub(console, 'info'); + sandbox = sinon.sandbox.create(); }); afterEach(function () { $$PREBID_GLOBAL$$.bidderSettings = {}; this.cStub1.restore(); + sandbox.restore(); }); describe('isBidRequestValid:', function () { @@ -528,6 +532,8 @@ describe('invibesBidAdapter:', function () { }); it('sends undefined lid when no cookie', function () { + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'getCookie').returns(null); let request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); expect(request.data.lId).to.be.undefined; }); @@ -563,7 +569,7 @@ describe('invibesBidAdapter:', function () { it('does not send handIid when it doesnt exist in cookie', function () { top.window.invibes.optIn = 1; top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; - global.document.cookie = ''; + sandbox.stub(storage, 'getCookie').returns(null) let bidderRequest = { gdprConsent: { vendorData: { diff --git a/test/spec/modules/kinessoIdSystem_spec.js b/test/spec/modules/kinessoIdSystem_spec.js new file mode 100644 index 00000000000..e5d9721737d --- /dev/null +++ b/test/spec/modules/kinessoIdSystem_spec.js @@ -0,0 +1,26 @@ +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {kinessoIdSubmodule} from '../../../modules/kinessoIdSystem.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; + +describe('kinesso ID', () => { + describe('eid', () => { + before(() => { + attachIdSystem(kinessoIdSubmodule); + }); + it('kpuid', function() { + const userId = { + kpuid: 'Sample_Token' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'kpuid.com', + uids: [{ + id: 'Sample_Token', + atype: 3 + }] + }); + }); + }); +}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index e5caacd1547..dae19d0f578 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -3,6 +3,8 @@ import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; import * as refererDetection from '../../../src/refererDetection.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') @@ -508,4 +510,280 @@ describe('LiveIntentId', function() { fpid: { id: expectedValue } }); }); + + describe('eid', () => { + before(() => { + attachIdSystem(liveIntentIdSubmodule); + }); + it('liveIntentId; getValue call and ext', function() { + const userId = { + lipb: { + lipbid: 'some-random-id-value', + segments: ['s1', 's2'] + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'some-random-id-value', atype: 3}], + ext: {segments: ['s1', 's2']} + }); + }); + it('fpid; getValue call', function() { + const userId = { + fpid: { + id: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'fpid.liveintent.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + it('bidswitch', function() { + const userId = { + bidswitch: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('bidswitch with ext', function() { + const userId = { + bidswitch: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('medianet', function() { + const userId = { + medianet: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('medianet with ext', function() { + const userId = { + medianet: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('sovrn', function() { + const userId = { + sovrn: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sovrn.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('sovrn with ext', function() { + const userId = { + sovrn: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sovrn.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('magnite', function() { + const userId = { + magnite: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'rubiconproject.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('magnite with ext', function() { + const userId = { + magnite: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'rubiconproject.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('index', function() { + const userId = { + index: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.indexexchange.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('index with ext', function() { + const userId = { + index: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.indexexchange.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('openx', function () { + const userId = { + openx: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'openx.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('openx with ext', function () { + const userId = { + openx: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'openx.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('pubmatic', function() { + const userId = { + pubmatic: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubmatic.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('pubmatic with ext', function() { + const userId = { + pubmatic: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubmatic.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('liveIntentId; getValue call and NO ext', function() { + const userId = { + lipb: { + lipbid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + }) }) diff --git a/test/spec/modules/lmpIdSystem_spec.js b/test/spec/modules/lmpIdSystem_spec.js index 37c7351f143..c17df3a7ef3 100644 --- a/test/spec/modules/lmpIdSystem_spec.js +++ b/test/spec/modules/lmpIdSystem_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js'; import { mockGdprConsent } from '../../helpers/consentData.js'; +import 'src/prebid.js'; function getConfigMock() { return { diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index ea538db08e1..fbd4ba7c000 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -6,6 +6,8 @@ import { uspDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import sinon from 'sinon'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const responseHeader = { 'Content-Type': 'application/json' }; @@ -21,7 +23,6 @@ describe('LotameId', function() { let requestHost; const nowTimestamp = new Date().getTime(); - beforeEach(function () { logErrorStub = sinon.stub(utils, 'logError'); getCookieStub = sinon.stub(storage, 'getCookie'); @@ -958,4 +959,20 @@ describe('LotameId', function() { }); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(lotamePanoramaIdSubmodule); + }); + it('lotamePanoramaId', function () { + const userId = { + lotamePanoramaId: 'some-random-id-value', + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'crwdcntrl.net', + uids: [{ id: 'some-random-id-value', atype: 1 }], + }); + }); + }) }); diff --git a/test/spec/modules/merkleIdSystem_spec.js b/test/spec/modules/merkleIdSystem_spec.js index 82c17336d20..b12bb365e5b 100644 --- a/test/spec/modules/merkleIdSystem_spec.js +++ b/test/spec/modules/merkleIdSystem_spec.js @@ -3,6 +3,8 @@ import * as utils from 'src/utils.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import sinon from 'sinon'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; let expect = require('chai').expect; @@ -248,4 +250,71 @@ describe('Merkle System', function () { expect(callbackSpy.calledOnce).to.be.true; }); }); + + describe('eid', () => { + before(() => { + attachIdSystem(merkleIdSubmodule); + }); + it('merkleId (legacy) - supports single id', function() { + const userId = { + merkleId: { + id: 'some-random-id-value', keyID: 1 + } + }; + const newEids = createEidsArray(userId); + + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'merkleinc.com', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { keyID: 1 } + }] + }); + }); + + it('merkleId supports multiple source providers', function() { + const userId = { + merkleId: [{ + id: 'some-random-id-value', ext: { enc: 1, keyID: 16, idName: 'pamId', ssp: 'ssp1' } + }, { + id: 'another-random-id-value', + ext: { + enc: 1, + idName: 'pamId', + third: 4, + ssp: 'ssp2' + } + }] + } + + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(2); + expect(newEids[0]).to.deep.equal({ + source: 'ssp1.merkleinc.com', + uids: [{id: 'some-random-id-value', + atype: 3, + ext: { + enc: 1, + keyID: 16, + idName: 'pamId', + ssp: 'ssp1' + } + }] + }); + expect(newEids[1]).to.deep.equal({ + source: 'ssp2.merkleinc.com', + uids: [{id: 'another-random-id-value', + atype: 3, + ext: { + third: 4, + enc: 1, + idName: 'pamId', + ssp: 'ssp2' + } + }] + }); + }); + }) }); diff --git a/test/spec/modules/netIdSystem_spec.js b/test/spec/modules/netIdSystem_spec.js new file mode 100644 index 00000000000..bbf59c39f32 --- /dev/null +++ b/test/spec/modules/netIdSystem_spec.js @@ -0,0 +1,23 @@ +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {netIdSubmodule} from '../../../modules/netIdSystem.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; + +describe('Net ID', () => { + describe('eid', () => { + before(() => { + attachIdSystem(netIdSubmodule); + }); + it('NetId', function () { + const userId = { + netId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'netid.de', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }); +}); diff --git a/test/spec/modules/operaadsIdSystem_spec.js b/test/spec/modules/operaadsIdSystem_spec.js index d81f643d62f..b6acb942331 100644 --- a/test/spec/modules/operaadsIdSystem_spec.js +++ b/test/spec/modules/operaadsIdSystem_spec.js @@ -1,53 +1,76 @@ import { operaIdSubmodule } from 'modules/operaadsIdSystem' import * as ajaxLib from 'src/ajax.js' +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const TEST_ID = 'opera-test-id'; const operaIdRemoteResponse = { uid: TEST_ID }; - -describe('operaId submodule properties', () => { - it('should expose a "name" property equal to "operaId"', () => { - expect(operaIdSubmodule.name).to.equal('operaId'); +describe('operaads ID', () => { + describe('operaId submodule properties', () => { + it('should expose a "name" property equal to "operaId"', () => { + expect(operaIdSubmodule.name).to.equal('operaId'); + }); }); -}); -function fakeRequest(fn) { - const ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { - return (url, cbObj) => { - cbObj.success(JSON.stringify(operaIdRemoteResponse)); - } - }); - fn(); - ajaxBuilderStub.restore(); -} + function fakeRequest(fn) { + const ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success(JSON.stringify(operaIdRemoteResponse)); + } + }); + fn(); + ajaxBuilderStub.restore(); + } -describe('operaId submodule getId', function() { - it('request to the fake server to correctly extract test ID', function() { - fakeRequest(() => { - const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: { pid: 'pub123' } }); - moduleIdCallbackResponse.callback((id) => { - expect(id).to.equal(operaIdRemoteResponse.operaId); + describe('operaId submodule getId', function() { + it('request to the fake server to correctly extract test ID', function() { + fakeRequest(() => { + const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: { pid: 'pub123' } }); + moduleIdCallbackResponse.callback((id) => { + expect(id).to.equal(operaIdRemoteResponse.operaId); + }); }); }); - }); - it('request to the fake server without publiser ID', function() { - fakeRequest(() => { - const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: {} }); - expect(moduleIdCallbackResponse).to.equal(undefined); + it('request to the fake server without publiser ID', function() { + fakeRequest(() => { + const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: {} }); + expect(moduleIdCallbackResponse).to.equal(undefined); + }); }); }); -}); -describe('operaId submodule decode', function() { - it('should respond with an object containing "operaId" as key with the value', () => { - expect(operaIdSubmodule.decode(TEST_ID)).to.deep.equal({ - operaId: TEST_ID + describe('operaId submodule decode', function() { + it('should respond with an object containing "operaId" as key with the value', () => { + expect(operaIdSubmodule.decode(TEST_ID)).to.deep.equal({ + operaId: TEST_ID + }); }); - }); - it('should respond with undefined if the value is not a string or an empty string', () => { - [1, 2.0, null, undefined, NaN, [], {}].forEach((value) => { - expect(operaIdSubmodule.decode(value)).to.equal(undefined); + it('should respond with undefined if the value is not a string or an empty string', () => { + [1, 2.0, null, undefined, NaN, [], {}].forEach((value) => { + expect(operaIdSubmodule.decode(value)).to.equal(undefined); + }); }); }); -}); + describe('eid', () => { + before(() => { + attachIdSystem(operaIdSubmodule); + }); + it('operaId', function() { + const userId = { + operaId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 't.adx.opera.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) +}) diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 55287e0bfec..6886fa827c0 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -5,10 +5,13 @@ import * as utils from 'src/utils.js'; import { newStorageManager } from 'src/storageManager.js'; import { getRefererInfo } from 'src/refererDetection.js'; import { uspDataHandler } from 'src/adapterManager.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import {attachIdSystem, init, requestBidsHook, setSubmoduleRegistry} from 'modules/userId/index.js'; import { parrableIdSubmodule } from 'modules/parrableIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import 'src/prebid.js'; +import {merkleIdSubmodule} from '../../../modules/merkleIdSystem.js'; const storage = newStorageManager(); @@ -759,4 +762,23 @@ describe('Parrable ID System', function() { }); }); }); + + describe('eid', () => { + before(() => { + attachIdSystem(merkleIdSubmodule); + }) + it('parrableId', function() { + const userId = { + parrableId: { + eid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'parrable.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }) }); diff --git a/test/spec/modules/quantcastIdSystem_spec.js b/test/spec/modules/quantcastIdSystem_spec.js index e9d44dd6124..157c00e7567 100644 --- a/test/spec/modules/quantcastIdSystem_spec.js +++ b/test/spec/modules/quantcastIdSystem_spec.js @@ -1,6 +1,9 @@ import { quantcastIdSubmodule, storage, firePixel, hasCCPAConsent, hasGDPRConsent, checkTCFv2 } from 'modules/quantcastIdSystem.js'; import * as utils from 'src/utils.js'; import {coppaDataHandler} from 'src/adapterManager'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('QuantcastId module', function () { beforeEach(function() { @@ -380,4 +383,23 @@ describe('Quantcast GDPR consent check', function() { } })).to.equal(false); }); + describe('eids', () => { + before(() => { + attachIdSystem(quantcastIdSubmodule); + }); + it('quantcastId', function() { + const userId = { + quantcastId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'quantcast.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index fcfbe5f7c3f..359cbeb4651 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -3,6 +3,8 @@ import {coppaDataHandler} from 'src/adapterManager'; import sinon from 'sinon'; import * as utils from 'src/utils.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; let expect = require('chai').expect; @@ -91,4 +93,20 @@ describe('SharedId System', function () { expect(result).to.be.undefined; }); }); + describe('eid', () => { + before(() => { + attachIdSystem(sharedIdSystemSubmodule); + }); + it('pubCommonId', function() { + const userId = { + pubcid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }) }); diff --git a/test/spec/modules/tapadIdSystem_spec.js b/test/spec/modules/tapadIdSystem_spec.js index bc31f1d37ba..e2696934ad9 100644 --- a/test/spec/modules/tapadIdSystem_spec.js +++ b/test/spec/modules/tapadIdSystem_spec.js @@ -2,6 +2,9 @@ import { tapadIdSubmodule, graphUrl } from 'modules/tapadIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; +import {attachIdSystem} from '../../../modules/userId/index.js'; describe('TapadIdSystem', function () { describe('getId', function() { @@ -62,4 +65,20 @@ describe('TapadIdSystem', function () { logMessageSpy.restore(); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(tapadIdSubmodule); + }); + it('tapadId', function() { + const userId = { + tapadId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'tapad.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }) }) diff --git a/test/spec/modules/tncIdSystem_spec.js b/test/spec/modules/tncIdSystem_spec.js index 57c5fa63645..56b97ff0561 100644 --- a/test/spec/modules/tncIdSystem_spec.js +++ b/test/spec/modules/tncIdSystem_spec.js @@ -1,4 +1,7 @@ import { tncidSubModule } from 'modules/tncIdSystem'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const consentData = { gdprApplies: true, @@ -106,4 +109,23 @@ describe('TNCID tests', function () { }) }); }); + describe('eid', () => { + before(() => { + attachIdSystem(tncidSubModule); + }); + it('tncid', function() { + const userId = { + tncid: 'TEST_TNCID' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'thenewco.it', + uids: [{ + id: 'TEST_TNCID', + atype: 3 + }] + }); + }); + }); }); diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 6e5011164cb..357dfdd9bea 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -1,6 +1,4 @@ -/* eslint-disable no-console */ - -import {coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; +import {attachIdSystem, coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import { uid2IdSubmodule } from 'modules/uid2IdSystem.js'; @@ -12,6 +10,7 @@ import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdS import {hook} from 'src/hook.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; import {server} from 'test/mocks/xhr'; +import {createEidsArray} from '../../../modules/userId/eids.js'; let expect = require('chai').expect; @@ -95,6 +94,7 @@ describe(`UID2 module`, function () { timerSpy = configureTimerInterceptors(debugOutput); hook.ready(); uninstallGdprEnforcement(); + attachIdSystem(uid2IdSubmodule); suiteSandbox = sinon.sandbox.create(); // I'm unable to find an authoritative source, but apparently subtle isn't available in some test stacks for security reasons. @@ -233,7 +233,6 @@ describe(`UID2 module`, function () { const bid = await runAuction(); - console.log('Storage', coreStorage.getDataFromLocalStorage(moduleCookieName)); init(config); setSubmoduleRegistry([uid2IdSubmodule]); config.setConfig(makePrebidConfig(legacyConfigParams)); @@ -249,7 +248,6 @@ describe(`UID2 module`, function () { name: 'Token provided in config call', setConfig: (token, extraConfig = {}) => { const gen = makePrebidConfig({uid2Token: token}, extraConfig); - console.log('GENERATED CONFIG', gen.userSync.userIds[0].params); return config.setConfig(gen); }, }, @@ -640,5 +638,39 @@ describe(`UID2 module`, function () { const bid = await runAuction(); expectNoIdentity(bid); }) + }); + describe('eid', () => { + it('uid2', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); + + it('uid2 with ext', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); }) }); diff --git a/test/spec/modules/unifiedIdSystem_spec.js b/test/spec/modules/unifiedIdSystem_spec.js new file mode 100644 index 00000000000..9e6ce4e127b --- /dev/null +++ b/test/spec/modules/unifiedIdSystem_spec.js @@ -0,0 +1,46 @@ +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {unifiedIdSubmodule} from '../../../modules/unifiedIdSystem.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; +import {server} from 'test/mocks/xhr.js'; + +describe('Unified ID', () => { + describe('getId', () => { + it('should use provided URL', () => { + unifiedIdSubmodule.getId({params: {url: 'https://given-url/'}}).callback(); + expect(server.requests[0].url).to.eql('https://given-url/'); + }); + it('should use partner URL', () => { + unifiedIdSubmodule.getId({params: {partner: 'rubicon'}}).callback(); + expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); + }); + }); + describe('eid', () => { + before(() => { + attachIdSystem(unifiedIdSubmodule); + }); + it('unifiedId: ext generation', function () { + const userId = { + tdid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'some-random-id-value', atype: 1, ext: {rtiPartner: 'TDID'}}] + }); + }); + + it('unifiedId: ext generation with provider', function () { + const userId = { + tdid: {'id': 'some-sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'some-sample_id', atype: 1, ext: {rtiPartner: 'TDID', provider: 'some.provider.com'}}] + }); + }); + }); +}); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 0f7e9cec6ce..a5684fa5c8f 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1,13 +1,14 @@ import { attachIdSystem, auctionDelay, - coreStorage, dep, - findRootDomain, getConsentHash, + coreStorage, + dep, + findRootDomain, + getConsentHash, init, PBJS_USER_ID_OPTOUT_NAME, requestBidsHook, requestDataDeletion, - setStoredConsentData, setStoredValue, setSubmoduleRegistry, syncDelay, @@ -18,38 +19,12 @@ import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {getPrebidInternal} from 'src/utils.js'; import * as events from 'src/events.js'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {getGlobal} from 'src/prebidGlobal.js'; -import {resetConsentData, } from 'modules/consentManagement.js'; -import {server} from 'test/mocks/xhr.js'; -import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; -import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; -import {id5IdSubmodule} from 'modules/id5IdSystem.js'; -import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; -import {dmdIdSubmodule} from 'modules/dmdIdSystem.js'; -import { - liveIntentIdSubmodule, - setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent -} from 'modules/liveIntentIdSystem.js'; -import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; -import {netIdSubmodule} from 'modules/netIdSystem.js'; -import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; -import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; +import {resetConsentData} from 'modules/consentManagement.js'; +import {setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from 'modules/liveIntentIdSystem.js'; import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; -import {hadronIdSubmodule} from 'modules/hadronIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; -import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; -import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; -import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; -import {tncidSubModule} from 'modules/tncIdSystem.js'; -import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; -import {euidIdSubmodule} from 'modules/euidIdSystem.js'; -import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; -import {deepintentDpesSubmodule} from 'modules/deepintentDpesIdSystem.js'; -import {amxIdSubmodule} from '../../../modules/amxIdSystem.js'; -import {kinessoIdSubmodule} from 'modules/kinessoIdSystem.js'; -import {adqueryIdSubmodule} from 'modules/adqueryIdSystem.js'; -import {imuIdSubmodule} from 'modules/imuIdSystem.js'; import * as mockGpt from '../integration/faker/googletag.js'; import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; @@ -361,7 +336,7 @@ describe('User ID', function () { customConfig = addConfig(customConfig, 'params', {extend: true}); init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(customConfig); return expectImmediateBidHook((config) => { @@ -387,7 +362,7 @@ describe('User ID', function () { customConfig = addConfig(customConfig, 'params', {create: false}); init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(customConfig); return expectImmediateBidHook((config) => { @@ -778,7 +753,7 @@ describe('User ID', function () { it('should set googletag ppid correctly', function () { let adUnits = [getAdUnitMock()]; init(config); - setSubmoduleRegistry([amxIdSubmodule, sharedIdSystemSubmodule, identityLinkSubmodule, imuIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); // before ppid should not be set expect(window.googletag._ppid).to.equal(undefined); @@ -787,10 +762,7 @@ describe('User ID', function () { userSync: { ppid: 'pubcid.org', userIds: [ - { name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }, { name: 'pubCommonId', value: {'pubcid': 'pubCommon-id-value-pubCommon-id-value'} }, - { name: 'identityLink', value: {'idl_env': 'identityLink-id-value-identityLink-id-value'} }, - { name: 'imuid', value: {'imppid': 'imppid-value-imppid-value-imppid-value'} }, ] } }); @@ -942,29 +914,6 @@ describe('User ID', function () { }); }); - it('should set googletag ppid correctly for imuIdSubmodule', function () { - let adUnits = [getAdUnitMock()]; - init(config); - setSubmoduleRegistry([imuIdSubmodule]); - - // before ppid should not be set - expect(window.googletag._ppid).to.equal(undefined); - - config.setConfig({ - userSync: { - ppid: 'ppid.intimatemerger.com', - userIds: [ - { name: 'imuid', value: {'imppid': 'imppid-value-imppid-value-imppid-value'} }, - ] - } - }); - - return expectImmediateBidHook(() => {}, {adUnits}).then(() => { - // ppid should have been set without dashes and stuff - expect(window.googletag._ppid).to.equal('imppidvalueimppidvalueimppidvalue'); - }); - }); - it('should log a warning if PPID too big or small', function () { let adUnits = [getAdUnitMock()]; @@ -1396,7 +1345,7 @@ describe('User ID', function () { it('handles config with no usersync object', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' expect(typeof utils.logInfo.args[0]).to.equal('undefined'); @@ -1404,14 +1353,14 @@ describe('User ID', function () { it('handles config with empty usersync object', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { userIds: [{}] @@ -1422,7 +1371,7 @@ describe('User ID', function () { it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { userIds: [{ @@ -1439,19 +1388,19 @@ describe('User ID', function () { it('config with 1 configurations should create 1 submodules', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); - config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); + setSubmoduleRegistry([sharedIdSystemSubmodule, pubProvidedIdSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubCommonId', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); it('handles config with name in different case', function () { init(config); - setSubmoduleRegistry([criteoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { userIds: [{ - name: 'Criteo' + name: 'SharedId' }] } }); @@ -1459,88 +1408,32 @@ describe('User ID', function () { expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 23 configurations should result in 23 submodules add', function () { + it('config with 2 configurations should result in 2 submodules add', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule, tncidSubModule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, pubProvidedIdSubmodule]); config.setConfig({ userSync: { syncDelay: 0, userIds: [{ - name: 'pubProvidedId' - }, { name: 'pubCommonId', value: {'pubcid': '11111'} }, { - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} - }, { - name: 'id5Id', - storage: {name: 'id5id', type: 'cookie'} - }, { - name: 'identityLink', - storage: {name: 'idl_env', type: 'cookie'} - }, { - name: 'liveIntentId', - storage: {name: '_li_pbid', type: 'cookie'} - }, { - name: 'britepoolId', - value: {'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'} - }, { - name: 'netId', - storage: {name: 'netId', type: 'cookie'} - }, { - name: 'intentIqId', - storage: {name: 'intentIqId', type: 'cookie'} - }, { - name: 'hadronId', - storage: {name: 'hadronId', type: 'html5'} - }, { - name: 'zeotapIdPlus' - }, { - name: 'criteo' - }, { - name: 'mwOpenLinkId' - }, { - name: 'tapadId', - storage: {name: 'tapad_id', type: 'cookie'} - }, { - name: 'uid2' - }, { - name: 'euid' - }, { - name: 'admixerId', - storage: {name: 'admixerId', type: 'cookie'} - }, { - name: 'deepintentId', - storage: {name: 'deepintentId', type: 'cookie'} - }, { - name: 'dmdId', - storage: {name: 'dmdId', type: 'cookie'} - }, { - name: 'amxId', - storage: {name: 'amxId', type: 'html5'} - }, { - name: 'kpuid', - storage: {name: 'kpuid', type: 'cookie'} - }, { - name: 'qid', - storage: {name: 'qid', type: 'html5'} - }, { - name: 'tncId' + name: 'pubProvidedId', + storage: {name: 'pubProvidedId', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 23 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 2 submodules'); }); it('config syncDelay updates module correctly', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { syncDelay: 99, userIds: [{ - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} + name: 'pubCommonId', + storage: {name: 'pubCommonId', type: 'cookie'} }] } }); @@ -1549,13 +1442,13 @@ describe('User ID', function () { it('config auctionDelay updates module correctly', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { auctionDelay: 100, userIds: [{ - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} + name: 'pubCommonId', + storage: {name: 'pubCommonId', type: 'cookie'} }] } }); @@ -1564,13 +1457,13 @@ describe('User ID', function () { it('config auctionDelay defaults to 0 if not a number', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { auctionDelay: '', userIds: [{ - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} + name: 'pubCommonId', + storage: {name: 'pubCommonId', type: 'cookie'} }] } }); @@ -1936,256 +1829,6 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook from UnifiedId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); - localStorage.setItem('unifiedid_alt_exp', ''); - - init(config); - setSubmoduleRegistry([unifiedIdSubmodule]); - config.setConfig(getConfigMock(['unifiedId', 'unifiedid_alt', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('testunifiedid_alt'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'testunifiedid_alt', atype: 1, ext: {rtiPartner: 'TDID'}}] - }); - }); - }); - localStorage.removeItem('unifiedid_alt'); - localStorage.removeItem('unifiedid_alt_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from amxId html5', (done) => { - // simulate existing localStorage values - localStorage.setItem('amxId', 'test_amxid_id'); - localStorage.setItem('amxId_exp', ''); - - init(config); - setSubmoduleRegistry([amxIdSubmodule]); - config.setConfig(getConfigMock(['amxId', 'amxId', 'html5'])); - - requestBidsHook(() => { - adUnits.forEach((adUnit) => { - adUnit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('userId.amxId'); - expect(bid.userId.amxId).to.equal('test_amxid_id'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'amxdt.net', - uids: [{ - id: 'test_amxid_id', - atype: 1, - }] - }); - }); - }); - - // clear LS - localStorage.removeItem('amxId'); - localStorage.removeItem('amxId_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from identityLink html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); - localStorage.setItem('idl_env_exp', ''); - - init(config); - setSubmoduleRegistry([identityLinkSubmodule]); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] - }); - }); - }); - localStorage.removeItem('idl_env'); - localStorage.removeItem('idl_env_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from identityLink cookie', function (done) { - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([identityLinkSubmodule]); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] - }); - }); - }); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from criteoIdModule cookie', function (done) { - coreStorage.setCookie('storage_bidid', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([criteoIdSubmodule]); - config.setConfig(getConfigMock(['criteo', 'storage_bidid', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.criteoId'); - expect(bid.userId.criteoId).to.equal('test_bidid'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'criteo.com', - uids: [{id: 'test_bidid', atype: 1}] - }); - }); - }); - coreStorage.setCookie('storage_bidid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from tapadIdModule cookie', function (done) { - coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([tapadIdSubmodule]); - config.setConfig(getConfigMock(['tapadId', 'tapad_id', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.tapadId'); - expect(bid.userId.tapadId).to.equal('test-tapad-id'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'tapad.com', - uids: [{id: 'test-tapad-id', atype: 1}] - }); - }); - }) - coreStorage.setCookie('tapad_id', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from liveIntentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); - localStorage.setItem('_li_pbid_exp', ''); - - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 3}] - }); - }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from Kinesso cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([kinessoIdSubmodule]); - config.setConfig(getConfigMock(['kpuid', 'kpuid', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.kpuid'); - expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'kpuid.com', - uids: [{id: 'KINESSO_ID', atype: 3}] - }); - }); - }); - coreStorage.setCookie('kpuid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from Kinesso html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('kpuid', 'KINESSO_ID'); - localStorage.setItem('kpuid_exp', ''); - - init(config); - setSubmoduleRegistry([kinessoIdSubmodule]); - config.setConfig(getConfigMock(['kpuid', 'kpuid', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.kpuid'); - expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'kpuid.com', - uids: [{id: 'KINESSO_ID', atype: 3}] - }); - }); - }); - localStorage.removeItem('kpuid'); - localStorage.removeItem('kpuid_exp', ''); - done(); - }, {adUnits}); - }); - - it('test hook from liveIntentId cookie', function (done) { - coreStorage.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier'}), (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-cookie-identifier', atype: 3}] - }); - }); - }); - coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('eidPermissions fun with bidders', function (done) { coreStorage.setCookie('pubcid', 'test222', (new Date(Date.now() + 5000).toUTCString())); @@ -2397,723 +2040,49 @@ describe('User ID', function () { done(); }, {adUnits}); }); - - it('test hook from liveIntentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier', 'segments': ['123']})); - localStorage.setItem('_li_pbid_exp', ''); + it('should add new id system ', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); - expect(bid.userId.lipb.segments).to.include('123'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 3}], - ext: {segments: ['123']} - }); - }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); + setSubmoduleRegistry([sharedIdSystemSubmodule]); - it('test hook from liveIntentId cookie', function (done) { - coreStorage.setCookie('_li_pbid', JSON.stringify({ - 'unifiedId': 'random-cookie-identifier', - 'segments': ['123'] - }), (new Date(Date.now() + 100000).toUTCString())); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [{ + name: 'pubCommonId', storage: {name: 'pubcid', type: 'cookie'} + }, { + name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }] + } + }); - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + // Add new submodule named 'mockId' + attachIdSystem({ + name: 'mockId', + decode: function (value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: function (config, storedId) { + if (storedId) return {}; + return {id: {'MOCKID': '1234'}}; + } + }); requestBidsHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); - expect(bid.userId.lipb.segments).to.include('123'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-cookie-identifier', atype: 3}], - ext: {segments: ['123']} - }); + // check PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + // check MockId data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mid'); + expect(bid.userId.mid).to.equal('1234'); }); }); - coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from britepoolid cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'}), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([britepoolIdSubmodule]); - config.setConfig(getConfigMock(['britepoolId', 'britepoolid', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('279c0161-5152-487f-809e-05d7f7e653fd'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'britepool.com', - uids: [{id: '279c0161-5152-487f-809e-05d7f7e653fd', atype: 3}] - }); - }); - }); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from dmdId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([dmdIdSubmodule]); - config.setConfig(getConfigMock(['dmdId', 'dmdId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'hcn.health', - uids: [{id: 'testdmdId', atype: 3}] - }); - }); - }); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from netId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('netId', JSON.stringify({'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'}), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([netIdSubmodule]); - config.setConfig(getConfigMock(['netId', 'netId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'netid.de', - uids: [{id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', atype: 1}] - }); - }); - }); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from intentIqId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('intentIqId', 'abcdefghijk', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([intentIqIdSubmodule]); - config.setConfig(getConfigMock(['intentIqId', 'intentIqId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('abcdefghijk'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'intentiq.com', - uids: [{id: 'abcdefghijk', atype: 1}] - }); - }); - }); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from hadronId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); - localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); - - init(config); - setSubmoduleRegistry([hadronIdSubmodule]); - config.setConfig(getConfigMock(['hadronId', 'hadronId', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId1'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'audigent.com', - uids: [{id: 'testHadronId1', atype: 1}] - }); - }); - }); - localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from merkleId cookies - legacy', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('merkleId', JSON.stringify({'pam_id': {'id': 'testmerkleId', 'keyID': 1}}), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([merkleIdSubmodule]); - config.setConfig(getConfigMock(['merkleId', 'merkleId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.merkleId'); - expect(bid.userId.merkleId).to.deep.equal({'id': 'testmerkleId', 'keyID': 1}); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'merkleinc.com', - uids: [{id: 'testmerkleId', atype: 3, ext: {keyID: 1}}] - }); - }); - }); - coreStorage.setCookie('merkleId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from merkleId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('merkleId', JSON.stringify({ - 'merkleId': [{id: 'testmerkleId', ext: { keyID: 1, ssp: 'ssp1' }}, {id: 'another-random-id-value', ext: { ssp: 'ssp2' }}], - '_svsid': 'svs-id-1' - }), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([merkleIdSubmodule]); - config.setConfig(getConfigMock(['merkleId', 'merkleId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.merkleId'); - expect(bid.userId.merkleId.length).to.equal(2); - expect(bid.userIdAsEids.length).to.equal(2); - expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'ssp1.merkleinc.com', uids: [{id: 'testmerkleId', atype: 3, ext: { keyID: 1, ssp: 'ssp1' }}] }); - expect(bid.userIdAsEids[1]).to.deep.equal({ source: 'ssp2.merkleinc.com', uids: [{id: 'another-random-id-value', atype: 3, ext: { ssp: 'ssp2' }}] }); - }); - }); - coreStorage.setCookie('merkleId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from zeotapIdPlus cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('IDP', btoa(JSON.stringify('abcdefghijk')), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([zeotapIdPlusSubmodule]); - config.setConfig(getConfigMock(['zeotapIdPlus', 'IDP', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('abcdefghijk'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'zeotap.com', - uids: [{id: 'abcdefghijk', atype: 1}] - }); - }); - }); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from mwOpenLinkId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([mwOpenLinkIdSubModule]); - config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - }); - }); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from admixerId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('admixerId', 'testadmixerId'); - localStorage.setItem('admixerId_exp', ''); - - init(config); - setSubmoduleRegistry([admixerIdSubmodule]); - config.setConfig(getConfigMock(['admixerId', 'admixerId', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'admixer.net', - uids: [{id: 'testadmixerId', atype: 3}] - }); - }); - }); - localStorage.removeItem('admixerId'); - done(); - }, {adUnits}); - }); - - it('test hook from admixerId cookie', function (done) { - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([admixerIdSubmodule]); - config.setConfig(getConfigMock(['admixerId', 'admixerId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'admixer.net', - uids: [{id: 'testadmixerId', atype: 3}] - }); - }); - }); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from deepintentId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([deepintentDpesSubmodule]); - config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.deep.equal('testdeepintentId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'deepintent.com', - uids: [{id: 'testdeepintentId', atype: 3}] - }); - }); - }); - coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from deepintentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('deepintentId', 'testdeepintentId'); - localStorage.setItem('deepintentId_exp', ''); - - init(config); - setSubmoduleRegistry([deepintentDpesSubmodule]); - config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'deepintent.com', - uids: [{id: 'testdeepintentId', atype: 3}] - }); - }); - }); - localStorage.removeItem('deepintentId'); - done(); - }, {adUnits}); - }); - - it('test hook from qid html5', (done) => { - // simulate existing localStorage values - localStorage.setItem('qid', 'testqid'); - localStorage.setItem('qid_exp', ''); - - init(config); - setSubmoduleRegistry([adqueryIdSubmodule]); - config.setConfig(getConfigMock(['qid', 'qid', 'html5'])); - - requestBidsHook(() => { - adUnits.forEach((adUnit) => { - adUnit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('userId.qid'); - expect(bid.userId.qid).to.equal('testqid'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'adquery.io', - uids: [{ - id: 'testqid', - atype: 1, - }] - }); - }); - }); - - // clear LS - localStorage.removeItem('qid'); - localStorage.removeItem('qid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, hadronId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid, qid and mwOpenLinkId have data to pass', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - // hadronId only supports localStorage - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); - localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); - coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - // amxId only supports localStorage - localStorage.setItem('amxId', 'test_amxid_id'); - localStorage.setItem('amxId_exp', (new Date(Date.now() + 5000)).toUTCString()); - // qid only supports localStorage - localStorage.setItem('qid', 'testqid'); - localStorage.setItem('qid_exp', (new Date(Date.now() + 5000)).toUTCString()); - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], - ['unifiedId', 'unifiedid', 'cookie'], - ['id5Id', 'id5id', 'cookie'], - ['identityLink', 'idl_env', 'cookie'], - ['britepoolId', 'britepoolid', 'cookie'], - ['dmdId', 'dmdId', 'cookie'], - ['netId', 'netId', 'cookie'], - ['intentIqId', 'intentIqId', 'cookie'], - ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'html5'], - ['criteo', 'storage_criteo', 'cookie'], - ['mwOpenLinkId', 'mwol', 'cookie'], - ['tapadId', 'tapad_id', 'cookie'], - ['uid2', 'uid2id', 'cookie'], - ['admixerId', 'admixerId', 'cookie'], - ['amxId', 'amxId', 'html5'], - ['deepintentId', 'deepintentId', 'cookie'], - ['kpuid', 'kpuid', 'cookie'], - ['qid', 'qid', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // also check that UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('testunifiedid'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that dmdID id was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - // also check that netId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that hadronId id was copied to bid - expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId1'); - // also check that criteo id was copied to bid - expect(bid).to.have.deep.nested.property('userId.criteoId'); - expect(bid.userId.criteoId).to.equal('test_bidid'); - // also check that mwOpenLink id was copied to bid - expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' - }); - expect(bid).to.have.deep.nested.property('userId.amxId'); - expect(bid.userId.amxId).to.equal('test_amxid_id'); - - // also check that criteo id was copied to bid - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - - // also check that deepintentId was copied to bid - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - - expect(bid).to.have.deep.nested.property('userId.kpuid'); - expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - - expect(bid).to.have.deep.nested.property('userId.qid'); - expect(bid.userId.qid).to.equal('testqid'); - - expect(bid.userIdAsEids.length).to.equal(18); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp'); - coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('deepintentId', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); - localStorage.removeItem('amxId'); - localStorage.removeItem('amxId_exp'); - localStorage.removeItem('qid'); - localStorage.removeItem('qid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from UID2 cookie', function (done) { - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([uid2IdSubmodule]); - config.setConfig(getConfigMock(['uid2', 'uid2id', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.uid2'); - expect(bid.userId.uid2).to.have.deep.nested.property('id'); - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' - }); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'uidapi.com', - uids: [{ - id: 'Sample_AD_Token', - atype: 3, - }] - }); - }); - }); - coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('should add new id system ', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); - localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); - coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('__uid2_advertising_token', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - localStorage.setItem('amxId', 'test_amxid_id'); - localStorage.setItem('amxId_exp', new Date(Date.now() + 5000).toUTCString()) - coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - localStorage.setItem('qid', 'testqid'); - localStorage.setItem('qid_exp', new Date(Date.now() + 5000).toUTCString()) - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); - - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [{ - name: 'pubCommonId', storage: {name: 'pubcid', type: 'cookie'} - }, { - name: 'unifiedId', storage: {name: 'unifiedid', type: 'cookie'} - }, { - name: 'id5Id', storage: {name: 'id5id', type: 'cookie'} - }, { - name: 'identityLink', storage: {name: 'idl_env', type: 'cookie'} - }, { - name: 'britepoolId', storage: {name: 'britepoolid', type: 'cookie'} - }, { - name: 'dmdId', storage: {name: 'dmdId', type: 'cookie'} - }, { - name: 'netId', storage: {name: 'netId', type: 'cookie'} - }, { - name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} - }, { - name: 'zeotapIdPlus' - }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} - }, { - name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} - }, { - name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }, { - name: 'uid2' - }, { - name: 'deepintentId', storage: {name: 'deepintentId', type: 'cookie'} - }, { - name: 'amxId', storage: {name: 'amxId', type: 'html5'} - }, { - name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} - }, { - name: 'qid', storage: {name: 'qid', type: 'html5'} - }] - } - }); - - // Add new submodule named 'mockId' - attachIdSystem({ - name: 'mockId', - decode: function (value) { - return { - 'mid': value['MOCKID'] - }; - }, - getId: function (config, storedId) { - if (storedId) return {}; - return {id: {'MOCKID': '1234'}}; - } - }); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // check PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // check UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // also check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that dmdId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - // check MockId data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); - // check MockId data was copied to bid - expect(bid).to.have.deep.nested.property('userId.mid'); - expect(bid.userId.mid).to.equal('1234'); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that hadronId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId1'); - expect(bid.userId.uid2).to.deep.equal({ - id: 'Sample_AD_Token' - }); - - expect(bid).to.have.deep.nested.property('userId.amxId'); - expect(bid.userId.amxId).to.equal('test_amxid_id'); - - // also check that admixerId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.admixerId'); - expect(bid.userId.admixerId).to.equal('testadmixerId'); - - // also check that deepintentId was copied to bid - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userId.deepintentId).to.equal('testdeepintentId'); - - expect(bid).to.have.deep.nested.property('userId.kpuid'); - expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - - expect(bid).to.have.deep.nested.property('userId.qid'); - expect(bid.userId.qid).to.equal('testqid'); - expect(bid.userIdAsEids.length).to.equal(16); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp'); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('amxId'); - localStorage.removeItem('amxId_exp'); - coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('__uid2_advertising_token', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -3171,16 +2140,12 @@ describe('User ID', function () { sinon.stub(events, 'getEvents').returns([]); sinon.stub(utils, 'triggerPixel'); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); afterEach(function () { events.getEvents.restore(); utils.triggerPixel.restore(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); resetConsentData(); delete window.__tcfapi; }); @@ -3197,7 +2162,7 @@ describe('User ID', function () { customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(customCfg); return runBidsHook((config) => { innerAdUnits = config.adUnits @@ -3208,44 +2173,6 @@ describe('User ID', function () { expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); }); }); - - it('unifiedid callback with url', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); - config.setConfig(customCfg); - return runBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}).then(() => { - expect(server.requests).to.be.empty; - return endAuction(); - }).then(() => { - expect(server.requests[0].url).to.match(/\/any\/unifiedid\/url/); - }); - }); - - it('unifiedid callback with partner', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {partner: 'rubicon'}); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); - config.setConfig(customCfg); - return runBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}).then(() => { - expect(server.requests).to.be.empty; - return endAuction(); - }).then(() => { - expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); - }); - }); }); describe('Set cookie behavior', function () { @@ -3692,14 +2619,12 @@ describe('User ID', function () { ] } init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, amxIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { auctionDelay: 10, userIds: [{ name: 'pubCommonId', value: {'pubcid': '11111'} - }, { - name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }] } }); diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 54483f0c00e..67e2939a97d 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -1,10 +1,12 @@ import { expect } from 'chai'; import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import {attachIdSystem, init, requestBidsHook, setSubmoduleRegistry} from 'modules/userId/index.js'; import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; import * as storageManager from 'src/storageManager.js'; import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import 'src/prebid.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; @@ -193,4 +195,23 @@ describe('Zeotap ID System', function() { }, { adUnits }); }); }); + describe('eids', () => { + before(() => { + attachIdSystem(zeotapIdPlusSubmodule); + }); + it('zeotapIdPlus', function() { + const userId = { + IDP: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'zeotap.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }); }); diff --git a/test/test_index.js b/test/test_index.js index ce9b671be89..030082735c3 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,25 +1,4 @@ -[it, describe].forEach((ob) => { - ob.only = function () { - [ - 'describe.only and it.only are disabled unless you provide a single spec --file,', - 'because they can silently break the pipeline tests', - // eslint-disable-next-line no-console - ].forEach(l => console.error(l)) - throw new Error('do not use .only()') - }; -}); - -[it, describe].forEach((ob) => { - ob.skip = function () { - [ - 'describe.skip and it.skip are disabled,', - 'because they pollute the pipeline test output', - // eslint-disable-next-line no-console - ].forEach(l => console.error(l)) - throw new Error('do not use .skip()') - }; -}); - +require('./pipeline_setup.js'); require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/); From 524c2add68898f518852bc4e4d3775f780417dee Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Mon, 3 Jun 2024 12:51:50 -0400 Subject: [PATCH 0137/1097] PBS adapter: add tmaxmax (#11540) * Update logic to determine tmax * Update the logic in `ortbConverter.js` so that if a publisher specifies `tmaxmax` as a part of their setup (i.e. specifies it in their `setConfig()` call), `tmaxmax` is what is used to determine `tmax` for a request. * Update tmaxmax logic and test * Update `request()` method in ortbConverter.js to properly account for fact that tmaxmax is actually now a new available property off of the ext property. * Update unit test to verify that when a publisher specifies a tmaxmax value in the config setup, it will be honored. * Use requestBidsTimeout for tmaxmax alternative * Update ortbConverter logic so that `requestBidsTimeout` is the alternative value when `tmaxmax` is not available. * Update prebidServerBidAdapter_spec.js * Add a test to verify that fallback for `tmaxmax` is `requestBidsTimeout`. --- .../prebidServerBidAdapter/ortbConverter.js | 1 + src/adapterManager.js | 2 +- .../modules/prebidServerBidAdapter_spec.js | 38 ++++++++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 1ec3eb0ccd0..d445a52dcc6 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -58,6 +58,7 @@ const PBS_CONVERTER = ortbConverter({ const request = buildRequest(imps, proxyBidderRequest, context); request.tmax = s2sBidRequest.s2sConfig.timeout; + request.ext.tmaxmax = request.ext.tmaxmax || context.s2sBidRequest.requestBidsTimeout; [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { diff --git a/src/adapterManager.js b/src/adapterManager.js index 557f4c1eee4..8228001892d 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -420,7 +420,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request let uniqueServerRequests = serverBidderRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); if (s2sAdapter) { - let s2sBidRequest = {'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments}; + let s2sBidRequest = {'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments, requestBidsTimeout}; if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index c6335227202..3b433d34955 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -763,12 +763,40 @@ describe('S2S Adapter', function () { }); }) - it('should set tmax to s2sConfig.timeout', () => { - const cfg = {...CONFIG, timeout: 123}; - config.setConfig({s2sConfig: cfg}); - adapter.callBids({...REQUEST, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + it('should set tmaxmax correctly when publisher has specified it', () => { + const cfg = {...CONFIG}; + + // publisher has specified a tmaxmax in their setup + const ortb2Fragments = { + global: { + ext: { + tmaxmax: 4242 + } + } + }; + const s2sCfg = {...REQUEST, cfg} + const payloadWithFragments = { ...s2sCfg, ortb2Fragments }; + + adapter.callBids(payloadWithFragments, BID_REQUESTS, addBidResponse, done, ajax); const req = JSON.parse(server.requests[0].requestBody); - expect(req.tmax).to.eql(123); + + expect(req.ext.tmaxmax).to.eql(4242); + }); + + it('should set tmaxmax correctly when publisher has not specified it', () => { + const cfg = {...CONFIG}; + + // publisher has not specified a tmaxmax in their setup - so we should be + // falling back to requestBidsTimeout + const ortb2Fragments = {}; + const s2sCfg = {...REQUEST, cfg}; + const requestBidsTimeout = 808; + const payloadWithFragments = { ...s2sCfg, ortb2Fragments, requestBidsTimeout }; + + adapter.callBids(payloadWithFragments, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + + expect(req.ext.tmaxmax).to.eql(808); }); it('should block request if config did not define p1Consent URL in endpoint object config', function () { From eb5d18fb2d5b88aea5aeeb54e4c1be8e24de0dd4 Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:58:33 +0200 Subject: [PATCH 0138/1097] Improve Digital Bid Adapter: remove video.placement (#11658) * Improve Digital PG flag * Remove parsing of addtlConsent * Fix test * Remove video.placement --------- Co-authored-by: Catalin Ciocov --- modules/improvedigitalBidAdapter.js | 10 +------ .../modules/improvedigitalBidAdapter_spec.js | 27 ++----------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index e28285d9d29..c1108d779cb 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -25,11 +25,7 @@ const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; const VIDEO_PARAMS = { - DEFAULT_MIMES: ['video/mp4'], - PLACEMENT_TYPE: { - INSTREAM: 1, - OUTSTREAM: 3, - } + DEFAULT_MIMES: ['video/mp4'] }; export const spec = { @@ -232,10 +228,6 @@ export const CONVERTER = ortbConverter({ context ); deepSetValue(imp, 'ext.is_rewarded_inventory', (video.rewarded === 1 || deepAccess(video, 'ext.rewarded') === 1) || undefined); - if (!imp.video.placement && ID_REQUEST.isOutstreamVideo(bidRequest)) { - // fillImpVideo will have already set placement = 1 for instream - imp.video.placement = VIDEO_PARAMS.PLACEMENT_TYPE.OUTSTREAM; - } } } } diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 9427037b620..78e938dd074 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -284,7 +284,6 @@ describe('Improve Digital Adapter Tests', function () { }, ...(FEATURES.VIDEO && { video: { - placement: OUTSTREAM_TYPE, w: 640, h: 480, mimes: ['video/mp4'], @@ -474,25 +473,6 @@ describe('Improve Digital Adapter Tests', function () { }); if (FEATURES.VIDEO) { - it('should add correct placement value for instream and outstream video', function () { - let bidRequest = deepClone(simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video).to.not.exist; - - bidRequest = deepClone(simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] - } - }; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(1); - bidRequest.mediaTypes.video.context = 'outstream'; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(3); - }); - it('should set video params for instream', function() { const bidRequest = deepClone(instreamBidRequest); delete bidRequest.mediaTypes.video.playerSize; @@ -507,13 +487,12 @@ describe('Improve Digital Adapter Tests', function () { minbitrate: 500, maxbitrate: 2000, w: 1024, - h: 640, - placement: INSTREAM_TYPE, + h: 640 }; bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal(videoParams); + expect(payload.imp[0].video).to.deep.include(videoParams); }); it('should set video playerSize over video params', () => { @@ -550,7 +529,6 @@ describe('Improve Digital Adapter Tests', function () { const payload = JSON.parse(request.data); expect(payload.imp[0].video).to.deep.equal({...{ mimes: ['video/mp4'], - placement: OUTSTREAM_TYPE, w: bidRequest.mediaTypes.video.playerSize[0], h: bidRequest.mediaTypes.video.playerSize[1], }, @@ -563,7 +541,6 @@ describe('Improve Digital Adapter Tests', function () { const request = spec.buildRequests([bidRequest], {})[0]; const payload = JSON.parse(request.data); const testVideoParams = Object.assign({ - placement: OUTSTREAM_TYPE, w: 640, h: 480, mimes: ['video/mp4'], From 00ffb825f8d3bc9b5d55018c89aae157c0ad8ff3 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 3 Jun 2024 14:38:50 -0400 Subject: [PATCH 0139/1097] ampliffyBidAdapter.js: remove linter exceptions (#11666) --- modules/ampliffyBidAdapter.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ampliffyBidAdapter.js b/modules/ampliffyBidAdapter.js index bcd28e5bcf1..e79b04ab4c4 100644 --- a/modules/ampliffyBidAdapter.js +++ b/modules/ampliffyBidAdapter.js @@ -278,7 +278,6 @@ function extractTrackingURL(htmlContent, ret) { const trackingUrlDiv = htmlContent.querySelectorAll('[bidder-tracking-url]')[0]; if (trackingUrlDiv) { const trackingUrl = trackingUrlDiv.getAttribute('bidder-tracking-url'); - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'parseXML: trackingUrl: ', trackingUrl) ret.trackingUrl = trackingUrl; } @@ -304,10 +303,8 @@ export function parseXML(xml, bid) { ret.userSyncs = extractSyncs(htmlContent); } } catch (e) { - // eslint-disable-next-line no-console logError(LOG_PREFIX + 'Error parsing XML', e); } - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'parseXML RET:', ret); return ret; @@ -341,7 +338,6 @@ export function isAllowedToBidUp(html, currentURL) { } } } catch (e) { - // eslint-disable-next-line no-console logError(LOG_PREFIX + 'isAllowedToBidUp', e); } return allowedToPush; @@ -399,7 +395,6 @@ function onBidWon(bid) { } } function onTimeOut() { - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'TIMEOUT'); } From dbf981739da6d402b0c09e0aa2289cdbb6e86be1 Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:42:32 -0700 Subject: [PATCH 0140/1097] GPID is set first from GPID, then from pbadslot as a fallback. (#11542) --- modules/gumgumBidAdapter.js | 2 +- test/spec/modules/gumgumBidAdapter_spec.js | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 4b12431302f..b1ed98bc743 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -315,7 +315,7 @@ function buildRequests(validBidRequests, bidderRequest) { } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); const eids = getEids(userId); - const gpid = deepAccess(ortb2Imp, 'ext.data.pbadslot') || deepAccess(ortb2Imp, 'ext.data.adserver.adslot'); + const gpid = deepAccess(ortb2Imp, 'ext.gpid') || deepAccess(ortb2Imp, 'ext.data.pbadslot'); let sizes = [1, 1]; let data = {}; data.displaymanager = 'Prebid.js - gumgum'; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 1c5940c06a3..8680b3320d1 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -285,10 +285,21 @@ describe('gumgumAdapter', function () { }); it('should set the global placement id (gpid) if in adserver property', function () { - const req = { ...bidRequests[0], ortb2Imp: { ext: { data: { adserver: { name: 'test', adslot: 123456 } } } } } + const req = { ...bidRequests[0], + ortb2Imp: { + ext: { + gpid: '/17037559/jeusol/jeusol_D_1', + data: { + adserver: { + name: 'test', + adslot: 123456 + } + } + } + } } const bidRequest = spec.buildRequests([req])[0]; expect(bidRequest.data).to.have.property('gpid'); - expect(bidRequest.data.gpid).to.equal(123456); + expect(bidRequest.data.gpid).to.equal('/17037559/jeusol/jeusol_D_1'); }); it('should set the global placement id (gpid) if in pbadslot property', function () { From 566988f2f0cf2b8ffeab6375d917eb321f5fcb34 Mon Sep 17 00:00:00 2001 From: ccorbo Date: Mon, 3 Jun 2024 15:46:13 -0400 Subject: [PATCH 0141/1097] chore: pass through paapi imp extension [PB-2799] (#11639) Co-authored-by: Chris Corbo --- modules/ixBidAdapter.js | 8 +++++++- test/spec/modules/ixBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index f56e2790ad6..4fcf7830685 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -974,6 +974,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { const tid = impressions[impKeys[adUnitIndex]].tid; const sid = impressions[impKeys[adUnitIndex]].sid; const auctionEnvironment = impressions[impKeys[adUnitIndex]].ae; + const paapi = impressions[impKeys[adUnitIndex]].paapi; const bannerImpressions = impressionObjects.filter(impression => BANNER in impression); const otherImpressions = impressionObjects.filter(impression => !(BANNER in impression)); @@ -1023,7 +1024,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID) { + if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID || paapi) { _bannerImpression.ext = {}; _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; @@ -1035,6 +1036,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { // enable fledge auction if (auctionEnvironment == 1) { _bannerImpression.ext.ae = 1; + _bannerImpression.ext.paapi = paapi; } } @@ -1440,6 +1442,10 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') if (fledgeEnabled) { const auctionEnvironment = deepAccess(validBidRequest, 'ortb2Imp.ext.ae') + const paapi = deepAccess(validBidRequest, 'ortb2Imp.ext.paapi') + if (paapi) { + bannerImps[validBidRequest.adUnitCode].paapi = paapi + } if (auctionEnvironment) { if (isInteger(auctionEnvironment)) { bannerImps[validBidRequest.adUnitCode].ae = auctionEnvironment; diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 88cfb93850f..e5b2fbc359a 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -3517,6 +3517,22 @@ describe('IndexexchangeAdapter', function () { expect(logWarnSpy.calledWith('error setting auction environment flag - must be an integer')).to.be.true; logWarnSpy.restore(); }); + + it('impression should have paapi extension when passed', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + let bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); + bid.ortb2Imp.ext.ae = 1 + bid.ortb2Imp.ext.paapi = { + requestedSize: { + width: 300, + height: 250 + } + } + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + expect(impression.ext.paapi.requestedSize.width).to.equal(300); + expect(impression.ext.paapi.requestedSize.height).to.equal(250); + }); }); describe('integration through exchangeId and externalId', function () { From eb1a360fceb42fb6356e24d6c9a4080dc8023362 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 3 Jun 2024 18:56:50 -0400 Subject: [PATCH 0142/1097] ViouslyBidAdapter.js: replace find (#11667) --- modules/viouslyBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/viouslyBidAdapter.js b/modules/viouslyBidAdapter.js index 5ccca7590dd..e474a8de93c 100644 --- a/modules/viouslyBidAdapter.js +++ b/modules/viouslyBidAdapter.js @@ -2,7 +2,7 @@ import { deepAccess, logError, parseUrl, parseSizesInput, triggerPixel } from '. import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import find from 'core-js-pure/features/array/find.js'; // eslint-disable-line prebid/validate-imports +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'viously'; const GVLID = 1028; From 2752e0a6ae411cd5daa16bd3925c2a2308f32e8a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 3 Jun 2024 18:43:04 -0700 Subject: [PATCH 0143/1097] New PAAPI module: topLevelPaapi (#11379) * refactor size logic * fill in requestedSize on auction configs * topLevelPaapi * WIP * getPAAPIBids * include size in paapi bids * update TL example * slightly nicer example * slight improvement * refactor * add PAAPI_ERROR event * use optable in TL example * allow async bid retrieval on render: safeframes * allow async bid retrieval on render: renderAd * do not force string on requestedSize * support rendering of paapi bids * include auctionConfig in events * fix tests * overrideWinner; autorun by default * autorun & overrideWinner * fix tests * emit BID_WON for paapi bids * add no ad server example * improve bid override logic * fix lint --- .../gpt/top-level-paapi/tl_paapi_example.html | 188 ------- .../top-level-paapi/gam-contextual.html | 134 +++++ .../top-level-paapi/no_adserver.html | 113 ++++ .../shared}/decisionLogic.js | 0 .../top-level-paapi/shared/example-setup.js | 95 ++++ libraries/ortbConverter/lib/sizes.js | 14 - libraries/ortbConverter/processors/banner.js | 13 +- libraries/ortbConverter/processors/video.js | 5 +- libraries/weakStore/weakStore.js | 15 + modules/fledgeForGpt.js | 68 ++- modules/paapi.js | 123 +++-- modules/topLevelPaapi.js | 215 ++++++++ src/adRendering.js | 22 +- src/constants.js | 6 +- src/secureCreatives.js | 41 +- src/utils.js | 44 +- test/spec/libraries/weakStore_spec.js | 32 ++ test/spec/modules/fledgeForGpt_spec.js | 31 +- test/spec/modules/paapi_spec.js | 363 +++++++++---- test/spec/modules/topLevelPaapi_spec.js | 502 ++++++++++++++++++ test/spec/unit/adRendering_spec.js | 24 +- test/spec/unit/pbjs_api_spec.js | 214 ++++---- test/spec/unit/secureCreatives_spec.js | 246 +++++---- test/spec/utils_spec.js | 42 +- 24 files changed, 1933 insertions(+), 617 deletions(-) delete mode 100644 integrationExamples/gpt/top-level-paapi/tl_paapi_example.html create mode 100644 integrationExamples/top-level-paapi/gam-contextual.html create mode 100644 integrationExamples/top-level-paapi/no_adserver.html rename integrationExamples/{gpt/top-level-paapi => top-level-paapi/shared}/decisionLogic.js (100%) create mode 100644 integrationExamples/top-level-paapi/shared/example-setup.js delete mode 100644 libraries/ortbConverter/lib/sizes.js create mode 100644 libraries/weakStore/weakStore.js create mode 100644 modules/topLevelPaapi.js create mode 100644 test/spec/libraries/weakStore_spec.js create mode 100644 test/spec/modules/topLevelPaapi_spec.js diff --git a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html b/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html deleted file mode 100644 index 9a4991d2711..00000000000 --- a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - -

Standalone PAAPI Prebid.js Example

-

Start local server with:

-gulp serve-fast --https -

Chrome flags:

---enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true - --privacy-sandbox-enrollment-overrides=https://localhost:9999 -

Join interest group at https://privacysandbox.openx.net/fledge/advertiser -

-
Div-1
-
- -
- - - diff --git a/integrationExamples/top-level-paapi/gam-contextual.html b/integrationExamples/top-level-paapi/gam-contextual.html new file mode 100644 index 00000000000..b51b512e0ca --- /dev/null +++ b/integrationExamples/top-level-paapi/gam-contextual.html @@ -0,0 +1,134 @@ + + + + + + + + + + + +

GAM contextual + Publisher as top level PAAPI seller example

+ +

+ This example starts PAAPI auctions at the same time as GAM targeting. The flow is + similar to a typical GAM auction, but if Prebid wins, and got a + PAAPI bid, it is rendered instead of the contextual bid. +

+
+ +
+
Div-1
+
+ +
+
+

Instructions

+

Start local server with:

+gulp serve-fast --https +

Chrome flags:

+--enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://www.optable.co/ +

+
+ + diff --git a/integrationExamples/top-level-paapi/no_adserver.html b/integrationExamples/top-level-paapi/no_adserver.html new file mode 100644 index 00000000000..0b37f80f27c --- /dev/null +++ b/integrationExamples/top-level-paapi/no_adserver.html @@ -0,0 +1,113 @@ + + + + + + + + + +

No ad server, publisher as top level PAAPI seller example

+ +

+ +

+
+ +
+
Div-1
+
+ +
+
+

Instructions

+

Start local server with:

+ gulp serve-fast --https +

Chrome flags:

+ --enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://www.optable.co/ +

+
+ + diff --git a/integrationExamples/gpt/top-level-paapi/decisionLogic.js b/integrationExamples/top-level-paapi/shared/decisionLogic.js similarity index 100% rename from integrationExamples/gpt/top-level-paapi/decisionLogic.js rename to integrationExamples/top-level-paapi/shared/decisionLogic.js diff --git a/integrationExamples/top-level-paapi/shared/example-setup.js b/integrationExamples/top-level-paapi/shared/example-setup.js new file mode 100644 index 00000000000..1c52abf02c9 --- /dev/null +++ b/integrationExamples/top-level-paapi/shared/example-setup.js @@ -0,0 +1,95 @@ +// intercept navigator.runAdAuction and print parameters to console +(() => { + var originalRunAdAuction = navigator.runAdAuction; + navigator.runAdAuction = function (...args) { + console.log('%c runAdAuction', 'background: cyan; border: 2px; border-radius: 3px', ...args); + return originalRunAdAuction.apply(navigator, args); + }; +})(); +init(); +setupContextualResponse(); + +function addExampleControls(requestBids) { + const ctl = document.createElement('div'); + ctl.innerHTML = ` + + Simulate contextual bid: + + CPM + + + `; + ctl.style = 'margin-top: 30px'; + document.body.appendChild(ctl); + ctl.querySelector('.bid').addEventListener('click', function (ev) { + const cpm = ctl.querySelector('.cpm').value; + if (cpm) { + setupContextualResponse(parseInt(cpm, 10)); + } + requestBids(); + }); +} + +function init() { + window.pbjs = window.pbjs || {que: []}; + window.pbjs.que.push(() => { + pbjs.aliasBidder('optable', 'contextual'); + [ + 'auctionInit', + 'auctionTimeout', + 'auctionEnd', + 'bidAdjustment', + 'bidTimeout', + 'bidRequested', + 'bidResponse', + 'bidRejected', + 'noBid', + 'seatNonBid', + 'bidWon', + 'bidderDone', + 'bidderError', + 'setTargeting', + 'beforeRequestBids', + 'beforeBidderHttp', + 'requestBids', + 'addAdUnits', + 'adRenderFailed', + 'adRenderSucceeded', + 'tcf2Enforcement', + 'auctionDebug', + 'bidViewable', + 'staleRender', + 'billableEvent', + 'bidAccepted', + 'paapiRunAuction', + 'paapiBid', + 'paapiNoBid', + 'paapiError', + ].forEach(evt => { + pbjs.onEvent(evt, (arg) => { + console.log('Event:', evt, arg); + }) + }); + }); +} + +function setupContextualResponse(cpm = 1) { + pbjs.que.push(() => { + pbjs.setConfig({ + debugging: { + enabled: true, + intercept: [ + { + when: { + bidder: 'contextual' + }, + then: { + cpm, + currency: 'USD' + } + } + ] + } + }); + }); +} diff --git a/libraries/ortbConverter/lib/sizes.js b/libraries/ortbConverter/lib/sizes.js deleted file mode 100644 index 16b75048203..00000000000 --- a/libraries/ortbConverter/lib/sizes.js +++ /dev/null @@ -1,14 +0,0 @@ -import {parseSizesInput} from '../../../src/utils.js'; - -export function sizesToFormat(sizes) { - sizes = parseSizesInput(sizes); - - // get sizes in form [{ w: , h: }, ...] - return sizes.map(size => { - const [width, height] = size.split('x'); - return { - w: parseInt(width, 10), - h: parseInt(height, 10) - }; - }); -} diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js index 2d0136c84b2..fca9598022b 100644 --- a/libraries/ortbConverter/processors/banner.js +++ b/libraries/ortbConverter/processors/banner.js @@ -1,6 +1,13 @@ -import {createTrackPixelHtml, deepAccess, encodeMacroURI, inIframe, mergeDeep} from '../../../src/utils.js'; +import { + createTrackPixelHtml, + deepAccess, + inIframe, + mergeDeep, + sizesToSizeTuples, + sizeTupleToRtbSize, + encodeMacroURI +} from '../../../src/utils.js'; import {BANNER} from '../../../src/mediaTypes.js'; -import {sizesToFormat} from '../lib/sizes.js'; /** * fill in a request `imp` with banner parameters from `bidRequest`. @@ -14,7 +21,7 @@ export function fillBannerImp(imp, bidRequest, context) { topframe: inIframe() === true ? 0 : 1 }; if (bannerParams.sizes) { - banner.format = sizesToFormat(bannerParams.sizes); + banner.format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); } if (bannerParams.hasOwnProperty('pos')) { banner.pos = bannerParams.pos; diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index c38231d9002..b10ad4032c5 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -1,6 +1,5 @@ -import {deepAccess, isEmpty, logWarn, mergeDeep} from '../../../src/utils.js'; +import {deepAccess, isEmpty, logWarn, mergeDeep, sizesToSizeTuples, sizeTupleToRtbSize} from '../../../src/utils.js'; import {VIDEO} from '../../../src/mediaTypes.js'; -import {sizesToFormat} from '../lib/sizes.js'; // parameters that share the same name & semantics between pbjs adUnits and imp.video const ORTB_VIDEO_PARAMS = new Set([ @@ -41,7 +40,7 @@ export function fillVideoImp(imp, bidRequest, context) { .filter(([name]) => ORTB_VIDEO_PARAMS.has(name)) ); if (videoParams.playerSize) { - const format = sizesToFormat(videoParams.playerSize); + const format = sizesToSizeTuples(videoParams.playerSize).map(sizeTupleToRtbSize); if (format.length > 1) { logWarn('video request specifies more than one playerSize; all but the first will be ignored') } diff --git a/libraries/weakStore/weakStore.js b/libraries/weakStore/weakStore.js new file mode 100644 index 00000000000..09606354dae --- /dev/null +++ b/libraries/weakStore/weakStore.js @@ -0,0 +1,15 @@ +import {auctionManager} from '../../src/auctionManager.js'; + +export function weakStore(get) { + const store = new WeakMap(); + return function (id, init = {}) { + const obj = get(id); + if (obj == null) return; + if (!store.has(obj)) { + store.set(obj, init); + } + return store.get(obj); + }; +} + +export const auctionStore = () => weakStore((auctionId) => auctionManager.index.getAuction({auctionId})); diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index bda4494faaf..a356785dbe1 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -1,8 +1,8 @@ /** * GPT-specific slot configuration logic for PAAPI. */ -import {submodule} from '../src/hook.js'; -import {deepAccess, logInfo, logWarn} from '../src/utils.js'; +import {getHook, submodule} from '../src/hook.js'; +import {deepAccess, logInfo, logWarn, sizeTupleToSizeString} from '../src/utils.js'; import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; @@ -12,6 +12,7 @@ import {getGlobal} from '../src/prebidGlobal.js'; // TODO: remove this in prebid 9 // eslint-disable-next-line prebid/validate-imports import './paapi.js'; +import {keyCompare} from '../src/utils/reducers.js'; const MODULE = 'fledgeForGpt'; let getPAAPIConfig; @@ -73,6 +74,68 @@ export function onAuctionConfigFactory(setGptConfig = setComponentAuction) { } } +export const getPAAPISizeHook = (() => { + /* + https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing#faq + https://support.google.com/admanager/answer/1100453?hl=en + + Ignore any placeholder sizes, where placeholder is defined as a square creative with a side of <= 5 pixels + Look if there are any sizes that are part of the set of supported ad sizes defined here. If there are, choose the largest supported size by area (width * height) + For clarity, the set of supported ad sizes includes all of the ad sizes listed under “Top-performing ad sizes”, “Other supported ad sizes”, and “Regional ad sizes”. + If not, choose the largest remaining size (i.e. that isn’t in the list of supported ad sizes) by area (width * height) + */ + const SUPPORTED_SIZES = [ + [728, 90], + [336, 280], + [300, 250], + [300, 50], + [160, 600], + [1024, 768], + [970, 250], + [970, 90], + [768, 1024], + [480, 320], + [468, 60], + [320, 480], + [320, 100], + [320, 50], + [300, 600], + [300, 100], + [250, 250], + [234, 60], + [200, 200], + [180, 150], + [125, 125], + [120, 600], + [120, 240], + [120, 60], + [88, 31], + [980, 120], + [980, 90], + [950, 90], + [930, 180], + [750, 300], + [750, 200], + [750, 100], + [580, 400], + [250, 360], + [240, 400], + ].sort(keyCompare(([w, h]) => -(w * h))) + .map(size => [size, sizeTupleToSizeString(size)]); + + return function(next, sizes) { + if (sizes?.length) { + const sizeStrings = new Set(sizes.map(sizeTupleToSizeString)); + const preferredSize = SUPPORTED_SIZES.find(([_, sizeStr]) => sizeStrings.has(sizeStr)); + if (preferredSize) { + next.bail(preferredSize[0]); + return; + } + } + next(sizes); + } +})(); + export function setPAAPIConfigFactory( getConfig = (filters) => getPAAPIConfig(filters, true), setGptConfig = setComponentAuction) { @@ -105,5 +168,6 @@ submodule('paapi', { onAuctionConfig: onAuctionConfigFactory(), init(params) { getPAAPIConfig = params.getPAAPIConfig; + getHook('getPAAPISize').before(getPAAPISizeHook); } }); diff --git a/modules/paapi.js b/modules/paapi.js index 28252d0bb7a..3d562e83c56 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -2,15 +2,15 @@ * Collect PAAPI component auction configs from bid adapters and make them available through `pbjs.getPAAPIConfig()` */ import {config} from '../src/config.js'; -import {getHook, module} from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep, deepEqual, parseSizesInput, deepAccess} from '../src/utils.js'; +import {getHook, hook, module} from '../src/hook.js'; +import {deepSetValue, logInfo, logWarn, mergeDeep, sizesToSizeTuples, deepAccess, deepEqual} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; import {currencyCompare} from '../libraries/currencyUtils/currency.js'; -import {maximum, minimum} from '../src/utils/reducers.js'; -import {auctionManager} from '../src/auctionManager.js'; +import {keyCompare, maximum, minimum} from '../src/utils/reducers.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {auctionStore} from '../libraries/weakStore/weakStore.js'; const MODULE = 'PAAPI'; @@ -19,23 +19,14 @@ const USED = new WeakSet(); export function registerSubmodule(submod) { submodules.push(submod); - submod.init && submod.init({getPAAPIConfig}); + submod.init && submod.init({ + getPAAPIConfig, + expandFilters + }); } module('paapi', registerSubmodule); -function auctionStore() { - const store = new WeakMap(); - return function (auctionId, init = {}) { - const auction = auctionManager.index.getAuction({auctionId}); - if (auction == null) return; - if (!store.has(auction)) { - store.set(auction, init); - } - return store.get(auction); - }; -} - const pendingConfigsForAuction = auctionStore(); const configsForAuction = auctionStore(); const pendingBuyersForAuction = auctionStore(); @@ -71,7 +62,7 @@ getHook('addPaapiConfig').before(addPaapiConfigHook); getHook('makeBidRequests').after(markForFledge); events.on(EVENTS.AUCTION_END, onAuctionEnd); -function getSlotSignals(bidsReceived = [], bidRequests = []) { +function getSlotSignals(adUnit = {}, bidsReceived = [], bidRequests = []) { let bidfloor, bidfloorcur; if (bidsReceived.length > 0) { const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); @@ -88,6 +79,10 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) { deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); } + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + cfg.requestedSize = requestedSize; + } return cfg; } @@ -109,7 +104,7 @@ export function buyersToAuctionConfigs(igbRequests, merge = mergeBuyers, config } function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) { - const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []) + const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []); const allReqs = bidderRequests?.flatMap(br => br.bids); const paapiConfigs = {}; (adUnitCodes || []).forEach(au => { @@ -125,23 +120,11 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adU } Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); + const slotSignals = getSlotSignals(adUnitsByCode[adUnitCode], bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); paapiConfigs[adUnitCode] = { ...slotSignals, componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg)) }; - // TODO: need to flesh out size treatment: - // - which size should the paapi auction pick? (this uses the first one defined) - // - should we signal it to SSPs, and how? - // - what should we do if adapters pick a different one? - // - what does size mean for video and native? - const size = parseSizesInput(adUnitsByCode[adUnitCode]?.mediaTypes?.banner?.sizes)?.[0]?.split('x'); - if (size) { - paapiConfigs[adUnitCode].requestedSize = { - width: size[0], - height: size[1], - }; - } latestAuctionForAdUnit[adUnitCode] = auctionId; }); configsForAuction(auctionId, paapiConfigs); @@ -263,33 +246,54 @@ export function partitionBuyersByBidder(igbRequests) { }) return Object.entries(igbs).map(([bidder, igbs]) => [requests[bidder], igbs]) } + +/** + * Expand PAAPI api filters into a map from ad unit code to auctionId. + * + * @param auctionId when specified, the result will have this as the value for each entry. + * when not specified, each ad unit will map to the latest auction that involved that ad unit. + * @param adUnitCode when specified, the result will contain only one entry (for this ad unit) or be empty (if this ad + * unit was never involved in an auction). + * when not specified, the result will contain an entry for every ad unit that was involved in any auction. + * @return {{[adUnitCode: string]: string}} + */ +function expandFilters({auctionId, adUnitCode} = {}) { + let adUnitCodes = []; + if (adUnitCode == null) { + adUnitCodes = Object.keys(latestAuctionForAdUnit); + } else if (latestAuctionForAdUnit.hasOwnProperty(adUnitCode)) { + adUnitCodes = [adUnitCode]; + } + return Object.fromEntries( + adUnitCodes.map(au => [au, auctionId ?? latestAuctionForAdUnit[au]]) + ); +} + /** * Get PAAPI auction configuration. * - * @param auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used - * @param adUnitCode? optional ad unit filter + * @param filters + * @param filters.auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used + * @param filters.adUnitCode? optional ad unit filter * @param includeBlanks if true, include null entries for ad units that match the given filters but do not have any available auction configs. * @returns {{}} a map from ad unit code to auction config for the ad unit. */ -export function getPAAPIConfig({auctionId, adUnitCode} = {}, includeBlanks = false) { +export function getPAAPIConfig(filters = {}, includeBlanks = false) { const output = {}; - const targetedAuctionConfigs = auctionId && configsForAuction(auctionId); - Object.keys((auctionId != null ? targetedAuctionConfigs : latestAuctionForAdUnit) ?? []).forEach(au => { - const latestAuctionId = latestAuctionForAdUnit[au]; - const auctionConfigs = targetedAuctionConfigs ?? (latestAuctionId && configsForAuction(latestAuctionId)); - if ((adUnitCode ?? au) === au) { - let candidate; - if (targetedAuctionConfigs?.hasOwnProperty(au)) { - candidate = targetedAuctionConfigs[au]; - } else if (auctionId == null && auctionConfigs?.hasOwnProperty(au)) { - candidate = auctionConfigs[au]; - } + Object.entries(expandFilters(filters)).forEach(([au, auctionId]) => { + const auctionConfigs = configsForAuction(auctionId); + if (auctionConfigs?.hasOwnProperty(au)) { + // ad unit was involved in a PAAPI auction + const candidate = auctionConfigs[au]; if (candidate && !USED.has(candidate)) { output[au] = candidate; USED.add(candidate); } else if (includeBlanks) { output[au] = null; } + } else if (auctionId == null && includeBlanks) { + // ad unit was involved in a non-PAAPI auction + output[au] = null; } }); return output; @@ -310,6 +314,29 @@ function getFledgeConfig() { }; } +/** + * Given an array of size tuples, return the one that should be used for PAAPI. + */ +export const getPAAPISize = hook('sync', function (sizes) { + if (sizes?.length) { + return sizes + .filter(([w, h]) => !(w === h && w <= 5)) + .reduce(maximum(keyCompare(([w, h]) => w * h))); + } +}, 'getPAAPISize'); + +function getRequestedSize(adUnit) { + return adUnit.ortb2Imp?.ext?.paapi?.requestedSize || (() => { + const size = getPAAPISize(sizesToSizeTuples(adUnit.mediaTypes?.banner?.sizes)); + if (size) { + return { + width: size[0], + height: size[1] + }; + } + })(); +} + export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { bidderRequests.forEach((bidderReq) => { @@ -338,6 +365,10 @@ export function markForFledge(next, bidderRequests) { ae: bidAe, biddable: 1 }, bidReq.ortb2Imp.ext.igs) + const requestedSize = getRequestedSize(bidReq); + if (requestedSize) { + deepSetValue(bidReq, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); + } } }); }); diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js new file mode 100644 index 00000000000..040c0125b3a --- /dev/null +++ b/modules/topLevelPaapi.js @@ -0,0 +1,215 @@ +import {submodule} from '../src/hook.js'; +import {config} from '../src/config.js'; +import {logError, logInfo, logWarn, mergeDeep} from '../src/utils.js'; +import {auctionStore} from '../libraries/weakStore/weakStore.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {emit} from '../src/events.js'; +import {BID_STATUS, EVENTS} from '../src/constants.js'; +import {GreedyPromise} from '../src/utils/promise.js'; +import {getBidToRender, getRenderingData, markWinningBid} from '../src/adRendering.js'; + +let getPAAPIConfig, expandFilters, moduleConfig; + +const paapiBids = auctionStore(); +const MODULE_NAME = 'topLevelPaapi'; + +config.getConfig('paapi', (cfg) => { + moduleConfig = cfg.paapi?.topLevelSeller; + if (moduleConfig) { + getBidToRender.before(renderPaapiHook); + getBidToRender.after(renderOverrideHook); + getRenderingData.before(getRenderingDataHook); + markWinningBid.before(markWinningBidHook); + } else { + getBidToRender.getHooks({hook: renderPaapiHook}).remove(); + getBidToRender.getHooks({hook: renderOverrideHook}).remove(); + getRenderingData.getHooks({hook: getRenderingDataHook}).remove(); + markWinningBid.getHooks({hook: markWinningBidHook}).remove(); + } +}); + +function isPaapiBid(bid) { + return bid?.source === 'paapi'; +} + +function bidIfRenderable(bid) { + if (bid && !bid.urn) { + logWarn(MODULE_NAME, 'rendering in fenced frames is not supported. Consider using resolveToConfig: false', bid); + return; + } + return bid; +} + +const forRenderStack = []; + +function renderPaapiHook(next, adId, forRender = true, override = GreedyPromise.resolve()) { + forRenderStack.push(forRender); + const ids = parsePaapiAdId(adId); + if (ids) { + override = override.then((bid) => { + if (bid) return bid; + const [auctionId, adUnitCode] = ids; + return paapiBids(auctionId)?.[adUnitCode]?.then(bid => { + if (!bid) { + logWarn(MODULE_NAME, `No PAAPI bid found for auctionId: "${auctionId}", adUnit: "${adUnitCode}"`); + } + return bidIfRenderable(bid); + }); + }); + } + next(adId, forRender, override); +} + +function renderOverrideHook(next, bidPm) { + const forRender = forRenderStack.pop(); + if (moduleConfig?.overrideWinner) { + bidPm = bidPm.then((bid) => { + if (isPaapiBid(bid) || bid?.status === BID_STATUS.RENDERED) return bid; + return getPAAPIBids({adUnitCode: bid.adUnitCode}).then(res => { + let paapiBid = bidIfRenderable(res[bid.adUnitCode]); + if (paapiBid) { + if (!forRender) return paapiBid; + if (forRender && paapiBid.status !== BID_STATUS.RENDERED) { + paapiBid.overriddenAdId = bid.adId; + logInfo(MODULE_NAME, 'overriding contextual bid with PAAPI bid', bid, paapiBid) + return paapiBid; + } + } + return bid; + }); + }); + } + next(bidPm); +} + +export function getRenderingDataHook(next, bid, options) { + if (isPaapiBid(bid)) { + next.bail({ + width: bid.width, + height: bid.height, + adUrl: bid.urn + }); + } else { + next(bid, options); + } +} + +export function markWinningBidHook(next, bid) { + if (isPaapiBid(bid)) { + bid.status = BID_STATUS.RENDERED; + emit(EVENTS.BID_WON, bid); + next.bail(); + } else { + next(bid); + } +} + +function getBaseAuctionConfig() { + if (moduleConfig?.auctionConfig) { + return Object.assign({ + resolveToConfig: false + }, moduleConfig.auctionConfig); + } +} + +function onAuctionConfig(auctionId, auctionConfigs) { + const base = getBaseAuctionConfig(); + if (base) { + Object.entries(auctionConfigs).forEach(([adUnitCode, auctionConfig]) => { + mergeDeep(auctionConfig, base); + if (moduleConfig.autorun ?? true) { + getPAAPIBids({adUnitCode, auctionId}); + } + }); + } +} + +export function parsePaapiSize(size) { + /* From https://github.com/WICG/turtledove/blob/main/FLEDGE.md#12-interest-group-attributes: + * Each size has the format {width: widthVal, height: heightVal}, + * where the values can have either pixel units (e.g. 100 or '100px') or screen dimension coordinates (e.g. 100sw or 100sh). + */ + if (typeof size === 'number') return size; + if (typeof size === 'string') { + const px = /^(\d+)(px)?$/.exec(size)?.[1]; + if (px) { + return parseInt(px, 10); + } + } + return null; +} + +export function getPaapiAdId(auctionId, adUnitCode) { + return `paapi:/${auctionId.replace(/:/g, '::')}/:/${adUnitCode.replace(/:/g, '::')}`; +} + +export function parsePaapiAdId(adId) { + const match = /^paapi:\/(.*)\/:\/(.*)$/.exec(adId); + if (match) { + return [match[1], match[2]].map(s => s.replace(/::/g, ':')); + } +} + +/** + * Returns the PAAPI runAdAuction result for the given filters, as a map from ad unit code to auction result + * (an object with `width`, `height`, and one of `urn` or `frameConfig`). + * + * @param filters + * @param raa + * @return {Promise<{[p: string]: any}>} + */ +export function getPAAPIBids(filters, raa = (...args) => navigator.runAdAuction(...args)) { + return Promise.all( + Object.entries(expandFilters(filters)) + .map(([adUnitCode, auctionId]) => { + const bids = paapiBids(auctionId); + if (bids && !bids.hasOwnProperty(adUnitCode)) { + const auctionConfig = getPAAPIConfig({adUnitCode, auctionId})[adUnitCode]; + if (auctionConfig) { + emit(EVENTS.RUN_PAAPI_AUCTION, { + auctionId, + adUnitCode, + auctionConfig + }); + bids[adUnitCode] = new Promise((resolve, reject) => raa(auctionConfig).then(resolve, reject)) + .then(result => { + if (result) { + const bid = { + source: 'paapi', + adId: getPaapiAdId(auctionId, adUnitCode), + width: parsePaapiSize(auctionConfig.requestedSize?.width), + height: parsePaapiSize(auctionConfig.requestedSize?.height), + adUnitCode, + auctionId, + [typeof result === 'string' ? 'urn' : 'frameConfig']: result, + auctionConfig, + }; + emit(EVENTS.PAAPI_BID, bid); + return bid; + } else { + emit(EVENTS.PAAPI_NO_BID, {auctionId, adUnitCode, auctionConfig}); + return null; + } + }).catch(error => { + logError(MODULE_NAME, `error (auction "${auctionId}", adUnit "${adUnitCode}"):`, error); + emit(EVENTS.PAAPI_ERROR, {auctionId, adUnitCode, error, auctionConfig}); + return null; + }); + } + } + return bids?.[adUnitCode]?.then(res => [adUnitCode, res]); + }).filter(e => e) + ).then(result => Object.fromEntries(result)); +} + +getGlobal().getPAAPIBids = (filters) => getPAAPIBids(filters); + +export const topLevelPAAPI = { + name: MODULE_NAME, + init(params) { + getPAAPIConfig = params.getPAAPIConfig; + expandFilters = params.expandFilters; + }, + onAuctionConfig +}; +submodule('paapi', topLevelPAAPI); diff --git a/src/adRendering.js b/src/adRendering.js index 7d306adc9cc..33f7fe9252c 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -8,10 +8,22 @@ import {auctionManager} from './auctionManager.js'; import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; +import {GreedyPromise} from './utils/promise.js'; const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; +export const getBidToRender = hook('sync', function (adId, forRender = true, override = GreedyPromise.resolve()) { + return override + .then(bid => bid ?? auctionManager.findBidByAdId(adId)) + .catch(() => {}) +}) + +export const markWinningBid = hook('sync', function (bid) { + events.emit(BID_WON, bid); + auctionManager.addWinningBid(bid); +}) + /** * Emit the AD_RENDER_FAILED event. * @@ -168,8 +180,7 @@ export function handleRender({renderFn, resizeFn, adId, options, bidResponse, do bid: bidResponse }); } - auctionManager.addWinningBid(bidResponse); - events.emit(BID_WON, bidResponse); + markWinningBid(bidResponse); } export function renderAdDirect(doc, adId, options) { @@ -211,12 +222,13 @@ export function renderAdDirect(doc, adId, options) { if (!adId || !doc) { fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); } else { - bid = auctionManager.findBidByAdId(adId); - if ((doc === document && !inIframe())) { fail(AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`); } else { - handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); + getBidToRender(adId).then(bidResponse => { + bid = bidResponse; + handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse, doc}); + }); } } } catch (e) { diff --git a/src/constants.js b/src/constants.js index bb76083862b..4ca5f6a1b12 100644 --- a/src/constants.js +++ b/src/constants.js @@ -41,7 +41,11 @@ export const EVENTS = { BID_VIEWABLE: 'bidViewable', STALE_RENDER: 'staleRender', BILLABLE_EVENT: 'billableEvent', - BID_ACCEPTED: 'bidAccepted' + BID_ACCEPTED: 'bidAccepted', + RUN_PAAPI_AUCTION: 'paapiRunAuction', + PAAPI_BID: 'paapiBid', + PAAPI_NO_BID: 'paapiNoBid', + PAAPI_ERROR: 'paapiError', }; export const AD_RENDER_FAILED_REASON = { diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 96ace0792e4..a33f742b738 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -3,19 +3,15 @@ access to a publisher page from creative payloads. */ -import * as events from './events.js'; import {getAllAssetsMessage, getAssetMessage} from './native.js'; -import { BID_STATUS, EVENTS, MESSAGES } from './constants.js'; +import {BID_STATUS, MESSAGES} from './constants.js'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; -import {auctionManager} from './auctionManager.js'; import {find, includes} from './polyfill.js'; -import {handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; +import {getBidToRender, handleCreativeEvent, handleNativeMessage, handleRender, markWinningBid} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; -const BID_WON = EVENTS.BID_WON; - const HANDLER_MAP = { [REQUEST]: handleRenderRequest, [EVENT]: handleEventRequest, @@ -28,7 +24,9 @@ if (FEATURES.NATIVE) { } export function listenMessagesFromCreative() { - window.addEventListener('message', receiveMessage, false); + window.addEventListener('message', function (ev) { + receiveMessage(ev); + }, false); } export function getReplier(ev) { @@ -49,6 +47,12 @@ export function getReplier(ev) { } } +function ensureAdId(adId, reply) { + return function (data, ...args) { + return reply(Object.assign({}, data, {adId}), ...args); + } +} + export function receiveMessage(ev) { var key = ev.message ? 'message' : 'data'; var data = {}; @@ -58,19 +62,19 @@ export function receiveMessage(ev) { return; } - if (data && data.adId && data.message) { - const adObject = find(auctionManager.getBidsReceived(), function (bid) { - return bid.adId === data.adId; - }); - if (HANDLER_MAP.hasOwnProperty(data.message)) { - HANDLER_MAP[data.message](getReplier(ev), data, adObject); - } + if (data && data.adId && data.message && HANDLER_MAP.hasOwnProperty(data.message)) { + return getBidToRender(data.adId, data.message === MESSAGES.REQUEST).then(adObject => { + HANDLER_MAP[data.message](ensureAdId(data.adId, getReplier(ev)), data, adObject); + }) } } -function getResizer(bidResponse) { +function getResizer(adId, bidResponse) { + // in some situations adId !== bidResponse.adId + // the first is the one that was requested and is tied to the element + // the second is the one that is being rendered (sometimes different, e.g. in some paapi setups) return function (width, height) { - resizeRemoteCreative({...bidResponse, width, height}); + resizeRemoteCreative({...bidResponse, width, height, adId}); } } function handleRenderRequest(reply, message, bidResponse) { @@ -81,7 +85,7 @@ function handleRenderRequest(reply, message, bidResponse) { renderer: getCreativeRendererSource(bidResponse) }, adData)); }, - resizeFn: getResizer(bidResponse), + resizeFn: getResizer(message.adId, bidResponse), options: message.options, adId: message.adId, bidResponse @@ -100,8 +104,7 @@ function handleNativeRequest(reply, data, adObject) { } if (adObject.status !== BID_STATUS.RENDERED) { - auctionManager.addWinningBid(adObject); - events.emit(BID_WON, adObject); + markWinningBid(adObject); } switch (data.action) { diff --git a/src/utils.js b/src/utils.js index 46dd06a6a41..12656951c21 100644 --- a/src/utils.js +++ b/src/utils.js @@ -130,37 +130,55 @@ export function transformAdServerTargetingObj(targeting) { } /** - * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' - * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] - * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of width, height tuples `[[300, 250]]` or '[[300,250], [970,90]]' */ -export function parseSizesInput(sizeObj) { - if (typeof sizeObj === 'string') { +export function sizesToSizeTuples(sizes) { + if (typeof sizes === 'string') { // multiple sizes will be comma-separated - return sizeObj.split(',').filter(sz => sz.match(/^(\d)+x(\d)+$/i)) - } else if (typeof sizeObj === 'object') { - if (sizeObj.length === 2 && typeof sizeObj[0] === 'number' && typeof sizeObj[1] === 'number') { - return [parseGPTSingleSizeArray(sizeObj)]; - } else { - return sizeObj.map(parseGPTSingleSizeArray) + return sizes + .split(/\s*,\s*/) + .map(sz => sz.match(/^(\d+)x(\d+)$/i)) + .filter(match => match) + .map(([_, w, h]) => [parseInt(w, 10), parseInt(h, 10)]) + } else if (Array.isArray(sizes)) { + if (isValidGPTSingleSize(sizes)) { + return [sizes] } + return sizes.filter(isValidGPTSingleSize); } return []; } +/** + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' + * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] + * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + */ +export function parseSizesInput(sizeObj) { + return sizesToSizeTuples(sizeObj).map(sizeTupleToSizeString); +} + +export function sizeTupleToSizeString(size) { + return size[0] + 'x' + size[1] +} + // Parse a GPT style single size array, (i.e [300, 250]) // into an AppNexus style string, (i.e. 300x250) export function parseGPTSingleSizeArray(singleSize) { if (isValidGPTSingleSize(singleSize)) { - return singleSize[0] + 'x' + singleSize[1]; + return sizeTupleToSizeString(singleSize); } } +export function sizeTupleToRtbSize(size) { + return {w: size[0], h: size[1]}; +} + // Parse a GPT style single size array, (i.e [300, 250]) // into OpenRTB-compatible (imp.banner.w/h, imp.banner.format.w/h, imp.video.w/h) object(i.e. {w:300, h:250}) export function parseGPTSingleSizeArrayToRtbSize(singleSize) { if (isValidGPTSingleSize(singleSize)) { - return {w: singleSize[0], h: singleSize[1]}; + return sizeTupleToRtbSize(singleSize) } } diff --git a/test/spec/libraries/weakStore_spec.js b/test/spec/libraries/weakStore_spec.js new file mode 100644 index 00000000000..407b83391ef --- /dev/null +++ b/test/spec/libraries/weakStore_spec.js @@ -0,0 +1,32 @@ +import {weakStore} from '../../../libraries/weakStore/weakStore.js'; + +describe('weakStore', () => { + let targets, store; + beforeEach(() => { + targets = { + id: {} + }; + store = weakStore((id) => targets[id]); + }); + + it('returns undef if getter returns undef', () => { + expect(store('missing')).to.not.exist; + }); + + it('inits to empty object by default', () => { + expect(store('id')).to.eql({}); + }); + + it('inits to given value', () => { + expect(store('id', {initial: 'value'})).to.eql({'initial': 'value'}); + }); + + it('returns the same object as long as the target does not change', () => { + expect(store('id')).to.equal(store('id')); + }); + + it('ignores init value if already initialized', () => { + store('id', {initial: 'value'}); + expect(store('id', {second: 'value'})).to.eql({initial: 'value'}); + }) +}); diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js index 8ab11171121..aa513f931db 100644 --- a/test/spec/modules/fledgeForGpt_spec.js +++ b/test/spec/modules/fledgeForGpt_spec.js @@ -1,4 +1,9 @@ -import {onAuctionConfigFactory, setPAAPIConfigFactory, slotConfigurator} from 'modules/fledgeForGpt.js'; +import { + getPAAPISizeHook, + onAuctionConfigFactory, + setPAAPIConfigFactory, + slotConfigurator +} from 'modules/fledgeForGpt.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; @@ -173,5 +178,29 @@ describe('fledgeForGpt module', () => { sinon.assert.calledWith(setGptConfig, au, config?.componentAuctions ?? [], true); }) }); + }); + + describe('getPAAPISizeHook', () => { + let next; + beforeEach(() => { + next = sinon.stub(); + next.bail = sinon.stub(); + }); + + it('should pick largest supported size over larger unsupported size', () => { + getPAAPISizeHook(next, [[999, 999], [300, 250], [300, 600], [1234, 4321]]); + sinon.assert.calledWith(next.bail, [300, 600]); + }); + + Object.entries({ + 'present': [], + 'supported': [[123, 4], [321, 5]], + 'defined': undefined, + }).forEach(([t, sizes]) => { + it(`should defer to next when no size is ${t}`, () => { + getPAAPISizeHook(next, sizes); + sinon.assert.calledWith(next, sizes); + }) + }) }) }); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index bc1faa23833..768e2ba8853 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -2,31 +2,32 @@ import {expect} from 'chai'; import {config} from '../../../src/config.js'; import adapterManager from '../../../src/adapterManager.js'; import * as utils from '../../../src/utils.js'; +import {deepAccess, deepClone} from '../../../src/utils.js'; import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import { addPaapiConfigHook, + buyersToAuctionConfigs, getPAAPIConfig, - parseExtPrebidFledge, - registerSubmodule, - setImpExtAe, - setResponsePaapiConfigs, - reset, - parseExtIgi, - mergeBuyers, + getPAAPISize, IGB_TO_CONFIG, + mergeBuyers, + parseExtIgi, + parseExtPrebidFledge, partitionBuyers, partitionBuyersByBidder, - buyersToAuctionConfigs + registerSubmodule, + reset, + setImpExtAe, + setResponsePaapiConfigs } from 'modules/paapi.js'; import * as events from 'src/events.js'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {AuctionIndex} from '../../../src/auctionIndex.js'; -import {deepAccess, deepClone} from '../../../src/utils.js'; describe('paapi module', () => { let sandbox; @@ -44,6 +45,24 @@ describe('paapi module', () => { 'paapi' ].forEach(configNS => { describe(`using ${configNS} for configuration`, () => { + let getPAAPISizeStub; + + function getPAAPISizeHook(next, sizes) { + next.bail(getPAAPISizeStub(sizes)); + } + + before(() => { + getPAAPISize.before(getPAAPISizeHook, 100); + }); + + after(() => { + getPAAPISize.getHooks({hook: getPAAPISizeHook}).remove(); + }); + + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + }); + describe('getPAAPIConfig', function () { let nextFnSpy, auctionConfig, paapiConfig; before(() => { @@ -56,7 +75,7 @@ describe('paapi module', () => { }; paapiConfig = { config: auctionConfig - } + }; nextFnSpy = sinon.spy(); }); @@ -80,11 +99,11 @@ describe('paapi module', () => { }; igb2 = { origin: 'buyer2' - } + }; buyerAuctionConfig = { seller: 'seller', decisionLogicURL: 'seller-decision-logic' - } + }; config.mergeConfig({ [configNS]: { componentSeller: { @@ -106,14 +125,14 @@ describe('paapi module', () => { sinon.assert.match(buyerConfig, { interestGroupBuyers: [igb1.origin, igb2.origin], ...buyerAuctionConfig - }) + }); }); describe('FPD', () => { let ortb2, ortb2Imp; beforeEach(() => { ortb2 = {'fpd': 1}; - ortb2Imp = {'fpd': 2} + ortb2Imp = {'fpd': 2}; }); function getBuyerAuctionConfig() { @@ -128,7 +147,7 @@ describe('paapi module', () => { ortb2, ortb2Imp } - }) + }); }); it('should not override existing perBuyerSignals', () => { @@ -139,11 +158,11 @@ describe('paapi module', () => { }; igb1.pbs = { prebid: deepClone(original) - } + }; sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { prebid: original - }) - }) + }); + }); }); }); @@ -187,11 +206,11 @@ describe('paapi module', () => { const cfg = getPAAPIConfig({}, true); expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); expect(cfg.au3).to.eql(null); - }) + }); it('includes the targeted adUnit', () => { expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ au3: null - }) + }); }); it('includes the targeted auction', () => { const cfg = getPAAPIConfig({auctionId}, true); @@ -203,41 +222,17 @@ describe('paapi module', () => { }); it('does not include non-existing auctions', () => { expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); - }) + }); }); }); it('should drop auction configs after end of auction', () => { - events.emit(EVENTS.AUCTION_END, { auctionId }); + events.emit(EVENTS.AUCTION_END, {auctionId}); addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, { auctionId }); + events.emit(EVENTS.AUCTION_END, {auctionId}); expect(getPAAPIConfig({auctionId})).to.eql({}); }); - it('should use first size as requestedSize', () => { - addPaapiConfigHook(nextFnSpy, { - auctionId, - adUnitCode: 'au1', - }, paapiConfig); - events.emit(EVENTS.AUCTION_END, { - auctionId, - adUnits: [ - { - code: 'au1', - mediaTypes: { - banner: { - sizes: [[200, 100], [300, 200]] - } - } - } - ] - }); - expect(getPAAPIConfig({auctionId}).au1.requestedSize).to.eql({ - width: '200', - height: '100' - }) - }) - describe('FPD', () => { let ortb2, ortb2Imp; beforeEach(() => { @@ -259,17 +254,17 @@ describe('paapi module', () => { it('should be added to auctionSignals', () => { sinon.assert.match(getComponentAuctionConfig().auctionSignals, { prebid: {ortb2, ortb2Imp} - }) + }); }); it('should not override existing auctionSignals', () => { - auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}} + auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}}; sinon.assert.match(getComponentAuctionConfig().auctionSignals, { prebid: { ortb2: {fpd: 'original'}, ortb2Imp } - }) - }) + }); + }); it('should be added to perBuyerSignals', () => { auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; @@ -277,7 +272,7 @@ describe('paapi module', () => { sinon.assert.match(pbs, { buyer1: {prebid: {ortb2, ortb2Imp}}, buyer2: {prebid: {ortb2, ortb2Imp}} - }) + }); }); it('should not override existing perBuyerSignals', () => { @@ -288,13 +283,13 @@ describe('paapi module', () => { fpd: 'original' } } - } + }; auctionConfig.perBuyerSignals = { buyer: deepClone(original) - } + }; sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); }); - }) + }); describe('submodules', () => { let submods; @@ -309,7 +304,7 @@ describe('paapi module', () => { describe('onAuctionConfig', () => { const auctionId = 'aid'; it('is invoked with null configs when there\'s no config', () => { - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au'] }); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); }); it('is invoked with relevant configs', () => { @@ -321,7 +316,7 @@ describe('paapi module', () => { au1: {componentAuctions: [auctionConfig]}, au2: {componentAuctions: [auctionConfig]}, au3: null - }) + }); }); }); it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { @@ -336,7 +331,7 @@ describe('paapi module', () => { addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); expect(getPAAPIConfig()).to.not.be.empty; - }) + }); }); }); @@ -450,6 +445,65 @@ describe('paapi module', () => { }); }); }); + + describe('requestedSize', () => { + let adUnit; + beforeEach(() => { + adUnit = { + code: 'au', + }; + }); + + function getConfig() { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: adUnit.code}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: [adUnit.code], adUnits: [adUnit]}); + return getPAAPIConfig()[adUnit.code]; + } + + Object.entries({ + 'adUnit.ortb2Imp.ext.paapi.requestedSize'() { + adUnit.ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 + } + } + } + }; + }, + 'largest size'() { + getPAAPISizeStub.returns([123, 321]); + } + }).forEach(([t, setup]) => { + describe(`should be set from ${t}`, () => { + beforeEach(setup); + + it('without overriding component auctions, if set', () => { + auctionConfig.requestedSize = {width: '1px', height: '2px'}; + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: '1px', + height: '2px' + }); + }); + + it('on component auction, if missing', () => { + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: 123, + height: 321 + }); + }); + + it('on top level auction', () => { + expect(getConfig().requestedSize).to.eql({ + width: 123, + height: 321, + }); + }); + }); + }); + }); }); describe('with multiple auctions', () => { @@ -485,7 +539,7 @@ describe('paapi module', () => { configs[auctionId][adUnitCode] = cfg; addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); }); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes) }); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); }); }); @@ -553,15 +607,16 @@ describe('paapi module', () => { } else { expect(cfg[au]).to.be.null; } - }) - }) - }) + }); + }); + }); }); }); }); describe('markForFledge', function () { const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + let adUnits; before(function () { // navigator.runAdAuction & co may not exist, so we can't stub it normally with @@ -577,27 +632,30 @@ describe('paapi module', () => { Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); }); + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + }); + afterEach(function () { config.resetConfig(); }); - const adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, - }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - function mark() { return Object.fromEntries( adapterManager.makeBidRequests( @@ -628,7 +686,7 @@ describe('paapi module', () => { biddable: 1 }); } - }) + }); } describe('with setBidderConfig()', () => { @@ -693,8 +751,8 @@ describe('paapi module', () => { } }); Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); - }) - }) + }); + }); it('should not override pub-defined ext.ae', () => { config.setConfig({ @@ -717,7 +775,7 @@ describe('paapi module', () => { }); Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); - }) + }); it('should not override pub-defined ext.igs', () => { config.setConfig({ @@ -734,8 +792,8 @@ describe('paapi module', () => { ae: 1, biddable: 0 } - }) - }) + }); + }); }); it('should fill ext.ae from ext.igs, if defined', () => { @@ -745,7 +803,61 @@ describe('paapi module', () => { } }); Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}) + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + }); + }); + + describe('ortb2Imp.ext.paapi.requestedSize', () => { + beforeEach(() => { + config.setConfig({ + [configNS]: { + enabled: true, + defaultForSlots: 1, + } + }); + }); + + it('should default to value returned by getPAAPISize', () => { + getPAAPISizeStub.returns([123, 321]); + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + requestedSize: { + width: 123, + height: 321 + } + }); + }); + }); + + it('should not be overridden, if provided by the pub', () => { + adUnits[0].ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: '123px', + height: '321px' + } + } + } + }; + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + requestedSize: { + width: '123px', + height: '321px' + } + }); + }); + sinon.assert.notCalled(getPAAPISizeStub); + }); + + it('should not be set if adUnit has no banner sizes', () => { + adUnits[0].mediaTypes = { + video: {} + }; + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + expect(bidRequest.ortb2Imp?.ext?.paapi?.requestedSize).to.not.exist; + }); }); }); }); @@ -778,7 +890,7 @@ describe('paapi module', () => { ps: { priority: 2 } - } + }; }); describe('mergeBuyers', () => { @@ -831,7 +943,7 @@ describe('paapi module', () => { it('ignores igbs with duplicate origin', () => { igb2.origin = igb1.origin; expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb1])); - }) + }); }); describe('partitionBuyers', () => { @@ -841,7 +953,7 @@ describe('paapi module', () => { it('should ignore igbs that have no origin', () => { delete igb1.origin; expect(partitionBuyers([igb1, igb2])).to.eql([[igb2]]); - }) + }); it('should return a single partition when duplicates exist, but do not conflict', () => { expect(partitionBuyers([igb1, igb2, deepClone(igb1)])).to.eql([[igb1, igb2]]); }); @@ -855,7 +967,7 @@ describe('paapi module', () => { [igb3], [igb4] ]); - }) + }); }); describe('partitionBuyersByBidder', () => { @@ -875,8 +987,8 @@ describe('paapi module', () => { ])).to.eql([ [{bidder: 'a', extra: 'data'}, [igb1, igb2]], [{bidder: 'b', more: 'data'}, [igb1, igb2]] - ]) - }) + ]); + }); describe('buyersToAuctionConfig', () => { let config, partitioners, merge, igbRequests; beforeEach(() => { @@ -884,11 +996,11 @@ describe('paapi module', () => { auctionConfig: { decisionLogicURL: 'mock-decision-logic' } - } + }; partitioners = { compact: sinon.stub(), expand: sinon.stub(), - } + }; let i = 0; merge = sinon.stub().callsFake(() => ({config: i++})); igbRequests = [ @@ -911,7 +1023,7 @@ describe('paapi module', () => { sinon.assert.match(cf2, { ...config.auctionConfig, config: 1 - }) + }); sinon.assert.calledWith(partitioners.compact, igbRequests); [1, 2].forEach(mockPart => sinon.assert.calledWith(merge, mockPart)); }); @@ -933,13 +1045,42 @@ describe('paapi module', () => { const fpd = { ortb2: {fpd: 1}, ortb2Imp: {fpd: 2} - } + }; partitioners.compact.returns([[{}], [fpd]]); const [cf1, cf2] = toAuctionConfig(); expect(cf1.auctionSignals?.prebid).to.not.exist; expect(cf2.auctionSignals.prebid).to.eql(fpd); - }) - }) + }); + }); + }); + }); + + describe('getPAAPISize', () => { + before(() => { + getPAAPISize.getHooks().remove(); + }); + + Object.entries({ + 'ignores placeholders': { + in: [[1, 1], [0, 0], [3, 4]], + out: [3, 4] + }, + 'picks largest size by area': { + in: [[200, 100], [150, 150]], + out: [150, 150] + }, + 'can handle no sizes': { + in: [], + out: undefined + }, + 'can handle no input': { + in: undefined, + out: undefined + } + }).forEach(([t, {in: input, out}]) => { + it(t, () => { + expect(getPAAPISize(input)).to.eql(out); + }); }); }); @@ -958,7 +1099,7 @@ describe('paapi module', () => { igs: { biddable: 0 } - }) + }); }); describe('response parsing', () => { @@ -1001,7 +1142,7 @@ describe('paapi module', () => { igs: configs }] } - } + }; }, 'ext.igi.igs with impid on igi'(configs) { return { @@ -1012,10 +1153,10 @@ describe('paapi module', () => { return { impid, igs: [cfg] - } + }; }) } - } + }; }, 'ext.igi.igs with conflicting impid'(configs) { return { @@ -1025,7 +1166,7 @@ describe('paapi module', () => { igs: configs }] } - } + }; } } } @@ -1058,9 +1199,9 @@ describe('paapi module', () => { parser({}, resp, ctx); expect(extractResult('config', ctx.impContext)).to.eql({}); }); - }) - }) - }) + }); + }); + }); }); describe('response ext.igi.igb', () => { @@ -1092,15 +1233,15 @@ describe('paapi module', () => { } ] } - } + }; parseExtIgi({}, resp, ctx); expect(extractResult('igb', ctx.impContext)).to.eql({ e1: [1, 2], e2: [3], }); - }) - }) - }) + }); + }); + }); describe('setResponsePaapiConfigs', () => { it('should set paapi configs/igb paired with their corresponding bid id', () => { diff --git a/test/spec/modules/topLevelPaapi_spec.js b/test/spec/modules/topLevelPaapi_spec.js new file mode 100644 index 00000000000..e2cad9593e9 --- /dev/null +++ b/test/spec/modules/topLevelPaapi_spec.js @@ -0,0 +1,502 @@ +import { + addPaapiConfigHook, + getPAAPIConfig, + registerSubmodule, + reset as resetPaapi +} from '../../../modules/paapi.js'; +import {config} from 'src/config.js'; +import {BID_STATUS, EVENTS} from 'src/constants.js'; +import * as events from 'src/events.js'; +import { + getPaapiAdId, + getPAAPIBids, + getRenderingDataHook, markWinningBidHook, + parsePaapiAdId, + parsePaapiSize, resizeCreativeHook, + topLevelPAAPI +} from '/modules/topLevelPaapi.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {expect} from 'chai/index.js'; +import {getBidToRender} from '../../../src/adRendering.js'; + +describe('topLevelPaapi', () => { + let sandbox, auctionConfig, next, auctionId, auctions; + before(() => { + resetPaapi(); + }); + beforeEach(() => { + registerSubmodule(topLevelPAAPI); + }); + afterEach(() => { + resetPaapi(); + }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + auctions = {}; + sandbox.stub(auctionManager.index, 'getAuction').callsFake(({auctionId}) => auctions[auctionId]?.auction); + next = sinon.stub(); + auctionId = 'auct'; + auctionConfig = { + seller: 'mock.seller' + }; + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1 + } + }); + }); + afterEach(() => { + config.resetConfig(); + sandbox.restore(); + }); + + function addPaapiConfig(adUnitCode, auctionConfig, _auctionId = auctionId) { + let auction = auctions[_auctionId]; + if (!auction) { + auction = auctions[_auctionId] = { + auction: {}, + adUnits: {} + }; + } + if (!auction.adUnits.hasOwnProperty(adUnitCode)) { + auction.adUnits[adUnitCode] = { + code: adUnitCode, + ortb2Imp: { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 + } + } + } + } + }; + } + addPaapiConfigHook(next, {adUnitCode, auctionId: _auctionId}, { + config: { + ...auctionConfig, + auctionId: _auctionId, + adUnitCode + } + }); + } + + function endAuctions() { + Object.entries(auctions).forEach(([auctionId, {adUnits}]) => { + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: Object.keys(adUnits), adUnits: Object.values(adUnits)}); + }); + } + + describe('when configured', () => { + let auctionConfig; + beforeEach(() => { + auctionConfig = { + seller: 'top.seller', + decisionLogicURL: 'https://top.seller/decision-logic.js' + }; + config.mergeConfig({ + paapi: { + topLevelSeller: { + auctionConfig, + autorun: false + } + } + }); + }); + + it('should augment config returned by getPAAPIConfig', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + sinon.assert.match(getPAAPIConfig().au, auctionConfig); + }); + + it('should not choke if auction config is not defined', () => { + const cfg = config.getConfig('paapi'); + delete cfg.topLevelSeller.auctionConfig; + config.setConfig(cfg); + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig().au.componentAuctions).to.exist; + }); + + it('should default resolveToConfig: false', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig()['au'].resolveToConfig).to.eql(false); + }); + + describe('when autoRun is set', () => { + let origRaa; + beforeEach(() => { + origRaa = navigator.runAdAuction; + navigator.runAdAuction = sinon.stub(); + }); + afterEach(() => { + navigator.runAdAuction = origRaa; + }); + + it('should start auctions automatically, when autoRun is set', () => { + config.mergeConfig({ + paapi: { + topLevelSeller: { + autorun: true + } + } + }) + addPaapiConfig('au', auctionConfig); + endAuctions(); + sinon.assert.called(navigator.runAdAuction); + }); + }); + + describe('getPAAPIBids', () => { + Object.entries({ + 'a string URN': { + pack: (val) => val, + unpack: (urn) => ({urn}), + canRender: true, + }, + 'a frameConfig object': { + pack: (val) => ({val}), + unpack: (val) => ({frameConfig: {val}}), + canRender: false + } + }).forEach(([t, {pack, unpack, canRender}]) => { + describe(`when runAdAuction returns ${t}`, () => { + let raa; + beforeEach(() => { + raa = sinon.stub().callsFake((cfg) => { + const {auctionId, adUnitCode} = cfg.componentAuctions[0]; + return Promise.resolve(pack(`raa-${adUnitCode}-${auctionId}`)); + }); + }); + + function getBids(filters) { + return getPAAPIBids(filters, raa); + } + + function expectBids(actual, expected) { + expect(Object.keys(actual)).to.eql(Object.keys(expected)); + Object.entries(expected).forEach(([au, val]) => { + sinon.assert.match(actual[au], val == null ? val : { + adId: sinon.match(val => parsePaapiAdId(val)[1] === au), + width: 123, + height: 321, + source: 'paapi', + ...unpack(val) + }); + }); + } + + describe('with one auction config', () => { + beforeEach(() => { + addPaapiConfig('au', auctionConfig, 'auct'); + endAuctions(); + }); + it('should resolve to raa result', () => { + return getBids({adUnitCode: 'au', auctionId}).then(result => { + sinon.assert.calledWith(raa, sinon.match({ + ...auctionConfig, + componentAuctions: sinon.match(cmp => cmp.find(cfg => sinon.match(cfg, auctionConfig))) + })); + expectBids(result, {au: 'raa-au-auct'}); + }); + }); + + Object.entries({ + 'returns null': () => Promise.resolve(), + 'throws': () => { throw new Error() }, + 'rejects': () => Promise.reject(new Error()) + }).forEach(([t, behavior]) => { + it('should resolve to null when runAdAuction returns null', () => { + raa = sinon.stub().callsFake(behavior); + return getBids({adUnitCode: 'au', auctionId: 'auct'}).then(result => { + expectBids(result, {au: null}); + }); + }); + }) + + it('should resolve to the same result when called again', () => { + getBids({adUnitCode: 'au', auctionId}); + return getBids({adUnitCode: 'au', auctionId: 'auct'}).then(result => { + sinon.assert.calledOnce(raa); + expectBids(result, {au: 'raa-au-auct'}); + }); + }); + + describe('events', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + it('should fire PAAPI_RUN_AUCTION', () => { + return Promise.all([ + getBids({adUnitCode: 'au', auctionId}), + getBids({adUnitCode: 'other', auctionId}) + ]).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { + adUnitCode: 'au', + auctionId, + auctionConfig: sinon.match(auctionConfig) + }); + sinon.assert.neverCalledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { + adUnitCode: 'other' + }); + }); + }); + it('should fire PAAPI_BID', () => { + return getBids({adUnitCode: 'au', auctionId}).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_BID, sinon.match({ + ...unpack('raa-au-auct'), + adUnitCode: 'au', + auctionId: 'auct' + })); + }); + }); + it('should fire PAAPI_NO_BID', () => { + raa = sinon.stub().callsFake(() => Promise.resolve(null)); + return getBids({adUnitCode: 'au', auctionId}).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_NO_BID, sinon.match({ + adUnitCode: 'au', + auctionId: 'auct' + })); + }); + }); + + it('should fire PAAPI_ERROR', () => { + raa = sinon.stub().callsFake(() => Promise.reject(new Error('message'))); + return getBids({adUnitCode: 'au', auctionId}).then(res => { + expect(res).to.eql({au: null}); + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_ERROR, sinon.match({ + adUnitCode: 'au', + auctionId: 'auct', + error: sinon.match({message: 'message'}) + })); + }); + }); + }); + + it('should hook into getBidToRender', () => { + return getBids({adUnitCode: 'au', auctionId}).then(res => { + return getBidToRender(res.au.adId).then(bidToRender => [res.au, bidToRender]) + }).then(([paapiBid, bidToRender]) => { + if (canRender) { + expect(bidToRender).to.eql(paapiBid) + } else { + expect(bidToRender).to.not.exist; + } + }); + }); + + describe('when overrideWinner is set', () => { + let mockContextual; + beforeEach(() => { + mockContextual = { + adId: 'mock', + adUnitCode: 'au' + } + sandbox.stub(auctionManager, 'findBidByAdId').returns(mockContextual); + config.mergeConfig({ + paapi: { + topLevelSeller: { + overrideWinner: true + } + } + }); + }); + + it(`should ${!canRender ? 'NOT' : ''} override winning bid for the same adUnit`, () => { + return Promise.all([ + getBids({adUnitCode: 'au', auctionId}).then(res => res.au), + getBidToRender(mockContextual.adId) + ]).then(([paapiBid, bidToRender]) => { + if (canRender) { + expect(bidToRender).to.eql(paapiBid); + expect(paapiBid.overriddenAdId).to.eql(mockContextual.adId); + } else { + expect(bidToRender).to.eql(mockContextual) + } + }) + }); + + it('should not override when the ad unit has no paapi winner', () => { + mockContextual.adUnitCode = 'other'; + return getBidToRender(mockContextual.adId).then(bidToRender => { + expect(bidToRender).to.eql(mockContextual); + }) + }); + + it('should not override when already a paapi bid', () => { + return getBids({adUnitCode: 'au', auctionId}).then(res => { + return getBidToRender(res.au.adId).then((bidToRender) => [bidToRender, res.au]); + }).then(([bidToRender, paapiBid]) => { + expect(bidToRender).to.eql(canRender ? paapiBid : mockContextual) + }) + }); + + if (canRender) { + it('should not not override when the bid was already rendered', () => { + getBids(); + return getBidToRender(mockContextual.adId).then((bid) => { + // first pass - paapi wins over contextual + expect(bid.source).to.eql('paapi'); + bid.status = BID_STATUS.RENDERED; + return getBidToRender(mockContextual.adId, false).then(bidToRender => [bid, bidToRender]) + }).then(([paapiBid, bidToRender]) => { + // if `forRender` = false (bit retrieved for x-domain events and such) + // the referenced bid is still paapi + expect(bidToRender).to.eql(paapiBid); + return getBidToRender(mockContextual.adId); + }).then(bidToRender => { + // second pass, paapi has been rendered, contextual should win + expect(bidToRender).to.eql(mockContextual); + bidToRender.status = BID_STATUS.RENDERED; + return getBidToRender(mockContextual.adId, false); + }).then(bidToRender => { + // if the contextual bid has been rendered, it's the one being referenced + expect(bidToRender).to.eql(mockContextual); + }); + }) + } + }); + }); + + it('should resolve the same result from different filters', () => { + const targets = { + auct1: ['au1', 'au2'], + auct2: ['au1', 'au3'] + }; + Object.entries(targets).forEach(([auctionId, adUnitCodes]) => { + adUnitCodes.forEach(au => addPaapiConfig(au, auctionConfig, auctionId)); + }); + endAuctions(); + return Promise.all( + [ + [ + {adUnitCode: 'au1', auctionId: 'auct1'}, + { + au1: 'raa-au1-auct1' + } + ], + [ + {}, + { + au1: 'raa-au1-auct2', + au2: 'raa-au2-auct1', + au3: 'raa-au3-auct2' + } + ], + [ + {auctionId: 'auct1'}, + { + au1: 'raa-au1-auct1', + au2: 'raa-au2-auct1' + } + ], + [ + {adUnitCode: 'au1'}, + { + au1: 'raa-au1-auct2' + } + ], + ].map(([filters, expected]) => getBids(filters).then(res => [res, expected])) + ).then(res => { + res.forEach(([actual, expected]) => { + expectBids(actual, expected); + }); + }); + }); + }); + }); + }); + }); + + describe('when not configured', () => { + it('should not alter configs returned by getPAAPIConfig', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig().au.seller).to.not.exist; + }); + }); + + describe('paapi adId', () => { + [ + ['auctionId', 'adUnitCode'], + ['auction:id', 'adUnit:code'], + ['auction:uid', 'ad:unit'] + ].forEach(([auctionId, adUnitCode]) => { + it(`can encode and decode ${auctionId}, ${adUnitCode}`, () => { + expect(parsePaapiAdId(getPaapiAdId(auctionId, adUnitCode))).to.eql([auctionId, adUnitCode]); + }); + }); + + [undefined, null, 'not-a-paapi-ad', 'paapi:/malformed'].forEach(adId => { + it(`returns null for adId ${adId}`, () => { + expect(parsePaapiAdId(adId)).to.not.exist; + }); + }); + }); + + describe('parsePaapiSize', () => { + [ + [null, null], + [undefined, null], + [123, 123], + ['123', 123], + ['123px', 123], + ['1sw', null], + ['garbage', null] + ].forEach(([input, expected]) => { + it(`can parse ${input} => ${expected}`, () => { + expect(parsePaapiSize(input)).to.eql(expected); + }); + }); + }); + + describe('rendering hooks', () => { + let next; + beforeEach(() => { + next = sinon.stub() + next.bail = sinon.stub() + }); + describe('getRenderingDataHook', () => { + it('intercepts paapi bids', () => { + getRenderingDataHook(next, { + source: 'paapi', + width: 123, + height: null, + urn: 'url' + }); + sinon.assert.calledWith(next.bail, { + width: 123, + height: null, + adUrl: 'url' + }); + }); + it('does not touch non-paapi bids', () => { + getRenderingDataHook(next, {bid: 'data'}, {other: 'options'}); + sinon.assert.calledWith(next, {bid: 'data'}, {other: 'options'}); + }); + }); + + describe('markWinnigBidsHook', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + it('handles paapi bids', () => { + const bid = {source: 'paapi'}; + markWinningBidHook(next, bid); + sinon.assert.notCalled(next); + sinon.assert.called(next.bail); + expect(bid.status).to.eql(BID_STATUS.RENDERED); + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bid); + }); + it('ignores non-paapi bids', () => { + markWinningBidHook(next, {other: 'bid'}); + sinon.assert.calledWith(next, {other: 'bid'}); + sinon.assert.notCalled(next.bail); + }); + }); + }); +}); diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index df837e5547e..4d0962a0b2c 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -1,7 +1,7 @@ import * as events from 'src/events.js'; import * as utils from 'src/utils.js'; import { - doRender, + doRender, getBidToRender, getRenderingData, handleCreativeEvent, handleNativeMessage, @@ -24,6 +24,28 @@ describe('adRendering', () => { sandbox.restore(); }) + describe('getBidToRender', () => { + beforeEach(() => { + sandbox.stub(auctionManager, 'findBidByAdId').callsFake(() => 'auction-bid') + }); + it('should default to bid from auctionManager', () => { + return getBidToRender('adId', true, Promise.resolve(null)).then((res) => { + expect(res).to.eql('auction-bid'); + sinon.assert.calledWith(auctionManager.findBidByAdId, 'adId'); + }); + }); + it('should give precedence to override promise', () => { + return getBidToRender('adId', true, Promise.resolve('override')).then((res) => { + expect(res).to.eql('override'); + sinon.assert.notCalled(auctionManager.findBidByAdId); + }) + }); + it('should return undef when override rejects', () => { + return getBidToRender('adId', true, Promise.reject(new Error('any reason'))).then(res => { + expect(res).to.not.exist; + }) + }) + }) describe('getRenderingData', () => { let bidResponse; beforeEach(() => { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index deb80873cfa..94643f34a05 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -28,6 +28,7 @@ import {mockFpdEnrichments} from '../../helpers/fpd.js'; import {generateUUID} from '../../../src/utils.js'; import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; import { BID_STATUS, EVENTS, GRANULARITY_OPTIONS, TARGETING_KEYS } from 'src/constants.js'; +import {getBidToRender} from '../../../src/adRendering.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -201,12 +202,16 @@ window.apntag = { describe('Unit: Prebid Module', function () { let bidExpiryStub, sandbox; - + function getBidToRenderHook(next, adId) { + // make sure we can handle async bidToRender + next(adId, new Promise((resolve) => setTimeout(resolve))) + } before((done) => { hook.ready(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); resetDebugging(); sinon.stub(filters, 'isActualBid').returns(true); // stub this out so that we can use vanilla objects as bids + getBidToRender.before(getBidToRenderHook, 100); // preload creative renderer getCreativeRenderer({}).then(() => done()); }); @@ -229,6 +234,7 @@ describe('Unit: Prebid Module', function () { after(function() { auctionManager.clearAllAuctions(); filters.isActualBid.restore(); + getBidToRender.getHooks({hook: getBidToRenderHook}).remove(); }); describe('and global adUnits', () => { @@ -1246,16 +1252,25 @@ describe('Unit: Prebid Module', function () { spyAddWinningBid.restore(); }); + function renderAd(...args) { + $$PREBID_GLOBAL$$.renderAd(...args); + return new Promise((resolve) => { + setTimeout(resolve, 10); + }); + } + it('should require doc and id params', function () { - $$PREBID_GLOBAL$$.renderAd(); - var error = 'Error rendering ad (id: undefined): missing adId'; - assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); + return renderAd().then(() => { + var error = 'Error rendering ad (id: undefined): missing adId'; + assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); + }) }); it('should log message with bid id', function () { - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var message = 'Calling renderAd with adId :' + bidId; - assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); + return renderAd(doc, bidId).then(() => { + var message = 'Calling renderAd with adId :' + bidId; + assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); + }) }); it('should write the ad to the doc', function () { @@ -1263,23 +1278,26 @@ describe('Unit: Prebid Module', function () { ad: "" }); adResponse.ad = ""; - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); - assert.ok(doc.close.called, 'close method called'); + return renderAd(doc, bidId).then(() => { + assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); + assert.ok(doc.close.called, 'close method called'); + }) }); it('should place the url inside an iframe on the doc', function () { pushBidResponseToAuction({ adUrl: 'http://server.example.com/ad/ad.js' }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(doc.createElement, 'iframe'); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(doc.createElement, 'iframe'); + }); }); it('should log an error when no ad or url', function () { pushBidResponseToAuction({}); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.called(spyLogError); + return renderAd(doc, bidId).then(() => { + sinon.assert.called(spyLogError); + }); }); it('should log an error when not in an iFrame', function () { @@ -1287,17 +1305,19 @@ describe('Unit: Prebid Module', function () { ad: "" }); inIframe = false; - $$PREBID_GLOBAL$$.renderAd(document, bidId); - const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + return renderAd(document, bidId).then(() => { + const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); }); it('should not render videos', function () { pushBidResponseToAuction({ mediatype: 'video' }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.notCalled(doc.write); + return renderAd(doc, bidId).then(() => { + sinon.assert.notCalled(doc.write); + }); }); it('should catch errors thrown when trying to write ads to the page', function () { @@ -1307,25 +1327,28 @@ describe('Unit: Prebid Module', function () { var error = { message: 'doc write error' }; doc.write = sinon.stub().throws(error); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` - assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + return renderAd(doc, bidId).then(() => { + var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + }); }); it('should log an error when ad not found', function () { var fakeId = 99; - $$PREBID_GLOBAL$$.renderAd(doc, fakeId); - var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + return renderAd(doc, fakeId).then(() => { + var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); }); it('should save bid displayed to winning bid', function () { pushBidResponseToAuction({ ad: "" }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); + return renderAd(doc, bidId).then(() => { + assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); + }); }); it('fires billing url if present on s2s bid', function () { @@ -1336,22 +1359,23 @@ describe('Unit: Prebid Module', function () { burl }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - - sinon.assert.calledOnce(triggerPixelStub); - sinon.assert.calledWith(triggerPixelStub, burl); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, burl); + }); }); it('should call addWinningBid', function () { pushBidResponseToAuction({ ad: "" }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var message = 'Calling renderAd with adId :' + bidId; - sinon.assert.calledWith(spyLogMessage, message); + return renderAd(doc, bidId).then(() => { + var message = 'Calling renderAd with adId :' + bidId; + sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + }); }); it('should warn stale rendering', function () { @@ -1368,38 +1392,40 @@ describe('Unit: Prebid Module', function () { }); // First render should pass with no warning and added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.neverCalledWith(spyLogWarn, warning); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.notCalled(onStaleEvent); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - // Reset call history for spies and stubs - spyLogMessage.resetHistory(); - spyLogWarn.resetHistory(); - spyAddWinningBid.resetHistory(); - onWonEvent.resetHistory(); - onStaleEvent.resetHistory(); + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); - // Second render should have a warning but still added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledWith(spyLogWarn, warning); + // Second render should have a warning but still added to winning bids + return renderAd(doc, bidId); + }).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.calledWith(onStaleEvent, adResponse); + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.calledWith(onStaleEvent, adResponse); - // Clean up - $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + // Clean up + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + }); }); it('should stop stale rendering', function () { @@ -1419,38 +1445,40 @@ describe('Unit: Prebid Module', function () { }); // First render should pass with no warning and added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.neverCalledWith(spyLogWarn, warning); - - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.notCalled(onStaleEvent); - - // Reset call history for spies and stubs - spyLogMessage.resetHistory(); - spyLogWarn.resetHistory(); - spyAddWinningBid.resetHistory(); - onWonEvent.resetHistory(); - onStaleEvent.resetHistory(); - - // Second render should have a warning and do not proceed further - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledWith(spyLogWarn, warning); - - sinon.assert.notCalled(spyAddWinningBid); - - sinon.assert.notCalled(onWonEvent); - sinon.assert.calledWith(onStaleEvent, adResponse); - - // Clean up - $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); - configObj.setConfig({'auctionOptions': {}}); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); + + // Second render should have a warning and do not proceed further + return renderAd(doc, bidId); + }).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); + + sinon.assert.notCalled(spyAddWinningBid); + + sinon.assert.notCalled(onWonEvent); + sinon.assert.calledWith(onStaleEvent, adResponse); + + // Clean up + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + configObj.setConfig({'auctionOptions': {}}); + }); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 189066f7f88..664ba51ff1f 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -13,11 +13,23 @@ import 'modules/nativeRendering.js'; import {expect} from 'chai'; -import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; +import {AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS} from 'src/constants.js'; +import {getBidToRender} from '../../../src/adRendering.js'; describe('secureCreatives', () => { let sandbox; + function getBidToRenderHook(next, adId) { + // make sure that bids can be retrieved asynchronously + next(adId, new Promise((resolve) => setTimeout(resolve))) + } + before(() => { + getBidToRender.before(getBidToRenderHook); + }); + after(() => { + getBidToRender.getHooks({hook: getBidToRenderHook}).remove() + }); + beforeEach(() => { sandbox = sinon.sandbox.create(); }); @@ -30,6 +42,10 @@ describe('secureCreatives', () => { return Object.assign({origin: 'mock-origin', ports: []}, ev) } + function receive(ev) { + return Promise.resolve(receiveMessage(ev)); + } + describe('getReplier', () => { it('should use source.postMessage if no MessagePort is available', () => { const ev = { @@ -153,17 +169,17 @@ describe('secureCreatives', () => { data: JSON.stringify(data), }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + }); }); it('should allow stale rendering without config', function () { @@ -180,29 +196,26 @@ describe('secureCreatives', () => { data: JSON.stringify(data) }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - resetHistories(adResponse.renderer.render); - - receiveMessage(ev); - - sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + resetHistories(adResponse.renderer.render); + return receive(ev); + }).then(() => { + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + }); }); it('should stop stale rendering with config', function () { @@ -221,29 +234,27 @@ describe('secureCreatives', () => { data: JSON.stringify(data) }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - resetHistories(adResponse.renderer.render); - - receiveMessage(ev); - - sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.notCalled(adResponse.renderer.render); - sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); - - configObj.setConfig({'auctionOptions': {}}); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + + resetHistories(adResponse.renderer.render); + return receive(ev) + }).then(() => { + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.notCalled(adResponse.renderer.render); + sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + configObj.setConfig({'auctionOptions': {}}); + }); }); it('should emit AD_RENDER_FAILED if requested missing adId', () => { @@ -253,11 +264,12 @@ describe('secureCreatives', () => { adId: 'missing' }) }); - receiveMessage(ev); - sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - adId: 'missing' - })); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + adId: 'missing' + })); + }); }); it('should emit AD_RENDER_FAILED if creative can\'t be sent to rendering frame', () => { @@ -271,11 +283,12 @@ describe('secureCreatives', () => { adId: bidId }) }); - receiveMessage(ev) - sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: AD_RENDER_FAILED_REASON.EXCEPTION, - adId: bidId - })); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.EXCEPTION, + adId: bidId + })); + }) }); it('should include renderers in responses', () => { @@ -287,8 +300,9 @@ describe('secureCreatives', () => { }, data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) }); - receiveMessage(ev); - sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + return receive(ev).then(() => { + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + }); }); if (FEATURES.NATIVE) { @@ -318,23 +332,24 @@ describe('secureCreatives', () => { }, data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) }) - receiveMessage(ev); - sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { - const data = JSON.parse(ob); - ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); - const native = data.native; - sinon.assert.match(native, { - ortb: bid.native.ortb, - adTemplate: bid.native.adTemplate, - rendererUrl: bid.native.rendererUrl, - }) - expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ - adTemplate: bid.native.adTemplate, - rendererUrl: bid.native.rendererUrl, - body: 'vbody' - }); - return true; - })) + return receive(ev).then(() => { + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { + const data = JSON.parse(ob); + ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); + const native = data.native; + sinon.assert.match(native, { + ortb: bid.native.ortb, + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + }) + expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + body: 'vbody' + }); + return true; + })) + }); }) } }); @@ -361,16 +376,16 @@ describe('secureCreatives', () => { origin: 'any origin' }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + }); }); it('Prebid native should not fire BID_WON when receiveMessage is called more than once', () => { @@ -391,11 +406,12 @@ describe('secureCreatives', () => { origin: 'any origin' }); - receiveMessage(ev); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - - receiveMessage(ev); - stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + return receive(ev); + }).then(() => { + stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; + }); }); }); @@ -422,13 +438,14 @@ describe('secureCreatives', () => { }, }) }); - receiveMessage(event); - expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { - adId: bidId, - bid: adResponse, - reason: 'Fail reason', - message: 'Fail message' - })).to.equal(shouldEmit); + return receive(event).then(() => { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { + adId: bidId, + bid: adResponse, + reason: 'Fail reason', + message: 'Fail message' + })).to.equal(shouldEmit); + }); }); it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_SUCCEEDED`, () => { @@ -439,12 +456,13 @@ describe('secureCreatives', () => { adId: bidId, }) }); - receiveMessage(event); - expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { - adId: bidId, - bid: adResponse, - doc: null - })).to.equal(shouldEmit); + return receive(event).then(() => { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { + adId: bidId, + bid: adResponse, + doc: null + })).to.equal(shouldEmit); + }); }); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index fb85b410bd9..d7d7007674a 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,9 +1,8 @@ import {getAdServerTargeting} from 'test/fixtures/fixtures.js'; import {expect} from 'chai'; -import { TARGETING_KEYS } from 'src/constants.js'; +import {TARGETING_KEYS} from 'src/constants.js'; import * as utils from 'src/utils.js'; -import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js'; -import {binarySearch, deepEqual, encodeMacroURI, memoize, waitForElementToLoad} from 'src/utils.js'; +import {binarySearch, deepEqual, encodeMacroURI, memoize, sizesToSizeTuples, waitForElementToLoad} from 'src/utils.js'; import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js'; var assert = require('assert'); @@ -167,6 +166,43 @@ describe('Utils', function () { }); }); + describe('sizesToSizeTuples', () => { + Object.entries({ + 'single size, numerical': { + in: [1, 2], + out: [[1, 2]] + }, + 'single size, numerical, nested': { + in: [[1, 2]], + out: [[1, 2]] + }, + 'multiple sizes, numerical': { + in: [[1, 2], [3, 4]], + out: [[1, 2], [3, 4]] + }, + 'single size, string': { + in: '1x2', + out: [[1, 2]] + }, + 'multiple sizes, string': { + in: '1x2, 4x3', + out: [[1, 2], [4, 3]] + }, + 'incorrect size, numerical': { + in: [1], + out: [] + }, + 'incorrect size, string': { + in: '1x', + out: [] + } + }).forEach(([t, {in: input, out}]) => { + it(`can parse ${t}`, () => { + expect(sizesToSizeTuples(input)).to.eql(out); + }) + }) + }) + describe('parseSizesInput', function () { it('should return query string using multi size array', function () { var sizes = [[728, 90], [970, 90]]; From 61a6ce4a2cb34df1b25ed956eba136ed276a8ee8 Mon Sep 17 00:00:00 2001 From: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Tue, 4 Jun 2024 04:50:02 +0300 Subject: [PATCH 0144/1097] e-Volution Bid Adapter : update bid request validation and added video placement keys (#11561) * updates for Prebid v5 * add id5id * update tests * add gvlid * updated adapter * removed redundant endpointId --- modules/e_volutionBidAdapter.js | 231 +++++++------- .../spec/modules/e_volutionBidAdapter_spec.js | 298 ++++++++++++------ 2 files changed, 331 insertions(+), 198 deletions(-) diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 2ce6cda16d1..26a1f9c5718 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -1,36 +1,80 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { deepAccess, logMessage, logError } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'e_volution'; const GVLID = 957; const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; -const URL_SYNC = 'https://service.e-volution.ai/?c=o&m=sync'; -const NO_SYNC = true; function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { return false; } + switch (bid.mediaType) { case BANNER: return Boolean(bid.width && bid.height && bid.ad); case VIDEO: - return Boolean(bid.vastUrl); + return Boolean(bid.vastUrl || bid.vastXml); case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); default: return false; } } -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; + const schain = bid.schain || {}; + const { placementId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + placementId, + bidId, + schain, + bidfloor + }; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.plcmt = mediaTypes[VIDEO].plcmt; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; } + return placement; +} + +function getBidFloor(bid) { try { const bidFloor = bid.getFloor({ currency: 'USD', @@ -38,21 +82,9 @@ function getBidFloor(bid) { size: '*', }); return bidFloor.floor; - } catch (_) { - return 0 - } -} - -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); + } catch (err) { + logError(err); + return 0; } } @@ -60,92 +92,91 @@ export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - noSync: NO_SYNC, - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && params.placementId); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; }, - buildRequests: (validBidRequests = [], bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest = {}) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; } catch (e) { - location = winTop.location; logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } + winLocation = window.location; } - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - bidfloor: getBidFloor(bid), - eids: [] - }; + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, + tmax: bidderRequest.timeout + }; - if (bid.userId) { - getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); - } + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } - if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - placement.traffic = BANNER; - placement.sizes = bid.mediaTypes[BANNER].sizes; - } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - placement.traffic = VIDEO; - placement.wPlayer = bid.mediaTypes[VIDEO].playerSize[0]; - placement.hPlayer = bid.mediaTypes[VIDEO].playerSize[1]; - placement.minduration = bid.mediaTypes[VIDEO].minduration; - placement.maxduration = bid.mediaTypes[VIDEO].maxduration; - placement.mimes = bid.mediaTypes[VIDEO].mimes; - placement.protocols = bid.mediaTypes[VIDEO].protocols; - placement.startdelay = bid.mediaTypes[VIDEO].startdelay; - placement.placement = bid.mediaTypes[VIDEO].placement; - placement.skip = bid.mediaTypes[VIDEO].skip; - placement.skipafter = bid.mediaTypes[VIDEO].skipafter; - placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate; - placement.delivery = bid.mediaTypes[VIDEO].delivery; - placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod; - placement.api = bid.mediaTypes[VIDEO].api; - placement.linearity = bid.mediaTypes[VIDEO].linearity; - } else if (bid.mediaTypes && bid.mediaTypes[NATIVE]) { - placement.traffic = NATIVE; - placement.native = bid.mediaTypes[NATIVE]; - } + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } - if (bid.schain) { - placements.schain = bid.schain; - } + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } - placements.push(placement); + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); } + return { method: 'POST', url: AD_URL, @@ -165,19 +196,7 @@ export const spec = { } } return response; - }, - - getUserSyncs: (syncOptions, serverResponses) => { - if (NO_SYNC) { - return false - } else { - return [{ - type: 'image', - url: URL_SYNC - }]; - } } - }; registerBidder(spec); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index d488048060a..f257434fd70 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -1,117 +1,239 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/e_volutionBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/e_volutionBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'e_volution'; describe('EvolutionTechBidAdapter', function () { - let bids = [{ - bidId: '23fhj33i987f', - bidder: 'e_volution', - params: { - placementId: 0 - }, - mediaTypes: { - banner: { - sizes: [[300, 250]], + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids }, - userId: { - id5id: 'id5id' - } - }, { - bidId: '23fhj33i987f', - bidder: 'e_volution', - params: { - placementId: 0 + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - video: { - playerSize: [300, 250] + [BANNER]: { + sizes: [[300, 250]] } }, - userId: { - id5id: 'id5id' - } - }, { - bidId: '23fhj33i987f', - bidder: 'e_volution', params: { - placementId: 0 - }, - mediaTypes: { - native: {} - }, - userId: { - id5id: 'id5id' + } - }]; + } const bidderRequest = { - uspConsent: 'uspConsent', - gdprConsent: 'gdprConsent' + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com' + }, + timeout: 500 }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { + it('Should return true if there are bidId, params and key parameters present', function () { expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bids[0].params.placementId; - expect(spec.isBidRequestValid(bids[0])).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://service.e-volution.ai/?c=o&m=multi'); - }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.ccpa).to.be.equal('uspConsent'); - expect(data.gdpr).to.be.equal('gdprConsent'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'bidfloor', 'eids'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - placement = data['placements'][1]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'wPlayer', 'hPlayer', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', - 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('video'); + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); - placement = data['placements'][2]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'native'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('native'); + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; }); }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -128,7 +250,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -137,15 +260,16 @@ describe('EvolutionTechBidAdapter', function () { let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -160,7 +284,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -177,6 +302,7 @@ describe('EvolutionTechBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -195,7 +321,8 @@ describe('EvolutionTechBidAdapter', function () { netRevenue: true, currency: 'USD', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -216,6 +343,7 @@ describe('EvolutionTechBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -282,18 +410,4 @@ describe('EvolutionTechBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); - describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); - it('Returns valid URL and type', function () { - if (spec.noSync) { - expect(userSync).to.be.equal(false); - } else { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://service.e-volution.ai/?c=o&m=sync'); - } - }); - }); }); From 3e2df8d5da6b4fa46dedbd999d442a934543f8ea Mon Sep 17 00:00:00 2001 From: ops-co <159886704+ops-co@users.noreply.github.com> Date: Tue, 4 Jun 2024 04:54:37 +0200 Subject: [PATCH 0145/1097] Opsco Bid Adapter : update process for retrieving placementId from bid request params (#11604) * Opsco bid adapter init commit * Opsco bid adapter banner implementation * Changing test parameter * Changing endpoint * Retrieving placement Id from bid request params --------- Co-authored-by: adtech-sky --- modules/opscoBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index 87d00f14de0..2ad14227804 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -19,7 +19,11 @@ export const spec = { Array.isArray(bid.mediaTypes?.banner?.sizes)), buildRequests: (validBidRequests, bidderRequest) => { - const {publisherId, placementId, siteId} = validBidRequests[0].params; + if (!validBidRequests || !bidderRequest) { + return; + } + + const {publisherId, siteId} = validBidRequests[0].params; const payload = { id: bidderRequest.bidderRequestId, @@ -28,7 +32,7 @@ export const spec = { banner: {format: extractSizes(bidRequest)}, ext: { opsco: { - placementId: placementId, + placementId: bidRequest.params.placementId, publisherId: publisherId, } } From 62b1bea53ac39c0f82d570a57bd20fd82813248c Mon Sep 17 00:00:00 2001 From: Philip Watson Date: Tue, 4 Jun 2024 22:46:13 +1200 Subject: [PATCH 0146/1097] StroeerCore Bid Adapter: remove 'ssl' flag from the request payload (#11678) --- modules/stroeerCoreBidAdapter.js | 3 --- test/spec/modules/stroeerCoreBidAdapter_spec.js | 1 - 2 files changed, 4 deletions(-) diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index e67941ed3a1..35f40953b1f 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -52,7 +52,6 @@ export const spec = { const basePayload = { id: generateUUID(), ref: refererInfo.ref, - ssl: isSecureWindow(), mpa: isMainPageAccessible(), timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart), url: refererInfo.page, @@ -147,8 +146,6 @@ export const spec = { } }; -const isSecureWindow = () => getWindowSelf().location.protocol === 'https:'; - const isMainPageAccessible = () => { try { return !!getWindowTop().location.href; diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 6f4874cef75..a8295c197ef 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -407,7 +407,6 @@ describe('stroeerCore bid adapter', function () { 'timeout': expectedTimeout, 'ref': 'https://www.example.com/?search=monkey', 'mpa': true, - 'ssl': false, 'url': 'https://www.example.com/monkey/index.html', 'bids': [{ 'sid': 'NDA=', From d6e9ca04a8b493a15933bc90600308b7ee724315 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Tue, 4 Jun 2024 13:55:25 +0300 Subject: [PATCH 0147/1097] AdMatic Bid Adapter: add monetixads alias (#11679) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 6c268b2d382..34a6ec788bd 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -40,7 +40,8 @@ export const spec = { code: BIDDER_CODE, gvlid: 1281, aliases: [ - {code: 'pixad', gvlid: 1281} + {code: 'pixad', gvlid: 1281}, + {code: 'monetixads', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -140,11 +141,14 @@ export const spec = { if (payload) { switch (bidderName) { + case 'monetixads': + SYNC_URL = 'https://static.cdn.monetixads.com/sync.html'; + break; case 'pixad': SYNC_URL = 'https://static.cdn.pixad.com.tr/sync.html'; break; default: - SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.html'; + SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; break; } From 56530582576976d322cafb58d4c76e792785a45c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 4 Jun 2024 09:14:49 -0400 Subject: [PATCH 0148/1097] Various places: jsdoc fixes (#11672) * Update adapterManager.js * Update consentHandler.js * Update prebid.js * Update targeting.js * Update refererDetection.js * Update video.js * Update videoCache.js * Update params.js * Update rules.js * Update paapi.js * Update ortb.js * Update composer.js * Update coreVideo.js * Update adapterManager.js * Update consentHandler.js * Update ttlCollection.js * Update enrichment.js * Update gamAdServerSubmodule.js --- libraries/ortbConverter/lib/composer.js | 8 ++++---- libraries/video/constants/ortb.js | 2 +- modules/paapi.js | 10 +++++----- modules/videoModule/coreVideo.js | 1 - modules/videoModule/gamAdServerSubmodule.js | 1 - src/activities/params.js | 6 ++++-- src/activities/rules.js | 14 +++++++------- src/adapterManager.js | 8 +++++--- src/consentHandler.js | 13 +++++++++---- src/fpd/enrichment.js | 6 +++--- src/prebid.js | 6 +++--- src/refererDetection.js | 12 +++++++----- src/targeting.js | 3 ++- src/utils/ttlCollection.js | 15 +++++++++++---- src/video.js | 3 ++- src/videoCache.js | 6 ++++-- 16 files changed, 67 insertions(+), 47 deletions(-) diff --git a/libraries/ortbConverter/lib/composer.js b/libraries/ortbConverter/lib/composer.js index 0ceff7f9edb..477d4e10890 100644 --- a/libraries/ortbConverter/lib/composer.js +++ b/libraries/ortbConverter/lib/composer.js @@ -11,13 +11,13 @@ const SORTED = new WeakMap(); /** * - * @param {Object[string, Component]} components to compose - * @param {Object[string, function|boolean]} overrides a map from component name, to a function that should override that component. + * @param {Object.} components - An object where keys are component names and values are components to compose. + * @param {Object.} overrides - A map from component names to functions that should override those components. * Override functions are replacements, except that they get the original function they are overriding as their first argument. If the override * is `false`, the component is disabled. * - * @return a function that will run all components in order of priority, with functions from `overrides` taking - * precedence over components that match names + * @return {function} - A function that will run all components in order of priority, with functions from `overrides` taking + * precedence over components that match names. */ export function compose(components, overrides = {}) { if (!SORTED.has(components)) { diff --git a/libraries/video/constants/ortb.js b/libraries/video/constants/ortb.js index d67c8a5f393..6b64296500e 100644 --- a/libraries/video/constants/ortb.js +++ b/libraries/video/constants/ortb.js @@ -1,6 +1,6 @@ /** * @typedef {Object} OrtbParams - * @property {OrtbVideoParamst} video + * @property {OrtbVideoParams} video * @property {OrtbContentParams} content */ diff --git a/modules/paapi.js b/modules/paapi.js index 3d562e83c56..c6cd380c1c9 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -272,11 +272,11 @@ function expandFilters({auctionId, adUnitCode} = {}) { /** * Get PAAPI auction configuration. * - * @param filters - * @param filters.auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used - * @param filters.adUnitCode? optional ad unit filter - * @param includeBlanks if true, include null entries for ad units that match the given filters but do not have any available auction configs. - * @returns {{}} a map from ad unit code to auction config for the ad unit. + * @param {Object} [filters] - Filters object + * @param {string} [filters.auctionId] optional auction filter; if omitted, the latest auction for each ad unit is used + * @param {string} [filters.adUnitCode] optional ad unit filter + * @param {boolean} [includeBlanks=false] if true, include null entries for ad units that match the given filters but do not have any available auction configs. + * @returns {Object} a map from ad unit code to auction config for the ad unit. */ export function getPAAPIConfig(filters = {}, includeBlanks = false) { const output = {}; diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js index fc54d0d0b98..4ac5f090334 100644 --- a/modules/videoModule/coreVideo.js +++ b/modules/videoModule/coreVideo.js @@ -104,7 +104,6 @@ import { ParentModule, SubmoduleBuilder } from '../../libraries/video/shared/par /** * @summary Maps a Video Provider factory to the video player's vendor code. - * @type {vendorSubmoduleDirectory} */ const videoVendorDirectory = {}; diff --git a/modules/videoModule/gamAdServerSubmodule.js b/modules/videoModule/gamAdServerSubmodule.js index 87db71ae38b..728ee4d060e 100644 --- a/modules/videoModule/gamAdServerSubmodule.js +++ b/modules/videoModule/gamAdServerSubmodule.js @@ -4,7 +4,6 @@ import { getGlobal } from '../../src/prebidGlobal.js'; /** * @constructor * @param {Object} dfpModule_ - the DFP ad server module - * @returns {AdServerProvider} */ function GamAdServerProvider(dfpModule_) { const dfp = dfpModule_; diff --git a/src/activities/params.js b/src/activities/params.js index 036a6657cf8..859f5d5beed 100644 --- a/src/activities/params.js +++ b/src/activities/params.js @@ -40,8 +40,10 @@ export const ACTIVITY_PARAM_SYNC_TYPE = 'syncType' export const ACTIVITY_PARAM_SYNC_URL = 'syncUrl'; /** * @private - * configuration options for analytics adapter - the argument passed to `enableAnalytics`. - * relevant for: reportAnalytics + * Configuration options for analytics adapter - the argument passed to `enableAnalytics`. + * Relevant for: reportAnalytics. + * @constant + * @type {string} */ export const ACTIVITY_PARAM_ANL_CONFIG = '_config'; diff --git a/src/activities/rules.js b/src/activities/rules.js index f84f1080843..7b4f4634f07 100644 --- a/src/activities/rules.js +++ b/src/activities/rules.js @@ -40,19 +40,19 @@ export function ruleRegistry(logger = prefixLog('Activity control:')) { /** * Register an activity control rule. * - * @param {string} activity activity name - set is defined in `activities.js` - * @param {string} ruleName a name for this rule; used for logging. - * @param {function({}): {allow: boolean, reason?: string}} rule definition function. Takes in activity + * @param {string} activity - Activity name, as defined in `activities.js`. + * @param {string} ruleName - A name for this rule, used for logging. + * @param {function(Object): {allow: boolean, reason?: string}} rule - Rule definition function. Takes in activity * parameters as a single map; MAY return an object {allow, reason}, where allow is true/false, * and reason is an optional message used for logging. * - * {allow: true} will allow this activity AS LONG AS no other rules with same or higher priority return {allow: false}; + * {allow: true} will allow this activity AS LONG AS no other rules with the same or higher priority return {allow: false}; * {allow: false} will deny this activity AS LONG AS no other rules with higher priority return {allow: true}; - * returning null/undefined has no effect - the decision is left to other rules. + * Returning null/undefined has no effect - the decision is left to other rules. * If no rule returns an allow value, the default is to allow the activity. * - * @param {number} priority rule priority; lower number means higher priority - * @returns {function(void): void} a function that unregisters the rule when called. + * @param {number} [priority=10] - Rule priority; lower number means higher priority. + * @returns {function(): void} - A function that unregisters the rule when called. */ function registerActivityControl(activity, ruleName, rule, priority = 10) { const rules = getRules(activity); diff --git a/src/adapterManager.js b/src/adapterManager.js index 8228001892d..2d4bd7a5f6b 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -230,9 +230,11 @@ export function getS2SBidderSet(s2sConfigs) { } /** - * @returns {{[PARTITIONS.CLIENT]: Array, [PARTITIONS.SERVER]: Array}} - * All the bidder codes in the given `adUnits`, divided in two arrays - - * those that should be routed to client, and server adapters (according to the configuration in `s2sConfigs`). + * @param {Array} adUnits - The ad units to be processed. + * @param {Object} s2sConfigs - The server-to-server configurations. + * @returns {Object} - An object containing arrays of bidder codes for client and server. + * @returns {Object} return.client - Array of bidder codes that should be routed to client adapters. + * @returns {Object} return.server - Array of bidder codes that should be routed to server adapters. */ export function _partitionBidders (adUnits, s2sConfigs, {getS2SBidders = getS2SBidderSet} = {}) { const serverBidders = getS2SBidders(s2sConfigs); diff --git a/src/consentHandler.js b/src/consentHandler.js index 5b5d8b805cd..19137d9a422 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -163,14 +163,19 @@ export function gvlidRegistry() { } } }, + /** + * @typedef {Object} GvlIdResult + * @property {Object.} modules - A map from module type to that module's GVL ID. + * @property {number} [gvlid] - The single GVL ID for this family of modules (only defined if all modules with this name declared the same ID). + */ + /** * Get a module's GVL ID(s). * - * @param {string} moduleName - * @return {{modules: {[moduleType]: number}, gvlid?: number}} an object where: + * @param {string} moduleName - The name of the module. + * @return {GvlIdResult} An object where: * `modules` is a map from module type to that module's GVL ID; - * `gvlid` is the single GVL ID for this family of modules (only defined - * if all modules with this name declared the same ID). + * `gvlid` is the single GVL ID for this family of modules (only defined if all modules with this name declare the same ID). */ get(moduleName) { const result = {modules: registry[moduleName] || {}}; diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 49e2f7d7cad..65c3db65974 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -23,9 +23,9 @@ export const dep = { const oneClient = clientSectionChecker('FPD') /** - * Enrich an ortb2 object with first party data. - * @param {Promise[{}]} fpd: a promise to an ortb2 object. - * @returns: {Promise[{}]}: a promise to an enriched ortb2 object. + * Enrich an ortb2 object with first-party data. + * @param {Promise} fpd - A promise that resolves to an ortb2 object. + * @returns {Promise} - A promise that resolves to an enriched ortb2 object. */ export const enrichFPD = hook('sync', (fpd) => { const promArr = [fpd, getSUA().catch(() => null), tryToGetCdepLabel().catch(() => null)]; diff --git a/src/prebid.js b/src/prebid.js index 7f2d8798e2a..df8ce019ed1 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -283,7 +283,7 @@ pbjsInstance.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { /** * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param adUnitCode {string} adUnitCode to get the bid responses for + * @param adunitCode {string} adUnitCode to get the bid responses for * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode * @returns {Object} returnObj return bid */ @@ -393,7 +393,7 @@ pbjsInstance.getBidResponsesForAdUnitCode = function (adUnitCode) { /** * Set query string targeting on one or more GPT ad units. * @param {(string|string[])} adUnit a single `adUnit.code` or multiple. - * @param {function(object)} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; + * @param {function(object): function(string): boolean} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; * @alias module:pbjs.setTargetingForGPTAsync */ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { @@ -426,7 +426,7 @@ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { /** * Set query string targeting on all AST (AppNexus Seller Tag) ad units. Note that this function has to be called after all ad units on page are defined. For working example code, see [Using Prebid.js with AppNexus Publisher Ad Server](http://prebid.org/dev-docs/examples/use-prebid-with-appnexus-ad-server.html). - * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes + * @param {(string|string[])} adUnitCodes adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ pbjsInstance.setTargetingForAst = function (adUnitCodes) { diff --git a/src/refererDetection.js b/src/refererDetection.js index 93ebf085dd5..bfe7fb02671 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -34,9 +34,11 @@ export function ensureProtocol(url, win = window) { /** * Extract the domain portion from a URL. - * @param url - * @param noLeadingWww: if true, remove 'www.' appearing at the beginning of the domain. - * @param noPort: if true, do not include the ':[port]' portion + * @param {string} url - The URL to extract the domain from. + * @param {Object} options - Options for parsing the domain. + * @param {boolean} options.noLeadingWww - If true, remove 'www.' appearing at the beginning of the domain. + * @param {boolean} options.noPort - If true, do not include the ':[port]' portion. + * @return {string|undefined} - The extracted domain or undefined if the URL is invalid. */ export function parseDomain(url, {noLeadingWww = false, noPort = false} = {}) { try { @@ -108,13 +110,13 @@ export function detectReferer(win) { * @property {string|null} ref the referrer (document.referrer) to the current page, or null if not available (due to cross-origin restrictions) * @property {string} topmostLocation of the top-most frame for which we could guess the location. Outside of cross-origin scenarios, this is equivalent to `location`. * @property {number} numIframes number of steps between window.self and window.top - * @property {Array[string|null]} stack our best guess at the location for each frame, in the direction top -> self. + * @property {Array} stack our best guess at the location for each frame, in the direction top -> self. */ /** * Walk up the windows to get the origin stack and best available referrer, canonical URL, etc. * - * @returns {refererInfo} + * @returns {refererInfo} An object containing referer information. */ function refererInfo() { const stack = []; diff --git a/src/targeting.js b/src/targeting.js index acb3ddb09ff..d3fb3878248 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -482,7 +482,8 @@ export function newTargeting(auctionManager) { /** * Returns top bids for a given adUnit or set of adUnits. * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes - * @return {[type]} [description] + * @param {Array} [bidsReceived=getBidsReceived()] - The received bids, defaulting to the result of getBidsReceived(). + * @return {Array} - An array of winning bids. */ targeting.getWinningBids = function(adUnitCode, bidsReceived = getBidsReceived()) { const adUnitCodes = getAdUnitCodes(adUnitCode); diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index 0972d175848..dfad7f01933 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -4,19 +4,26 @@ import {binarySearch, logError, timestamp} from '../utils.js'; /** * Create a set-like collection that automatically forgets items after a certain time. * - * @param {({}) => Number|Promise} startTime? a function taking an item added to this collection, + * @param {function(*): (number|Promise)} [startTime=timestamp] - A function taking an item added to this collection, * and returning (a promise to) a timestamp to be used as the starting time for the item * (the item will be dropped after `ttl(item)` milliseconds have elapsed since this timestamp). * Defaults to the time the item was added to the collection. - * @param {({}) => Number|void|Promise} ttl a function taking an item added to this collection, + * @param {function(*): (number|void|Promise)} [ttl=() => null] - A function taking an item added to this collection, * and returning (a promise to) the duration (in milliseconds) the item should be kept in it. * May return null to indicate that the item should be persisted indefinitely. - * @param {boolean} monotonic? set to true for better performance, but only if, given any two items A and B in this collection: + * @param {boolean} [monotonic=false] - Set to true for better performance, but only if, given any two items A and B in this collection: * if A was added before B, then: * - startTime(A) + ttl(A) <= startTime(B) + ttl(B) * - Promise.all([startTime(A), ttl(A)]) never resolves later than Promise.all([startTime(B), ttl(B)]) - * @param {number} slack? maximum duration (in milliseconds) that an item is allowed to persist + * @param {number} [slack=5000] - Maximum duration (in milliseconds) that an item is allowed to persist * once past its TTL. This is also roughly the interval between "garbage collection" sweeps. + * @returns {Object} A set-like collection with automatic TTL expiration. + * @returns {function(*)} return.add - Add an item to the collection. + * @returns {function()} return.clear - Clear the collection. + * @returns {function(): Array<*>} return.toArray - Get all the items in the collection, in insertion order. + * @returns {function()} return.refresh - Refresh the TTL for each item in the collection. + * @returns {function(function(*))} return.onExpiry - Register a callback to be run when an item has expired and is about to be + * removed from the collection. */ export function ttlCollection( { diff --git a/src/video.js b/src/video.js index ff137892a2b..f8de2b98861 100644 --- a/src/video.js +++ b/src/video.js @@ -25,7 +25,8 @@ export function fillVideoDefaults(adUnit) { /** * Validate that the assets required for video context are present on the bid * @param {VideoBid} bid Video bid to validate - * @param index + * @param {Object} [options] - Options object + * @param {Object} [options.index=auctionManager.index] - Index object, defaulting to `auctionManager.index` * @return {Boolean} If object is valid */ export function isValidVideoBid(bid, {index = auctionManager.index} = {}) { diff --git a/src/videoCache.js b/src/videoCache.js index ce03f2f624e..6cba77de308 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -39,7 +39,7 @@ const ttlBufferInSeconds = 15; * Function which wraps a URI that serves VAST XML, so that it can be loaded. * * @param {string} uri The URI where the VAST content can be found. - * @param {string} impUrl An impression tracker URL for the delivery of the video ad + * @param {(string|string[])} impTrackerURLs An impression tracker URL for the delivery of the video ad * @return A VAST URL which loads XML from the given URI. */ function wrapURI(uri, impTrackerURLs) { @@ -65,7 +65,9 @@ function wrapURI(uri, impTrackerURLs) { * the bid can't be converted cleanly. * * @param {CacheableBid} bid - * @param index + * @param {Object} [options] - Options object. + * @param {Object} [options.index=auctionManager.index] - Index object, defaulting to `auctionManager.index`. + * @return {Object|null} - The payload to be sent to the prebid-server endpoints, or null if the bid can't be converted cleanly. */ function toStorageRequest(bid, {index = auctionManager.index} = {}) { const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); From 1258c532813ecec8f8e86bfd97a07301fae3b651 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:32:40 +0300 Subject: [PATCH 0149/1097] Smarthub replace placement (#11629) * update adapter SmartHub: add aliases * SmartHub adapter: replace placement * add getter video.plcmt and update test * revert placement to plcmt --------- Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 45 ++++++++++---------- test/spec/modules/smarthubBidAdapter_spec.js | 2 + 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index ea2b62c95c9..b5970fbb9ee 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -48,7 +48,7 @@ function getPlacementReqData(bid) { const { partnerName, seat, token, iabCat, minBidfloor, pos } = params; const bidfloor = getBidFloor(bid); - const placement = { + const plcmt = { partnerName: String(partnerName || bidder).toLowerCase(), seat, token, @@ -61,31 +61,32 @@ function getPlacementReqData(bid) { }; if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; + plcmt.adFormat = BANNER; + plcmt.sizes = mediaTypes[BANNER].sizes; } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; + plcmt.adFormat = VIDEO; + plcmt.playerSize = mediaTypes[VIDEO].playerSize; + plcmt.minduration = mediaTypes[VIDEO].minduration; + plcmt.maxduration = mediaTypes[VIDEO].maxduration; + plcmt.mimes = mediaTypes[VIDEO].mimes; + plcmt.protocols = mediaTypes[VIDEO].protocols; + plcmt.startdelay = mediaTypes[VIDEO].startdelay; + plcmt.placement = mediaTypes[VIDEO].plcmt; + plcmt.plcmt = mediaTypes[VIDEO].plcmt; // https://github.com/prebid/Prebid.js/issues/10452 + plcmt.skip = mediaTypes[VIDEO].skip; + plcmt.skipafter = mediaTypes[VIDEO].skipafter; + plcmt.minbitrate = mediaTypes[VIDEO].minbitrate; + plcmt.maxbitrate = mediaTypes[VIDEO].maxbitrate; + plcmt.delivery = mediaTypes[VIDEO].delivery; + plcmt.playbackmethod = mediaTypes[VIDEO].playbackmethod; + plcmt.api = mediaTypes[VIDEO].api; + plcmt.linearity = mediaTypes[VIDEO].linearity; } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; + plcmt.native = mediaTypes[NATIVE]; + plcmt.adFormat = NATIVE; } - return placement; + return plcmt; } function getBidFloor(bid) { diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index dcbfd297013..a5e8787e21a 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -49,6 +49,7 @@ describe('SmartHubBidAdapter', function () { playerSize: [[300, 300]], minduration: 5, maxduration: 60, + plcmt: 1, } }, params: { @@ -197,6 +198,7 @@ describe('SmartHubBidAdapter', function () { expect(placement.playerSize).to.be.an('array'); expect(placement.minduration).to.be.an('number'); expect(placement.maxduration).to.be.an('number'); + expect(placement.plcmt).to.be.an('number'); break; case NATIVE: expect(placement.native).to.be.an('object'); From 402b524738b1539f350de7b91ee9d2820f4ca3ab Mon Sep 17 00:00:00 2001 From: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:01:19 +0300 Subject: [PATCH 0150/1097] Vis X Bid Adapter : retrieve and send seller defined audiences (#11638) * AF-3647 added device object to request in visxBidAdapter * AF-3647 added user, regs, site and user agent data to request * AF-3647 removed userAgentClientHints because of it is gotten from device object * AF-3647 updated test scenarios with sda signals * AF-3647 fixed to getting ortb2 and spec file * AF-3647 reverted to get user data from cookie/local storage --- modules/visxBidAdapter.js | 48 +- test/spec/modules/visxBidAdapter_spec.js | 732 +++++++++++++++++++++-- 2 files changed, 725 insertions(+), 55 deletions(-) diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index beffcf5da95..c7f415e4dac 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -59,11 +59,16 @@ export const spec = { config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; + let request; let reqId; let payloadSchain; let payloadUserId; let payloadUserEids; let timeout; + let payloadDevice; + let payloadSite; + let payloadRegs; + let payloadContent; if (currencyWhiteList.indexOf(currency) === -1) { logError(LOG_ERROR_MESS.notAllowedCurrency + currency); @@ -80,9 +85,7 @@ export const spec = { imp.push(impObj); bidsMap[bid.bidId] = bid; } - const { params: { uid }, schain, userId, userIdAsEids } = bid; - if (!payloadSchain && schain) { payloadSchain = schain; } @@ -93,6 +96,7 @@ export const spec = { if (!payloadUserId && userId) { payloadUserId = userId; } + auids.push(uid); }); @@ -100,10 +104,7 @@ export const spec = { if (bidderRequest) { timeout = bidderRequest.timeout; - if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { - // TODO: is 'page' the right value here? - payload.u = bidderRequest.refererInfo.page; - } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; @@ -112,6 +113,21 @@ export const spec = { (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } + + const { ortb2 } = bidderRequest; + const { device, site, regs, content } = ortb2; + if (device) { + payloadDevice = device; + } + if (site) { + payloadSite = site; + } + if (regs) { + payloadRegs = regs; + } + if (content) { + payloadContent = content; + } } const tmax = timeout; @@ -131,21 +147,25 @@ export const spec = { ...(vads && { vads }) } }; - const regs = ('gdpr_applies' in payload) && { - ext: { - gdpr: payload.gdpr_applies - } - }; + if (payloadRegs === undefined) { + payloadRegs = ('gdpr_applies' in payload) && { + ext: { + gdpr: payload.gdpr_applies + } + }; + } - const request = { + request = { id: reqId, imp, tmax, cur: [currency], source, - site: { page: payload.u }, ...(Object.keys(user.ext).length && { user }), - ...(regs && { regs }) + ...(payloadRegs && {regs: payloadRegs}), + ...(payloadDevice && { device: payloadDevice }), + ...(payloadSite && { site: payloadSite }), + ...(payloadContent && { content: payloadContent }), }; return { diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 5528705efd7..f70f614b2c8 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -89,6 +89,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + ortb2: { + device: { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + site: { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -109,7 +149,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -120,7 +160,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90], [300, 250]], 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -131,7 +171,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '42dbe3a7168a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -151,7 +191,7 @@ describe('VisxAdapter', function () { }, 'bidId': '39a4e3a7168a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; @@ -217,7 +257,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -235,7 +312,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -255,7 +369,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['GBP'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); getConfigStub.restore(); @@ -277,7 +428,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['USD'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, }); getConfigStub.restore(); @@ -294,9 +482,50 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'regs': { + 'ext': { + 'gdpr': 1 + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, - 'regs': {'ext': {'gdpr': 1}} }); }); @@ -311,7 +540,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, 'regs': {'ext': {'gdpr': 0}} }); @@ -328,7 +594,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, 'regs': {'ext': {'gdpr': 1}} }); @@ -359,7 +662,44 @@ describe('VisxAdapter', function () { 'schain': schainObject } }, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -408,7 +748,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'eids': eids}} }); }); @@ -429,7 +806,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); }); @@ -448,6 +862,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -467,7 +921,7 @@ describe('VisxAdapter', function () { }, 'bidId': '39aff3a7169a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a476', + 'auctionId': '1d1a030790a476' } ]; @@ -494,7 +948,6 @@ describe('VisxAdapter', function () { const payload = parseRequest(request.url); expect(payload).to.be.an('object'); expect(payload).to.have.property('auids', '903538'); - const postData = request.data; expect(postData).to.be.an('object'); expect(postData).to.deep.equal({ @@ -512,7 +965,50 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ + '124' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '124' + ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ + '99' + ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + } }); }); }); @@ -531,6 +1027,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + ortb2: { + device: { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + site: { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -544,7 +1080,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -555,7 +1091,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; let sandbox; @@ -612,7 +1148,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -641,7 +1214,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); }); @@ -669,7 +1279,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const request = spec.buildRequests(bidRequests); @@ -719,7 +1329,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' }, { 'bidder': 'visx', @@ -730,7 +1340,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '4dff80cc4ee346', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' }, { 'bidder': 'visx', @@ -741,7 +1351,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '5703af74d0472a', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' } ]; const request = spec.buildRequests(bidRequests); @@ -823,7 +1433,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const getConfigStub = sinon.stub(config, 'getConfig').returns('PLN'); @@ -888,7 +1498,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71321', 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', + 'auctionId': '1fa09aee5c84d34' }, { 'bidder': 'visx', @@ -899,7 +1509,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '300bfeb0d7183bb', 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', + 'auctionId': '1fa09aee5c84d34' } ]; const request = spec.buildRequests(bidRequests); @@ -925,7 +1535,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -936,7 +1546,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '326bde7fbf69', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -947,7 +1557,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '4e111f1b66e4', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -958,7 +1568,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '26d6f897b516', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -969,7 +1579,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '1751cd90161', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1075,7 +1685,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '5126e301f4be', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' }, { 'bidder': 'visx', @@ -1086,7 +1696,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '57b2ebe70e16', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' }, { 'bidder': 'visx', @@ -1097,7 +1707,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '225fcd44b18c', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' } ]; const request = spec.buildRequests(bidRequests); @@ -1162,7 +1772,7 @@ describe('VisxAdapter', function () { 'sizes': [[400, 300]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1214,7 +1824,7 @@ describe('VisxAdapter', function () { 'sizes': [[400, 300]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1251,7 +1861,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const request = spec.buildRequests(bidRequests); @@ -1434,13 +2044,53 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; const bidderRequest = { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; From 6478666c2990a669896aedbf3403fdef406343c3 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Tue, 4 Jun 2024 11:55:27 -0500 Subject: [PATCH 0151/1097] 33Across User ID Module : support for the recently introduced "multiple storage types" feature (#11563) * Refactoring - break functions that are handling multiple storage types. * user id: introduce the concept of enabled storage types * Apply domain override to 33across ID * First party ID - Support for multiple storage types * 33Across User ID: Recommend both storage types * refactor the way enabled storage types are populated --- modules/33acrossIdSystem.js | 64 ++++++---- modules/33acrossIdSystem.md | 4 +- test/spec/modules/33acrossIdSystem_spec.js | 131 ++++++++++++++++----- 3 files changed, 146 insertions(+), 53 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index e0f7435a1ec..0118408f08d 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -11,6 +11,7 @@ import { submodule } from '../src/hook.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.js'; import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { domainOverrideToRootDomain } from '../libraries/domainOverrideToRootDomain/index.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -28,6 +29,10 @@ const STORAGE_FPID_KEY = '33acrossIdFp'; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +export const domainUtils = { + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME) +}; + function calculateResponseObj(response) { if (!response.succeeded) { if (response.error == 'Cookied User') { @@ -50,7 +55,7 @@ function calculateResponseObj(response) { }; } -function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { +function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) { const uspString = uspDataHandler.getConsentData(); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); @@ -78,7 +83,7 @@ function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { params.gdpr_consent = gdprConsentData.consentString; } - const fp = getStoredValue(STORAGE_FPID_KEY, storageConfig); + const fp = getStoredValue(STORAGE_FPID_KEY, enabledStorageTypes); if (fp) { params.fp = encodeURIComponent(fp); } @@ -90,32 +95,42 @@ function deleteFromStorage(key) { if (storage.cookiesAreEnabled()) { const expiredDate = new Date(0).toUTCString(); - storage.setCookie(key, '', expiredDate, 'Lax'); + storage.setCookie(key, '', expiredDate, 'Lax', domainUtils.domainOverride()); } storage.removeDataFromLocalStorage(key); } -function storeValue(key, value, storageConfig = {}) { - if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { - const expirationInMs = 60 * 60 * 24 * 1000 * storageConfig.expires; - const expirationTime = new Date(Date.now() + expirationInMs); +function storeValue(key, value, { enabledStorageTypes, expires }) { + enabledStorageTypes.forEach(storageType => { + if (storageType === STORAGE_TYPE_COOKIES) { + const expirationInMs = 60 * 60 * 24 * 1000 * expires; + const expirationTime = new Date(Date.now() + expirationInMs); - storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax'); - } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { - storage.setDataInLocalStorage(key, value); - } + storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax', domainUtils.domainOverride()); + } else if (storageType === STORAGE_TYPE_LOCALSTORAGE) { + storage.setDataInLocalStorage(key, value); + } + }); } -function getStoredValue(key, storageConfig = {}) { - if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { - return storage.getCookie(key); - } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { - return storage.getDataFromLocalStorage(key); - } +function getStoredValue(key, enabledStorageTypes) { + let storedValue; + + enabledStorageTypes.find(storageType => { + if (storageType === STORAGE_TYPE_COOKIES) { + storedValue = storage.getCookie(key); + } else if (storageType === STORAGE_TYPE_LOCALSTORAGE) { + storedValue = storage.getDataFromLocalStorage(key); + } + + return !!storedValue; + }); + + return storedValue; } -function handleFpId(fpId, storageConfig = {}) { +function handleFpId(fpId, storageConfig) { fpId ? storeValue(STORAGE_FPID_KEY, fpId, storageConfig) : deleteFromStorage(STORAGE_FPID_KEY); @@ -151,7 +166,7 @@ export const thirthyThreeAcrossIdSubmodule = { * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId({ params = { }, storage: storageConfig }, gdprConsentData) { + getId({ params = { }, enabledStorageTypes = [], storage: storageConfig }, gdprConsentData) { if (typeof params.pid !== 'string') { logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); @@ -183,7 +198,10 @@ export const thirthyThreeAcrossIdSubmodule = { } if (storeFpid) { - handleFpId(responseObj.fp, storageConfig); + handleFpId(responseObj.fp, { + enabledStorageTypes, + expires: storageConfig.expires + }); } cb(responseObj.envelope); @@ -193,10 +211,14 @@ export const thirthyThreeAcrossIdSubmodule = { cb(); } - }, calculateQueryStringParams(pid, gdprConsentData, storageConfig), { method: 'GET', withCredentials: true }); + }, calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes), { + method: 'GET', + withCredentials: true + }); } }; }, + domainOverride: domainUtils.domainOverride, eids: { '33acrossId': { source: '33across.com', diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index 8b73a43069d..5f5e7805ff9 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -14,7 +14,7 @@ pbjs.setConfig({ name: "33acrossId", storage: { name: "33acrossId", - type: "html5", + type: "cookie&html5", expires: 30, refreshInSeconds: 8*3600 }, @@ -40,7 +40,7 @@ The following settings are available for the `storage` property in the `userSync | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` | -| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` | +| type | Required | String | `"cookie&html5"` (preferred) or `"cookie"` or `"html5"` | `"cookie&html5"` | | expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `30`. | `30` | | refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` | diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index e2fb6f22cd2..2454d790f90 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -1,4 +1,4 @@ -import { thirthyThreeAcrossIdSubmodule, storage } from 'modules/33acrossIdSystem.js'; +import { thirthyThreeAcrossIdSubmodule, storage, domainUtils } from 'modules/33acrossIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; @@ -55,7 +55,7 @@ describe('33acrossIdSystem', () => { context('if the use of a first-party ID has been enabled', () => { context('and the response includes a first-party ID', () => { - context('and the storage type is "cookie"', () => { + context('and the enabled storage types include "cookie"', () => { it('should store the provided first-party ID in a cookie', () => { const completeCallback = () => {}; @@ -64,10 +64,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: true }, - storage: { - type: 'cookie', - expires: 30 - } + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } }); callback(completeCallback); @@ -76,6 +74,7 @@ describe('33acrossIdSystem', () => { const setCookie = sinon.stub(storage, 'setCookie'); const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -88,14 +87,15 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledOnceWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; setCookie.restore(); cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); - context('and the storage type is "html5"', () => { + context('and the enabled storage types include "html5"', () => { it('should store the provided first-party ID in local storage', () => { const completeCallback = () => {}; @@ -104,9 +104,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: true }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -131,6 +130,49 @@ describe('33acrossIdSystem', () => { setDataInLocalStorage.restore(); }); }); + + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided first-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: true + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setCookie.restore(); + cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); + }); }); context('and the response lacks a first-party ID', () => { @@ -142,9 +184,8 @@ describe('33acrossIdSystem', () => { pid: '12345', storeFpid: true }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -154,6 +195,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -166,11 +208,12 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledOnceWithExactly('33acrossIdFp')).to.be.true; - expect(setCookie.calledOnceWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; removeDataFromLocalStorage.restore(); setCookie.restore(); cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); }); @@ -185,8 +228,8 @@ describe('33acrossIdSystem', () => { pid: '12345' // no storeFpid param }, + enabledStorageTypes: [ 'cookie' ], storage: { - type: 'cookie', expires: 30 } }); @@ -197,6 +240,7 @@ describe('33acrossIdSystem', () => { const setCookie = sinon.stub(storage, 'setCookie'); const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -209,10 +253,11 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(setCookie.calledOnceWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.false; + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.false; setCookie.restore(); cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); it('should not store the provided first-party ID in local storage', () => { @@ -223,9 +268,8 @@ describe('33acrossIdSystem', () => { pid: '12345' // no storeFpid param }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -260,9 +304,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -272,6 +315,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -284,11 +328,12 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledWith('33acrossId')).to.be.true; - expect(setCookie.calledWith('33acrossId', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossId', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; removeDataFromLocalStorage.restore(); setCookie.restore(); cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); @@ -502,9 +547,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); sinon.stub(storage, 'getDataFromLocalStorage') @@ -528,9 +572,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'cookie' - } + enabledStorageTypes: [ 'cookie' ], + storage: {} }); sinon.stub(storage, 'getCookie') @@ -547,6 +590,34 @@ describe('33acrossIdSystem', () => { }); }); + context('when a first-party ID is present only in one of the enabled storage types', () => { + it('should call endpoint with the first-party ID found', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + sinon.stub(storage, 'getCookie') + .withArgs('33acrossIdFp') + .returns(''); + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdFp') + .returns('33acrossIdFpValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('fp=33acrossIdFpValue'); + + storage.getCookie.restore(); + }); + }); + context('when a first-party ID is not present in storage', () => { it('should not call endpoint with the first-party ID included', () => { const completeCallback = () => {}; From 82ce88e4481e69f564b41ccbc6a11b5b42fa4bf6 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Tue, 4 Jun 2024 18:59:14 +0200 Subject: [PATCH 0152/1097] Default to fetch keepalive (#11682) Co-authored-by: Marcin Komorski --- modules/genericAnalyticsAdapter.js | 2 +- src/ajax.js | 3 +++ test/spec/modules/genericAnalyticsAdapter_spec.js | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/genericAnalyticsAdapter.js b/modules/genericAnalyticsAdapter.js index 7f721863912..ce37e5c02fe 100644 --- a/modules/genericAnalyticsAdapter.js +++ b/modules/genericAnalyticsAdapter.js @@ -142,7 +142,7 @@ export function defaultHandler({url, method, batchSize, ajax = ajaxBuilder()}) { const serialize = method === 'GET' ? (data) => ({data: JSON.stringify(data)}) : (data) => JSON.stringify(data); return function (events) { - ajax(url, callbacks, serialize(extract(events)), {method}) + ajax(url, callbacks, serialize(extract(events)), {method, keepalive: true}) } } diff --git a/src/ajax.js b/src/ajax.js index ef4c2e4bcb4..92bff6dd527 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -52,6 +52,9 @@ export function toFetchRequest(url, data, options = {}) { // but we're not in a secure context rqOpts.browsingTopics = true; } + if (options.keepalive) { + rqOpts.keepalive = true; + } return dep.makeRequest(url, rqOpts); } diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index 2d9c7b4ae45..f574a33bf86 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -265,7 +265,7 @@ describe('Generic analytics', () => { handler([payload, {}]); sinon.assert.calledWith(ajax, url, sinon.match.any, sinon.match(data => sinon.match(payload).test(parse(data))), - {method} + {method, keepalive: true} ); }); @@ -275,7 +275,7 @@ describe('Generic analytics', () => { handler(payload); sinon.assert.calledWith(ajax, url, sinon.match.any, sinon.match(data => sinon.match(payload).test(parse(data))), - {method} + {method, keepalive: true} ); }); }); From 16263f3700b0a565376ec8101c66994237fabdd6 Mon Sep 17 00:00:00 2001 From: Kevin Siow Date: Tue, 4 Jun 2024 20:13:46 +0200 Subject: [PATCH 0153/1097] Dailymotion Bid Adapter: add support for user syncs & new fields (#11603) * Dailymotion Bid Adapter: add support for playbackmethod & plcmt * Dailymotion Bid Adapter: add support for user syncs * Dailymotion Bid Adapter: Add support for ortb2 device, and contextual informations * Dailymotion Bid Adapter: Fix tests * Dailymotion Bid Adapter: add support for content.url & device.ext.atts * Dailymotion Bid Adapter: change markdown header levels * Dailymotion Bid Adapter: collect prebid.version --------- Co-authored-by: Kevin Siow --- modules/dailymotionBidAdapter.js | 70 ++++++- modules/dailymotionBidAdapter.md | 62 +++++- .../modules/dailymotionBidAdapter_spec.js | 187 ++++++++++++++++-- 3 files changed, 290 insertions(+), 29 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index afded538fd0..bf8eaaebb55 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -54,14 +54,37 @@ function getVideoMetadata(bidRequest, bidderRequest) { : Object.keys(parsedContentData.iabcat2), id: videoParams.id || deepAccess(contentObj, 'id', ''), lang: videoParams.lang || deepAccess(contentObj, 'language', ''), + livestream: typeof videoParams.livestream === 'number' + ? !!videoParams.livestream + : !!deepAccess(contentObj, 'livestream', 0), private: videoParams.private || false, tags: videoParams.tags || deepAccess(contentObj, 'keywords', ''), title: videoParams.title || deepAccess(contentObj, 'title', ''), + url: videoParams.url || deepAccess(contentObj, 'url', ''), topics: videoParams.topics || '', xid: videoParams.xid || '', - livestream: typeof videoParams.livestream === 'number' - ? !!videoParams.livestream - : !!deepAccess(contentObj, 'livestream', 0), + isCreatedForKids: typeof videoParams.isCreatedForKids === 'boolean' + ? videoParams.isCreatedForKids + : null, + context: { + siteOrAppCat: deepAccess(contentObj, 'cat', ''), + videoViewsInSession: ( + typeof videoParams.videoViewsInSession === 'number' && + videoParams.videoViewsInSession >= 0 + ) + ? videoParams.videoViewsInSession + : null, + autoplay: typeof videoParams.autoplay === 'boolean' + ? videoParams.autoplay + : null, + playerVolume: ( + typeof videoParams.playerVolume === 'number' && + videoParams.playerVolume >= 0 && + videoParams.playerVolume <= 10 + ) + ? videoParams.playerVolume + : null, + }, }; return videoMetadata; @@ -111,6 +134,7 @@ export const spec = { method: 'POST', url: 'https://pb.dmxleo.com', data: { + pbv: '$prebid.version$', bidder_request: { gdprConsent: { apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), @@ -139,6 +163,13 @@ export const spec = { appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), } : {}), + ...(deepAccess(bidderRequest, 'ortb2.device') ? { + device: { + lmt: deepAccess(bidderRequest, 'ortb2.device.lmt', null), + ifa: deepAccess(bidderRequest, 'ortb2.device.ifa', ''), + atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), + }, + } : {}), request: { adUnitCode: deepAccess(bid, 'adUnitCode', ''), auctionId: deepAccess(bid, 'auctionId', ''), @@ -149,6 +180,8 @@ export const spec = { mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, + playbackmethod: bid.mediaTypes?.[VIDEO]?.playbackmethod || [], + plcmt: bid.mediaTypes?.[VIDEO]?.plcmt || 1, // Fallback to instream considering logic of `isBidRequestValid` protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], skip: bid.mediaTypes?.[VIDEO]?.skip || 0, skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, @@ -177,6 +210,37 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: serverResponse => serverResponse?.body ? [serverResponse.body] : [], + + /** + * Retrieves user synchronization URLs based on provided options and consents. + * + * @param {object} syncOptions - Options for synchronization. + * @param {object[]} serverResponses - Array of server responses. + * @returns {object[]} - Array of synchronization URLs. + */ + getUserSyncs: (syncOptions, serverResponses) => { + if (!!serverResponses?.length && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + const iframeSyncs = []; + const pixelSyncs = []; + + serverResponses.forEach((response) => { + (response.user_syncs || []).forEach((syncUrl) => { + if (syncUrl.type === 'image') { + pixelSyncs.push({ url: syncUrl.url, type: 'image' }); + } + + if (syncUrl.type === 'iframe') { + iframeSyncs.push({ url: syncUrl.url, type: 'iframe' }); + } + }); + }); + + if (syncOptions.iframeEnabled) return iframeSyncs; + return pixelSyncs; + } + + return []; + }, }; registerBidder(spec); diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 7c871b0d536..7ff3fd47d74 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -1,4 +1,4 @@ -# Overview +### Overview ``` Module Name: Dailymotion Bid Adapter @@ -6,12 +6,12 @@ Module Type: Bidder Adapter Maintainer: ad-leo-engineering@dailymotion.com ``` -# Description +### Description Dailymotion prebid adapter. Supports video ad units in instream context. -# Configuration options +### Configuration options Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters: @@ -36,7 +36,29 @@ const adUnits = [ `apiKey` is your publisher API key. For testing purpose, you can use "dailymotion-testing". -# Test Parameters +#### User Sync + +To enable user synchronization, add the following code. Dailymotion highly recommends using iframes and/or pixels for user syncing. This feature enhances DSP user match rates, resulting in higher bid rates and bid prices. Ensure that `pbjs.setConfig()` is called only once. + +```javascript +pbjs.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: '*', // Or add dailymotion to your list included bidders + filter: 'include' + }, + image: { + bidders: '*', // Or add dailymotion to your list of included bidders + filter: 'include' + }, + }, + }, +}); +``` + +### Test Parameters By setting the following bid parameters, you'll get a constant response to any request, to validate your adapter integration: @@ -61,7 +83,7 @@ const adUnits = [ Please note that failing to set these will result in the adapter not bidding at all. -# Sample video AdUnit +### Sample video AdUnit To allow better targeting, you should provide as much context about the video as possible. There are three ways of doing this depending on if you're using Dailymotion player or a third party one. @@ -118,6 +140,10 @@ const adUnits = [ tags: 'tag_1,tag_2,tag_3', title: 'test video', topics: 'topic_1, topic_2', + isCreatedForKids: false, + videoViewsInSession: 1, + autoplay: false, + playerVolume: 8 } } }], @@ -126,6 +152,12 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [7, 8, 11, 12, 13, 14] startdelay: 0, w: 1280, h: 720, @@ -147,10 +179,18 @@ Each of the following video metadata fields can be added in bids.params.video. * `private` - True if video is not publicly available * `tags` - Tags for the video, comma separated * `title` - Video title +* `url` - URL of the content * `topics` - Main topics for the video, comma separated * `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) +* `isCreatedForKids` - [The content is created for children as primary audience](https://faq.dailymotion.com/hc/en-us/articles/360020920159-Content-created-for-kids) + +The following contextual informations can also be added in bids.params.video. -If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will fallback to those values when possible. See the mapping below. +* `videoViewsInSession` - Number of videos viewed within the current user session +* `autoplay` - Playback was launched without user interaction +* `playerVolume` - Player volume between 0 (muted, 0%) and 10 (100%) + +If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will collect the following values and fallback to bids.params.video values when applicable. See the mapping below. | From ortb2 | Metadata fields | |---------------------------------------------------------------------------------|-----------------| @@ -161,8 +201,14 @@ If you already specify [First-Party data](https://docs.prebid.org/features/first | `ortb2.site.content.livestream` | `livestream` | | `ortb2.site.content.keywords` | `tags` | | `ortb2.site.content.title` | `title` | - -# Integrating the adapter +| `ortb2.site.content.url` | `url` | +| `ortb2.app.bundle` | N/A | +| `ortb2.app.storeurl` | N/A | +| `ortb2.device.lmt` | N/A | +| `ortb2.device.ifa` | N/A | +| `ortb2.device.ext.atts` | N/A | + +### Integrating the adapter To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index fe9484b2814..0fa199ed034 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -90,13 +90,15 @@ describe('dailymotionBidAdapterTests', () => { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + playbackmethod: [3], + plcmt: 1, protocols: [1, 2, 3, 4, 5, 6, 7, 8], skip: 1, skipafter: 5, skipmin: 10, startdelay: 0, w: 1280, - h: 720 + h: 720, }, }, sizes: [[1920, 1080]], @@ -112,9 +114,14 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', topics: 'topic_1, topic_2', xid: 'x123456', livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, }, }, }]; @@ -160,6 +167,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -170,9 +178,8 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.true; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + + expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-1'], @@ -182,10 +189,18 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidRequestData[0].params.video.tags, title: bidRequestData[0].params.video.title, + url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidRequestData[0].params.video.livestream, + isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, + context: { + siteOrAppCat: '', + videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, + autoplay: bidRequestData[0].params.video.autoplay, + playerVolume: bidRequestData[0].params.video.playerVolume, + }, }); }); @@ -200,6 +215,8 @@ describe('dailymotionBidAdapterTests', () => { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + playbackmethod: [3], + plcmt: 1, protocols: [1, 2, 3, 4, 5, 6, 7, 8], skip: 1, skipafter: 5, @@ -220,9 +237,15 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', topics: 'topic_1, topic_2', xid: 'x123456', livestream: 1, + // Test invalid values + isCreatedForKids: 'false', + videoViewsInSession: -1, + autoplay: 'true', + playerVolume: 12, }, }, }]; @@ -245,6 +268,13 @@ describe('dailymotionBidAdapterTests', () => { regs: { coppa: 1, }, + device: { + lmt: 1, + ifa: 'xxx', + ext: { + atts: 2, + }, + }, app: { bundle: 'app-bundle', storeurl: 'https://play.google.com/store/apps/details?id=app-bundle', @@ -276,6 +306,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -286,19 +317,14 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.true; expect(reqData.appBundle).to.eql(bidderRequestData.ortb2.app.bundle); expect(reqData.appStoreUrl).to.eql(bidderRequestData.ortb2.app.storeurl); + expect(reqData.device.lmt).to.eql(bidderRequestData.ortb2.device.lmt); + expect(reqData.device.ifa).to.eql(bidderRequestData.ortb2.device.ifa); + expect(reqData.device.atts).to.eql(bidderRequestData.ortb2.device.ext.atts); expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.mimes).to.eql(bidRequestData[0].mediaTypes.video.mimes); - expect(reqData.request.mediaTypes.video.minduration).to.eql(bidRequestData[0].mediaTypes.video.minduration); - expect(reqData.request.mediaTypes.video.maxduration).to.eql(bidRequestData[0].mediaTypes.video.maxduration); - expect(reqData.request.mediaTypes.video.protocols).to.eql(bidRequestData[0].mediaTypes.video.protocols); - expect(reqData.request.mediaTypes.video.skip).to.eql(bidRequestData[0].mediaTypes.video.skip); - expect(reqData.request.mediaTypes.video.skipafter).to.eql(bidRequestData[0].mediaTypes.video.skipafter); - expect(reqData.request.mediaTypes.video.skipmin).to.eql(bidRequestData[0].mediaTypes.video.skipmin); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); - expect(reqData.request.mediaTypes.video.w).to.eql(bidRequestData[0].mediaTypes.video.w); - expect(reqData.request.mediaTypes.video.h).to.eql(bidRequestData[0].mediaTypes.video.h); + + expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); + expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-1'], @@ -308,11 +334,19 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidRequestData[0].params.video.tags, title: bidRequestData[0].params.video.title, + url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, // Overriden through bidder params duration: bidderRequestData.ortb2.app.content.len, livestream: !!bidRequestData[0].params.video.livestream, + isCreatedForKids: null, + context: { + siteOrAppCat: '', + videoViewsInSession: null, + autoplay: null, + playerVolume: null, + }, }); }); @@ -337,6 +371,10 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', topics: 'topic_1, topic_2', xid: 'x123456', + isCreatedForKids: false, + videoViewsInSession: 10, + autoplay: false, + playerVolume: 0, }, }, }]; @@ -363,6 +401,7 @@ describe('dailymotionBidAdapterTests', () => { language: 'FR', keywords: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', livestream: 1, cat: ['IAB-2'], data: [ @@ -419,6 +458,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -432,9 +472,22 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.false; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + + expect(reqData.request.mediaTypes.video).to.eql({ + ...bidRequestData[0].mediaTypes.video, + mimes: [], + minduration: 0, + maxduration: 0, + playbackmethod: [], + plcmt: 1, + protocols: [], + skip: 0, + skipafter: 0, + skipmin: 0, + w: 0, + h: 0, + }); + expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-2'], @@ -444,10 +497,18 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidderRequestData.ortb2.site.content.keywords, title: bidderRequestData.ortb2.site.content.title, + url: bidderRequestData.ortb2.site.content.url, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidderRequestData.ortb2.site.content.livestream, + isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, + context: { + siteOrAppCat: bidderRequestData.ortb2.site.content.cat, + videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, + autoplay: bidRequestData[0].params.video.autoplay, + playerVolume: bidRequestData[0].params.video.playerVolume, + }, }); }); @@ -470,6 +531,7 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.config.api_key).to.eql(bidRequestDataWithApi[0].params.apiKey); expect(reqData.coppa).to.be.false; + expect(reqData.pbv).to.eql('$prebid.version$'); expect(reqData.bidder_request).to.eql({ gdprConsent: { apiVersion: 1, @@ -496,6 +558,8 @@ describe('dailymotionBidAdapterTests', () => { mimes: [], minduration: 0, maxduration: 0, + playbackmethod: [], + plcmt: 1, protocols: [], skip: 0, skipafter: 0, @@ -518,9 +582,17 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: '', title: '', + url: '', topics: '', xid: '', livestream: false, + isCreatedForKids: null, + context: { + siteOrAppCat: '', + videoViewsInSession: null, + autoplay: null, + playerVolume: null, + }, }); }); @@ -557,4 +629,83 @@ describe('dailymotionBidAdapterTests', () => { expect(spec.interpretResponse(undefined)).to.have.lengthOf(0); }); + + it('validates getUserSyncs', () => { + // Nothing sent in getUserSyncs + expect(config.runWithBidder('dailymotion', () => spec.getUserSyncs())).to.eql([]); + + // No server response + { + const responses = []; + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // No permissions + { + const responses = [{ user_syncs: [{ url: 'https://usersyncurl.com', type: 'image' }] }]; + const syncOptions = { iframeEnabled: false, pixelEnabled: false }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // Has permissions but no user_syncs urls + { + const responses = [{}]; + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // Return user_syncs urls for pixels + { + const responses = [{ + user_syncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + }]; + + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([ + { type: 'image', url: 'https://usersyncurl.com' }, + { type: 'image', url: 'https://usersyncurl2.com' }, + ]); + } + + // Return user_syncs urls for iframes + { + const responses = [{ + user_syncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + }]; + + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([ + { type: 'iframe', url: 'https://usersyncurl3.com' }, + ]); + } + }); }); From 5b7df85d3c0a51b1016138b45c25d6dfff62effa Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:55:24 +0200 Subject: [PATCH 0154/1097] ZetaGlobalSsp Analytics Adapter : provide device object (#11607) * ZetaGlobalSpp Analytics adapter: provide device object * ZetaGlobalSpp Analytics adapter: provide ua in adRenderSucceeded event * provide domain and page in timeout event --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 8 ++- .../zeta_global_sspAnalyticsAdapter_spec.js | 62 ++++++++++++------- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 2ba119b4d35..31bcffcd7b1 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -42,6 +42,9 @@ function adRenderSucceededHandler(args) { adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, cpm: args.bid?.cpm + }, + device: { + ua: navigator.userAgent } } sendEvent(EVENTS.AD_RENDER_SUCCEEDED, event); @@ -59,7 +62,8 @@ function auctionEndHandler(args) { auctionId: b?.auctionId, bidder: b?.bidder, mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), - size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s) + size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), + device: b?.ortb2?.device })) })), bidsReceived: args.bidsReceived?.map(br => ({ @@ -80,6 +84,8 @@ function auctionEndHandler(args) { function bidTimeoutHandler(args) { const event = { zetaParams: zetaParams, + domain: args.find(t => t?.ortb2?.site?.domain), + page: args.find(t => t?.ortb2?.site?.page), timeouts: args.map(t => ({ bidId: t?.bidId, auctionId: t?.auctionId, diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 604bc780d6b..4331060b1cd 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -107,7 +107,12 @@ const SAMPLE_EVENTS = { 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 + 'bidderWinsCount': 0, + 'ortb2': { + 'device': { + 'mobile': 1 + } + } } ], 'auctionStart': 1638441234544, @@ -168,7 +173,12 @@ const SAMPLE_EVENTS = { 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 + 'bidderWinsCount': 0, + 'ortb2': { + 'device': { + 'mobile': 1 + } + } } ], 'auctionStart': 1638441234544, @@ -395,8 +405,6 @@ describe('Zeta Global SSP Analytics Adapter', function () { expect(requests.length).to.equal(2); const auctionEnd = JSON.parse(requests[0].requestBody); - const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionEnd).to.be.deep.equal({ zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, bidderRequests: [{ @@ -408,7 +416,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { auctionId: '75e394d9', bidder: 'zeta_global_ssp', mediaType: 'BANNER', - size: '300x250' + size: '300x250', + device: { + mobile: 1 + } }] }, { bidderCode: 'appnexus', @@ -419,7 +430,10 @@ describe('Zeta Global SSP Analytics Adapter', function () { auctionId: '75e394d9', bidder: 'appnexus', mediaType: 'BANNER', - size: '300x250' + size: '300x250', + device: { + mobile: 1 + } }] }], bidsReceived: [{ @@ -434,23 +448,29 @@ describe('Zeta Global SSP Analytics Adapter', function () { cpm: 2.258302852806723 }] }); - expect(auctionSucceeded).to.be.deep.equal({ - zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, - domain: 'test-zeta-ssp.net', - page: 'test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html', - bid: { - adId: '5759bb3ef7be1e8', - requestId: '206be9a13236af', - auctionId: '75e394d9', - creativeId: '456456456', - bidder: 'zeta_global_ssp', - mediaType: 'banner', - size: '480x320', - adomain: 'example.adomain', - timeToRespond: 123, - cpm: 2.258302852806723 + const auctionSucceeded = JSON.parse(requests[1].requestBody); + expect(auctionSucceeded.zetaParams).to.be.deep.equal({ + sid: 111, + tags: { + position: 'top', + shortname: 'name' } }); + expect(auctionSucceeded.domain).to.eql('test-zeta-ssp.net'); + expect(auctionSucceeded.page).to.eql('test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html'); + expect(auctionSucceeded.bid).to.be.deep.equal({ + adId: '5759bb3ef7be1e8', + requestId: '206be9a13236af', + auctionId: '75e394d9', + creativeId: '456456456', + bidder: 'zeta_global_ssp', + mediaType: 'banner', + size: '480x320', + adomain: 'example.adomain', + timeToRespond: 123, + cpm: 2.258302852806723 + }); + expect(auctionSucceeded.device.ua).to.not.be.empty; }); }); }); From 47e6561919680454d1b57b06315744852a7e07f8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 4 Jun 2024 16:54:27 -0400 Subject: [PATCH 0155/1097] Core Utils: fix jsdoc warnings (#11694) * Update utils.js * Update perfMetrics.js * Update ttlCollection.js * Update adpod.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagement.js * Update consentManagementUsp.js * Update currency.js * Update dfpAdServerVideo.js * Update instreamTracking.js * Update s2sTesting.js * Update sizeMapping.js * Update topicsFpdModule.js * Update uid2IdSystem.js * Update consentManagementUsp.js * Update sizeMapping.js --- modules/adpod.js | 9 ++++---- modules/consentManagement.js | 10 ++++++--- modules/consentManagementGpp.js | 7 +++--- modules/consentManagementUsp.js | 10 +++++---- modules/currency.js | 1 + modules/dfpAdServerVideo.js | 6 ++--- modules/instreamTracking.js | 7 +++--- modules/s2sTesting.js | 2 +- modules/sizeMapping.js | 22 ++++++++++-------- modules/topicsFpdModule.js | 3 ++- modules/uid2IdSystem.js | 2 +- src/utils.js | 15 ++++++------- src/utils/perfMetrics.js | 40 +++++++++++++++++---------------- src/utils/ttlCollection.js | 10 ++++----- 14 files changed, 79 insertions(+), 65 deletions(-) diff --git a/modules/adpod.js b/modules/adpod.js index b6d13673178..35a78766979 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -451,7 +451,6 @@ export function callPrebidCacheAfterAuction(bids, callback) { /** * Compare function to be used in sorting long-form bids. This will compare bids on price per second. * @param {Object} bid - * @param {Object} bid */ export function sortByPricePerSecond(a, b) { if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { @@ -465,10 +464,10 @@ export function sortByPricePerSecond(a, b) { /** * This function returns targeting keyvalue pairs for long-form adserver modules. Freewheel and GAM are currently supporting Prebid long-form - * @param {Object} options - * @param {Array[string]} codes - * @param {function} callback - * @returns targeting kvs for adUnitCodes + * @param {Object} options - Options for targeting. + * @param {Array} options.codes - Array of ad unit codes. + * @param {function} options.callback - Callback function to handle the targeting key-value pairs. + * @returns {Object} Targeting key-value pairs for ad unit codes. */ export function getTargeting({ codes, callback } = {}) { if (!callback) { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 346b241fc1f..c7c618635ba 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -35,7 +35,9 @@ const cmpCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when the value is read from config; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} [options.onError] - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). Optional. */ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) @@ -45,8 +47,10 @@ function lookupStaticConsentData({onSuccess, onError}) { * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP - * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). + * @param {function(Object): void} options.onEvent - Acts as an event callback for processing TCF data events from CMP. */ function lookupIabConsent({onSuccess, onError, onEvent}) { function cmpResponseCallback(tcfData, success) { diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index a7bbca62205..f94048813c6 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -304,8 +304,10 @@ class GPP11Client extends GPPClient { * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP - * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). + * @param {function(): Object} [mkCmp=cmpClient] - A function to create the CMP client. Defaults to `cmpClient`. */ export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) { pipeCallbacks(() => GPPClient.init(mkCmp).then(([client, gppDataPm]) => gppDataPm), {onSuccess, onError}); @@ -434,7 +436,6 @@ function processCmpData(consentData) { /** * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction * @param {{}} gppData the result of calling a CMP's `getGPPData` (or equivalent) - * @param {{}} sectionData map from GPP section name to the result of calling a CMP's `getSection` (or equivalent) */ export function storeConsentData(gppData = {}) { consentData = { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 78ec13cb891..29a67af0631 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -163,10 +163,12 @@ export const requestBidsHook = timedAuctionHook('usp', function requestBidsHook( /** * This function checks the consent data provided by USPAPI to ensure it's in an expected state. * If it's bad, we exit the module depending on config settings. - * If it's good, then we store the value and exits the module. - * @param {object} consentObject required; object returned by USPAPI that contains user's consent choices - * @param {function(string)} onSuccess callback accepting the resolved consent USP consent string - * @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging) + * If it's good, then we store the value and exit the module. + * + * @param {Object} consentObject - The object returned by USPAPI that contains the user's consent choices. + * @param {Object} callbacks - An object containing the callback functions. + * @param {function(string): void} callbacks.onSuccess - Callback accepting the resolved USP consent string. + * @param {function(string, ...Object?): void} callbacks.onError - Callback accepting an error message and any extra error arguments (used purely for logging). */ function processUspData(consentObject, {onSuccess, onError}) { const valid = !!(consentObject && consentObject.usPrivacy); diff --git a/modules/currency.js b/modules/currency.js index e2da8b519fa..aa464b81f9a 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -28,6 +28,7 @@ export let responseReady = defer(); /** * Configuration function for currency + * @param {object} config * @param {string} [config.adServerCurrency = 'USD'] * ISO 4217 3-letter currency code that represents the target currency. (e.g. 'EUR'). If this value is present, * the currency conversion feature is activated. diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index abf58aceb45..6314ed15ff9 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -308,7 +308,7 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { * Builds a video url from a base dfp video url and a winning bid, appending * Prebid-specific key-values. * @param {Object} components base video adserver url parsed into components object - * @param {AdapterBidResponse} bid winning bid object to append parameters from + * @param {Object} bid winning bid object to append parameters from * @param {Object} options Options which should be used to construct the URL (used for custom params). * @return {string} video url */ @@ -325,7 +325,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { /** * Returns the encoded vast url if it exists on a bid object, only if prebid-cache * is disabled, and description_url is not already set on a given input - * @param {AdapterBidResponse} bid object to check for vast url + * @param {Object} bid object to check for vast url * @param {Object} components the object to check that description_url is NOT set on * @param {string} prop the property of components that would contain description_url * @return {string | undefined} The encoded vast url if it exists, or undefined @@ -336,7 +336,7 @@ function getDescriptionUrl(bid, components, prop) { /** * Returns the encoded `cust_params` from the bid.adserverTargeting and adds the `hb_uuid`, and `hb_cache_id`. Optionally the options.params.cust_params - * @param {AdapterBidResponse} bid + * @param {Object} bid * @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function * @return {Object} Encoded key value pairs for cust_params */ diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index 2686feab679..909c21b29bd 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -41,9 +41,10 @@ const whitelistedResources = /video|fetch|xmlhttprequest|other/; * * Note: this is a workaround till a better approach is engineered. * - * @param {Array} adUnits - * @param {Array} bidsReceived - * @param {Array} bidderRequests + * @param {object} config + * @param {Array} config.adUnits + * @param {Array} config.bidsReceived + * @param {Array} config.bidderRequests * * @return {boolean} returns TRUE if tracking started */ diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index 8e9628c8810..4c78b62d710 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -44,7 +44,7 @@ s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) { /** * @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level - * @param s2sConfigs server-to-server configuration + * @param s2sConfig server-to-server configuration */ s2sTesting.calculateBidSources = function(s2sConfig = {}) { // calculate bid source (server/client) for each s2s bidder diff --git a/modules/sizeMapping.js b/modules/sizeMapping.js index fcd0b0963f2..eab85aa3d93 100644 --- a/modules/sizeMapping.js +++ b/modules/sizeMapping.js @@ -35,7 +35,7 @@ config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig)); * Returns object describing the status of labels on the adUnit or bidder along with labels passed into requestBids * @param bidOrAdUnit the bidder or adUnit to get label info on * @param activeLabels the labels passed to requestBids - * @returns {LabelDescriptor} + * @returns {object} */ export function getLabels(bidOrAdUnit, activeLabels) { if (bidOrAdUnit.labelAll) { @@ -66,14 +66,18 @@ if (FEATURES.VIDEO) { } /** - * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match - * @param {Array} labels Labels specified on adUnit or bidder - * @param {boolean} labelAll if true, all labels must match to be enabled - * @param {Array} activeLabels Labels passed in through requestBids - * @param {object} mediaTypes A mediaTypes object describing the various media types (banner, video, native) - * @param {Array>} sizes Sizes specified on adUnit (deprecated) - * @param {Array} configs - * @returns {{labels: Array, sizes: Array>}} + * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match. + * + * @param {Object} options - The options object. + * @param {Array} [options.labels=[]] - Labels specified on adUnit or bidder. + * @param {boolean} [options.labelAll=false] - If true, all labels must match to be enabled. + * @param {Array} [options.activeLabels=[]] - Labels passed in through requestBids. + * @param {Object} mediaTypes - A mediaTypes object describing the various media types (banner, video, native). + * @param {Array} configs - An array of SizeConfig objects. + * @returns {Object} - An object containing the active status, media types, and filter results. + * @returns {boolean} return.active - Whether the media types are active. + * @returns {Object} return.mediaTypes - The media types object. + * @returns {Object} [return.filterResults] - The filter results before and after applying size filtering. */ export function resolveStatus({labels = [], labelAll = false, activeLabels = []} = {}, mediaTypes, configs = sizeConfig) { let maps = evaluateSizeConfig(configs); diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 523c0db326a..be3e8444dae 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -201,7 +201,8 @@ export function receiveMessage(evt) { /** Function to store Topics data received from iframe in storage(name: "prebid:topics") - * @param {Topics} topics + * @param {string} bidder + * @param {object} topics */ export function storeInLocalStorage(bidder, topics) { const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 061f0f981e5..1ce9b0f5a09 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -69,7 +69,7 @@ export const uid2IdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleConfig} [configparams] + * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @returns {uid2Id} */ diff --git a/src/utils.js b/src/utils.js index 12656951c21..2affc52ab8c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -382,7 +382,8 @@ export function isEmptyStr(str) { * Iterate object with the function * falls back to es5 `forEach` * @param {Array|Object} object - * @param {Function(value, key, object)} fn + * @param {Function} fn - The function to execute for each element. It receives three arguments: value, key, and the original object. + * @returns {void} */ export function _each(object, fn) { if (isFn(object?.forEach)) return object.forEach(fn, this); @@ -397,7 +398,7 @@ export function contains(a, obj) { * Map an array or object into another array * given a function * @param {Array|Object} object - * @param {Function(value, key, object)} callback + * @param {Function} callback - The function to execute for each element. It receives three arguments: value, key, and the original object. * @return {Array} */ export function _map(object, callback) { @@ -500,7 +501,6 @@ export function insertHtmlIntoIframe(htmlCode) { /** * Inserts empty iframe with the specified `url` for cookie sync * @param {string} url URL to be requested - * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process * @param {Number} [timeout] an optional timeout in milliseconds for the iframe to load before calling `done` */ @@ -737,7 +737,6 @@ export function delayExecution(func, numRequiredCalls) { /** * https://stackoverflow.com/a/34890276/428704 - * @export * @param {Array} xs * @param {string} key * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}} @@ -953,9 +952,9 @@ export function buildUrl(obj) { * This function deeply compares two objects checking for their equivalence. * @param {Object} obj1 * @param {Object} obj2 - * @param checkTypes {boolean} if set, two objects with identical properties but different constructors will *not* - * be considered equivalent. - * @returns {boolean} + * @param {Object} [options] - Options for comparison. + * @param {boolean} [options.checkTypes=false] - If set, two objects with identical properties but different constructors will *not* be considered equivalent. + * @returns {boolean} - Returns `true` if the objects are equivalent, `false` otherwise. */ export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { if (obj1 === obj2) return true; @@ -1090,7 +1089,7 @@ export function memoize(fn, key = function (arg) { return arg; }) { /** * Sets dataset attributes on a script - * @param {Script} script + * @param {HTMLScriptElement} script * @param {object} attributes */ export function setScriptAttributes(script, attributes) { diff --git a/src/utils/perfMetrics.js b/src/utils/perfMetrics.js index b1fdb38effe..d0736b71554 100644 --- a/src/utils/perfMetrics.js +++ b/src/utils/perfMetrics.js @@ -63,9 +63,9 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make /** * Get the tame passed since `checkpoint`, and optionally save it as a metric. * - * @param checkpoint checkpoint name - * @param metric? metric name - * @return {number} time between now and `checkpoint` + * @param {string} checkpoint checkpoint name + * @param {string} [metric] - The name of the metric to save. Optional. + * @returns {number|null} - The time in milliseconds between now and the checkpoint, or `null` if the checkpoint is not found. */ function timeSince(checkpoint, metric) { const ts = getTimestamp(checkpoint); @@ -79,10 +79,10 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make /** * Get the time passed between `startCheckpoint` and `endCheckpoint`, optionally saving it as a metric. * - * @param startCheckpoint begin checkpoint - * @param endCheckpoint end checkpoint - * @param metric? metric name - * @return {number} time passed between `startCheckpoint` and `endCheckpoint` + * @param {string} startCheckpoint - The name of the starting checkpoint. + * @param {string} endCheckpoint - The name of the ending checkpoint. + * @param {string} [metric] - The name of the metric to save. Optional. + * @returns {number|null} - The time in milliseconds between `startCheckpoint` and `endCheckpoint`, or `null` if either checkpoint is not found. */ function timeBetween(startCheckpoint, endCheckpoint, metric) { const start = getTimestamp(startCheckpoint); @@ -128,12 +128,12 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make } /** - * @typedef {function: T} HookFn - * @property {function(T): void} bail + * @typedef {Function} HookFn + * @property {Function(T): void} bail * * @template T - * @typedef {T: HookFn} TimedHookFn - * @property {function(): void} stopTiming + * @typedef {HookFn} TimedHookFn + * @property {Function(): void} stopTiming * @property {T} untimed */ @@ -141,12 +141,12 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * Convenience method for measuring time spent in a `.before` or `.after` hook. * * @template T - * @param name metric name - * @param {HookFn} next the hook's `next` (first) argument - * @param {function(TimedHookFn): T} fn a function that will be run immediately; it takes `next`, + * @param {string} name - The metric name. + * @param {HookFn} next - The hook's `next` (first) argument. + * @param {function(TimedHookFn): T} fn - A function that will be run immediately; it takes `next`, * where both `next` and `next.bail` automatically * call `stopTiming` before continuing with the original hook. - * @return {T} fn's return value + * @return {T} - The return value of `fn`. */ function measureHookTime(name, next, fn) { const stopTiming = startTiming(name); @@ -208,10 +208,11 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * ``` * * - * @param propagate if false, the forked metrics will not be propagated here - * @param stopPropagation if true, propagation from the new metrics is stopped here - instead of - * continuing up the chain (if for example these metrics were themselves created through `.fork()`) - * @param includeGroups if true, the forked metrics will also replicate metrics that were propagated + * @param {Object} [options={}] - Options for forking the metrics. + * @param {boolean} [options.propagate=true] - If false, the forked metrics will not be propagated here. + * @param {boolean} [options.stopPropagation=false] - If true, propagation from the new metrics is stopped here, instead of + * continuing up the chain (if for example these metrics were themselves created through `.fork()`). + * @param {boolean} [options.includeGroups=false] - If true, the forked metrics will also replicate metrics that were propagated * here from elsewhere. For example: * ``` * const metrics = newMetrics(); @@ -222,6 +223,7 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * withoutGroups.getMetrics() // {} * withGroups.getMetrics() // {foo: ['bar']} * ``` + * @returns {Object} - The new metrics object. */ function fork({propagate = true, stopPropagation = false, includeGroups = false} = {}) { return makeMetrics(mkNode([[self, {propagate, stopPropagation, includeGroups}]]), rename); diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index dfad7f01933..2294c072108 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -18,12 +18,12 @@ import {binarySearch, logError, timestamp} from '../utils.js'; * @param {number} [slack=5000] - Maximum duration (in milliseconds) that an item is allowed to persist * once past its TTL. This is also roughly the interval between "garbage collection" sweeps. * @returns {Object} A set-like collection with automatic TTL expiration. - * @returns {function(*)} return.add - Add an item to the collection. - * @returns {function()} return.clear - Clear the collection. + * @returns {function(*): void} return.add - Add an item to the collection. + * @returns {function(): void} return.clear - Clear the collection. * @returns {function(): Array<*>} return.toArray - Get all the items in the collection, in insertion order. - * @returns {function()} return.refresh - Refresh the TTL for each item in the collection. - * @returns {function(function(*))} return.onExpiry - Register a callback to be run when an item has expired and is about to be - * removed from the collection. + * @returns {function(): void} return.refresh - Refresh the TTL for each item in the collection. + * @returns {function(function(*)): function(): void} return.onExpiry - Register a callback to be run when an item has expired and is about to be + * removed from the collection. Returns an un-registration function */ export function ttlCollection( { From e802e21a78aac71f7f9eb045ee6f8f12b99bf626 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 5 Jun 2024 09:17:27 -0400 Subject: [PATCH 0156/1097] datablocksBidAdapter.js: fix syncs issue (#11684) fixes https://github.com/prebid/Prebid.js/issues/11319 --- modules/datablocksBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 395706994fe..088dfd32979 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -417,7 +417,7 @@ export const spec = { // INITIATE USER SYNCING getUserSyncs: function(options, rtbResponse, gdprConsent) { const syncs = []; - let bidResponse = rtbResponse[0].body; + let bidResponse = rtbResponse?.[0]?.body ?? null; let scope = this; // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC From 13b87fad140d101dd1728983f67de234df683fae Mon Sep 17 00:00:00 2001 From: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:42:15 +0530 Subject: [PATCH 0157/1097] ColossusSSP Bid Adapter : replace gpid for pbadslot (#11701) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data * gpp support * accepting eids from request * fixing lint errors * resolving a conflict * fixing a failed test case related to tid * fixing karma version for conflict resolution * reverting package json files to original version * switching placement to plcmt * replacing gpid for pbadslot --------- Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally --- modules/colossussspBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 3bc6c772bc6..c69e484feb3 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -147,7 +147,7 @@ export const spec = { if (bid.schain) { placement.schain = bid.schain; } - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { placement.gpid = gpid; } From 105c6620a4882dd59245b1a7c264414efbdbe3b1 Mon Sep 17 00:00:00 2001 From: Gena Date: Wed, 5 Jun 2024 18:15:01 +0300 Subject: [PATCH 0158/1097] Bidmatic Bid Adapter: Initial Release (#11690) * Bidmatic Initial commit * Use getFloor from price module --- modules/bidmaticBidAdapter.js | 110 ++++++++ modules/bidmaticBidAdapter.md | 26 ++ package-lock.json | 2 +- test/spec/modules/bidmaticBidAdapter_spec.js | 268 +++++++++++++++++++ 4 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 modules/bidmaticBidAdapter.js create mode 100644 modules/bidmaticBidAdapter.md create mode 100644 test/spec/modules/bidmaticBidAdapter_spec.js diff --git a/modules/bidmaticBidAdapter.js b/modules/bidmaticBidAdapter.js new file mode 100644 index 00000000000..8c22d70c632 --- /dev/null +++ b/modules/bidmaticBidAdapter.js @@ -0,0 +1,110 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { replaceAuctionPrice, isNumber, deepAccess, isFn } from '../src/utils.js'; + +export const END_POINT = 'https://adapter.bidmatic.io/ortb-client'; +const BIDDER_CODE = 'bidmatic'; +const DEFAULT_CURRENCY = 'USD'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 290, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floorInfo = isFn(bidRequest.getFloor) ? bidRequest.getFloor({ + currency: context.currency || 'USD', + size: '*', + mediaType: '*' + }) : { + floor: imp.bidfloor || deepAccess(bidRequest, 'params.bidfloor') || 0, + currency: DEFAULT_CURRENCY + }; + + if (floorInfo) { + imp.bidfloor = floorInfo.floor; + imp.bidfloorcur = floorInfo.currency; + } + imp.tagid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || bidRequest.adUnitCode; + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + if (!request.cur) { + request.cur = [DEFAULT_CURRENCY]; + } + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + + let resMediaType; + const reqMediaTypes = Object.keys(bidRequest.mediaTypes); + if (reqMediaTypes.length === 1) { + resMediaType = reqMediaTypes[0]; + } else { + if (bid.adm.search(/^(<\?xml| { + acc[bidRequest.params.source] = acc[bidRequest.params.source] || []; + acc[bidRequest.params.source].push(bidRequest); + return acc; + }, {}); + + return Object.entries(requestsBySource).map(([source, bidRequests]) => { + const data = converter.toORTB({ bidRequests, bidderRequest }); + const url = new URL(END_POINT); + url.searchParams.append('source', source); + return { + method: 'POST', + url: url.toString(), + data: data, + options: { + withCredentials: true, + } + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body) return []; + const parsedSeatbid = serverResponse.body.seatbid.map(seatbidItem => { + const parsedBid = seatbidItem.bid.map((bidItem) => ({ + ...bidItem, + adm: replaceAuctionPrice(bidItem.adm, bidItem.price), + nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price) + })); + return { ...seatbidItem, bid: parsedBid }; + }); + const responseBody = { ...serverResponse.body, seatbid: parsedSeatbid }; + return converter.fromORTB({ + response: responseBody, + request: bidRequest.data, + }).bids; + }, + +}; +registerBidder(spec); diff --git a/modules/bidmaticBidAdapter.md b/modules/bidmaticBidAdapter.md new file mode 100644 index 00000000000..d248b386ea3 --- /dev/null +++ b/modules/bidmaticBidAdapter.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: Bidmatic Bid Adapter +Module Type: Bidder Adapter +Maintainer: mg@bidmatic.io +``` + +# Description + +Adds access to Bidmatic SSP oRTB service. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'bg-test-rectangle', + sizes: [[300, 250]], + bids: [{ + bidder: 'bidmatic', + params: { + source: 886409, + bidfloor: 0.1 + } + }] +}] +``` diff --git a/package-lock.json b/package-lock.json index dd9e3ad9d27..36afa9c25ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.49.0-pre", + "version": "8.52.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/bidmaticBidAdapter_spec.js b/test/spec/modules/bidmaticBidAdapter_spec.js new file mode 100644 index 00000000000..dcf35d032ea --- /dev/null +++ b/test/spec/modules/bidmaticBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { expect } from 'chai'; +import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js'; +import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils'; + +const expectedImp = { + 'id': '2eb89f0f062afe', + 'banner': { + 'topframe': 0, + 'format': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ] + }, + 'bidfloor': 0, + 'bidfloorcur': 'USD', + 'tagid': 'div-gpt-ad-1460505748561-0' +} + +describe('Bidmatic Bid Adapter', () => { + const GPID_RTB_EXT = { + 'ortb2Imp': { + 'ext': { + 'gpid': 'gpId', + } + }, + } + const FLOOR_RTB_EXT = { + 'ortb2Imp': { + bidfloor: 1 + }, + } + const DEFAULT_BID_REQUEST = { + 'id': '10bb57ee-712f-43e9-9769-b26d03df8a39', + 'bidder': 'bidmatic', + 'params': { + 'source': 886409, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '7d79850b-70aa-4c0f-af95-c1def0452825', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2eb89f0f062afe', + 'bidderRequestId': '1ae6c8e18f8462', + 'auctionId': '1286637c-51bc-4fdd-8e35-2435ec11775a', + 'ortb2': {} + }; + + describe('adapter interface', () => { + const bidRequest = deepClone(DEFAULT_BID_REQUEST); + + it('should validate params', () => { + expect(spec.isBidRequestValid({ + params: { + source: 1 + } + })).to.equal(true, 'source param must be a number'); + + expect(spec.isBidRequestValid({ + params: { + source: '1' + } + })).to.equal(false, 'source param must be a number'); + + expect(spec.isBidRequestValid({})).to.equal(false, 'source param must be a number'); + }); + + it('should build hb request', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0]).to.deep.equal(expectedImp); + expect(ortbRequest.data.cur).to.deep.equal(['USD']); + }); + + it('should request with source in url', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + expect(ortbRequest.url).to.equal(`${END_POINT}?source=886409`); + }); + + it('should split http reqs by sources', () => { + const bidRequest2 = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + params: { + source: 1111 + } + }); + const [ortbRequest1, ortbRequest2] = spec.buildRequests([bidRequest2, bidRequest, bidRequest2], { + bids: [bidRequest2, bidRequest, bidRequest2] + }); + expect(ortbRequest1.url).to.equal(`${END_POINT}?source=1111`); + expect(ortbRequest1.data.imp.length).to.eq(2) + expect(ortbRequest2.url).to.equal(`${END_POINT}?source=886409`); + expect(ortbRequest2.data.imp.length).to.eq(1) + }); + + it('should grab bid floor info', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(0) + expect(ortbRequest.data.imp[0].bidfloorcur).eq('USD') + }); + + it('should grab bid floor info from exts', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), FLOOR_RTB_EXT); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(1) + }); + + it('should grab bid floor info from params', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + params: { + bidfloor: 2 + } + }); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(2) + }); + + it('should set gpid as tagid', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), GPID_RTB_EXT); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].tagid).eq(GPID_RTB_EXT.ortb2Imp.ext.gpid) + }); + }) + + describe('response interpreter', () => { + const SERVER_RESPONSE = { + 'body': { + 'id': '10bb57ee-712f-43e9-9769-b26d03df8a39', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'c5BsBD5QHHgx4aS8', + 'impid': '2eb89f0f062afe', + 'price': 1, + 'adid': 'BDhclfXLcGzRMeV', + 'adm': '123', + 'adomain': [ + 'https://test.com' + ], + 'crid': 'display_300x250', + 'w': 300, + 'h': 250, + } + ], + 'seat': '1' + } + ], + 'cur': 'USD' + }, + 'headers': {} + } + + it('should return empty results', () => { + const [req] = spec.buildRequests([deepClone(DEFAULT_BID_REQUEST)], { + bids: [deepClone(DEFAULT_BID_REQUEST)] + }) + const result = spec.interpretResponse(null, { + data: req.data + }) + + expect(result.length).to.eq(0); + }); + it('should detect media type based on adm', () => { + const [req] = spec.buildRequests([deepClone(DEFAULT_BID_REQUEST)], { + bids: [deepClone(DEFAULT_BID_REQUEST)] + }) + const result = spec.interpretResponse(SERVER_RESPONSE, { + data: req.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + it('should detect video adm', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(SERVER_RESPONSE); + const [ortbReq] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + deepSetValue(bannerResponse, 'body.seatbid.0.bid.0.adm', ''); + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('video') + }); + + it('should detect banner adm', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(SERVER_RESPONSE); + const [ortbReq] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + }) +}) From cb91a7c94c96e7c400668c3f623c03524ac64f9b Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:49:33 -0700 Subject: [PATCH 0159/1097] Support for cids (#11713) --- modules/gumgumBidAdapter.js | 30 +++++++++++++++---- test/spec/modules/gumgumBidAdapter_spec.js | 34 +++++++++++++++++----- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index b1ed98bc743..774b4b2f6f7 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -373,15 +373,12 @@ function buildRequests(validBidRequests, bidderRequest) { data.fp = floor; data.fpc = currency; } - + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site) { + setIrisId(data, bidderRequest.ortb2.site, params); + } if (params.iriscat && typeof params.iriscat === 'string') { data.iriscat = params.iriscat; } - - if (params.irisid && typeof params.irisid === 'string') { - data.irisid = params.irisid; - } - if (params.zone || params.pubId) { params.zone ? (data.t = params.zone) : (data.pubId = params.pubId); @@ -447,6 +444,27 @@ function buildRequests(validBidRequests, bidderRequest) { }); return bids; } +export function getCids(site) { + if (site.content && Array.isArray(site.content.data)) { + for (const dataItem of site.content.data) { + if (dataItem.name.includes('iris.com') || dataItem.name.includes('iris.tv')) { + return dataItem.ext.cids.join(','); + } + } + } + return null; +} +export function setIrisId(data, site, params) { + let irisID = getCids(site); + if (irisID) { + data.irisid = irisID; + } else { + // Just adding this chechk for safty and if needed we can remove + if (params.irisid && typeof params.irisid === 'string') { + data.irisid = params.irisid; + } + } +} function handleLegacyParams(params, sizes) { const data = {}; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 8680b3320d1..75d7ffd6bc7 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -100,6 +100,28 @@ describe('gumgumAdapter', function () { describe('buildRequests', function () { let sizesArray = [[300, 250], [300, 600]]; + const bidderRequest = { + ortb2: { + site: { + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'http://pub.com/news', + ref: 'http://google.com', + publisher: { + id: 'p10000', + domain: 'pub.com' + } + } + } + }; + let bidRequests = [ { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', @@ -259,19 +281,17 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('iriscat'); }); + it('should set the irisid param when found iris_c73g5jq96mwso4d8', function() { + const request = { ...bidRequests[0], params: { irisid: 'abc123' } }; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data).to.have.property('irisid', 'iris_c73g5jq96mwso4d8'); + }); it('should not set the iriscat param when not found', function () { const request = { ...bidRequests[0] } const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.not.have.property('iriscat'); }); - - it('should set the irisid param when found', function () { - const request = { ...bidRequests[0], params: { irisid: 'abc123' } } - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data).to.have.property('irisid'); - }); - it('should not set the irisid param when not found', function () { const request = { ...bidRequests[0] } const bidRequest = spec.buildRequests([request])[0]; From f4c0b7b9aa1b851dbda887a3900727dc762c8898 Mon Sep 17 00:00:00 2001 From: Brian Schaaf Date: Wed, 5 Jun 2024 19:55:13 -0400 Subject: [PATCH 0160/1097] Add plmct (#11706) --- modules/pubwiseBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index eca0c971050..639a39d4636 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -63,6 +63,7 @@ const VIDEO_CUSTOM_PARAMS = { 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER, 'skip': DATA_TYPES.NUMBER From 2853602ecde23d59ffa821193c9ee98767b47f4d Mon Sep 17 00:00:00 2001 From: aivanov-zeta <144369215+aivanov-zeta@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:29:08 +0300 Subject: [PATCH 0161/1097] pass user.geo and device.geo to payload (#11723) --- modules/zeta_global_sspBidAdapter.js | 12 ++++++ .../modules/zeta_global_sspBidAdapter_spec.js | 37 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 918d03861ae..a273927e1ec 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -147,6 +147,18 @@ export const spec = { payload.device.w = screen.width; payload.device.h = screen.height; + if (bidderRequest.ortb2?.user?.geo && bidderRequest.ortb2?.device?.geo) { + payload.device.geo = { ...payload.device.geo, ...bidderRequest.ortb2?.device.geo }; + payload.user.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.user.geo }; + } else { + if (bidderRequest.ortb2?.user?.geo) { + payload.user.geo = payload.device.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.user.geo }; + } + if (bidderRequest.ortb2?.device?.geo) { + payload.user.geo = payload.device.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.device.geo }; + } + } + if (bidderRequest?.ortb2?.device?.sua) { payload.device.sua = bidderRequest.ortb2.device.sua; } diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index f6079f08460..dfa7c9e6984 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -155,7 +155,17 @@ describe('Zeta Ssp Bid Adapter', function () { { id: '59' } ] } - ] + ], + geo: { + lat: 40.0, + lon: -80.0, + type: 2, + country: 'USA', + region: 'NY', + metro: '501', + city: 'New York', + zip: '10001', + } } } }]; @@ -658,12 +668,37 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.device.sua.platform.brand).to.eql('Chrome'); expect(payload.device.sua.platform.version[0]).to.eql('102'); + // expecting the same values for user.geo and device.geo + expect(payload.device.geo.type).to.eql(2); + expect(payload.device.geo.lat).to.eql(40.0); + expect(payload.device.geo.lon).to.eql(-80.0); + expect(payload.device.geo.country).to.eql('USA'); + expect(payload.device.geo.region).to.eql('NY'); + expect(payload.device.geo.metro).to.eql('501'); + expect(payload.device.geo.city).to.eql('New York'); + expect(payload.device.geo.zip).to.eql('10001'); + expect(payload.device.ua).to.not.be.undefined; expect(payload.device.language).to.not.be.undefined; expect(payload.device.w).to.not.be.undefined; expect(payload.device.h).to.not.be.undefined; }); + it('Test provide user params', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + // expecting the same values for user.geo and device.geo + expect(payload.user.geo.type).to.eql(2); + expect(payload.user.geo.lat).to.eql(40.0); + expect(payload.user.geo.lon).to.eql(-80.0); + expect(payload.user.geo.country).to.eql('USA'); + expect(payload.user.geo.region).to.eql('NY'); + expect(payload.user.geo.metro).to.eql('501'); + expect(payload.user.geo.city).to.eql('New York'); + expect(payload.user.geo.zip).to.eql('10001'); + }); + it('Test that all empties are removed', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); From 85564260fbe70603e9c8ef9204dbb055fa8696e4 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Thu, 6 Jun 2024 12:43:38 -0400 Subject: [PATCH 0162/1097] KRKPD-1247: sends refererInfo to Kraken (#36) (#11725) * sends refererInfo to kraken * minor change * removes comment --- modules/kargoBidAdapter.js | 24 ++++++++++----- test/spec/modules/kargoBidAdapter_spec.js | 36 +++++++++++++++++++---- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index cafbbd982fa..210fb6d5d59 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -95,16 +95,13 @@ function buildRequests(validBidRequests, bidderRequest) { ] }, imp: impressions, - user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent) + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), + ext: getExtensions(firstBidRequest.ortb2, bidderRequest?.refererInfo) }); - // Add full ortb2 object as backup - if (firstBidRequest.ortb2) { - const siteCat = firstBidRequest.ortb2.site?.cat; - if (siteCat != null) { - krakenParams.site = { cat: siteCat }; - } - krakenParams.ext = { ortb2: firstBidRequest.ortb2 }; + // Add site.cat if it exists + if (firstBidRequest.ortb2?.site?.cat != null) { + krakenParams.site = { cat: firstBidRequest.ortb2.site.cat }; } // Add schain @@ -186,6 +183,10 @@ function buildRequests(validBidRequests, bidderRequest) { krakenParams.page = page; } + if (krakenParams.ext && Object.keys(krakenParams.ext).length === 0) { + delete krakenParams.ext; + } + return Object.assign({}, bidderRequest, { method: BIDDER.REQUEST_METHOD, url: `https://${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, @@ -300,6 +301,13 @@ function onTimeout(timeoutData) { }); } +function getExtensions(ortb2, refererInfo) { + const ext = {}; + if (ortb2) ext.ortb2 = ortb2; + if (refererInfo) ext.refererInfo = refererInfo; + return ext; +} + function _generateRandomUUID() { try { // crypto.getRandomValues is supported everywhere but Opera Mini for years diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index ee89d6468a5..510b5979333 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -300,7 +300,7 @@ describe('kargo adapter tests', function() { domain, isAmp: false, location: topUrl, - numIframs: 0, + numIframes: 0, page: topUrl, reachedTop: true, ref: referer, @@ -428,12 +428,12 @@ describe('kargo adapter tests', function() { } } }]); - expect(payload.ext).to.deep.equal({ ortb2: { + expect(payload.ext.ortb2).to.deep.equal({ user: { key: 'value' } - }}); + }); payload = getPayloadFromTestBids(testBids); - expect(payload.ext).to.be.undefined; + expect(payload.ext.ortb2).to.be.undefined; payload = getPayloadFromTestBids([{ ...minimumBidParams, @@ -450,9 +450,33 @@ describe('kargo adapter tests', function() { } } }]); - expect(payload.ext).to.deep.equal({ortb2: { + expect(payload.ext.ortb2).to.deep.equal({ user: { key: 'value' } - }}); + } + ); + }); + + it('copies the refererInfo object from bidderRequest if present', function() { + let payload; + payload = getPayloadFromTestBids(testBids); + expect(payload.ext.refererInfo).to.deep.equal({ + canonicalUrl: 'https://random.com/this/is/a/url', + domain: 'random.com', + isAmp: false, + location: 'https://random.com/this/is/a/url', + numIframes: 0, + page: 'https://random.com/this/is/a/url', + reachedTop: true, + ref: 'https://random.com/', + stack: [ + 'https://random.com/this/is/a/url' + ], + topmostLocation: 'https://random.com/this/is/a/url' + }); + + delete bidderRequest.refererInfo + payload = getPayloadFromTestBids(testBids); + expect(payload.ext).to.be.undefined; }); it('pulls the site category from the first bids ortb2 object', function() { From 577e8a29e709af3e7fa4464227e7cdc612b24fc9 Mon Sep 17 00:00:00 2001 From: hasanideepak <80700939+hasanideepak@users.noreply.github.com> Date: Fri, 7 Jun 2024 00:06:25 +0530 Subject: [PATCH 0163/1097] RelevateHealth Bid Adapter : Initial release (#11640) * added bid adapter for relevatehealth * made suggested changes by reviewer * solved indentation issues as suggested * solved indentation issues again as suggested * solved indentation issues mentioned in ci/circleci * removed trailing spaces mentioned in ci/circleci * removed trailing spaces and added line at the end mentioned in ci/circleci --- modules/relevatehealthBidAdapter.js | 160 ++++++++++++ modules/relevatehealthBidAdapter.md | 40 +++ .../modules/relevatehealthBidAdapter_spec.js | 239 ++++++++++++++++++ 3 files changed, 439 insertions(+) create mode 100644 modules/relevatehealthBidAdapter.js create mode 100644 modules/relevatehealthBidAdapter.md create mode 100644 test/spec/modules/relevatehealthBidAdapter_spec.js diff --git a/modules/relevatehealthBidAdapter.js b/modules/relevatehealthBidAdapter.js new file mode 100644 index 00000000000..1d60e7f67cc --- /dev/null +++ b/modules/relevatehealthBidAdapter.js @@ -0,0 +1,160 @@ +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + BANNER +} from '../src/mediaTypes.js'; +import { + deepAccess, + generateUUID, + isArray, + logError +} from '../src/utils.js'; +const BIDDER_CODE = 'relevatehealth'; +const ENDPOINT_URL = 'https://rtb.relevate.health/prebid/relevate'; + +function buildRequests(bidRequests, bidderRequest) { + const requests = []; + // Loop through each bid request + bidRequests.forEach(bid => { + // Construct the bid request object + const request = { + id: generateUUID(), + placementId: bid.params.placement_id, + imp: [{ + id: bid.bidId, + banner: getBanner(bid), + bidfloor: getFloor(bid) + }], + site: getSite(bidderRequest), + user: buildUser(bid) + }; + // Get uspConsent from bidderRequest + if (bidderRequest && bidderRequest.uspConsent) { + request.us_privacy = bidderRequest.uspConsent; + } + // Get GPP Consent from bidderRequest + if (bidderRequest?.gppConsent?.gppString) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest?.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + // Get coppa compliance from bidderRequest + if (bidderRequest?.ortb2?.regs?.coppa) { + request.coppa = 1; + } + // Push the constructed bid request to the requests array + requests.push(request); + }); + // Return the array of bid requests + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(requests), + options: { + contentType: 'application/json', + } + }; +} +// Format the response as per the standards +function interpretResponse(bidResponse, bidRequest) { + let resp = []; + if (bidResponse && bidResponse.body) { + try { + let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : []; + if (bids) { + bids.forEach(bidObj => { + let newBid = formatResponse(bidObj); + newBid.mediaType = BANNER; + resp.push(newBid); + }); + } + } catch (err) { + logError(err); + } + } + return resp; +} +// Function to check if Bid is valid +function isBidRequestValid(bid) { + return !!(bid.params.placement_id && bid.params.user_id); +} +// Function to get banner details +function getBanner(bid) { + if (deepAccess(bid, 'mediaTypes.banner')) { + // Fetch width and height from MediaTypes object, if not provided in bid params + if (deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) { + let sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + if (isArray(sizes) && sizes.length > 0) { + return { + h: sizes[0][1], + w: sizes[0][0] + }; + } + } else { + return { + h: bid.params.height, + w: bid.params.width + }; + } + } +} +// Function to get bid_floor +function getFloor(bid) { + if (bid.params && bid.params.bid_floor) { + return bid.params.bid_floor; + } else { + return 0; + } +} +// Function to get site details +function getSite(bidderRequest) { + let site = {}; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + site.name = bidderRequest.refererInfo.domain; + } else { + site.name = ''; + } + return site; +} +// Function to format response +function formatResponse(bid) { + return { + requestId: bid && bid.impid ? bid.impid : undefined, + cpm: bid && bid.price ? bid.price : 0.0, + width: bid && bid.w ? bid.w : 0, + height: bid && bid.h ? bid.h : 0, + ad: bid && bid.adm ? bid.adm : '', + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, + creativeId: bid && bid.crid ? bid.crid : undefined, + netRevenue: false, + currency: bid && bid.cur ? bid.cur : 'USD', + ttl: 300, + dealId: bid && bid.dealId ? bid.dealId : undefined + }; +} +// Function to build the user object +function buildUser(bid) { + if (bid && bid.params) { + return { + id: bid.params.user_id && typeof bid.params.user_id == 'string' ? bid.params.user_id : '', + buyeruid: localStorage.getItem('adx_profile_guid') ? localStorage.getItem('adx_profile_guid') : '', + keywords: bid.params.keywords && typeof bid.params.keywords == 'string' ? bid.params.keywords : '', + customdata: bid.params.customdata && typeof bid.params.customdata == 'string' ? bid.params.customdata : '' + }; + } +} +// Export const spec +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: BANNER, + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/relevatehealthBidAdapter.md b/modules/relevatehealthBidAdapter.md new file mode 100644 index 00000000000..432e4fcec02 --- /dev/null +++ b/modules/relevatehealthBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: relevatehealth Bidder Adapter +Module Type: Bidder Adapter +Maintainer: marketingops@relevatehealth.com +``` + +# Description + +relevatehealth currently supports the BANNER type ads through prebid js + +Module that connects to relevatehealth's demand sources. + +# Banner Test Request +``` + var adUnits = [ + { + code: 'banner-ad', + mediaTypes: { + banner: { + sizes: [[160, 600]], + } + } + bids: [ + { + bidder: 'relevatehealth', + params: { + placement_id: 110011, // Required parameter + user_id: '1111111' // Required parameter + width: 160, // Optional parameter + height: 600, // Optional parameter + domain: '', // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/relevatehealthBidAdapter_spec.js b/test/spec/modules/relevatehealthBidAdapter_spec.js new file mode 100644 index 00000000000..ef974bc3ac1 --- /dev/null +++ b/test/spec/modules/relevatehealthBidAdapter_spec.js @@ -0,0 +1,239 @@ +import { + expect +} from 'chai'; +import { + spec +} from '../../../modules/relevatehealthBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('relevatehealth adapter', function() { + let request; + let bannerResponse, invalidResponse; + + beforeEach(function() { + request = [{ + bidder: 'relevatehealth', + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + params: { + placement_id: 110011, + user_id: '11211', + width: 160, + height: 600, + domain: '', + bid_floor: 0.5 + } + }]; + bannerResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 20.68, + 'adid': '3389', + 'adm': "", + 'adomain': ['google.com'], + 'iurl': 'https://rtb.relevate.health/prebid/relevate', + 'cid': '1431/3389', + 'crid': '3389', + 'w': 160, + 'h': 600, + 'cat': ['IAB1-15'] + }], + 'seat': '00001', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1276' + } + }; + invalidResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 20.68, + 'adid': '3389', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://rtb.relevate.health/prebid/relevate', + 'cid': '1431/3389', + 'crid': '3389', + 'w': 160, + 'h': 600, + 'cat': ['IAB1-15'] + }], + 'seat': '00001', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1276' + } + }; + }); + + describe('validations', function() { + it('isBidValid : placement_id and user_id are passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + placement_id: 110011, + user_id: '11211' + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id and user_id are not passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + width: 160, + height: 600, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + it('isBidValid : placement_id is passed but user_id is not passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + placement_id: 110011, + width: 160, + height: 600, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + it('isBidValid : user_id is passed but placement_id is not passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + width: 160, + height: 600, + domain: '', + bid_floor: 0.5, + user_id: '11211' + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Request', function() { + it('Immutable bid request validate', function() { + let _Request = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(_Request); + }); + it('Validate bidder connection', function() { + let _Request = spec.buildRequests(request); + expect(_Request.url).to.equal('https://rtb.relevate.health/prebid/relevate'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function() { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].id).to.equal(request[0].bidId); + expect(data[0].placementId).to.equal(110011); + }); + it('Validate bid request : ad size', function() { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(160); + expect(data[0].imp[0].banner.h).to.equal(600); + }); + it('Validate bid request : user object', function() { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function() { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(request, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].us_privacy).to.equal('1NYN'); + }); + }); + describe('Validate response ', function() { + it('Validate bid response : valid bid response', function() { + let bResponse = spec.interpretResponse(bannerResponse, request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function() { + let bRequest = spec.buildRequests(request); + let response = spec.interpretResponse(invalidResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('GPP and coppa', function() { + it('Request params check with GPP Consent', function() { + let bidderReq = { + gppConsent: { + gppString: 'gpp-string-test', + applicableSections: [5] + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].gpp).to.equal('gpp-string-test'); + expect(data[0].gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function() { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].gpp).to.equal('gpp-test-string'); + expect(data[0].gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { + ortb2: { + regs: { + coppa: 1 + } + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].coppa).to.equal(1); + }); + }); +}); From 03d7059502eed8d4cd7deefe457bd786c233aa3f Mon Sep 17 00:00:00 2001 From: Scott Sundahl <37344964+ssundahlTTD@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:15:46 -0600 Subject: [PATCH 0164/1097] UID2: Remove obsolete optout-check code for EUID (#11709) * remove obsolete optout-check code for EUID * fix jsdoc warnings --- modules/euidIdSystem.js | 12 +++++++++--- modules/uid2IdSystem_shared.js | 4 +--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js index d98dc02cdce..281fa04a12a 100644 --- a/modules/euidIdSystem.js +++ b/modules/euidIdSystem.js @@ -14,6 +14,13 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // eslint-disable-next-line prebid/validate-imports import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'euid'; const MODULE_REVISION = Uid2CodeVersion; const PREBID_VERSION = '$prebid.version$'; @@ -75,9 +82,9 @@ export const euidIdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleConfig} [configparams] + * @param {SubmoduleConfig} [config] * @param {ConsentData|undefined} consentData - * @returns {euidId} + * @returns {IdResponse} */ getId(config, consentData) { if (consentData?.gdprApplies !== true) { @@ -103,7 +110,6 @@ export const euidIdSubmodule = { mappedConfig.cstg = { serverPublicKey: config?.params?.serverPublicKey, subscriptionId: config?.params?.subscriptionId, - optoutCheck: 1, ...extractIdentityFromParams(config?.params ?? {}) } } diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 808a61cb388..917e305f3fb 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -393,7 +393,6 @@ if (FEATURES.UID2_CSTG) { this._baseUrl = opts.baseUrl; this._serverPublicKey = opts.cstg.serverPublicKey; this._subscriptionId = opts.cstg.subscriptionId; - this._optoutCheck = opts.cstg.optoutCheck; this._logInfo = logInfo; this._logWarn = logWarn; } @@ -451,8 +450,7 @@ if (FEATURES.UID2_CSTG) { } async generateToken(cstgIdentity) { - const requestIdentity = await this.generateCstgRequest(cstgIdentity); - const request = { optout_check: this._optoutCheck, ...requestIdentity }; + const request = await this.generateCstgRequest(cstgIdentity); this._logInfo('Building CSTG request for', request); const box = await UID2CstgBox.build( this.stripPublicKeyPrefix(this._serverPublicKey) From 3002377a6ebb33391311fd9dfffa0f2b7290c3b4 Mon Sep 17 00:00:00 2001 From: Jason Piros Date: Thu, 6 Jun 2024 17:11:34 -0600 Subject: [PATCH 0165/1097] Consumable Bid Adapter: add language to request (#11722) * consumableBidAdapter: add language param * consumableBidAdapter: add language param test * consumableBidAdapter: get lang from bidderRequest --- modules/consumableBidAdapter.js | 3 ++- .../spec/modules/consumableBidAdapter_spec.js | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 30b081e53d3..cb802508de9 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -61,7 +61,8 @@ export const spec = { source: [{ 'name': 'prebidjs', 'version': '$prebid.version$' - }] + }], + lang: bidderRequest.ortb2.device.language, }, validBidRequests[0].params); if (bidderRequest && bidderRequest.gdprConsent) { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d8e75454245..073e889d172 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -66,6 +66,11 @@ const BIDDER_REQUEST_1 = { 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ] + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -130,6 +135,11 @@ const BIDDER_REQUEST_2 = { 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ] + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -177,6 +187,11 @@ const BIDDER_REQUEST_VIDEO = { 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ] + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -188,6 +203,11 @@ const BIDDER_REQUEST_EMPTY = { gdprConsent: { consentString: 'consent-test', gdprApplies: false + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -519,6 +539,12 @@ describe('Consumable BidAdapter', function () { expect(data1.placements[0].bidfloor).to.equal(0.05); expect(data2.placements[0].bidfloor).to.equal(0.15); }); + it('should contain the language param', function () { + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + + expect(data.lang).to.equal('en'); + }); }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { From e3ea28063e5228370faaabe6497515be11b8ce4f Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Thu, 6 Jun 2024 21:21:50 -0400 Subject: [PATCH 0166/1097] AMX Bid Adapter: add support for overriding bidderCode (allowAlternateBidderCodes) (#11712) * add support for overriding bidderCode (allowAlternateBidderCodes) in AMX Bid adapter * fix formatting and actually run tests --- modules/amxBidAdapter.js | 7 ++++ test/spec/modules/amxBidAdapter_spec.js | 49 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 6e14f65b0c8..df260958104 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -454,6 +454,10 @@ export const spec = { setUIDSafe(response.am); } + const bidderSettings = config.getConfig('bidderSettings'); + const settings = bidderSettings?.amx ?? bidderSettings?.standard ?? {}; + const allowAlternateBidderCodes = !!settings.allowAlternateBidderCodes; + return flatMap(Object.keys(response.r), (bidID) => { return flatMap(response.r[bidID], (siteBid) => siteBid.b.map((bid) => { @@ -466,8 +470,10 @@ export const spec = { const size = resolveSize(bid, request.data, bidID); const defaultExpiration = mediaType === BANNER ? 240 : 300; + const { bc: bidderCode, ds: demandSource } = bid.ext ?? {}; return { + ...(bidderCode != null && allowAlternateBidderCodes ? { bidderCode } : {}), requestId: bidID, cpm: bid.price, width: size[0], @@ -479,6 +485,7 @@ export const spec = { meta: { advertiserDomains: bid.adomain, mediaType, + ...(demandSource != null ? { demandSource } : {}), }, mediaType, ttl: typeof bid.exp === 'number' ? bid.exp : defaultExpiration, diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 21fa2e2617c..a84a30a945a 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -583,6 +583,55 @@ describe('AmxBidAdapter', () => { expect(parsed).to.eql([]); }); + it('will read an bidderCode override from bid.ext.prebid.meta', () => { + const currentConfig = config.getConfig(); + config.setConfig({ + ...currentConfig, + bidderSettings: { + amx: { + allowAlternateBidderCodes: true + } + } + }); + + const parsed = spec.interpretResponse( + { body: { + ...sampleServerResponse, + r: { + [sampleRequestId]: [{ + ...sampleServerResponse.r[sampleRequestId][0], + b: [{ + ...sampleServerResponse.r[sampleRequestId][0].b[0], + ext: { + bc: 'amx-pmp', + ds: 'example', + } + }] + }] + }}}, + baseRequest + ); + + config.setConfig(currentConfig); + expect(parsed.length).to.equal(1); // we removed one + + // we should have display, video, display + expect(parsed[0]).to.deep.equal({ + ...baseBidResponse, + meta: { + ...baseBidResponse.meta, + mediaType: BANNER, + demandSource: 'example' + }, + mediaType: BANNER, + bidderCode: 'amx-pmp', + width: 300, + height: 600, // from the bid itself + ttl: 90, + ad: sampleDisplayAd, + }); + }); + it('can parse a display ad', () => { const parsed = spec.interpretResponse( { body: sampleServerResponse }, From 203ebe544f0f3eaa0a74f168419d8b71f114ec9b Mon Sep 17 00:00:00 2001 From: decemberWP <155962474+decemberWP@users.noreply.github.com> Date: Fri, 7 Jun 2024 03:28:02 +0200 Subject: [PATCH 0167/1097] Cee id System: add custom token name and optional value to pass (#11547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * add pirIdSystem * pirIdSystem * piridSystem - preCR * fix after CR * name change * custom token name read + optional token value --------- Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: Wojciech Biały --- modules/ceeIdSystem.js | 11 +++-- modules/ceeIdSystem.md | 4 ++ test/spec/modules/ceeIdSystem_spec.js | 66 ++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js index 5558e2c7d6f..d1534ddada2 100644 --- a/modules/ceeIdSystem.js +++ b/modules/ceeIdSystem.js @@ -16,14 +16,13 @@ import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomai */ const MODULE_NAME = 'ceeId'; -const ID_TOKEN = 'WPxid'; export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); /** * Reads the ID token from local storage or cookies. * @returns {string|undefined} The ID token, or undefined if not found. */ -export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); +export const readId = tokenName => storage.getDataFromLocalStorage(tokenName) || storage.getCookie(tokenName); /** @type {Submodule} */ export const ceeIdSubmodule = { @@ -44,9 +43,11 @@ export const ceeIdSubmodule = { * performs action to obtain id and return a value * @function * @returns {(IdResponse|undefined)} - */ - getId() { - const ceeIdToken = readId(); + */ + getId(config) { + const { params = {} } = config; + const { tokenName, value } = params + const ceeIdToken = value || readId(tokenName); return ceeIdToken ? { id: ceeIdToken } : undefined; }, diff --git a/modules/ceeIdSystem.md b/modules/ceeIdSystem.md index 811efe08069..fe7a543748d 100644 --- a/modules/ceeIdSystem.md +++ b/modules/ceeIdSystem.md @@ -20,6 +20,10 @@ pbjs.setConfig({ name: 'ceeIdToken', expires: 7, refreshInSeconds: 360 + }, + params: { + tokenName: 'name' // Your custom name of token to read + value: 'tokenValue' // Optional param if you want to pass token value directly through setConfig (this param shouldn't be set if token value will be taken from cookie or LS) } }] } diff --git a/test/spec/modules/ceeIdSystem_spec.js b/test/spec/modules/ceeIdSystem_spec.js index 049e596ad15..62ae2898ede 100644 --- a/test/spec/modules/ceeIdSystem_spec.js +++ b/test/spec/modules/ceeIdSystem_spec.js @@ -17,17 +17,65 @@ describe('ceeIdSystem', () => { }); describe('getId', () => { - it('should return an object with id when ceeIdToken is found', () => { + it('should return an object with id when ceeIdToken is found in LS', () => { + const config = { + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360, + }, + params: { + tokenName: 'WPxid', + }, + }; + getDataFromLocalStorageStub.returns('testToken'); getCookieStub.returns('testToken'); - const result = ceeIdSubmodule.getId(); + const result = ceeIdSubmodule.getId(config); expect(result).to.deep.equal({ id: 'testToken' }); }); + it('should return an object with id when ceeIdToken is passed in setConfig', () => { + const config = { + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360, + }, + params: { + tokenName: 'WPxid', + value: 'testTokenFromSetConfig' + }, + }; + + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = ceeIdSubmodule.getId(config); + + expect(result).to.deep.equal({ id: 'testTokenFromSetConfig' }); + }); + it('should return undefined when ceeIdToken is not found', () => { - const result = ceeIdSubmodule.getId(); + const config = { + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360, + }, + params: { + tokenName: 'WPxid', + }, + }; + const result = ceeIdSubmodule.getId(config); expect(result).to.be.undefined; }); @@ -49,27 +97,33 @@ describe('ceeIdSystem', () => { describe('readId', () => { it('should return data from local storage when it exists', () => { + const tokenName = 'testToken'; + getDataFromLocalStorageStub.returns('local_storage_data'); - const result = readId(); + const result = readId(tokenName); expect(result).to.equal('local_storage_data'); }); it('should return data from cookie when local storage data does not exist', () => { + const tokenName = 'testToken'; + getDataFromLocalStorageStub.returns(null); getCookieStub.returns('cookie_data'); - const result = readId(); + const result = readId(tokenName); expect(result).to.equal('cookie_data'); }); it('should return null when neither local storage data nor cookie data exists', () => { + const tokenName = 'testToken'; + getDataFromLocalStorageStub.returns(null); getCookieStub.returns(null); - const result = readId(); + const result = readId(tokenName); expect(result).to.be.null; }); From 903b9ad5af7da3a5fcca6e1287179807a62f462c Mon Sep 17 00:00:00 2001 From: mustafa kemal Date: Fri, 7 Jun 2024 04:31:09 +0300 Subject: [PATCH 0168/1097] TheAdx Bid Adapter : eids support added (#11681) * eids support added * theadx bid adaptor new eids test added --------- Co-authored-by: mku --- modules/theAdxBidAdapter.js | 45 +++++++++++++++++++++- test/spec/modules/theAdxBidAdapter_spec.js | 42 +++++++++++++++++++- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index f19f7cfe515..6d3c2e07b84 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -20,6 +20,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'theadx'; const ENDPOINT_URL = 'https://ssp.theadx.com/request'; +const ENDPOINT_TR_URL = 'https://ssptr.theadx.com/request'; const NATIVEASSETNAMES = { 0: 'title', @@ -125,7 +126,7 @@ const NATIVEPROBS = { export const spec = { code: BIDDER_CODE, - aliases: ['theadx'], // short code + aliases: ['theadx', 'theAdx'], // short code supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -160,10 +161,11 @@ export const spec = { if (!isEmpty(validBidRequests)) { results = validBidRequests.map( bidRequest => { + let url = `${getRegionEndPoint(bidRequest)}?tagid=${bidRequest.params.tagId}`; return { method: requestType, type: requestType, - url: `${ENDPOINT_URL}?tagid=${bidRequest.params.tagId}`, + url: url, options: { withCredentials: true, }, @@ -500,6 +502,14 @@ let generateImpBody = (bidRequest, bidderRequest) => { return result; } +let getRegionEndPoint = (bidRequest) => { + if (bidRequest && bidRequest.params && bidRequest.params.region) { + if (bidRequest.params.region.toLowerCase() == 'tr') { + return ENDPOINT_TR_URL; + } + } + return ENDPOINT_URL; +}; let generatePayload = (bidRequest, bidderRequest) => { // Generate the expected OpenRTB payload @@ -511,7 +521,38 @@ let generatePayload = (bidRequest, bidderRequest) => { imp: [generateImpBody(bidRequest, bidderRequest)], }; // return payload; + let eids = getEids(bidRequest); + if (Object.keys(eids).length > 0) { + payload.ext = eids; + } return JSON.stringify(payload); }; +function getEids(bidRequest) { + let eids = {} + + let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); + if (uId2) { + eids['uid2'] = uId2; + } + + let id5 = deepAccess(bidRequest, 'userId.id5id.uid'); + if (id5) { + eids['id5id'] = id5; + let id5Linktype = deepAccess(bidRequest, 'userId.id5id.ext.linkType'); + if (id5Linktype) { + eids['id5_linktype'] = id5Linktype; + } + } + let netId = deepAccess(bidRequest, 'userId.netId'); + if (netId) { + eids['netid'] = netId; + } + let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + eids['sharedid'] = sharedId; + } + return eids; +}; + registerBidder(spec); diff --git a/test/spec/modules/theAdxBidAdapter_spec.js b/test/spec/modules/theAdxBidAdapter_spec.js index eb00834421a..53a65c1b044 100644 --- a/test/spec/modules/theAdxBidAdapter_spec.js +++ b/test/spec/modules/theAdxBidAdapter_spec.js @@ -81,7 +81,21 @@ describe('TheAdxAdapter', function () { [300, 600] ] } - } + }, + userId: { + uid2: { id: 'sample-uid2' }, + id5id: { + 'uid': 'sample-id5id', + 'ext': { + 'linkType': 'abc' + } + }, + netId: 'sample-netid', + sharedid: { + 'id': 'sample-sharedid', + }, + + }, }; const sampleBidderRequest = { @@ -357,6 +371,30 @@ describe('TheAdxAdapter', function () { expect(mediaTypes.video).to.not.be.null; expect(mediaTypes.video).to.not.be.undefined; }); + + it('add eids to request', function () { + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + let payload = JSON.parse(result.data); + expect(payload).to.not.be.null; + expect(payload.ext).to.not.be.null; + + expect(payload.ext.uid2).to.not.be.null; + expect(payload.ext.uid2.length).to.greaterThan(0); + + expect(payload.ext.id5id).to.not.be.null; + expect(payload.ext.id5id.length).to.greaterThan(0); + expect(payload.ext.id5_linktype).to.not.be.null; + expect(payload.ext.id5_linktype.length).to.greaterThan(0); + + expect(payload.ext.netid).to.not.be.null; + expect(payload.ext.netid.length).to.greaterThan(0); + + expect(payload.ext.sharedid).to.not.be.null; + expect(payload.ext.sharedid.length).to.greaterThan(0); + }); }); describe('response interpreter', function () { @@ -495,7 +533,7 @@ describe('TheAdxAdapter', function () { banner: {} }, requestId: incomingRequestId, - deals: [{id: dealId}] + deals: [{ id: dealId }] }; let serverResponse = { body: sampleResponse From bf3af240a1b49cad6715de20ad6c92742e03f86b Mon Sep 17 00:00:00 2001 From: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Date: Fri, 7 Jun 2024 03:43:58 +0200 Subject: [PATCH 0169/1097] SeedingAlliance Bid Adapter : get and set UUID and read EIDs where applicable (#11201) * add seedingAlliance Adapter * add two native default params * ... * ... * seedingAlliance Adapter: add two more default native params * updating seedingAlliance Adapter * seedingAlliance Adapter * Add UUID from LocalStorage * get eids and add nativendo first party id * Add Unit Tests for UUID and EIDs * fix minor lint error --------- Co-authored-by: Jonas Hilsen Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: sag-jonhil <78849369+sag-jonhil@users.noreply.github.com> --- modules/seedingAllianceBidAdapter.js | 51 +++++++++++- .../modules/seedingAllianceAdapter_spec.js | 79 +++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index e287ea7ff78..f998df27d5c 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -3,17 +3,21 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; -import {_map, deepSetValue, isArray, isEmpty, replaceAuctionPrice} from '../src/utils.js'; +import {_map, generateUUID, deepSetValue, isArray, isEmpty, replaceAuctionPrice} from '../src/utils.js'; import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {getStorageManager} from '../src/storageManager.js'; const GVL_ID = 371; const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; +const NATIVENDO_KEY = 'nativendo_id'; const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + const NATIVE_PARAMS = { title: { id: 0, name: 'title' }, body: { id: 1, name: 'data', type: 2 }, @@ -37,6 +41,7 @@ export const spec = { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); let url = bidderRequest.refererInfo.page; + let eids = getEids(validBidRequests[0]); const imps = validBidRequests.map((bidRequest, id) => { const imp = { @@ -130,11 +135,14 @@ export const spec = { deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + deepSetValue(request, 'user.ext.eids', eids); } + let endpoint = config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL; + return { method: 'POST', - url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, + url: endpoint, data: JSON.stringify(request), bidRequests: validBidRequests }; @@ -191,6 +199,45 @@ export const spec = { } }; +const getNativendoID = () => { + let nativendoID = storage.localStorageIsEnabled() && + storage.getDataFromLocalStorage(NATIVENDO_KEY); + + if (!nativendoID) { + if (storage.localStorageIsEnabled()) { + nativendoID = generateUUID(); + storage.setDataInLocalStorage(NATIVENDO_KEY, nativendoID); + } + } + + return nativendoID; +} + +const getEids = (bidRequest) => { + const eids = []; + const nativendoID = getNativendoID(); + + if (nativendoID) { + const nativendoUserEid = { + source: 'nativendo.de', + uids: [ + { + id: nativendoID, + atype: 1 + } + ] + }; + + eids.push(nativendoUserEid); + } + + if (bidRequest.userIdAsEids) { + eids.push(bidRequest.userIdAsEids); + } + + return eids; +} + function transformSizes(requestSizes) { if (!isArray(requestSizes)) { return []; diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 03548cf923a..45d1544d100 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -1,5 +1,6 @@ // jshint esversion: 6, es3: false, node: true import {assert, expect} from 'chai'; +import {getStorageManager} from 'src/storageManager.js'; import {spec} from 'modules/seedingAllianceBidAdapter.js'; import { NATIVE } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; @@ -100,6 +101,84 @@ describe('SeedingAlliance adapter', function () { }); }); + describe('check user ID functionality', function () { + let storage = getStorageManager({ bidderCode: 'seedingAlliance' }); + let localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + let getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + const bidRequests = [{ + bidId: 'bidId', + params: {} + }]; + const bidderRequest = { + refererInfo: { referer: 'page' }, + gdprConsent: 'CP0j9IAP0j9IAAGABCENAYEgAP_gAAAAAAYgIxBVBCpNDWFAMHBVAJIgCYAU1sARIAQAABCAAyAFAAOA8IAA0QECEAQAAAACAAAAgVABAAAAAABEAACAAAAEAQFkAAQQgAAIAAAAAAEQQgBQAAgAAAAAEAAIgAABAwQAkACQIYLEBUCAhIAgCgAAAIgBgICAAgMACEAYAAAAAAIAAIBAAgIEMIAAAAECAQAAAFhIEoACAAKgAcgA-AEAAMgAaABEACYAG8APwAhIBDAESAJYATQAw4B9gH6ARQAjQBKQC5gF6AMUAbQA3ACdgFDgLzAYMAw0BmYDVwGsgOCAcmA8cCEMELQQuCAAgGQgQMHQKAAKgAcgA-AEAAMgAaABEACYAG8AP0AhgCJAEsAJoAYYA0YB9gH6ARQAiwBIgCUgFzAL0AYoA2gBuAEXgJkATsAocBeYDBgGGgMqAZYAzMBpoDVwHFgOTAeOBC0cAHAAQABcAKACEAF0AMEAZCQgFABMADeARQAlIBcwDFAG0AeOBCgCFpAAGAAgBggEMyUAwABAAHAAPgBEACZAIYAiQB-AFzAMUAi8BeYEISQAMAC4DLAIZlIEAAFQAOQAfACAAGQANAAiABMACkAH6AQwBEgDRgH4AfoBFgCRAEpALmAYoA2gBuAEXgJ2AUOAvMBhoDLAGsgOCAcmA8cCEIELQIZlAAoAFwB9gLoAYIBAwtADAL0AzMB44AAA.f_wAAAAAAAAA' + } + let request; + + before(function () { + storage.removeDataFromLocalStorage('nativendo_id'); + const localStorageData = { + nativendo_id: '123' + }; + + getDataFromLocalStorageStub.callsFake(function (key) { + return localStorageData[key]; + }); + }); + + it('should return an empty array if local storage is not enabled', function () { + localStorageIsEnabledStub.returns(false); + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: false + } + }; + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + expect(request.user.ext.eids).to.be.an('array').that.is.empty; + }); + + it('should return an empty array if local storage is enabled but storageAllowed is false', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: false + } + }; + localStorageIsEnabledStub.returns(true); + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + expect(request.user.ext.eids).to.be.an('array').that.is.empty; + }); + + it('should return a non empty array if local storage is enabled and storageAllowed is true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: true + } + }; + localStorageIsEnabledStub.returns(true); + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + expect(request.user.ext.eids).to.be.an('array').that.is.not.empty; + }); + + it('should return an array containing the nativendoUserEid', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: true + } + }; + localStorageIsEnabledStub.returns(true); + + let nativendoUserEid = { source: 'nativendo.de', uids: [{ id: '123', atype: 1 }] }; + storage.setDataInLocalStorage('nativendo_id', '123'); + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(request.user.ext.eids).to.deep.include(nativendoUserEid); + }); + }); + describe('interpretResponse', function () { const goodNativeResponse = { body: { From 031f96b3eccaf5d0af0650f8a2475f7220e2be90 Mon Sep 17 00:00:00 2001 From: xmgiddev <133856186+xmgiddev@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:53:57 +0300 Subject: [PATCH 0170/1097] MGIDX Adapter: Update (#11728) * new adapter - MgidX * add new required param host * rem host, add region * MGIDX Adapter: update --------- Co-authored-by: Evgeny Nagorny Co-authored-by: xmgiddev <> --- modules/mgidXBidAdapter.js | 34 +++++-- test/spec/modules/mgidXBidAdapter_spec.js | 107 ++++++++++++++++++++-- 2 files changed, 126 insertions(+), 15 deletions(-) diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index f073fb4c576..40ac760e46d 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -1,5 +1,4 @@ import { - isFn, deepAccess, logMessage, logError, @@ -39,7 +38,7 @@ function isBidResponseValid(bid) { } function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; + const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; const schain = bid.schain || {}; const { placementId, endpointId } = params; const bidfloor = getBidFloor(bid); @@ -69,7 +68,7 @@ function getPlacementReqData(bid) { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; @@ -83,14 +82,19 @@ function getPlacementReqData(bid) { placement.adFormat = NATIVE; } + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; + } + return placement; } function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - try { const bidFloor = bid.getFloor({ currency: 'USD', @@ -150,6 +154,7 @@ export const spec = { } catch (e) { logMessage(e); } + let location = refferLocation || winLocation; const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; const host = location.host; @@ -164,17 +169,28 @@ export const spec = { host, page, placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, tmax: bidderRequest.timeout }; + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { request.gdpr = { consentString: bidderRequest.gdprConsent.consentString }; } + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 9efaf94c954..0660676996c 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -8,6 +8,16 @@ import { USERSYNC_DEFAULT_CONFIG } from '../../../src/userSync'; const bidder = 'mgidX' describe('MGIDXBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -19,8 +29,9 @@ describe('MGIDXBidAdapter', function () { }, params: { region: 'eu', - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -33,8 +44,9 @@ describe('MGIDXBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -57,8 +69,9 @@ describe('MGIDXBidAdapter', function () { }, params: { region: 'eu', - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -160,6 +173,56 @@ describe('MGIDXBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -205,6 +268,38 @@ describe('MGIDXBidAdapter', function () { }); }); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { From e94e0810495fdc530bef2b802410d3d53ac2bb98 Mon Sep 17 00:00:00 2001 From: shashidhar-insticator <91495253+shashidhar-insticator@users.noreply.github.com> Date: Sat, 8 Jun 2024 05:26:28 +0530 Subject: [PATCH 0171/1097] Insticator adapter update with publisherId param (#11733) * Adding publisherId to the bidrequest for insticator adapter * test cases to insticator adapter * test cases to insticator adapter --------- Co-authored-by: shashidharm --- modules/insticatorBidAdapter.js | 4 +++ .../spec/modules/insticatorBidAdapter_spec.js | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 636c0162a02..1ddd9334dc4 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -439,6 +439,10 @@ function buildRequest(validBidRequests, bidderRequest) { deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } + if (validBidRequests[0]?.params?.publisherId) { + deepSetValue(req, 'site.publisher.id', validBidRequests[0].params.publisherId); + } + return req; } diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 72ee132f6f6..64033c5a7d4 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -766,6 +766,37 @@ describe('InsticatorBidAdapter', function () { expect(data.regs.ext).to.have.property('us_privacy'); expect(data.regs.ext).to.have.property('gppSid'); }); + + it('should return true if publisherId is absent', () => { + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + + it('should have publisher object with id in site object, if publisherId present in params', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: '86dd03a1-053f-4e3e-90e7-389070a0c62c' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.publisher).to.be.an('object'); + expect(data.site.publisher.id).to.equal(tempBiddRequest.params.publisherId) + }); + + it('should have publisher object should be empty, if publisherId is empty string', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: '' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.publisher).to.not.an('object'); + }); }); describe('interpretResponse', function () { From 090863f8d9f00c961d9aaa277b49d535ae3fa72f Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Fri, 7 Jun 2024 16:57:54 -0700 Subject: [PATCH 0172/1097] Yieldmo Bid Adapter: Prevent Consent Override and Eids fix (#11734) * Prevent Consent Override Bug fix where we do not override consent with eids. * Eids fix Adding eids to user.ext instead of user --- modules/yieldmoBidAdapter.js | 2 +- test/spec/modules/yieldmoBidAdapter_spec.js | 47 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 0f0c1b46e54..af01ee73f09 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -177,7 +177,7 @@ export const spec = { serverRequest.topics = topicsData; } if (eids.length) { - serverRequest.user = { eids }; + deepSetValue(serverRequest, 'user.ext.eids', eids); }; serverRequests.push({ method: 'POST', diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 06e94ed3919..b2a8d550e84 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -261,7 +261,11 @@ describe('YieldmoAdapter', function () { vendorData: {blerp: 1}, gdprApplies: true, }; - const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({gdprConsent})); + const data = buildAndGetData( + [mockBannerBid()], + 0, + mockBidderRequest({ gdprConsent }) + ); expect(data.userConsent).equal( JSON.stringify({ gdprApplies: true, @@ -637,6 +641,45 @@ describe('YieldmoAdapter', function () { expect(buildAndGetData([mockVideoBid({ortb2Imp})]).imp[0].ext.gpid).to.be.equal(ortb2Imp.ext.data.pbadslot); }); + it('should pass consent in video bid along with eids', () => { + const params = { + userIdAsEids: [ + { + source: 'pubcid.org', + uids: [ + { + id: 'fake_pubcid', + atype: 1, + }, + ], + }, + ], + fakeUserIdAsEids: [ + { + source: 'pubcid.org', + uids: [ + { + id: 'fake_pubcid', + atype: 1, + }, + ], + }, + ], + }; + let videoBidder = mockBidderRequest( + { + gdprConsent: { + gdprApplies: 1, + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + }, + }, + [mockVideoBid()] + ); + let payload = buildAndGetData([mockVideoBid({...params})], 0, videoBidder); + expect(payload.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(payload.user.ext.eids).to.eql(params.fakeUserIdAsEids); + }); + it('should add eids to the video bid request', function () { const params = { userIdAsEids: [{ @@ -656,7 +699,7 @@ describe('YieldmoAdapter', function () { }] }] }; - expect(buildAndGetData([mockVideoBid({...params})]).user.eids).to.eql(params.fakeUserIdAsEids); + expect(buildAndGetData([mockVideoBid({...params})]).user.ext.eids).to.eql(params.fakeUserIdAsEids); }); it('should add topics to the bid request', function () { From dc01e40cee9e8d9263512dc0ac238720f2e9d92c Mon Sep 17 00:00:00 2001 From: CleanMediaNet <46790659+CleanMediaNet@users.noreply.github.com> Date: Fri, 7 Jun 2024 21:49:29 -0400 Subject: [PATCH 0173/1097] Cleanmedianet Bid Adapter : update Placement to plcmt (#11698) * Update cleanmedianetBidAdapter_spec.js * Update cleanmedianetBidAdapter.js * Update cleanmedianetBidAdapter_spec.js * Update cleanmedianetBidAdapter_spec.js --------- Co-authored-by: Patrick McCann --- modules/cleanmedianetBidAdapter.js | 2 +- test/spec/modules/cleanmedianetBidAdapter_spec.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index 601a237baa8..a0e85032798 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { maxduration: bidRequest.mediaTypes.video.maxduration, api: bidRequest.mediaTypes.video.api, skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, - placement: bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement, + placement: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index 3c73dac07de..d1ec37c4999 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -376,7 +376,7 @@ describe('CleanmedianetAdapter', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.params.video = { - placement: 1, + plcmt: 1, minduration: 1, } @@ -405,7 +405,7 @@ describe('CleanmedianetAdapter', () => { playerSize: [302, 252], mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -428,7 +428,7 @@ describe('CleanmedianetAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -457,7 +457,7 @@ describe('CleanmedianetAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, From b3fbd024d0bcaa7f99fc45cfa69b76547be54dfc Mon Sep 17 00:00:00 2001 From: Damyan Date: Sat, 8 Jun 2024 04:58:26 +0300 Subject: [PATCH 0174/1097] AdHash Bid Adapter: brand safety changes (#11617) * AdHash Bidder Adapter: minor changes We're operating on a com TLD now. Added publisher in URL for easier routing. * Implemented brand safety Implemented brand safety checks * Fix for GDPR consent Removing the extra information as request data becomes too big and is sometimes truncated * Ad fraud prevention formula changed Ad fraud prevention formula changed to support negative values as well as linear distribution of article length * AdHash brand safety additions Adding starts-with and ends-with rules that will help us with languages such as German where a single word can be written in multiple ways depending on the gender and grammatical case. * AdHash brand safety updates Added support for Cyrillic characters. Added support for bidderURL parameter. Fixed score multiplier from 500 to 1000. * AdHash Analytics adapter * Support for recent ads Support for recent ads which gives us the option to do frequency and recency capping. * Fix for timestamp * PUB-222 Added logic for measuring the fill rate (fallbacks) for Prebid impressions * Unit tests for the analytics adapter Added unit tests for the analytics adapter * Removed export causing errors Removed an unneeded export of a const that was causing errors with the analytics adapter * Added globalScript parameter * PUB-227 Support for non-latin and non-cyrillic symbols * GEN-964 - Brand safety now checks the page URL for bad words. No ad is shown if there is at least one match. - Repeating code is optimized and moved to helper function - Multi-language support for brand safety * GEN-1025 Sending the needed ad density data to the bidder * Removing the analytics adaptor * Fix for regexp match * Version change * MINOR Code review changes * GEN-1153 Adding support for preroll ads * MINOR Video unit test added * Removing globalScript flag * Brand safety change Adding support for compound words as well as combo-patterns. * Accessing local storage and fixing text selectors * Adding the options to read and write recent ads from the local storage when enabled. * Using outerText for whole page text selection. * Unit tests updated * Fixing test selector Using textContent to get the raw text from the body. * Unit tests fixed --------- Co-authored-by: NikolayMGeorgiev Co-authored-by: Ventsislav Saraminev Co-authored-by: Dimitar Kalenderov Co-authored-by: NikolaiMGeorgiev --- modules/adhashBidAdapter.js | 20 +++++++++++++-- test/spec/modules/adhashBidAdapter_spec.js | 30 +++++++++++----------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index 96e93883de6..7eb91dfcd52 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -7,6 +7,7 @@ const VERSION = '3.6'; const BAD_WORD_STEP = 0.1; const BAD_WORD_MIN = 0.2; const ADHASH_BIDDER_CODE = 'adhash'; +const storage = getStorageManager({ bidderCode: ADHASH_BIDDER_CODE }); /** * Function that checks the page where the ads are being served for brand safety. @@ -120,7 +121,7 @@ function brandSafety(badWords, maxScore) { .replaceAll(/\s\s+/g, ' ') .toLowerCase() .trim(); - const content = window.top.document.body.innerText.toLowerCase(); + const content = window.top.document.body.textContent.toLowerCase(); // \p{L} matches a single unicode code point in the category 'letter'. Matches any kind of letter from any language. const regexp = new RegExp('[\\p{L}]+', 'gu'); const wordsMatched = content.match(regexp); @@ -171,7 +172,6 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { - const storage = getStorageManager({ bidderCode: ADHASH_BIDDER_CODE }); const { gdprConsent } = bidderRequest; const bidRequests = []; const body = document.body; @@ -199,9 +199,11 @@ export const spec = { position: validBidRequests[i].adUnitCode }; let recentAds = []; + let recentAdsPrebid = []; if (storage.localStorageIsEnabled()) { const prefix = validBidRequests[i].params.prefix || 'adHash'; recentAds = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAds') || '[]'); + recentAdsPrebid = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAdsPrebid') || '[]'); } // Needed for the ad density calculation @@ -237,6 +239,7 @@ export const spec = { blockedCreatives: [], currentTimestamp: (new Date().getTime() / 1000) | 0, recentAds: recentAds, + recentAdsPrebid: recentAdsPrebid, GDPRApplies: gdprConsent ? gdprConsent.gdprApplies : null, GDPR: gdprConsent ? gdprConsent.consentString : null, servedAdsCount: window.adsCount, @@ -263,6 +266,19 @@ export const spec = { return []; } + if (storage.localStorageIsEnabled()) { + const prefix = request.bidRequest.params.prefix || 'adHash'; + let recentAdsPrebid = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAdsPrebid') || '[]'); + recentAdsPrebid.push([ + (new Date().getTime() / 1000) | 0, + responseBody.creatives[0].advertiserId, + responseBody.creatives[0].budgetId, + responseBody.creatives[0].expectedHashes.length ? responseBody.creatives[0].expectedHashes[0] : '', + ]); + let recentAdsPrebidFinal = JSON.stringify(recentAdsPrebid.slice(-100)); + storage.setDataInLocalStorage(prefix + 'recentAdsPrebid', recentAdsPrebidFinal); + } + const publisherURL = JSON.stringify(request.bidRequest.params.platformURL); const bidderURL = request.bidRequest.params.bidderURL || 'https://bidder.adhash.com'; const oneTimeId = request.bidRequest.adUnitCode + Math.random().toFixed(16).replace('0.', '.'); diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js index cc643d6d2ab..f3b63a2359b 100644 --- a/test/spec/modules/adhashBidAdapter_spec.js +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -178,105 +178,105 @@ describe('adhashBidAdapter', function () { }); it('should return empty array when there are bad words (full)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text badword badword example badword text' + ' word'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (full cyrillic)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text дума дума example дума text' + ' текст'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (partial)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbadwordb badwordb example badwordbtext' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (partial, compound phrase)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbad wordb bad wordb example bad wordbtext' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (starts)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text startsWith starts text startsAgain' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (starts cyrillic)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text стартТекст старт text стартТекст' + ' дума'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (ends)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text wordEnds ends text anotherends' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (ends cyrillic)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text ДругКрай край text ощеединкрай' + ' дума'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (combo)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'queen of england dies, the queen dies' + ' word'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (regexp)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text xxxayyy zzxxxAyyyzz text xxxbyyy' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return non-empty array when there are not enough bad words (full)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text badword badword example text' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are not enough bad words (partial)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbadwordb example' + ' word'.repeat(996); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are no-bad word matches', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbadword example text' + ' word'.repeat(995); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are bad words and good words', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text badword badword example badword goodWord goodWord ' + ' word'.repeat(992); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there is a problem with the brand-safety', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return null; }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); From 5985642987594552a872eb58792494f9fec6260c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Sat, 8 Jun 2024 08:37:25 -0400 Subject: [PATCH 0175/1097] Github action: Create jscpd.yml (#11738) * Create jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml --- .github/workflows/jscpd.yml | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/jscpd.yml diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml new file mode 100644 index 00000000000..c89fedf8c51 --- /dev/null +++ b/.github/workflows/jscpd.yml @@ -0,0 +1,72 @@ +name: Check for Duplicated Code + +on: + pull_request: + branches: + - master + +jobs: + check-duplication: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history for all branches + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: | + npm install -g jscpd diff-so-fancy + + - name: Get the diff + run: git diff origin/master...HEAD --name-only > changed_files.txt + + - name: Filter JavaScript files + run: | + grep -E '\.js$' changed_files.txt > js_files.txt || true + + - name: Run jscpd on changed files + run: | + if [ -s js_files.txt ]; then + jscpd --files $(cat js_files.txt | tr '\n' ',') --threshold 1 --min-tokens 50 --reporters json --output jscpd-report.json + else + echo '{}' > jscpd-report.json + fi + + - name: Upload jscpd report + if: always() + uses: actions/upload-artifact@v3 + with: + name: jscpd-report + path: jscpd-report.json + + - name: Parse jscpd report and post comment + id: post-comment + run: | + DUPLICATIONS=$(jq '.duplicates | length' jscpd-report.json) + if [ "$DUPLICATIONS" -gt 0 ]; then + COMMENT="Found $DUPLICATIONS duplications in the codebase:\n\n" + COMMENT+=$(jq -r '.duplicates[] | "- `\(.firstFile):\(.lines[0])-\(.lines[1])` duplicated in `\(.secondFile):\(.lines[0])-\(.lines[1])`"' jscpd-report.json) + echo "comment<> $GITHUB_ENV + echo "$COMMENT" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 1 + fi + + - name: Post GitHub comment + if: failure() + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: process.env.comment + }) From 869696d11998c693ba6cb946e039f9da8c0fc8f1 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Sat, 8 Jun 2024 12:54:50 -0400 Subject: [PATCH 0176/1097] GitHub action: report on code dupe (#11739) * Test pr on code dupe * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml --- .github/workflows/jscpd.yml | 58 +++++++++++++--------------------- modules/mediafuseBidAdapter.js | 2 +- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index c89fedf8c51..d3a2de26f11 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -24,49 +24,35 @@ jobs: run: | npm install -g jscpd diff-so-fancy + - name: Create jscpd config file + run: | + echo '{ + "threshold": 20, + "minTokens": 50, + "reporters": [ + "json" + ], + "output": "./", + "pattern": "**/*.js", + "ignore": "**/*spec.js" + }' > .jscpd.json + + - name: Run jscpd on entire codebase + run: jscpd + - name: Get the diff run: git diff origin/master...HEAD --name-only > changed_files.txt - name: Filter JavaScript files run: | - grep -E '\.js$' changed_files.txt > js_files.txt || true + grep -E '\.js$' changed_files.txt | grep -v 'spec.js' > js_files.txt || true - - name: Run jscpd on changed files - run: | - if [ -s js_files.txt ]; then - jscpd --files $(cat js_files.txt | tr '\n' ',') --threshold 1 --min-tokens 50 --reporters json --output jscpd-report.json - else - echo '{}' > jscpd-report.json - fi + - name: List generated files (debug) + run: ls -l - - name: Upload jscpd report + - name: Upload unfiltered jscpd report if: always() uses: actions/upload-artifact@v3 with: - name: jscpd-report - path: jscpd-report.json - - - name: Parse jscpd report and post comment - id: post-comment - run: | - DUPLICATIONS=$(jq '.duplicates | length' jscpd-report.json) - if [ "$DUPLICATIONS" -gt 0 ]; then - COMMENT="Found $DUPLICATIONS duplications in the codebase:\n\n" - COMMENT+=$(jq -r '.duplicates[] | "- `\(.firstFile):\(.lines[0])-\(.lines[1])` duplicated in `\(.secondFile):\(.lines[0])-\(.lines[1])`"' jscpd-report.json) - echo "comment<> $GITHUB_ENV - echo "$COMMENT" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - exit 1 - fi - - - name: Post GitHub comment - if: failure() - uses: actions/github-script@v6 - with: - script: | - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: process.env.comment - }) + name: unfiltered-jscpd-report + path: ./jscpd-report.json diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index d969314f406..c7e31198673 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -1036,7 +1036,7 @@ function hideSASIframe(elementId) { function outstreamRender(bid) { hidedfpContainer(bid.adUnitCode); hideSASIframe(bid.adUnitCode); - // push to render queue because ANOutstreamVideo may not be loaded yet + // push to render queue because ANOutstreamVideo may not be loaded bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, From 77a96f614c927855ff40cb78ecb49c2e49c7efbd Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Mon, 10 Jun 2024 00:43:02 +1000 Subject: [PATCH 0177/1097] Update how key-values are passed into ad server (#11721) --- modules/adnuntiusBidAdapter.js | 22 ++-- src/utils.js | 27 ++++ test/spec/modules/adnuntiusBidAdapter_spec.js | 115 +++++++++++++++--- test/spec/utils_spec.js | 38 ++++++ 4 files changed, 175 insertions(+), 27 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 189e2287571..52c5499b0b8 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { isStr, deepAccess } from '../src/utils.js'; +import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -19,10 +19,6 @@ const METADATA_KEY = 'adn.metaData'; const METADATA_KEY_SEPARATOR = '@@@'; export const misc = { - getUnixTimestamp: function (addDays, asMinutes) { - const multiplication = addDays / (asMinutes ? 1440 : 1); - return Date.now() + (addDays && addDays > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0); - } }; const storageTool = (function () { @@ -50,11 +46,11 @@ const storageTool = (function () { if (datum.key === 'voidAuIds' && Array.isArray(datum.value)) { return true; } - return datum.key && datum.value && datum.exp && datum.exp > misc.getUnixTimestamp() && (!network || network === datum.network); + return datum.key && datum.value && datum.exp && datum.exp > getUnixTimestampFromNow() && (!network || network === datum.network); }) : []; const voidAuIdsEntry = filteredEntries.find(entry => entry.key === 'voidAuIds'); if (voidAuIdsEntry) { - const now = misc.getUnixTimestamp(); + const now = getUnixTimestampFromNow(); voidAuIdsEntry.value = voidAuIdsEntry.value.filter(voidAuId => voidAuId.auId && voidAuId.exp > now); if (!voidAuIdsEntry.value.length) { filteredEntries = filteredEntries.filter(entry => entry.key !== 'voidAuIds'); @@ -73,7 +69,7 @@ const storageTool = (function () { const notNewExistingAuIds = currentVoidAuIds.filter(auIdObj => { return newAuIds.indexOf(auIdObj.value) < -1; }) || []; - const oneDayFromNow = misc.getUnixTimestamp(1); + const oneDayFromNow = getUnixTimestampFromNow(1); const apiIdsArray = newAuIds.map(auId => { return { exp: oneDayFromNow, auId: auId }; }) || []; @@ -86,7 +82,7 @@ const storageTool = (function () { if (key !== 'voidAuIds') { metaAsObj[key + METADATA_KEY_SEPARATOR + network] = { value: apiRespMetadata[key], - exp: misc.getUnixTimestamp(100), + exp: getUnixTimestampFromNow(100), network: network } } @@ -201,10 +197,14 @@ const targetingTool = (function() { }, mergeKvsFromOrtb: function(bidTargeting, bidderRequest) { const kv = getKvsFromOrtb(bidderRequest || {}); - if (!kv) { + if (isEmpty(kv)) { return; } - bidTargeting.kv = {...kv, ...bidTargeting.kv}; + if (bidTargeting.kv && !Array.isArray(bidTargeting.kv)) { + bidTargeting.kv = convertObjectToArray(bidTargeting.kv); + } + bidTargeting.kv = bidTargeting.kv || []; + bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(kv)); } } })(); diff --git a/src/utils.js b/src/utils.js index 2affc52ab8c..be22d4a82a9 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1087,6 +1087,33 @@ export function memoize(fn, key = function (arg) { return arg; }) { return memoized; } +/** + * Returns a Unix timestamp for given time value and unit. + * @param {number} timeValue numeric value, defaults to 0 (which means now) + * @param {string} timeUnit defaults to days (or 'd'), use 'm' for minutes. Any parameter that isn't 'd' or 'm' will return Date.now(). + * @returns {number} + */ +export function getUnixTimestampFromNow(timeValue = 0, timeUnit = 'd') { + const acceptableUnits = ['m', 'd']; + if (acceptableUnits.indexOf(timeUnit) < 0) { + return Date.now(); + } + const multiplication = timeValue / (timeUnit === 'm' ? 1440 : 1); + return Date.now() + (timeValue && timeValue > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0); +} + +/** + * Converts given object into an array, so {key: 1, anotherKey: 'fred', third: ['fred']} is turned + * into [{key: 1}, {anotherKey: 'fred'}, {third: ['fred']}] + * @param {Object} obj the object + * @returns {Array} + */ +export function convertObjectToArray(obj) { + return Object.keys(obj).map(key => { + return {[key]: obj[key]}; + }); +} + /** * Sets dataset attributes on a script * @param {HTMLScriptElement} script diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 4044e62280a..d0d16d0f399 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -6,13 +6,14 @@ import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { getStorageManager } from 'src/storageManager.js'; import { getGlobal } from '../../../src/prebidGlobal'; +import {getUnixTimestampFromNow} from 'src/utils.js'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const usi = utils.generateUUID() - const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp() }, { exp: misc.getUnixTimestamp(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1) }, { key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1) }, { key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp() }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp() }] + const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow() }, { exp: getUnixTimestampFromNow(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: getUnixTimestampFromNow(1) }, { key: 'valid', value: 'also-valid', exp: getUnixTimestampFromNow(1) }, { key: 'expired', value: 'fwefew', exp: getUnixTimestampFromNow() }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: getUnixTimestampFromNow(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow() }] let storage; // need this to make the restore work correctly -- something to do with stubbing static prototype methods @@ -505,7 +506,7 @@ describe('adnuntiusBidAdapter', function () { }); it('Test request changes for voided au ids', function () { - storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp(1) }, { auId: '0000000000000023', exp: misc.getUnixTimestamp(1) }] }])); + storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow(1) }, { auId: '0000000000000023', exp: getUnixTimestampFromNow(1) }] }])); const bRequests = bidderRequests.concat([{ bidId: 'adn-11118b6bc', bidder: 'adnuntius', @@ -592,6 +593,12 @@ describe('adnuntiusBidAdapter', function () { delete bidderRequests[0].params.targeting; }); + function countMatches(actualArray, expectedValue) { + return actualArray.filter(val => { + return JSON.stringify(val) === JSON.stringify(expectedValue); + }).length; + } + it('should pass site data ext as key values to ad server', function () { const ortb2 = { site: { @@ -599,7 +606,7 @@ describe('adnuntiusBidAdapter', function () { data: { '12345': 'true', '45678': 'true', - '9090': 'should-be-overwritten' + '9090': 'should-be-retained' } } } @@ -614,26 +621,102 @@ describe('adnuntiusBidAdapter', function () { expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); - expect(data.adUnits[0].kv['12345']).to.equal('true'); - expect(data.adUnits[0].kv['45678']).to.equal('true'); - expect(data.adUnits[0].kv['9090'][0]).to.equal('take it over'); - expect(data.adUnits[0].kv['merge'][0]).to.equal('this'); + expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(5); delete bidderRequests[0].params.targeting; }); - it('should skip passing site data ext if missing', function () { + it('should pass site data ext as key values to ad server with targeting in different format', function () { const ortb2 = { site: { ext: { + data: { + '12345': 'true', + '45678': 'true', + '9090': 'should-be-retained' + } } } }; + bidderRequests[0].params.targeting = { + kv: [ + {'merge': ['this']}, + {'9090': ['take it over']} + ] + }; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + const data = JSON.parse(request[0].data); + expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(5); + delete bidderRequests[0].params.targeting; + }); + + it('should pass site data ext as key values to ad server even if no kv targeting specified in params.targeting', function () { + const ortb2 = { + site: { + ext: { + data: { + '12345': 'true', + '45678': 'true', + '9090': 'should-be-retained' + } + } + } + }; const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); + expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(3); + + delete bidderRequests[0].params.targeting; + }); + + it('should skip passing site ext if missing', function () { + const ortb2 = { + site: { + ext: { + } + } + }; + + delete bidderRequests[0].params.targeting; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url'); + const data = JSON.parse(request[0].data); + expect(data.adUnits[0]).to.not.have.property('kv'); + }); + + it('should skip passing site ext data if missing', function () { + const ortb2 = { + site: { + ext: { + data: {} + } + } + }; + + delete bidderRequests[0].params.targeting; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url'); + const data = JSON.parse(request[0].data); expect(data.adUnits[0]).to.not.have.property('kv'); }); @@ -985,30 +1068,30 @@ describe('adnuntiusBidAdapter', function () { const usiEntry = results.find(entry => entry.key === 'usi' && entry.network === 'some-network-id'); expect(usiEntry.key).to.equal('usi'); expect(usiEntry.value).to.equal('from-api-server dude'); - expect(usiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); + expect(usiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90)); expect(usiEntry.network).to.equal('some-network-id') const voidAuIdsEntry = results.find(entry => entry.key === 'voidAuIds'); expect(voidAuIdsEntry.key).to.equal('voidAuIds'); expect(voidAuIdsEntry.exp).to.equal(undefined); expect(voidAuIdsEntry.value[0].auId).to.equal('00000000000abcde'); - expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(misc.getUnixTimestamp()); - expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(getUnixTimestampFromNow()); + expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(getUnixTimestampFromNow(2)); expect(voidAuIdsEntry.value[1].auId).to.equal('00000000000fffff'); - expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(misc.getUnixTimestamp()); - expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(getUnixTimestampFromNow()); + expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(getUnixTimestampFromNow(2)); const validEntry = results.find(entry => entry.key === 'valid'); expect(validEntry.key).to.equal('valid'); expect(validEntry.value).to.equal('also-valid'); - expect(validEntry.exp).to.be.greaterThan(misc.getUnixTimestamp()); - expect(validEntry.exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(validEntry.exp).to.be.greaterThan(getUnixTimestampFromNow()); + expect(validEntry.exp).to.be.lessThan(getUnixTimestampFromNow(2)); const randomApiEntry = results.find(entry => entry.key === 'randomApiKey'); expect(randomApiEntry.key).to.equal('randomApiKey'); expect(randomApiEntry.value).to.equal('randomApiValue'); expect(randomApiEntry.network).to.equal('some-network-id'); - expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); + expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90)); }); it('should not process valid response when passed alt bidder that is an adndeal', function () { diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index d7d7007674a..aaeeee48d83 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1170,6 +1170,44 @@ describe('Utils', function () { }); }); + describe('getUnixTimestampFromNow', () => { + it('correctly obtains unix timestamp', () => { + const nowValue = new Date('2024-01-01').valueOf(); + sinon.stub(Date, 'now').returns(nowValue); + let val = utils.getUnixTimestampFromNow(); + expect(val).equal(nowValue); + + val = utils.getUnixTimestampFromNow(1); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24)); + + val = utils.getUnixTimestampFromNow(1, 'd'); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24)); + + val = utils.getUnixTimestampFromNow(1, 'm'); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24 / 1440)); + + val = utils.getUnixTimestampFromNow(2, 'm'); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24 * 2 / 1440)); + + // any value that isn't 'm' or 'd' gets treated as Date.now(); + val = utils.getUnixTimestampFromNow(10, 'o'); + expect(val).equal(nowValue); + }); + }); + + describe('convertObjectToArray', () => { + it('correctly converts object to array', () => { + const obj = {key: 1, anotherKey: 'fred', third: ['fred'], fourth: {sub: {obj: 'test'}}}; + const array = utils.convertObjectToArray(obj); + + expect(JSON.stringify(array[0])).equal(JSON.stringify({'key': 1})) + expect(JSON.stringify(array[1])).equal(JSON.stringify({'anotherKey': 'fred'})) + expect(JSON.stringify(array[2])).equal(JSON.stringify({'third': ['fred']})) + expect(JSON.stringify(array[3])).equal(JSON.stringify({'fourth': {sub: {obj: 'test'}}})); + expect(array.length).to.equal(4); + }); + }); + describe('setScriptAttributes', () => { it('correctly adds attributes from an object', () => { const script = document.createElement('script'), From 1211c5cde2d490b1ef4c9500148ea124a8bce0b3 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Sun, 9 Jun 2024 13:56:17 -0400 Subject: [PATCH 0178/1097] GitHub action: update node (#11741) --- .github/workflows/jscpd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index d3a2de26f11..e6affdcc16d 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -11,12 +11,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for all branches - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '20' @@ -52,7 +52,7 @@ jobs: - name: Upload unfiltered jscpd report if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: unfiltered-jscpd-report path: ./jscpd-report.json From 990e4e81f715f02111776c74e93777d9d941ac33 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Sun, 9 Jun 2024 15:32:17 -0400 Subject: [PATCH 0179/1097] Update jscpd.yml (#11742) * Update jscpd.yml * Update 33acrossBidAdapter.js * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml * Update jscpd.yml --- .github/workflows/jscpd.yml | 59 ++++++++++++++++++++++++++++++++--- modules/33acrossBidAdapter.js | 2 +- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index e6affdcc16d..3cdd682e740 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -43,10 +43,6 @@ jobs: - name: Get the diff run: git diff origin/master...HEAD --name-only > changed_files.txt - - name: Filter JavaScript files - run: | - grep -E '\.js$' changed_files.txt | grep -v 'spec.js' > js_files.txt || true - - name: List generated files (debug) run: ls -l @@ -56,3 +52,58 @@ jobs: with: name: unfiltered-jscpd-report path: ./jscpd-report.json + + - name: Filter jscpd report for changed files + run: | + if [ ! -f ./jscpd-report.json ]; then + echo "jscpd-report.json not found" + exit 1 + fi + echo "Filtering jscpd report for changed files..." + CHANGED_FILES=$(jq -R -s -c 'split("\n")[:-1]' changed_files.txt) + echo "Changed files: $CHANGED_FILES" + jq --argjson changed_files "$CHANGED_FILES" ' + .duplicates | map(select( + (.firstFile?.name as $fname | $changed_files | any(. == $fname)) or + (.secondFile?.name as $sname | $changed_files | any(. == $sname)) + )) + ' ./jscpd-report.json > filtered-jscpd-report.json + cat filtered-jscpd-report.json + + - name: Check if filtered jscpd report exists + id: check_filtered_report + run: | + if [ -s ./filtered-jscpd-report.json ]; then + echo "filtered_report_exists=true" >> $GITHUB_ENV + else + echo "filtered_report_exists=false" >> $GITHUB_ENV + fi + + - name: Upload filtered jscpd report + if: env.filtered_report_exists == 'true' + uses: actions/upload-artifact@v4 + with: + name: filtered-jscpd-report + path: ./filtered-jscpd-report.json + + - name: Post GitHub comment + if: env.filtered_report_exists == 'true' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const filteredReport = JSON.parse(fs.readFileSync('filtered-jscpd-report.json', 'utf8')); + let comment = "Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:\n\n"; + filteredReport.forEach(duplication => { + const firstFile = duplication.firstFile.name; + const secondFile = duplication.secondFile.name; + const lines = duplication.lines; + comment += `- \`${firstFile}\` has ${lines} duplicated lines with \`${secondFile}\`\n`; + }); + comment += "\nReducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Keep up the great work! 🚀"; + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index d8a32d75afc..1eab05ba47f 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -15,7 +15,7 @@ import { import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; -// **************************** UTILS *************************** // +// **************************** UTILS ************************** // const BIDDER_CODE = '33across'; const BIDDER_ALIASES = ['33across_mgni']; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; From ba94bcaf4dceb1fde2b3db4fe6b0695c0feb6b6c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Sun, 9 Jun 2024 15:32:54 -0400 Subject: [PATCH 0180/1097] loyal: fix tests filename (#11743) * Delete test/spec/modules/loyalBidAdapter_spec .js * Add files via upload --- .../modules/{loyalBidAdapter_spec .js => loyalBidAdapter_spec.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/spec/modules/{loyalBidAdapter_spec .js => loyalBidAdapter_spec.js} (100%) diff --git a/test/spec/modules/loyalBidAdapter_spec .js b/test/spec/modules/loyalBidAdapter_spec.js similarity index 100% rename from test/spec/modules/loyalBidAdapter_spec .js rename to test/spec/modules/loyalBidAdapter_spec.js From 1fc387aba1d707ed6c416e2ab918d4c2d3191db8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Sun, 9 Jun 2024 15:52:25 -0400 Subject: [PATCH 0181/1097] Github Actions: fail the pr if duplication found (#11744) * Github Actions: fail the pr if duplication found * Update jscpd.yml * Update jscpd.yml --- .github/workflows/jscpd.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 3cdd682e740..0806d23c207 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -73,7 +73,7 @@ jobs: - name: Check if filtered jscpd report exists id: check_filtered_report run: | - if [ -s ./filtered-jscpd-report.json ]; then + if [ $(wc -l < ./filtered-jscpd-report.json) -gt 1 ]; then echo "filtered_report_exists=true" >> $GITHUB_ENV else echo "filtered_report_exists=false" >> $GITHUB_ENV @@ -107,3 +107,9 @@ jobs: issue_number: context.issue.number, body: comment }); + + - name: Fail if duplications are found + if: env.filtered_report_exists == 'true' + run: | + echo "Duplications found, failing the check." + exit 1 From d215211e644642876df769b8831662347d5605ef Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 10 Jun 2024 08:40:56 -0400 Subject: [PATCH 0182/1097] Update jscpd.yml (#11747) --- .github/workflows/jscpd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 0806d23c207..51b7a7c6123 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -88,7 +88,7 @@ jobs: - name: Post GitHub comment if: env.filtered_report_exists == 'true' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const fs = require('fs'); From 19e9c9917cae660cee140d1282d2dc69ff6da95a Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 10 Jun 2024 09:47:41 -0400 Subject: [PATCH 0183/1097] Github Actions: fix commenting on pr's from forks (#11751) * Update jscpd.yml * Update jscpd.yml --- .github/workflows/jscpd.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 51b7a7c6123..ab3eeebc81d 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -90,6 +90,7 @@ jobs: if: env.filtered_report_exists == 'true' uses: actions/github-script@v7 with: + github-token: ${{ secrets.PAT_TOKEN }} script: | const fs = require('fs'); const filteredReport = JSON.parse(fs.readFileSync('filtered-jscpd-report.json', 'utf8')); @@ -100,7 +101,7 @@ jobs: const lines = duplication.lines; comment += `- \`${firstFile}\` has ${lines} duplicated lines with \`${secondFile}\`\n`; }); - comment += "\nReducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Keep up the great work! 🚀"; + comment += "\nReducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. Keep up the great work! 🚀"; github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, From 33663e92cacf51b167b34219f3ef6e391783d318 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 10 Jun 2024 10:35:18 -0400 Subject: [PATCH 0184/1097] Github actions: Update jscpd.yml to actually fix commenting (#11753) * Update jscpd.yml * Update jscpd.yml --- .github/workflows/jscpd.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index ab3eeebc81d..87ff6787ea9 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -8,6 +8,9 @@ on: jobs: check-duplication: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - name: Checkout code @@ -90,7 +93,6 @@ jobs: if: env.filtered_report_exists == 'true' uses: actions/github-script@v7 with: - github-token: ${{ secrets.PAT_TOKEN }} script: | const fs = require('fs'); const filteredReport = JSON.parse(fs.readFileSync('filtered-jscpd-report.json', 'utf8')); From e2c30ff170f2d6262a637ce79af0c20946a38f9a Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 10 Jun 2024 10:53:42 -0400 Subject: [PATCH 0185/1097] Update jscpd.yml (#11755) --- .github/workflows/jscpd.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 87ff6787ea9..965d163bd7f 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -1,16 +1,13 @@ name: Check for Duplicated Code on: - pull_request: + pull_request_target: branches: - master jobs: check-duplication: runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write steps: - name: Checkout code From 7bdab2b1e8d0368ce01137341939800895ce41f1 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 10 Jun 2024 11:04:14 -0400 Subject: [PATCH 0186/1097] Update jscpd.yml --- .github/workflows/jscpd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 965d163bd7f..4f75b098b9a 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -1,7 +1,7 @@ name: Check for Duplicated Code on: - pull_request_target: + pull_request: branches: - master From 6ba49e67f46c7a390fd0b1c11a39b7c1e08f5a13 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 10 Jun 2024 11:53:11 -0400 Subject: [PATCH 0187/1097] Code duplication: Update jscpd.yml to run in repo context (#11757) * Code duplication: Update jscpd.yml to run in repo context * Update jscpd.yml --- .github/workflows/jscpd.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 4f75b098b9a..281a4e5e8ec 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -1,7 +1,7 @@ name: Check for Duplicated Code on: - pull_request: + pull_request_target: branches: - master @@ -40,8 +40,13 @@ jobs: - name: Run jscpd on entire codebase run: jscpd + - name: Fetch base and target branches + run: | + git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} + git fetch origin +refs/pull/${{ github.event.pull_request.number }}/merge:refs/remotes/pull/${{ github.event.pull_request.number }}/merge + - name: Get the diff - run: git diff origin/master...HEAD --name-only > changed_files.txt + run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge > changed_files.txt - name: List generated files (debug) run: ls -l From a96643fd8a5b3aa0372d755fd73906f39db3c6f1 Mon Sep 17 00:00:00 2001 From: khouajaSadok Date: Wed, 12 Jun 2024 14:09:57 +0200 Subject: [PATCH 0188/1097] Adot Bid Adapter: remove video.placement (#11771) Co-authored-by: KhouajaSadok --- modules/adotBidAdapter.js | 2 +- test/spec/modules/adotBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index 9f2810e13df..fd24ccdeecb 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -197,7 +197,7 @@ function buildVideo(video) { mimes: video.mimes, minduration: video.minduration, maxduration: video.maxduration, - placement: video.placement, + placement: video.plcmt, playbackmethod: video.playbackmethod, pos: video.position || 0, protocols: video.protocols, diff --git a/test/spec/modules/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js index 34252e00f9e..df628088bb0 100644 --- a/test/spec/modules/adotBidAdapter_spec.js +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -124,7 +124,7 @@ describe('Adot Adapter', function () { it('should build request (video)', function () { const bidderRequestId = 'bidderRequestId'; - const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { video: { playerSize: [[300, 250]], minduration: 1, maxduration: 2, api: 'api', linearity: 'linearity', mimes: [], placement: 'placement', playbackmethod: 'playbackmethod', protocols: 'protocol', startdelay: 'startdelay' } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; + const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { video: { playerSize: [[300, 250]], minduration: 1, maxduration: 2, api: 'api', linearity: 'linearity', mimes: [], plcmt: '1', playbackmethod: 'playbackmethod', protocols: 'protocol', startdelay: 'startdelay' } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; const bidderRequest = { position: 2, refererInfo: { page: 'http://localhost.com', domain: 'localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true }, userId: { pubProvidedId: 'userId' }, schain: { ver: '1.0' } }; const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -144,7 +144,7 @@ describe('Adot Adapter', function () { maxduration: 2, mimes: [], minduration: 1, - placement: 'placement', + placement: '1', playbackmethod: 'playbackmethod', pos: 0, protocols: 'protocol', From f61da87227c6a79c2b41e25b1555d6e1e44fcdc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=BEidar=20Bare=C5=A1i=C4=87?= <153560835+bbaresic@users.noreply.github.com> Date: Wed, 12 Jun 2024 07:19:01 -0500 Subject: [PATCH 0189/1097] json-deep-clone (#11768) --- modules/aidemBidAdapter.js | 4 +-- modules/apacdexBidAdapter.js | 4 +-- modules/asteriobidAnalyticsAdapter.js | 10 +++--- modules/bidwatchAnalyticsAdapter.js | 3 +- modules/debugging/bidInterceptor.js | 8 ++--- modules/etargetBidAdapter.js | 4 +-- modules/growthCodeAnalyticsAdapter.js | 2 +- modules/hadronAnalyticsAdapter.js | 2 +- modules/interactiveOffersBidAdapter.js | 22 ++++++------- modules/invisiblyAnalyticsAdapter.js | 9 ++++-- modules/newspassidBidAdapter.js | 11 ++++--- modules/nobidAnalyticsAdapter.js | 2 +- modules/oxxionAnalyticsAdapter.js | 3 +- modules/ozoneBidAdapter.js | 17 ++++++----- modules/prebidmanagerAnalyticsAdapter.js | 10 +++--- modules/sovrnAnalyticsAdapter.js | 12 ++++---- modules/widespaceBidAdapter.js | 4 +-- src/utils.js | 39 ++++++++++++++++++++++++ test/helpers/index_adapter_utils.js | 4 ++- test/spec/modules/debugging_mod_spec.js | 7 ++++- 20 files changed, 116 insertions(+), 61 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 0730149e909..3b55682b217 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, deepSetValue, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; +import {deepAccess, deepClone, deepSetValue, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -132,7 +132,7 @@ function getRegs(bidderRequest) { } function setPrebidRequestEnvironment(payload) { - const __navigator = JSON.parse(JSON.stringify(recur(navigator))); + const __navigator = deepClone(recur(navigator)); delete __navigator.plugins; deepSetValue(payload, 'environment.ri', getRefererInfo()); deepSetValue(payload, 'environment.hl', window.history.length); diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index dadbdb72e95..fce4d16daf6 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn, logError } from '../src/utils.js'; +import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn, logError, deepClone } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; @@ -85,7 +85,7 @@ export const spec = { bidReq.bidFloor = bidFloor; } - bids.push(JSON.parse(JSON.stringify(bidReq))); + bids.push(deepClone(bidReq)); }); const payload = {}; diff --git a/modules/asteriobidAnalyticsAdapter.js b/modules/asteriobidAnalyticsAdapter.js index d5b6c0b4cf7..615293e2641 100644 --- a/modules/asteriobidAnalyticsAdapter.js +++ b/modules/asteriobidAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { generateUUID, getParameterByName, logError, logInfo, parseUrl } from '../src/utils.js' +import { generateUUID, getParameterByName, logError, logInfo, parseUrl, deepClone, hasNonSerializableProperty } from '../src/utils.js' import { ajaxBuilder } from '../src/ajax.js' import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' @@ -192,10 +192,10 @@ function handleEvent(eventType, eventArgs) { return } - try { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {} - } catch (e) { - // keep eventArgs as is + if (eventArgs) { + eventArgs = hasNonSerializableProperty(eventArgs) ? eventArgs : deepClone(eventArgs) + } else { + eventArgs = {} } const pmEvent = {} diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index ffbd125eeab..e385b02fe5f 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -3,6 +3,7 @@ import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { deepClone } from '../src/utils.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -113,7 +114,7 @@ function addTimeout(args) { let stringArgs = JSON.parse(dereferenceWithoutRenderer(args)); argsDereferenced = stringArgs; argsDereferenced.forEach((attr) => { - argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); + argsCleaned.push(filterAttributes(deepClone(attr), false)); }); if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } auctionEnd[eventType].push(argsCleaned); diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 67b24b3f222..102d1723eec 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -1,9 +1,9 @@ import { deepAccess, deepClone, - deepEqual, delayExecution, - mergeDeep + mergeDeep, + hasNonSerializableProperty } from '../../src/utils.js'; /** @@ -22,9 +22,9 @@ Object.assign(BidInterceptor.prototype, { }, serializeConfig(ruleDefs) { const isSerializable = (ruleDef, i) => { - const serializable = deepEqual(ruleDef, JSON.parse(JSON.stringify(ruleDef)), {checkTypes: true}); + const serializable = !hasNonSerializableProperty(ruleDef); if (!serializable && !deepAccess(ruleDef, 'options.suppressWarnings')) { - this.logger.logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); + this.logger.logWarn(`Bid interceptor rule definition #${i + 1} contains non-serializable properties and will be lost after a refresh. Rule definition: `, ruleDef); } return serializable; } diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index 6f2d217993f..1653d849297 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue, isFn, isPlainObject } from '../src/utils.js'; +import { deepClone, deepSetValue, isFn, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -28,7 +28,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { var i, l, bid, reqParams, netRevenue, gdprObject; var request = []; - var bids = JSON.parse(JSON.stringify(validBidRequests)); + var bids = deepClone(validBidRequests); var lastCountry = 'sk'; var floors = []; for (i = 0, l = bids.length; i < l; i++) { diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index d2cd160a364..1bd7a80afa7 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -31,7 +31,7 @@ let analyticsType = 'endpoint'; let growthCodeAnalyticsAdapter = Object.assign(adapter({url: url, analyticsType}), { track({eventType, args}) { - let eventData = args ? JSON.parse(JSON.stringify(args)) : {}; + let eventData = args ? utils.deepClone(args) : {}; let data = {}; if (!trackEvents.includes(eventType)) return; switch (eventType) { diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index d9fd4fa6f19..95a1dfaa5e2 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -53,7 +53,7 @@ let analyticsType = 'endpoint'; let hadronAnalyticsAdapter = Object.assign(adapter({url: HADRON_ANALYTICS_URL, analyticsType}), { track({eventType, args}) { - args = args ? JSON.parse(JSON.stringify(args)) : {}; + args = args ? utils.deepClone(args) : {}; var data = {}; if (!eventsToTrack.includes(eventType)) return; switch (eventType) { diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js index feb576fbb02..8d2f0a7734d 100644 --- a/modules/interactiveOffersBidAdapter.js +++ b/modules/interactiveOffersBidAdapter.js @@ -1,4 +1,4 @@ -import {isNumber, logWarn} from '../src/utils.js'; +import {deepClone, isNumber, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -80,7 +80,7 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { let pageURL = window.location.href; let domain = window.location.hostname; let secure = (window.location.protocol == 'https:' ? 1 : 0); - let openRTBRequest = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequest'])); + let openRTBRequest = deepClone(DEFAULT['OpenRTBBidRequest']); openRTBRequest.id = bidderRequest.bidderRequestId; openRTBRequest.ext = { // TODO: please do not send internal data structures over the network @@ -89,36 +89,36 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { auctionId: prebidRequest.auctionId }; - openRTBRequest.site = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSite'])); + openRTBRequest.site = deepClone(DEFAULT['OpenRTBBidRequestSite']); openRTBRequest.site.id = domain; openRTBRequest.site.name = domain; openRTBRequest.site.domain = domain; openRTBRequest.site.page = pageURL; openRTBRequest.site.ref = prebidRequest.refererInfo.ref; - openRTBRequest.site.publisher = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSitePublisher'])); + openRTBRequest.site.publisher = deepClone(DEFAULT['OpenRTBBidRequestSitePublisher']); openRTBRequest.site.publisher.id = 0; openRTBRequest.site.publisher.name = prebidRequest.refererInfo.domain; openRTBRequest.site.publisher.domain = domain; openRTBRequest.site.publisher.domain = domain; - openRTBRequest.site.content = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSiteContent'])); + openRTBRequest.site.content = deepClone(DEFAULT['OpenRTBBidRequestSiteContent']); - openRTBRequest.source = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSource'])); + openRTBRequest.source = deepClone(DEFAULT['OpenRTBBidRequestSource']); openRTBRequest.source.fd = 0; openRTBRequest.source.tid = prebidRequest.ortb2?.source?.tid; openRTBRequest.source.pchain = ''; - openRTBRequest.device = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestDevice'])); + openRTBRequest.device = deepClone(DEFAULT['OpenRTBBidRequestDevice']); - openRTBRequest.user = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestUser'])); + openRTBRequest.user = deepClone(DEFAULT['OpenRTBBidRequestUser']); openRTBRequest.imp = []; prebidRequest.bids.forEach(function(bid) { if (!ret.partnerId) { ret.partnerId = bid.params.partnerId; } - let imp = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImp'])); + let imp = deepClone(DEFAULT['OpenRTBBidRequestImp']); imp.id = bid.bidId; imp.secure = secure; imp.tagid = bid.adUnitCode; @@ -131,7 +131,7 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { Object.keys(bid.mediaTypes).forEach(function(mediaType) { if (mediaType == 'banner') { - imp.banner = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImpBanner'])); + imp.banner = deepClone(DEFAULT['OpenRTBBidRequestImpBanner']); imp.banner.w = 0; imp.banner.h = 0; imp.banner.format = []; @@ -156,7 +156,7 @@ function parseResponseOpenRTBToPrebidjs(openRTBResponse) { response.seatbid.forEach(function(seatbid) { if (seatbid.bid && seatbid.bid.forEach) { seatbid.bid.forEach(function(bid) { - let prebid = JSON.parse(JSON.stringify(DEFAULT['PrebidBid'])); + let prebid = deepClone(DEFAULT['PrebidBid']); prebid.requestId = bid.impid; prebid.ad = bid.adm; prebid.creativeId = bid.crid; diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index 24c2c452402..af21b1470f6 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -5,7 +5,7 @@ import { ajaxBuilder } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { generateUUID, logInfo } from '../src/utils.js'; +import { deepClone, hasNonSerializableProperty, generateUUID, logInfo } from '../src/utils.js'; import { EVENTS } from '../src/constants.js'; const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events'; @@ -133,7 +133,12 @@ function flush() { } function handleEvent(eventType, eventArgs) { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; + if (eventArgs) { + eventArgs = hasNonSerializableProperty(eventArgs) ? eventArgs : deepClone(eventArgs) + } else { + eventArgs = {} + } + let invisiblyEvent = {}; switch (eventType) { diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index 2a4b2da186b..ac8aa2a0625 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -1,4 +1,5 @@ import { + deepClone, logInfo, logError, deepAccess, @@ -33,12 +34,12 @@ export const spec = { }, loadConfiguredData(bid) { if (this.propertyBag.config) { return; } - this.propertyBag.config = JSON.parse(JSON.stringify(this.config_defaults)); + this.propertyBag.config = deepClone(this.config_defaults); let bidder = bid.bidder || 'newspassid'; this.propertyBag.config.logId = bidder.toUpperCase(); this.propertyBag.config.bidder = bidder; let bidderConfig = config.getConfig(bidder) || {}; - logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); + logInfo('got bidderConfig: ', deepClone(bidderConfig)); let arrGetParams = this.getGetParametersAsObject(); if (bidderConfig.endpointOverride) { if (bidderConfig.endpointOverride.origin) { @@ -131,7 +132,7 @@ export const spec = { buildRequests(validBidRequests, bidderRequest) { this.loadConfiguredData(validBidRequests[0]); this.propertyBag.buildRequestsStart = new Date().getTime(); - logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, deepClone(validBidRequests), 'bidderRequest', deepClone(bidderRequest)); if (this.blockTheRequest()) { return []; } @@ -281,7 +282,7 @@ export const spec = { data: JSON.stringify(npRequest), bidderRequest: bidderRequest }; - logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(npRequest))); + logInfo('buildRequests request data for single = ', deepClone(npRequest)); this.propertyBag.buildRequestsEnd = new Date().getTime(); logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; @@ -309,7 +310,7 @@ export const spec = { if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadConfiguredData(request.bidderRequest.bids[0]); } let startTime = new Date().getTime(); logInfo(`interpretResponse time: ${startTime}. buildRequests done -> interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + logInfo(`serverResponse, request`, deepClone(serverResponse), deepClone(request)); serverResponse = serverResponse.body || {}; let aucId = serverResponse.id; // this will be correct for single requests and non-single if (!serverResponse.hasOwnProperty('seatbid')) { diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index a0aa5ee4989..b5de3df4626 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -241,7 +241,7 @@ window.nobidCarbonizer = { adunit.bids = allowedBidders; } for (const adunit of adunits) { - if (!nobidAnalytics.originalAdUnits[adunit.code]) nobidAnalytics.originalAdUnits[adunit.code] = JSON.parse(JSON.stringify(adunit)); + if (!nobidAnalytics.originalAdUnits[adunit.code]) nobidAnalytics.originalAdUnits[adunit.code] = deepClone(adunit); }; if (this.isActive()) { // 5% of the time do not block; diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js index b3bc2d3479f..9e18c92d25b 100644 --- a/modules/oxxionAnalyticsAdapter.js +++ b/modules/oxxionAnalyticsAdapter.js @@ -3,6 +3,7 @@ import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { deepClone } from '../src/utils.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -125,7 +126,7 @@ function addTimeout(args) { let stringArgs = JSON.parse(dereferenceWithoutRenderer(args)); argsDereferenced = stringArgs; argsDereferenced.forEach((attr) => { - argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); + argsCleaned.push(filterAttributes(deepClone(attr), false)); }); if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } auctionEnd[eventType].push(argsCleaned); diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 0d921f57cda..329aea47bd3 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,4 +1,5 @@ import { + deepClone, logInfo, logError, deepAccess, @@ -42,12 +43,12 @@ export const spec = { }, loadWhitelabelData(bid) { if (this.propertyBag.whitelabel) { return; } - this.propertyBag.whitelabel = JSON.parse(JSON.stringify(this.whitelabel_defaults)); + this.propertyBag.whitelabel = deepClone(this.whitelabel_defaults); let bidder = bid.bidder || 'ozone'; // eg. ozone this.propertyBag.whitelabel.logId = bidder.toUpperCase(); this.propertyBag.whitelabel.bidder = bidder; let bidderConfig = config.getConfig(bidder) || {}; - logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); + logInfo('got bidderConfig: ', deepClone(bidderConfig)); if (bidderConfig.kvpPrefix) { this.propertyBag.whitelabel.keyPrefix = bidderConfig.kvpPrefix; } @@ -177,7 +178,7 @@ export const spec = { this.propertyBag.buildRequestsStart = new Date().getTime(); let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; - logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, deepClone(validBidRequests), 'bidderRequest', deepClone(bidderRequest)); if (this.blockTheRequest()) { return []; } @@ -403,7 +404,7 @@ export const spec = { data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(ozoneRequest))); + logInfo('buildRequests request data for single = ', deepClone(ozoneRequest)); this.propertyBag.buildRequestsEnd = new Date().getTime(); logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; @@ -444,7 +445,7 @@ export const spec = { if (mediaTypesSizes.native) { ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native}); } - logInfo('getFloorObjectForAuction returning : ', JSON.parse(JSON.stringify(ret))); + logInfo('getFloorObjectForAuction returning : ', deepClone(ret)); return ret; }, interpretResponse(serverResponse, request) { @@ -453,7 +454,7 @@ export const spec = { let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + logInfo(`serverResponse, request`, deepClone(serverResponse), deepClone(request)); serverResponse = serverResponse.body || {}; let aucId = serverResponse.id; // this will be correct for single requests and non-single if (!serverResponse.hasOwnProperty('seatbid')) { @@ -573,7 +574,7 @@ export const spec = { } let endTime = new Date().getTime(); logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); - logInfo('interpretResponse arrAllBids (serialised): ', JSON.parse(JSON.stringify(arrAllBids))); // this is ok to log because the renderer has not been attached yet + logInfo('interpretResponse arrAllBids (serialised): ', deepClone(arrAllBids)); // this is ok to log because the renderer has not been attached yet return arrAllBids; }, setBidMediaTypeIfNotExist(thisBid, mediaType) { @@ -1039,7 +1040,7 @@ function newRenderer(adUnitCode, rendererOptions = {}) { } function outstreamRender(bid) { logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid = (first static, then reference)'); - logInfo(JSON.parse(JSON.stringify(spec.getLoggableBidObject(bid)))); + logInfo(deepClone(spec.getLoggableBidObject(bid))); bid.renderer.push(() => { logInfo('Going to execute window.ozoneVideo.outstreamRender'); window.ozoneVideo.outstreamRender(bid); diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index 858e30068db..39677d51320 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { generateUUID, getParameterByName, logError, parseUrl, logInfo } from '../src/utils.js'; +import { deepClone, generateUUID, getParameterByName, hasNonSerializableProperty, logError, parseUrl, logInfo } from '../src/utils.js'; import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -199,10 +199,10 @@ function trimBidderRequest(bidderRequest) { } function handleEvent(eventType, eventArgs) { - try { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; - } catch (e) { - // keep eventArgs as is + if (eventArgs) { + eventArgs = hasNonSerializableProperty(eventArgs) ? eventArgs : deepClone(eventArgs) + } else { + eventArgs = {} } const pmEvent = {}; diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js index a89b365e074..f8329b33f3a 100644 --- a/modules/sovrnAnalyticsAdapter.js +++ b/modules/sovrnAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {logError, timestamp} from '../src/utils.js'; +import {deepClone, logError, timestamp} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adaptermanager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; @@ -99,7 +99,7 @@ class BidWinner { // eslint-disable-next-line no-undef this.body.prebidVersion = $$REPO_AND_VERSION$$ this.body.sovrnId = sovrnId - this.body.winningBid = JSON.parse(JSON.stringify(event)) + this.body.winningBid = deepClone(event) this.body.url = rootURL this.body.payload = 'winner' delete this.body.winningBid.ad @@ -156,7 +156,7 @@ class AuctionData { * @param {*} event - the args object from the auction event */ bidRequested(event) { - const eventCopy = JSON.parse(JSON.stringify(event)) + const eventCopy = deepClone(event) delete eventCopy.doneCbCallCount delete eventCopy.auctionId this.auction.requests.push(eventCopy) @@ -170,13 +170,13 @@ class AuctionData { findBid(event) { const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode)) if (!bidder) { - this.auction.unsynced.push(JSON.parse(JSON.stringify(event))) + this.auction.unsynced.push(deepClone(event)) } let bid = find(bidder.bids, b => (b.bidId === event.requestId)) if (!bid) { event.unmatched = true - bidder.bids.push(JSON.parse(JSON.stringify(event))) + bidder.bids.push(deepClone(event)) } return bid } @@ -190,7 +190,7 @@ class AuctionData { originalBid(event) { let bid = this.findBid(event) if (bid) { - Object.assign(bid, JSON.parse(JSON.stringify(event))) + Object.assign(bid, deepClone(event)) this.dropBidFields.forEach((f) => delete bid[f]) } } diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index ea6f1bce793..6fa14f5d68c 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -1,6 +1,6 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; +import {deepClone, parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -212,7 +212,7 @@ function getLcuid() { } function encodedParamValue(value) { - const requiredStringify = typeof JSON.parse(JSON.stringify(value)) === 'object'; + const requiredStringify = typeof deepClone(value) === 'object'; return encodeURIComponent(requiredStringify ? JSON.stringify(value) : value); } diff --git a/src/utils.js b/src/utils.js index be22d4a82a9..1f05b7b37c1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1150,3 +1150,42 @@ export function binarySearch(arr, el, key = (el) => el) { } return left; } + +/** + * Checks if an object has non-serializable properties. + * Non-serializable properties are functions and RegExp objects. + * + * @param {Object} obj - The object to check. + * @param {Set} checkedObjects - A set of properties that have already been checked. + * @returns {boolean} - Returns true if the object has non-serializable properties, false otherwise. + */ +export function hasNonSerializableProperty(obj, checkedObjects = new Set()) { + for (const key in obj) { + const value = obj[key]; + const type = typeof value; + + if ( + value === undefined || + type === 'function' || + type === 'symbol' || + value instanceof RegExp || + value instanceof Map || + value instanceof Set || + value instanceof Date || + (value !== null && type === 'object' && value.hasOwnProperty('toJSON')) + ) { + return true; + } + if (value !== null && type === 'object' && value.constructor === Object) { + if (checkedObjects.has(value)) { + // circular reference, means we have a non-serializable property + return true; + } + checkedObjects.add(value); + if (hasNonSerializableProperty(value, checkedObjects)) { + return true; + } + } + } + return false; +} diff --git a/test/helpers/index_adapter_utils.js b/test/helpers/index_adapter_utils.js index f01145b573d..0eb7af88d14 100644 --- a/test/helpers/index_adapter_utils.js +++ b/test/helpers/index_adapter_utils.js @@ -1,3 +1,5 @@ +import { deepClone } from '../../src/utils'; + var AllowedAdUnits = [[728, 90], [120, 600], [300, 250], [160, 600], [336, 280], [234, 60], [300, 600], [300, 50], [320, 50], [970, 250], [300, 1050], [970, 90], [180, 150]]; var UnsupportedAdUnits = [[700, 100], [100, 600], [300, 200], [100, 600], [300, 200], [200, 60], [900, 200], [300, 1000], [900, 90], [100, 100]]; @@ -117,7 +119,7 @@ exports.getExpectedIndexSlots = function(bids) { } function clone(x) { - return JSON.parse(JSON.stringify(x)); + return deepClone(x); } // returns the difference(lhs, rhs), difference(rhs,lhs), and intersection(lhs, rhs) based on the object keys diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index 4de51cc02a4..cd7e642699a 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -37,7 +37,12 @@ describe('bid interceptor', () => { describe('serializeConfig', () => { Object.entries({ regexes: /pat/, - functions: () => ({}) + functions: () => ({}), + 'undefined': undefined, + date: new Date(), + symbol: Symbol('test'), + map: new Map(), + set: new Set(), }).forEach(([test, arg]) => { it(`should filter out ${test}`, () => { const valid = [{key1: 'value'}, {key2: 'value'}]; From df57b8150e33186c8af0f487af5c219fb7017b91 Mon Sep 17 00:00:00 2001 From: relaido <63339139+relaido@users.noreply.github.com> Date: Wed, 12 Jun 2024 21:26:04 +0900 Subject: [PATCH 0190/1097] Relaido Bid Adapter : add placementId to renderAd parameters (#11769) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * Add placementId to renderAd parameters --------- Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun Co-authored-by: n.maeura --- modules/relaidoBidAdapter.js | 3 ++- test/spec/modules/relaidoBidAdapter_spec.js | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index a180d04cc71..69f9be8d107 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -19,7 +19,7 @@ import { isSlotMatchingAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.2.0'; +const ADAPTER_VERSION = '1.2.1'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; @@ -270,6 +270,7 @@ function outstreamRender(bid) { height: bid.height, vastXml: bid.vastXml, mediaType: bid.mediaType, + placementId: bid.placementId, }); }); } diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 4a07c84a494..5cb47eb8f51 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -115,6 +115,11 @@ describe('RelaidoAdapter', function () { }] } }; + window.RelaidoPlayer = { + renderAd: function() { + return null; + } + }; }); afterEach(() => { @@ -569,4 +574,20 @@ describe('RelaidoAdapter', function () { expect(query.ref).to.include(window.location.href); }); }); + + describe('spec.outstreamRender', function () { + it('Should to pass a Bid to renderAd', function () { + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + const response = bidResponses[0]; + sinon.spy(window.RelaidoPlayer, 'renderAd'); + response.renderer.render(response); + const renderCall = window.RelaidoPlayer.renderAd.getCall(0); + const arg = renderCall.args[0]; + expect(arg.width).to.equal(640); + expect(arg.height).to.equal(360); + expect(arg.vastXml).to.equal(''); + expect(arg.mediaType).to.equal(VIDEO); + expect(arg.placementId).to.equal(100000); + }); + }); }); From 12e2e1c0d914519a78d207662b6ddd8ea1dbde16 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 12 Jun 2024 05:32:37 -0700 Subject: [PATCH 0191/1097] PBjs Core and Tests : fix spurious tests (#11767) * Fix spurious tests * fix ajax tests --- src/ajax.js | 2 +- .../spec/creative/crossDomainCreative_spec.js | 93 +++++++++++-------- test/spec/unit/core/ajax_spec.js | 32 +++---- 3 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/ajax.js b/src/ajax.js index 92bff6dd527..1ef100e7fd3 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -130,7 +130,7 @@ export function attachCallbacks(fetchPm, callback) { success: typeof callback === 'function' ? callback : () => null, error: (e, x) => logError('Network error', e, x) }; - fetchPm.then(response => response.text().then((responseText) => [response, responseText])) + return fetchPm.then(response => response.text().then((responseText) => [response, responseText])) .then(([response, responseText]) => { const xhr = toXHR(response, responseText); response.ok || response.status === 304 ? success(responseText, xhr) : error(response.statusText, xhr); diff --git a/test/spec/creative/crossDomainCreative_spec.js b/test/spec/creative/crossDomainCreative_spec.js index f4c98aa7b50..1bba54c752d 100644 --- a/test/spec/creative/crossDomainCreative_spec.js +++ b/test/spec/creative/crossDomainCreative_spec.js @@ -38,6 +38,27 @@ describe('cross-domain creative', () => { renderAd = renderer(win); }) + function waitFor(predicate, timeout = 1000) { + let timedOut = false; + return new Promise((resolve, reject) => { + let to = setTimeout(() => { + timedOut = true; + reject(new Error('timeout')) + }, timeout) + resolve = (orig => () => { clearTimeout(to); orig() })(resolve); + function check() { + if (!timedOut) { + setTimeout(() => { + if (predicate()) { + resolve() + } else check(); + }, 50) + } + } + check(); + }) + } + it('derives postMessage target origin from pubUrl ', () => { renderAd({pubUrl: 'https://domain.com:123/path'}); expect(messages[0].targetOrigin).to.eql('https://domain.com:123') @@ -75,11 +96,11 @@ describe('cross-domain creative', () => { sinon.assert.notCalled(mkIframe); }) - it('signals AD_RENDER_FAILED on exceptions', (done) => { + it('signals AD_RENDER_FAILED on exceptions', () => { mkIframe.callsFake(() => { throw new Error('error message') }); renderAd({adId: '123', pubUrl: ORIGIN}); reply({message: MESSAGE_RESPONSE, adId: '123', ad: 'markup'}); - setTimeout(() => { + return waitFor(() => messages[1]?.payload).then(() => { expect(messages[1].payload).to.eql({ message: MESSAGE_EVENT, adId: '123', @@ -89,8 +110,7 @@ describe('cross-domain creative', () => { message: 'error message' } }) - done(); - }, 100) + }) }); describe('renderer', () => { @@ -99,7 +119,7 @@ describe('cross-domain creative', () => { win.document.body.appendChild.callsFake(document.body.appendChild.bind(document.body)); }); - it('sets up and runs renderer', (done) => { + it('sets up and runs renderer', () => { window._render = sinon.stub(); const data = { message: MESSAGE_RESPONSE, @@ -108,14 +128,11 @@ describe('cross-domain creative', () => { } renderAd({adId: '123', pubUrl: ORIGIN}); reply(data); - setTimeout(() => { - try { - sinon.assert.calledWith(window._render, data, sinon.match.any, win); - done() - } finally { - delete window._render; - } - }, 100) + return waitFor(() => window._render.args.length).then(() => { + sinon.assert.calledWith(window._render, data, sinon.match.any, win); + }).finally(() => { + delete window._render; + }) }); Object.entries({ @@ -125,14 +142,14 @@ describe('cross-domain creative', () => { 'rejects (w/error)': ['window.render = function() { return Promise.reject(new Error("msg")) }'], 'rejects (w/reason)': ['window.render = function() { return Promise.reject({reason: "other", message: "msg"}) }', 'other'], }).forEach(([t, [renderer, reason = ERROR_EXCEPTION, message = 'msg']]) => { - it(`signals AD_RENDER_FAILED on renderer that ${t}`, (done) => { + it(`signals AD_RENDER_FAILED on renderer that ${t}`, () => { renderAd({adId: '123', pubUrl: ORIGIN}); reply({ message: MESSAGE_RESPONSE, adId: '123', renderer }); - setTimeout(() => { + return waitFor(() => messages[1]?.payload).then(() => { sinon.assert.match(messages[1].payload, { adId: '123', message: MESSAGE_EVENT, @@ -142,34 +159,33 @@ describe('cross-domain creative', () => { message: sinon.match(val => message == null || message === val) } }); - done(); - }, 100) + }) }) }); - it('signals AD_RENDER_SUCCEEDED when renderer resolves', (done) => { + it('signals AD_RENDER_SUCCEEDED when renderer resolves', () => { renderAd({adId: '123', pubUrl: ORIGIN}); reply({ message: MESSAGE_RESPONSE, adId: '123', renderer: 'window.render = function() { return new Promise((resolve) => { window.parent._resolve = resolve })}' }); - setTimeout(() => { + return waitFor(() => window._resolve).then(() => { expect(messages[1]).to.not.exist; window._resolve(); - setTimeout(() => { - sinon.assert.match(messages[1].payload, { - adId: '123', - message: MESSAGE_EVENT, - event: EVENT_AD_RENDER_SUCCEEDED - }) - delete window._resolve; - done(); - }, 100) - }, 100) + return waitFor(() => messages[1]?.payload) + }).then(() => { + sinon.assert.match(messages[1].payload, { + adId: '123', + message: MESSAGE_EVENT, + event: EVENT_AD_RENDER_SUCCEEDED + }) + }).finally(() => { + delete window._resolve; + }) }) - it('is provided a sendMessage that accepts replies', (done) => { + it('is provided a sendMessage that accepts replies', () => { renderAd({adId: '123', pubUrl: ORIGIN}); window._reply = sinon.stub(); reply({ @@ -177,17 +193,14 @@ describe('cross-domain creative', () => { message: MESSAGE_RESPONSE, renderer: 'window.render = function(_, {sendMessage}) { sendMessage("test", "data", function(reply) { window.parent._reply(reply) }) }' }); - setTimeout(() => { + return waitFor(() => messages[1]?.payload).then(() => { reply('response', 1); - setTimeout(() => { - try { - sinon.assert.calledWith(window._reply, sinon.match({data: JSON.stringify('response')})); - done(); - } finally { - delete window._reply; - } - }, 100) - }, 100) + return waitFor(() => window._reply.args.length) + }).then(() => { + sinon.assert.calledWith(window._reply, sinon.match({data: JSON.stringify('response')})); + }).finally(() => { + delete window._reply; + }) }); }); }); diff --git a/test/spec/unit/core/ajax_spec.js b/test/spec/unit/core/ajax_spec.js index dd03ad1a761..8140123d9fc 100644 --- a/test/spec/unit/core/ajax_spec.js +++ b/test/spec/unit/core/ajax_spec.js @@ -405,24 +405,22 @@ describe('attachCallbacks', () => { }).forEach(([cbType, makeResponse]) => { it(`do not choke ${cbType} callbacks`, () => { const {response} = makeResponse(); - return new Promise((resolve) => { - const result = {success: false, error: false}; - attachCallbacks(Promise.resolve(response), { - success() { - result.success = true; - throw new Error(); - }, - error() { - result.error = true; - throw new Error(); - } + const result = {success: false, error: false}; + return attachCallbacks(Promise.resolve(response), { + success() { + result.success = true; + throw new Error(); + }, + error() { + result.error = true; + throw new Error(); + } + }).catch(() => null) + .then(() => { + Object.entries(result).forEach(([typ, ran]) => { + expect(ran).to.be[typ === cbType ? 'true' : 'false']; + }); }); - setTimeout(() => resolve(result), 20); - }).then(result => { - Object.entries(result).forEach(([typ, ran]) => { - expect(ran).to.be[typ === cbType ? 'true' : 'false'] - }) - }); }); }); }); From 236a8ad55ea612e0069790a0177f12b99d95574e Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:26:30 +0300 Subject: [PATCH 0192/1097] Smartyads Analytics Adapter : initial release (#11397) * Smartyads analytics adapter setup * add tests and doc --------- Co-authored-by: vrishko --- modules/smartyadsAnalyticsAdapter.js | 133 ++++++ modules/smartyadsAnalyticsAdapter.md | 11 + .../modules/smartyadsAnalyticsAdapter_spec.js | 441 ++++++++++++++++++ 3 files changed, 585 insertions(+) create mode 100644 modules/smartyadsAnalyticsAdapter.js create mode 100644 modules/smartyadsAnalyticsAdapter.md create mode 100644 test/spec/modules/smartyadsAnalyticsAdapter_spec.js diff --git a/modules/smartyadsAnalyticsAdapter.js b/modules/smartyadsAnalyticsAdapter.js new file mode 100644 index 00000000000..7784e0bc831 --- /dev/null +++ b/modules/smartyadsAnalyticsAdapter.js @@ -0,0 +1,133 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { ajax } from '../src/ajax.js'; + +const { + AUCTION_INIT, + AUCTION_END, + BID_WON, + BID_TIMEOUT, + BIDDER_ERROR, + BID_REJECTED, + BID_REQUESTED, + AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, + AUCTION_TIMEOUT +} = EVENTS; + +const URL = 'https://ps.itdsmr.com'; +const ANALYTICS_TYPE = 'endpoint'; +const BIDDER_CODE = 'smartyads'; +const GVLID = 534; + +let smartyParams = {}; + +let smartyadsAdapter = Object.assign({}, + adapter({ + url: URL, + analyticsType: ANALYTICS_TYPE, + }), + { + track({ eventType, args }) { + switch (eventType) { + case AUCTION_INIT: + case AUCTION_TIMEOUT: + case AUCTION_END: + auctionHandler(eventType, args); + break; + case BID_REQUESTED: + if (args.bidderCode === BIDDER_CODE) { + for (const bid of args.bids) { + const bidParams = bid.params?.length ? bid.params[0] : bid.params; + smartyParams[bid.bidId] = bidParams; + } + }; + break; + case BID_WON: + case BID_TIMEOUT: + case BID_REJECTED: + bidHandler(eventType, args); + break; + case BIDDER_ERROR: + onBidderError(args); + break; + case AD_RENDER_FAILED: + case AD_RENDER_SUCCEEDED: + onAdRender(eventType, args); + break; + default: + break; + } + } + } +); + +const sendDataToServer = (data) => { + ajax(URL, () => {}, JSON.stringify(data)); +} + +const auctionHandler = (eventType, data) => { + const auctionData = { + auctionId: data.auctionId, + status: eventType, + timeout: data.timeout, + metrics: data.metrics, + bidderRequests: data.bidderRequests?.map(bidderRequest => { + delete bidderRequest.gdprConsent; + delete bidderRequest.refererInfo; + return bidderRequest; + }).filter(request => request.bidderCode === BIDDER_CODE), + } + + sendDataToServer({ eventType, auctionData }); +} + +const bidHandler = (eventType, bid) => { + let bids = bid.length ? bid : [ bid ]; + + for (const bidObj of bids) { + let bidToSend; + + if (bidObj.bidderCode != BIDDER_CODE) { + if (eventType === BID_WON) { + bidToSend = { + cpm: bidObj.cpm, + auctionId: bidObj.auctionId + }; + } else continue; + } + + bidToSend = bidObj; + + if (eventType === BID_REJECTED) { + bidToSend.params = smartyParams[bid.requestId]; + } + + sendDataToServer({ eventType, bid: bidToSend }); + } +} + +const onBidderError = (data) => { + sendDataToServer({ + eventType: BIDDER_ERROR, + error: data.error, + bidderRequests: data?.bidderRequests?.length + ? data.bidderRequests.filter(request => request.bidderCode === BIDDER_CODE) + : [ data.bidderRequest ] + }); +} + +const onAdRender = (eventType, data) => { + if (data?.bid?.bidderCode === BIDDER_CODE) { + sendDataToServer({ eventType, renderData: data }); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: smartyadsAdapter, + code: BIDDER_CODE, + gvlid: GVLID +}) + +export default smartyadsAdapter; diff --git a/modules/smartyadsAnalyticsAdapter.md b/modules/smartyadsAnalyticsAdapter.md new file mode 100644 index 00000000000..e08b2a02cb7 --- /dev/null +++ b/modules/smartyadsAnalyticsAdapter.md @@ -0,0 +1,11 @@ +#### Description + +Module that enables SmartyAds analytics + +### Configuration + +```javascript + pbjs.enableAnalytics({ + provider: 'smartyads' + }); +``` \ No newline at end of file diff --git a/test/spec/modules/smartyadsAnalyticsAdapter_spec.js b/test/spec/modules/smartyadsAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..de7e08a8a77 --- /dev/null +++ b/test/spec/modules/smartyadsAnalyticsAdapter_spec.js @@ -0,0 +1,441 @@ +import smartyadsAnalytics from 'modules/smartyadsAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from '../../../src/constants'; + +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); + +describe('SmartyAds Analytics', function () { + const auctionEnd = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'tag_200124_banner', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'smartyads', + 'params': { + 'placementId': 123456 + } + }, + { + 'bidder': 'smartyadsAst', + 'params': { + 'placementId': 234567 + } + } + ], + 'sizes': [ + [ + 300, + 600 + ] + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + } + ], + 'adUnitCodes': [ + 'tag_200124_banner' + ], + 'bidderRequests': [ + { + 'bidderCode': 'smartyads', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'bidderRequestId': '11dc6ff6378de7', + 'bids': [ + { + 'bidder': 'smartyads', + 'params': { + 'placementId': 123456 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '2bd3e8ff8a113f', + 'bidderRequestId': '11dc6ff6378de7', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ova': 'cleared' + } + ], + 'auctionStart': 1647424261187, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'CONSENT', + 'gdprApplies': true, + 'apiVersion': 2, + 'vendorData': 'a lot of borring stuff', + }, + 'start': 1647424261189 + }, + ], + 'noBids': [ + { + 'bidder': 'smartyadsAst', + 'params': { + 'placementId': 10471298 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '5fe418f2d70364', + 'bidderRequestId': '4229a45ab8ea87', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'smartyads', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'video', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158534630', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ], + 'demandSource': 'something' + }, + 'renderer': 'something', + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261559, + 'requestTimestamp': 1647424261189, + 'bidder': 'smartyads', + 'adUnitCode': 'tag_200124_banner', + 'timeToRespond': 370, + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'smartyads', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '300x600', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + } + } + ], + 'winningBids': [ + + ], + 'timeout': 1000 + }; + + let bidWon = { + 'bidderCode': 'smartyads', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158533702', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'renderer': 'something', + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261558, + 'requestTimestamp': 1647424261189, + 'bidder': 'smartyads', + 'adUnitCode': 'tag_200123_banner', + 'timeToRespond': 369, + 'originalBidder': 'smartyads', + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '970x250', + 'adserverTargeting': { + 'hb_bidder': 'smartyads', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '970x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 123456 + } + ] + }; + + let renderData = { + 'doc': { + 'location': { + 'ancestorOrigins': { + '0': 'http://localhost:9999' + }, + 'href': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', + 'origin': 'http://localhost:9999', + 'protocol': 'http:', + 'host': 'localhost:9999', + 'hostname': 'localhost', + 'port': '9999', + 'pathname': '/integrationExamples/gpt/gdpr_hello_world.html', + 'search': '?pbjs_debug=true', + 'hash': '' + }, + '__reactEvents$ffsiznmtn3p': {} + }, + 'bid': { + 'bidderCode': 'smartyads', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '3c81d46b03abb', + 'requestId': '2be8997209a61c', + 'transactionId': 'b3091239-660a-4b13-94a1-1737e11743c5', + 'adUnitId': '8097f75e-8c3c-4b3e-aebb-b261caa5e331', + 'auctionId': '5c0d10bf-96cb-4afa-92ac-2ef75470da22', + 'mediaType': 'banner', + 'source': 'client', + 'ad': "
\"test
", + 'cpm': 0.1, + 'ttl': 120, + 'creativeId': '123', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'HASH', + 'sid': 1234, + 'meta': { + 'advertiserDomains': [ + 'smartyads.com' + ], + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'name': 'smartyads' + } + ] + } + }, + 'metrics': { + 'requestBids.usp': 0.20000000298023224, + 'requestBids.gdpr': 67.19999999925494, + 'requestBids.fpd': 4.299999997019768, + 'requestBids.validate': 0.29999999701976776, + 'requestBids.makeRequests': 1.800000000745058, + 'requestBids.total': 740.9000000022352, + 'requestBids.callBids': 663, + 'adapter.client.validate': 0, + 'adapters.client.smartyads.validate': 0, + 'adapter.client.buildRequests': 1, + 'adapters.client.smartyads.buildRequests': 1, + 'adapter.client.total': 661.6999999992549, + 'adapters.client.smartyads.total': 661.6999999992549, + 'adapter.client.net': 657.8999999985099, + 'adapters.client.smartyads.net': 657.8999999985099, + 'adapter.client.interpretResponse': 0, + 'adapters.client.smartyads.interpretResponse': 0, + 'addBidResponse.validate': 0.19999999925494194, + 'addBidResponse.categoryTranslation': 0, + 'addBidResponse.dchain': 0.10000000149011612, + 'addBidResponse.dsa': 0, + 'addBidResponse.multibid': 0, + 'addBidResponse.total': 1.5999999977648258, + 'render.pending': 368.80000000074506, + 'render.e2e': 1109.7000000029802 + }, + 'adapterCode': 'smartyads', + 'originalCpm': 0.1, + 'originalCurrency': 'USD', + 'responseTimestamp': 1715350155081, + 'requestTimestamp': 1715350154420, + 'bidder': 'smartyads', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 661, + 'pbLg': '0.00', + 'pbMg': '0.10', + 'pbHg': '0.10', + 'pbAg': '0.10', + 'pbDg': '0.10', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'smartyads', + 'hb_adid': '3c81d46b03abb', + 'hb_pb': '0.10', + 'hb_size': '300x250', + 'hb_deal': 'HASH', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'smartyads.com', + 'hb_crid': '123' + }, + 'latestTargetedAuctionId': '5c0d10bf-96cb-4afa-92ac-2ef75470da22', + 'status': 'rendered', + 'params': [ + { + 'sourceid': '983', + 'host': 'prebid', + 'accountid': '18349', + 'traffic': 'banner' + } + ] + } + }; + + after(function () { + smartyadsAnalytics.disableAnalytics(); + }); + + describe('main test', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(smartyadsAnalytics, 'track'); + }); + + afterEach(function () { + events.getEvents.restore(); + smartyadsAnalytics.disableAnalytics(); + smartyadsAnalytics.track.restore(); + }); + + it('test auctionEnd', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'smartyads', + adapter: smartyadsAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'smartyads', + }); + + events.emit(EVENTS.AUCTION_END, auctionEnd); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('auctionData'); + expect(message).to.have.property('eventType').and.to.equal(EVENTS.AUCTION_END); + expect(message.auctionData).to.have.property('auctionId'); + expect(message.auctionData.bidderRequests).to.have.length(1); + }); + + it('test bidWon', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'smartyads', + adapter: smartyadsAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'smartyads', + }); + + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('eventType').and.to.equal(EVENTS.BID_WON); + expect(message).to.have.property('bid'); + expect(message.bid).to.have.property('bidder').and.to.equal('smartyads'); + expect(message.bid).to.have.property('cpm').and.to.equal(bidWon.cpm); + }); + + it('test adRender', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'smartyads', + adapter: smartyadsAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'smartyads', + }); + + events.emit(EVENTS.AD_RENDER_SUCCEEDED, renderData); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('eventType').and.to.equal(EVENTS.AD_RENDER_SUCCEEDED); + expect(message).to.have.property('renderData'); + expect(message.renderData).to.have.property('doc'); + expect(message.renderData).to.have.property('doc'); + expect(message.renderData).to.have.property('bid'); + expect(message.renderData.bid).to.have.property('adId').and.to.equal(renderData.bid.adId); + }); + }); +}); From 69d79062781c38d8fafcac189f807e99c4b28f65 Mon Sep 17 00:00:00 2001 From: maelmrgt <77864748+maelmrgt@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:12:00 +0200 Subject: [PATCH 0193/1097] feat(analytics): retrieve cpm and currency from bids (#11774) --- modules/greenbidsAnalyticsAdapter.js | 4 +++- test/spec/modules/greenbidsAnalyticsAdapter_spec.js | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index d6293adac82..86b306b0e7f 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -6,7 +6,7 @@ import {deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName} const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.2.1'; +export const ANALYTICS_VERSION = '2.3.0'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; @@ -135,6 +135,8 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER } else { if (status === BIDDER_STATUS.BID) { message.adUnits[adUnitIndex].bidders[bidderIndex].hasBid = true; + message.adUnits[adUnitIndex].bidders[bidderIndex].cpm = bid.cpm; + message.adUnits[adUnitIndex].bidders[bidderIndex].currency = bid.currency; } else if (status === BIDDER_STATUS.TIMEOUT) { message.adUnits[adUnitIndex].bidders[bidderIndex].isTimeout = true; } diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index 918da50d8bc..b4261806d24 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -278,12 +278,12 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { { bidder: 'greenbids', isTimeout: false, - hasBid: true + hasBid: true, }, { bidder: 'greenbidsx', isTimeout: false, - hasBid: true + hasBid: true, } ] }, @@ -312,7 +312,9 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { { bidder: 'greenbids', isTimeout: true, - hasBid: true + hasBid: true, + cpm: 0.09, + currency: 'USD', } ] } From 13c3dbb840fa056dc35b94ee984e1992ad9fe6c3 Mon Sep 17 00:00:00 2001 From: Tetiana Volkova <126241934+tetianaatsmaato@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:10:06 +0200 Subject: [PATCH 0194/1097] Added DSA support for smaato adapter (#11789) --- modules/smaatoBidAdapter.js | 20 ++---- test/spec/modules/smaatoBidAdapter_spec.js | 71 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 8f325aa13c9..5d3ab0414a4 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -19,7 +19,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.0' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.1' const TTL = 300; const CURRENCY = 'USD'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; @@ -141,7 +141,8 @@ export const spec = { meta: { advertiserDomains: bid.adomain, networkName: bid.bidderName, - agencyId: seatbid.seat + agencyId: seatbid.seat, + ...(bid.ext?.dsa && {dsa: bid.ext.dsa}) } }; @@ -276,18 +277,7 @@ const converter = ortbConverter({ } } - if (request.device) { - if (bidRequest.params.app) { - if (!deepAccess(request.device, 'geo')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(request.device, 'geo', geo); - } - if (!deepAccess(request.device, 'ifa')) { - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(request.device, 'ifa', ifa); - } - } - } else { + if (!request.device) { request.device = { language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', ua: navigator.userAgent, @@ -295,6 +285,8 @@ const converter = ortbConverter({ h: screen.height, w: screen.width } + } + if (bidRequest.params.app) { if (!deepAccess(request.device, 'geo')) { const geo = deepAccess(bidRequest, 'params.app.geo'); deepSetValue(request.device, 'geo', geo); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 2ac2a1e5c33..802fa8c254b 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -462,6 +462,48 @@ describe('smaatoBidAdapterTest', () => { const req = extractPayloadOfFirstAndOnlyRequest(reqs); expect(req.user.ext.eids).to.not.exist; }); + + it('sends dsa', () => { + const ortb2 = { + regs: { + ext: { + dsa: { + dsarequired: 2, + pubrender: 0, + datatopub: 1, + transparency: [ + { + domain: 'testdomain.com', + dsaparams: [1, 2, 3] + } + ] + } + } + } + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.dsa.dsarequired).to.eql(2); + expect(req.regs.ext.dsa.pubrender).to.eql(0); + expect(req.regs.ext.dsa.datatopub).to.eql(1); + expect(req.regs.ext.dsa.transparency[0].domain).to.eql('testdomain.com'); + expect(req.regs.ext.dsa.transparency[0].dsaparams).to.eql([1, 2, 3]); + }); + + it('sends no dsa', () => { + const ortb2 = { + regs: { + ext: {} + } + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.dsa).to.be.undefined; + }); }); describe('multiple requests', () => { @@ -1566,6 +1608,35 @@ describe('smaatoBidAdapterTest', () => { expect(bids[0].netRevenue).to.equal(false); }); + + it('uses dsa object sent from server', () => { + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); + const dsa = { + behalf: 'advertiser', + paid: 'advertiser', + adrender: 1, + transparency: [ + { + domain: 'dsp1domain.com', + dsaparams: [1, 2] + } + ] + }; + resp.body.seatbid[0].bid[0].ext.dsa = dsa; + + const bids = spec.interpretResponse(resp, buildBidRequest()); + + expect(bids[0].meta.dsa).to.deep.equal(dsa); + }); + + it('does not use dsa object if not sent from server', () => { + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); + resp.body.seatbid[0].bid[0].ext = {} + + const bids = spec.interpretResponse(resp, buildBidRequest()); + + expect(bids[0].meta.dsa).to.be.undefined; + }); }); describe('getUserSyncs', () => { From f7726ecf34c7a99fa0cfaed51a3b283a9f8f5f6f Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:17:38 -0700 Subject: [PATCH 0195/1097] Conversant Bid Adapter: handle paapi data in the response (#11663) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 5 +++-- test/spec/modules/conversantBidAdapter_spec.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 76ff2a9e2ad..6ef289af411 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -95,7 +95,7 @@ const converter = ortbConverter({ }, response(buildResponse, bidResponses, ortbResponse, context) { const response = buildResponse(bidResponses, ortbResponse, context); - return response.bids; + return response; }, overrides: { imp: { @@ -176,7 +176,8 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - return converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); + const ortbBids = converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); + return ortbBids; }, /** diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 9503a050092..39e66316ec1 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/conversantBidAdapter.js'; import * as utils from 'src/utils.js'; +import {deepSetValue} from 'src/utils.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {deepAccess} from 'src/utils'; // load modules that register ORTB processors @@ -465,7 +466,7 @@ describe('Conversant adapter tests', function() { before(() => { request = spec.buildRequests(bidRequests, {}); - response = spec.interpretResponse(bidResponses, request); + response = spec.interpretResponse(bidResponses, request).bids; }); it('Banner', function() { From ab238400680eeaa9bad7f33c9dcdfb7fbcac99ba Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 13 Jun 2024 11:33:42 -0700 Subject: [PATCH 0196/1097] Update jscpd.yml (#11793) --- .github/workflows/jscpd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 281a4e5e8ec..b581bcd500e 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -14,6 +14,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for all branches + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Node.js uses: actions/setup-node@v4 From 5126673921ff0bbc87f82cc1be86ca645f6f06d8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 13 Jun 2024 12:31:29 -0700 Subject: [PATCH 0197/1097] CI: improve duplicate checker comment (#11794) * improve duplicate checker * do not include exclusive end in linked block --- .github/workflows/jscpd.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index b581bcd500e..21e7aadf97c 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -1,6 +1,6 @@ name: Check for Duplicated Code -on: +on: pull_request_target: branches: - master @@ -100,11 +100,14 @@ jobs: const fs = require('fs'); const filteredReport = JSON.parse(fs.readFileSync('filtered-jscpd-report.json', 'utf8')); let comment = "Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:\n\n"; + function link(dup) { + return `https://github.com/${{ github.event.repository.full_name }}/blob/${{ github.event.pull_request.head.sha }}/${dup.name}#L${dup.start}-L${dup.end - 1}` + } filteredReport.forEach(duplication => { - const firstFile = duplication.firstFile.name; - const secondFile = duplication.secondFile.name; + const firstFile = duplication.firstFile; + const secondFile = duplication.secondFile; const lines = duplication.lines; - comment += `- \`${firstFile}\` has ${lines} duplicated lines with \`${secondFile}\`\n`; + comment += `- [\`${firstFile.name}\`](${link(firstFile)}) has ${lines} duplicated lines with [\`${secondFile.name}\`](${link(secondFile)})\n`; }); comment += "\nReducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. Keep up the great work! 🚀"; github.rest.issues.createComment({ From 19131b935da94c495624db10f9409f8fecce349d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 13 Jun 2024 13:39:14 -0700 Subject: [PATCH 0198/1097] Build system: increase heap size for test-coverage (#11792) * Build system: increase heap size for test-coverage * fix duplication * check out PR for duplicate checker * undo jscpd changes --- gulpfile.js | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 86c1b7fe509..a32a2d11ce6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -147,6 +147,17 @@ function makeVerbose(config = webpackConfig) { }); } +function prebidSource(webpackCfg) { + var externalModules = helpers.getArgModules(); + + const analyticsSources = helpers.getAnalyticsSources(); + const moduleSources = helpers.getModulePaths(externalModules); + + return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) + .pipe(helpers.nameModules(externalModules)) + .pipe(webpackStream(webpackCfg, webpack)); +} + function makeDevpackPkg(config = webpackConfig) { return function() { var cloned = _.cloneDeep(config); @@ -163,14 +174,7 @@ function makeDevpackPkg(config = webpackConfig) { .filter((use) => use.loader === 'babel-loader') .forEach((use) => use.options = Object.assign({}, use.options, babelConfig)); - var externalModules = helpers.getArgModules(); - - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); - - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) + return prebidSource(cloned) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } @@ -183,14 +187,7 @@ function makeWebpackPkg(config = webpackConfig) { } return function buildBundle() { - var externalModules = helpers.getArgModules(); - - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); - - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) + return prebidSource(cloned) .pipe(gulp.dest('build/dist')); } } @@ -413,7 +410,9 @@ function runKarma(options, done) { // the karma server appears to leak memory; starting it multiple times in a row will run out of heap // here we run it in a separate process to bypass the problem options = Object.assign({browsers: helpers.parseBrowserArgs(argv)}, options) - const child = fork('./karmaRunner.js'); + const child = fork('./karmaRunner.js', null, { + env: Object.assign({}, options.env, process.env) + }); child.on('exit', (exitCode) => { if (exitCode) { done(new Error('Karma tests failed with exit code ' + exitCode)); @@ -426,7 +425,15 @@ function runKarma(options, done) { // If --file "" is given, the task will only run tests in the specified file. function testCoverage(done) { - runKarma({coverage: true, browserstack: false, watch: false, file: argv.file}, done); + runKarma({ + coverage: true, + browserstack: false, + watch: false, + file: argv.file, + env: { + NODE_OPTIONS: '--max-old-space-size=8096' + } + }, done); } function coveralls() { // 2nd arg is a dependency: 'test' must be finished From 846686877e1c04f903674f1249457071fc3ddef9 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 13 Jun 2024 13:39:55 -0700 Subject: [PATCH 0199/1097] Magnite Analytics: Minor fix for default rule name (#11791) * minor fix for default rule name * delete comment - fix spelling --- modules/magniteAnalyticsAdapter.js | 7 +++-- .../modules/magniteAnalyticsAdapter_spec.js | 30 ++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 225607ad6d3..dd16baed66e 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -47,13 +47,13 @@ const pbsErrorMap = { 4: 'request-error', 999: 'generic-error' } -let cookieless; let browser; let pageReferer; let auctionIndex = 0; // count of auctions on page let accountId; let endpoint; +let cookieless; let prebidGlobal = getGlobal(); const { @@ -334,9 +334,9 @@ const getTopLevelDetails = () => { // Add DM wrapper details if (rubiConf.wrapperName) { - let rule; + let rule = rubiConf.rule_name; if (cookieless) { - rule = rubiConf.rule_name ? rubiConf.rule_name.concat('_cookieless') : 'cookieless'; + rule = rule ? rule.concat('_cookieless') : 'cookieless'; } payload.wrapper = { name: rubiConf.wrapperName, @@ -703,6 +703,7 @@ magniteAdapter.disableAnalytics = function () { magniteAdapter._oldEnable = enableMgniAnalytics; endpoint = undefined; accountId = undefined; + cookieless = undefined; auctionIndex = 0; resetConfs(); getHook('callPrebidCache').getHooks({ hook: callPrebidCacheHook }).remove(); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 731b4ab1682..df06a4e38f7 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -1758,16 +1758,32 @@ describe('magnite analytics adapter', function () { }); }); describe('cookieless', () => { - beforeEach(() => { - magniteAdapter.enableAnalytics({ - options: { - cookieles: undefined - } - }); - }) afterEach(() => { magniteAdapter.disableAnalytics(); }) + it('should not add cookieless and preserve original rule name', () => { + // Set the confs + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + wrapperFamily: 'general', + rule_name: 'desktop-magnite.com', + } + }); + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.match(/\/\/localhost:9999\/event/); + + let message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + name: '1001_general', + family: 'general', + rule: 'desktop-magnite.com', + }); + }) it('should add sufix _cookieless to the wrapper.rule if ortb2.device.ext.cdep start with "treatment" or "control_2"', () => { // Set the confs config.setConfig({ From 646f79d32fa1604fbfb1d89b42fb585db5cb65a7 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 13 Jun 2024 13:41:12 -0700 Subject: [PATCH 0200/1097] OrtbConverter: PBS Extension: Alias GVL ID (#11699) * Addresses #11628 * swap prios add test --- libraries/pbsExtensions/processors/aliases.js | 12 ++++ .../pbsExtensions/aliases_spec.js | 72 ++++++++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/libraries/pbsExtensions/processors/aliases.js b/libraries/pbsExtensions/processors/aliases.js index 3dcd2c4fd9b..42dea969e6b 100644 --- a/libraries/pbsExtensions/processors/aliases.js +++ b/libraries/pbsExtensions/processors/aliases.js @@ -1,4 +1,5 @@ import adapterManager from '../../../src/adapterManager.js'; +import {config} from '../../../src/config.js'; import {deepSetValue} from '../../../src/utils.js'; export function setRequestExtPrebidAliases(ortbRequest, bidderRequest, context, {am = adapterManager} = {}) { @@ -7,11 +8,22 @@ export function setRequestExtPrebidAliases(ortbRequest, bidderRequest, context, // adding alias only if alias source bidder exists and alias isn't configured to be standalone // pbs adapter if (!bidder || !bidder.getSpec().skipPbsAliasing) { + // set alias deepSetValue( ortbRequest, `ext.prebid.aliases.${bidderRequest.bidderCode}`, am.aliasRegistry[bidderRequest.bidderCode] ); + + // set alias gvlids if present also + const gvlId = config.getConfig(`gvlMapping.${bidderRequest.bidderCode}`) || bidder?.getSpec?.().gvlid; + if (gvlId) { + deepSetValue( + ortbRequest, + `ext.prebid.aliasgvlids.${bidderRequest.bidderCode}`, + gvlId + ); + } } } } diff --git a/test/spec/ortbConverter/pbsExtensions/aliases_spec.js b/test/spec/ortbConverter/pbsExtensions/aliases_spec.js index 712ceaa397c..6b5bf0371cc 100644 --- a/test/spec/ortbConverter/pbsExtensions/aliases_spec.js +++ b/test/spec/ortbConverter/pbsExtensions/aliases_spec.js @@ -1,4 +1,5 @@ import {setRequestExtPrebidAliases} from '../../../../libraries/pbsExtensions/processors/aliases.js'; +import {config} from 'src/config.js'; describe('PBS - ortb ext.prebid.aliases', () => { let aliasRegistry, bidderRegistry; @@ -17,7 +18,11 @@ describe('PBS - ortb ext.prebid.aliases', () => { beforeEach(() => { aliasRegistry = {}; bidderRegistry = {}; - }) + config.resetConfig(); + }); + afterEach(() => { + config.resetConfig(); + }); describe('has no effect if', () => { it('bidder is not an alias', () => { @@ -37,13 +42,16 @@ describe('PBS - ortb ext.prebid.aliases', () => { }); }); - it('sets ext.prebid.aliases.BIDDER', () => { + function initAlias(spec = {}) { aliasRegistry['alias'] = 'bidder'; bidderRegistry['alias'] = { getSpec() { - return {} + return spec } }; + } + it('sets ext.prebid.aliases.BIDDER', () => { + initAlias(); expect(setAliases({bidderCode: 'alias'})).to.eql({ ext: { prebid: { @@ -54,4 +62,62 @@ describe('PBS - ortb ext.prebid.aliases', () => { } }) }); + + it('sets ext.prebid.aliasgvlids.BIDDER if set on spec', () => { + initAlias({ gvlid: 24 }); + expect(setAliases({ bidderCode: 'alias' })).to.eql({ + ext: { + prebid: { + aliases: { + alias: 'bidder' + }, + aliasgvlids: { + alias: 24 + } + } + } + }) + }); + + it('sets ext.prebid.aliasgvlids.BIDDER if set on config', () => { + config.setConfig({ + gvlMapping: { + alias: 24 + } + }); + initAlias(); + expect(setAliases({ bidderCode: 'alias' })).to.eql({ + ext: { + prebid: { + aliases: { + alias: 'bidder' + }, + aliasgvlids: { + alias: 24 + } + } + } + }) + }); + + it('prefers ext.prebid.aliasgvlids.BIDDER set on config over spec', () => { + config.setConfig({ + gvlMapping: { + alias: 888 + } + }); + initAlias({ gvlid: 24 }); + expect(setAliases({ bidderCode: 'alias' })).to.eql({ + ext: { + prebid: { + aliases: { + alias: 'bidder' + }, + aliasgvlids: { + alias: 888 + } + } + } + }) + }); }) From e8be790bf285e5d122b5f42c7c44b21d0d0b94e8 Mon Sep 17 00:00:00 2001 From: maelmrgt <77864748+maelmrgt@users.noreply.github.com> Date: Thu, 13 Jun 2024 22:41:34 +0200 Subject: [PATCH 0201/1097] Greenbids Analytics Adapter : send params field to payload. (#11642) * feat(Adapter): send params field from bidder to payload * feat(analytics, test): remove unwanted tests * bump version * fix: successfully rebase test_file --- modules/greenbidsAnalyticsAdapter.js | 2 + .../modules/greenbidsAnalyticsAdapter_spec.js | 76 ++++++++++--------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index 86b306b0e7f..0aac98b453e 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -116,6 +116,7 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER bidder: bid.bidder, isTimeout: (status === BIDDER_STATUS.TIMEOUT), hasBid: (status === BIDDER_STATUS.BID), + params: (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {}, }; }, addBidResponseToMessage(message, bid, status) { @@ -133,6 +134,7 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER if (bidderIndex === -1) { message.adUnits[adUnitIndex].bidders.push(this.serializeBidResponse(bid, status)); } else { + message.adUnits[adUnitIndex].bidders[bidderIndex].params = (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {}; if (status === BIDDER_STATUS.BID) { message.adUnits[adUnitIndex].bidders[bidderIndex].hasBid = true; message.adUnits[adUnitIndex].bidders[bidderIndex].cpm = bid.cpm; diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index b4261806d24..d5362c9ed19 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -7,7 +7,7 @@ import { generateUUID } from '../../../src/utils.js'; import * as utils from 'src/utils.js'; -import {expect} from 'chai'; +import { expect } from 'chai'; import sinon from 'sinon'; const events = require('src/events'); @@ -65,17 +65,17 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { greenbidsAnalyticsAdapter.disableAnalytics(); }); - describe('#getCachedAuction()', function() { - const existing = {timeoutBids: [{}]}; + describe('#getCachedAuction()', function () { + const existing = { timeoutBids: [{}] }; greenbidsAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; - it('should get the existing cached object if it exists', function() { + it('should get the existing cached object if it exists', function () { const result = greenbidsAnalyticsAdapter.getCachedAuction('test_auction_id'); expect(result).to.equal(existing); }); - it('should create a new object and store it in the cache on cache miss', function() { + it('should create a new object and store it in the cache on cache miss', function () { const result = greenbidsAnalyticsAdapter.getCachedAuction('no_such_id'); expect(result).to.deep.include({ @@ -84,7 +84,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('when formatting JSON payload sent to backend', function() { + describe('when formatting JSON payload sent to backend', function () { const receivedBids = [ { auctionId: auctionId, @@ -106,7 +106,8 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { timeToRespond: 100, cpm: 0.08, currency: 'USD', - ad: 'fake ad2' + ad: 'fake ad2', + params: {'placement ID': 12784} }, { auctionId: auctionId, @@ -149,16 +150,16 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); } - describe('#createCommonMessage', function() { - it('should correctly serialize some common fields', function() { + describe('#createCommonMessage', function () { + it('should correctly serialize some common fields', function () { const message = greenbidsAnalyticsAdapter.createCommonMessage(auctionId); assertHavingRequiredMessageFields(message); }); }); - describe('#serializeBidResponse', function() { - it('should handle BID properly with timeout false and hasBid true', function() { + describe('#serializeBidResponse', function () { + it('should handle BID properly with timeout false and hasBid true', function () { const result = greenbidsAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); expect(result).to.include({ @@ -168,7 +169,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - it('should handle NO_BID properly and set hasBid to false', function() { + it('should handle NO_BID properly and set hasBid to false', function () { const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); expect(result).to.include({ @@ -178,7 +179,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - it('should handle TIMEOUT properly and set isTimeout to true', function() { + it('should handle TIMEOUT properly and set isTimeout to true', function () { const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); expect(result).to.include({ @@ -189,8 +190,8 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('#addBidResponseToMessage()', function() { - it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + describe('#addBidResponseToMessage()', function () { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function () { const message = { adUnits: [ { @@ -208,14 +209,15 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { bidder: 'greenbids', isTimeout: false, hasBid: false, + params: {} } ] }); }); }); - describe('#createBidMessage()', function() { - it('should format auction message sent to the backend', function() { + describe('#createBidMessage()', function () { + it('should format auction message sent to the backend', function () { const args = { auctionId: auctionId, timestamp: 1234567890, @@ -258,10 +260,9 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { noBids: noBids }; - sinon.stub(greenbidsAnalyticsAdapter, 'getCachedAuction').returns({timeoutBids: timeoutBids}); + sinon.stub(greenbidsAnalyticsAdapter, 'getCachedAuction').returns({ timeoutBids: timeoutBids }); const result = greenbidsAnalyticsAdapter.createBidMessage(args, timeoutBids); greenbidsAnalyticsAdapter.getCachedAuction.restore(); - assertHavingRequiredMessageFields(result); expect(result).to.deep.include({ auctionElapsed: 100, @@ -279,11 +280,13 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { bidder: 'greenbids', isTimeout: false, hasBid: true, + params: {} }, { bidder: 'greenbidsx', isTimeout: false, hasBid: true, + params: {'placement ID': 12784} } ] }, @@ -315,6 +318,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { hasBid: true, cpm: 0.09, currency: 'USD', + params: {} } ] } @@ -323,8 +327,8 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('#handleBidTimeout()', function() { - it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + describe('#handleBidTimeout()', function () { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function () { greenbidsAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; const args = [{ auctionId: 'test_timeout_auction_id', @@ -371,28 +375,28 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { events.getEvents.restore(); }); - it('should call handleAuctionInit as AUCTION_INIT trigger event', function() { + it('should call handleAuctionInit as AUCTION_INIT trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionInit'); - events.emit(constants.EVENTS.AUCTION_INIT, {auctionId: 'auctionId'}); + events.emit(constants.EVENTS.AUCTION_INIT, { auctionId: 'auctionId' }); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionInit, 1); greenbidsAnalyticsAdapter.handleAuctionInit.restore(); }); - it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleBidTimeout'); - events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: 'auctionId'}); + events.emit(constants.EVENTS.BID_TIMEOUT, { auctionId: 'auctionId' }); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleBidTimeout, 1); greenbidsAnalyticsAdapter.handleBidTimeout.restore(); }); - it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + it('should call handleAuctionEnd as AUCTION_END trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionEnd'); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: 'auctionId'}); + events.emit(constants.EVENTS.AUCTION_END, { auctionId: 'auctionId' }); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionEnd, 1); greenbidsAnalyticsAdapter.handleAuctionEnd.restore(); }); - it('should call handleBillable as BILLABLE_EVENT trigger event', function() { + it('should call handleBillable as BILLABLE_EVENT trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleBillable'); events.emit(constants.EVENTS.BILLABLE_EVENT, { type: 'auction', @@ -405,34 +409,34 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('isSampled', function() { - it('should return true for invalid sampling rates', function() { + describe('isSampled', function () { + it('should return true for invalid sampling rates', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', -1, 0.0)).to.be.true; expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 1.2, 0.0)).to.be.true; }); - it('should return determinist falsevalue for valid sampling rate given the predifined id and rate', function() { + it('should return determinist falsevalue for valid sampling rate given the predifined id and rate', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0)).to.be.false; }); - it('should return determinist true value for valid sampling rate given the predifined id and rate', function() { + it('should return determinist true value for valid sampling rate given the predifined id and rate', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999, 0.0)).to.be.true; }); - it('should return determinist true value for valid sampling rate given the predifined id and rate when we split to non exploration first', function() { + it('should return determinist true value for valid sampling rate given the predifined id and rate when we split to non exploration first', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999, 0.0, 1.0)).to.be.true; }); - it('should return determinist false value for valid sampling rate given the predifined id and rate when we split to non exploration first', function() { + it('should return determinist false value for valid sampling rate given the predifined id and rate when we split to non exploration first', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0, 1.0)).to.be.false; }); }); - describe('isSampled when analytic isforced', function() { + describe('isSampled when analytic isforced', function () { before(() => { sinon.stub(utils, 'getParameterByName').callsFake(par => par === 'greenbids_force_sampling' ? true : undefined); }); - it('should return determinist true when sampling flag activated', function() { + it('should return determinist true when sampling flag activated', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0)).to.be.true; }); after(() => { From 7ded2b735c207a54481c05bf70da0589d62d71d6 Mon Sep 17 00:00:00 2001 From: tudou <42998776+lhxx121@users.noreply.github.com> Date: Fri, 14 Jun 2024 04:42:32 +0800 Subject: [PATCH 0202/1097] Discovery Bid Adapter : fix window.top bug (#11511) * Discovery Bid Adapter: Fix window.top bug * Discovery Bid Adapter : fix window.top bug --------- Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 45 ++++++------ src/fpd/navigator.js | 29 ++++++++ test/spec/modules/discoveryBidAdapter_spec.js | 72 ++++++++++++++++++- 3 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 src/fpd/navigator.js diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 3ac905ef6b5..12f1d7ac1f2 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { getHLen, getHC, getDM } from '../src/fpd/navigator.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -455,11 +456,31 @@ function getParam(validBidRequests, bidderRequest) { const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const firstPartyData = bidderRequest.ortb2; const tpData = utils.deepAccess(bidderRequest, 'ortb2.user.data') || undefined; - const topWindow = window.top; const title = getPageTitle(); const desc = getPageDescription(); const keywords = getPageKeywords(); - + let ext = {}; + try { + ext = { + eids, + firstPartyData, + ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, + pmguid: getPmgUID(), + tpData, + utm: storage.getCookie(UTM_KEY), + page: { + title: title ? title.slice(0, 100) : undefined, + desc: desc ? desc.slice(0, 300) : undefined, + keywords: keywords ? keywords.slice(0, 100) : undefined, + hLen: getHLen(), + }, + device: { + nbw: getConnectionDownLink(), + hc: getHC(), + dm: getDM() + } + } + } catch (error) {} try { buildUTMTagData(page); } catch (error) { } @@ -480,25 +501,7 @@ function getParam(validBidRequests, bidderRequest) { ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, - ext: { - eids, - firstPartyData, - ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, - pmguid: getPmgUID(), - tpData, - utm: storage.getCookie(UTM_KEY), - page: { - title: title ? title.slice(0, 100) : undefined, - desc: desc ? desc.slice(0, 300) : undefined, - keywords: keywords ? keywords.slice(0, 100) : undefined, - hLen: topWindow.history?.length || undefined, - }, - device: { - nbw: getConnectionDownLink(), - hc: topWindow.navigator?.hardwareConcurrency || undefined, - dm: topWindow.navigator?.deviceMemory || undefined, - } - }, + ext, user: { buyeruid: storage.getCookie(COOKIE_KEY_MGUID) || undefined, id: sharedid || pubcid, diff --git a/src/fpd/navigator.js b/src/fpd/navigator.js new file mode 100644 index 00000000000..80025f88640 --- /dev/null +++ b/src/fpd/navigator.js @@ -0,0 +1,29 @@ +export function getHLen(win = window) { + let hLen; + try { + hLen = win.top.history.length; + } catch (error) { + hLen = undefined; + } + return hLen; +} + +export function getHC(win = window) { + let hc; + try { + hc = win.top.navigator.hardwareConcurrency; + } catch (error) { + hc = undefined; + } + return hc; +} + +export function getDM(win = window) { + let dm; + try { + dm = win.top.navigator.deviceMemory; + } catch (error) { + dm = undefined; + } + return dm; +}; diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 42cc6ff68eb..a3a252880c1 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -10,9 +10,10 @@ import { THIRD_PARTY_COOKIE_ORIGIN, COOKIE_KEY_MGUID, getCurrentTimeToUTCString, - buildUTMTagData + buildUTMTagData, } from 'modules/discoveryBidAdapter.js'; import * as utils from 'src/utils.js'; +import { getHLen, getHC, getDM } from '../../../src/fpd/navigator.js'; describe('discovery:BidAdapterTests', function () { let sandbox; @@ -633,5 +634,74 @@ describe('discovery Bid Adapter Tests', function () { expect(storage.setCookie.notCalled).to.be.true; }); }); + describe('getHLen', () => { + it('should return the correct length of history when accessible', () => { + const mockWindow = { + top: { + history: { + length: 3 + } + } + }; + const result = getHLen(mockWindow); + expect(result).to.equal(3); + }); + + it('should return undefined when accessing win.top.history.length throws an error', () => { + const mockWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getHLen(mockWindow); + expect(result).be.undefined; + }); + }); + + describe('getHC', () => { + it('should return the correct value of hardwareConcurrency when accessible', () => { + const mockWindow = { + top: { + navigator: { + hardwareConcurrency: 4 + } + } + }; + const result = getHC(mockWindow); + expect(result).to.equal(4); + }); + it('should return undefined when accessing win.top.navigator.hardwareConcurrency throws an error', () => { + const mockWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getHC(mockWindow); + expect(result).be.undefined; + }); + }); + + describe('getDM', () => { + it('should return the correct value of deviceMemory when accessible', () => { + const mockWindow = { + top: { + navigator: { + deviceMemory: 4 + } + } + }; + const result = getDM(mockWindow); + expect(result).to.equal(4); + }); + it('should return undefined when accessing win.top.navigator.deviceMemory throws an error', () => { + const mockWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getDM(mockWindow); + expect(result).be.undefined; + }); + }); }); }); From fd40156e68476adedf48b3477dec14297eac2ac1 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Thu, 13 Jun 2024 23:43:40 +0300 Subject: [PATCH 0203/1097] Yandex: add id system (#11196) * feat: add yandex id system * refactor: improve yandex user id adapter codestyle * tests: add unit tests for yandex user id module * fix: adjust eid key * refactor: remove explicit calls to cookie storage --- modules/.submodules.json | 3 +- modules/yandexIdSystem.js | 145 +++++++++++++++++++++++ test/spec/modules/yandexIdSystem_spec.js | 137 +++++++++++++++++++++ 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 modules/yandexIdSystem.js create mode 100644 test/spec/modules/yandexIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 9dfeaf910f8..224fdd6ab04 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -51,7 +51,8 @@ "euidIdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem" + "zeotapIdPlusIdSystem", + "yandexIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js new file mode 100644 index 00000000000..f24e33a8c44 --- /dev/null +++ b/modules/yandexIdSystem.js @@ -0,0 +1,145 @@ +/** + * The {@link module:modules/userId} module is required + * @module modules/yandexIdSystem + * @requires module:modules/userId + */ + +// @ts-check + +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { logError, logInfo } from '../src/utils.js'; + +// .com suffix is just a convention for naming the bidder eids +// See https://github.com/prebid/Prebid.js/pull/11196#discussion_r1591165139 +const BIDDER_EID_KEY = 'yandex.com'; +const YANDEX_ID_KEY = 'yandexId'; +export const BIDDER_CODE = 'yandex'; +export const YANDEX_USER_ID_KEY = '_ym_uid'; +export const YANDEX_COOKIE_STORAGE_TYPE = 'cookie'; +export const YANDEX_MIN_EXPIRE_DAYS = 30; + +export const PREBID_STORAGE = getStorageManager({ + moduleType: MODULE_TYPE_UID, + moduleName: BIDDER_CODE, + bidderCode: undefined +}); + +export const yandexIdSubmodule = { + /** + * Used to link submodule with config. + * @type {string} + */ + name: BIDDER_CODE, + /** + * Decodes the stored id value for passing to bid requests. + * @param {string} value + */ + decode(value) { + logInfo('decoded value yandexId', value); + + return { [YANDEX_ID_KEY]: value }; + }, + /** + * @param {import('./userId/index.js').SubmoduleConfig} submoduleConfig + * @param {unknown} [_consentData] + * @param {string} [storedId] Id that was saved by the core previously. + */ + getId(submoduleConfig, _consentData, storedId) { + if (checkConfigHasErrorsAndReport(submoduleConfig)) { + return; + } + + if (storedId) { + return { + id: storedId + }; + } + + return { + id: new YandexUidGenerator().generateUid(), + }; + }, + eids: { + [YANDEX_ID_KEY]: { + source: BIDDER_EID_KEY, + atype: 1, + }, + }, +}; + +/** + * @param {import('./userId/index.js').SubmoduleConfig} submoduleConfig + * @returns {boolean} `true` - when there are errors, `false` - otherwise. + */ +function checkConfigHasErrorsAndReport(submoduleConfig) { + let error = false; + + const READABLE_MODULE_NAME = 'Yandex ID module'; + + if (submoduleConfig.storage == null) { + logError(`Misconfigured ${READABLE_MODULE_NAME}. "storage" is required.`) + return true; + } + + if (submoduleConfig.storage?.name !== YANDEX_USER_ID_KEY) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is required to be "${YANDEX_USER_ID_KEY}"`); + error = true; + } + + if (submoduleConfig.storage?.type !== YANDEX_COOKIE_STORAGE_TYPE) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is required to be "${YANDEX_COOKIE_STORAGE_TYPE}"`); + error = true; + } + + if ((submoduleConfig.storage?.expires ?? 0) < YANDEX_MIN_EXPIRE_DAYS) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.expires" is required to be not less than "${YANDEX_MIN_EXPIRE_DAYS}"`); + error = true; + } + + return error; +} + +/** + * Yandex-specific generator for uid. Needs to be compatible with Yandex Metrica tag. + * @see https://github.com/yandex/metrica-tag/blob/main/src/utils/uid/uid.ts#L51 + */ +class YandexUidGenerator { + /** + * @param {number} min + * @param {number} max + */ + _getRandomInteger(min, max) { + const generateRandom = this._getRandomGenerator(); + + return Math.floor(generateRandom() * (max - min)) + min; + } + + _getCurrentSecTimestamp() { + return Math.round(Date.now() / 1000); + } + + generateUid() { + return [ + this._getCurrentSecTimestamp(), + this._getRandomInteger(1000000, 999999999), + ].join(''); + } + + _getRandomGenerator() { + if (crypto) { + return () => { + const buffer = new Uint32Array(1); + crypto.getRandomValues(buffer); + + return buffer[0] / 0xffffffff; + }; + } + + // Polyfill for environments that don't support Crypto API + return () => Math.random(); + } +} + +submodule('userId', yandexIdSubmodule); diff --git a/test/spec/modules/yandexIdSystem_spec.js b/test/spec/modules/yandexIdSystem_spec.js new file mode 100644 index 00000000000..d5f614dafb9 --- /dev/null +++ b/test/spec/modules/yandexIdSystem_spec.js @@ -0,0 +1,137 @@ +// @ts-check + +import { yandexIdSubmodule, PREBID_STORAGE, BIDDER_CODE, YANDEX_USER_ID_KEY, YANDEX_COOKIE_STORAGE_TYPE, YANDEX_MIN_EXPIRE_DAYS } from '../../../modules/yandexIdSystem.js'; +import {createSandbox} from 'sinon' +import * as utils from '../../../src/utils.js'; + +/** + * @typedef {import('sinon').SinonStub} SinonStub + * @typedef {import('sinon').SinonSpy} SinonSpy + * @typedef {import('sinon').SinonSandbox} SinonSandbox + */ + +const MIN_METRICA_ID_LEN = 17; + +/** @satisfies {import('../../../modules/userId/index.js').SubmoduleConfig} */ +const CORRECT_SUBMODULE_CONFIG = { + name: BIDDER_CODE, + storage: { + expires: YANDEX_MIN_EXPIRE_DAYS, + name: YANDEX_USER_ID_KEY, + type: YANDEX_COOKIE_STORAGE_TYPE, + refreshInSeconds: undefined, + }, + params: undefined, + value: undefined, +}; + +/** @type {import('../../../modules/userId/index.js').SubmoduleConfig[]} */ +const INCORRECT_SUBMODULE_CONFIGS = [ + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + expires: 0, + } + }, + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + type: 'html5' + } + }, + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + name: 'custom_key' + } + }, +]; + +describe('YandexId module', () => { + /** @type {SinonSandbox} */ + let sandbox; + /** @type {SinonStub} */ + let getCryptoRandomValuesStub; + /** @type {SinonStub} */ + let randomStub; + /** @type {SinonSpy} */ + let logErrorSpy; + + beforeEach(() => { + sandbox = createSandbox(); + logErrorSpy = sandbox.spy(utils, 'logError'); + + getCryptoRandomValuesStub = sandbox + .stub(window.crypto, 'getRandomValues') + .callsFake((bufferView) => { + if (bufferView != null) { + bufferView[0] = 10000; + } + + return null; + }); + randomStub = sandbox.stub(window.Math, 'random').returns(0.555); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId()', () => { + it('user id matches Yandex Metrica format', () => { + const generatedId = yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG)?.id; + + expect(isNaN(Number(generatedId))).to.be.false; + expect(generatedId).to.have.length.greaterThanOrEqual( + MIN_METRICA_ID_LEN + ); + }); + + it('uses stored id', () => { + const storedId = '11111111111111111'; + const generatedId = yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG, undefined, storedId)?.id; + + expect(generatedId).to.be.equal(storedId); + }) + + describe('config validation', () => { + INCORRECT_SUBMODULE_CONFIGS.forEach((config, i) => { + it(`invalid config #${i} fails`, () => { + const generatedId = yandexIdSubmodule.getId(config)?.id; + + expect(generatedId).to.be.undefined; + expect(logErrorSpy.called).to.be.true; + }) + }) + }) + + describe('crypto', () => { + it('uses Math.random when crypto is not available', () => { + sandbox.stub(window, 'crypto').value(undefined); + + yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG); + + expect(randomStub.calledOnce).to.be.true; + expect(getCryptoRandomValuesStub.called).to.be.false; + }); + + it('uses crypto when it is available', () => { + yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG); + + expect(randomStub.called).to.be.false; + expect(getCryptoRandomValuesStub.calledOnce).to.be.true; + }); + }); + }); + + describe('decode()', () => { + it('should not transform value', () => { + const value = 'test value'; + + expect(yandexIdSubmodule.decode(value).yandexId).to.equal(value); + }); + }); +}); From 0c0bc0ea186c35c44bfb816fc3c818ddc42250d1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 13 Jun 2024 21:46:44 +0000 Subject: [PATCH 0204/1097] Prebid 8.52.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36afa9c25ae..ad089b82826 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "8.52.0-pre", + "version": "8.52.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.52.0-pre", + "version": "8.52.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/package.json b/package.json index 34d1eb4a593..496925e4b1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.52.0-pre", + "version": "8.52.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From babd603318475dd6a021fd7420095197dfdd2646 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 13 Jun 2024 21:46:44 +0000 Subject: [PATCH 0205/1097] Increment version to 8.52.1-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ad089b82826..5819cb88217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "8.52.0", + "version": "8.52.1-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.52.0", + "version": "8.52.1-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/package.json b/package.json index 496925e4b1d..53211bd3e0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.52.0", + "version": "8.52.1-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c3a594d0718769b8f53b3e48abaa6c9eba4f4e35 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 13 Jun 2024 18:41:25 -0400 Subject: [PATCH 0206/1097] Prebid 9.0 - Breaking Changes (#11720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Brightcom adapter: remove adapters (#10925) * Update ooloAnalyticsAdapter.js (#11406) * Delete integrationExamples/gpt/creative_rendering.html (#11405) * Prebid 9.0: delete empty file (#11401) * Delete modules/enrichmentFpdModule.js * Update index.md * Update 33acrossAnalyticsAdapter.md * Update BTBidAdapter_spec.js * Sovrn Bid Adapter: Remove Sovrn Analytics Adapter (#11147) * sovrn analytics adapter: remove analytics adapter * delete md * Appnexus Bid Adapter: add support for ast_override_div debug feature (#11390) * ConsentManagementGpp module: throw error on some invalid sections (#11385) * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update modules/consentManagementGpp.js Co-authored-by: Demetrio Girardi --------- Co-authored-by: Demetrio Girardi * appnexusBidAdapter - video plcmt logic fix (#11403) * Remove 'transformBidParams' from relevantdigitalBidAdapter (#11412) * resolve conflicts/merge --------- Co-authored-by: Chris Prokopiak Co-authored-by: Dmitry Sinev Co-authored-by: Patrick McCann Co-authored-by: Demetrio Girardi Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> * removed adomik connectors (app sunsetted) (#11453) * Prebid 9: remove USP consent string from consent metadata (#11407) * Update consentHandler.js * Update consentManagementUsp_spec.js * Update consentManagementUsp_spec.js * OpenX Bid Adapter: remove transformBidParams (#11458) * OpenX Bid Adapter: remove transformBidParams * remove unneeded import --------- Co-authored-by: Chris Huie * PulsePoint bid adapter: Removing deprecated method (#11473) * Removing deprecated method * fixing linting issue * Core: export only public interface for NPM consumers (#11474) * Criteo bid adapter: Remove references to fast bid (#11435) Co-authored-by: v.raybaud * Prebid 9: Dead adapters (#11408) * Delete modules/spotxBidAdapter.md * Delete modules/spotxBidAdapter.js * Delete test/spec/modules/spotxBidAdapter_spec.js * Delete modules/britepoolIdSystem.md * Delete test/spec/modules/britepoolIdSystem_spec.js * Delete modules/britepoolIdSystem.js * Update .submodules.json * Update userId_spec.js * Update userId_spec.js * Update colossussspBidAdapter.js * Update ixBidAdapter.js * Update eids_spec.js * Update amxBidAdapter_spec.js * Update colossussspBidAdapter_spec.js * Update colossussspBidAdapter_spec.js * update horrid test * fix lint --------- Co-authored-by: Demetrio Girardi * Enable 1PID addressability by default (#11369) * Update adloader.js Spotx was removed in another commit * PB9: fix yukta analytics methods (#11475) * Update yuktamediaAnalyticsAdapter.js * Update yuktamediaAnalyticsAdapter.js * Update yuktamediaAnalyticsAdapter.js * Prebid 9: Deprecate native sendTargetingKeys/types, validate asset IDs (#11481) * Validate native ortb.asset.id * deprecate native sendTargetingKeys / types * undo package-lock changes * Prebid 9: change auctionDelay default to 500 (#11498) * pirIdSystem: Module delete (#11518) * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * OpenX Bid Adapter: remove use of deprecated video.placement (#11496) * piridsystem delete --------- Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: Wojciech Biały Co-authored-by: Brian Schmidt * pb9: Organize adloader.js (#11489) * Update adloader.js * Update adloader.js * Update adloader_spec.js * Update adloader.js * Rename yahoosspBidAdapter to yahooAdsBidAdapter for Prebid 9 (#11525) * change * removing global filtet * reverting page * message * adapter change * remove space * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * comment * update maintainer email address * rename to yahooads --------- Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: dsravana * Prebid 9: rename GDPR modules (#11521) * ✨ Converted Criteo bid adapter to oRTB (#11486) Co-authored-by: f.caspar * fix consentManagement import * Prebid 9: add deprecation warning for pbadslot (#11537) * rename bizzclick to blasto (#11512) * Prebid 9: remove support for GPP 1.0 (#11461) * Prebid 9: Removing innerText & adding eslint rule (#11531) * 11233 Removing innerText & adding eslint rule * tests fix --------- Co-authored-by: Marcin Komorski * Prebid 9: stop supporting top level app/site/device FPD config (#11522) * Prebid 9: stop using transformBidParams (#11499) * Prebid 9: gptPreAuction: use GPID by default (#11551) * Prebid 9.0: floors enforcement (#11586) * Delete modules/ebdrBidAdapter.md * Delete modules/ebdrBidAdapter.js * Delete test/spec/modules/ebdrBidAdapter_spec.js * Update iqmBidAdapter.js * Update lkqdBidAdapter.js * Update madvertiseBidAdapter.js * Delete modules/mytargetBidAdapter.md * Delete test/spec/modules/mytargetBidAdapter_spec.js * Update quantcastBidAdapter.js * Update truereachBidAdapter.js * Update vdoaiBidAdapter.js * Update truereachBidAdapter_spec.js * Prebid 9: Clean up remaining transformBidParams dead code (#11585) * Update adtelligentBidAdapter.js * Update connectadBidAdapter.js * Update trafficgateBidAdapter.js * Update trafficgateBidAdapter.js * Prebid 9.0: del parrable (#11589) * Delete test/spec/modules/parrableIdSystem_spec.js * Delete modules/parrableIdSystem.js * Update userId.md * Update eids.md * Update microadBidAdapter.js * Update yahooAdsBidAdapter.js * Update newspassidBidAdapter.js * Update illuminBidAdapter.js * Update .submodules.json * Update shinezRtbBidAdapter.js * Update ozoneBidAdapter.js * Update vidazooBidAdapter.js * Update vidazooBidAdapter_spec.js * Update shinezRtbBidAdapter_spec.js * Update microadBidAdapter_spec.js * Update illuminBidAdapter_spec.js * Update eids_spec.js * Prebid 9: TCF: use publisher consent for vendorless modules (#11536) * Prebid 9: Drop bbw (#11591) * Delete test/spec/modules/bluebillywigBidAdapter_spec.js * Delete modules/bluebillywigBidAdapter.js * Delete modules/bluebillywigBidAdapter.md * SirData RTD provider: use textContent instead of innerText * Prebid 9: rename utiqSystem to utiqIdSystem (#11593) * Revert "Revert "Utiq ID submodule: Update submodule name and parameters (#10587)" (#10606)" This reverts commit 2b3426d6cc135bc9f4c903b2270234fcfaf649f1. * Update .submodules.json --------- Co-authored-by: Patrick McCann * ImproveDigital Bid Adapter: Breaking Parameter Changes for PB9 (#11067) * Improve Digital PG flag * Remove parsing of addtlConsent * Fix test * Change default of "improvedigital.usePrebidSizes" config value to true * Make "params.publisherId" mandatory and remove "params.placementKey" * Updated docs * Fixes based on feedback * Send publisherId along placementId --------- Co-authored-by: Jozef Bartek Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> * Prebid 9.0: fix consent getconfigs (#11592) * Update apstreamBidAdapter.js * Update madvertiseBidAdapter.js * Update madvertiseBidAdapter.js * SirData RTD provider: use textContent instead of innerText (#11595) Co-authored-by: Demetrio Girardi * Update madvertiseBidAdapter_spec.js --------- Co-authored-by: Demetrio Girardi * Prebid 9.0: no harvest eids (#11588) * Update hadronRtdProvider.js * Update hadronRtdProvider_spec.js * Yahoo Bid Adapter: adding plcmt param support (#11569) * change * removing global filtet * reverting page * message * adapter change * remove space * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * comment * update maintainer email address * rename to yahooads * plcmt --------- Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: dsravana * 11071 Prevent Events system import in bidders (#11548) Co-authored-by: Marcin Komorski * Openweb bid adapter: Make placementId parameter mandatory (#11605) * Update PR_REVIEW.md (#11606) * 9.0 del idward (#11611) * Delete test/spec/modules/idWardRtdProvider_spec.js * Delete modules/idWardRtdProvider.js * Delete modules/idWardRtdProvider.md * Delete integrationExamples/gpt/idward_segments_example.html * Prebid 9: update PBS tmax default (#11609) * Prebid 9.0: del mmp (#11621) * Delete modules/minutemediaplusBidAdapter.js * Delete modules/minutemediaplusBidAdapter.md * Delete test/spec/modules/minutemediaplusBidAdapter_spec.js * 9.0: undocumented analytics adapters (#11590) * Delete test/spec/modules/eplanningAnalyticsAdapter_spec.js * Delete modules/eplanningAnalyticsAdapter.js * Delete modules/marsmediaAnalyticsAdapter.js * Delete modules/sigmoidAnalyticsAdapter.js * Delete test/spec/modules/sigmoidAnalyticsAdapter_spec.js * Delete modules/sonobiAnalyticsAdapter.js * Delete test/spec/modules/sonobiAnalyticsAdapter_spec.js * Delete test/spec/modules/staqAnalyticsAdapter_spec.js * Delete modules/staqAnalyticsAdapter.js * Delete test/spec/modules/terceptAnalyticsAdapter_spec.js * Delete modules/terceptAnalyticsAdapter.js * Add files via upload * Add files via upload * Update adloader.js * 9.0 : Change Prebidmanager Analytics to AsteriobidPbm (#11624) * Update AsteriobidPbm name * fix typo * Update AsteriobidPbmAnalyticsAdapter.js * Update AsteriobidPbmAnalyticsAdapter.js * Update AsteriobidPbmAnalyticsAdapter.js * Update AsteriobidPbmAnalyticsAdapter.md * Update AsteriobidPbmAnalyticsAdapter.js * appnexusBidAdapter 9.0 - remove transform bid params and create anPspParamsConverter module (#11583) * appnexusBidAdapter 9.0 - move transformBidParams logic to module * fix lint errors in test file * rework logic that reads bidderRequests * Update index.js (#11625) * Update adloader.js * Update package.json to reflect 9.0 (#11645) Was testing this branch and confused why the version didn't match... * Adagio Bid Adapter: 9.0: Remove external script + related code (#11626) * AdagioBidAdapter: 9.0: remove external script + related code * AdagioBidAdapter: 9.0: change "adagio" section in adloader.js * Prebid 9: Move bidders iframes urls to config (#11579) * Prebid 9: Move bidders iframes urls to config * removing default config * Update config.js --------- Co-authored-by: Marcin Komorski Co-authored-by: Patrick McCann * 9.0: raise browserstack version (#11653) * Update browsers.json * Update browsers.json * Update browsers.json * Update browsers.json * Change to Monterey * fix version --------- Co-authored-by: Chris Huie * update ras adapter and rename it to ringieraxelspringer (#11657) * 9.0: bidders cannot import from ad loader (#11655) * Update .eslintrc.js * Update improvedigitalBidAdapter.js * Update showheroes-bsBidAdapter.js * Update adloader.js * Update adloader.js * Update showheroes-bsBidAdapter.js * Update improvedigitalBidAdapter.js * Update showheroes-bsBidAdapter.js * Update improvedigitalBidAdapter.js * Update showheroes-bsBidAdapter.js * Prebid 9: extract DFP adpod logic into a separate dfpAdpod module (#11550) * 9.0: Adapter gpids (#11660) * Update colossussspBidAdapter.js * Update goldbachBidAdapter.js * Update mediafuseBidAdapter.js * Update pixfutureBidAdapter.js * Update richaudienceBidAdapter.js * Update winrBidAdapter.js * Update bliinkBidAdapter.js (#11664) * Update PR_REVIEW.md (#11669) * Prebid 9: re-whitelist 'oustream' for loadExternalScript (#11671) * re-whitelist 'outstream' for loadExternalScript * Update adloader.js * Delete modules/richaudienceBidAdapter.js (#11670) * Delete modules/richaudienceBidAdapter.js * Delete test/spec/modules/richaudienceBidAdapter_spec.js * 33x supplemental id for addressability (#11614) * Prebid 9: Raise minimum node version from 12 (#11528) * 10999 Raise minimum node version from 12 to 16 * Attempt bump to node 20 in circleci * do not fallback to previous cache * Force cache miss * actually force cache miss --------- Co-authored-by: Marcin Komorski Co-authored-by: Demetrio Girardi * Marsmedia: remove analytics adapter (#11686) * Change publisherId to zoneId Add gdpr Add supply chain Add video media type * Remove comments * Fix unit test coverage * fix request id bug add vastXml to video response * Remove bid response default sizes * Change endpoint url * Add unit test for vastXml * Change end point * Remove trailing-space * Add onBidWon function * New adapter - videofy * Marsmedia & Videofy - Add onTimeout onSetTargeting * Create sendbeacon function * - add viewability * remove unnecessary utils.getWindowTop() * Remove bidderCode from response for alias use * Remove unuse that var * MarsMeida: Remove analytics adapter * JW Player RTD Adapter: 9.0 migration (#11692) * enrich content id when empty * updates tests * updates documentation * updates examples * 9.0: Update gumgumBidAdapter.js (#11693) * 9.0: Update gumgumBidAdapter.js enforcing standard request object locations must also be supported when a param is offered. * Update gumgumBidAdapter_spec.js * Update gumgumBidAdapter_spec.js * Prebid 9: rename fledge to paapi (#11695) * rename bidderRequest.fledgeEnabled to bidderRequest.paapi.enabled * rename fledgeAuctionConfigs to paapi * remove support for bidder fledgeAuctionConfigs * remove support for fledgeForGpt config * rename fledgeForGpt -> paapiForGpt * update refs to fledgeForGpt in comments and docs * Remove special bidder configuration for fledge * flip paapiForGpt autoconfig default * rename integration examples * update paapiForGpt.md * 9.0: remove rsa validate * 9.0: Update package.json (#11697) * Criteo Bid Adapter: Update bidder DNS (#11700) Co-authored-by: Patrick McCann * Update sovrnBidAdapter.js: support plcmt (#11704) * 9.0: Update quantcastBidAdapter.js for plcmt (#11707) * 9.0: Update quantcastBidAdapter.js for plcmt * Update quantcastBidAdapter_spec.js * 9.0: Update acuityadsBidAdapter.js to add plcmt (#11710) * 33Across ID System: Fix merge conflicts between master & 9.0 (#11715) * Vis X Bid Adapter : retrieve and send seller defined audiences (#11638) * AF-3647 added device object to request in visxBidAdapter * AF-3647 added user, regs, site and user agent data to request * AF-3647 removed userAgentClientHints because of it is gotten from device object * AF-3647 updated test scenarios with sda signals * AF-3647 fixed to getting ortb2 and spec file * AF-3647 reverted to get user data from cookie/local storage * 33Across User ID Module : support for the recently introduced "multiple storage types" feature (#11563) * Refactoring - break functions that are handling multiple storage types. * user id: introduce the concept of enabled storage types * Apply domain override to 33across ID * First party ID - Support for multiple storage types * 33Across User ID: Recommend both storage types * refactor the way enabled storage types are populated * Default to fetch keepalive (#11682) Co-authored-by: Marcin Komorski * Dailymotion Bid Adapter: add support for user syncs & new fields (#11603) * Dailymotion Bid Adapter: add support for playbackmethod & plcmt * Dailymotion Bid Adapter: add support for user syncs * Dailymotion Bid Adapter: Add support for ortb2 device, and contextual informations * Dailymotion Bid Adapter: Fix tests * Dailymotion Bid Adapter: add support for content.url & device.ext.atts * Dailymotion Bid Adapter: change markdown header levels * Dailymotion Bid Adapter: collect prebid.version --------- Co-authored-by: Kevin Siow * ZetaGlobalSsp Analytics Adapter : provide device object (#11607) * ZetaGlobalSpp Analytics adapter: provide device object * ZetaGlobalSpp Analytics adapter: provide ua in adRenderSucceeded event * provide domain and page in timeout event --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko * Core Utils: fix jsdoc warnings (#11694) * Update utils.js * Update perfMetrics.js * Update ttlCollection.js * Update adpod.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagement.js * Update consentManagementUsp.js * Update currency.js * Update dfpAdServerVideo.js * Update instreamTracking.js * Update s2sTesting.js * Update sizeMapping.js * Update topicsFpdModule.js * Update uid2IdSystem.js * Update consentManagementUsp.js * Update sizeMapping.js * datablocksBidAdapter.js: fix syncs issue (#11684) fixes https://github.com/prebid/Prebid.js/issues/11319 * ColossusSSP Bid Adapter : replace gpid for pbadslot (#11701) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data * gpp support * accepting eids from request * fixing lint errors * resolving a conflict * fixing a failed test case related to tid * fixing karma version for conflict resolution * reverting package json files to original version * switching placement to plcmt * replacing gpid for pbadslot --------- Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally * Bidmatic Bid Adapter: Initial Release (#11690) * Bidmatic Initial commit * Use getFloor from price module --------- Co-authored-by: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Co-authored-by: mkomorski Co-authored-by: Marcin Komorski Co-authored-by: Kevin Siow Co-authored-by: Kevin Siow Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Patrick McCann Co-authored-by: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally Co-authored-by: Gena * Revert "9.0: Update gumgumBidAdapter.js (#11693)" (#11717) This reverts commit caa99798c708ac91062380db436f40cb2b614388. * 9.0: 10452 enforce, need plcmt to get placement (#11718) * Update adbookpspBidAdapter.js * Update admanBidAdapter.js * Update adtrueBidAdapter.js * Update appushBidAdapter.js * Update axisBidAdapter.js * Update beyondmediaBidAdapter.js * Update boldwinBidAdapter.js * Update dspxBidAdapter.js * Update edge226BidAdapter.js * Update emtvBidAdapter.js * Update globalsunBidAdapter.js * Update gothamadsBidAdapter.js * Update precisoBidAdapter.js * Update smartyadsBidAdapter.js * Update gothamadsBidAdapter.js * Update precisoBidAdapter.js * Update gothamadsBidAdapter.js * Update lkqdBidAdapter.js * Delete modules/iqmBidAdapter.js * Delete test/spec/modules/iqmBidAdapter_spec.js * Update smartyadsBidAdapter.js * Update kiviadsBidAdapter.js * Update krushmediaBidAdapter.js * Update kueezBidAdapter.js * Update loganBidAdapter.js * Update loyalBidAdapter.js * Update mediakeysBidAdapter.js * Update mgidXBidAdapter.js * Update mobfoxpbBidAdapter.js * Update operaadsBidAdapter.js * Update pgamsspBidAdapter.js * Update pstudioBidAdapter.js * Update pubgeniusBidAdapter.js * Update pubCircleBidAdapter.js * Update shinezBidAdapter.js * Update videobyteBidAdapter.js * Update visiblemeasuresBidAdapter.js * Update waardexBidAdapter.js * Update 33acrossBidAdapter.js * Update gothamadsBidAdapter.js * Update gothamadsBidAdapter.js * Update precisoBidAdapter.js * Update smartyadsBidAdapter.js * Update smartxBidAdapter.js * Update ozoneBidAdapter.js * Update smartxBidAdapter.js * Update 33acrossBidAdapter_spec.js * Update admanBidAdapter_spec.js * Update krushmediaBidAdapter_spec.js * Update loganBidAdapter_spec.js * Update mobfoxpbBidAdapter_spec.js * Update precisoBidAdapter.js * Update smartyadsBidAdapter.js * Update e_volutionBidAdapter.js * Update e_volutionBidAdapter.js * Update pubmaticBidAdapter.js * Update iqzoneBidAdapter.js * Update qtBidAdapter.js * Update lunamediahbBidAdapter.js * Update krushmediaBidAdapter.js * Update pubgeniusBidAdapter_spec.js * Update smartxBidAdapter_spec.js * Update apacdexBidAdapter_spec.js * Update apacdexBidAdapter_spec.js * ORTB2: don't rely on context to infer video.placement (#11719) * Update videojsVideoProvider.js * Update ortb.js * Update ortb.js * Update video.js * Update videojsVideoProvider.js * Update jwplayerVideoProvider.js * Update ixBidAdapter.js * Update videojsVideoProvider.js * Update ixBidAdapter.js * Update ortb.js * Update videojsVideoProvider_spec.js * Update video.js * Update jwplayerVideoProvider.js * Update videojsVideoProvider.js * Update videojsVideoProvider_spec.js * Update videojsVideoProvider.js * Update videojsVideoProvider_spec.js * Update video_spec.js * Update ixBidAdapter.js * Update prebidServerBidAdapter_spec.js * Update viantOrtbBidAdapter_spec.js * remove adbookpsp * IX Bid Adapter: Remove Roundel Alias (#11732) * pass user.geo and device.geo to payload (#11723) * chore: removed roundel alias [PB-3025] --------- Co-authored-by: aivanov-zeta <144369215+aivanov-zeta@users.noreply.github.com> Co-authored-by: Love Sharma * Update package-lock.json * Prebid 9: Update babel core (#11729) * Prebid 9: Update babel core * Update bedigitechBidAdapter_spec.js * Update bedigitechBidAdapter_spec.js * Update bedigitechBidAdapter_spec.js * Update bedigitechBidAdapter.js * Update bedigitechBidAdapter_spec.js * Delete test/spec/modules/iqmBidAdapter_spec.js * 9.0 upstream (#11735) * ampliffyBidAdapter.js: remove linter exceptions (#11666) * GPID is set first from GPID, then from pbadslot as a fallback. (#11542) * chore: pass through paapi imp extension [PB-2799] (#11639) Co-authored-by: Chris Corbo * ViouslyBidAdapter.js: replace find (#11667) * New PAAPI module: topLevelPaapi (#11379) * refactor size logic * fill in requestedSize on auction configs * topLevelPaapi * WIP * getPAAPIBids * include size in paapi bids * update TL example * slightly nicer example * slight improvement * refactor * add PAAPI_ERROR event * use optable in TL example * allow async bid retrieval on render: safeframes * allow async bid retrieval on render: renderAd * do not force string on requestedSize * support rendering of paapi bids * include auctionConfig in events * fix tests * overrideWinner; autorun by default * autorun & overrideWinner * fix tests * emit BID_WON for paapi bids * add no ad server example * improve bid override logic * fix lint * e-Volution Bid Adapter : update bid request validation and added video placement keys (#11561) * updates for Prebid v5 * add id5id * update tests * add gvlid * updated adapter * removed redundant endpointId * Opsco Bid Adapter : update process for retrieving placementId from bid request params (#11604) * Opsco bid adapter init commit * Opsco bid adapter banner implementation * Changing test parameter * Changing endpoint * Retrieving placement Id from bid request params --------- Co-authored-by: adtech-sky * StroeerCore Bid Adapter: remove 'ssl' flag from the request payload (#11678) * AdMatic Bid Adapter: add monetixads alias (#11679) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * Various places: jsdoc fixes (#11672) * Update adapterManager.js * Update consentHandler.js * Update prebid.js * Update targeting.js * Update refererDetection.js * Update video.js * Update videoCache.js * Update params.js * Update rules.js * Update paapi.js * Update ortb.js * Update composer.js * Update coreVideo.js * Update adapterManager.js * Update consentHandler.js * Update ttlCollection.js * Update enrichment.js * Update gamAdServerSubmodule.js * Smarthub replace placement (#11629) * update adapter SmartHub: add aliases * SmartHub adapter: replace placement * add getter video.plcmt and update test * revert placement to plcmt --------- Co-authored-by: Victor * Marsmedia: remove analytics adapter (#11686) * Change publisherId to zoneId Add gdpr Add supply chain Add video media type * Remove comments * Fix unit test coverage * fix request id bug add vastXml to video response * Remove bid response default sizes * Change endpoint url * Add unit test for vastXml * Change end point * Remove trailing-space * Add onBidWon function * New adapter - videofy * Marsmedia & Videofy - Add onTimeout onSetTargeting * Create sendbeacon function * - add viewability * remove unnecessary utils.getWindowTop() * Remove bidderCode from response for alias use * Remove unuse that var * MarsMeida: Remove analytics adapter * Vis X Bid Adapter : retrieve and send seller defined audiences (#11638) * AF-3647 added device object to request in visxBidAdapter * AF-3647 added user, regs, site and user agent data to request * AF-3647 removed userAgentClientHints because of it is gotten from device object * AF-3647 updated test scenarios with sda signals * AF-3647 fixed to getting ortb2 and spec file * AF-3647 reverted to get user data from cookie/local storage * 33Across User ID Module : support for the recently introduced "multiple storage types" feature (#11563) * Refactoring - break functions that are handling multiple storage types. * user id: introduce the concept of enabled storage types * Apply domain override to 33across ID * First party ID - Support for multiple storage types * 33Across User ID: Recommend both storage types * refactor the way enabled storage types are populated * Default to fetch keepalive (#11682) Co-authored-by: Marcin Komorski * Dailymotion Bid Adapter: add support for user syncs & new fields (#11603) * Dailymotion Bid Adapter: add support for playbackmethod & plcmt * Dailymotion Bid Adapter: add support for user syncs * Dailymotion Bid Adapter: Add support for ortb2 device, and contextual informations * Dailymotion Bid Adapter: Fix tests * Dailymotion Bid Adapter: add support for content.url & device.ext.atts * Dailymotion Bid Adapter: change markdown header levels * Dailymotion Bid Adapter: collect prebid.version --------- Co-authored-by: Kevin Siow * ZetaGlobalSsp Analytics Adapter : provide device object (#11607) * ZetaGlobalSpp Analytics adapter: provide device object * ZetaGlobalSpp Analytics adapter: provide ua in adRenderSucceeded event * provide domain and page in timeout event --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko * JW Player RTD Adapter: 9.0 migration (#11692) * enrich content id when empty * updates tests * updates documentation * updates examples * 9.0: Update gumgumBidAdapter.js (#11693) * 9.0: Update gumgumBidAdapter.js enforcing standard request object locations must also be supported when a param is offered. * Update gumgumBidAdapter_spec.js * Update gumgumBidAdapter_spec.js * Core Utils: fix jsdoc warnings (#11694) * Update utils.js * Update perfMetrics.js * Update ttlCollection.js * Update adpod.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagement.js * Update consentManagementUsp.js * Update currency.js * Update dfpAdServerVideo.js * Update instreamTracking.js * Update s2sTesting.js * Update sizeMapping.js * Update topicsFpdModule.js * Update uid2IdSystem.js * Update consentManagementUsp.js * Update sizeMapping.js * Prebid 9: rename fledge to paapi (#11695) * rename bidderRequest.fledgeEnabled to bidderRequest.paapi.enabled * rename fledgeAuctionConfigs to paapi * remove support for bidder fledgeAuctionConfigs * remove support for fledgeForGpt config * rename fledgeForGpt -> paapiForGpt * update refs to fledgeForGpt in comments and docs * Remove special bidder configuration for fledge * flip paapiForGpt autoconfig default * rename integration examples * update paapiForGpt.md * 9.0: remove rsa validate * 9.0: Update package.json (#11697) * Criteo Bid Adapter: Update bidder DNS (#11700) Co-authored-by: Patrick McCann * datablocksBidAdapter.js: fix syncs issue (#11684) fixes https://github.com/prebid/Prebid.js/issues/11319 * ColossusSSP Bid Adapter : replace gpid for pbadslot (#11701) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data * gpp support * accepting eids from request * fixing lint errors * resolving a conflict * fixing a failed test case related to tid * fixing karma version for conflict resolution * reverting package json files to original version * switching placement to plcmt * replacing gpid for pbadslot --------- Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally * Bidmatic Bid Adapter: Initial Release (#11690) * Bidmatic Initial commit * Use getFloor from price module * Update sovrnBidAdapter.js: support plcmt (#11704) * 9.0: Update quantcastBidAdapter.js for plcmt (#11707) * 9.0: Update quantcastBidAdapter.js for plcmt * Update quantcastBidAdapter_spec.js * 9.0: Update acuityadsBidAdapter.js to add plcmt (#11710) * 33Across ID System: Fix merge conflicts between master & 9.0 (#11715) * Vis X Bid Adapter : retrieve and send seller defined audiences (#11638) * AF-3647 added device object to request in visxBidAdapter * AF-3647 added user, regs, site and user agent data to request * AF-3647 removed userAgentClientHints because of it is gotten from device object * AF-3647 updated test scenarios with sda signals * AF-3647 fixed to getting ortb2 and spec file * AF-3647 reverted to get user data from cookie/local storage * 33Across User ID Module : support for the recently introduced "multiple storage types" feature (#11563) * Refactoring - break functions that are handling multiple storage types. * user id: introduce the concept of enabled storage types * Apply domain override to 33across ID * First party ID - Support for multiple storage types * 33Across User ID: Recommend both storage types * refactor the way enabled storage types are populated * Default to fetch keepalive (#11682) Co-authored-by: Marcin Komorski * Dailymotion Bid Adapter: add support for user syncs & new fields (#11603) * Dailymotion Bid Adapter: add support for playbackmethod & plcmt * Dailymotion Bid Adapter: add support for user syncs * Dailymotion Bid Adapter: Add support for ortb2 device, and contextual informations * Dailymotion Bid Adapter: Fix tests * Dailymotion Bid Adapter: add support for content.url & device.ext.atts * Dailymotion Bid Adapter: change markdown header levels * Dailymotion Bid Adapter: collect prebid.version --------- Co-authored-by: Kevin Siow * ZetaGlobalSsp Analytics Adapter : provide device object (#11607) * ZetaGlobalSpp Analytics adapter: provide device object * ZetaGlobalSpp Analytics adapter: provide ua in adRenderSucceeded event * provide domain and page in timeout event --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko * Core Utils: fix jsdoc warnings (#11694) * Update utils.js * Update perfMetrics.js * Update ttlCollection.js * Update adpod.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagement.js * Update consentManagementUsp.js * Update currency.js * Update dfpAdServerVideo.js * Update instreamTracking.js * Update s2sTesting.js * Update sizeMapping.js * Update topicsFpdModule.js * Update uid2IdSystem.js * Update consentManagementUsp.js * Update sizeMapping.js * datablocksBidAdapter.js: fix syncs issue (#11684) fixes https://github.com/prebid/Prebid.js/issues/11319 * ColossusSSP Bid Adapter : replace gpid for pbadslot (#11701) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data * gpp support * accepting eids from request * fixing lint errors * resolving a conflict * fixing a failed test case related to tid * fixing karma version for conflict resolution * reverting package json files to original version * switching placement to plcmt * replacing gpid for pbadslot --------- Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally * Bidmatic Bid Adapter: Initial Release (#11690) * Bidmatic Initial commit * Use getFloor from price module --------- Co-authored-by: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Co-authored-by: mkomorski Co-authored-by: Marcin Komorski Co-authored-by: Kevin Siow Co-authored-by: Kevin Siow Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Patrick McCann Co-authored-by: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally Co-authored-by: Gena * Support for cids (#11713) * Revert "9.0: Update gumgumBidAdapter.js (#11693)" (#11717) This reverts commit caa99798c708ac91062380db436f40cb2b614388. * Add plmct (#11706) * 9.0: 10452 enforce, need plcmt to get placement (#11718) * Update adbookpspBidAdapter.js * Update admanBidAdapter.js * Update adtrueBidAdapter.js * Update appushBidAdapter.js * Update axisBidAdapter.js * Update beyondmediaBidAdapter.js * Update boldwinBidAdapter.js * Update dspxBidAdapter.js * Update edge226BidAdapter.js * Update emtvBidAdapter.js * Update globalsunBidAdapter.js * Update gothamadsBidAdapter.js * Update precisoBidAdapter.js * Update smartyadsBidAdapter.js * Update gothamadsBidAdapter.js * Update precisoBidAdapter.js * Update gothamadsBidAdapter.js * Update lkqdBidAdapter.js * Delete modules/iqmBidAdapter.js * Delete test/spec/modules/iqmBidAdapter_spec.js * Update smartyadsBidAdapter.js * Update kiviadsBidAdapter.js * Update krushmediaBidAdapter.js * Update kueezBidAdapter.js * Update loganBidAdapter.js * Update loyalBidAdapter.js * Update mediakeysBidAdapter.js * Update mgidXBidAdapter.js * Update mobfoxpbBidAdapter.js * Update operaadsBidAdapter.js * Update pgamsspBidAdapter.js * Update pstudioBidAdapter.js * Update pubgeniusBidAdapter.js * Update pubCircleBidAdapter.js * Update shinezBidAdapter.js * Update videobyteBidAdapter.js * Update visiblemeasuresBidAdapter.js * Update waardexBidAdapter.js * Update 33acrossBidAdapter.js * Update gothamadsBidAdapter.js * Update gothamadsBidAdapter.js * Update precisoBidAdapter.js * Update smartyadsBidAdapter.js * Update smartxBidAdapter.js * Update ozoneBidAdapter.js * Update smartxBidAdapter.js * Update 33acrossBidAdapter_spec.js * Update admanBidAdapter_spec.js * Update krushmediaBidAdapter_spec.js * Update loganBidAdapter_spec.js * Update mobfoxpbBidAdapter_spec.js * Update precisoBidAdapter.js * Update smartyadsBidAdapter.js * Update e_volutionBidAdapter.js * Update e_volutionBidAdapter.js * Update pubmaticBidAdapter.js * Update iqzoneBidAdapter.js * Update qtBidAdapter.js * Update lunamediahbBidAdapter.js * Update krushmediaBidAdapter.js * Update pubgeniusBidAdapter_spec.js * Update smartxBidAdapter_spec.js * Update apacdexBidAdapter_spec.js * Update apacdexBidAdapter_spec.js * ORTB2: don't rely on context to infer video.placement (#11719) * Update videojsVideoProvider.js * Update ortb.js * Update ortb.js * Update video.js * Update videojsVideoProvider.js * Update jwplayerVideoProvider.js * Update ixBidAdapter.js * Update videojsVideoProvider.js * Update ixBidAdapter.js * Update ortb.js * Update videojsVideoProvider_spec.js * Update video.js * Update jwplayerVideoProvider.js * Update videojsVideoProvider.js * Update videojsVideoProvider_spec.js * Update videojsVideoProvider.js * Update videojsVideoProvider_spec.js * Update video_spec.js * Update ixBidAdapter.js * Update prebidServerBidAdapter_spec.js * Update viantOrtbBidAdapter_spec.js * remove adbookpsp * IX Bid Adapter: Remove Roundel Alias (#11732) * pass user.geo and device.geo to payload (#11723) * chore: removed roundel alias [PB-3025] --------- Co-authored-by: aivanov-zeta <144369215+aivanov-zeta@users.noreply.github.com> Co-authored-by: Love Sharma * Update package-lock.json * Update package-lock.json --------- Co-authored-by: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Co-authored-by: ccorbo Co-authored-by: Chris Corbo Co-authored-by: Demetrio Girardi Co-authored-by: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Co-authored-by: ops-co <159886704+ops-co@users.noreply.github.com> Co-authored-by: adtech-sky Co-authored-by: Philip Watson Co-authored-by: Fatih Kaya Co-authored-by: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Co-authored-by: Victor Co-authored-by: vladi-mmg Co-authored-by: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Co-authored-by: Carlos Felix Co-authored-by: mkomorski Co-authored-by: Marcin Komorski Co-authored-by: Kevin Siow Co-authored-by: Kevin Siow Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Karim Mourra Co-authored-by: Léonard Labat Co-authored-by: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally Co-authored-by: Gena Co-authored-by: Brian Schaaf Co-authored-by: Chris Huie Co-authored-by: Love Sharma Co-authored-by: aivanov-zeta <144369215+aivanov-zeta@users.noreply.github.com> Co-authored-by: Love Sharma * Revert "9.0 upstream (#11735)" (#11736) This reverts commit fe0d8bbdaaed21f8b926fa06f54a1285691002be. * Update bedigitechBidAdapter_spec.js --------- Co-authored-by: Marcin Komorski Co-authored-by: Patrick McCann Co-authored-by: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Co-authored-by: ccorbo Co-authored-by: Chris Corbo Co-authored-by: Demetrio Girardi Co-authored-by: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Co-authored-by: ops-co <159886704+ops-co@users.noreply.github.com> Co-authored-by: adtech-sky Co-authored-by: Philip Watson Co-authored-by: Fatih Kaya Co-authored-by: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Co-authored-by: Victor Co-authored-by: vladi-mmg Co-authored-by: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Co-authored-by: Carlos Felix Co-authored-by: Kevin Siow Co-authored-by: Kevin Siow Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Karim Mourra Co-authored-by: Léonard Labat Co-authored-by: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally Co-authored-by: Gena Co-authored-by: Brian Schaaf Co-authored-by: Chris Huie Co-authored-by: Love Sharma Co-authored-by: aivanov-zeta <144369215+aivanov-zeta@users.noreply.github.com> Co-authored-by: Love Sharma * 9.0: Update PR_REVIEW.md for code duplication * Update PR_REVIEW.md * Prebid 9: paapiForGpt: add support for customSlotMatching, remove `autoconfig` (#11714) * paapiForGpt: support customSlotMatching * paapiForGpt: replace autoconfig with configWithTargeting * flip default to true for configWithTargeting * pull in querystring --------- Co-authored-by: Alexandru Co-authored-by: Irakli Gotsiridze Co-authored-by: Chris Prokopiak Co-authored-by: Dmitry Sinev Co-authored-by: Demetrio Girardi Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> Co-authored-by: Pierre Faure Co-authored-by: Brian Schmidt Co-authored-by: Chris Huie Co-authored-by: Anand Venkatraman Co-authored-by: Léonard Labat Co-authored-by: v.raybaud Co-authored-by: Carlos Felix Co-authored-by: decemberWP <155962474+decemberWP@users.noreply.github.com> Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: Wojciech Biały Co-authored-by: Deepthi Neeladri Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: dsravana Co-authored-by: Florent Caspar Co-authored-by: f.caspar Co-authored-by: BizzClick <73241175+BizzClick@users.noreply.github.com> Co-authored-by: mkomorski Co-authored-by: Marcin Komorski Co-authored-by: Catalin Ciocov Co-authored-by: Jozef Bartek Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Zdravko Kosanović <41286499+zkosanovic@users.noreply.github.com> Co-authored-by: Matt Kendall <1870166+mkendall07@users.noreply.github.com> Co-authored-by: Olivier Co-authored-by: wsusrasp <106743463+wsusrasp@users.noreply.github.com> Co-authored-by: vladi-mmg Co-authored-by: Karim Mourra Co-authored-by: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Co-authored-by: Kevin Siow Co-authored-by: Kevin Siow Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally Co-authored-by: Gena Co-authored-by: Love Sharma Co-authored-by: aivanov-zeta <144369215+aivanov-zeta@users.noreply.github.com> Co-authored-by: Love Sharma Co-authored-by: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Co-authored-by: ccorbo Co-authored-by: Chris Corbo Co-authored-by: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Co-authored-by: ops-co <159886704+ops-co@users.noreply.github.com> Co-authored-by: adtech-sky Co-authored-by: Philip Watson Co-authored-by: Fatih Kaya Co-authored-by: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Co-authored-by: Victor Co-authored-by: Brian Schaaf --- .circleci/config.yml | 4 +- .devcontainer/Dockerfile | 2 +- .eslintrc.js | 12 + PR_REVIEW.md | 17 +- allowedModules.js | 1 - browsers.json | 24 +- .../gpt/creative_rendering.html | 15 - .../gpt/idward_segments_example.html | 112 - ...fledge_example.html => paapi_example.html} | 2 +- ...e.html => prebidServer_paapi_example.html} | 13 +- .../jwplayerRtdProvider_example.html | 6 +- libraries/appnexusUtils/anUtils.js | 16 + libraries/dfpUtils/dfpUtils.js | 13 + libraries/ortb2.5Translator/translator.js | 2 + libraries/ortbConverter/processors/video.js | 9 +- libraries/pbsExtensions/processors/params.js | 12 +- libraries/video/constants/ortb.js | 15 +- modules/.submodules.json | 9 +- modules/33acrossAnalyticsAdapter.md | 2 +- modules/33acrossBidAdapter.js | 3 - modules/33acrossIdSystem.js | 32 +- modules/33acrossIdSystem.md | 3 +- ...er.js => AsteriobidPbmAnalyticsAdapter.js} | 4 +- modules/AsteriobidPbmAnalyticsAdapter.md | 9 + modules/acuityadsBidAdapter.js | 1 + modules/adagioBidAdapter.js | 560 +- modules/adbookpspBidAdapter.js | 830 - modules/adbookpspBidAdapter.md | 191 - modules/admanBidAdapter.js | 1 + modules/adomikAnalyticsAdapter.js | 262 - modules/adtelligentBidAdapter.js | 6 - modules/adtrueBidAdapter.js | 1 + modules/anPspParamsConverter.js | 128 + modules/anPspParamsConverter.md | 10 + modules/appnexusBidAdapter.js | 93 +- modules/appushBidAdapter.js | 1 + modules/apstreamBidAdapter.js | 3 +- modules/axisBidAdapter.js | 1 + modules/beyondmediaBidAdapter.js | 1 + ...clickBidAdapter.js => blastoBidAdapter.js} | 6 +- ...clickBidAdapter.md => blastoBidAdapter.md} | 12 +- modules/bliinkBidAdapter.js | 2 - modules/bluebillywigBidAdapter.js | 374 - modules/bluebillywigBidAdapter.md | 38 - modules/boldwinBidAdapter.js | 1 + modules/brightcomBidAdapter.js | 303 - modules/brightcomBidAdapter.md | 46 - modules/brightcomSSPBidAdapter.js | 321 - modules/brightcomSSPBidAdapter.md | 46 - modules/britepoolIdSystem.js | 155 - modules/britepoolIdSystem.md | 42 - modules/colossussspBidAdapter.js | 1 - modules/connectadBidAdapter.js | 8 - modules/consentManagementGpp.js | 201 +- ...tManagement.js => consentManagementTcf.js} | 0 modules/criteoBidAdapter.js | 803 +- modules/dfpAdServerVideo.js | 119 +- modules/dfpAdpod.js | 102 + modules/dspxBidAdapter.js | 2 +- modules/e_volutionBidAdapter.js | 3 +- modules/ebdrBidAdapter.js | 156 - modules/ebdrBidAdapter.md | 53 - modules/edge226BidAdapter.js | 1 + modules/emtvBidAdapter.js | 1 + modules/enrichmentFpdModule.js | 2 - modules/eplanningAnalyticsAdapter.js | 130 - modules/fpdModule/index.md | 3 +- modules/globalsunBidAdapter.js | 1 + modules/goldbachBidAdapter.js | 2 +- modules/gothamadsBidAdapter.js | 19 +- modules/gptPreAuction.js | 13 +- modules/hadronRtdProvider.js | 7 +- modules/holidBidAdapter.js | 22 +- modules/idImportLibrary.js | 2 +- modules/idWardRtdProvider.js | 10 - modules/idWardRtdProvider.md | 51 - modules/illuminBidAdapter.js | 6 - modules/improvedigitalBidAdapter.js | 25 +- modules/improvedigitalBidAdapter.md | 5 +- modules/iqmBidAdapter.js | 277 - modules/iqzoneBidAdapter.js | 3 +- modules/ixBidAdapter.js | 20 +- modules/ixBidAdapter.md | 2 +- modules/jwplayerRtdProvider.js | 8 +- modules/jwplayerRtdProvider.md | 22 +- modules/kargoBidAdapter.js | 2 +- modules/kiviadsBidAdapter.js | 1 + modules/krushmediaBidAdapter.js | 17 +- modules/kueezBidAdapter.js | 5 +- modules/lkqdBidAdapter.js | 3 +- modules/loganBidAdapter.js | 1 + modules/logicadBidAdapter.js | 4 +- modules/loyalBidAdapter.js | 1 + modules/luceadBidAdapter.js | 2 +- modules/lunamediahbBidAdapter.js | 14 +- modules/madvertiseBidAdapter.js | 6 +- modules/marsmediaAnalyticsAdapter.js | 53 - modules/mediafuseBidAdapter.js | 2 +- modules/mediakeysBidAdapter.js | 1 + modules/medianetBidAdapter.js | 4 +- modules/medianetBidAdapter.md | 6 +- modules/mgidXBidAdapter.js | 1 + modules/microadBidAdapter.js | 1 - modules/minutemediaplusBidAdapter.js | 349 - modules/minutemediaplusBidAdapter.md | 35 - modules/mobfoxpbBidAdapter.js | 1 + modules/mytargetBidAdapter.md | 40 - modules/nativoBidAdapter.js | 2 +- modules/newspassidBidAdapter.js | 4 - modules/onetagBidAdapter.js | 4 +- modules/ooloAnalyticsAdapter.js | 5 + modules/openwebBidAdapter.js | 5 + modules/openxBidAdapter.js | 13 +- modules/operaadsBidAdapter.js | 1 - modules/optableBidAdapter.js | 2 +- modules/ozoneBidAdapter.js | 6 +- modules/paapi.js | 83 +- modules/{fledgeForGpt.js => paapiForGpt.js} | 78 +- modules/{fledgeForGpt.md => paapiForGpt.md} | 76 +- modules/parrableIdSystem.js | 416 - modules/pgamsspBidAdapter.js | 1 + modules/pirIdSystem.js | 62 - modules/pirIdSystem.md | 27 - modules/pixfutureBidAdapter.js | 2 +- modules/prebidServerBidAdapter/config.js | 8 +- modules/prebidServerBidAdapter/index.js | 11 +- .../prebidServerBidAdapter/ortbConverter.js | 13 +- modules/prebidmanagerAnalyticsAdapter.md | 9 - modules/precisoBidAdapter.js | 15 +- modules/pstudioBidAdapter.js | 5 +- modules/pubCircleBidAdapter.js | 1 + modules/pubgeniusBidAdapter.js | 16 +- modules/pubmaticBidAdapter.js | 5 +- modules/pulsepointBidAdapter.js | 8 - modules/qtBidAdapter.js | 3 +- modules/quantcastBidAdapter.js | 5 +- modules/richaudienceBidAdapter.js | 378 - ...er.js => ringieraxelspringerBidAdapter.js} | 63 +- ...er.md => ringieraxelspringerBidAdapter.md} | 10 +- modules/rtbhouseBidAdapter.js | 6 +- modules/rtbhouseBidAdapter.md | 30 +- modules/rubiconBidAdapter.js | 2 +- modules/sharethroughBidAdapter.js | 4 +- modules/shinezBidAdapter.js | 5 +- modules/shinezRtbBidAdapter.js | 6 - modules/showheroes-bsBidAdapter.js | 8 +- modules/sigmoidAnalyticsAdapter.js | 293 - modules/silverpushBidAdapter.js | 2 +- modules/smartxBidAdapter.js | 7 - modules/smartyadsBidAdapter.js | 15 +- modules/sonobiAnalyticsAdapter.js | 275 - modules/sovrnAnalyticsAdapter.js | 287 - modules/sovrnAnalyticsAdapter.md | 23 - modules/sovrnBidAdapter.js | 5 +- modules/spotxBidAdapter.js | 528 - modules/spotxBidAdapter.md | 136 - modules/staqAnalyticsAdapter.js | 433 - modules/taboolaBidAdapter.js | 2 +- modules/{gdprEnforcement.js => tcfControl.js} | 50 +- modules/topicsFpdModule.js | 36 +- modules/trafficgateBidAdapter.js | 10 - modules/tripleliftBidAdapter.js | 6 +- modules/truereachBidAdapter.js | 7 +- modules/twistDigitalBidAdapter.js | 2 +- modules/unrulyBidAdapter.js | 4 +- modules/userId/eids.md | 8 - modules/userId/index.js | 7 +- modules/userId/userId.md | 6 - modules/{utiqSystem.js => utiqIdSystem.js} | 8 +- modules/{utiqSystem.md => utiqIdSystem.md} | 7 +- modules/vdoaiBidAdapter.js | 1 - modules/vidazooBidAdapter.js | 8 +- modules/videobyteBidAdapter.js | 11 +- modules/videojsVideoProvider.js | 5 +- modules/visiblemeasuresBidAdapter.js | 1 + modules/waardexBidAdapter.js | 1 - modules/winrBidAdapter.js | 2 +- ...sspBidAdapter.js => yahooAdsBidAdapter.js} | 2 +- ...sspBidAdapter.md => yahooAdsBidAdapter.md} | 2 + modules/yuktamediaAnalyticsAdapter.js | 17 +- package-lock.json | 25831 +++++++++------- package.json | 21 +- plugins/eslint/index.js | 76 + plugins/eslint/package.json | 2 +- plugins/eslint/validateImports.js | 30 +- src/adapters/bidderFactory.js | 15 +- src/adloader.js | 36 +- src/config.js | 7 +- src/consentHandler.js | 9 - src/fpd/enrichment.js | 12 - src/prebid.js | 44 +- src/prebid.public.js | 1 + src/targeting.js | 94 +- src/userSync.js | 2 +- test/mocks/ortbConverter.js | 8 + test/spec/adloader_spec.js | 18 +- test/spec/fpd/enrichment_spec.js | 55 - test/spec/fpd/gdpr_spec.js | 2 +- .../fpd/{oneClient.js => oneClient_spec.js} | 0 test/spec/modules/33acrossBidAdapter_spec.js | 36 +- test/spec/modules/33acrossIdSystem_spec.js | 453 +- ... => AsteriobidPbmAnalyticsAdapter_spec.js} | 2 +- test/spec/modules/BTBidAdapter_spec.js | 5 +- .../modules/adagioAnalyticsAdapter_spec.js | 8 +- test/spec/modules/adagioBidAdapter_spec.js | 566 +- test/spec/modules/adbookpspBidAdapter_spec.js | 1344 - .../modules/adgenerationBidAdapter_spec.js | 8 +- test/spec/modules/admanBidAdapter_spec.js | 2 +- test/spec/modules/admaruBidAdapter_spec.js | 8 +- test/spec/modules/admixerBidAdapter_spec.js | 16 +- test/spec/modules/adoceanBidAdapter_spec.js | 8 +- .../modules/adomikAnalyticsAdapter_spec.js | 253 - .../modules/adrelevantisBidAdapter_spec.js | 8 +- test/spec/modules/adyoulikeBidAdapter_spec.js | 14 +- test/spec/modules/ajaBidAdapter_spec.js | 8 +- test/spec/modules/amxBidAdapter_spec.js | 1 - .../spec/modules/anPspParamsConverter_spec.js | 134 + test/spec/modules/aniviewBidAdapter_spec.js | 8 +- test/spec/modules/apacdexBidAdapter_spec.js | 7 +- test/spec/modules/appierBidAdapter_spec.js | 12 +- test/spec/modules/appnexusBidAdapter_spec.js | 117 +- test/spec/modules/asealBidAdapter_spec.js | 8 +- test/spec/modules/asoBidAdapter_spec.js | 2 +- .../modules/audiencerunBidAdapter_spec.js | 16 +- .../spec/modules/bedigitechBidAdapter_spec.js | 14 +- test/spec/modules/bidglassAdapter_spec.js | 8 +- ...apter_spec.js => blastoBidAdapter_spec.js} | 16 +- .../modules/bluebillywigBidAdapter_spec.js | 1094 - test/spec/modules/brightcomBidAdapter_spec.js | 411 - .../modules/brightcomSSPBidAdapter_spec.js | 411 - test/spec/modules/britepoolIdSystem_spec.js | 148 - test/spec/modules/c1xBidAdapter_spec.js | 6 +- .../spec/modules/clickforceBidAdapter_spec.js | 8 +- test/spec/modules/colombiaBidAdapter_spec.js | 6 +- .../modules/colossussspBidAdapter_spec.js | 5 +- .../spec/modules/consentManagementGpp_spec.js | 420 +- .../spec/modules/consentManagementUsp_spec.js | 1 - test/spec/modules/consentManagement_spec.js | 2 +- .../spec/modules/conversantBidAdapter_spec.js | 2 +- test/spec/modules/craftBidAdapter_spec.js | 16 +- test/spec/modules/criteoBidAdapter_spec.js | 2574 +- test/spec/modules/dailyhuntBidAdapter_spec.js | 8 +- test/spec/modules/datawrkzBidAdapter_spec.js | 20 +- test/spec/modules/dfpAdServerVideo_spec.js | 255 +- test/spec/modules/dfpAdpod_spec.js | 257 + test/spec/modules/dspxBidAdapter_spec.js | 8 +- test/spec/modules/ebdrBidAdapter_spec.js | 245 - .../modules/eplanningAnalyticsAdapter_spec.js | 164 - test/spec/modules/euidIdSystem_spec.js | 6 +- test/spec/modules/fledgeForGpt_spec.js | 206 - test/spec/modules/fluctBidAdapter_spec.js | 24 +- .../modules/freewheel-sspBidAdapter_spec.js | 16 +- test/spec/modules/gammaBidAdapter_spec.js | 6 +- test/spec/modules/gmosspBidAdapter_spec.js | 8 +- test/spec/modules/gnetBidAdapter_spec.js | 8 +- test/spec/modules/goldbachBidAdapter_spec.js | 16 +- test/spec/modules/gptPreAuction_spec.js | 2 +- test/spec/modules/gridBidAdapter_spec.js | 8 +- test/spec/modules/gumgumBidAdapter_spec.js | 30 +- test/spec/modules/hadronRtdProvider_spec.js | 6 +- test/spec/modules/id5AnalyticsAdapter_spec.js | 2 +- test/spec/modules/id5IdSystem_spec.js | 1 + test/spec/modules/idWardRtdProvider_spec.js | 116 - test/spec/modules/illuminBidAdapter_spec.js | 4 +- .../modules/improvedigitalBidAdapter_spec.js | 111 +- test/spec/modules/iqmBidAdapter_spec.js | 414 - test/spec/modules/ixBidAdapter_spec.js | 60 +- test/spec/modules/jwplayerRtdProvider_spec.js | 17 +- test/spec/modules/kargoBidAdapter_spec.js | 8 +- .../spec/modules/krushmediaBidAdapter_spec.js | 2 +- test/spec/modules/lassoBidAdapter_spec.js | 4 +- test/spec/modules/lkqdBidAdapter_spec.js | 8 +- test/spec/modules/loganBidAdapter_spec.js | 2 +- test/spec/modules/logicadBidAdapter_spec.js | 8 +- .../spec/modules/luponmediaBidAdapter_spec.js | 8 +- .../spec/modules/madvertiseBidAdapter_spec.js | 2 +- test/spec/modules/mantisBidAdapter_spec.js | 8 +- test/spec/modules/mediafuseBidAdapter_spec.js | 16 +- test/spec/modules/medianetBidAdapter_spec.js | 28 +- test/spec/modules/microadBidAdapter_spec.js | 4 - .../modules/minutemediaplusBidAdapter_spec.js | 654 - test/spec/modules/mobfoxpbBidAdapter_spec.js | 2 +- test/spec/modules/mytargetBidAdapter_spec.js | 199 - test/spec/modules/omsBidAdapter_spec.js | 6 +- test/spec/modules/onetagBidAdapter_spec.js | 14 +- test/spec/modules/onomagicBidAdapter_spec.js | 6 +- test/spec/modules/openwebBidAdapter_spec.js | 21 +- test/spec/modules/openxBidAdapter_spec.js | 37 +- test/spec/modules/optableBidAdapter_spec.js | 4 +- test/spec/modules/paapiForGpt_spec.js | 216 + test/spec/modules/paapi_spec.js | 1328 +- test/spec/modules/parrableIdSystem_spec.js | 784 - test/spec/modules/pirIdSystem_spec.js | 77 - test/spec/modules/pixfutureBidAdapter_spec.js | 8 +- .../modules/prebidServerBidAdapter_spec.js | 488 +- test/spec/modules/prismaBidAdapter_spec.js | 2 +- test/spec/modules/pubgeniusBidAdapter_spec.js | 1 - test/spec/modules/pubmaticBidAdapter_spec.js | 10 +- test/spec/modules/pubxBidAdapter_spec.js | 8 +- test/spec/modules/pxyzBidAdapter_spec.js | 8 +- test/spec/modules/quantcastBidAdapter_spec.js | 4 - test/spec/modules/radsBidAdapter_spec.js | 8 +- test/spec/modules/rakutenBidAdapter_spec.js | 8 +- .../spec/modules/retailspotBidAdapter_spec.js | 14 +- .../modules/richaudienceBidAdapter_spec.js | 1304 - ... => ringieraxelspringerBidAdapter_spec.js} | 118 +- test/spec/modules/rtbhouseBidAdapter_spec.js | 24 +- test/spec/modules/rubiconBidAdapter_spec.js | 10 +- .../modules/sharethroughBidAdapter_spec.js | 2 +- test/spec/modules/shinezRtbBidAdapter_spec.js | 6 +- .../modules/sigmoidAnalyticsAdapter_spec.js | 57 - test/spec/modules/silvermobBidAdapter_spec.js | 2 +- test/spec/modules/slimcutBidAdapter_spec.js | 24 +- test/spec/modules/smaatoBidAdapter_spec.js | 2 +- test/spec/modules/smartxBidAdapter_spec.js | 9 - .../modules/sonobiAnalyticsAdapter_spec.js | 85 - .../modules/sovrnAnalyticsAdapter_spec.js | 530 - test/spec/modules/sovrnBidAdapter_spec.js | 12 +- test/spec/modules/spotxBidAdapter_spec.js | 711 - .../spec/modules/staqAnalyticsAdapter_spec.js | 302 - .../modules/stroeerCoreBidAdapter_spec.js | 13 +- test/spec/modules/stvBidAdapter_spec.js | 8 +- test/spec/modules/taboolaBidAdapter_spec.js | 4 +- ...Enforcement_spec.js => tcfControl_spec.js} | 75 +- test/spec/modules/teadsBidAdapter_spec.js | 32 +- test/spec/modules/topicsFpdModule_spec.js | 12 + test/spec/modules/tpmnBidAdapter_spec.js | 2 +- .../modules/trafficgateBidAdapter_spec.js | 22 +- .../spec/modules/tripleliftBidAdapter_spec.js | 14 +- test/spec/modules/truereachBidAdapter_spec.js | 5 +- .../modules/twistDigitalBidAdapter_spec.js | 2 +- test/spec/modules/uid2IdSystem_helpers.js | 2 +- test/spec/modules/uid2IdSystem_spec.js | 6 +- test/spec/modules/unrulyBidAdapter_spec.js | 12 +- test/spec/modules/userId_spec.js | 38 +- ...tiqSystem_spec.js => utiqIdSystem_spec.js} | 28 +- test/spec/modules/viantOrtbBidAdapter_spec.js | 1 + test/spec/modules/vidazooBidAdapter_spec.js | 6 +- .../submodules/videojsVideoProvider_spec.js | 4 +- .../spec/modules/videoreachBidAdapter_spec.js | 8 +- test/spec/modules/vidoomyBidAdapter_spec.js | 6 +- test/spec/modules/visxBidAdapter_spec.js | 16 +- test/spec/modules/winrBidAdapter_spec.js | 18 +- test/spec/modules/wipesBidAdapter_spec.js | 6 +- ...ter_spec.js => yahooAdsBidAdapter_spec.js} | 21 +- test/spec/modules/yieldoneBidAdapter_spec.js | 6 +- test/spec/ortbConverter/gdpr_spec.js | 2 +- .../pbsExtensions/params_spec.js | 58 - test/spec/ortbConverter/video_spec.js | 1 - test/spec/unit/core/bidderFactory_spec.js | 50 +- test/spec/unit/core/targeting_spec.js | 70 + test/spec/unit/pbjs_api_spec.js | 65 +- test/test_deps.js | 1 + webpack.conf.js | 11 - 354 files changed, 19919 insertions(+), 34382 deletions(-) delete mode 100644 integrationExamples/gpt/creative_rendering.html delete mode 100644 integrationExamples/gpt/idward_segments_example.html rename integrationExamples/gpt/{fledge_example.html => paapi_example.html} (97%) rename integrationExamples/gpt/{prebidServer_fledge_example.html => prebidServer_paapi_example.html} (91%) create mode 100644 libraries/dfpUtils/dfpUtils.js rename modules/{prebidmanagerAnalyticsAdapter.js => AsteriobidPbmAnalyticsAdapter.js} (99%) create mode 100644 modules/AsteriobidPbmAnalyticsAdapter.md delete mode 100644 modules/adbookpspBidAdapter.js delete mode 100644 modules/adbookpspBidAdapter.md delete mode 100644 modules/adomikAnalyticsAdapter.js create mode 100644 modules/anPspParamsConverter.js create mode 100644 modules/anPspParamsConverter.md rename modules/{bizzclickBidAdapter.js => blastoBidAdapter.js} (90%) rename modules/{bizzclickBidAdapter.md => blastoBidAdapter.md} (88%) delete mode 100644 modules/bluebillywigBidAdapter.js delete mode 100644 modules/bluebillywigBidAdapter.md delete mode 100644 modules/brightcomBidAdapter.js delete mode 100644 modules/brightcomBidAdapter.md delete mode 100644 modules/brightcomSSPBidAdapter.js delete mode 100644 modules/brightcomSSPBidAdapter.md delete mode 100644 modules/britepoolIdSystem.js delete mode 100644 modules/britepoolIdSystem.md rename modules/{consentManagement.js => consentManagementTcf.js} (100%) create mode 100644 modules/dfpAdpod.js delete mode 100644 modules/ebdrBidAdapter.js delete mode 100644 modules/ebdrBidAdapter.md delete mode 100644 modules/enrichmentFpdModule.js delete mode 100644 modules/eplanningAnalyticsAdapter.js delete mode 100644 modules/idWardRtdProvider.js delete mode 100644 modules/idWardRtdProvider.md delete mode 100644 modules/iqmBidAdapter.js delete mode 100644 modules/marsmediaAnalyticsAdapter.js delete mode 100644 modules/minutemediaplusBidAdapter.js delete mode 100644 modules/minutemediaplusBidAdapter.md delete mode 100644 modules/mytargetBidAdapter.md rename modules/{fledgeForGpt.js => paapiForGpt.js} (68%) rename modules/{fledgeForGpt.md => paapiForGpt.md} (55%) delete mode 100644 modules/parrableIdSystem.js delete mode 100644 modules/pirIdSystem.js delete mode 100644 modules/pirIdSystem.md delete mode 100644 modules/prebidmanagerAnalyticsAdapter.md delete mode 100755 modules/richaudienceBidAdapter.js rename modules/{rasBidAdapter.js => ringieraxelspringerBidAdapter.js} (89%) rename modules/{rasBidAdapter.md => ringieraxelspringerBidAdapter.md} (88%) delete mode 100644 modules/sigmoidAnalyticsAdapter.js delete mode 100644 modules/sonobiAnalyticsAdapter.js delete mode 100644 modules/sovrnAnalyticsAdapter.js delete mode 100644 modules/sovrnAnalyticsAdapter.md delete mode 100644 modules/spotxBidAdapter.js delete mode 100644 modules/spotxBidAdapter.md delete mode 100644 modules/staqAnalyticsAdapter.js rename modules/{gdprEnforcement.js => tcfControl.js} (90%) rename modules/{utiqSystem.js => utiqIdSystem.js} (96%) rename modules/{utiqSystem.md => utiqIdSystem.md} (54%) rename modules/{yahoosspBidAdapter.js => yahooAdsBidAdapter.js} (99%) rename modules/{yahoosspBidAdapter.md => yahooAdsBidAdapter.md} (99%) create mode 100644 plugins/eslint/index.js create mode 100644 src/prebid.public.js create mode 100644 test/mocks/ortbConverter.js rename test/spec/fpd/{oneClient.js => oneClient_spec.js} (100%) rename test/spec/modules/{prebidmanagerAnalyticsAdapter_spec.js => AsteriobidPbmAnalyticsAdapter_spec.js} (98%) delete mode 100755 test/spec/modules/adbookpspBidAdapter_spec.js delete mode 100644 test/spec/modules/adomikAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/anPspParamsConverter_spec.js rename test/spec/modules/{bizzclickBidAdapter_spec.js => blastoBidAdapter_spec.js} (97%) delete mode 100644 test/spec/modules/bluebillywigBidAdapter_spec.js delete mode 100644 test/spec/modules/brightcomBidAdapter_spec.js delete mode 100644 test/spec/modules/brightcomSSPBidAdapter_spec.js delete mode 100644 test/spec/modules/britepoolIdSystem_spec.js create mode 100644 test/spec/modules/dfpAdpod_spec.js delete mode 100644 test/spec/modules/ebdrBidAdapter_spec.js delete mode 100644 test/spec/modules/eplanningAnalyticsAdapter_spec.js delete mode 100644 test/spec/modules/fledgeForGpt_spec.js delete mode 100644 test/spec/modules/idWardRtdProvider_spec.js delete mode 100644 test/spec/modules/iqmBidAdapter_spec.js delete mode 100644 test/spec/modules/minutemediaplusBidAdapter_spec.js delete mode 100644 test/spec/modules/mytargetBidAdapter_spec.js create mode 100644 test/spec/modules/paapiForGpt_spec.js delete mode 100644 test/spec/modules/parrableIdSystem_spec.js delete mode 100644 test/spec/modules/pirIdSystem_spec.js delete mode 100644 test/spec/modules/richaudienceBidAdapter_spec.js rename test/spec/modules/{rasBidAdapter_spec.js => ringieraxelspringerBidAdapter_spec.js} (89%) delete mode 100644 test/spec/modules/sigmoidAnalyticsAdapter_spec.js delete mode 100644 test/spec/modules/sonobiAnalyticsAdapter_spec.js delete mode 100644 test/spec/modules/sovrnAnalyticsAdapter_spec.js delete mode 100644 test/spec/modules/spotxBidAdapter_spec.js delete mode 100644 test/spec/modules/staqAnalyticsAdapter_spec.js rename test/spec/modules/{gdprEnforcement_spec.js => tcfControl_spec.js} (95%) rename test/spec/modules/{utiqSystem_spec.js => utiqIdSystem_spec.js} (86%) rename test/spec/modules/{yahoosspBidAdapter_spec.js => yahooAdsBidAdapter_spec.js} (99%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 22539912268..dcf2ba804c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ aliases: - &environment docker: # specify the version you desire here - - image: cimg/node:16.20-browsers + - image: cimg/node:20.14.0-browsers resource_class: xlarge # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -18,8 +18,6 @@ aliases: - &restore_dep_cache keys: - v1-dependencies-{{ checksum "package.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - &save_dep_cache paths: diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 69e13850258..9b1bb6e39cf 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG VARIANT="12" +ARG VARIANT="20" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg diff --git a/.eslintrc.js b/.eslintrc.js index f17c7a0063d..184b042813d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -83,6 +83,7 @@ module.exports = { files: key + '/**/*.js', rules: { 'prebid/validate-imports': ['error', allowedModules[key]], + 'prebid/no-innerText': ['error', allowedModules[key]], 'no-restricted-globals': [ 'error', { @@ -95,5 +96,16 @@ module.exports = { // code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain. files: 'plugins/*/**/*.js', parser: 'esprima' + }, + { + files: '**BidAdapter.js', + rules: { + 'no-restricted-imports': [ + 'error', { + patterns: ["**/src/events.js", + "**/src/adloader.js"] + } + ] + } }]) }; diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 9deac9963fb..f6a2c157d2d 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -23,10 +23,11 @@ General gulp commands include separate commands for serving the codebase on a bu - Checkout the branch (these instructions are available on the GitHub PR page as well). - Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests. - Verify code under review has at least 80% unit test coverage. If legacy code doesn't have enough unit test coverage, require that additional unit tests to be included in the PR. -- Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test` +- Verify tests are green in circle-ci + local build by running `gulp serve` | `gulp test` - Verify no code quality violations are present from linting (should be reported in terminal) - Make sure the code is not setting cookies or localstorage directly -- it must use the `StorageManager`. - Review for obvious errors or bad coding practice / use best judgement here. +- Don't allow needless code duplication with other js files; require both files import common code. Do not allow commits designed to fool the code duplication checker. - If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change. - If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed. - If all above is good, add a `LGTM` comment and, if the change is in PBS-core or is an important module like the prebidServerBidAdapter, request 1 additional core member to review. @@ -51,20 +52,21 @@ Follow steps above for general review process. In addition, please verify the fo - If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. - All bidder parameter conventions must be followed: - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit. - - First party data must be read from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd). + - First party data must be read from the bid request object: bidrequest.ortb2 - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function. - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain. - The bidderRequest.refererInfo.referer must be checked in addition to any bidder-specific parameter. - Page position must come from bidrequest.mediaTypes.banner.pos or bidrequest.mediaTypes.video.pos - - Global OpenRTB fields should come from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd): + - Eids object is to be preferred to Userids object in the bid request, as the userid object may be removed in a future version + - Global OpenRTB fields should come from bidrequest.ortb2 - bcat, battr, badv - Impression-specific OpenRTB fields should come from bidrequest.ortb2imp - instl - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file): - - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` - - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` + - If they support the TCF consentManagementTcf module and TCF2, add `tcf2_supported: true` - If they support the US Privacy consentManagementUsp module, add `usp_supported: true` - - If they support one or more userId modules, add `userId: (list of supported vendors)` + - If they support the GPP consentManagementGpp module, add `gpp_supported: true` + - If they support one or more userId modules, add `userId: (list of supported vendors) or (all)` - If they support video and/or native mediaTypes add `media_types: video, native`. Note that display is added by default. If you don't support display, add "no-display" as the first entry, e.g. `media_types: no-display, native` - If they support COPPA, add `coppa_supported: true` - If they support SChain, add `schain_supported: true` @@ -100,7 +102,7 @@ Follow steps above for general review process. In addition: - modules/userId/userId.md - tests can go either within the userId_spec.js file or in their own _spec file if they wish - GVLID is recommended in the *IdSystem file if they operate in EU -- make sure example configurations align to the actual code (some modules use the userId storage settings and allow pub configuration, while others handle reading/writing cookies on their own, so should not include the storage params in examples) +- make sure example configurations align to the actual code (some modules use the userId storage settings and allow pub configuration, while others handle reading/writing cookies on their own, so should not include the storage params in examples). This ability to write will be removed in a future version, see https://github.com/prebid/Prebid.js/issues/10710 - the 3 available methods (getId, extendId, decode) should be used as they were intended - decode (required method) should not be making requests to retrieve a new ID, it should just be decoding a response - extendId (optional method) should not be making requests to retrieve a new ID, it should just be adding additional data to the id object @@ -121,6 +123,7 @@ Follow steps above for general review process. In addition: - Confirm that the module - is not loading external code. If it is, escalate to the #prebid-js Slack channel. - is reading `config` from the function signature rather than calling `getConfig`. + - Is practicing reasonable data minimization, eg not sending all eids over the wire without publisher whitelisting - is sending data to the bid request only as either First Party Data or in bidRequest.rtd.RTDPROVIDERCODE. - is making HTTPS requests as early as possible, but not more often than needed. - doesn't force bid adapters to load additional code. diff --git a/allowedModules.js b/allowedModules.js index bc9ada39571..dbcae2db2cc 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -1,7 +1,6 @@ module.exports = { 'modules': [ - 'criteo-direct-rsa-validate', 'crypto-js', 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], diff --git a/browsers.json b/browsers.json index bd6bd5772d6..1bb8108d456 100644 --- a/browsers.json +++ b/browsers.json @@ -1,39 +1,39 @@ { - "bs_edge_latest_windows_10": { + "bs_edge_latest_windows_11": { "base": "BrowserStack", - "os_version": "10", + "os_version": "11", "browser": "edge", "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_latest_windows_10": { + "bs_chrome_latest_windows_11": { "base": "BrowserStack", - "os_version": "10", + "os_version": "11", "browser": "chrome", "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_87_windows_10": { + "bs_chrome_107_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "87.0", + "browser_version": "107.0", "device": null, "os": "Windows" }, - "bs_firefox_latest_windows_10": { + "bs_firefox_latest_windows_11": { "base": "BrowserStack", - "os_version": "10", + "os_version": "11", "browser": "firefox", "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_safari_latest_mac_bigsur": { + "bs_safari_latest_mac_ventura": { "base": "BrowserStack", - "os_version": "Big Sur", + "os_version": "Ventura", "browser": "safari", "browser_version": "latest", "device": null, @@ -41,9 +41,9 @@ }, "bs_safari_15_catalina": { "base": "BrowserStack", - "os_version": "Catalina", + "os_version": "Monterey", "browser": "safari", - "browser_version": "13.1", + "browser_version": "15.6", "device": null, "os": "OS X" } diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html deleted file mode 100644 index 04d4736c631..00000000000 --- a/integrationExamples/gpt/creative_rendering.html +++ /dev/null @@ -1,15 +0,0 @@ - - diff --git a/integrationExamples/gpt/idward_segments_example.html b/integrationExamples/gpt/idward_segments_example.html deleted file mode 100644 index 9bc06124c77..00000000000 --- a/integrationExamples/gpt/idward_segments_example.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
-
First Party Data (ortb2) Sent to Bidding Adapter
-
- - diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/paapi_example.html similarity index 97% rename from integrationExamples/gpt/fledge_example.html rename to integrationExamples/gpt/paapi_example.html index 5a6ab7a5fef..860d7c22edf 100644 --- a/integrationExamples/gpt/fledge_example.html +++ b/integrationExamples/gpt/paapi_example.html @@ -3,7 +3,7 @@ diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_paapi_example.html similarity index 91% rename from integrationExamples/gpt/prebidServer_fledge_example.html rename to integrationExamples/gpt/prebidServer_paapi_example.html index eb2fc438997..d138d2b7753 100644 --- a/integrationExamples/gpt/prebidServer_fledge_example.html +++ b/integrationExamples/gpt/prebidServer_paapi_example.html @@ -3,7 +3,7 @@ @@ -44,8 +44,8 @@ pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { - enabled: true + paapi: { + enabled: true, }, s2sConfig: [{ accountId : '1', @@ -57,13 +57,6 @@ }] }); - pbjs.setBidderConfig({ - bidders: ['openx'], - config: { - fledgeEnabled: true - } - }); - pbjs.addAdUnits(adUnits); pbjs.requestBids({ diff --git a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html index f3f0c64fb1a..c6170b565b5 100644 --- a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html +++ b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html @@ -65,7 +65,11 @@ waitForIt: true, params: { // Note: the following media Ids are placeholders and should be replaced with your Ids. - mediaIDs: ['abc', 'def', 'ghi', 'jkl'] + mediaIDs: ['abc', 'def', 'ghi', 'jkl'], + overrideContentId: 'always', + overrideContentUrl: 'always', + overrideContentTitle: 'always', + overrideContentDescription: 'always' } }] } diff --git a/libraries/appnexusUtils/anUtils.js b/libraries/appnexusUtils/anUtils.js index 9b55cd5c2a4..7897cfc0e0e 100644 --- a/libraries/appnexusUtils/anUtils.js +++ b/libraries/appnexusUtils/anUtils.js @@ -10,6 +10,22 @@ export function convertCamelToUnderscore(value) { }).replace(/^_/, ''); } +export const appnexusAliases = [ + { code: 'appnexusAst', gvlid: 32 }, + { code: 'emxdigital', gvlid: 183 }, + { code: 'emetriq', gvlid: 213 }, + { code: 'pagescience', gvlid: 32 }, + { code: 'gourmetads', gvlid: 32 }, + { code: 'matomy', gvlid: 32 }, + { code: 'featureforward', gvlid: 32 }, + { code: 'oftmedia', gvlid: 32 }, + { code: 'adasta', gvlid: 32 }, + { code: 'beintoo', gvlid: 618 }, + { code: 'projectagora', gvlid: 1032 }, + { code: 'uol', gvlid: 32 }, + { code: 'adzymic', gvlid: 723 }, +]; + /** * Creates an array of n length and fills each item with the given value */ diff --git a/libraries/dfpUtils/dfpUtils.js b/libraries/dfpUtils/dfpUtils.js new file mode 100644 index 00000000000..0f070b15ba2 --- /dev/null +++ b/libraries/dfpUtils/dfpUtils.js @@ -0,0 +1,13 @@ +/** Safe defaults which work on pretty much all video calls. */ +export const DEFAULT_DFP_PARAMS = { + env: 'vp', + gdfp_req: 1, + output: 'vast', + unviewed_position_start: 1, +} + +export const DFP_ENDPOINT = { + protocol: 'https', + host: 'securepubads.g.doubleclick.net', + pathname: '/gampad/ads' +} diff --git a/libraries/ortb2.5Translator/translator.js b/libraries/ortb2.5Translator/translator.js index 1afad516ef0..6dd6d247d1c 100644 --- a/libraries/ortb2.5Translator/translator.js +++ b/libraries/ortb2.5Translator/translator.js @@ -1,10 +1,12 @@ import {deepAccess, deepSetValue, logError} from '../../src/utils.js'; export const EXT_PROMOTIONS = [ + 'device.sua', 'source.schain', 'regs.gdpr', 'regs.us_privacy', 'regs.gpp', + 'regs.gpp_sid', 'user.consent', 'user.eids' ]; diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index b10ad4032c5..caa855566eb 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -26,10 +26,6 @@ const ORTB_VIDEO_PARAMS = new Set([ 'playbackend' ]); -const PLACEMENT = { - 'instream': 1, -} - export function fillVideoImp(imp, bidRequest, context) { if (context.mediaType && context.mediaType !== VIDEO) return; @@ -46,10 +42,7 @@ export function fillVideoImp(imp, bidRequest, context) { } Object.assign(video, format[0]); } - const placement = PLACEMENT[videoParams.context]; - if (placement != null) { - video.placement = placement; - } + imp.video = mergeDeep(video, imp.video); } } diff --git a/libraries/pbsExtensions/processors/params.js b/libraries/pbsExtensions/processors/params.js index 010ffa5b372..dbfbb928953 100644 --- a/libraries/pbsExtensions/processors/params.js +++ b/libraries/pbsExtensions/processors/params.js @@ -1,17 +1,7 @@ -import {auctionManager} from '../../../src/auctionManager.js'; -import adapterManager from '../../../src/adapterManager.js'; import {deepSetValue} from '../../../src/utils.js'; -export function setImpBidParams( - imp, bidRequest, context, - {adUnit, bidderRequests, index = auctionManager.index, bidderRegistry = adapterManager.bidderRegistry} = {}) { +export function setImpBidParams(imp, bidRequest) { let params = bidRequest.params; - const adapter = bidderRegistry[bidRequest.bidder]; - if (adapter && adapter.getSpec().transformBidParams) { - adUnit = adUnit || index.getAdUnit(bidRequest); - bidderRequests = bidderRequests || [context.bidderRequest]; - params = adapter.getSpec().transformBidParams(params, true, adUnit, bidderRequests); - } if (params) { deepSetValue( imp, diff --git a/libraries/video/constants/ortb.js b/libraries/video/constants/ortb.js index 6b64296500e..86e7b499774 100644 --- a/libraries/video/constants/ortb.js +++ b/libraries/video/constants/ortb.js @@ -13,7 +13,8 @@ * @property {number} w - Width of the video player in device independent pixels (DIPS). * @property {number} h - Height of the video player in device independent pixels (DIPS). * @property {number|undefined} startdelay - Indicates the offset of the ad placement. - * @property {number|undefined} placement - Placement type for the impression. + * @property {number|undefined} placement - Legacy Placement type for the impression. + * @property {number|undefined} plcmt - Modern placement type for the impression. * @property {number|undefined} linearity - Indicates if the impression must be linear, nonlinear, etc. If omitted, assume all are allowed. * @property {number} skip - Indicates if the player can allow the video to be skipped, where 0 is no, 1 is yes. * @property {number|undefined} skipmin - Only ad creatives with a duration greater than this value can be skippable; only applicable if the ad is skippable. @@ -97,6 +98,18 @@ export const PLACEMENT = { INTERSTITIAL_SLIDER_FLOATING: 5 }; +/** + * ADCOM - https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/develop/AdCOM%20v1.0%20FINAL.md#list_plcmtsubtypesvideo + * @enum OrtbVideoParams.plcmt + */ +export const PLCMT = { + INSTREAM: 1, + ACCOMPANYING_CONTENT: 2, + INTERSTITIAL: 3, + OUTSTREAM: 4, + NO_CONTENT: 4 +}; + /** * ORTB 2.5 section 5.4 - Ad Position * @enum OrtbVideoParams.pos diff --git a/modules/.submodules.json b/modules/.submodules.json index 224fdd6ab04..39f3969c4fd 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -6,7 +6,6 @@ "adtelligentIdSystem", "adqueryIdSystem", "amxIdSystem", - "britepoolIdSystem", "connectIdSystem", "czechAdIdSystem", "criteoIdSystem", @@ -37,7 +36,6 @@ "novatiqIdSystem", "oneKeyIdSystem", "operaadsIdSystem", - "parrableIdSystem", "pubProvidedIdSystem", "publinkIdSystem", "quantcastIdSystem", @@ -45,7 +43,7 @@ "tapadIdSystem", "teadsIdSystem", "tncIdSystem", - "utiqSystem", + "utiqIdSystem", "utiqMtpIdSystem", "uid2IdSystem", "euidIdSystem", @@ -56,7 +54,7 @@ ], "adpod": [ "freeWheelAdserverVideo", - "dfpAdServerVideo" + "dfpAdpod" ], "rtdModule": [ "1plusXRtdProvider", @@ -110,7 +108,8 @@ "videojsVideoProvider" ], "paapi": [ - "fledgeForGpt" + "paapiForGpt", + "topLevelPaapi" ] } } diff --git a/modules/33acrossAnalyticsAdapter.md b/modules/33acrossAnalyticsAdapter.md index c56059e5526..d093434dc97 100644 --- a/modules/33acrossAnalyticsAdapter.md +++ b/modules/33acrossAnalyticsAdapter.md @@ -49,7 +49,7 @@ by default when Prebid is downloaded. If you are compiling from source, this might look something like: ```sh -gulp bundle --modules=gptPreAuction,consentManagement,consentManagementGpp,consentManagementUsp,enrichmentFpdModule,gdprEnforcement,33acrossBidAdapter,33acrossIdSystem,33acrossAnalyticsAdapter +gulp bundle --modules=gptPreAuction,consentManagementTcf,consentManagementGpp,consentManagementUsp,tcfControl,33acrossBidAdapter,33acrossIdSystem,33acrossAnalyticsAdapter ``` Enable the 33Across Analytics Adapter in Prebid.js using the analytics provider `33across` diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 1eab05ba47f..60d732e35d3 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -492,7 +492,6 @@ function _buildVideoORTB(bidRequest) { // Placement Inference Rules: // - If no placement is defined then default to 2 (In Banner) // - If the old deprecated field is defined, use its value for the recent placement field - // - If product is instream (for instream context) then override placement to 1 const calculatePlacementValue = () => { const IN_BANNER_PLACEMENT_VALUE = 2; @@ -510,8 +509,6 @@ function _buildVideoORTB(bidRequest) { if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; - video.plcmt = 1; - video.placement &&= 1; } // bidfloors diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 0118408f08d..8f99846017a 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -26,6 +26,9 @@ const CALLER_NAME = 'pbjs'; const GVLID = 58; const STORAGE_FPID_KEY = '33acrossIdFp'; +const STORAGE_TPID_KEY = '33acrossIdTp'; +const DEFAULT_1PID_SUPPORT = true; +const DEFAULT_TPID_SUPPORT = true; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); @@ -51,7 +54,8 @@ function calculateResponseObj(response) { return { envelope: response.data.envelope, - fp: response.data.fp + fp: response.data.fp, + tp: response.data.tp }; } @@ -88,6 +92,11 @@ function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) { params.fp = encodeURIComponent(fp); } + const tp = getStoredValue(STORAGE_TPID_KEY, enabledStorageTypes); + if (tp) { + params.tp = encodeURIComponent(tp); + } + return params; } @@ -130,10 +139,10 @@ function getStoredValue(key, enabledStorageTypes) { return storedValue; } -function handleFpId(fpId, storageConfig) { - fpId - ? storeValue(STORAGE_FPID_KEY, fpId, storageConfig) - : deleteFromStorage(STORAGE_FPID_KEY); +function handleSupplementalId(key, id, storageConfig) { + id + ? storeValue(key, id, storageConfig) + : deleteFromStorage(key); } /** @type {Submodule} */ @@ -166,7 +175,7 @@ export const thirthyThreeAcrossIdSubmodule = { * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId({ params = { }, enabledStorageTypes = [], storage: storageConfig }, gdprConsentData) { + getId({ params = { }, enabledStorageTypes = [], storage: storageConfig = {} }, gdprConsentData) { if (typeof params.pid !== 'string') { logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); @@ -179,7 +188,7 @@ export const thirthyThreeAcrossIdSubmodule = { return; } - const { pid, storeFpid, apiUrl = API_URL } = params; + const { pid, storeFpid = DEFAULT_1PID_SUPPORT, storeTpid = DEFAULT_TPID_SUPPORT, apiUrl = API_URL } = params; return { callback(cb) { @@ -198,7 +207,14 @@ export const thirthyThreeAcrossIdSubmodule = { } if (storeFpid) { - handleFpId(responseObj.fp, { + handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, { + enabledStorageTypes, + expires: storageConfig.expires + }); + } + + if (storeTpid) { + handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, { enabledStorageTypes, expires: storageConfig.expires }); diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index 5f5e7805ff9..e983c8ab871 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -51,4 +51,5 @@ The following settings are available in the `params` property in `userSync.userI | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` | -| storeFpid | Optional | Boolean | Indicates whether a supplemental first-party ID may be stored to improve addressability | `false` (default) or `true` | +| storeFpid | Optional | Boolean | Indicates whether a supplemental first-party ID may be stored to improve addressability, this feature is enabled by default | `true` (default) or `false` | +| storeTpid | Optional | Boolean | Indicates whether a supplemental third-party ID may be stored to improve addressability, this feature is enabled by default | `true` (default) or `false` | diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/AsteriobidPbmAnalyticsAdapter.js similarity index 99% rename from modules/prebidmanagerAnalyticsAdapter.js rename to modules/AsteriobidPbmAnalyticsAdapter.js index 39677d51320..7f56f5064b7 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/AsteriobidPbmAnalyticsAdapter.js @@ -9,10 +9,10 @@ import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ -export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'prebidmanager'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'asteriobidpbm'}); const DEFAULT_EVENT_URL = 'https://endpt.prebidmanager.com/endpoint'; const analyticsType = 'endpoint'; -const analyticsName = 'Prebid Manager Analytics'; +const analyticsName = 'Asteriobid PBM Analytics'; let ajax = ajaxBuilder(0); diff --git a/modules/AsteriobidPbmAnalyticsAdapter.md b/modules/AsteriobidPbmAnalyticsAdapter.md new file mode 100644 index 00000000000..0331a71b17c --- /dev/null +++ b/modules/AsteriobidPbmAnalyticsAdapter.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: Asteriobid PBM Analytics Adapter +Module Type: Analytics Adapter +Maintainer: admin@prebidmanager.com + +# Description + +Analytics adapter for Asteriobid PBM. Contact admin@prebidmanager.com for information. diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index 5b12eb2133b..4000230b1e0 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -53,6 +53,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b6ffc9b8d0d..3cc31336827 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,4 +1,4 @@ -import {find} from '../src/polyfill.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { canAccessWindowTop, cleanObj, @@ -6,13 +6,11 @@ import { deepClone, generateUUID, getDNT, - getUniqueIdentifierStr, getWindowSelf, getWindowTop, isArray, isArrayOfNums, isFn, - inIframe, isInteger, isNumber, isSafeFrameWindow, @@ -20,39 +18,27 @@ import { logError, logInfo, logWarn, - mergeDeep, } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {loadExternalScript} from '../src/adloader.js'; -import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; -import {OUTSTREAM} from '../src/video.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import { getRefererInfo, parseDomain } from '../src/refererDetection.js'; +import { OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; +import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { find } from '../src/polyfill.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { userSync } from '../src/userSync.js'; -import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; -const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; -const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; -const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; const GVLID = 617; -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BB_PUBLICATION = 'adagio'; const BB_RENDERER_DEFAULT = 'renderer'; export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER.js`; -const MAX_SESS_DURATION = 30 * 60 * 1000; -const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; -const ADAGIO_PUBKEY_E = 65537; const CURRENCY = 'USD'; // This provide a whitelist and a basic validation of OpenRTB 2.5 options used by the Adagio SSP. @@ -88,142 +74,15 @@ export const ORTB_VIDEO_PARAMS = { 'api': (value) => isArrayOfNums(value) }; -let currentWindow; - -export const GlobalExchange = (function() { - let features; - let exchangeData = {}; - - return { - clearFeatures: function() { - features = undefined; - }, - - clearExchangeData: function() { - exchangeData = {}; - }, - - getOrSetGlobalFeatures: function () { - if (!features) { - features = { - type: 'bidAdapter', - page_dimensions: getPageDimensions().toString(), - viewport_dimensions: getViewPortDimensions().toString(), - user_timestamp: getTimestampUTC().toString(), - dom_loading: getDomLoadingDuration().toString(), - } - } - - return { ...features }; - }, - - prepareExchangeData(storageValue) { - const adagioStorage = JSON.parse(storageValue, function(name, value) { - if (name.charAt(0) !== '_' || name === '') { - return value; - } - }); - let random = deepAccess(adagioStorage, 'session.rnd'); - let newSession = false; - - if (internal.isNewSession(adagioStorage)) { - newSession = true; - random = Math.random(); - } - - const data = { - session: { - new: newSession, - rnd: random, - } - } - - mergeDeep(exchangeData, adagioStorage, data); - - internal.enqueue({ - action: 'session', - ts: Date.now(), - data: exchangeData - }); - }, - - getExchangeData() { - return exchangeData - } - }; -})(); - -/** - * @deprecated will be removed in Prebid.js 9. - */ -export function adagioScriptFromLocalStorageCb(ls) { - try { - if (!ls) { - logWarn(`${LOG_PREFIX} script not found.`); - return; - } - - const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; - - if (!hashRgx.test(ls)) { - logWarn(`${LOG_PREFIX} no hash found.`); - storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); - } else { - const r = ls.match(hashRgx); - const hash = r[2]; - const content = r[3]; - - if (verify(content, hash, ADAGIO_PUBKEY, ADAGIO_PUBKEY_E)) { - logInfo(`${LOG_PREFIX} start script.`); - Function(ls)(); // eslint-disable-line no-new-func - } else { - logWarn(`${LOG_PREFIX} invalid script found.`); - storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); - } - } - } catch (err) { - logError(LOG_PREFIX, err); - } -} - /** - * @deprecated will be removed in Prebid.js 9. + * Returns the window.ADAGIO global object used to store Adagio data. + * This object is created in window.top if possible, otherwise in window.self. */ -export function getAdagioScript() { - storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => { - internal.adagioScriptFromLocalStorageCb(ls); - }); - - storage.localStorageIsEnabled(isValid => { - if (isValid) { - loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE); - } else { - // Try-catch to avoid error when 3rd party cookies is disabled (e.g. in privacy mode) - try { - // ensure adagio removing for next time. - // It's an antipattern regarding the TCF2 enforcement logic - // but it's the only way to respect the user choice update. - window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); - // Extra data from external script. - // This key is removed only if localStorage is not accessible. - window.localStorage.removeItem('adagio'); - } catch (e) { - logInfo(`${LOG_PREFIX} unable to clear Adagio scripts from localstorage.`); - } - } - }); -} - -function getCurrentWindow() { - return currentWindow || getWindowSelf(); -} - -function initAdagio() { - currentWindow = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - const w = currentWindow; +const _ADAGIO = (function() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; w.ADAGIO.queue = w.ADAGIO.queue || []; @@ -231,36 +90,8 @@ function initAdagio() { w.ADAGIO.versions.pbjs = '$prebid.version$'; w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); - storage.getDataFromLocalStorage('adagio', (storageData) => { - try { - if (w.ADAGIO.hasRtd !== true) { - logInfo(`${LOG_PREFIX} RTD module not found. Loading external script from adagioBidAdapter is deprecated and will be removed in Prebid.js 9.`); - - GlobalExchange.prepareExchangeData(storageData); - getAdagioScript(); - } - } catch (e) { - logError(LOG_PREFIX, e); - } - }); -} - -function enqueue(ob) { - const w = internal.getCurrentWindow(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.queue.push(ob); -}; - -function getPageviewId() { - const w = internal.getCurrentWindow(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); - - return w.ADAGIO.pageviewId; -}; + return w.ADAGIO; +})(); function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; @@ -283,30 +114,6 @@ function getSite(bidderRequest) { }; }; -function getElementFromTopWindow(element, currentWindow) { - try { - if (getWindowTop() === currentWindow) { - if (!element.getAttribute('id')) { - element.setAttribute('id', `adg-${getUniqueIdentifierStr()}`); - } - return element; - } else { - const frame = currentWindow.frameElement; - const frameClientRect = frame.getBoundingClientRect(); - const elementClientRect = element.getBoundingClientRect(); - - if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) { - return false; - } - - return getElementFromTopWindow(frame, currentWindow.parent); - } - } catch (err) { - logWarn(`${LOG_PREFIX}`, err); - return false; - } -}; - function autoDetectAdUnitElementIdFromGpt(adUnitCode) { const autoDetectedAdUnit = getGptSlotInfoForAdUnitCode(adUnitCode); @@ -331,49 +138,28 @@ function isRendererPreferredFromPublisher(bidRequest) { } /** - * - * @param {object} adagioStorage - * @returns {boolean} + * Check if the publisher has defined its own video player and uses it for all ad-units. + * If not or if the `backupOnly` flag is true, this means we use our own player (BlueBillywig) defined in this adapter. */ -function isNewSession(adagioStorage) { - const now = Date.now(); - const { lastActivityTime, vwSmplg } = deepAccess(adagioStorage, 'session', {}); - return ( - !isNumber(lastActivityTime) || - !isNumber(vwSmplg) || - (now - lastActivityTime) > MAX_SESS_DURATION - ) -} - -function setPlayerName(bidRequest) { - const playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; - - if (playerName === 'other') { - logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); - } - - return playerName; +function getPlayerName(bidRequest) { + return _internal.isRendererPreferredFromPublisher(bidRequest) ? 'other' : 'adagio'; ; } function hasRtd() { - const w = internal.getCurrentWindow(); - - return !!(w.ADAGIO && w.ADAGIO.hasRtd); + const rtdConfigs = config.getConfig('realTimeData.dataProviders') || []; + return rtdConfigs.find(provider => provider.name === 'adagio'); }; -export const internal = { - enqueue, - getPageviewId, +export const _internal = { + canAccessWindowTop, + getAdagioNs: function() { + return _ADAGIO; + }, getDevice, getSite, - getElementFromTopWindow, getRefererInfo, - adagioScriptFromLocalStorageCb, - getCurrentWindow, - canAccessWindowTop, + hasRtd, isRendererPreferredFromPublisher, - isNewSession, - hasRtd }; function _getGdprConsent(bidderRequest) { @@ -447,7 +233,7 @@ function _buildVideoBidRequest(bidRequest) { }; if (videoParams.context && videoParams.context === OUTSTREAM) { - bidRequest.mediaTypes.video.playerName = setPlayerName(bidRequest); + bidRequest.mediaTypes.video.playerName = getPlayerName(bidRequest); } // Only whitelisted OpenRTB options need to be validated. @@ -688,201 +474,6 @@ function autoFillParams(bid) { setExtraParam(bid, 'category'); } -function getPageDimensions() { - if (isSafeFrameWindow() || !canAccessWindowTop()) { - return ''; - } - - // the page dimension can be computed on window.top only. - const wt = getWindowTop(); - const body = wt.document.querySelector('body'); - - if (!body) { - return ''; - } - const html = wt.document.documentElement; - const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); - const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); - - return `${pageWidth}x${pageHeight}`; -} - -/** - * @todo Move to prebid Core as Utils. - * @returns - */ -function getViewPortDimensions() { - if (!isSafeFrameWindow() && !canAccessWindowTop()) { - return ''; - } - - const viewportDims = { w: 0, h: 0 }; - - if (isSafeFrameWindow()) { - const ws = getWindowSelf(); - - if (typeof ws.$sf.ext.geom !== 'function') { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); - return ''; - } - - const sfGeom = ws.$sf.ext.geom(); - - if (!sfGeom || !sfGeom.win) { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api. Missing `geom().win` property'); - return ''; - } - - viewportDims.w = Math.round(sfGeom.w); - viewportDims.h = Math.round(sfGeom.h); - } else { - // window.top based computing - const wt = getWindowTop(); - viewportDims.w = wt.innerWidth; - viewportDims.h = wt.innerHeight; - } - - return `${viewportDims.w}x${viewportDims.h}`; -} - -function getSlotPosition(adUnitElementId) { - if (!adUnitElementId) { - return ''; - } - - if (!isSafeFrameWindow() && !canAccessWindowTop()) { - return ''; - } - - const position = { x: 0, y: 0 }; - - if (isSafeFrameWindow()) { - const ws = getWindowSelf(); - - if (typeof ws.$sf.ext.geom !== 'function') { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); - return ''; - } - - const sfGeom = ws.$sf.ext.geom(); - - if (!sfGeom || !sfGeom.self) { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api. Missing `geom().self` property'); - return ''; - } - - position.x = Math.round(sfGeom.t); - position.y = Math.round(sfGeom.l); - } else if (canAccessWindowTop()) { - try { - // window.top based computing - const wt = getWindowTop(); - const d = wt.document; - - let domElement; - - if (inIframe() === true) { - const ws = getWindowSelf(); - const currentElement = ws.document.getElementById(adUnitElementId); - domElement = internal.getElementFromTopWindow(currentElement, ws); - } else { - domElement = wt.document.getElementById(adUnitElementId); - } - - if (!domElement) { - return ''; - } - - let box = domElement.getBoundingClientRect(); - - const docEl = d.documentElement; - const body = d.body; - const clientTop = d.clientTop || body.clientTop || 0; - const clientLeft = d.clientLeft || body.clientLeft || 0; - const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const elComputedStyle = wt.getComputedStyle(domElement, null); - const mustDisplayElement = elComputedStyle.display === 'none'; - - if (mustDisplayElement) { - logWarn(LOG_PREFIX, 'The element is hidden. The slot position cannot be computed.'); - } - - position.x = Math.round(box.left + scrollLeft - clientLeft); - position.y = Math.round(box.top + scrollTop - clientTop); - } catch (err) { - logError(LOG_PREFIX, err); - return ''; - } - } else { - return ''; - } - - return `${position.x}x${position.y}`; -} - -function getTimestampUTC() { - // timestamp returned in seconds - return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; -} - -/** - * domLoading feature is computed on window.top if reachable. - */ -function getDomLoadingDuration() { - let domLoadingDuration = -1; - let performance; - - performance = (canAccessWindowTop()) ? getWindowTop().performance : getWindowSelf().performance; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - -function storeRequestInAdagioNS(bidRequest) { - const w = getCurrentWindow(); - // Store adUnits config. - // If an adUnitCode has already been stored, it will be replaced. - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== bidRequest.adUnitCode); - - let printNumber - if (bidRequest.features && bidRequest.features.print_number) { - printNumber = bidRequest.features.print_number; - } else if (bidRequest.params.features && bidRequest.params.features.print_number) { - printNumber = bidRequest.params.features.print_number; - } - - w.ADAGIO.pbjsAdUnits.push({ - code: bidRequest.adUnitCode, - mediaTypes: bidRequest.mediaTypes || {}, - sizes: (bidRequest.mediaTypes && bidRequest.mediaTypes.banner && Array.isArray(bidRequest.mediaTypes.banner.sizes)) ? bidRequest.mediaTypes.banner.sizes : bidRequest.sizes, - bids: [{ - bidder: bidRequest.bidder, - params: bidRequest.params // use the updated bid.params object with auto-detected params - }], - auctionId: bidRequest.auctionId, // this auctionId has been generated by adagioBidAdapter - pageviewId: internal.getPageviewId(), - printNumber, - localPbjs: '$$PREBID_GLOBAL$$', - localPbjsRef: getGlobal() - }); - - // (legacy) Store internal adUnit information - w.ADAGIO.adUnits[bidRequest.adUnitCode] = { - auctionId: bidRequest.auctionId, // this auctionId has been generated by adagioBidAdapter - pageviewId: internal.getPageviewId(), - printNumber, - }; -} - // See https://support.bluebillywig.com/developers/vast-renderer/ const OUTSTREAM_RENDERER = { bootstrapPlayer: function(bid) { @@ -954,31 +545,6 @@ const OUTSTREAM_RENDERER = { } }; -/** - * - * @param {*} bidRequest - * @returns - */ -const _getFeatures = (bidRequest) => { - const f = { ...deepAccess(bidRequest, 'ortb2.site.ext.data.adg_rtd.features', GlobalExchange.getOrSetGlobalFeatures()) } || {}; - - f.print_number = deepAccess(bidRequest, 'bidderRequestsCount', 1).toString(); - - if (f.type === 'bidAdapter') { - f.adunit_position = getSlotPosition(bidRequest.params.adUnitElementId) - } else { - f.adunit_position = deepAccess(bidRequest, 'ortb2Imp.ext.data.adg_rtd.adunit_position'); - } - - Object.keys(f).forEach((prop) => { - if (f[prop] === '') { - delete f[prop]; - } - }); - - return f; -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -992,7 +558,6 @@ export const spec = { // Note: `bid.params.placement` is not related to the video param `placement`. if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { logWarn(`${LOG_PREFIX} at least one required param is missing.`); - // internal.enqueue(debugData()); return false; } @@ -1004,10 +569,9 @@ export const spec = { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const secure = (location.protocol === 'https:') ? 1 : 0; - const device = internal.getDevice(); - const site = internal.getSite(bidderRequest); - const pageviewId = internal.getPageviewId(); - const hasRtd = internal.hasRtd(); + const device = _internal.getDevice(); + const site = _internal.getSite(bidderRequest); + const pageviewId = _internal.getAdagioNs().pageviewId; const gdprConsent = _getGdprConsent(bidderRequest) || {}; const uspConsent = _getUspConsent(bidderRequest) || {}; const coppa = _getCoppa(); @@ -1015,15 +579,20 @@ export const spec = { const schain = _getSchain(validBidRequests[0]); const eids = _getEids(validBidRequests[0]) || []; const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled') - const usIfr = syncEnabled && userSync.canBidderRegisterSync('iframe', 'adagio') + const canSyncWithIframe = syncEnabled && userSync.canBidderRegisterSync('iframe', 'adagio') // We don't validate the dsa object in adapter and let our server do it. const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); - let rtdSamplingSession = deepAccess(bidderRequest, 'ortb2.site.ext.data.adg_rtd.session'); - const dataExchange = (rtdSamplingSession) ? { session: rtdSamplingSession } : GlobalExchange.getExchangeData(); + // If no session data is provided, we always generate a new one. + const sessionData = deepAccess(bidderRequest, 'ortb2.site.ext.data.adg_rtd.session', {}); + if (!Object.keys(sessionData).length) { + logInfo(LOG_PREFIX, 'No session data provided. A new session is be generated.') + sessionData.new = true; + sessionData.rnd = Math.random() + } - const aucId = generateUUID() + const aucId = deepAccess('bidderRequest', 'ortb2.site.ext.data.adg_rtd.uid') || generateUUID() const adUnits = validBidRequests.map(rawBidRequest => { const bidRequest = deepClone(rawBidRequest); @@ -1074,21 +643,6 @@ export const spec = { } } - const features = _getFeatures(bidRequest); - bidRequest.features = features; - - if (!hasRtd) { - internal.enqueue({ - action: 'features', - ts: Date.now(), - data: { - features, - params: { ...bidRequest.params }, - adUnitCode: bidRequest.adUnitCode - } - }); - } - // Handle priceFloors module // We need to use `rawBidRequest` as param because: // - adagioBidAdapter generates its own auctionId due to transmitTid activity limitation (see https://github.com/prebid/Prebid.js/pull/10079) @@ -1142,10 +696,14 @@ export const spec = { bidRequest.gpid = gpid; } - if (!hasRtd) { - // store the whole bidRequest (adUnit) object in the ADAGIO namespace. - storeRequestInAdagioNS(bidRequest); + // features are added by the adagioRtdProvider. + const rawFeatures = { + ...deepAccess(bidRequest, 'ortb2.site.ext.data.adg_rtd.features', {}), + print_number: (bidRequest.bidderRequestsCount || 1).toString(), + adunit_position: deepAccess(bidRequest, 'ortb2Imp.ext.data.adg_rtd.adunit_position', null) } + // Clean the features object from null or undefined values. + bidRequest.features = Object.entries(rawFeatures).reduce((a, [k, v]) => (v == null ? a : (a[k] = v, a)), {}) // Remove some params that are not needed on the server side. delete bidRequest.params.siteId; @@ -1193,13 +751,15 @@ export const spec = { url: ENDPOINT, data: { organizationId: organizationId, - hasRtd: hasRtd ? 1 : 0, + hasRtd: _internal.hasRtd() ? 1 : 0, secure: secure, device: device, site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], - data: dataExchange, + data: { + session: sessionData + }, regs: { gdpr: gdprConsent, coppa: coppa, @@ -1213,9 +773,7 @@ export const spec = { eids: eids }, prebidVersion: '$prebid.version$', - featuresVersion: FEATURES_VERSION, - usIfr: usIfr, - adgjs: storage.localStorageIsEnabled() + usIfr: canSyncWithIframe }, options: { contentType: 'text/plain' @@ -1232,11 +790,13 @@ export const spec = { const response = serverResponse.body; if (response) { if (response.data) { - internal.enqueue({ - action: 'ssp-data', - ts: Date.now(), - data: response.data - }); + if (_internal.hasRtd()) { + _internal.getAdagioNs().queue.push({ + action: 'ssp-data', + ts: Date.now(), + data: response.data + }); + } } if (response.bids) { response.bids.forEach(bidObj => { @@ -1300,6 +860,4 @@ export const spec = { }, }; -initAdagio(); - registerBidder(spec); diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js deleted file mode 100644 index cb03f2ffc17..00000000000 --- a/modules/adbookpspBidAdapter.js +++ /dev/null @@ -1,830 +0,0 @@ -import {find, includes} from '../src/polyfill.js'; -import {config} from '../src/config.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {getStorageManager} from '../src/storageManager.js'; -import { - deepAccess, - deepSetValue, - flatten, - generateUUID, - inIframe, - isArray, - isEmptyStr, - isNumber, - isPlainObject, - isStr, - logError, - logWarn, - triggerPixel, - uniques -} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - -/** - * CONSTANTS - */ - -export const VERSION = '1.0.0'; -const EXCHANGE_URL = 'https://ex.fattail.com/openrtb2'; -const WIN_TRACKING_URL = 'https://ev.fattail.com/wins'; -const BIDDER_CODE = 'adbookpsp'; -const USER_ID_KEY = 'hb_adbookpsp_uid'; -const USER_ID_COOKIE_EXP = 2592000000; // lasts 30 days -const BID_TTL = 300; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; -const DEFAULT_CURRENCY = 'USD'; -const VIDEO_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'protocols', - 'w', - 'h', - 'startdelay', - 'placement', - 'linearity', - 'skip', - 'skipmin', - 'skipafter', - 'sequence', - 'battr', - 'maxextended', - 'minbitrate', - 'maxbitrate', - 'boxingallowed', - 'playbackmethod', - 'playbackend', - 'delivery', - 'pos', - 'companionad', - 'api', - 'companiontype', - 'ext', -]; -const TARGETING_VALUE_SEPARATOR = ','; - -export const DEFAULT_BIDDER_CONFIG = { - bidTTL: BID_TTL, - defaultCurrency: DEFAULT_CURRENCY, - exchangeUrl: EXCHANGE_URL, - winTrackingEnabled: true, - winTrackingUrl: WIN_TRACKING_URL, - orgId: null, -}; - -config.setDefaults({ - adbookpsp: DEFAULT_BIDDER_CONFIG, -}); - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - - buildRequests, - getUserSyncs, - interpretResponse, - isBidRequestValid, - onBidWon, -}; - -registerBidder(spec); - -/** - * BID REQUEST - */ - -function isBidRequestValid(bidRequest) { - return ( - hasRequiredParams(bidRequest) && - (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest)) - ); -} - -function buildRequests(validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - const requests = []; - - if (validBidRequests.length > 0) { - requests.push({ - method: 'POST', - url: getBidderConfig('exchangeUrl'), - options: { - contentType: 'application/json', - withCredentials: true, - }, - data: buildRequest(validBidRequests, bidderRequest), - }); - } - - return requests; -} - -function buildRequest(validBidRequests, bidderRequest) { - const request = { - id: bidderRequest.bidderRequestId, - tmax: bidderRequest.timeout, - site: { - domain: bidderRequest.refererInfo.domain, - page: bidderRequest.refererInfo.page, - ref: bidderRequest.refererInfo.ref, - }, - source: buildSource(validBidRequests, bidderRequest), - device: buildDevice(), - regs: buildRegs(bidderRequest), - user: buildUser(bidderRequest), - imp: validBidRequests.map(buildImp), - ext: { - adbook: { - config: getBidderConfig(), - version: { - prebid: '$prebid.version$', - adapter: VERSION, - }, - }, - }, - }; - - return JSON.stringify(request); -} - -function buildDevice() { - const { innerWidth, innerHeight } = common.getWindowDimensions(); - - const device = { - w: innerWidth, - h: innerHeight, - js: true, - ua: navigator.userAgent, - dnt: - navigator.doNotTrack === 'yes' || - navigator.doNotTrack == '1' || - navigator.msDoNotTrack == '1' - ? 1 - : 0, - }; - - const deviceConfig = common.getConfig('device'); - - if (isPlainObject(deviceConfig)) { - return { ...device, ...deviceConfig }; - } - - return device; -} - -function buildRegs(bidderRequest) { - const regs = { - coppa: common.getConfig('coppa') === true ? 1 : 0, - }; - - if (bidderRequest.gdprConsent) { - deepSetValue( - regs, - 'ext.gdpr', - bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - ); - deepSetValue( - regs, - 'ext.gdprConsentString', - bidderRequest.gdprConsent.consentString || '' - ); - } - - if (bidderRequest.uspConsent) { - deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent); - } - - return regs; -} - -function buildSource(bidRequests, bidderRequest) { - const source = { - fd: 1, - tid: bidderRequest.ortb2.source.tid, - }; - const schain = deepAccess(bidRequests, '0.schain'); - - if (schain) { - deepSetValue(source, 'ext.schain', schain); - } - - return source; -} - -function buildUser(bidderRequest) { - const user = { - id: getUserId(), - }; - - if (bidderRequest.gdprConsent) { - user.gdprConsentString = bidderRequest.gdprConsent.consentString || ''; - } - - return user; -} - -function buildImp(bidRequest) { - let impBase = { - id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - ext: buildImpExt(bidRequest), - }; - - return Object.keys(bidRequest.mediaTypes) - .filter((mediaType) => includes(SUPPORTED_MEDIA_TYPES, mediaType)) - .reduce((imp, mediaType) => { - return { - ...imp, - [mediaType]: buildMediaTypeObject(mediaType, bidRequest), - }; - }, impBase); -} - -function buildMediaTypeObject(mediaType, bidRequest) { - switch (mediaType) { - case BANNER: - return buildBannerObject(bidRequest); - case VIDEO: - return buildVideoObject(bidRequest); - default: - logWarn(`${BIDDER_CODE}: Unsupported media type ${mediaType}!`); - } -} - -function buildBannerObject(bidRequest) { - const format = bidRequest.mediaTypes.banner.sizes.map((size) => { - const [w, h] = size; - - return { w, h }; - }); - const { w, h } = format[0]; - - return { - pos: 0, - topframe: inIframe() ? 0 : 1, - format, - w, - h, - }; -} - -function buildVideoObject(bidRequest) { - const { w, h } = getVideoSize(bidRequest); - let videoObj = { - w, - h, - }; - - for (const param of VIDEO_PARAMS) { - const paramsValue = deepAccess(bidRequest, `params.video.${param}`); - const mediaTypeValue = deepAccess( - bidRequest, - `mediaTypes.video.${param}` - ); - - if (paramsValue || mediaTypeValue) { - videoObj[param] = paramsValue || mediaTypeValue; - } - } - - return videoObj; -} - -function getVideoSize(bidRequest) { - const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', [[]]); - const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); - - if (isNumber(w) && isNumber(h)) { - return { w, h }; - } - - return { - w: playerSize[0][0], - h: playerSize[0][1], - } -} - -function buildImpExt(validBidRequest) { - const defaultOrgId = getBidderConfig('orgId'); - const { orgId, placementId } = validBidRequest.params || {}; - const effectiverOrgId = orgId || defaultOrgId; - const ext = {}; - - if (placementId) { - deepSetValue(ext, 'adbook.placementId', placementId); - } - - if (effectiverOrgId) { - deepSetValue(ext, 'adbook.orgId', effectiverOrgId); - } - - return ext; -} - -/** - * BID RESPONSE - */ - -function interpretResponse(bidResponse, bidderRequest) { - const bidderRequestBody = safeJSONparse(bidderRequest.data); - - if ( - deepAccess(bidderRequestBody, 'id') != - deepAccess(bidResponse, 'body.id') - ) { - logError( - `${BIDDER_CODE}: Bid response id does not match bidder request id` - ); - - return []; - } - - const referrer = deepAccess(bidderRequestBody, 'site.ref', ''); - const incomingBids = deepAccess(bidResponse, 'body.seatbid', []) - .filter((seat) => isArray(seat.bid)) - .reduce((bids, seat) => bids.concat(seat.bid), []) - .filter(validateBid(bidderRequestBody)); - const targetingMap = buildTargetingMap(incomingBids); - - return impBidsToPrebidBids( - incomingBids, - bidderRequestBody, - bidResponse.body.cur, - referrer, - targetingMap - ); -} - -function impBidsToPrebidBids( - incomingBids, - bidderRequestBody, - bidResponseCurrency, - referrer, - targetingMap -) { - return incomingBids - .map( - impToPrebidBid( - bidderRequestBody, - bidResponseCurrency, - referrer, - targetingMap - ) - ) - .filter((i) => i !== null); -} - -const impToPrebidBid = - (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => { - try { - const bidRequest = findBidRequest(bidderRequestBody, bid); - - if (!bidRequest) { - logError(`${BIDDER_CODE}: Could not match bid to bid request`); - - return null; - } - const categories = deepAccess(bid, 'cat', []); - const mediaType = getMediaType(bid.adm); - let prebidBid = { - ad: bid.adm, - adId: bid.adid, - adserverTargeting: targetingMap[bidIndex], - adUnitCode: bidRequest.tagid, - bidderRequestId: bidderRequestBody.id, - bidId: bid.id, - cpm: bid.price, - creativeId: bid.crid || bid.id, - currency: bidResponseCurrency || getBidderConfig('defaultCurrency'), - height: bid.h, - lineItemId: deepAccess(bid, 'ext.liid'), - mediaType, - meta: { - advertiserDomains: bid.adomain, - mediaType, - primaryCatId: categories[0], - secondaryCatIds: categories.slice(1), - }, - netRevenue: true, - nurl: bid.nurl, - referrer: referrer, - requestId: bid.impid, - ttl: getBidderConfig('bidTTL'), - width: bid.w, - }; - - if (mediaType === VIDEO) { - prebidBid = { - ...prebidBid, - ...getVideoSpecificParams(bidRequest, bid), - }; - } - - if (deepAccess(bid, 'ext.pa_win') === true) { - prebidBid.auctionWinner = true; - } - return prebidBid; - } catch (error) { - logError(`${BIDDER_CODE}: Error while building bid`, error); - - return null; - } - }; - -function getVideoSpecificParams(bidRequest, bid) { - return { - height: bid.h || bidRequest.video.h, - vastXml: bid.adm, - width: bid.w || bidRequest.video.w, - }; -} - -function buildTargetingMap(bids) { - const impIds = bids.map(({ impid }) => impid).filter(uniques); - const values = impIds.reduce((result, id) => { - result[id] = { - lineItemIds: [], - orderIds: [], - dealIds: [], - adIds: [], - adAndOrderIndexes: [] - }; - - return result; - }, {}); - - bids.forEach((bid, bidIndex) => { - let impId = bid.impid; - values[impId].lineItemIds.push(bid.ext.liid); - values[impId].dealIds.push(bid.dealid); - values[impId].adIds.push(bid.adid); - - if (deepAccess(bid, 'ext.ordid')) { - values[impId].orderIds.push(bid.ext.ordid); - bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => { - let adIdIndex = values[impId].adIds.indexOf(bid.adid); - values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex) - }) - } - }); - - const targetingMap = {}; - - bids.forEach((bid, bidIndex) => { - let id = bid.impid; - - targetingMap[bidIndex] = { - hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), - hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), - hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR), - hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), - hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR), - }; - }) - return targetingMap; -} - -/** - * VALIDATION - */ - -function hasRequiredParams(bidRequest) { - const value = - deepAccess(bidRequest, 'params.placementId') != null || - deepAccess(bidRequest, 'params.orgId') != null || - getBidderConfig('orgId') != null; - - if (!value) { - logError(`${BIDDER_CODE}: missing orgId and placementId parameter`); - } - - return value; -} - -function isValidBannerRequest(bidRequest) { - const value = validateSizes( - deepAccess(bidRequest, 'mediaTypes.banner.sizes', []) - ); - - return value; -} - -function isValidVideoRequest(bidRequest) { - const value = - isArray(deepAccess(bidRequest, 'mediaTypes.video.mimes')) && - validateVideoSizes(bidRequest); - - return value; -} - -function validateSize(size) { - return isArray(size) && size.length === 2 && size.every(isNumber); -} - -function validateSizes(sizes) { - return isArray(sizes) && sizes.length > 0 && sizes.every(validateSize); -} - -function validateVideoSizes(bidRequest) { - const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); - - return ( - validateSizes( - deepAccess(bidRequest, 'mediaTypes.video.playerSize') - ) || - (isNumber(w) && isNumber(h)) - ); -} - -function validateBid(bidderRequestBody) { - return function (bid) { - const mediaType = getMediaType(bid.adm); - const bidRequest = findBidRequest(bidderRequestBody, bid); - let validators = commonBidValidators; - - if (mediaType === BANNER) { - validators = [...commonBidValidators, ...bannerBidValidators]; - } - - const value = validators.every((validator) => validator(bid, bidRequest)); - - if (!value) { - logWarn(`${BIDDER_CODE}: Invalid bid`, bid); - } - - return value; - }; -} - -const commonBidValidators = [ - (bid) => isPlainObject(bid), - (bid) => isNonEmptyStr(bid.adid), - (bid) => isNonEmptyStr(bid.adm), - (bid) => isNonEmptyStr(bid.id), - (bid) => isNonEmptyStr(bid.impid), - (bid) => isNonEmptyStr(deepAccess(bid, 'ext.liid')), - (bid) => isNumber(bid.price), -]; - -const bannerBidValidators = [ - validateBannerDimension('w'), - validateBannerDimension('h'), -]; - -function validateBannerDimension(dimension) { - return function (bid, bidRequest) { - if (bid[dimension] == null) { - return bannerHasSingleSize(bidRequest); - } - - return isNumber(bid[dimension]); - }; -} - -function bannerHasSingleSize(bidRequest) { - return deepAccess(bidRequest, 'banner.format', []).length === 1; -} - -/** - * USER SYNC - */ - -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { - return responses - .map((response) => deepAccess(response, 'body.ext.sync')) - .filter(isArray) - .reduce(flatten, []) - .filter(validateSync(syncOptions)) - .map(applyConsents(gdprConsent, uspConsent)); -} - -const validateSync = (syncOptions) => (sync) => { - return ( - ((sync.type === 'image' && syncOptions.pixelEnabled) || - (sync.type === 'iframe' && syncOptions.iframeEnabled)) && - sync.url - ); -}; - -const applyConsents = (gdprConsent, uspConsent) => (sync) => { - const url = getUrlBuilder(sync.url); - - if (gdprConsent) { - url.set('gdpr', gdprConsent.gdprApplies ? 1 : 0); - url.set('consentString', gdprConsent.consentString || ''); - } - if (uspConsent) { - url.set('us_privacy', encodeURIComponent(uspConsent)); - } - if (common.getConfig('coppa') === true) { - url.set('coppa', 1); - } - - return { ...sync, url: url.toString() }; -}; - -function getUserId() { - const id = getUserIdFromStorage() || common.generateUUID(); - - setUserId(id); - - return id; -} - -function getUserIdFromStorage() { - const id = storage.localStorageIsEnabled() - ? storage.getDataFromLocalStorage(USER_ID_KEY) - : storage.getCookie(USER_ID_KEY); - - if (!validateUUID(id)) { - return; - } - - return id; -} - -function setUserId(userId) { - if (storage.localStorageIsEnabled()) { - storage.setDataInLocalStorage(USER_ID_KEY, userId); - } - - if (storage.cookiesAreEnabled()) { - const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toISOString(); - - storage.setCookie(USER_ID_KEY, userId, expires); - } -} - -function validateUUID(uuid) { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( - uuid - ); -} - -/** - * EVENT TRACKING - */ - -function onBidWon(bid) { - if (!getBidderConfig('winTrackingEnabled')) { - return; - } - - const wurl = buildWinUrl(bid); - - if (wurl !== null) { - triggerPixel(wurl); - } - - if (isStr(bid.nurl)) { - triggerPixel(bid.nurl); - } -} - -function buildWinUrl(bid) { - try { - const url = getUrlBuilder(getBidderConfig('winTrackingUrl')); - - url.set('impId', bid.requestId); - url.set('reqId', bid.bidderRequestId); - url.set('bidId', bid.bidId); - - return url.toString(); - } catch (_) { - logError( - `${BIDDER_CODE}: Could not build win tracking URL with %s`, - getBidderConfig('winTrackingUrl') - ); - - return null; - } -} - -/** - * COMMON - */ - -const VAST_REGEXP = /VAST\s+version/; - -function getMediaType(adm) { - const videoRegex = new RegExp(VAST_REGEXP); - - if (videoRegex.test(adm)) { - return VIDEO; - } - - const markup = safeJSONparse(adm.replace(/\\/g, '')); - - if (markup && isPlainObject(markup.native)) { - return NATIVE; - } - - return BANNER; -} - -function safeJSONparse(...args) { - try { - return JSON.parse(...args); - } catch (_) { - return undefined; - } -} - -function isNonEmptyStr(value) { - return isStr(value) && !isEmptyStr(value); -} - -function findBidRequest(bidderRequest, bid) { - return find(bidderRequest.imp, (imp) => imp.id === bid.impid); -} - -function getBidderConfig(property) { - if (!property) { - return common.getConfig(`${BIDDER_CODE}`); - } - - return common.getConfig(`${BIDDER_CODE}.${property}`); -} - -const getUrlBase = function (url) { - return url.split('?')[0]; -}; - -const getUrlQuery = function (url) { - const query = url.split('?')[1]; - - if (!query) { - return; - } - - return '?' + query.split('#')[0]; -}; - -const getUrlHash = function (url) { - const hash = url.split('#')[1]; - - if (!hash) { - return; - } - - return '#' + hash; -}; - -const getUrlBuilder = function (url) { - const hash = getUrlHash(url); - const base = getUrlBase(url); - const query = getUrlQuery(url); - const pairs = []; - - function set(key, value) { - pairs.push([key, value]); - - return { - set, - toString, - }; - } - - function toString() { - if (!pairs.length) { - return url; - } - - const queryString = pairs - .map(function (pair) { - return pair.join('='); - }) - .join('&'); - - if (!query) { - return base + '?' + queryString + (hash || ''); - } - - return base + query + '&' + queryString + (hash || ''); - } - - return { - set, - toString, - }; -}; - -export const common = { - generateUUID: function () { - return generateUUID(); - }, - getConfig: function (property) { - return config.getConfig(property); - }, - getWindowDimensions: function () { - return { - innerWidth: window.innerWidth, - innerHeight: window.innerHeight, - }; - }, -}; diff --git a/modules/adbookpspBidAdapter.md b/modules/adbookpspBidAdapter.md deleted file mode 100644 index e258b1fd7c3..00000000000 --- a/modules/adbookpspBidAdapter.md +++ /dev/null @@ -1,191 +0,0 @@ -### Overview - -``` -Module Name: AdbookPSP Bid Adapter -Module Type: Bidder Adapter -Maintainer: hbsupport@fattail.com -``` - -### Description - -Prebid.JS adapter that connects to the AdbookPSP demand sources. - -*NOTE*: The AdBookPSP Bidder Adapter requires setup and approval before use. The adapter uses custom targeting keys that require a dedicated Google Ad Manager setup to work. Please reach out to your AdbookPSP representative for more details. - -### Bidder parameters - -Each adUnit with `adbookpsp` adapter has to have either `placementId` or `orgId` set. - -```js -var adUnits = [ - { - bids: [ - { - bidder: 'adbookpsp', - params: { - placementId: 'example-placement-id', - orgId: 'example-org-id', - }, - }, - ], - }, -]; -``` - -Alternatively, `orgId` can be set globally while configuring prebid.js: - -```js -pbjs.setConfig({ - adbookpsp: { - orgId: 'example-org-id', - }, -}); -``` - -*NOTE*: adUnit orgId will take precedence over the globally set orgId. - -#### Banner parameters - -Required: - -- sizes - -Example configuration: - -```js -var adUnits = [ - { - code: 'div-1', - mediaTypes: { - banner: { - sizes: [[300, 250]], - }, - } - }, -]; -``` - -#### Video parameters - -Required: - -- context -- mimes -- playerSize - -Additionaly, all `Video` object parameters described in chapter `3.2.7` of the [OpenRTB 2.5 specification](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf) can be passed as bidder params. - -Example configuration: - -```js -var adUnits = [ - { - code: 'div-1', - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4', 'video/x-flv'], - playerSize: [400, 300], - protocols: [2, 3], - }, - }, - bids: [ - { - bidder: 'adbookpsp', - params: { - placementId: 'example-placement-id', - video: { - placement: 2, - }, - }, - }, - ], - }, -]; -``` - -*NOTE*: Supporting outstream video requires the publisher to set up a renderer as described [in the Prebid docs](https://docs.prebid.org/dev-docs/show-outstream-video-ads.html). - -#### Testing params - -To test the adapter, either `placementId: 'example-placement-id'` or `orgId: 'example-org-id'` can be used. - -*NOTE*: If any adUnit uses the testing params, all adUnits will receive testing responses. - -Example adUnit configuration: - -```js -var adUnits = [ - { - code: 'div-1', - mediaTypes: { - banner: { - sizes: [[300, 250]], - }, - }, - bids: [ - { - bidder: 'adbookpsp', - params: { - placementId: 'example-placement-id', - }, - }, - ], - }, -]; -``` - -Example google publisher tag configuration: - -```js -googletag - .defineSlot('/22094606581/example-adbookPSP', sizes, 'div-1') - .addService(googletag.pubads()); -``` - -### Configuration - -Setting of the `orgId` can be done in the `pbjs.setConfig()` call. If this is the case, both `orgId` and `placementId` become optional. Remember to only call `pbjs.setConfig()` once as each call overwrites anything set in previous calls. - -Enabling iframe based user syncs is also encouraged. - -```javascript -pbjs.setConfig({ - adbookpsp: { - orgId: 'example-org-id', - winTrackingEnabled: true, - }, - userSync: { - filterSettings: { - iframe: { - bidders: '*', - filter: 'include', - }, - }, - }, -}); -``` - -### Privacy - -GDPR and US Privacy are both supported by default. - -#### Event tracking - -This adapter tracks win events for it’s bids. This functionality can be disabled by adding `winTrackingEnabled: false` to the adapter configuration: - -```js -pbjs.setConfig({ - adbookpsp: { - winTrackingEnabled: false, - }, -}); -``` - -#### COPPA support - -COPPA support can be enabled for all the visitors by changing the config value: - -```js -config.setConfig({ coppa: true }); -``` diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index b78737722bd..fd59ba74944 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -144,6 +144,7 @@ export const spec = { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js deleted file mode 100644 index d6e1547cce8..00000000000 --- a/modules/adomikAnalyticsAdapter.js +++ /dev/null @@ -1,262 +0,0 @@ -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import {EVENTS} from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import {logInfo} from '../src/utils.js'; -import {find, findIndex} from '../src/polyfill.js'; - -// Events used in adomik analytics adapter. -const auctionInit = EVENTS.AUCTION_INIT; -const auctionEnd = EVENTS.AUCTION_END; -const bidRequested = EVENTS.BID_REQUESTED; -const bidResponse = EVENTS.BID_RESPONSE; -const bidWon = EVENTS.BID_WON; -const bidTimeout = EVENTS.BID_TIMEOUT; -const ua = navigator.userAgent; - -var _sampled = true; - -let adomikAdapter = Object.assign(adapter({}), - { - // Track every event needed - track({ eventType, args }) { - switch (eventType) { - case auctionInit: - adomikAdapter.initializeBucketEvents() - adomikAdapter.currentContext.id = args.auctionId - break; - - case bidTimeout: - adomikAdapter.currentContext.timeouted = true; - break; - - case bidResponse: - adomikAdapter.saveBidResponse(args); - break; - - case bidWon: - args.id = args.adId; - args.placementCode = args.adUnitCode; - adomikAdapter.sendWonEvent(args); - break; - - case bidRequested: - args.bids.forEach(function(bid) { - adomikAdapter.bucketEvents.push({ - type: 'request', - event: { - bidder: bid.bidder.toUpperCase(), - placementCode: bid.adUnitCode - } - }); - }); - break; - - case auctionEnd: - if (adomikAdapter.bucketEvents.length > 0) { - adomikAdapter.sendTypedEvent(); - } - break; - } - } - } -); - -adomikAdapter.initializeBucketEvents = function() { - adomikAdapter.bucketEvents = []; -} - -adomikAdapter.saveBidResponse = function(args) { - let responseSaved = adomikAdapter.bucketEvents.find((bucketEvent) => - bucketEvent.type == 'response' && bucketEvent.event.id == args.id - ); - if (responseSaved) { return true; } - adomikAdapter.bucketEvents.push({ - type: 'response', - event: adomikAdapter.buildBidResponse(args) - }); -} - -adomikAdapter.maxPartLength = function () { - return (ua.includes(' MSIE ')) ? 1600 : 60000; -}; - -adomikAdapter.sendTypedEvent = function() { - let [testId, testValue] = adomikAdapter.getKeyValues(); - const groupedTypedEvents = adomikAdapter.buildTypedEvents(); - - const bulkEvents = { - testId: testId, - testValue: testValue, - uid: adomikAdapter.currentContext.uid, - ahbaid: adomikAdapter.currentContext.id, - hostname: window.location.hostname, - sampling: adomikAdapter.currentContext.sampling, - eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { - let sizes = []; - const eventKeys = ['request', 'response', 'winner']; - let events = {}; - - eventKeys.forEach((eventKey) => { - events[`${eventKey}s`] = []; - if (typedEventsByType[eventKey] !== undefined) { - typedEventsByType[eventKey].forEach((typedEvent) => { - if (typedEvent.event.size !== undefined) { - const size = adomikAdapter.sizeUtils.handleSize(sizes, typedEvent.event.size); - if (size !== null) { - sizes = [...sizes, size]; - } - } - events[`${eventKey}s`] = [...events[`${eventKey}s`], typedEvent.event]; - }); - } - }); - - return { - placementCode: typedEventsByType.placementCode, - sizes, - events - }; - }) - }; - - const stringBulkEvents = JSON.stringify(bulkEvents) - logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents); - - const encodedBuf = window.btoa(stringBulkEvents); - - const encodedUri = encodeURIComponent(encodedBuf); - const maxLength = adomikAdapter.maxPartLength(); - const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g')); - - splittedUrl.forEach((split, i) => { - const partUrl = `${split}&id=${adomikAdapter.currentContext.id}&part=${i}&on=${splittedUrl.length - 1}`; - const img = new Image(1, 1); - img.src = 'https://' + adomikAdapter.currentContext.url + '/?q=' + partUrl; - }) -}; - -adomikAdapter.sendWonEvent = function (wonEvent) { - let [testId, testValue] = adomikAdapter.getKeyValues(); - let keyValues = { testId: testId, testValue: testValue }; - let samplingInfo = { sampling: adomikAdapter.currentContext.sampling }; - wonEvent = { ...adomikAdapter.buildBidResponse(wonEvent), ...keyValues, ...samplingInfo }; - - const stringWonEvent = JSON.stringify(wonEvent); - logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent); - - const encodedBuf = window.btoa(stringWonEvent); - const encodedUri = encodeURIComponent(encodedBuf); - const img = new Image(1, 1); - img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true`; -} - -adomikAdapter.buildBidResponse = function (bid) { - return { - bidder: bid.bidderCode.toUpperCase(), - placementCode: bid.adUnitCode, - id: bid.adId, - status: (bid.statusMessage === 'Bid available') ? 'VALID' : 'EMPTY_OR_ERROR', - cpm: parseFloat(bid.cpm), - size: { - width: Number(bid.width), - height: Number(bid.height) - }, - timeToRespond: bid.timeToRespond, - afterTimeout: adomikAdapter.currentContext.timeouted - }; -} - -adomikAdapter.sizeUtils = { - sizeAlreadyExists: (sizes, typedEventSize) => { - return find(sizes, (size) => size.height === typedEventSize.height && size.width === typedEventSize.width); - }, - formatSize: (typedEventSize) => { - return { - width: Number(typedEventSize.width), - height: Number(typedEventSize.height) - }; - }, - handleSize: (sizes, typedEventSize) => { - let formattedSize = null; - if (adomikAdapter.sizeUtils.sizeAlreadyExists(sizes, typedEventSize) === undefined) { - formattedSize = adomikAdapter.sizeUtils.formatSize(typedEventSize); - } - return formattedSize; - } -}; - -adomikAdapter.buildTypedEvents = function () { - const groupedTypedEvents = []; - adomikAdapter.bucketEvents.forEach(function(typedEvent, i) { - const [placementCode, type] = [typedEvent.event.placementCode, typedEvent.type]; - let existTypedEvent = findIndex(groupedTypedEvents, (groupedTypedEvent) => groupedTypedEvent.placementCode === placementCode); - - if (existTypedEvent === -1) { - groupedTypedEvents.push({ - placementCode: placementCode, - [type]: [typedEvent] - }); - existTypedEvent = groupedTypedEvents.length - 1; - } - - if (groupedTypedEvents[existTypedEvent][type]) { - groupedTypedEvents[existTypedEvent][type] = [...groupedTypedEvents[existTypedEvent][type], typedEvent]; - } else { - groupedTypedEvents[existTypedEvent][type] = [typedEvent]; - } - }); - - return groupedTypedEvents; -} - -adomikAdapter.getKeyValues = function () { - let preventTest = sessionStorage.getItem(window.location.hostname + '_NoAdomikTest') - let inScope = sessionStorage.getItem(window.location.hostname + '_AdomikTestInScope') - let keyValues = JSON.parse(sessionStorage.getItem(window.location.hostname + '_AdomikTest')) - let testId; - let testValue; - if (typeof (keyValues) === 'object' && keyValues != undefined && !preventTest && inScope) { - testId = keyValues.testId - testValue = keyValues.testOptionLabel - } - return [testId, testValue] -} - -adomikAdapter.enable = function(options) { - adomikAdapter.currentContext = { - uid: options.id, - url: options.url, - id: '', - timeouted: false, - sampling: options.sampling - } - logInfo('Adomik Analytics enabled with config', options); - adomikAdapter.adapterEnableAnalytics(options); -}; - -adomikAdapter.checkOptions = function(options) { - if (typeof options !== 'undefined') { - if (options.id && options.url) { adomikAdapter.enable(options); } else { logInfo('Adomik Analytics disabled because id and/or url is missing from config', options); } - } else { logInfo('Adomik Analytics disabled because config is missing'); } -}; - -adomikAdapter.checkSampling = function(options) { - _sampled = typeof options === 'undefined' || - typeof options.sampling === 'undefined' || - (options.sampling > 0 && Math.random() < parseFloat(options.sampling)); - if (_sampled) { adomikAdapter.checkOptions(options) } else { logInfo('Adomik Analytics ignored for sampling', options.sampling); } -}; - -adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; - -adomikAdapter.enableAnalytics = function ({ provider, options }) { - logInfo('Adomik Analytics enableAnalytics', provider); - adomikAdapter.checkSampling(options); -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: adomikAdapter, - code: 'adomik' -}); - -export default adomikAdapter; diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index a95b9ed5652..afdc49a71f4 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -4,7 +4,6 @@ import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -138,11 +137,6 @@ export const spec = { return bids; }, - transformBidParams(params) { - return convertTypes({ - 'aid': 'number', - }, params); - } }; function parseRTBResponse(serverResponse, adapterRequest) { diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 389986eb586..a6186d6129f 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -43,6 +43,7 @@ const VIDEO_CUSTOM_PARAMS = { 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER }; diff --git a/modules/anPspParamsConverter.js b/modules/anPspParamsConverter.js new file mode 100644 index 00000000000..27b90168476 --- /dev/null +++ b/modules/anPspParamsConverter.js @@ -0,0 +1,128 @@ +/* +- register a hook function on the makeBidRequests hook (after the main function ran) + +- this hook function will: +1. verify s2sconfig is defined and we (or our aliases) are included to the config +2. filter bidRequests that match to our bidderName or any registered aliases +3. for each request, read the bidderRequests.bids[].params to modify the keys/values + a. in particular change the keywords structure, apply underscore casing for keys, adjust use_payment_rule name, and convert certain values' types + b. will import some functions from the anKeywords library, but ideally should be kept separate to avoid including this code when it's not needed (strict client-side setups) and avoid the rest of the appnexus adapter's need for inclusion for those strictly server-side setups. +*/ + +// import { CONSTANTS } from '../src/cons tants.js'; +import {isArray, isPlainObject, isStr} from '../src/utils.js'; +import {getHook} from '../src/hook.js'; +import {config} from '../src/config.js'; +import {convertCamelToUnderscore, appnexusAliases} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import adapterManager from '../src/adapterManager.js'; + +// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' +function convertKeywordsToString(keywords) { + let result = ''; + Object.keys(keywords).forEach(key => { + // if 'text' or '' + if (isStr(keywords[key])) { + if (keywords[key] !== '') { + result += `${key}=${keywords[key]},` + } else { + result += `${key},`; + } + } else if (isArray(keywords[key])) { + if (keywords[key][0] === '') { + result += `${key},` + } else { + keywords[key].forEach(val => { + result += `${key}=${val},` + }); + } + } + }); + + // remove last trailing comma + result = result.substring(0, result.length - 1); + return result; +} + +function digForAppNexusBidder(s2sConfig) { + let result = false; + // check for plain setup + if (s2sConfig?.bidders?.includes('appnexus')) result = true; + + // registered aliases + const aliasList = appnexusAliases.map(aliasObj => (aliasObj.code)); + if (!result && s2sConfig?.bidders?.filter(s2sBidder => aliasList.includes(s2sBidder)).length > 0) result = true; + + // pbjs.aliasBidder + if (!result) { + result = !!(s2sConfig?.bidders?.find(bidder => (adapterManager.resolveAlias(bidder) === 'appnexus'))); + } + + return result; +} + +// need a separate check b/c we're checking a specific bidRequest to see if we modify it, not just that we have a server-side bidder somewhere in prebid.js +// function isThisOurBidderInDisguise(tarBidder, s2sConfig) { +// if (tarBidder === 'appnexus') return true; + +// if (isPlainObject(s2sConfig?.extPrebid?.aliases) && !!(Object.entries(s2sConfig?.extPrebid?.aliases).find((pair) => (pair[0] === tarBidder && pair[1] === 'appnexus')))) return true; + +// if (appnexusAliases.map(aliasObj => (aliasObj.code)).includes(tarBidder)) return true; + +// if (adapterManager.resolveAlias(tarBidder) === 'appnexus') return true; + +// return false; +// } + +export function convertAnParams(next, bidderRequests) { + // check s2sconfig + const s2sConfig = config.getConfig('s2sConfig'); + let proceed = false; + + if (isPlainObject(s2sConfig)) { + proceed = digForAppNexusBidder(s2sConfig); + } else if (isArray(s2sConfig)) { + s2sConfig.forEach(s2sCfg => { + proceed = digForAppNexusBidder(s2sCfg); + }); + } + + if (proceed) { + bidderRequests + .flatMap(br => br.bids) + .filter(bid => bid.src === 's2s' && adapterManager.resolveAlias(bid.bidder) === 'appnexus') + .forEach((bid) => { + transformBidParams(bid); + }); + } + + next(bidderRequests); +} + +function transformBidParams(bid) { + let params = bid.params; + if (params) { + params = convertTypes({ + 'member': 'string', + 'invCode': 'string', + 'placementId': 'number', + 'keywords': convertKeywordsToString, + 'publisherId': 'number' + }, params); + + Object.keys(params).forEach(paramKey => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + + params.use_pmt_rule = (typeof params.use_payment_rule === 'boolean') ? params.use_payment_rule : false; + if (params.use_payment_rule) { + delete params.use_payment_rule; + } + } +} + +getHook('makeBidRequests').after(convertAnParams, 9); diff --git a/modules/anPspParamsConverter.md b/modules/anPspParamsConverter.md new file mode 100644 index 00000000000..f341b0a5976 --- /dev/null +++ b/modules/anPspParamsConverter.md @@ -0,0 +1,10 @@ +## Quick Summary + +This module is a temporary measure for publishers running Prebid.js 9.0+ and using the AppNexus PSP endpoint through their Prebid.js setup. Please ensure to include this module in your builds of Prebid.js 9.0+, otherwise requests to PSP may not complete successfully. + +## Module's purpose + +This module replicates certain functionality that was previously stored in the appnexusBidAdapter.js file within a function named transformBidParams. + +This transformBidParams was a standard function in all adapters, which helped to change/modify the params and their values to a format that matched the bidder's request structure on the server-side endpoint. In Prebid.js 9.0, this standard function was removed in all adapter files, so that the whole client-side file (eg appnexusBidAdapter.js) wouldn't have to be included in a prebid.js build file that was meant for server-side bidders. + diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index b0c91a14a46..3d851c96a1f 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -31,10 +31,9 @@ import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping import { convertKeywordStringToANMap, getANKewyordParamFromMaps, - getANKeywordParam, - transformBidderParamKeywords + getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; +import {convertCamelToUnderscore, fill, appnexusAliases} from '../libraries/appnexusUtils/anUtils.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; @@ -108,21 +107,7 @@ const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: [ - { code: 'appnexusAst', gvlid: 32 }, - { code: 'emxdigital', gvlid: 183 }, - { code: 'emetriq', gvlid: 213 }, - { code: 'pagescience', gvlid: 32 }, - { code: 'gourmetads', gvlid: 32 }, - { code: 'matomy', gvlid: 32 }, - { code: 'featureforward', gvlid: 32 }, - { code: 'oftmedia', gvlid: 32 }, - { code: 'adasta', gvlid: 32 }, - { code: 'beintoo', gvlid: 618 }, - { code: 'projectagora', gvlid: 1032 }, - { code: 'uol', gvlid: 32 }, - { code: 'adzymic', gvlid: 723 }, - ], + aliases: appnexusAliases, supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -449,51 +434,6 @@ export const spec = { url: 'https://acdn.adnxs.com/dmp/async_usersync.html' }]; } - }, - - transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { - let conversionFn = transformBidderParamKeywords; - if (isOpenRtb === true) { - let s2sEndpointUrl = null; - let s2sConfig = config.getConfig('s2sConfig'); - - if (isPlainObject(s2sConfig)) { - s2sEndpointUrl = deepAccess(s2sConfig, 'endpoint.p1Consent'); - } else if (isArray(s2sConfig)) { - s2sConfig.forEach(s2sCfg => { - if (includes(s2sCfg.bidders, adUnit.bids[0].bidder)) { - s2sEndpointUrl = deepAccess(s2sCfg, 'endpoint.p1Consent'); - } - }); - } - - if (s2sEndpointUrl && s2sEndpointUrl.match('/openrtb2/prebid')) { - conversionFn = convertKeywordsToString; - } - } - - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': conversionFn, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - - params.use_pmt_rule = (typeof params.use_payment_rule === 'boolean') ? params.use_payment_rule : false; - if (params.use_payment_rule) { delete params.use_payment_rule; } - } - - return params; } }; @@ -1256,31 +1196,4 @@ function getBidFloor(bid) { return null; } -// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' -function convertKeywordsToString(keywords) { - let result = ''; - Object.keys(keywords).forEach(key => { - // if 'text' or '' - if (isStr(keywords[key])) { - if (keywords[key] !== '') { - result += `${key}=${keywords[key]},` - } else { - result += `${key},`; - } - } else if (isArray(keywords[key])) { - if (keywords[key][0] === '') { - result += `${key},` - } else { - keywords[key].forEach(val => { - result += `${key}=${val},` - }); - } - } - }); - - // remove last trailing comma - result = result.substring(0, result.length - 1); - return result; -} - registerBidder(spec); diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js index 97772b65e45..67557aed10c 100644 --- a/modules/appushBidAdapter.js +++ b/modules/appushBidAdapter.js @@ -57,6 +57,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 2856fb02087..37e2bde44c1 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -292,7 +292,6 @@ function getConsentStringFromPrebid(gdprConsentConfig) { return null; } - let isIab = config.getConfig('consentManagement.cmpApi') != 'static'; let vendorConsents = ( gdprConsentConfig.vendorData.vendorConsents || (gdprConsentConfig.vendorData.vendor || {}).consents || @@ -300,7 +299,7 @@ function getConsentStringFromPrebid(gdprConsentConfig) { ); let isConsentGiven = !!vendorConsents[CONSTANTS.GVLID.toString(10)]; - return isIab && isConsentGiven ? consentString : null; + return isConsentGiven ? consentString : null; } function getIabConsentString(bidderRequest) { diff --git a/modules/axisBidAdapter.js b/modules/axisBidAdapter.js index 8d7f2dd04fd..da167fae062 100644 --- a/modules/axisBidAdapter.js +++ b/modules/axisBidAdapter.js @@ -53,6 +53,7 @@ function getPlacementReqData(bid) { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.placement = mediaTypes[VIDEO].placement; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index bbcd972470c..d3c7d185058 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -52,6 +52,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/bizzclickBidAdapter.js b/modules/blastoBidAdapter.js similarity index 90% rename from modules/bizzclickBidAdapter.js rename to modules/blastoBidAdapter.js index d2eba3f0f81..0e97c294049 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/blastoBidAdapter.js @@ -3,11 +3,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -const BIDDER_CODE = 'bizzclick'; +const BIDDER_CODE = 'blasto'; const SOURCE_ID_MACRO = '[sourceid]'; const ACCOUNT_ID_MACRO = '[accountid]'; const HOST_MACRO = '[host]'; -const URL = `https://${HOST_MACRO}.bizzclick.com/bid?rtb_seat_id=${SOURCE_ID_MACRO}&secret_key=${ACCOUNT_ID_MACRO}&integration_type=prebidjs`; +const URL = `https://${HOST_MACRO}.blasto.ai/bid?rtb_seat_id=${SOURCE_ID_MACRO}&secret_key=${ACCOUNT_ID_MACRO}&integration_type=prebidjs`; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_HOST = 'us-e-node1'; @@ -53,7 +53,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { if (validBidRequests && validBidRequests.length === 0) return []; const { sourceId, accountId } = validBidRequests[0].params; - const host = validBidRequests[0].params.host || 'USE'; + const host = validBidRequests[0].params.host; const endpointURL = URL.replace(HOST_MACRO, host || DEFAULT_HOST) .replace(ACCOUNT_ID_MACRO, accountId) .replace(SOURCE_ID_MACRO, sourceId); diff --git a/modules/bizzclickBidAdapter.md b/modules/blastoBidAdapter.md similarity index 88% rename from modules/bizzclickBidAdapter.md rename to modules/blastoBidAdapter.md index ad342f34e07..60ebad14764 100644 --- a/modules/bizzclickBidAdapter.md +++ b/modules/blastoBidAdapter.md @@ -1,14 +1,14 @@ # Overview ``` -Module Name: BizzClick SSP Bidder Adapter +Module Name: Blasto SSP Bidder Adapter Module Type: Bidder Adapter -Maintainer: support@bizzclick.com +Maintainer: support@blasto.ai ``` # Description -Module that connects to BizzClick SSP demand sources +Module that connects to Blasto SSP demand sources # Test Parameters @@ -26,7 +26,7 @@ const adUnits = [ }, bids: [ { - bidder: "bizzclick", + bidder: "blasto", params: { placementId: "hash", accountId: "accountId", @@ -68,7 +68,7 @@ const adUnits = [ }, bids: [ { - bidder: "bizzclick", + bidder: "blasto", params: { placementId: "hash", accountId: "accountId", @@ -96,7 +96,7 @@ const adUnits = [ }, bids: [ { - bidder: "bizzclick", + bidder: "blasto", params: { placementId: "hash", accountId: "accountId", diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 37c99878d68..0fb56949539 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,5 +1,3 @@ -// eslint-disable-next-line prebid/validate-imports -// eslint-disable-next-line prebid/validate-imports import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' import { _each, deepAccess, deepSetValue, getWindowSelf, getWindowTop } from '../src/utils.js' diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js deleted file mode 100644 index 0718f512cdd..00000000000 --- a/modules/bluebillywigBidAdapter.js +++ /dev/null @@ -1,374 +0,0 @@ -import {deepAccess, deepClone, deepSetValue, logError, logWarn} from '../src/utils.js'; -import {find} from '../src/polyfill.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {Renderer} from '../src/Renderer.js'; - -const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); - -// Blue Billywig Constants -const BB_CONSTANTS = { - BIDDER_CODE: 'bluebillywig', - AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', - SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', - RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', - DEFAULT_TIMEOUT: 5000, - DEFAULT_TTL: 300, - DEFAULT_WIDTH: 768, - DEFAULT_HEIGHT: 432, - DEFAULT_NET_REVENUE: true, - VIDEO_PARAMS: ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', - 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', - 'api', 'companiontype', 'ext'] -}; - -// Aliasing -const getConfig = config.getConfig; - -// Helper Functions -const BB_HELPERS = { - addSiteAppDevice: function(request, pageUrl) { - if (typeof getConfig('app') === 'object') request.app = getConfig('app'); - else { - request.site = {}; - if (typeof getConfig('site') === 'object') request.site = getConfig('site'); - if (pageUrl) request.site.page = pageUrl; - } - - if (typeof getConfig('device') === 'object') request.device = getConfig('device'); - if (!request.device) request.device = {}; - if (!request.device.w) request.device.w = window.innerWidth; - if (!request.device.h) request.device.h = window.innerHeight; - }, - addSchain: function(request, validBidRequests) { - const schain = deepAccess(validBidRequests, '0.schain'); - if (schain) request.source.ext = { schain: schain }; - }, - addCurrency: function(request) { - const adServerCur = getConfig('currency.adServerCurrency'); - if (adServerCur && typeof adServerCur === 'string') request.cur = [adServerCur]; - else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; - }, - addUserIds: function(request, validBidRequests) { - const eids = deepAccess(validBidRequests, '0.userIdAsEids'); - - if (eids != null && eids.length) { - deepSetValue(request, 'user.ext.eids', eids); - } - }, - substituteUrl: function (url, publication, renderer) { - return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer); - }, - getAuctionUrl: function(publication) { - return BB_HELPERS.substituteUrl(BB_CONSTANTS.AUCTION_URL, publication); - }, - getSyncUrl: function(publication) { - return BB_HELPERS.substituteUrl(BB_CONSTANTS.SYNC_URL, publication); - }, - getRendererUrl: function(publication, renderer) { - return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer); - }, - transformVideoParams: function(videoParams, videoParamsExt) { - videoParams = deepClone(videoParams); - - let playerSize = videoParams.playerSize || [BB_CONSTANTS.DEFAULT_WIDTH, BB_CONSTANTS.DEFAULT_HEIGHT]; - if (Array.isArray(playerSize[0])) playerSize = playerSize[0]; - - videoParams.w = playerSize[0]; - videoParams.h = playerSize[1]; - videoParams.placement = 3; - - if (videoParamsExt) videoParams = Object.assign(videoParams, videoParamsExt); - - const videoParamsProperties = Object.keys(videoParams); - - videoParamsProperties.forEach(property => { - if (BB_CONSTANTS.VIDEO_PARAMS.indexOf(property) === -1) delete videoParams[property]; - }); - - return videoParams; - }, - transformRTBToPrebidProps: function(bid, serverResponse) { - const bidObject = { - cpm: bid.price, - currency: serverResponse.cur, - netRevenue: BB_CONSTANTS.DEFAULT_NET_REVENUE, - bidId: bid.impid, - requestId: bid.impid, - creativeId: bid.crid, - mediaType: VIDEO, - width: bid.w || BB_CONSTANTS.DEFAULT_WIDTH, - height: bid.h || BB_CONSTANTS.DEFAULT_HEIGHT, - ttl: BB_CONSTANTS.DEFAULT_TTL - }; - - const extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); - const extPrebidCache = deepAccess(bid, 'ext.prebid.cache'); - - if (extPrebidCache && typeof extPrebidCache.vastXml === 'object' && extPrebidCache.vastXml.cacheId && extPrebidCache.vastXml.url) { - bidObject.videoCacheKey = extPrebidCache.vastXml.cacheId; - bidObject.vastUrl = extPrebidCache.vastXml.url; - } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { - bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; - bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; - } - if (bid.adm) { - bidObject.ad = bid.adm; - bidObject.vastXml = bid.adm; - } - if (!bidObject.vastUrl && bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 - bidObject.vastUrl = bid.nurl; - } - bidObject.meta = bid.meta || {}; - if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - return bidObject; - }, -}; - -// Renderer Functions -const BB_RENDERER = { - bootstrapPlayer: function(bid) { - const config = { - code: bid.adUnitCode, - }; - - if (bid.vastXml) config.vastXml = bid.vastXml; - else if (bid.vastUrl) config.vastUrl = bid.vastUrl; - - if (!bid.vastXml && !bid.vastUrl) { - logWarn(`${BB_CONSTANTS.BIDDER_CODE}: No vastXml or vastUrl on bid, bailing...`); - return; - } - - if (!(window.bluebillywig && window.bluebillywig.renderers)) { - logWarn(`${BB_CONSTANTS.BIDDER_CODE}: renderer code failed to initialize...`); - return; - } - - const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode); - const ele = document.getElementById(bid.adUnitCode); // NB convention - const renderer = find(window.bluebillywig.renderers, r => r._id === rendererId); - - if (renderer) renderer.bootstrap(config, ele, bid.rendererSettings || {}); - else logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); - }, - newRenderer: function(rendererUrl, adUnitCode) { - const renderer = Renderer.install({ - url: rendererUrl, - loaded: false, - adUnitCode - }); - - try { - renderer.setRender(BB_RENDERER.outstreamRender); - } catch (err) { - logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Error tying to setRender on renderer`, err); - } - - return renderer; - }, - outstreamRender: function(bid) { - bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) }); - }, - getRendererId: function(pub, renderer) { - return `${pub}-${renderer}`; // NB convention! - } -}; - -// Spec Functions -// These functions are used to construct the core spec for the adapter -export const spec = { - code: BB_CONSTANTS.BIDDER_CODE, - supportedMediaTypes: [VIDEO], - syncStore: { bidders: [], }, - isBidRequestValid(bid) { - const publicationNameRegex = /^\w+\.?\w+$/; - const rendererRegex = /^[\w+_]+$/; - - if (!bid.params) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no params set on bid. Rejecting bid: `, bid); - return false; - } - - if (!bid.params.hasOwnProperty('publicationName') || typeof bid.params.publicationName !== 'string') { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no publicationName specified in bid params, or it's not a string. Rejecting bid: `, bid); - return false; - } else if (!publicationNameRegex.test(bid.params.publicationName)) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: publicationName must be in format 'publication' or 'publication.environment'. Rejecting bid: `, bid); - return false; - } - - if ((!bid.params.hasOwnProperty('rendererCode') || typeof bid.params.rendererCode !== 'string')) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no rendererCode was specified in bid params. Rejecting bid: `, bid); - return false; - } else if (!rendererRegex.test(bid.params.rendererCode)) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: rendererCode must be alphanumeric, including underscores. Rejecting bid: `, bid); - return false; - } - - if (!bid.params.accountId) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no accountId specified in bid params. Rejecting bid: `, bid); - return false; - } - - if (bid.params.hasOwnProperty('connections')) { - if (!Array.isArray(bid.params.connections)) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); - return false; - } else { - for (let i = 0; i < bid.params.connections.length; i++) { - if (!bid.params.hasOwnProperty(bid.params.connections[i])) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid); - return false; - } - } - } - } else { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no connections specified in bid. Rejecting bid: `, bid); - return false; - } - - if (bid.params.hasOwnProperty('video') && (bid.params.video === null || typeof bid.params.video !== 'object')) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: params.video must be of type object. Rejecting bid: `, bid); - return false; - } - - if (bid.params.hasOwnProperty('rendererSettings') && (bid.params.rendererSettings === null || typeof bid.params.rendererSettings !== 'object')) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: params.rendererSettings must be of type object. Rejecting bid: `, bid); - return false; - } - - if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { - if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); - return false; - } - - if (bid.mediaTypes[VIDEO].context !== 'outstream') { - logError(`${BB_CONSTANTS.BIDDER_CODE}: video.context is invalid, must be "outstream". Rejecting bid: `, bid); - return false; - } - } else { - logError(`${BB_CONSTANTS.BIDDER_CODE}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); - return false; - } - - return true; - }, - buildRequests(validBidRequests, bidderRequest) { - const imps = []; - - validBidRequests.forEach(validBidRequest => { - if (!this.syncStore.publicationName) this.syncStore.publicationName = validBidRequest.params.publicationName; - if (!this.syncStore.accountId) this.syncStore.accountId = validBidRequest.params.accountId; - - const ext = validBidRequest.params.connections.reduce((extBuilder, connection) => { - extBuilder[connection] = validBidRequest.params[connection]; - - if (this.syncStore.bidders.indexOf(connection) === -1) this.syncStore.bidders.push(connection); - - return extBuilder; - }, {}); - - const videoParams = BB_HELPERS.transformVideoParams(deepAccess(validBidRequest, 'mediaTypes.video'), deepAccess(validBidRequest, 'params.video')); - imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: videoParams }); - }); - - const request = { - id: bidderRequest.bidderRequestId, - source: {tid: bidderRequest.ortb2?.source?.tid}, - tmax: Math.min(BB_CONSTANTS.DEFAULT_TIMEOUT, bidderRequest.timeout), - imp: imps, - test: DEV_MODE ? 1 : 0, - ext: { - prebid: { - targeting: { includewinners: true, includebidderkeys: false } - } - } - }; - - // handle privacy settings for GDPR/CCPA/COPPA - if (bidderRequest.gdprConsent) { - let gdprApplies = 0; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - this.syncStore.uspConsent = bidderRequest.uspConsent; - } - - if (getConfig('coppa') == true) deepSetValue(request, 'regs.coppa', 1); - - // Enrich the request with any external data we may have - BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.page); - BB_HELPERS.addSchain(request, validBidRequests); - BB_HELPERS.addCurrency(request); - BB_HELPERS.addUserIds(request, validBidRequests); - - return { - method: 'POST', - url: BB_HELPERS.getAuctionUrl(validBidRequests[0].params.publicationName), - data: JSON.stringify(request), - bidderRequest: bidderRequest - }; - }, - interpretResponse(serverResponse, request) { - serverResponse = serverResponse.body || {}; - - if (!serverResponse.hasOwnProperty('seatbid') || !Array.isArray(serverResponse.seatbid)) { - return []; - } - - const bids = []; - - serverResponse.seatbid.forEach(seatbid => { - if (!seatbid.bid || !Array.isArray(seatbid.bid)) return; - seatbid.bid.forEach(bid => { - bid = BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); - - const bidParams = find(request.bidderRequest.bids, bidderRequestBid => bidderRequestBid.bidId === bid.bidId).params; - bid.publicationName = bidParams.publicationName; - bid.rendererCode = bidParams.rendererCode; - bid.accountId = bidParams.accountId; - bid.rendererSettings = bidParams.rendererSettings; - - const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode); - bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode); - - bids.push(bid); - }); - }); - - return bids; - }, - getUserSyncs(syncOptions, serverResponses, gdpr) { - if (!syncOptions.iframeEnabled) return []; - - const queryString = []; - - if (gdpr.gdprApplies) queryString.push(`gdpr=${gdpr.gdprApplies ? 1 : 0}`); - if (gdpr.gdprApplies && gdpr.consentString) queryString.push(`gdpr_consent=${gdpr.consentString}`); - - if (this.syncStore.uspConsent) queryString.push(`usp_consent=${this.syncStore.uspConsent}`); - - queryString.push(`accountId=${this.syncStore.accountId}`); - queryString.push(`bidders=${btoa(JSON.stringify(this.syncStore.bidders))}`); - queryString.push(`cb=${Date.now()}-${Math.random().toString().replace('.', '')}`); - - if (DEV_MODE) queryString.push('bbpbs_debug=true'); - - // NB syncUrl by default starts with ?pub=$$PUBLICATION - const syncUrl = `${BB_HELPERS.getSyncUrl(this.syncStore.publicationName)}&${queryString.join('&')}`; - - return [{ - type: 'iframe', - url: syncUrl - }]; - } -}; - -registerBidder(spec); diff --git a/modules/bluebillywigBidAdapter.md b/modules/bluebillywigBidAdapter.md deleted file mode 100644 index 7879697baf5..00000000000 --- a/modules/bluebillywigBidAdapter.md +++ /dev/null @@ -1,38 +0,0 @@ -# Overview - -``` -Module Name: Blue Billywig Adapter -Module Type: Bidder Adapter -Maintainer: dev+prebid@bluebillywig.com -``` - -# Description - -Prebid Blue Billywig Bidder Adapter - -# Test Parameters - -``` - const adUnits = [{ - code: 'ad-unit', - sizes: [[[768,432],[640,480],[640,360]]], - mediaTypes: { - video: { - playerSize: [768, 432], - context: 'outstream', - mimes: ['video/mp4'], - protocols: [ 2,3,5,6] - } - }, - bids: [{ - bidder: 'bluebillywig', - params: { - publicationName: "bbprebid", - rendererCode: "renderer", - accountId: 642, - connections: [ 'bluebillywig' ], - bluebillywig: {} - } - }] - }]; -``` diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index c7def383b5e..6d49c814486 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -110,6 +110,7 @@ export const spec = { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js deleted file mode 100644 index 1fa1dac4e95..00000000000 --- a/modules/brightcomBidAdapter.js +++ /dev/null @@ -1,303 +0,0 @@ -import { - _each, - isArray, - getWindowTop, - getUniqueIdentifierStr, - deepSetValue, - logError, - logWarn, - createTrackPixelHtml, - getWindowSelf, - isFn, - isPlainObject, - getBidIdParameter -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'brightcom'; -const URL = 'https://brightcombid.marphezis.com/hb'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - gvlid: 883, - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; - -function buildRequests(bidReqs, bidderRequest) { - try { - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.page; - } - const brightcomImps = []; - const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); - _each(bidReqs, function (bid) { - let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; - bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => isArray(size)); - const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); - - const element = document.getElementById(bid.adUnitCode); - const minSize = _getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, getWindowTop(), minSize) - : 'na'; - const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); - - const imp = { - id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded - } - }, - tagid: String(bid.adUnitCode) - }; - const bidFloor = _getBidFloor(bid); - if (bidFloor) { - imp.bidfloor = bidFloor; - } - brightcomImps.push(imp); - }); - const brightcomBidReq = { - id: getUniqueIdentifierStr(), - imp: brightcomImps, - site: { - domain: bidderRequest?.refererInfo?.domain || '', - page: referrer, - publisher: { - id: publisherId - } - }, - device: { - devicetype: _getDeviceType(), - w: screen.width, - h: screen.height - }, - tmax: bidderRequest?.timeout - }; - - if (bidderRequest && bidderRequest.gdprConsent) { - deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); - deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest && bidderRequest.uspConsent) { - deepSetValue(brightcomBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(brightcomBidReq, 'regs.coppa', 1); - } - - if (bidReqs[0] && bidReqs[0].schain) { - deepSetValue(brightcomBidReq, 'source.ext.schain', bidReqs[0].schain) - } - - if (bidReqs[0] && bidReqs[0].userIdAsEids) { - deepSetValue(brightcomBidReq, 'user.ext.eids', bidReqs[0].userIdAsEids || []) - } - - if (bidReqs[0] && bidReqs[0].userId) { - deepSetValue(brightcomBidReq, 'user.ext.ids', bidReqs[0].userId || []) - } - - return { - method: 'POST', - url: URL, - data: JSON.stringify(brightcomBidReq), - }; - } catch (e) { - logError(e, {bidReqs, bidderRequest}); - } -} - -function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { - return false; - } - - if (typeof bid.params.publisherId === 'undefined') { - return false; - } - - return true; -} - -function interpretResponse(serverResponse) { - if (!serverResponse.body || typeof serverResponse.body != 'object') { - logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); - return []; - } - const {body: {id, seatbid}} = serverResponse; - try { - const brightcomBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(brightcomBid => { - brightcomBidResponses.push({ - requestId: brightcomBid.impid, - cpm: parseFloat(brightcomBid.price), - width: parseInt(brightcomBid.w), - height: parseInt(brightcomBid.h), - creativeId: brightcomBid.crid || brightcomBid.id, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ad: _getAdMarkup(brightcomBid), - ttl: 60, - meta: { - advertiserDomains: brightcomBid && brightcomBid.adomain ? brightcomBid.adomain : [] - } - }); - }); - } - return brightcomBidResponses; - } catch (e) { - logError(e, {id, seatbid}); - } -} - -// Don't do user sync for now -function getUserSyncs(syncOptions, responses, gdprConsent) { - return []; -} - -function _isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function _isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function _getDeviceType() { - return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; -} - -function _getAdMarkup(bid) { - let adm = bid.adm; - if ('nurl' in bid) { - adm += createTrackPixelHtml(bid.nurl); - } - return adm; -} - -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; -} - -function _getViewability(element, topWin, {w, h} = {}) { - return getWindowTop().document.visibilityState === 'visible' - ? _getPercentInView(element, topWin, {w, h}) - : 0; -} - -function _isIframe() { - try { - return getWindowSelf() !== getWindowTop(); - } catch (e) { - return true; - } -} - -function _getMinSize(sizes) { - return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); -} - -function _getBoundingBox(element, {w, h} = {}) { - let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); - - if ((width === 0 || height === 0) && w && h) { - width = w; - height = h; - right = left + w; - bottom = top + h; - } - - return {width, height, left, top, right, bottom}; -} - -function _getIntersectionOfRects(rects) { - const bbox = { - left: rects[0].left, - right: rects[0].right, - top: rects[0].top, - bottom: rects[0].bottom - }; - - for (let i = 1; i < rects.length; ++i) { - bbox.left = Math.max(bbox.left, rects[i].left); - bbox.right = Math.min(bbox.right, rects[i].right); - - if (bbox.left >= bbox.right) { - return null; - } - - bbox.top = Math.max(bbox.top, rects[i].top); - bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); - - if (bbox.top >= bbox.bottom) { - return null; - } - } - - bbox.width = bbox.right - bbox.left; - bbox.height = bbox.bottom - bbox.top; - - return bbox; -} - -function _getPercentInView(element, topWin, {w, h} = {}) { - const elementBoundingBox = _getBoundingBox(element, {w, h}); - - // Obtain the intersection of the element and the viewport - const elementInViewBoundingBox = _getIntersectionOfRects([{ - left: 0, - top: 0, - right: topWin.innerWidth, - bottom: topWin.innerHeight - }, elementBoundingBox]); - - let elementInViewArea, elementTotalArea; - - if (elementInViewBoundingBox !== null) { - // Some or all of the element is in view - elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; - elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; - - return ((elementInViewArea / elementTotalArea) * 100); - } - - // No overlap between element and the viewport; therefore, the element - // lies completely out of view - return 0; -} - -function _getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.bidFloor ? bid.params.bidFloor : null; - } - - let floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -registerBidder(spec); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md deleted file mode 100644 index 9f9aa0e5dd7..00000000000 --- a/modules/brightcomBidAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview - -``` -Module Name: Brightcom Bid Adapter -Module Type: Bidder Adapter -Maintainer: vladislavy@brightcom.com -``` - -# Description - -Brightcom's adapter integration to the Prebid library. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-leaderboard', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bids: [{ - bidder: 'brightcom', - params: { - publisherId: 2141020, - bidFloor: 0.01 - } - }] - }, { - code: 'test-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'brightcom', - params: { - publisherId: 2141020 - } - }] - } -] -``` diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js deleted file mode 100644 index 4750881da40..00000000000 --- a/modules/brightcomSSPBidAdapter.js +++ /dev/null @@ -1,321 +0,0 @@ -import { - isArray, - getWindowTop, - getUniqueIdentifierStr, - deepSetValue, - logError, - logWarn, - createTrackPixelHtml, - getWindowSelf, - isFn, - isPlainObject, getBidIdParameter, -} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {ajax} from '../src/ajax.js'; - -const BIDDER_CODE = 'bcmssp'; -const URL = 'https://rt.marphezis.com/hb'; -const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' - -export const spec = { - code: BIDDER_CODE, - gvlid: 883, - supportedMediaTypes: [BANNER], - isBidRequestValid, - buildRequests, - interpretResponse, - onBidderError, - onTimeout, - onBidWon, - getUserSyncs, -}; - -function buildRequests(bidReqs, bidderRequest) { - try { - const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; - bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => isArray(size)); - const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); - - const element = document.getElementById(bid.adUnitCode); - const minSize = _getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; - const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); - - const imp = { - id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded - } - }, - tagid: String(bid.adUnitCode) - }; - - const bidFloor = _getBidFloor(bid); - - if (bidFloor) { - imp.bidfloor = bidFloor; - } - - return imp; - }) - - const referrer = bidderRequest?.refererInfo?.page || ''; - const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); - - const payload = { - id: getUniqueIdentifierStr(), - imp: impressions, - site: { - domain: bidderRequest?.refererInfo?.domain || '', - page: referrer, - publisher: { - id: publisherId - } - }, - device: { - devicetype: _getDeviceType(), - w: screen.width, - h: screen.height - }, - tmax: bidderRequest?.timeout - }; - - if (bidderRequest?.gdprConsent) { - deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); - deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest?.uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(payload, 'regs.coppa', 1); - } - - if (bidReqs?.[0]?.schain) { - deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) - } - - if (bidReqs?.[0]?.userIdAsEids) { - deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) - } - - if (bidReqs?.[0].userId) { - deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) - } - - return { - method: 'POST', - url: URL, - data: JSON.stringify(payload), - }; - } catch (e) { - logError(e, {bidReqs, bidderRequest}); - } -} - -function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { - return false; - } - - return true; -} - -function interpretResponse(serverResponse) { - let response = []; - if (!serverResponse.body || typeof serverResponse.body != 'object') { - logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); - return response; - } - - const {body: {id, seatbid}} = serverResponse; - - try { - if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { - response = seatbid[0].bid.map(bid => { - return { - requestId: bid.impid, - cpm: parseFloat(bid.price), - width: parseInt(bid.w), - height: parseInt(bid.h), - creativeId: bid.crid || bid.id, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ad: _getAdMarkup(bid), - ttl: 60, - meta: { - advertiserDomains: bid?.adomain || [] - } - }; - }); - } - } catch (e) { - logError(e, {id, seatbid}); - } - - return response; -} - -// Don't do user sync for now -function getUserSyncs(syncOptions, responses, gdprConsent) { - return []; -} - -function onTimeout(timeoutData) { - if (timeoutData === null) { - return; - } - - _trackEvent('timeout', timeoutData); -} - -function onBidderError(errorData) { - if (errorData === null || !errorData.bidderRequest) { - return; - } - - _trackEvent('error', errorData.bidderRequest) -} - -function onBidWon(bid) { - if (bid === null) { - return; - } - - _trackEvent('bidwon', bid) -} - -function _trackEvent(endpoint, data) { - ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { - method: 'POST', - withCredentials: false - }); -} - -function _isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function _isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function _getDeviceType() { - return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; -} - -function _getAdMarkup(bid) { - let adm = bid.adm; - if ('nurl' in bid) { - adm += createTrackPixelHtml(bid.nurl); - } - return adm; -} - -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; -} - -function _getViewability(element, topWin, {w, h} = {}) { - return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, {w, h}) : 0; -} - -function _isIframe() { - try { - return getWindowSelf() !== getWindowTop(); - } catch (e) { - return true; - } -} - -function _getMinSize(sizes) { - return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); -} - -function _getBoundingBox(element, {w, h} = {}) { - let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); - - if ((width === 0 || height === 0) && w && h) { - width = w; - height = h; - right = left + w; - bottom = top + h; - } - - return {width, height, left, top, right, bottom}; -} - -function _getIntersectionOfRects(rects) { - const bbox = { - left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom - }; - - for (let i = 1; i < rects.length; ++i) { - bbox.left = Math.max(bbox.left, rects[i].left); - bbox.right = Math.min(bbox.right, rects[i].right); - - if (bbox.left >= bbox.right) { - return null; - } - - bbox.top = Math.max(bbox.top, rects[i].top); - bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); - - if (bbox.top >= bbox.bottom) { - return null; - } - } - - bbox.width = bbox.right - bbox.left; - bbox.height = bbox.bottom - bbox.top; - - return bbox; -} - -function _getPercentInView(element, topWin, {w, h} = {}) { - const elementBoundingBox = _getBoundingBox(element, {w, h}); - - // Obtain the intersection of the element and the viewport - const elementInViewBoundingBox = _getIntersectionOfRects([{ - left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight - }, elementBoundingBox]); - - let elementInViewArea, elementTotalArea; - - if (elementInViewBoundingBox !== null) { - // Some or all of the element is in view - elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; - elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; - - return ((elementInViewArea / elementTotalArea) * 100); - } - - // No overlap between element and the viewport; therefore, the element - // lies completely out of view - return 0; -} - -function _getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.bidFloor ? bid.params.bidFloor : null; - } - - let floor = bid.getFloor({ - currency: 'USD', mediaType: '*', size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -registerBidder(spec); diff --git a/modules/brightcomSSPBidAdapter.md b/modules/brightcomSSPBidAdapter.md deleted file mode 100644 index 8d0e4ec70dc..00000000000 --- a/modules/brightcomSSPBidAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview - -``` -Module Name: Brightcom SSP Bid Adapter -Module Type: Bidder Adapter -Maintainer: alexandruc@brightcom.com -``` - -# Description - -Brightcom's adapter integration to the Prebid library. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-leaderboard', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bids: [{ - bidder: 'bcmssp', - params: { - publisherId: 2141020, - bidFloor: 0.01 - } - }] - }, { - code: 'test-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'bcmssp', - params: { - publisherId: 2141020 - } - }] - } -] -``` diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js deleted file mode 100644 index dcc365faaac..00000000000 --- a/modules/britepoolIdSystem.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * This module adds BritePoolId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/britepoolIdSystem - * @requires module:modules/userId - */ - -import { isEmpty, triggerPixel, logError } from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js'; -const PIXEL = 'https://px.britepool.com/new?partner_id=t'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - * @typedef {import('../modules/userId/index.js').SubmoduleParams} SubmoduleParams - */ - -/** @type {Submodule} */ -export const britepoolIdSubmodule = { - /** - * Used to link submodule with config - * @type {string} - */ - name: 'britepoolId', - /** - * Decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{britepoolid:string}} - */ - decode(value) { - return (value && typeof value['primaryBPID'] === 'string') ? { 'britepoolid': value['primaryBPID'] } : null; - }, - /** - * Performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [submoduleConfig] - * @param {ConsentData|undefined} consentData - * @returns {function} - */ - getId(submoduleConfig, consentData) { - const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; - const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams(submoduleConfigParams, consentData); - let getterResponse = null; - if (typeof getter === 'function') { - getterResponse = getter(params); - // First let's rule out that the response is not a function - if (typeof getterResponse !== 'function') { - // Optimization to return value from getter - return { - id: britepoolIdSubmodule.normalizeValue(getterResponse) - }; - } - } - if (isEmpty(params)) { - triggerPixel(PIXEL); - } - // Return for async operation - return { - callback: function(callback) { - if (errors.length > 0) { - errors.forEach(error => logError(error)); - callback(); - return; - } - if (getterResponse) { - // Resolve the getter function response - try { - getterResponse(function(response) { - callback(britepoolIdSubmodule.normalizeValue(response)); - }); - } catch (error) { - if (error !== '') logError(error); - callback(); - } - } else { - ajax(url, { - success: response => { - const responseObj = britepoolIdSubmodule.normalizeValue(response); - callback(responseObj ? { primaryBPID: responseObj.primaryBPID } : null); - }, - error: error => { - if (error !== '') logError(error); - callback(); - } - }, JSON.stringify(params), { customHeaders: headers, contentType: 'application/json', method: 'POST', withCredentials: true }); - } - } - } - }, - /** - * Helper method to create params for our API call - * @param {SubmoduleParams} [submoduleConfigParams] - * @param {ConsentData|undefined} consentData - * @returns {object} Object with parsed out params - */ - createParams(submoduleConfigParams, consentData) { - const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const gdprConsentString = hasGdprData ? consentData.consentString : undefined; - let errors = []; - const headers = {}; - const dynamicVars = typeof britepool_pubparams !== 'undefined' ? britepool_pubparams : {}; // eslint-disable-line camelcase, no-undef - let params = Object.assign({}, submoduleConfigParams, dynamicVars); - if (params.getter) { - // Custom getter will not require other params - if (typeof params.getter !== 'function') { - errors.push(`userIdTargeting - britepoolId submodule requires getter to be a function`); - return { errors }; - } - } else { - if (params.api_key) { - // Add x-api-key into the header - headers['x-api-key'] = params.api_key; - } - } - const url = params.url || `https://api.britepool.com/v1/britepool/id${gdprConsentString ? '?gdprString=' + encodeURIComponent(gdprConsentString) : ''}`; - const getter = params.getter; - delete params.api_key; - delete params.url; - delete params.getter; - return { - params, - headers, - url, - getter, - errors - }; - }, - /** - * Helper method to normalize a JSON value - */ - normalizeValue(value) { - let valueObj = null; - if (typeof value === 'object') { - valueObj = value; - } else if (typeof value === 'string') { - try { - valueObj = JSON.parse(value); - } catch (error) { - logError(error); - } - } - return valueObj; - }, - eids: { - 'britepoolid': { - source: 'britepool.com', - atype: 3 - }, - } -}; - -submodule('userId', britepoolIdSubmodule); diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md deleted file mode 100644 index 72edbe2324b..00000000000 --- a/modules/britepoolIdSystem.md +++ /dev/null @@ -1,42 +0,0 @@ -## BritePool User ID Submodule - -BritePool User ID Module. For assistance setting up your module please contact us at [prebid@britepool.com](prebid@britepool.com). - -### Prebid Params - -Individual params may be set for the BritePool User ID Submodule. -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'britepoolId', - storage: { - name: 'britepoolid', - type: 'cookie', - expires: 30 - }, - params: { - url: 'https://sandbox-api.britepool.com/v1/britepool/id', // optional - api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by britepool - hash: '31c5543c1734d25c7206f5fd591525d0295bec6fe84ff82f946a34fe970a1e66', // example hash identifier (sha256) - ssid: '221aa074-57fc-453b-81f0-6c74f628cd5c' // example identifier - } - }] - } -}); -``` -## Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the BritePool User ID Module integration. - -| Param under usersync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the BritePool module - `"britepoolId"` | `"britepoolId"` | -| params | Required | Object | Details for BritePool initialization. | | -| params.api_key | Required | String |BritePool API Key provided by BritePool | "3fdbe297-3690-4f5c-9e11-ee9186a6d77c" | -| params.url | Optional | String |BritePool API url | "https://sandbox-api.britepool.com/v1/britepool/id" | -| params.identifier | Required | String | Where identifier in the params object is the key name. At least one identifier is required. Available Identifiers `aaid` `dtid` `idfa` `ilid` `luid` `mmid` `msid` `mwid` `rida` `ssid` `hash` | `params.ssid` `params.aaid` | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"britepoolid"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the BritePool ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"primaryBPID": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index c69e484feb3..2abe9cb94a8 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -152,7 +152,6 @@ export const spec = { placement.gpid = gpid; } if (bid.userId) { - getUserId(placement.eids, bid.userId.britepoolid, 'britepool.com'); getUserId(placement.eids, bid.userId.idl_env, 'identityLink'); getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index b40ef30f6bc..5b892a6df22 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -3,7 +3,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; @@ -141,13 +140,6 @@ export const spec = { return bidResponses; }, - transformBidParams: function (params, isOpenRtb) { - return convertTypes({ - 'siteId': 'number', - 'networkId': 'number' - }, params); - }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { let syncEndpoint = 'https://cdn.connectad.io/connectmyusers.php?'; diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index f94048813c6..bd40713924e 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -10,7 +10,7 @@ import {gppDataHandler} from '../src/adapterManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import {cmpClient, MODE_CALLBACK, MODE_MIXED, MODE_RETURN} from '../libraries/cmp/cmpClient.js'; +import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; import {GreedyPromise} from '../src/utils/promise.js'; import {buildActivityParams} from '../src/activities/params.js'; @@ -38,9 +38,6 @@ function lookupStaticConsentData(callbacks) { return pipeCallbacks(() => processCmpData(staticConsentData), callbacks); } -const GPP_10 = '1.0'; -const GPP_11 = '1.1'; - class GPPError { constructor(message, arg) { this.message = message; @@ -49,104 +46,22 @@ class GPPError { } export class GPPClient { - static CLIENTS = {}; - - static register(apiVersion, defaultVersion = false) { - this.apiVersion = apiVersion; - this.CLIENTS[apiVersion] = this; - if (defaultVersion) { - this.CLIENTS.default = this; - } - } - + apiVersion = '1.1'; static INST; - /** - * Ping the CMP to set up an appropriate client for it, and initialize it. - * - * @param mkCmp - * @returns {Promise<[GPPClient,Promise<{}>]>} a promise to two objects: - * - a GPPClient that talks the best GPP dialect we know for the CMP's version; - * - a promise to GPP data. - */ - static init(mkCmp = cmpClient) { - let inst = this.INST; - if (!inst) { - let err; - const reset = () => err && (this.INST = null); - inst = this.INST = this.ping(mkCmp).catch(e => { - err = true; - reset(); - throw e; + static get(mkCmp = cmpClient) { + if (this.INST == null) { + const cmp = mkCmp({ + apiName: '__gpp', + apiArgs: ['command', 'callback', 'parameter'], // do not pass version - not clear what it's for (or what we should use), + mode: MODE_CALLBACK }); - reset(); - } - return inst.then(([client, pingData]) => [ - client, - client.initialized ? client.refresh() : client.init(pingData) - ]); - } - - /** - * Ping the CMP to determine its version and set up a client appropriate for it. - * - * @param mkCmp - * @returns {Promise<[GPPClient, {}]>} a promise to two objects: - * - a GPPClient that talks the best GPP dialect we know for the CMP's version; - * - the result from pinging the CMP. - */ - static ping(mkCmp = cmpClient) { - const cmpOptions = { - apiName: '__gpp', - apiArgs: ['command', 'callback', 'parameter'], // do not pass version - not clear what it's for (or what we should use) - }; - - // in 1.0, 'ping' should return pingData but ignore callback; - // in 1.1 it should not return anything but run the callback - // the following looks for either - but once the version is known, produce a client that knows whether the - // rest of the interactions should pick return values or pass callbacks - - const probe = mkCmp({...cmpOptions, mode: MODE_RETURN}); - return new GreedyPromise((resolve, reject) => { - if (probe == null) { - reject(new GPPError('GPP CMP not found')); - return; + if (cmp == null) { + throw new GPPError('GPP CMP not found'); } - let done = false; // some CMPs do both return value and callbacks - avoid repeating log messages - const pong = (result, success) => { - if (done) return; - if (success != null && !success) { - reject(result); - return; - } - if (result == null) return; - done = true; - const cmpVersion = result?.gppVersion; - const Client = this.getClient(cmpVersion); - if (cmpVersion !== Client.apiVersion) { - logWarn(`Unrecognized GPP CMP version: ${cmpVersion}. Continuing using GPP API version ${Client}...`); - } else { - logInfo(`Using GPP version ${cmpVersion}`); - } - const mode = Client.apiVersion === GPP_10 ? MODE_MIXED : MODE_CALLBACK; - const client = new Client( - cmpVersion, - mkCmp({...cmpOptions, mode}) - ); - resolve([client, result]); - }; - - probe({ - command: 'ping', - callback: pong - }).then((res) => pong(res, true), reject); - }).finally(() => { - probe && probe.close(); - }); - } - - static getClient(cmpVersion) { - return this.CLIENTS.hasOwnProperty(cmpVersion) ? this.CLIENTS[cmpVersion] : this.CLIENTS.default; + this.INST = new this(cmp); + } + return this.INST; } #resolve; @@ -155,9 +70,7 @@ export class GPPClient { initialized = false; - constructor(cmpVersion, cmp) { - this.apiVersion = this.constructor.apiVersion; - this.cmpVersion = cmp; + constructor(cmp) { this.cmp = cmp; [this.#resolve, this.#reject] = [0, 1].map(slot => (result) => { while (this.#pending.length) { @@ -176,6 +89,9 @@ export class GPPClient { init(pingData) { const ready = this.updateWhenReady(pingData); if (!this.initialized) { + if (pingData.gppVersion !== this.apiVersion) { + logWarn(`Unrecognized GPP CMP version: ${pingData.apiVersion}. Continuing using GPP API version ${this.apiVersion}...`); + } this.initialized = true; this.cmp({ command: 'addEventListener', @@ -184,7 +100,7 @@ export class GPPClient { this.#reject(new GPPError('Received error response from CMP', event)); } else if (event?.pingData?.cmpStatus === 'error') { this.#reject(new GPPError('CMP status is "error"; please check CMP setup', event)); - } else if (this.isCMPReady(event?.pingData || {}) && this.events.includes(event?.eventName)) { + } else if (this.isCMPReady(event?.pingData || {}) && ['sectionChange', 'signalStatus'].includes(event?.eventName)) { this.#resolve(this.updateConsent(event.pingData)); } } @@ -194,7 +110,7 @@ export class GPPClient { } refresh() { - return this.cmp({command: 'ping'}).then(this.updateWhenReady.bind(this)); + return this.cmp({command: 'ping'}).then(this.init.bind(this)); } /** @@ -204,15 +120,14 @@ export class GPPClient { * @returns {Promise<{}>} a promise to GPP consent data */ updateConsent(pingData) { - return this.getGPPData(pingData).then((data) => { - if (data == null || isEmpty(data)) { - throw new GPPError('Received empty response from CMP', data); + return new GreedyPromise(resolve => { + if (pingData == null || isEmpty(pingData)) { + throw new GPPError('Received empty response from CMP', pingData); } - return processCmpData(data); - }).then((data) => { - logInfo('Retrieved GPP consent from CMP:', data); - return data; - }); + const consentData = processCmpData(pingData); + logInfo('Retrieved GPP consent from CMP:', consentData); + resolve(consentData); + }) } /** @@ -236,68 +151,10 @@ export class GPPClient { updateWhenReady(pingData) { return this.isCMPReady(pingData) ? this.updateConsent(pingData) : this.nextUpdate(); } -} - -// eslint-disable-next-line no-unused-vars -class GPP10Client extends GPPClient { - static { - super.register(GPP_10); - } - - events = ['sectionChange', 'cmpStatus']; - - isCMPReady(pingData) { - return pingData.cmpStatus === 'loaded'; - } - - getGPPData(pingData) { - const parsedSections = GreedyPromise.all( - (pingData.supportedAPIs || pingData.apiSupport || []).map((api) => this.cmp({ - command: 'getSection', - parameter: api - }).catch(err => { - logWarn(`Could not retrieve GPP section '${api}'`, err); - }).then((section) => [api, section])) - ).then(sections => { - // parse single section object into [core, gpc] to uniformize with 1.1 parsedSections - return Object.fromEntries( - sections.filter(([_, val]) => val != null) - .map(([api, section]) => { - const subsections = [ - Object.fromEntries(Object.entries(section).filter(([k]) => k !== 'Gpc')) - ]; - if (section.Gpc != null) { - subsections.push({ - SubsectionType: 1, - Gpc: section.Gpc - }); - } - return [api, subsections]; - }) - ); - }); - return GreedyPromise.all([ - this.cmp({command: 'getGPPData'}), - parsedSections - ]).then(([gppData, parsedSections]) => Object.assign({}, gppData, {parsedSections})); - } -} - -// eslint-disable-next-line no-unused-vars -class GPP11Client extends GPPClient { - static { - super.register(GPP_11, true); - } - - events = ['sectionChange', 'signalStatus']; isCMPReady(pingData) { return pingData.signalStatus === 'ready'; } - - getGPPData(pingData) { - return GreedyPromise.resolve(pingData); - } } /** @@ -310,7 +167,7 @@ class GPP11Client extends GPPClient { * @param {function(): Object} [mkCmp=cmpClient] - A function to create the CMP client. Defaults to `cmpClient`. */ export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) { - pipeCallbacks(() => GPPClient.init(mkCmp).then(([client, gppDataPm]) => gppDataPm), {onSuccess, onError}); + pipeCallbacks(() => GPPClient.get(mkCmp).refresh(), {onSuccess, onError}); } // add new CMPs here, with their dedicated lookup function @@ -427,9 +284,9 @@ function processCmpData(consentData) { } ['usnatv1', 'uscav1'].forEach(section => { if (consentData?.parsedSections?.[section]) { - logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData) + logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData); } - }) + }); return storeConsentData(consentData); } diff --git a/modules/consentManagement.js b/modules/consentManagementTcf.js similarity index 100% rename from modules/consentManagement.js rename to modules/consentManagementTcf.js diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index c95cbf7af73..55879089530 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,15 +1,14 @@ -import { deepAccess, generateUUID, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 -import { getStorageManager } from '../src/storageManager.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; -import { Renderer } from '../src/Renderer.js'; -import { OUTSTREAM } from '../src/video.js'; -import { ajax } from '../src/ajax.js'; +import {deepAccess, deepSetValue, isArray, logError, logWarn, parseUrl} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {Renderer} from '../src/Renderer.js'; +import {OUTSTREAM} from '../src/video.js'; +import {ajax} from '../src/ajax.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {ortb25Translator} from '../libraries/ortb2.5Translator/translator.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -20,35 +19,199 @@ import { ajax } from '../src/ajax.js'; */ const GVLID = 91; -export const ADAPTER_VERSION = 36; +export const ADAPTER_VERSION = 37; const BIDDER_CODE = 'criteo'; -const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; +const CDB_ENDPOINT = 'https://grid-bidder.criteo.com/openrtb_2_5/pbjs/auction/request'; const PROFILE_ID_INLINE = 207; -export const PROFILE_ID_PUBLISHERTAG = 185; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; +const TRANSLATOR = ortb25Translator(); -/* - If you don't want to use the FastBid adapter feature, you can lighten criteoBidAdapter size by : - 1. commenting the tryGetCriteoFastBid function inner content (see ref#1) - 2. removing the line 'verify' function import line (see ref#2) - - Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js -*/ -const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 144; -const FAST_BID_VERSION_LATEST = 'latest'; -const FAST_BID_VERSION_NONE = 'none'; -const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; const PUBLISHER_TAG_OUTSTREAM_SRC = 'https://static.criteo.net/js/ld/publishertag.renderer.js' -const FAST_BID_PUBKEY_E = 65537; -const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; - const OPTOUT_COOKIE_NAME = 'cto_optout'; const BUNDLE_COOKIE_NAME = 'cto_bundle'; const GUID_RETENTION_TIME_HOUR = 24 * 30 * 13; // 13 months const OPTOUT_RETENTION_TIME_HOUR = 5 * 12 * 30 * 24; // 5 years +/** + * Defines the generic oRTB converter and all customization functions. + */ +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 60 + }, + imp, + request, + bidResponse, + response +}); + +/** + * Builds an impression object for the ORTB 2.5 request. + * + * @param {function} buildImp - The function for building an imp object. + * @param {Object} bidRequest - The bid request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 imp object. + */ +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const params = bidRequest.params; + + imp.tagid = bidRequest.adUnitCode; + deepSetValue(imp, 'ext', { + ...bidRequest.params.ext, + ...imp.ext, + rwdd: imp.rwdd, + floors: getFloors(bidRequest), + bidder: { + publishersubid: params?.publisherSubId, + zoneid: params?.zoneId, + uid: params?.uid, + }, + }); + + delete imp.rwdd // oRTB 2.6 field moved to ext + + if (!context.fledgeEnabled && imp.ext.igs?.ae) { + delete imp.ext.igs.ae; + } + + if (hasVideoMediaType(bidRequest)) { + const paramsVideo = bidRequest.params.video; + if (paramsVideo !== undefined) { + deepSetValue(imp, 'video', { + ...imp.video, + skip: imp.video.skip || paramsVideo.skip || 0, + placement: imp.video.placement || paramsVideo.placement, + minduration: imp.video.minduration || paramsVideo.minduration, + playbackmethod: imp.video.playbackmethod || paramsVideo.playbackmethod, + startdelay: imp.video.startdelay || paramsVideo.startdelay || 0, + }) + } + deepSetValue(imp, 'video.ext', { + context: bidRequest.mediaTypes.video.context, + playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), + plcmt: bidRequest.mediaTypes.video.plcmt, + poddur: bidRequest.mediaTypes.video.adPodDurationSec, + rqddurs: bidRequest.mediaTypes.video.durationRangeSec, + }) + } + + if (imp.native && typeof imp.native.request !== 'undefined') { + let requestNative = JSON.parse(imp.native.request); + + // We remove the native asset requirements if we used the bypass to generate the imp + const hasAssetRequirements = requestNative.assets && + (requestNative.assets.length !== 1 || Object.keys(requestNative.assets[0]).length); + if (!hasAssetRequirements) { + delete requestNative.assets; + } + + deepSetValue(imp, 'native.request_native', requestNative); + delete imp.native.request; + } + + return imp; +} + +/** + * Builds a request object for the ORTB 2.5 request. + * + * @param {function} buildRequest - The function for building a request object. + * @param {Array} imps - An array of ORTB 2.5 impression objects. + * @param {Object} bidderRequest - The bidder request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + + // params.pubid should override publisher id + if (typeof context.publisherId !== 'undefined') { + if (typeof request.app !== 'undefined') { + deepSetValue(request, 'app.publisher.id', context.publisherId); + } else { + deepSetValue(request, 'site.publisher.id', context.publisherId); + } + } + + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue(request, 'regs.ext.gdprversion', bidderRequest.gdprConsent.apiVersion); + } + + // Translate 2.6 OpenRTB request into 2.5 OpenRTB request + request = TRANSLATOR(request); + + return request; +} + +/** + * Build bid from oRTB 2.5 bid. + * + * @param buildBidResponse + * @param bid + * @param context + * @returns {*} + */ +function bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.mediatype'); + if (context.mediaType === NATIVE && typeof bid.adm_native !== 'undefined') { + bid.adm = bid.adm_native; + delete bid.adm_native; + } + + let bidResponse = buildBidResponse(bid, context); + const {bidRequest} = context; + + bidResponse.currency = deepAccess(bid, 'ext.cur') + + if (typeof deepAccess(bid, 'ext.meta') !== 'undefined') { + deepSetValue(bidResponse, 'meta', { + ...bidResponse.meta, + ...bid.ext.meta + }); + } + if (typeof deepAccess(bid, 'ext.paf.content_id') !== 'undefined') { + deepSetValue(bidResponse, 'meta.paf.content_id', bid.ext.paf.content_id) + } + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastUrl = bid.ext?.displayurl; + // if outstream video, add a default render for it. + if (deepAccess(bidRequest, 'mediaTypes.video.context') === OUTSTREAM) { + bidResponse.renderer = createOutstreamVideoRenderer(bid); + } + } + + return bidResponse; +} + +/** + * Builds bid response from the oRTB 2.5 bid response. + * + * @param buildResponse + * @param bidResponses + * @param ortbResponse + * @param context + * @returns * + */ +function response(buildResponse, bidResponses, ortbResponse, context) { + let response = buildResponse(bidResponses, ortbResponse, context); + + const pafTransmission = deepAccess(ortbResponse, 'ext.paf.transmission'); + response.bids.forEach(bid => { + if (typeof pafTransmission !== 'undefined' && typeof deepAccess(bid, 'meta.paf.content_id') !== 'undefined') { + deepSetValue(bid, 'meta.paf.transmission', pafTransmission); + } else { + delete bid.meta.paf; + } + }); + + return response; +} + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, @@ -58,15 +221,10 @@ export const spec = { getUserSyncs: function (syncOptions, _, gdprConsent, uspConsent, gppConsent = {}) { let { gppString = '', applicableSections = [] } = gppConsent; - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { - const fastBidVersion = config.getConfig('criteo.fastBidVersion'); - if (canFastBid(fastBidVersion)) { - return []; - } - - const refererInfo = getRefererInfo(); - const origin = 'criteoPrebidAdapter'; + const refererInfo = getRefererInfo(); + const origin = 'criteoPrebidAdapter'; + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { const queryParams = []; queryParams.push(`origin=${origin}`); queryParams.push(`topUrl=${refererInfo.domain}`); @@ -191,50 +349,25 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - let url; - let data; - let fpd = bidderRequest.ortb2 || {}; - - Object.assign(bidderRequest, { - publisherExt: fpd.site?.ext, - userExt: fpd.user?.ext, - ceh: config.getConfig('criteo.ceh'), - coppa: config.getConfig('coppa') - }); - - // If publisher tag not already loaded try to get it from fast bid - const fastBidVersion = config.getConfig('criteo.fastBidVersion'); - const canLoadPublisherTag = canFastBid(fastBidVersion); - if (!publisherTagAvailable() && canLoadPublisherTag) { - window.Criteo = window.Criteo || {}; - window.Criteo.usePrebidEvents = false; - - tryGetCriteoFastBid(); + bidRequests.forEach(bidRequest => { + if (hasNativeMediaType(bidRequest)) { + if (!checkNativeSendId(bidRequest)) { + logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); + } - const fastBidUrl = getFastBidUrl(fastBidVersion); - // Reload the PublisherTag after the timeout to ensure FastBid is up-to-date and tracking done properly - setTimeout(() => { - loadExternalScript(fastBidUrl, BIDDER_CODE); - }, bidderRequest.timeout); - } + // We support native request without assets requirements because we can fill them later on. + // This is a trick to fool oRTB converter isOpenRTBBidRequestValid(ortb) fn because it needs + // nativeOrtbRequest.assets to be non-empty. + if (deepAccess(bidRequest, 'nativeOrtbRequest.assets') == null) { + logWarn(LOG_PREFIX + 'native asset requirements are missing'); + deepSetValue(bidRequest, 'nativeOrtbRequest.assets', [{}]); + } + } + }); - if (publisherTagAvailable()) { - // eslint-disable-next-line no-undef - const adapter = new Criteo.PubTag.Adapters.Prebid( - PROFILE_ID_PUBLISHERTAG, - ADAPTER_VERSION, - bidRequests, - bidderRequest, - '$prebid.version$', - { createOutstreamVideoRenderer: createOutstreamVideoRenderer } - ); - url = adapter.buildCdbUrl(); - data = adapter.buildCdbRequest(); - } else { - const context = buildContext(bidRequests, bidderRequest); - url = buildCdbUrl(context); - data = buildCdbRequest(context, bidRequests, bidderRequest); - } + const context = buildContext(bidRequests, bidderRequest); + const url = buildCdbUrl(context); + const data = CONVERTER.toORTB({bidderRequest, bidRequests, context}); if (data) { return { method: 'POST', url, data, bidRequests }; @@ -247,131 +380,24 @@ export const spec = { * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} */ interpretResponse: (response, request) => { - const body = response.body || response; - - if (publisherTagAvailable()) { - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(request); - if (adapter) { - return adapter.interpretResponse(body, request); - } - } - - const bids = []; - const fledgeAuctionConfigs = []; - - if (body && body.slots && isArray(body.slots)) { - body.slots.forEach(slot => { - const bidRequest = getAssociatedBidRequest(request.bidRequests, slot); - if (bidRequest) { - const bidId = bidRequest.bidId; - const bid = { - requestId: bidId, - cpm: slot.cpm, - currency: slot.currency, - netRevenue: true, - ttl: slot.ttl || 60, - creativeId: slot.creativecode, - width: slot.width, - height: slot.height, - dealId: slot.deal, - }; - if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { - const pafResponseMeta = { - content_id: slot.ext.paf.content_id, - transmission: response.ext.paf.transmission - }; - bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); - } - if (slot.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [slot.adomain].flat() }); - } - if (slot.ext?.meta?.networkName) { - bid.meta = Object.assign({}, bid.meta, { networkName: slot.ext.meta.networkName }) - } - if (slot.ext?.dsa) { - bid.meta = Object.assign({}, bid.meta, { dsa: slot.ext.dsa }) - } - if (slot.native) { - if (bidRequest.params.nativeCallback) { - bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); - } else { - bid.native = createPrebidNativeAd(slot.native); - bid.mediaType = NATIVE; - } - } else if (slot.video) { - bid.vastUrl = slot.displayurl; - bid.mediaType = VIDEO; - const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - // if outstream video, add a default render for it. - if (context === OUTSTREAM) { - bid.renderer = createOutstreamVideoRenderer(slot); - } - } else { - bid.ad = slot.creative; - } - bids.push(bid); - } - }); + if (typeof response?.body == 'undefined') { + return []; // no bid } - if (isArray(body.ext?.igi)) { - body.ext.igi.forEach((igi) => { - if (isArray(igi?.igs)) { - igi.igs.forEach((igs) => { - fledgeAuctionConfigs.push(igs); - }); - } - }); - } + const interpretedResponse = CONVERTER.fromORTB({response: response.body, request: request.data}); + const bids = interpretedResponse.bids || []; - if (fledgeAuctionConfigs.length) { + const fledgeAuctionConfigs = deepAccess(response.body, 'ext.igi')?.filter(igi => isArray(igi?.igs)) + .flatMap(igi => igi.igs); + if (fledgeAuctionConfigs?.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } return bids; }, - /** - * @param {TimedOutBid} timeoutData - */ - onTimeout: (timeoutData) => { - if (publisherTagAvailable() && Array.isArray(timeoutData)) { - var auctionsIds = []; - timeoutData.forEach((bid) => { - if (auctionsIds.indexOf(bid.auctionId) === -1) { - auctionsIds.push(bid.auctionId); - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); - adapter.handleBidTimeout(); - } - }); - } - }, - - /** - * @param {Bid} bid - */ - onBidWon: (bid) => { - if (publisherTagAvailable() && bid) { - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); - adapter.handleBidWon(bid); - } - }, - - /** - * @param {Bid} bid - */ - onSetTargeting: (bid) => { - if (publisherTagAvailable()) { - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); - adapter.handleSetTargeting(bid); - } - }, /** * @param {BidRequest[]} bidRequests @@ -412,43 +438,26 @@ function deleteFromAllStorages(name) { storage.removeDataFromLocalStorage(name); } -/** - * @return {boolean} - */ -function publisherTagAvailable() { - // eslint-disable-next-line no-undef - return typeof Criteo !== 'undefined' && Criteo.PubTag && Criteo.PubTag.Adapters && Criteo.PubTag.Adapters.Prebid; -} - /** * @param {BidRequest[]} bidRequests * @param bidderRequest */ function buildContext(bidRequests, bidderRequest) { - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.page; - } const queryString = parseUrl(bidderRequest?.refererInfo?.topmostLocation).search; - const context = { - url: referrer, + return { + url: bidderRequest?.refererInfo?.page || '', debug: queryString['pbt_debug'] === '1', noLog: queryString['pbt_nolog'] === '1', - amp: false, + fledgeEnabled: bidderRequest.paapi?.enabled, + amp: bidRequests.some(bidRequest => bidRequest.params.integrationMode === 'amp'), + networkId: bidRequests.find(bidRequest => bidRequest.params?.networkId)?.params.networkId, + publisherId: bidRequests.find(bidRequest => bidRequest.params?.pubid)?.params.pubid, }; - - bidRequests.forEach(bidRequest => { - if (bidRequest.params.integrationMode === 'amp') { - context.amp = true; - } - }); - - return context; } /** - * @param {CriteoContext} context + * @param {Object} context * @return {string} */ function buildCdbUrl(context) { @@ -484,6 +493,10 @@ function buildCdbUrl(context) { url += `&optout=1`; } + if (context.networkId) { + url += `&networkId=` + context.networkId; + } + return url; } @@ -499,185 +512,6 @@ function checkNativeSendId(bidRequest) { )); } -/** - * @param {CriteoContext} context - * @param {BidRequest[]} bidRequests - * @param bidderRequest - * @return {*} - */ -function buildCdbRequest(context, bidRequests, bidderRequest) { - let networkId; - let pubid; - let schain; - let userIdAsEids; - let regs = Object.assign({}, { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) - }, bidderRequest.ortb2?.regs); - const request = { - id: generateUUID(), - publisher: { - url: context.url, - ext: bidderRequest.publisherExt, - }, - regs: regs, - slots: bidRequests.map(bidRequest => { - if (!userIdAsEids) { - userIdAsEids = bidRequest.userIdAsEids; - } - networkId = bidRequest.params.networkId || networkId; - pubid = bidRequest.params.pubid || pubid; - schain = bidRequest.schain || schain; - const slot = { - slotid: bidRequest.bidId, - impid: bidRequest.adUnitCode, - transactionid: bidRequest.ortb2Imp?.ext?.tid - }; - if (bidRequest.params.zoneId) { - slot.zoneid = bidRequest.params.zoneId; - } - if (deepAccess(bidRequest, 'ortb2Imp.ext')) { - slot.ext = bidRequest.ortb2Imp.ext; - } - - if (deepAccess(bidRequest, 'ortb2Imp.rwdd')) { - slot.rwdd = bidRequest.ortb2Imp.rwdd; - } - - if (bidRequest.params.ext) { - slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); - } - if (bidRequest.nativeOrtbRequest?.assets) { - slot.ext = Object.assign({}, slot.ext, { assets: bidRequest.nativeOrtbRequest.assets }); - } - if (bidRequest.params.uid) { - slot.ext = Object.assign({}, slot.ext, { bidder: { uid: bidRequest.params.uid } }); - } - - if (bidRequest.params.publisherSubId) { - slot.publishersubid = bidRequest.params.publisherSubId; - } - - if (bidRequest.params.nativeCallback || hasNativeMediaType(bidRequest)) { - slot.native = true; - if (!checkNativeSendId(bidRequest)) { - logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); - } - } - - if (hasBannerMediaType(bidRequest)) { - slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize); - } else { - slot.sizes = []; - } - - if (hasVideoMediaType(bidRequest)) { - const video = { - context: bidRequest.mediaTypes.video.context, - playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), - mimes: bidRequest.mediaTypes.video.mimes, - protocols: bidRequest.mediaTypes.video.protocols, - maxduration: bidRequest.mediaTypes.video.maxduration, - api: bidRequest.mediaTypes.video.api, - skip: bidRequest.mediaTypes.video.skip, - placement: bidRequest.mediaTypes.video.placement, - minduration: bidRequest.mediaTypes.video.minduration, - playbackmethod: bidRequest.mediaTypes.video.playbackmethod, - startdelay: bidRequest.mediaTypes.video.startdelay, - plcmt: bidRequest.mediaTypes.video.plcmt, - w: bidRequest.mediaTypes.video.w, - h: bidRequest.mediaTypes.video.h, - linearity: bidRequest.mediaTypes.video.linearity, - skipmin: bidRequest.mediaTypes.video.skipmin, - skipafter: bidRequest.mediaTypes.video.skipafter, - minbitrate: bidRequest.mediaTypes.video.minbitrate, - maxbitrate: bidRequest.mediaTypes.video.maxbitrate, - delivery: bidRequest.mediaTypes.video.delivery, - pos: bidRequest.mediaTypes.video.pos, - playbackend: bidRequest.mediaTypes.video.playbackend, - adPodDurationSec: bidRequest.mediaTypes.video.adPodDurationSec, - durationRangeSec: bidRequest.mediaTypes.video.durationRangeSec, - }; - const paramsVideo = bidRequest.params.video; - if (paramsVideo !== undefined) { - video.skip = video.skip || paramsVideo.skip || 0; - video.placement = video.placement || paramsVideo.placement; - video.minduration = video.minduration || paramsVideo.minduration; - video.playbackmethod = video.playbackmethod || paramsVideo.playbackmethod; - video.startdelay = video.startdelay || paramsVideo.startdelay || 0; - } - - slot.video = video; - } - - enrichSlotWithFloors(slot, bidRequest); - - if (!bidderRequest.fledgeEnabled && slot.ext?.ae) { - delete slot.ext.ae; - } - - return slot; - }), - }; - if (networkId) { - request.publisher.networkid = networkId; - } - - request.source = { - tid: bidderRequest.ortb2?.source?.tid - }; - - if (schain) { - request.source.ext = { - schain: schain - }; - }; - request.user = bidderRequest.ortb2?.user || {}; - request.site = bidderRequest.ortb2?.site || {}; - request.app = bidderRequest.ortb2?.app || {}; - - if (pubid) { - request.site.publisher = {...request.site.publisher, ...{ id: pubid }}; - request.app.publisher = {...request.app.publisher, ...{ id: pubid }}; - } - - request.device = bidderRequest.ortb2?.device || {}; - if (bidderRequest && bidderRequest.ceh) { - request.user.ceh = bidderRequest.ceh; - } - if (bidderRequest && bidderRequest.gdprConsent) { - request.gdprConsent = {}; - if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - request.gdprConsent.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); - } - request.gdprConsent.version = bidderRequest.gdprConsent.apiVersion; - if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { - request.gdprConsent.consentData = bidderRequest.gdprConsent.consentString; - } - } - if (bidderRequest && bidderRequest.uspConsent) { - request.user.uspIab = bidderRequest.uspConsent; - } - if (bidderRequest && bidderRequest.ortb2?.device?.sua) { - request.user.ext = request.user.ext || {}; - request.user.ext.sua = bidderRequest.ortb2?.device?.sua || {}; - } - if (userIdAsEids) { - request.user.ext = request.user.ext || {}; - request.user.ext.eids = [...userIdAsEids]; - } - if (bidderRequest && bidderRequest.ortb2?.bcat) { - request.bcat = bidderRequest.ortb2.bcat; - } - if (bidderRequest && bidderRequest.ortb2?.badv) { - request.badv = bidderRequest.ortb2.badv; - } - if (bidderRequest && bidderRequest.ortb2?.bapp) { - request.bapp = bidderRequest.ortb2.bapp; - } - request.tmax = bidderRequest.timeout; - return request; -} - function parseSizes(sizes, parser = s => s) { if (sizes == undefined) { return []; @@ -696,10 +530,6 @@ function hasVideoMediaType(bidRequest) { return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; } -function hasBannerMediaType(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.banner') !== undefined; -} - function hasNativeMediaType(bidRequest) { return deepAccess(bidRequest, 'mediaTypes.native') !== undefined; } @@ -719,54 +549,6 @@ function hasValidVideoMediaType(bidRequest) { return isValid; } -/** - * Create prebid compatible native ad with native payload - * @param {*} payload - * @returns prebid native ad assets - */ -function createPrebidNativeAd(payload) { - return { - sendTargetingKeys: false, // no key is added to KV by default - title: payload.products[0].title, - body: payload.products[0].description, - sponsoredBy: payload.advertiser.description, - icon: payload.advertiser.logo, - image: payload.products[0].image, - clickUrl: payload.products[0].click_url, - privacyLink: payload.privacy.optout_click_url, - privacyIcon: payload.privacy.optout_image_url, - cta: payload.products[0].call_to_action, - price: payload.products[0].price, - impressionTrackers: payload.impression_pixels.map(pix => pix.url) - }; -} - -/** - * @param {string} id - * @param {*} payload - * @param {*} callback - * @return {string} - */ -function createNativeAd(id, payload, callback) { - // Store the callback and payload in a global object to be later accessed from the creative - var slotsName = 'criteo_prebid_native_slots'; - window[slotsName] = window[slotsName] || {}; - window[slotsName][id] = { callback, payload }; - - // The creative is in an iframe so we have to get the callback and payload - // from the parent window (doesn't work with safeframes) - return ` -`; -} - function pickAvailableGetFloorFunc(bidRequest) { if (bidRequest.getFloor) { return bidRequest.getFloor; @@ -785,87 +567,58 @@ function pickAvailableGetFloorFunc(bidRequest) { return undefined; } -function enrichSlotWithFloors(slot, bidRequest) { +function getFloors(bidRequest) { try { - const slotFloors = {}; + const floors = {}; const getFloor = pickAvailableGetFloorFunc(bidRequest); if (getFloor) { if (bidRequest.mediaTypes?.banner) { - slotFloors.banner = {}; + floors.banner = {}; const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) - bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER })); + bannerSizes.forEach(bannerSize => floors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER })); } if (bidRequest.mediaTypes?.video) { - slotFloors.video = {}; + floors.video = {}; const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) - videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO })); + videoSizes.forEach(videoSize => floors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO })); } if (bidRequest.mediaTypes?.native) { - slotFloors.native = {}; - slotFloors.native['*'] = getFloor.call(bidRequest, { size: '*', mediaType: NATIVE }); + floors.native = {}; + floors.native['*'] = getFloor.call(bidRequest, { size: '*', mediaType: NATIVE }); } - if (Object.keys(slotFloors).length > 0) { - if (!slot.ext) { - slot.ext = {} - } - Object.assign(slot.ext, { - floors: slotFloors - }); - } + return floors; } } catch (e) { logError('Could not parse floors from Prebid: ' + e); } } -export function canFastBid(fastBidVersion) { - return fastBidVersion !== FAST_BID_VERSION_NONE; -} - -export function getFastBidUrl(fastBidVersion) { - let version; - if (fastBidVersion === FAST_BID_VERSION_LATEST) { - version = ''; - } else if (fastBidVersion) { - let majorVersion = String(fastBidVersion).split('.')[0]; - if (majorVersion < 102) { - logWarn('Specifying a Fastbid version which is not supporting version selection.') - } - version = '.' + fastBidVersion; - } else { - version = '.' + FAST_BID_VERSION_CURRENT; - } - - return PUBLISHER_TAG_URL_TEMPLATE.replace(FAST_BID_VERSION_PLACEHOLDER, version); -} - -function createOutstreamVideoRenderer(slot) { - if (slot.ext.videoPlayerConfig === undefined || slot.ext.videoPlayerType === undefined) { +function createOutstreamVideoRenderer(bid) { + if (bid.ext?.videoPlayerConfig === undefined || bid.ext?.videoPlayerType === undefined) { return undefined; } const config = { - documentResolver: (bid, sourceDocument, renderDocument) => { + documentResolver: (_, sourceDocument, renderDocument) => { return renderDocument ?? sourceDocument; } } - const render = (bid, renderDocument) => { + const render = (_, renderDocument) => { let payload = { - slotid: slot.impid, - vastUrl: slot.displayurl, - vastXml: slot.creative, + slotid: bid.id, + vastUrl: bid.ext?.displayurl, + vastXml: bid.adm, documentContext: renderDocument, }; - let outstreamConfig = slot.ext.videoPlayerConfig; - - window.CriteoOutStream[slot.ext.videoPlayerType].play(payload, outstreamConfig) + let outstreamConfig = bid.ext.videoPlayerConfig; + window.CriteoOutStream[bid.ext.videoPlayerType].play(payload, outstreamConfig) }; const renderer = Renderer.install({ url: PUBLISHER_TAG_OUTSTREAM_SRC, config: config }); @@ -873,60 +626,4 @@ function createOutstreamVideoRenderer(slot) { return renderer; } -function getAssociatedBidRequest(bidRequests, slot) { - for (const request of bidRequests) { - if (request.adUnitCode === slot.impid) { - if (request.params.zoneId && parseInt(request.params.zoneId) === slot.zoneid) { - return request; - } else if (slot.native) { - if (request.mediaTypes?.native || request.nativeParams) { - return request; - } - } else if (slot.video) { - if (request.mediaTypes?.video) { - return request; - } - } else if (request.mediaTypes?.banner || request.sizes) { - return request; - } - } - } - return undefined; -} - -export function tryGetCriteoFastBid() { - // begin ref#1 - try { - const fastBidStorageKey = 'criteo_fast_bid'; - const hashPrefix = '// Hash: '; - const fastBidFromStorage = storage.getDataFromLocalStorage(fastBidStorageKey); - - if (fastBidFromStorage !== null) { - // The value stored must contain the file's encrypted hash as first line - const firstLineEndPosition = fastBidFromStorage.indexOf('\n'); - const firstLine = fastBidFromStorage.substr(0, firstLineEndPosition).trim(); - - if (firstLine.substr(0, hashPrefix.length) !== hashPrefix) { - logWarn('No hash found in FastBid'); - storage.removeDataFromLocalStorage(fastBidStorageKey); - } else { - // Remove the hash part from the locally stored value - const publisherTagHash = firstLine.substr(hashPrefix.length); - const publisherTag = fastBidFromStorage.substr(firstLineEndPosition + 1); - - if (verify(publisherTag, publisherTagHash, FAST_BID_PUBKEY_N, FAST_BID_PUBKEY_E)) { - logInfo('Using Criteo FastBid'); - eval(publisherTag); // eslint-disable-line no-eval - } else { - logWarn('Invalid Criteo FastBid found'); - storage.removeDataFromLocalStorage(fastBidStorageKey); - } - } - } - } catch (e) { - // Unable to get fast bid - } - // end ref#1 -} - registerBidder(spec); diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 6314ed15ff9..8325af56b20 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -5,26 +5,26 @@ import {registerVideoSupport} from '../src/adServerManager.js'; import {targeting} from '../src/targeting.js'; import { - isNumber, buildUrl, deepAccess, formatQS, isEmpty, + isNumber, logError, parseSizesInput, parseUrl, uniques } from '../src/utils.js'; import {config} from '../src/config.js'; -import {getHook, submodule} from '../src/hook.js'; +import {getHook} from '../src/hook.js'; import {auctionManager} from '../src/auctionManager.js'; import {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; +import {EVENTS} from '../src/constants.js'; import {getPPID} from '../src/adserver.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; - +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; /** * @typedef {Object} DfpVideoParams * @@ -54,16 +54,6 @@ import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; * @param {string} [url] video adserver url */ -/** Safe defaults which work on pretty much all video calls. */ -const defaultParamConstants = { - env: 'vp', - gdfp_req: 1, - output: 'vast', - unviewed_position_start: 1, -}; - -export const adpodUtils = {}; - export const dep = { ri: getRefererInfo } @@ -115,7 +105,7 @@ export function buildDfpVideoUrl(options) { let encodedCustomParams = getCustParams(bid, options, urlSearchComponent && urlSearchComponent.cust_params); const queryParams = Object.assign({}, - defaultParamConstants, + DEFAULT_DFP_PARAMS, urlComponents.search, derivedParams, options.params, @@ -202,11 +192,7 @@ export function buildDfpVideoUrl(options) { })) } - return buildUrl(Object.assign({ - protocol: 'https', - host: 'securepubads.g.doubleclick.net', - pathname: '/gampad/ads' - }, urlComponents, { search: queryParams })); + return buildUrl(Object.assign({}, DFP_ENDPOINT, urlComponents, { search: queryParams })); } export function notifyTranslationModule(fn) { @@ -215,95 +201,6 @@ export function notifyTranslationModule(fn) { if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('registerAdserver').before(notifyTranslationModule); } -/** - * @typedef {Object} DfpAdpodOptions - * - * @param {string} code Ad Unit code - * @param {Object} params Query params which should be set on the DFP request. - * These will override this module's defaults whenever they conflict. - * @param {function} callback Callback function to execute when master tag is ready - */ - -/** - * Creates master tag url for long-form - * @param {DfpAdpodOptions} options - * @returns {string} A URL which calls DFP with custom adpod targeting key values to compete with rest of the demand in DFP - */ -export function buildAdpodVideoUrl({code, params, callback} = {}) { - // TODO: the public API for this does not take in enough info to fill all DFP params (adUnit/bid), - // and is marked "alpha": https://docs.prebid.org/dev-docs/publisher-api-reference/adServers.dfp.buildAdpodVideoUrl.html - if (!params || !callback) { - logError(`A params object and a callback is required to use pbjs.adServers.dfp.buildAdpodVideoUrl`); - return; - } - - const derivedParams = { - correlator: Date.now(), - sz: getSizeForAdUnit(code), - url: encodeURIComponent(location.href), - }; - - function getSizeForAdUnit(code) { - let adUnit = auctionManager.getAdUnits() - .filter((adUnit) => adUnit.code === code) - let sizes = deepAccess(adUnit[0], 'mediaTypes.video.playerSize'); - return parseSizesInput(sizes).join('|'); - } - - adpodUtils.getTargeting({ - 'codes': [code], - 'callback': createMasterTag - }); - - function createMasterTag(err, targeting) { - if (err) { - callback(err, null); - return; - } - - let initialValue = { - [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, - [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined - }; - let customParams = {}; - if (targeting[code]) { - customParams = targeting[code].reduce((acc, curValue) => { - if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_PB_CAT_DUR) { - acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] = (typeof acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] !== 'undefined') ? acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] + ',' + curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR] : curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR]; - } else if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_CACHE_ID) { - acc[adpodUtils.TARGETING_KEY_CACHE_ID] = curValue[adpodUtils.TARGETING_KEY_CACHE_ID] - } - return acc; - }, initialValue); - } - - let encodedCustomParams = encodeURIComponent(formatQS(customParams)); - - const queryParams = Object.assign({}, - defaultParamConstants, - derivedParams, - params, - { cust_params: encodedCustomParams } - ); - - const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } - } - - const masterTag = buildUrl({ - protocol: 'https', - host: 'securepubads.g.doubleclick.net', - pathname: '/gampad/ads', - search: queryParams - }); - - callback(null, masterTag); - } -} - /** * Builds a video url from a base dfp video url and a winning bid, appending * Prebid-specific key-values. @@ -375,8 +272,4 @@ function getCustParams(bid, options, urlCustParams) { registerVideoSupport('dfp', { buildVideoUrl: buildDfpVideoUrl, - buildAdpodVideoUrl: buildAdpodVideoUrl, - getAdpodTargeting: (args) => adpodUtils.getTargeting(args) }); - -submodule('adpod', adpodUtils); diff --git a/modules/dfpAdpod.js b/modules/dfpAdpod.js new file mode 100644 index 00000000000..a5bd48f60e4 --- /dev/null +++ b/modules/dfpAdpod.js @@ -0,0 +1,102 @@ +import {submodule} from '../src/hook.js'; +import {buildUrl, deepAccess, formatQS, logError, parseSizesInput} from '../src/utils.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; +import {gdprDataHandler} from '../src/consentHandler.js'; +import {registerVideoSupport} from '../src/adServerManager.js'; + +export const adpodUtils = {}; + +/** + * @typedef {Object} DfpAdpodOptions + * + * @param {string} code Ad Unit code + * @param {Object} params Query params which should be set on the DFP request. + * These will override this module's defaults whenever they conflict. + * @param {function} callback Callback function to execute when master tag is ready + */ + +/** + * Creates master tag url for long-form + * @param {DfpAdpodOptions} options + * @returns {string} A URL which calls DFP with custom adpod targeting key values to compete with rest of the demand in DFP + */ +export function buildAdpodVideoUrl({code, params, callback} = {}) { + // TODO: the public API for this does not take in enough info to fill all DFP params (adUnit/bid), + // and is marked "alpha": https://docs.prebid.org/dev-docs/publisher-api-reference/adServers.dfp.buildAdpodVideoUrl.html + if (!params || !callback) { + logError(`A params object and a callback is required to use pbjs.adServers.dfp.buildAdpodVideoUrl`); + return; + } + + const derivedParams = { + correlator: Date.now(), + sz: getSizeForAdUnit(code), + url: encodeURIComponent(location.href), + }; + + function getSizeForAdUnit(code) { + let adUnit = auctionManager.getAdUnits() + .filter((adUnit) => adUnit.code === code) + let sizes = deepAccess(adUnit[0], 'mediaTypes.video.playerSize'); + return parseSizesInput(sizes).join('|'); + } + + adpodUtils.getTargeting({ + 'codes': [code], + 'callback': createMasterTag + }); + + function createMasterTag(err, targeting) { + if (err) { + callback(err, null); + return; + } + + let initialValue = { + [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, + [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined + }; + let customParams = {}; + if (targeting[code]) { + customParams = targeting[code].reduce((acc, curValue) => { + if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_PB_CAT_DUR) { + acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] = (typeof acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] !== 'undefined') ? acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] + ',' + curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR] : curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR]; + } else if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_CACHE_ID) { + acc[adpodUtils.TARGETING_KEY_CACHE_ID] = curValue[adpodUtils.TARGETING_KEY_CACHE_ID] + } + return acc; + }, initialValue); + } + + let encodedCustomParams = encodeURIComponent(formatQS(customParams)); + + const queryParams = Object.assign({}, + DEFAULT_DFP_PARAMS, + derivedParams, + params, + { cust_params: encodedCustomParams } + ); + + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + + const masterTag = buildUrl({ + ...DFP_ENDPOINT, + search: queryParams + }); + + callback(null, masterTag); + } +} + +registerVideoSupport('dfp', { + buildAdpodVideoUrl: buildAdpodVideoUrl, + getAdpodTargeting: (args) => adpodUtils.getTargeting(args) +}); + +submodule('adpod', adpodUtils); diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index ea47c64094d..2b819789ec1 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -12,7 +12,7 @@ const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; const GVLID = 602; -const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', +const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'plcmt', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext']; diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 26a1f9c5718..5c4627cfe1b 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { deepAccess, logMessage, logError } from '../src/utils.js'; +import { deepAccess, logMessage } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'e_volution'; @@ -83,7 +83,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js deleted file mode 100644 index e830f8a94f7..00000000000 --- a/modules/ebdrBidAdapter.js +++ /dev/null @@ -1,156 +0,0 @@ -import {getBidIdParameter, logInfo} from '../src/utils.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'ebdr'; -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [ BANNER, VIDEO ], - isBidRequestValid: function(bid) { - return !!(bid && bid.params && bid.params.zoneid); - }, - buildRequests: function(bids) { - const rtbServerDomain = 'dsp.bnmla.com'; - let domain = window.location.host; - let page = window.location.pathname + location.search + location.hash; - let ebdrImps = []; - const ebdrReq = {}; - let ebdrParams = {}; - let zoneid = ''; - let requestId = ''; - bids.forEach(bid => { - logInfo('Log bid', bid); - let bidFloor = getBidIdParameter('bidfloor', bid.params); - let whArr = getWidthAndHeight(bid); - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video) ? VIDEO : BANNER; - zoneid = getBidIdParameter('zoneid', bid.params); - requestId = bid.bidderRequestId; - ebdrImps.push({ - id: bid.bidId, - [_mediaTypes]: { - w: whArr[0], - h: whArr[1] - }, - bidfloor: bidFloor - }); - ebdrReq[bid.bidId] = {mediaTypes: _mediaTypes, - w: whArr[0], - h: whArr[1] - }; - // TODO: fix lat and long to only come from request - ebdrParams['latitude'] = '0'; - ebdrParams['longitude'] = '0'; - ebdrParams['ifa'] = (getBidIdParameter('IDFA', bid.params).length > getBidIdParameter('ADID', bid.params).length) ? getBidIdParameter('IDFA', bid.params) : getBidIdParameter('ADID', bid.params); - }); - let ebdrBidReq = { - id: requestId, - imp: ebdrImps, - site: { - domain: domain, - page: page - }, - device: { - geo: { - lat: ebdrParams.latitude, - log: ebdrParams.longitude - }, - ifa: ebdrParams.ifa - } - }; - return { - method: 'GET', - url: 'https://' + rtbServerDomain + '/hb?' + '&zoneid=' + zoneid + '&br=' + encodeURIComponent(JSON.stringify(ebdrBidReq)), - bids: ebdrReq - }; - }, - interpretResponse: function(serverResponse, bidRequest) { - logInfo('Log serverResponse', serverResponse); - logInfo('Log bidRequest', bidRequest); - let ebdrResponseImps = []; - const ebdrResponseObj = serverResponse.body; - if (!ebdrResponseObj || !ebdrResponseObj.seatbid || ebdrResponseObj.seatbid.length === 0 || !ebdrResponseObj.seatbid[0].bid || ebdrResponseObj.seatbid[0].bid.length === 0) { - return []; - } - ebdrResponseObj.seatbid[0].bid.forEach(ebdrBid => { - let responseCPM; - responseCPM = parseFloat(ebdrBid.price); - let adm; - let type; - let _mediaTypes; - let vastURL; - if (bidRequest.bids[ebdrBid.id].mediaTypes == BANNER) { - adm = decodeURIComponent(ebdrBid.adm) - type = 'ad'; - _mediaTypes = BANNER; - } else { - adm = ebdrBid.adm - type = 'vastXml' - _mediaTypes = VIDEO; - if (ebdrBid.nurl) { - vastURL = ebdrBid.nurl; - } - } - let response = { - requestId: ebdrBid.id, - [type]: adm, - mediaType: _mediaTypes, - creativeId: ebdrBid.crid, - cpm: responseCPM, - width: ebdrBid.w, - height: ebdrBid.h, - currency: 'USD', - netRevenue: true, - ttl: 3600, - meta: { - advertiserDomains: ebdrBid.adomain || [] - } - }; - if (vastURL) { - response.vastUrl = vastURL; - } - ebdrResponseImps.push(response); - }); - return ebdrResponseImps; - }, - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = [] - if (syncOptions.pixelEnabled) { - const ebdrResponseObj = serverResponses.body; - if (!ebdrResponseObj || !ebdrResponseObj.seatbid || ebdrResponseObj.seatbid.length === 0 || !ebdrResponseObj.seatbid[0].bid || ebdrResponseObj.seatbid[0].bid.length === 0) { - return []; - } - ebdrResponseObj.seatbid[0].bid.forEach(ebdrBid => { - if (ebdrBid.iurl && ebdrBid.iurl.length > 0) { - syncs.push({ - type: 'image', - url: ebdrBid.iurl - }); - } - }); - } - return syncs; - } -} -function getWidthAndHeight(bid) { - let adW = null; - let adH = null; - // Handing old bidder only has size object - if (bid.sizes && bid.sizes.length) { - let sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW = bid.sizes[0]; - adH = bid.sizes[1]; - } - } - let _mediaTypes = bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER; - if (bid.mediaTypes && bid.mediaTypes[_mediaTypes]) { - if (_mediaTypes == BANNER && bid.mediaTypes[_mediaTypes].sizes && bid.mediaTypes[_mediaTypes].sizes[0] && bid.mediaTypes[_mediaTypes].sizes[0].length === 2) { - adW = bid.mediaTypes[_mediaTypes].sizes[0][0]; - adH = bid.mediaTypes[_mediaTypes].sizes[0][1]; - } else if (_mediaTypes == VIDEO && bid.mediaTypes[_mediaTypes].playerSize && bid.mediaTypes[_mediaTypes].playerSize.length === 2) { - adW = bid.mediaTypes[_mediaTypes].playerSize[0]; - adH = bid.mediaTypes[_mediaTypes].playerSize[1]; - } - } - return [adW, adH]; -} -registerBidder(spec); diff --git a/modules/ebdrBidAdapter.md b/modules/ebdrBidAdapter.md deleted file mode 100644 index 64483797b25..00000000000 --- a/modules/ebdrBidAdapter.md +++ /dev/null @@ -1,53 +0,0 @@ -# Overview - -``` -Module Name: EngageBDR Bid Adapter -Module Type: Bidder Adapter -Maintainer: tech@engagebdr.com -``` - -# Description - -Adapter that connects to EngageBDR's demand sources. - -# Test Parameters -``` - var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'ebdr', - params: { - zoneid: '99999', - bidfloor: '1.00', - IDFA:'xxx-xxx', - ADID:'xxx-xxx', - latitude:'34.089811', - longitude:'-118.392805' - } - }] - },{ - code: 'test-video', - mediaTypes: { - video: { - context: 'instream', - playerSize: [300, 250] - } - }, - bids: [{ - bidder: 'ebdr', - params: { - zoneid: '99998', - bidfloor: '1.00', - IDFA:'xxx-xxx', - ADID:'xxx-xxx', - latitude:'34.089811', - longitude:'-118.392805' - } - }] - }]; -``` diff --git a/modules/edge226BidAdapter.js b/modules/edge226BidAdapter.js index 6d1e2466abe..f0b91183a3e 100644 --- a/modules/edge226BidAdapter.js +++ b/modules/edge226BidAdapter.js @@ -57,6 +57,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js index 7a2fdae8adf..30a63ea5942 100644 --- a/modules/emtvBidAdapter.js +++ b/modules/emtvBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js deleted file mode 100644 index 59d5d326109..00000000000 --- a/modules/enrichmentFpdModule.js +++ /dev/null @@ -1,2 +0,0 @@ -// Logic from this module was moved into core since approx. 7.27 -// TODO: remove this in v8 diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js deleted file mode 100644 index 45a0be54715..00000000000 --- a/modules/eplanningAnalyticsAdapter.js +++ /dev/null @@ -1,130 +0,0 @@ -import { logError } from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; - -const analyticsType = 'endpoint'; -const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/'; - -function auctionEndHandler(args) { - return {auctionId: args.auctionId}; -} - -function auctionInitHandler(args) { - return { - auctionId: args.auctionId, - time: args.timestamp - }; -} - -function bidRequestedHandler(args) { - return { - auctionId: args.auctionId, - time: args.start, - bidder: args.bidderCode, - bids: args.bids.map(function(bid) { - return { - time: bid.startTime, - bidder: bid.bidder, - placementCode: bid.placementCode, - auctionId: bid.auctionId, - sizes: bid.sizes - }; - }), - }; -} - -function bidResponseHandler(args) { - return { - bidder: args.bidder, - size: args.size, - auctionId: args.auctionId, - cpm: args.cpm, - time: args.responseTimestamp, - }; -} - -function bidWonHandler(args) { - return { - auctionId: args.auctionId, - size: args.width + 'x' + args.height, - }; -} - -function bidTimeoutHandler(args) { - return args.map(function(bid) { - return { - bidder: bid.bidder, - auctionId: bid.auctionId - }; - }) -} - -function callHandler(evtype, args) { - let handler = null; - - if (evtype === EVENTS.AUCTION_INIT) { - handler = auctionInitHandler; - eplAnalyticsAdapter.context.events = []; - } else if (evtype === EVENTS.AUCTION_END) { - handler = auctionEndHandler; - } else if (evtype === EVENTS.BID_REQUESTED) { - handler = bidRequestedHandler; - } else if (evtype === EVENTS.BID_RESPONSE) { - handler = bidResponseHandler - } else if (evtype === EVENTS.BID_TIMEOUT) { - handler = bidTimeoutHandler; - } else if (evtype === EVENTS.BID_WON) { - handler = bidWonHandler; - } - - if (handler) { - eplAnalyticsAdapter.context.events.push({ec: evtype, p: handler(args)}); - } -} - -var eplAnalyticsAdapter = Object.assign(adapter( - { - EPL_HOST, - analyticsType - }), -{ - track({eventType, args}) { - if (typeof args !== 'undefined') { - callHandler(eventType, args); - } - - if (eventType === EVENTS.AUCTION_END) { - try { - let strjson = JSON.stringify(eplAnalyticsAdapter.context.events); - ajax(eplAnalyticsAdapter.context.host + eplAnalyticsAdapter.context.ci + '?d=' + encodeURIComponent(strjson)); - } catch (err) {} - } - } -} -); - -eplAnalyticsAdapter.originEnableAnalytics = eplAnalyticsAdapter.enableAnalytics; - -eplAnalyticsAdapter.enableAnalytics = function (config) { - if (!config.options.ci) { - logError('Client ID (ci) option is not defined. Analytics won\'t work'); - return; - } - - eplAnalyticsAdapter.context = { - events: [], - host: config.options.host || EPL_HOST, - ci: config.options.ci - }; - - eplAnalyticsAdapter.originEnableAnalytics(config); -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: eplAnalyticsAdapter, - code: 'eplanning' -}); - -export default eplAnalyticsAdapter; diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md index 238881db96b..42ae663b090 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -44,6 +44,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. -enrichmentFpdModule validationFpdModule -topicsFpdModule \ No newline at end of file +topicsFpdModule diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index 5b5d97c2cac..eeecf152869 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index df0336c6cd4..8f6f3f74cea 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -742,7 +742,7 @@ function bidToTag(bid) { } tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords); - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index ab59c6febec..bcd382e507a 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; +import { deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -80,18 +80,9 @@ export const spec = { if (validBidRequests && validBidRequests.length === 0) return [] let accuontId = validBidRequests[0].params.accountId; const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); - let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; let bids = []; for (let bidRequest of validBidRequests) { let impObject = prepareImpObject(bidRequest); @@ -105,8 +96,8 @@ export const spec = { language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { - page: location.pathname, - host: location.host + page: location?.page, + host: location?.domain }, source: { tid: bidderRequest?.ortb2?.source?.tid, @@ -332,7 +323,7 @@ const parseSizes = (bid, mediaType) => { const addVideoParameters = (bidRequest) => { let videoObj = {}; - let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] for (let param of supportParamsList) { if (bidRequest.mediaTypes.video[param] !== undefined) { diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index bf5b4a55dbb..65b1bf24eef 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -4,7 +4,7 @@ import { isGptPubadsDefined, logInfo, pick, - deepSetValue + deepSetValue, logWarn } from '../src/utils.js'; import {config} from '../src/config.js'; import {getHook} from '../src/hook.js'; @@ -113,6 +113,10 @@ export const appendPbAdSlot = adUnit => { return true; }; +function warnDeprecation(adUnit) { + logWarn(`pbadslot is deprecated and will soon be removed, use gpid instead`, adUnit) +} + export const makeBidRequestsHook = (fn, adUnits, ...args) => { appendGptSlots(adUnits); const { useDefaultPreAuction, customPreAuction } = _currentConfig; @@ -122,15 +126,18 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; const context = adUnit.ortb2Imp.ext; - // if neither new confs set do old stuff if (!customPreAuction && !useDefaultPreAuction) { + warnDeprecation(adUnit); const usedAdUnitCode = appendPbAdSlot(adUnit); // gpid should be set to itself if already set, or to what pbadslot was (as long as it was not adUnit code) if (!context.gpid && !usedAdUnitCode) { context.gpid = context.data.pbadslot; } } else { + if (context.data?.pbadslot) { + warnDeprecation(adUnit); + } let adserverSlot = deepAccess(context, 'data.adserver.adslot'); let result; if (customPreAuction) { @@ -153,7 +160,7 @@ const handleSetGptConfig = moduleConfig => { typeof customGptSlotMatching === 'function' && customGptSlotMatching, 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot, 'customPreAuction', customPreAuction => typeof customPreAuction === 'function' && customPreAuction, - 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction === true, + 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction ?? true, ]); if (_currentConfig.enabled) { diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 5c604709b4b..930e0922829 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -24,7 +24,7 @@ const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; -export const HALOID_LOCAL_NAME = 'auHadronId'; +export const HADRONID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); @@ -132,7 +132,6 @@ export function addRealTimeData(bidConfig, rtd, rtdConfig) { if (rtdConfig.params && rtdConfig.params.handleRtd) { rtdConfig.params.handleRtd(bidConfig, rtd, rtdConfig, config); } else { - // TODO: this and haloRtdProvider are a copy-paste of each other if (isPlainObject(rtd.ortb2)) { mergeLazy(bidConfig.ortb2Fragments?.global, rtd.ortb2); } @@ -165,9 +164,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { } } - const userIds = typeof getGlobal().getUserIds === 'function' ? (getGlobal()).getUserIds() : {}; + const userIds = {}; - let hadronId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { if (typeof getGlobal().refreshUserIds === 'function') { (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index f046c860562..c72d21d08b4 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -5,8 +5,6 @@ import { logMessage, triggerPixel, } from '../src/utils.js'; -import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -19,8 +17,6 @@ const TIME_TO_LIVE = 300 const TMAX = 500 let wurlMap = {} -events.on(EVENTS.BID_WON, bidWonHandler) - export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -120,6 +116,15 @@ export const spec = { return syncs }, + + onBidWon(bid) { + const wurl = getWurl(bid.requestId) + if (wurl) { + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) + triggerPixel(wurl) + removeWurl(bid.requestId) + } + } } function getImp(bid) { @@ -176,13 +181,4 @@ function getWurl(requestId) { } } -function bidWonHandler(bid) { - const wurl = getWurl(bid.requestId) - if (wurl) { - logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) - triggerPixel(wurl) - removeWurl(bid.requestId) - } -} - registerBidder(spec) diff --git a/modules/idImportLibrary.js b/modules/idImportLibrary.js index e1847edfce4..f6819a485e0 100644 --- a/modules/idImportLibrary.js +++ b/modules/idImportLibrary.js @@ -155,7 +155,7 @@ function handleTargetElement() { const targetElement = document.getElementById(conf.target); if (targetElement) { - email = targetElement.innerText; + email = targetElement.textContent; if (!email) { _logInfo('Finding the email with observer'); diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js deleted file mode 100644 index 8e6e3c20a64..00000000000 --- a/modules/idWardRtdProvider.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This module adds the ID Ward RTD provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * The module will populate real-time data from ID Ward - * @module modules/idWardRtdProvider - * @requires module:modules/realTimeData - */ -import { createRtdProvider } from './anonymisedRtdProvider.js';/* eslint prebid/validate-imports: "off" */ - -export const { getRealTimeData, rtdSubmodule: idWardRtdSubmodule, storage } = createRtdProvider('idWard'); diff --git a/modules/idWardRtdProvider.md b/modules/idWardRtdProvider.md deleted file mode 100644 index 1c9f0654de6..00000000000 --- a/modules/idWardRtdProvider.md +++ /dev/null @@ -1,51 +0,0 @@ -> **Warning!** -> -> The **idWardRtdProvider** module has been renamed to [anonymisedRtdProvider](anonymisedRtdProvider.md) in light of the company's rebranding. -> **idWardRtdProvider** module is maintained for backward compatibility until the next major Prebid release. -> -> Please use anonymisedRtdProvider instead of idWardRtdProvider in your Prebid integration. - -### Overview - -ID Ward is a data anonymization technology for privacy-preserving advertising. Publishers and advertisers are able to target and retarget custom audience segments covering 100% of consented audiences. -ID Ward’s Real-time Data Provider automatically obtains segment IDs from the ID Ward on-domain script (via localStorage) and passes them to the bid-stream. - -### Integration - - 1) Build the idWardRtd module into the Prebid.js package with: - - ``` - gulp build --modules=idWardRtdProvider,... - ``` - - 2) Use `setConfig` to instruct Prebid.js to initilaize the idWardRtdProvider module, as specified below. - -### Configuration - -``` - pbjs.setConfig({ - realTimeData: { - dataProviders: [ - { - name: "idWard", - waitForIt: true, - params: { - cohortStorageKey: "cohort_ids", - segtax: , - } - } - ] - } - }); - ``` - -Please note that idWardRtdProvider should be integrated into the publisher website along with the [ID Ward Pixel](https://publishers-web.id-ward.com/pixel-integration). -Please reach out to Id Ward representative(support@id-ward.com) if you have any questions or need further help to integrate Prebid, idWardRtdProvider, and Id Ward Pixel - -### Testing -To view an example of available segments returned by Id Ward: -``` -‘gulp serve --modules=rtdModule,idWardRtdProvider,pubmaticBidAdapter -``` -and then point your browser at: -"http://localhost:9999/integrationExamples/gpt/idward_segments_example.html" \ No newline at end of file diff --git a/modules/illuminBidAdapter.js b/modules/illuminBidAdapter.js index 45b74bec5c9..e830c27a32b 100644 --- a/modules/illuminBidAdapter.js +++ b/modules/illuminBidAdapter.js @@ -150,15 +150,9 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { key = `uid.${idSystemProviderName}`; switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; case 'lipb': payloadRef[key] = userId.lipbid; break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; case 'id5id': payloadRef[key] = userId.uid; break; diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index c1108d779cb..2d67caf6a61 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -5,6 +5,12 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +/** + * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception + * ImproveDigital only imports after winning a bid and only if the creative cannot reach top + * Also see https://github.com/prebid/Prebid.js/issues/11656 + */ +// eslint-disable-next-line no-restricted-imports import {loadExternalScript} from '../src/adloader.js'; /** @@ -42,7 +48,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid(bid) { - return !!(bid && bid.params && (bid.params.placementId || (bid.params.placementKey && bid.params.publisherId))); + return !!(bid && bid.params && bid.params.placementId && bid.params.publisherId); }, /** @@ -136,14 +142,11 @@ export const CONVERTER = ortbConverter({ } const bidderParamsPath = context.extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; const placementId = bidRequest.params.placementId; - if (placementId) { - deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); - if (context.extendMode) { - deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); - } - } else { - deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params)); - deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params)); + const publisherId = bidRequest.params.publisherId; + deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); + deepSetValue(imp, `${bidderParamsPath}.publisherId`, publisherId); + if (context.extendMode) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); } deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); @@ -206,9 +209,9 @@ export const CONVERTER = ortbConverter({ overrides: { imp: { banner(fillImpBanner, imp, bidRequest, context) { - // override to disregard banner.sizes if usePrebidSizes is not set + // override to disregard banner.sizes if usePrebidSizes is false if (!bidRequest.mediaTypes[BANNER]) return; - if (config.getConfig('improvedigital.usePrebidSizes') !== true) { + if (config.getConfig('improvedigital.usePrebidSizes') === false) { const banner = Object.assign({}, bidRequest.mediaTypes[BANNER], {sizes: null}); bidRequest = {...bidRequest, mediaTypes: {[BANNER]: banner}} } diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md index 15602d11038..7206dd8ba7b 100644 --- a/modules/improvedigitalBidAdapter.md +++ b/modules/improvedigitalBidAdapter.md @@ -17,6 +17,7 @@ Module that connects to Improve Digital's demand sources { bidder: 'improvedigital', params: { + publisherId: 123, placementId:1053688 } } @@ -27,6 +28,7 @@ Module that connects to Improve Digital's demand sources bids: [{ bidder: 'improvedigital', params: { + publisherId: 123, placementId:1053689, keyValues: { testKey: ["testValue"] @@ -39,6 +41,7 @@ Module that connects to Improve Digital's demand sources bids: [{ bidder: 'improvedigital', params: { + publisherId: 123, placementId:1053687, size: { w:300, @@ -47,4 +50,4 @@ Module that connects to Improve Digital's demand sources } }] }]; -``` \ No newline at end of file +``` diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js deleted file mode 100644 index c94a88748a7..00000000000 --- a/modules/iqmBidAdapter.js +++ /dev/null @@ -1,277 +0,0 @@ -import {_each, deepAccess, getBidIdParameter, isArray} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {INSTREAM} from '../src/video.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - */ - -const BIDDER_CODE = 'iqm'; -const VERSION = 'v.1.0.0'; -const VIDEO_ORTB_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'placement', - 'protocols', - 'startdelay' -]; -var ENDPOINT_URL = 'https://pbd.bids.iqm.com'; - -export const spec = { - supportedMediaTypes: [BANNER, VIDEO], - code: BIDDER_CODE, - aliases: ['iqm'], - - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - const banner = deepAccess(bid, 'mediaTypes.banner'); - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - if ((videoMediaType && context === INSTREAM)) { - const videoBidderParams = deepAccess(bid, 'params.video', {}); - - if (!Array.isArray(videoMediaType.playerSize)) { - return false; - } - - if (!videoMediaType.context) { - return false; - } - - const videoParams = { - ...videoMediaType, - ...videoBidderParams - }; - - if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { - return false; - } - - if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { - return false; - } - - if ( - typeof videoParams.placement !== 'undefined' && - typeof videoParams.placement !== 'number' - ) { - return false; - } - if ( - videoMediaType.context === INSTREAM && - typeof videoParams.startdelay !== 'undefined' && - typeof videoParams.startdelay !== 'number' - ) { - return false; - } - - return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId); - } else { - if (banner === 'undefined') { - return false; - } - return !!(bid && bid.params && bid.params.publisherId && bid.params.placementId); - } - }, - /** - * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. - *It prepares a bid request with the required information for the DSP side and sends this request to alloted endpoint - * parameter{validBidRequests, bidderRequest} bidderRequest object is useful because it carries a couple of bid parameters that are global to all the bids. - */ - buildRequests: function (validBidRequests, bidderRequest) { - return validBidRequests.map(bid => { - var finalRequest = {}; - let bidfloor = getBidIdParameter('bidfloor', bid.params); - - const imp = { - id: bid.bidId, - secure: 1, - bidfloor: bidfloor || 0, - displaymanager: 'Prebid.js', - displaymanagerver: VERSION, - - } - if (deepAccess(bid, 'mediaTypes.banner')) { - imp.banner = getSize(bid.sizes); - imp.mediatype = 'banner'; - } else if (deepAccess(bid, 'mediaTypes.video')) { - imp.video = _buildVideoORTB(bid); - imp.mediatype = 'video'; - } - const site = getSite(bidderRequest); - let device = getDevice(bid.params); - finalRequest = { - sizes: bid.sizes, - id: bid.bidId, - publisherId: getBidIdParameter('publisherId', bid.params), - placementId: getBidIdParameter('placementId', bid.params), - device: device, - site: site, - imp: imp, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bid.auctionId, - adUnitCode: bid.adUnitCode, - bidderRequestId: bid.bidderRequestId, - uuid: bid.bidId, - // TODO: please do not send internal data structures over the network - // I am not going to attempt to accommodate this, no way this is usable on their end, it changes way too frequently - bidderRequest - } - const request = { - method: 'POST', - url: ENDPOINT_URL, - data: finalRequest, - options: { - withCredentials: false - }, - - } - return request; - }); - }, - /** - * Takes Response from server as input and request. - *It parses the response from server side and generates bidresponses for with required rendering paramteres - * parameter{serverResponse, bidRequest} serverReponse: Response from the server side with ad creative. - */ - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - serverResponse = serverResponse.body; - if (serverResponse && isArray(serverResponse.seatbid)) { - _each(serverResponse.seatbid, function (bidList) { - _each(bidList.bid, function (bid) { - const responseCPM = parseFloat(bid.price); - if (responseCPM > 0.0 && bid.impid) { - const bidResponse = { - requestId: bidRequest.data.id, - currency: serverResponse.cur || 'USD', - cpm: responseCPM, - netRevenue: true, - creativeId: bid.crid || '', - adUnitCode: bidRequest.data.adUnitCode, - auctionId: bidRequest.data.auctionId, - mediaType: bidRequest.data.imp.mediatype, - - ttl: bid.ttl || 60 - }; - - if (bidRequest.data.imp.mediatype === VIDEO) { - bidResponse.width = bid.w || bidRequest.data.imp.video.w; - bidResponse.height = bid.h || bidRequest.data.imp.video.h; - bidResponse.adResponse = { - content: bid.adm, - height: bidRequest.data.imp.video.h, - width: bidRequest.data.imp.video.w - }; - - if (bidRequest.data.imp.video.context === INSTREAM) { - bidResponse.vastUrl = bid.adm; - } - } else if (bidRequest.data.imp.mediatype === BANNER) { - bidResponse.ad = bid.adm; - bidResponse.width = bid.w || bidRequest.data.imp.banner.w; - bidResponse.height = bid.h || bidRequest.data.imp.banner.h; - } - bidResponses.push(bidResponse); - } - }) - }); - } - return bidResponses; - }, - -}; - -let getDevice = function (bidparams) { - const language = navigator.language ? 'language' : 'userLanguage'; - return { - geo: bidparams.geo, - h: screen.height, - w: screen.width, - dnt: _getDNT() ? 1 : 0, - language: navigator[language].split('-')[0], - make: navigator.vendor ? navigator.vendor : '', - ua: navigator.userAgent, - devicetype: _isMobile() ? 1 : _isConnectedTV() ? 3 : 2 - }; -}; - -let _getDNT = function () { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes'; -}; - -let getSize = function (sizes) { - let sizeMap; - if (sizes.length === 2 && typeof sizes[0] === 'number' && typeof sizes[1] === 'number') { - sizeMap = {w: sizes[0], h: sizes[1]}; - } else { - sizeMap = {w: sizes[0][0], h: sizes[0][1]}; - } - return sizeMap; -}; - -function _isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(global.navigator.userAgent); -} - -function _isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent); -} - -function getSite(bidderRequest) { - let domain = ''; - let page = ''; - let referrer = ''; - const Id = 1; - - const {refererInfo} = bidderRequest; - - // TODO: are these the right refererInfo values? - domain = refererInfo.domain; - page = refererInfo.page; - referrer = refererInfo.ref; - - return { - domain, - page, - Id, - referrer - }; -}; - -function _buildVideoORTB(bidRequest) { - const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); - const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); - const video = {}; - - const videoParams = { - ...videoAdUnit, - ...videoBidderParams // Bidder Specific overrides - }; - video.context = 1; - const {w, h} = getSize(videoParams.playerSize[0]); - video.w = w; - video.h = h; - - VIDEO_ORTB_PARAMS.forEach((param) => { - if (videoParams.hasOwnProperty(param)) { - video[param] = videoParams[param]; - } - }); - - video.placement = video.placement || 2; - - video.startdelay = video.startdelay || 0; - video.placement = 1; - video.context = INSTREAM; - - return video; -} -registerBidder(spec); diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 3ce622dba10..0af26ccf6e7 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, logError, deepAccess } from '../src/utils.js'; +import { logMessage, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -91,7 +91,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 4fcf7830685..e182634b52a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -26,7 +26,6 @@ import { Renderer } from '../src/Renderer.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'ix'; -const ALIAS_BIDDER_CODE = 'roundel'; const GLOBAL_VENDOR_ID = 10; const SECURE_BID_URL = 'https://htlb.casalemedia.com/openrtb/pbjs'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; @@ -74,7 +73,6 @@ const SOURCE_RTI_MAPPING = { 'google.com': '' }; const PROVIDERS = [ - 'britepoolid', 'lipbid', 'criteoId', 'merkleId', @@ -696,11 +694,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { addRTI(userEids, eidInfo); } - // If `roundel` alias bidder, only send requests if liveramp ids exist. - if (bidderRequest && bidderRequest.bidderCode === ALIAS_BIDDER_CODE && !eidInfo.seenSources['liveramp.com']) { - return []; - } - const requests = []; let r = createRequest(validBidRequests); @@ -708,7 +701,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) // getting ixdiags for adunits of the video, outstream & multi format (MF) style - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') let ixdiag = buildIXDiag(validBidRequests, fledgeEnabled); for (let key in ixdiag) { r.ext.ixdiag[key] = ixdiag[key]; @@ -1439,7 +1432,7 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde bannerImps[validBidRequest.adUnitCode].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); // Add Fledge flag if enabled - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') if (fledgeEnabled) { const auctionEnvironment = deepAccess(validBidRequest, 'ortb2Imp.ext.ae') const paapi = deepAccess(validBidRequest, 'ortb2Imp.ext.paapi') @@ -1452,8 +1445,6 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde } else { logWarn('error setting auction environment flag - must be an integer') } - } else if (deepAccess(bidderRequest, 'defaultForSlots') == 1) { - bannerImps[validBidRequest.adUnitCode].ae = 1 } } @@ -1609,11 +1600,6 @@ export const spec = { code: BIDDER_CODE, gvlid: GLOBAL_VENDOR_ID, - aliases: [{ - code: ALIAS_BIDDER_CODE, - gvlid: GLOBAL_VENDOR_ID, - skipPbsAliasing: false - }], supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -1865,7 +1851,7 @@ export const spec = { try { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } catch (error) { logWarn('Error attaching AuctionConfigs', error); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 0705c5932cf..f2f6d97daf9 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -472,7 +472,7 @@ The timeout value must be a positive whole number in milliseconds. Protected Audience API (FLEDGE) =========================== -In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) module to build and enable Fledge. +In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module to build and enable Fledge. Additional Information ====================== diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 29ce0da5317..a06404e52f2 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -31,9 +31,7 @@ const playlistItemCache = {}; const pendingRequests = {}; let activeRequestCount = 0; let resumeBidRequest; -// defaults to 'always' for backwards compatibility -// TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY -let overrideContentId = ENRICH_ALWAYS; +let overrideContentId = ENRICH_WHEN_EMPTY; let overrideContentUrl = ENRICH_WHEN_EMPTY; let overrideContentTitle = ENRICH_WHEN_EMPTY; let overrideContentDescription = ENRICH_WHEN_EMPTY; @@ -83,9 +81,7 @@ export function fetchTargetingInformation(jwTargeting) { } export function setOverrides(params) { - // For backwards compatibility, default to always unless overridden by Publisher. - // TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY - overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_ALWAYS); + overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_WHEN_EMPTY); overrideContentUrl = sanitizeOverrideParam(params.overrideContentUrl, ENRICH_WHEN_EMPTY); overrideContentTitle = sanitizeOverrideParam(params.overrideContentTitle, ENRICH_WHEN_EMPTY); overrideContentDescription = sanitizeOverrideParam(params.overrideContentDescription, ENRICH_WHEN_EMPTY); diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 936cd1d10a2..44d696eea6d 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -12,16 +12,20 @@ Publishers must register JW Player as a real time data provider by setting up a following structure: ```javascript -const jwplayerDataProvider = { - name: "jwplayer" -}; - pbjs.setConfig({ ..., realTimeData: { - dataProviders: [ - jwplayerDataProvider - ] + dataProviders: [{ + name: 'jwplayer', + waitForIt: true, + params: { + mediaIDs: ['abc', 'def', 'ghi', 'jkl'], + overrideContentId: 'always', + overrideContentUrl: 'always', + overrideContentTitle: 'always', + overrideContentDescription: 'always' + } + }] } }); ``` @@ -86,7 +90,7 @@ realTimeData = { | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | params | Object | | | | params.mediaIDs | Array of Strings | Media Ids for prefetching | Optional | -| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'always' | +| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'whenEmpty' | | params.overrideContentUrl | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.url | Defaults to 'whenEmpty' | | params.overrideContentTitle | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.title | Defaults to 'whenEmpty' | | params.overrideContentDescription | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.ext.description | Defaults to 'whenEmpty' | @@ -155,7 +159,7 @@ To view an example: - in your browser, navigate to: -`http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` +`http://localhost:9999/integrationExamples/realTimeData/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 210fb6d5d59..91d067ab67d 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -251,7 +251,7 @@ function interpretResponse(response, bidRequest) { if (fledgeAuctionConfigs.length > 0) { return { bids: bidResponses, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs } } else { return bidResponses; diff --git a/modules/kiviadsBidAdapter.js b/modules/kiviadsBidAdapter.js index 13739d57cb2..cc1e319c348 100644 --- a/modules/kiviadsBidAdapter.js +++ b/modules/kiviadsBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index c4507201064..b2c1548a02d 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -1,4 +1,4 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { isFn, deepAccess } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -55,23 +55,15 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; const placements = []; const request = { deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', secure: 1, - host: location.host, - page: location.pathname, + host: location?.domain ?? '', + page: location?.page ?? '', placements: placements }; @@ -107,6 +99,7 @@ export const spec = { placement.protocols = bid.mediaTypes[VIDEO].protocols; placement.startdelay = bid.mediaTypes[VIDEO].startdelay; placement.placement = bid.mediaTypes[VIDEO].placement; + placement.plcmt = bid.mediaTypes[VIDEO].plcmt; placement.skip = bid.mediaTypes[VIDEO].skip; placement.skipafter = bid.mediaTypes[VIDEO].skipafter; placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 5a5536e0c1a..63a01bfea02 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -417,6 +417,7 @@ function populateVideoParams(params, bid) { const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); const placement = deepAccess(bid, `mediaTypes.video.placement`); + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); const playbackMethod = getPlaybackMethod(bid); const skip = deepAccess(bid, `mediaTypes.video.skip`); @@ -435,7 +436,9 @@ function populateVideoParams(params, bid) { if (placement) { params.placement = placement; } - + if (plcmt) { + params.plcmt = plcmt; + } if (playbackMethod) { params.playbackMethod = playbackMethod; } diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index d4b1cdea0d1..6c97f64e6a8 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -47,7 +47,7 @@ export const spec = { const GDPR = BIDDER_GDPR || bid.params.gdpr || null; const GDPRS = BIDDER_GDPRS || bid.params.gdprs || null; const DNT = bid.params.dnt || null; - const BID_FLOOR = bid.params.flrd > bid.params.flrmp ? bid.params.flrd : bid.params.flrmp; + const BID_FLOOR = 0; const VIDEO_BID = bid.video ? bid.video : {}; const requestData = { @@ -157,7 +157,6 @@ export const spec = { h: sizes[1], skip: VIDEO_BID.skip || 0, playbackmethod: VIDEO_BID.playbackmethod || [1], - placement: (bid.params.execution === 'outstream' || VIDEO_BID.context === 'outstream') ? 5 : 1, ext: { lkqdcustomparameters: {} }, diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js index 7aa82e3046c..bec23cddc2d 100644 --- a/modules/loganBidAdapter.js +++ b/modules/loganBidAdapter.js @@ -99,6 +99,7 @@ export const spec = { placement.protocols = mediaType[VIDEO].protocols; placement.startdelay = mediaType[VIDEO].startdelay; placement.placement = mediaType[VIDEO].placement; + placement.plcmt = mediaType[VIDEO].plcmt; placement.skip = mediaType[VIDEO].skip; placement.skipafter = mediaType[VIDEO].skipafter; placement.minbitrate = mediaType[VIDEO].minbitrate; diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index fe4dd83c9e2..e7c5300d072 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { if (fledgeAuctionConfigs.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } @@ -74,7 +74,7 @@ function newBidRequest(bidRequest, bidderRequest) { mediaTypes: bidRequest.mediaTypes, } - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') if (fledgeEnabled) { const ae = deepAccess(bidRequest, 'ortb2Imp.ext.ae'); if (ae) { diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js index 30fdeb44233..ffa88529b2f 100644 --- a/modules/loyalBidAdapter.js +++ b/modules/loyalBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index b95dfc08732..ffc2307bcb8 100755 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -134,7 +134,7 @@ function interpretResponse(serverResponse, bidRequest) { } })); - return {bids, fledgeAuctionConfigs}; + return {bids, paapi: fledgeAuctionConfigs}; } function report(type, data) { diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index 66838014e18..470a11510c5 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,4 +1,3 @@ -import { logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -39,14 +38,7 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; + location = bidderRequest?.refererInfo ?? null; const placements = []; const request = { @@ -54,8 +46,8 @@ export const spec = { 'deviceHeight': winTop.screen.height, 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', 'secure': 1, - 'host': location.host, - 'page': location.pathname, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'placements': placements }; diff --git a/modules/madvertiseBidAdapter.js b/modules/madvertiseBidAdapter.js index 3b031623aef..9fc7ceb68aa 100644 --- a/modules/madvertiseBidAdapter.js +++ b/modules/madvertiseBidAdapter.js @@ -1,5 +1,4 @@ import { parseSizesInput, _each } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; /** @@ -27,9 +26,6 @@ export const spec = { if (sizes.length > 0 && sizes[0] === undefined) { return false; } - if (typeof bid.params.floor == 'undefined' || parseFloat(bid.params.floor) < 0.01) { - bid.params.floor = 0.01; - } return typeof bid.params.s != 'undefined'; }, @@ -58,7 +54,7 @@ export const spec = { } if (bidderRequest && bidderRequest.gdprConsent) { - src = src + '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0') + '&consent[0][format]=' + config.getConfig('consentManagement.cmpApi') + '&consent[0][value]=' + bidderRequest.gdprConsent.consentString; + src = src + '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0') + '&consent[0][format]=iab&consent[0][value]=' + bidderRequest.gdprConsent.consentString; } return { diff --git a/modules/marsmediaAnalyticsAdapter.js b/modules/marsmediaAnalyticsAdapter.js deleted file mode 100644 index f1e53a3c20c..00000000000 --- a/modules/marsmediaAnalyticsAdapter.js +++ /dev/null @@ -1,53 +0,0 @@ -import {ajax} from '../src/ajax.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import {getGlobal} from '../src/prebidGlobal.js'; - -/**** - * Mars Media Analytics - * Contact: prebid@m-m-g.com‏ - * Developer: Chen Saadia - */ - -const MARS_BIDDER_CODE = 'marsmedia'; -const analyticsType = 'endpoint'; -const MARS_VERSION = '1.0.1'; -const MARS_ANALYTICS_URL = 'https://prebid_stats.mars.media/prebidjs/api/analytics.php'; -var events = {}; - -var marsmediaAnalyticsAdapter = Object.assign(adapter( - { - MARS_ANALYTICS_URL, - analyticsType - }), -{ - track({eventType, args}) { - if (typeof args !== 'undefined' && args.bidderCode === MARS_BIDDER_CODE) { - events[eventType] = args; - } - - if (eventType === 'auctionEnd') { - setTimeout(function() { - ajax( - MARS_ANALYTICS_URL, - { - success: function() {}, - error: function() {} - }, - JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': getGlobal().getBidResponses(), ver: MARS_VERSION}), - { - method: 'POST' - } - ); - }, 3000); - } - } -} -); - -adapterManager.registerAnalyticsAdapter({ - adapter: marsmediaAnalyticsAdapter, - code: 'marsmedia' -}); - -export default marsmediaAnalyticsAdapter; diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index c7e31198673..32eea23c8a6 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -729,7 +729,7 @@ function bidToTag(bid) { if (!isEmpty(bid.params.keywords)) { tag.keywords = getANKewyordParamFromMaps(bid.params.keywords); } - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index f4967fed170..987b2689f6b 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -66,6 +66,7 @@ const ORTB_VIDEO_PARAMS = { h: value => isInteger(value), startdelay: value => isInteger(value), placement: value => [1, 2, 3, 4, 5].indexOf(value) !== -1, + plcmt: value => [1, 2, 3, 4].indexOf(value) !== -1, linearity: value => [1, 2].indexOf(value) !== -1, skip: value => [0, 1].indexOf(value) !== -1, skipmin: value => isInteger(value), diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 4d4cf0d80ed..5949e198fd1 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -261,7 +261,7 @@ function slotParams(bidRequest, bidderRequests) { if (floorInfo && floorInfo.length > 0) { params.bidfloors = floorInfo; } - if (bidderRequests.fledgeEnabled) { + if (bidderRequests.paapi?.enabled) { params.ext.ae = bidRequest?.ortb2Imp?.ext?.ae; } return params; @@ -508,7 +508,7 @@ export const spec = { } return { bids: validBids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } }, getUserSyncs: function(syncOptions, serverResponses) { diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md index d401a72f1f6..500c9f3f12b 100644 --- a/modules/medianetBidAdapter.md +++ b/modules/medianetBidAdapter.md @@ -186,12 +186,12 @@ var adUnits = [{ In order to enable PAAPI auctions follow the instructions below: -1. Add the fledgeForGpt and paapi modules to your prebid bundle. +1. Add the paapiForGpt and paapi modules to your prebid bundle. 2. Add the following configuration for the module ``` pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { + paapi: { enabled: true, bidders: ['medianet'], defaultForSlots: 1 @@ -200,4 +200,4 @@ pbjs.que.push(function() { }); ``` -For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index 40ac760e46d..3ef97cddb88 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -68,6 +68,7 @@ function getPlacementReqData(bid) { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 61aa9b795de..82b9025766b 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -28,7 +28,6 @@ const AUDIENCE_IDS = [ {type: 8, bidKey: 'userId.id5id.uid', source: 'id5-sync.com'}, {type: 9, bidKey: 'userId.tdid', source: 'adserver.org'}, {type: 10, bidKey: 'userId.novatiq.snowflake', source: 'novatiq.com'}, - {type: 11, bidKey: 'userId.parrableId.eid', source: 'parrable.com'}, {type: 12, bidKey: 'userId.dacId.id', source: 'dac.co.jp'}, {type: 13, bidKey: 'userId.idl_env', source: 'liveramp.com'}, {type: 14, bidKey: 'userId.criteoId', source: 'criteo.com'}, diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js deleted file mode 100644 index 146d437b1fa..00000000000 --- a/modules/minutemediaplusBidAdapter.js +++ /dev/null @@ -1,349 +0,0 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {config} from '../src/config.js'; - -const GVLID = 918; -const DEFAULT_SUB_DOMAIN = 'exchange'; -const BIDDER_CODE = 'mmplus'; -const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; - -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - -export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { - return `https://${subDomain}.minutemedia-prebid.com`; -} - -export function extractCID(params) { - return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; -} - -export function extractPID(params) { - return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; -} - -export function extractSubDomain(params) { - return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - auctionId, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: auctionId, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.minutemedia-prebid.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.minutemedia-prebid.com/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} - -export const spec = { - code: BIDDER_CODE, - version: BIDDER_VERSION, - gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; - -registerBidder(spec); diff --git a/modules/minutemediaplusBidAdapter.md b/modules/minutemediaplusBidAdapter.md deleted file mode 100644 index 410c00e7017..00000000000 --- a/modules/minutemediaplusBidAdapter.md +++ /dev/null @@ -1,35 +0,0 @@ -# Overview - -**Module Name:** MinuteMediaPlus Bidder Adapter - -**Module Type:** Bidder Adapter - -**Maintainer:** hb@minutemedia.com - -# Description - -Module that connects to MinuteMediaPlus's demand sources. - -# Test Parameters -```js -var adUnits = [ - { - code: 'test-ad', - sizes: [[300, 250]], - bids: [ - { - bidder: 'mmplus', - params: { - cId: '562524b21b1c1f08117fc7f9', - pId: '59ac17c192832d0011283fe3', - bidFloor: 0.0001, - ext: { - param1: 'loremipsum', - param2: 'dolorsitamet' - } - } - } - ] - } -]; -``` diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js index 35e9b03c031..7fb585ada06 100644 --- a/modules/mobfoxpbBidAdapter.js +++ b/modules/mobfoxpbBidAdapter.js @@ -107,6 +107,7 @@ export const spec = { placement.protocols = mediaType[VIDEO].protocols; placement.startdelay = mediaType[VIDEO].startdelay; placement.placement = mediaType[VIDEO].placement; + placement.plcmt = mediaType[VIDEO].plcmt; placement.skip = mediaType[VIDEO].skip; placement.skipafter = mediaType[VIDEO].skipafter; placement.minbitrate = mediaType[VIDEO].minbitrate; diff --git a/modules/mytargetBidAdapter.md b/modules/mytargetBidAdapter.md deleted file mode 100644 index 3292ff561fa..00000000000 --- a/modules/mytargetBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -``` -Module Name: myTarget Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support_target@corp.my.com -``` - -# Description - -Module that connects to myTarget demand sources. - -# Test Parameters - -``` - var adUnits = [{ - code: 'placementCode', - mediaTypes: { - banner: { - sizes: [[240, 400]], - } - }, - bids: [{ - bidder: 'mytarget', - params: { - placementId: '379783', - - // OPTIONAL: custom bid floor - bidfloor: 10000, - - // OPTIONAL: if you know the ad position on the page, specify it here - // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) - position: 0, - - // OPTIONAL: bid response type: 0 - ad url (default), 1 - ad markup - response: 0 - } - }] - }]; -``` diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 69a270247cd..c9da876b292 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -34,7 +34,7 @@ const localPbjsRef = getGlobal() * Keep track of bid data by keys * @returns {Object} - Map of bid data that can be referenced by multiple keys */ -export const BidDataMap = () => { +export function BidDataMap() { const referenceMap = {} const bids = [] diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index ac8aa2a0625..d33b4e64297 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -465,10 +465,6 @@ export const spec = { if (id5id) { ret['id5id'] = id5id; } - let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); - if (parrableId) { - ret['parrableId'] = parrableId; - } let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); if (sharedid) { ret['sharedid'] = sharedid; diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 62bee5c2aeb..8ddcb2c3980 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -93,7 +93,7 @@ function buildRequests(validBidRequests, bidderRequest) { const connection = navigator.connection || navigator.webkitConnection; payload.networkConnectionType = (connection && connection.type) ? connection.type : null; payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null; - payload.fledgeEnabled = Boolean(bidderRequest && bidderRequest.fledgeEnabled) + payload.fledgeEnabled = Boolean(bidderRequest?.paapi?.enabled) return { method: 'POST', url: ENDPOINT, @@ -156,7 +156,7 @@ function interpretResponse(serverResponse, bidderRequest) { const fledgeAuctionConfigs = body.fledgeAuctionConfigs return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return bids; diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js index 8a6ef88a7fb..573fee3b0b3 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -433,6 +433,11 @@ function sendPage() { function sendHbConfigData() { const conf = {} const pbjsConfig = config.getConfig() + // Check if pbjsConfig.userSync exists and has userIds property + if (pbjsConfig.userSync && pbjsConfig.userSync.userIds) { + // Delete the userIds property + delete pbjsConfig.userSync.userIds; + } Object.keys(pbjsConfig).forEach(key => { if (key[0] !== '_') { diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 5bc74ac6465..f083647c480 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -46,6 +46,11 @@ export const spec = { return false; } + if (!bidRequest.params.placementId) { + logWarn('placementId is a mandatory param for OpenWeb adapter'); + return false; + } + return true; }, buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 81b710d09a1..8b16aa1a84e 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -4,7 +4,6 @@ import * as utils from '../src/utils.js'; import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '2.0'; @@ -18,8 +17,7 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - getUserSyncs, - transformBidParams + getUserSyncs }; registerBidder(spec); @@ -116,7 +114,7 @@ const converter = ortbConverter({ }); return { bids: response.bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return response.bids @@ -150,13 +148,6 @@ const converter = ortbConverter({ } }); -function transformBidParams(params, isOpenRtb) { - return convertTypes({ - 'unit': 'string', - 'customFloor': 'number' - }, params); -} - function isBidRequestValid(bidRequest) { const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index 957192d1bec..486d5ac726b 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -525,7 +525,6 @@ function createImp(bidRequest) { playbackmethod: videoReq.playbackmethod || VIDEO_DEFAULTS.PLAYBACK_METHODS, delivery: videoReq.delivery || VIDEO_DEFAULTS.DELIVERY, api: videoReq.api || VIDEO_DEFAULTS.API, - placement: videoReq.context === OUTSTREAM ? 3 : 1, }; mediaType = VIDEO; diff --git a/modules/optableBidAdapter.js b/modules/optableBidAdapter.js index f6c7cf00a35..4e639fb88ee 100644 --- a/modules/optableBidAdapter.js +++ b/modules/optableBidAdapter.js @@ -35,7 +35,7 @@ export const spec = { return { bidId: impid, config } }) - return { bids, fledgeAuctionConfigs: auctionConfigs } + return { bids, paapi: auctionConfigs } }, supportedMediaTypes: [BANNER] } diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 329aea47bd3..1e5b2ae8ca5 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -685,10 +685,6 @@ export const spec = { if (id5id) { ret['id5id'] = id5id; } - let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); - if (parrableId) { - ret['parrableId'] = parrableId; - } let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); if (sharedid) { ret['sharedid'] = sharedid; @@ -768,7 +764,7 @@ export const spec = { return ret; }, _unpackVideoConfigIntoIABformat(ret, objConfig) { - let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; + let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'plcmt', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; for (const key in objConfig) { var found = false; arrVideoKeysAllowed.forEach(function(arg) { diff --git a/modules/paapi.js b/modules/paapi.js index c6cd380c1c9..310974b31fe 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -34,10 +34,8 @@ const pendingBuyersForAuction = auctionStore(); let latestAuctionForAdUnit = {}; let moduleConfig = {}; -['paapi', 'fledgeForGpt'].forEach(ns => { - config.getConfig(ns, config => { - init(config[ns], ns); - }); +config.getConfig('paapi', config => { + init(config.paapi); }); export function reset() { @@ -45,10 +43,7 @@ export function reset() { latestAuctionForAdUnit = {}; } -export function init(cfg, configNamespace) { - if (configNamespace !== 'paapi') { - logWarn(`'${configNamespace}' configuration options will be renamed to 'paapi'; consider using setConfig({paapi: [...]}) instead`); - } +export function init(cfg) { if (cfg && cfg.enabled === true) { moduleConfig = cfg; logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); @@ -147,7 +142,7 @@ function setFPD(target, {ortb2, ortb2Imp}) { } export function addPaapiConfigHook(next, request, paapiConfig) { - if (getFledgeConfig().enabled) { + if (getFledgeConfig(config.getCurrentBidder()).enabled) { const {adUnitCode, auctionId} = request; // eslint-disable-next-line no-inner-declarations @@ -305,12 +300,11 @@ function isFledgeSupported() { return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator; } -function getFledgeConfig() { - const bidder = config.getCurrentBidder(); - const useGlobalConfig = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); +function getFledgeConfig(bidder) { + const enabled = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); return { - enabled: config.getConfig('fledgeEnabled') ?? useGlobalConfig, - ae: config.getConfig('defaultForSlots') ?? (useGlobalConfig ? moduleConfig.defaultForSlots : undefined) + enabled, + ae: enabled ? moduleConfig.defaultForSlots : undefined }; } @@ -340,37 +334,34 @@ function getRequestedSize(adUnit) { export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { bidderRequests.forEach((bidderReq) => { - config.runWithBidder(bidderReq.bidderCode, () => { - const {enabled, ae} = getFledgeConfig(); - Object.assign(bidderReq, { - fledgeEnabled: enabled, - paapi: { - enabled, - componentSeller: !!moduleConfig.componentSeller?.auctionConfig - } - }); - bidderReq.bids.forEach(bidReq => { - // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md - const igsAe = bidReq.ortb2Imp?.ext?.igs != null - ? bidReq.ortb2Imp.ext.igs.ae || 1 - : null - const extAe = bidReq.ortb2Imp?.ext?.ae; - if (igsAe !== extAe && igsAe != null && extAe != null) { - logWarn(MODULE, `Bid request defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, bidReq); - } - const bidAe = igsAe ?? extAe ?? ae; - if (bidAe) { - deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidAe); - bidReq.ortb2Imp.ext.igs = Object.assign({ - ae: bidAe, - biddable: 1 - }, bidReq.ortb2Imp.ext.igs) - const requestedSize = getRequestedSize(bidReq); - if (requestedSize) { - deepSetValue(bidReq, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); - } + const {enabled, ae} = getFledgeConfig(bidderReq.bidderCode); + Object.assign(bidderReq, { + paapi: { + enabled, + componentSeller: !!moduleConfig.componentSeller?.auctionConfig + } + }); + bidderReq.bids.forEach(bidReq => { + // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + const igsAe = bidReq.ortb2Imp?.ext?.igs != null + ? bidReq.ortb2Imp.ext.igs.ae || 1 + : null; + const extAe = bidReq.ortb2Imp?.ext?.ae; + if (igsAe !== extAe && igsAe != null && extAe != null) { + logWarn(MODULE, `Bid request defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, bidReq); + } + const bidAe = igsAe ?? extAe ?? ae; + if (bidAe) { + deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidAe); + bidReq.ortb2Imp.ext.igs = Object.assign({ + ae: bidAe, + biddable: 1 + }, bidReq.ortb2Imp.ext.igs); + const requestedSize = getRequestedSize(bidReq); + if (requestedSize) { + deepSetValue(bidReq, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); } - }); + } }); }); } @@ -378,7 +369,7 @@ export function markForFledge(next, bidderRequests) { } export function setImpExtAe(imp, bidRequest, context) { - if (!context.bidderRequest.fledgeEnabled) { + if (!context.bidderRequest.paapi?.enabled) { delete imp.ext?.ae; delete imp.ext?.igs; } @@ -449,7 +440,7 @@ export function setResponsePaapiConfigs(response, ortbResponse, context) { registerOrtbProcessor({ type: RESPONSE, - name: 'fledgeAuctionConfigs', + name: 'paapiConfigs', priority: -1, fn: setResponsePaapiConfigs, }); diff --git a/modules/fledgeForGpt.js b/modules/paapiForGpt.js similarity index 68% rename from modules/fledgeForGpt.js rename to modules/paapiForGpt.js index a356785dbe1..61133014e28 100644 --- a/modules/fledgeForGpt.js +++ b/modules/paapiForGpt.js @@ -3,40 +3,39 @@ */ import {getHook, submodule} from '../src/hook.js'; import {deepAccess, logInfo, logWarn, sizeTupleToSizeString} from '../src/utils.js'; -import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; -// import parent module to keep backwards-compat for NPM consumers after paapi was split from fledgeForGpt -// there's a special case in webpack.conf.js to avoid duplicating build output on non-npm builds -// TODO: remove this in prebid 9 -// eslint-disable-next-line prebid/validate-imports -import './paapi.js'; import {keyCompare} from '../src/utils/reducers.js'; -const MODULE = 'fledgeForGpt'; +import {getGPTSlotsForAdUnits, targeting} from '../src/targeting.js'; -let getPAAPIConfig; +const MODULE = 'paapiForGpt'; -// for backwards compat, we attempt to automatically set GPT configuration as soon as we -// have the auction configs available. Disabling this allows one to call pbjs.setPAAPIConfigForGPT at their -// own pace. -let autoconfig = true; +let getPAAPIConfig; -Object.entries({ - [MODULE]: MODULE, - 'paapi': 'paapi.gpt' -}).forEach(([topic, ns]) => { - const configKey = `${ns}.autoconfig`; - config.getConfig(topic, (cfg) => { - autoconfig = deepAccess(cfg, configKey, true); - }); +config.getConfig('paapi', (cfg) => { + if (deepAccess(cfg, 'paapi.gpt.configWithTargeting', true)) { + logInfo(MODULE, 'enabling PAAPI configuration with setTargetingForGPTAsync') + targeting.setTargetingForGPT.before(setTargetingHook); + } else { + targeting.setTargetingForGPT.getHooks({hook: setTargetingHook}).remove(); + } }); +export function setTargetingHookFactory(setPaapiConfig = getGlobal().setPAAPIConfigForGPT) { + return function(next, adUnit, customSlotMatching) { + const adUnitCodes = Array.isArray(adUnit) ? adUnit : [adUnit] + adUnitCodes + .map(adUnitCode => adUnitCode == null ? undefined : {adUnitCode}) + .forEach(filters => setPaapiConfig(filters, customSlotMatching)) + next(adUnit, customSlotMatching); + } +} + export function slotConfigurator() { const PREVIOUSLY_SET = {}; - return function setComponentAuction(adUnitCode, auctionConfigs, reset = true) { - const gptSlot = getGptSlotForAdUnitCode(adUnitCode); - if (gptSlot && gptSlot.setConfig) { + return function setComponentAuction(adUnitCode, gptSlots, auctionConfigs, reset = true) { + if (gptSlots.length > 0) { let previous = PREVIOUSLY_SET[adUnitCode] ?? {}; let configsBySeller = Object.fromEntries(auctionConfigs.map(cfg => [cfg.seller, cfg])); const sellers = Object.keys(configsBySeller); @@ -52,8 +51,10 @@ export function slotConfigurator() { const componentAuction = Object.entries(configsBySeller) .map(([configKey, auctionConfig]) => ({configKey, auctionConfig})); if (componentAuction.length > 0) { - gptSlot.setConfig({componentAuction}); - logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); + gptSlots.forEach(gptSlot => { + gptSlot.setConfig({componentAuction}); + logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); + }); } } else if (auctionConfigs.length > 0) { logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); @@ -63,17 +64,6 @@ export function slotConfigurator() { const setComponentAuction = slotConfigurator(); -export function onAuctionConfigFactory(setGptConfig = setComponentAuction) { - return function onAuctionConfig(auctionId, configsByAdUnit, markAsUsed) { - if (autoconfig) { - Object.entries(configsByAdUnit).forEach(([adUnitCode, cfg]) => { - setGptConfig(adUnitCode, cfg?.componentAuctions ?? []); - markAsUsed(adUnitCode); - }); - } - } -} - export const getPAAPISizeHook = (() => { /* https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing#faq @@ -138,20 +128,22 @@ export const getPAAPISizeHook = (() => { export function setPAAPIConfigFactory( getConfig = (filters) => getPAAPIConfig(filters, true), - setGptConfig = setComponentAuction) { + setGptConfig = setComponentAuction, + getSlots = getGPTSlotsForAdUnits) { /** * Configure GPT slots with PAAPI auction configs. * `filters` are the same filters accepted by `pbjs.getPAAPIConfig`; */ - return function(filters = {}) { + return function(filters = {}, customSlotMatching) { let some = false; - Object.entries( - getConfig(filters) || {} - ).forEach(([au, config]) => { + const cfg = getConfig(filters) || {}; + const auToSlots = getSlots(Object.keys(cfg), customSlotMatching); + + Object.entries(cfg).forEach(([au, config]) => { if (config != null) { some = true; } - setGptConfig(au, config?.componentAuctions || [], true); + setGptConfig(au, auToSlots[au], config?.componentAuctions || [], true); }) if (!some) { logInfo(`${MODULE}: No component auctions available to set`); @@ -162,10 +154,10 @@ export function setPAAPIConfigFactory( * Configure GPT slots with PAAPI component auctions. Accepts the same filter arguments as `pbjs.getPAAPIConfig`. */ getGlobal().setPAAPIConfigForGPT = setPAAPIConfigFactory(); +const setTargetingHook = setTargetingHookFactory(); submodule('paapi', { name: 'gpt', - onAuctionConfig: onAuctionConfigFactory(), init(params) { getPAAPIConfig = params.getPAAPIConfig; getHook('getPAAPISize').before(getPAAPISizeHook); diff --git a/modules/fledgeForGpt.md b/modules/paapiForGpt.md similarity index 55% rename from modules/fledgeForGpt.md rename to modules/paapiForGpt.md index 28f44da6459..31cde2e268d 100644 --- a/modules/fledgeForGpt.md +++ b/modules/paapiForGpt.md @@ -1,22 +1,22 @@ # Overview -This module allows Prebid.js to support FLEDGE by integrating it with GPT's [experimental FLEDGE +This module allows Prebid.js to support PAAPI by integrating it with GPT's [experimental PAAPI support](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing). -To learn more about FLEDGE in general, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md). +To learn more about PAAPI in general, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md). -This document covers the steps necessary for publishers to enable FLEDGE on their inventory. It also describes -the changes Bid Adapters need to implement in order to support FLEDGE. +This document covers the steps necessary for publishers to enable PAAPI on their inventory. It also describes +the changes Bid Adapters need to implement in order to support PAAPI. ## Publisher Integration -Publishers wishing to enable FLEDGE support must do two things. First, they must compile Prebid.js with support for this module. -This is accomplished by adding the `fledgeForGpt` module to the list of modules they are already using: +Publishers wishing to enable PAAPI support must do two things. First, they must compile Prebid.js with support for this module. +This is accomplished by adding the `paapiForGpt` module to the list of modules they are already using: ``` -gulp build --modules=fledgeForGpt,... +gulp build --modules=paapiForGpt,... ``` -Second, they must enable FLEDGE in their Prebid.js configuration. -This is done through module level configuration, but to provide a high degree of flexiblity for testing, FLEDGE settings also exist at the bidder level and slot level. +Second, they must enable PAAPI in their Prebid.js configuration. +This is done through module level configuration, but to provide a high degree of flexiblity for testing, PAAPI settings also exist the slot level. ### Module Configuration This module exposes the following settings: @@ -27,14 +27,13 @@ This module exposes the following settings: |bidders | Array[String] |Optional list of bidders |Defaults to all bidders | |defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1 | -As noted above, FLEDGE support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). -using the `setConfig` method of Prebid.js. Optionally, a list of -bidders to apply these settings to may be provided: +As noted above, PAAPI support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). +using the `setConfig` method of Prebid.js. Optionally, a list of bidders to apply these settings to may be provided: ```js pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { + paapi: { enabled: true, bidders: ['openx', 'rtbhouse'], defaultForSlots: 1 @@ -43,35 +42,14 @@ pbjs.que.push(function() { }); ``` -### Bidder Configuration -This module adds the following setting for bidders: - -|Name |Type |Description |Notes | -| :------------ | :------------ | :------------ |:------------ | -| fledgeEnabled | Boolean | Enable/disable a bidder to participate in FLEDGE | Defaults to `false` | -|defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1| - -Individual bidders may be further included or excluded here using the `setBidderConfig` method -of Prebid.js: - -```js -pbjs.setBidderConfig({ - bidders: ["openx"], - config: { - fledgeEnabled: true, - defaultForSlots: 1 - } -}); -``` - ### AdUnit Configuration -All adunits can be opted-in to FLEDGE in the global config via the `defaultForSlots` parameter. +All adunits can be opted-in to PAAPI in the global config via the `defaultForSlots` parameter. If needed, adunits can be configured individually by setting an attribute of the `ortb2Imp` object for that adunit. This attribute will take precedence over `defaultForSlots` setting. |Name |Type |Description |Notes | | :------------ | :------------ | :------------ |:------------ | -| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates FLEDGE eligible, 0 indicates it is not | Absence indicates this is not FLEDGE eligible | +| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates PAAPI eligible, 0 indicates it is not | Absence indicates this is not PAAPI eligible | The `ae` field stands for Auction Environment and was chosen to be consistent with the field that GAM passes to bidders in their Open Bidding and Exchange Bidding APIs. More details on that can be found @@ -91,31 +69,31 @@ pbjs.addAdUnits({ ``` ## Bid Adapter Integration -Chrome has enabled a two-tier auction in FLEDGE. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with +Chrome has enabled a two-tier auction in PAAPI. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with a single entity serving as the final decision maker. In their [current approach](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing), GPT has opted to run the final auction layer while allowing other SSPs/sellers to participate as -[Component Auctions](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) which feed their -bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#24-scoring-bids-in-component-auctions). +[Component Auctions](https://github.com/WICG/turtledove/blob/main/PAAPI.md#21-initiating-an-on-device-auction) which feed their +bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md#24-scoring-bids-in-component-auctions). -The FLEDGE auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given -seller. This module enables FLEDGE support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an +The PAAPI auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given +seller. This module enables PAAPI support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an `AuctionConfig` object, Prebid.js will register it with the appropriate GPT ad slot so the bidder can participate as a Component Auction in the overall -FLEDGE auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). +PAAPI auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). -Modifying a bid adapter to support FLEDGE is a straightforward process and consists of the following steps: -1. Detecting when a bid request is FLEDGE eligible +Modifying a bid adapter to support PAAPI is a straightforward process and consists of the following steps: +1. Detecting when a bid request is PAAPI eligible 2. Responding with AuctionConfig -FLEDGE eligibility is made available to bid adapters through the `bidderRequest.fledgeEnabled` field. +PAAPI eligibility is made available to bid adapters through the `bidderRequest.paapi.enabled` field. The [`bidderRequest`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidderrequest-parameters) object is passed to the [`buildRequests`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#building-the-request) method of an adapter. Bid adapters -who wish to participate should read this flag and pass it to their server. FLEDGE eligibility depends on a number of parameters: +who wish to participate should read this flag and pass it to their server. PAAPI eligibility depends on a number of parameters: 1. Chrome enablement 2. Publisher participatipon in the [Origin Trial](https://developer.chrome.com/docs/privacy-sandbox/unified-origin-trial/#configure) 3. Publisher Prebid.js configuration (detailed above) -When a bid request is FLEDGE enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: +When a bid request is PAAPI enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: ```js function interpretResponse(resp, req) { @@ -138,8 +116,8 @@ An AuctionConfig must be associated with an adunit and auction, and this is acco `validBidRequests` array passed to the `buildRequests` function - see [here](https://docs.prebid.org/dev-docs/bidder-adaptor.html#ad-unit-params-in-the-validbidrequests-array) for more details. This means that the AuctionConfig objects returned from `interpretResponse` must contain a `bidId` field whose value corresponds to the request it should be associated with. This may raise the question: why isn't the AuctionConfig object returned as part of the bid? The -answer is that it's possible to participate in the FLEDGE auction without returning a contextual bid. +answer is that it's possible to participate in the PAAPI auction without returning a contextual bid. An example of this can be seen in the OpenX OpenRTB bid adapter [here](https://github.com/prebid/Prebid.js/blob/master/modules/openxOrtbBidAdapter.js#L327). -Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in FLEDGE. The details of creating an AuctionConfig object are beyond the scope of this document. +Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in PAAPI. The details of creating an AuctionConfig object are beyond the scope of this document. diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js deleted file mode 100644 index 5651bdf0434..00000000000 --- a/modules/parrableIdSystem.js +++ /dev/null @@ -1,416 +0,0 @@ -/** - * This module adds Parrable to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/parrableIdSystem - * @requires module:modules/userId - */ - -// ci trigger: 1 - -import { - contains, - deepClone, - inIframe, - isEmpty, - isPlainObject, - logError, - logWarn, - pick, - timestamp -} from '../src/utils.js'; -import {find} from '../src/polyfill.js'; -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {uspDataHandler} from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - */ - -const PARRABLE_URL = 'https://h.parrable.com/prebid'; -const PARRABLE_COOKIE_NAME = '_parrable_id'; -const PARRABLE_GVLID = 928; -const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; -const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; -const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; -const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; -const MODULE_NAME = 'parrableId'; - -const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); - -function getExpirationDate() { - const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); - return oneYearFromNow.toGMTString(); -} - -function deserializeParrableId(parrableIdStr) { - const parrableId = {}; - const values = parrableIdStr.split(','); - - values.forEach(function(value) { - const pair = value.split(':'); - if (pair[0] === 'ccpaOptout' || pair[0] === 'ibaOptout') { // unpack a value of 0 or 1 as boolean - parrableId[pair[0]] = Boolean(+pair[1]); - } else if (!isNaN(pair[1])) { // convert to number if is a number - parrableId[pair[0]] = +pair[1] - } else { - parrableId[pair[0]] = pair[1] - } - }); - - return parrableId; -} - -function serializeParrableId(parrableIdAndParams) { - let components = []; - - if (parrableIdAndParams.eid) { - components.push('eid:' + parrableIdAndParams.eid); - } - if (parrableIdAndParams.ibaOptout) { - components.push('ibaOptout:1'); - } - if (parrableIdAndParams.ccpaOptout) { - components.push('ccpaOptout:1'); - } - if (parrableIdAndParams.tpcSupport !== undefined) { - const tpcSupportComponent = parrableIdAndParams.tpcSupport === true ? 'tpc:1' : 'tpc:0'; - const tpcUntil = `tpcUntil:${parrableIdAndParams.tpcUntil}`; - components.push(tpcSupportComponent); - components.push(tpcUntil); - } - if (parrableIdAndParams.filteredUntil) { - components.push(`filteredUntil:${parrableIdAndParams.filteredUntil}`); - components.push(`filterHits:${parrableIdAndParams.filterHits}`); - } - - return components.join(','); -} - -function isValidConfig(configParams) { - if (!configParams) { - logError('User ID - parrableId submodule requires configParams'); - return false; - } - if (!configParams.partners && !configParams.partner) { - logError('User ID - parrableId submodule requires partner list'); - return false; - } - if (configParams.storage) { - logWarn('User ID - parrableId submodule does not require a storage config'); - } - return true; -} - -function encodeBase64UrlSafe(base64) { - const ENC = { - '+': '-', - '/': '_', - '=': '.' - }; - return base64.replace(/[+/=]/g, (m) => ENC[m]); -} - -function readCookie() { - const parrableIdStr = storage.getCookie(PARRABLE_COOKIE_NAME); - if (parrableIdStr) { - const parsedCookie = deserializeParrableId(decodeURIComponent(parrableIdStr)); - const { tpc, tpcUntil, filteredUntil, filterHits, ...parrableId } = parsedCookie; - let { eid, ibaOptout, ccpaOptout, ...params } = parsedCookie; - - if ((Date.now() / 1000) >= tpcUntil) { - params.tpc = undefined; - } - - if ((Date.now() / 1000) < filteredUntil) { - params.shouldFilter = true; - params.filteredUntil = filteredUntil; - } else { - params.shouldFilter = false; - params.filterHits = filterHits; - } - return { parrableId, params }; - } - return null; -} - -function writeCookie(parrableIdAndParams) { - if (parrableIdAndParams) { - const parrableIdStr = encodeURIComponent(serializeParrableId(parrableIdAndParams)); - storage.setCookie(PARRABLE_COOKIE_NAME, parrableIdStr, getExpirationDate(), 'lax'); - } -} - -function readLegacyCookies() { - const eid = storage.getCookie(LEGACY_ID_COOKIE_NAME); - const ibaOptout = (storage.getCookie(LEGACY_OPTOUT_COOKIE_NAME) === 'true'); - if (eid || ibaOptout) { - const parrableId = {}; - if (eid) { - parrableId.eid = eid; - } - if (ibaOptout) { - parrableId.ibaOptout = ibaOptout; - } - return parrableId; - } - return null; -} - -function migrateLegacyCookies(parrableId) { - if (parrableId) { - writeCookie(parrableId); - if (parrableId.eid) { - storage.setCookie(LEGACY_ID_COOKIE_NAME, '', EXPIRE_COOKIE_DATE); - } - if (parrableId.ibaOptout) { - storage.setCookie(LEGACY_OPTOUT_COOKIE_NAME, '', EXPIRE_COOKIE_DATE); - } - } -} - -function shouldFilterImpression(configParams, parrableId) { - const config = configParams.timezoneFilter; - - if (!config) { - return false; - } - - if (parrableId) { - return false; - } - - const offset = (new Date()).getTimezoneOffset() / 60; - const zone = Intl.DateTimeFormat().resolvedOptions().timeZone; - - function isZoneListed(list, zone) { - // IE does not provide a timeZone in IANA format so zone will be empty - const zoneLowercase = zone && zone.toLowerCase(); - return !!(list && zone && find(list, zn => zn.toLowerCase() === zoneLowercase)); - } - - function isAllowed() { - if (isEmpty(config.allowedZones) && - isEmpty(config.allowedOffsets)) { - return true; - } - if (isZoneListed(config.allowedZones, zone)) { - return true; - } - if (contains(config.allowedOffsets, offset)) { - return true; - } - return false; - } - - function isBlocked() { - if (isEmpty(config.blockedZones) && - isEmpty(config.blockedOffsets)) { - return false; - } - if (isZoneListed(config.blockedZones, zone)) { - return true; - } - if (contains(config.blockedOffsets, offset)) { - return true; - } - return false; - } - - return isBlocked() || !isAllowed(); -} - -function epochFromTtl(ttl) { - return Math.floor((Date.now() / 1000) + ttl); -} - -function incrementFilterHits(parrableId, params) { - params.filterHits += 1; - writeCookie({ ...parrableId, ...params }) -} - -function fetchId(configParams, gdprConsentData) { - if (!isValidConfig(configParams)) return; - - let { parrableId, params } = readCookie() || {}; - if (!parrableId) { - parrableId = readLegacyCookies(); - migrateLegacyCookies(parrableId); - } - - if (shouldFilterImpression(configParams, parrableId)) { - return null; - } - - const eid = parrableId ? parrableId.eid : null; - const refererInfo = getRefererInfo(); - const tpcSupport = params ? params.tpc : null; - const shouldFilter = params ? params.shouldFilter : null; - const uspString = uspDataHandler.getConsentData(); - const gdprApplies = (gdprConsentData && typeof gdprConsentData.gdprApplies === 'boolean' && gdprConsentData.gdprApplies); - const gdprConsentString = (gdprConsentData && gdprApplies && gdprConsentData.consentString) || ''; - const partners = configParams.partners || configParams.partner; - const trackers = typeof partners === 'string' - ? partners.split(',') - : partners; - - const data = { - eid, - trackers, - url: refererInfo.page, - prebidVersion: '$prebid.version$', - isIframe: inIframe(), - tpcSupport - }; - - if (shouldFilter === false) { - data.filterHits = params.filterHits; - } - - const searchParams = { - data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), - gdpr: gdprApplies ? 1 : 0, - _rand: Math.random() - }; - - if (uspString) { - searchParams.us_privacy = uspString; - } - - if (gdprApplies) { - searchParams.gdpr_consent = gdprConsentString; - } - - const options = { - method: 'GET', - withCredentials: true - }; - - const callback = function (cb) { - const callbacks = { - success: response => { - let newParrableId = parrableId ? deepClone(parrableId) : {}; - let newParams = {}; - if (response) { - try { - let responseObj = JSON.parse(response); - if (responseObj) { - if (responseObj.ccpaOptout !== true) { - newParrableId.eid = responseObj.eid; - } else { - newParrableId.eid = null; - newParrableId.ccpaOptout = true; - } - if (responseObj.ibaOptout === true) { - newParrableId.ibaOptout = true; - } - if (responseObj.tpcSupport !== undefined) { - newParams.tpcSupport = responseObj.tpcSupport; - newParams.tpcUntil = epochFromTtl(responseObj.tpcSupportTtl); - } - if (responseObj.filterTtl) { - newParams.filteredUntil = epochFromTtl(responseObj.filterTtl); - newParams.filterHits = 0; - } - } - } catch (error) { - logError(error); - cb(); - } - writeCookie({ ...newParrableId, ...newParams }); - cb(newParrableId); - } else { - logError('parrableId: ID fetch returned an empty result'); - cb(); - } - }, - error: error => { - logError(`parrableId: ID fetch encountered an error`, error); - cb(); - } - }; - - if (shouldFilter) { - incrementFilterHits(parrableId, params); - } else { - ajax(PARRABLE_URL, callbacks, searchParams, options); - } - }; - - return { - callback, - id: parrableId - }; -} - -/** @type {Submodule} */ -export const parrableIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * Global Vendor List ID - * @type {number} - */ - gvlid: PARRABLE_GVLID, - - /** - * decode the stored id value for passing to bid requests - * @function - * @param {ParrableId} parrableId - * @return {(Object|undefined} - */ - decode(parrableId) { - if (parrableId && isPlainObject(parrableId)) { - return { parrableId }; - } - return undefined; - }, - - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @param {ConsentData} [consentData] - * @returns {function(callback:function), id:ParrableId} - */ - getId(config, gdprConsentData, currentStoredId) { - const configParams = (config && config.params) || {}; - return fetchId(configParams, gdprConsentData); - }, - eids: { - 'parrableId': { - source: 'parrable.com', - atype: 1, - getValue: function(parrableId) { - if (parrableId.eid) { - return parrableId.eid; - } - if (parrableId.ccpaOptout) { - // If the EID was suppressed due to a non consenting ccpa optout then - // we still wish to provide this as a reason to the adapters - return ''; - } - return null; - }, - getUidExt: function(parrableId) { - const extendedData = pick(parrableId, [ - 'ibaOptout', - 'ccpaOptout' - ]); - if (Object.keys(extendedData).length) { - return extendedData; - } - } - }, - }, -}; - -submodule('userId', parrableIdSubmodule); diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index fdc6bcf302f..0304c325c33 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -64,6 +64,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/pirIdSystem.js b/modules/pirIdSystem.js deleted file mode 100644 index 233176028d3..00000000000 --- a/modules/pirIdSystem.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * This module adds pirId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/pirId - * @requires module:modules/userId - */ - -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { submodule } from '../src/hook.js'; -import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse - */ - -const MODULE_NAME = 'pirId'; -const ID_TOKEN = 'WPxid'; -export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); - -/** - * Reads the ID token from local storage or cookies. - * @returns {string|undefined} The ID token, or undefined if not found. - */ -export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); - -/** @type {Submodule} */ -export const pirIdSubmodule = { - name: MODULE_NAME, - gvlid: 676, - - /** - * decode the stored id value for passing to bid requests - * @function decode - * @param {string} value - * @returns {(Object|undefined)} - */ - decode(value) { - return typeof value === 'string' ? { 'pirId': value } : undefined; - }, - - /** - * performs action to obtain id and return a value - * @function - * @returns {(IdResponse|undefined)} - */ - getId() { - const pirIdToken = readId(); - - return pirIdToken ? { id: pirIdToken } : undefined; - }, - domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME), - eids: { - 'pirId': { - source: 'pir.wp.pl', - atype: 1 - }, - }, -}; - -submodule('userId', pirIdSubmodule); diff --git a/modules/pirIdSystem.md b/modules/pirIdSystem.md deleted file mode 100644 index 913804f85c4..00000000000 --- a/modules/pirIdSystem.md +++ /dev/null @@ -1,27 +0,0 @@ -# Overview - -Module Name: pirIDSystem -Module Type: UserID Module -Maintainer: pawel.grudzien@grupawp.pl - -# Description - -User identification system for WPM - -### Prebid Params example - -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'pirID', - storage: { - type: 'cookie', - name: 'pirIdToken', - expires: 7, - refreshInSeconds: 360 - }, - }] - } -}); -``` diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 1c3f9b8da1a..60a10621d97 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -277,7 +277,7 @@ function bidToTag(bid) { } tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords) - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 87274504f64..a1bad2d69ba 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -11,7 +11,7 @@ export const S2S_VENDORS = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' }, - timeout: 1000 + maxTimeout: 1000 }, 'rubicon': { adapter: 'prebidServer', @@ -24,7 +24,7 @@ export const S2S_VENDORS = { p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', }, - timeout: 500 + maxTimeout: 500 }, 'openx': { adapter: 'prebidServer', @@ -37,7 +37,7 @@ export const S2S_VENDORS = { p1Consent: 'https://prebid.openx.net/cookie_sync', noP1Consent: 'https://prebid.openx.net/cookie_sync' }, - timeout: 1000 + maxTimeout: 1000 }, 'openwrap': { adapter: 'prebidServer', @@ -46,6 +46,6 @@ export const S2S_VENDORS = { p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' }, - timeout: 500 + maxTimeout: 500 } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 8c5b89b5794..168758763e8 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -82,6 +82,7 @@ let eidPermissions; * @property {string} [syncEndpoint] endpoint URL for syncing cookies * @property {Object} [extPrebid] properties will be merged into request.ext.prebid * @property {Object} [ortbNative] base value for imp.native.request + * @property {Number} [maxTimeout] */ /** @@ -89,7 +90,6 @@ let eidPermissions; */ export const s2sDefaultConfig = { bidders: Object.freeze([]), - timeout: 1000, syncTimeout: 1000, maxBids: 1, adapter: 'prebidServer', @@ -100,7 +100,8 @@ export const s2sDefaultConfig = { eventtrackers: [ {event: 1, methods: [1, 2]} ], - } + }, + maxTimeout: 1500 }; config.setDefaults({ @@ -559,10 +560,10 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); + const {bids, paapi} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs.forEach(onFledge); + if (paapi) { + paapi.forEach(onFledge); } } catch (error) { logError(error); diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index d445a52dcc6..bb033271b3c 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -25,6 +25,7 @@ import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_TRANSMIT_TID} from '../../src/activities/activities.js'; import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; import {minimum} from '../../src/utils/reducers.js'; +import {s2sDefaultConfig} from './index.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; @@ -57,8 +58,8 @@ const PBS_CONVERTER = ortbConverter({ let {s2sBidRequest, requestedBidders, eidPermissions} = context; const request = buildRequest(imps, proxyBidderRequest, context); - request.tmax = s2sBidRequest.s2sConfig.timeout; - request.ext.tmaxmax = request.ext.tmaxmax || context.s2sBidRequest.requestBidsTimeout; + request.tmax = s2sBidRequest.s2sConfig.timeout ?? Math.min(s2sBidRequest.requestBidsTimeout * 0.75, s2sBidRequest.s2sConfig.maxTimeout ?? s2sDefaultConfig.maxTimeout); + request.ext.tmaxmax = request.ext.tmaxmax || s2sBidRequest.requestBidsTimeout; [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { @@ -231,7 +232,7 @@ const PBS_CONVERTER = ortbConverter({ // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); }, - fledgeAuctionConfigs(orig, response, ortbResponse, context) { + paapiConfigs(orig, response, ortbResponse, context) { const configs = Object.values(context.impContext) .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => { const bidderReq = impCtx.actualBidderRequests.find(br => br.bidderCode === cfg.bidder); @@ -244,7 +245,7 @@ const PBS_CONVERTER = ortbConverter({ }; })); if (configs.length > 0) { - response.fledgeAuctionConfigs = configs; + response.paapi = configs; } } } @@ -300,7 +301,9 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste const proxyBidderRequest = { ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), - fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled) + paapi: { + enabled: bidderRequests.some(br => br.paapi?.enabled) + } } return PBS_CONVERTER.toORTB({ diff --git a/modules/prebidmanagerAnalyticsAdapter.md b/modules/prebidmanagerAnalyticsAdapter.md deleted file mode 100644 index 030e79b406f..00000000000 --- a/modules/prebidmanagerAnalyticsAdapter.md +++ /dev/null @@ -1,9 +0,0 @@ -# Overview - -Module Name: Prebid Manager Analytics Adapter -Module Type: Analytics Adapter -Maintainer: admin@prebidmanager.com - -# Description - -Analytics adapter for Prebid Manager. Contact admin@prebidmanager.com for information. diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 9125f6f3911..b4f1b665d91 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, isFn, deepAccess, logInfo } from '../src/utils.js'; +import { isFn, deepAccess, logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -34,14 +34,7 @@ export const spec = { const countryCode = getCountryCodeByTimezone(city); logInfo(`The country code for ${city} is ${countryCode}`); - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; + location = bidderRequest?.refererInfo ?? null; let request = { id: validBidRequests[0].bidderRequestId, @@ -87,8 +80,8 @@ export const spec = { // Show a map centered at latitude / longitude. }) || { utcoffset: new Date().getTimezoneOffset() }, city: city, - 'host': location.host, - 'page': location.pathname, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'coppa': config.getConfig('coppa') === true ? 1 : 0 // userId: validBidRequests[0].userId }; diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js index 77a11ac58c6..1265d5c546f 100644 --- a/modules/pstudioBidAdapter.js +++ b/modules/pstudioBidAdapter.js @@ -14,8 +14,8 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'pstudio'; const ENDPOINT = 'https://exchange.pstudio.tadex.id/prebid-bid' const TIME_TO_LIVE = 300; -// in case that the publisher limits number of user syncs, thisse syncs will be discarded from the end of the list -// so more improtant syncing calls should be at the start of the list +// in case that the publisher limits number of user syncs, these syncs will be discarded from the end of the list +// so more important syncing calls should be at the start of the list const USER_SYNCS = [ // PARTNER_UID is a partner user id { @@ -40,6 +40,7 @@ const VIDEO_PARAMS = [ 'protocols', 'startdelay', 'placement', + 'plcmt', 'skip', 'skipafter', 'minbitrate', diff --git a/modules/pubCircleBidAdapter.js b/modules/pubCircleBidAdapter.js index 54224fd0403..db435d5fa4f 100644 --- a/modules/pubCircleBidAdapter.js +++ b/modules/pubCircleBidAdapter.js @@ -60,6 +60,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index d92a9352cee..617123746e5 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -155,7 +155,7 @@ function buildVideoParams(videoMediaType, videoParams) { 'maxduration', 'protocols', 'startdelay', - 'placement', + 'plcmt', 'skip', 'skipafter', 'minbitrate', @@ -166,17 +166,6 @@ function buildVideoParams(videoMediaType, videoParams) { 'linearity', ]); - switch (videoMediaType.context) { - case 'instream': - params.placement = 1; - break; - case 'outstream': - params.placement = 2; - break; - default: - break; - } - if (videoMediaType.playerSize) { params.w = videoMediaType.playerSize[0][0]; params.h = videoMediaType.playerSize[0][1]; @@ -301,8 +290,7 @@ function isValidBanner(banner) { function isValidVideo(videoMediaType, videoParams) { const params = buildVideoParams(videoMediaType, videoParams); - return !!(params.placement && - isValidSize([params.w, params.h]) && + return !!(isValidSize([params.w, params.h]) && params.mimes && params.mimes.length && isArrayOfNums(params.protocols) && params.protocols.length); } diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 5b470fdc34a..5add3fb9be1 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -663,7 +663,7 @@ function _createImpressionObject(bid, bidderRequest) { var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; var mediaTypes = ''; var format = []; - var isFledgeEnabled = bidderRequest?.fledgeEnabled; + var isFledgeEnabled = bidderRequest?.paapi?.enabled; impObj = { id: bid.bidId, @@ -1094,7 +1094,6 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { @@ -1419,7 +1418,7 @@ export const spec = { }); return { bids: bidResponses, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } } catch (error) { diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 516254b358b..50747616872 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,7 +1,6 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import {isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const DEFAULT_CURRENCY = 'USD'; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'battr', 'deals']; @@ -58,13 +57,6 @@ export const spec = { url: 'https://bh.contextweb.com/visitormatch/prebid' }]; } - }, - transformBidParams: function(params) { - return convertTypes({ - 'cf': 'string', - 'cp': 'number', - 'ct': 'number' - }, params); } }; diff --git a/modules/qtBidAdapter.js b/modules/qtBidAdapter.js index e26aad8f9ec..7616b990ff8 100644 --- a/modules/qtBidAdapter.js +++ b/modules/qtBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, logError, deepAccess } from '../src/utils.js'; +import { logMessage, deepAccess } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -91,7 +91,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 1ba23302367..ea907f0429c 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -49,7 +49,6 @@ function makeVideoImp(bid) { maxbitrate: video.maxbitrate, playbackmethod: video.playbackmethod, delivery: video.delivery, - placement: video.placement, api: video.api, w: video.w, h: video.h @@ -58,7 +57,7 @@ function makeVideoImp(bid) { return { video: videoCopy, placementCode: bid.placementCode, - bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR + bidFloor: DEFAULT_BID_FLOOR }; } @@ -76,7 +75,7 @@ function makeBannerImp(bid) { }) }, placementCode: bid.placementCode, - bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR + bidFloor: DEFAULT_BID_FLOOR }; } diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js deleted file mode 100755 index b63e31266fb..00000000000 --- a/modules/richaudienceBidAdapter.js +++ /dev/null @@ -1,378 +0,0 @@ -import {deepAccess, isStr, triggerPixel} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; -import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; - -const BIDDER_CODE = 'richaudience'; -let REFERER = ''; - -export const spec = { - code: BIDDER_CODE, - gvlid: 108, - aliases: ['ra'], - supportedMediaTypes: [BANNER, VIDEO], - - /*** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {boolean} True if this is a valid bid, and false otherwise - */ - isBidRequestValid: function (bid) { - return !!(bid.params && bid.params.pid && bid.params.supplyType); - }, - /*** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ - buildRequests: function (validBidRequests, bidderRequest) { - return validBidRequests.map(bid => { - var payload = { - bidfloor: raiGetFloor(bid, config), - ifa: bid.params.ifa, - pid: bid.params.pid, - supplyType: bid.params.supplyType, - currencyCode: config.getConfig('currency.adServerCurrency'), - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bid.auctionId, - bidId: bid.bidId, - BidRequestsCount: bid.bidRequestsCount, - bidder: bid.bidder, - bidderRequestId: bid.bidderRequestId, - tagId: bid.adUnitCode, - sizes: raiGetSizes(bid), - // TODO: is 'page' the right value here? - referer: (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null), - numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), - transactionId: bid.ortb2Imp?.ext?.tid, - timeout: bidderRequest.timeout || 600, - user: raiSetEids(bid), - demand: raiGetDemandType(bid), - videoData: raiGetVideoInfo(bid), - scr_rsl: raiGetResolution(), - cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), - kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','), - schain: bid.schain, - gpid: raiSetPbAdSlot(bid) - }; - - // TODO: is 'page' the right value here? - REFERER = (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null) - - payload.gdpr_consent = ''; - payload.gdpr = false; - - if (bidderRequest && bidderRequest.gdprConsent) { - if (typeof bidderRequest.gdprConsent.gdprApplies != 'undefined') { - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; - } - if (typeof bidderRequest.gdprConsent.consentString != 'undefined') { - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - } - - if (bidderRequest?.gppConsent) { - payload.privacy = { - gpp: bidderRequest.gppConsent.gppString, - gpp_sid: bidderRequest.gppConsent.applicableSections - } - } else if (bidderRequest?.ortb2?.regs?.gpp) { - payload.privacy = { - gpp: bidderRequest.ortb2.regs.gpp, - gpp_sid: bidderRequest.ortb2.regs.gpp_sid - } - } - - var payloadString = JSON.stringify(payload); - - var endpoint = 'https://shb.richaudience.com/hb/'; - - return { - method: 'POST', - url: endpoint, - data: payloadString, - }; - }); - }, - /*** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - // try catch - var response = serverResponse.body; - if (response) { - var bidResponse = { - requestId: JSON.parse(bidRequest.data).bidId, - cpm: response.cpm, - width: response.width, - height: response.height, - creativeId: response.creative_id, - mediaType: response.media_type, - netRevenue: response.netRevenue, - currency: response.currency, - ttl: response.ttl, - meta: response.adomain, - dealId: response.dealId - }; - - if (response.media_type === 'video') { - bidResponse.vastXml = response.vastXML; - try { - if (bidResponse.vastXml != null) { - if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { - bidResponse.renderer = Renderer.install({ - id: bidRequest.bidId, - adunitcode: bidRequest.tagId, - loaded: false, - config: response.media_type, - url: 'https://cdn3.richaudience.com/prebidVideo/player.js' - }); - } - bidResponse.renderer.setRender(renderer); - } - } catch (e) { - bidResponse.ad = response.adm; - } - } else { - bidResponse.ad = response.adm; - } - - bidResponses.push(bidResponse); - } - return bidResponses; - }, - /*** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object - * @returns {Array} - */ - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { - const syncs = []; - - var rand = Math.floor(Math.random() * 9999999999); - var syncUrl = ''; - var consent = ''; - var consentGPP = ''; - - var raiSync = {}; - - raiSync = raiGetSyncInclude(config); - - if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { - consent = `consentString=${gdprConsent.consentString}` - } - - // GPP Consent - if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { - consentGPP = 'gpp=' + encodeURIComponent(gppConsent.gppString); - consentGPP += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); - } - - if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { - syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand - if (consent != '') { - syncUrl += `&${consent}` - } - if (consentGPP != '') { - syncUrl += `&${consentGPP}` - } - syncs.push({ - type: 'iframe', - url: syncUrl - }); - } - - if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { - syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; - if (consent != '') { - syncUrl += `&${consent}` - } - if (consentGPP != '') { - syncUrl += `&${consentGPP}` - } - syncs.push({ - type: 'image', - url: syncUrl - }); - } - return syncs - }, - - onTimeout: function (data) { - let url = raiGetTimeoutURL(data); - if (url) { - triggerPixel(url); - } - } -}; - -registerBidder(spec); - -function raiGetSizes(bid) { - let raiNewSizes; - if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { - raiNewSizes = bid.mediaTypes.banner.sizes - } - if (raiNewSizes != null) { - return raiNewSizes.map(size => ({ - w: size[0], - h: size[1] - })); - } -} - -function raiGetDemandType(bid) { - let raiFormat = 'display'; - if (typeof bid.sizes != 'undefined') { - bid.sizes.forEach(function (sz) { - if ((sz[0] == '1800' && sz[1] == '1000') || (sz[0] == '1' && sz[1] == '1')) { - raiFormat = 'skin' - } - }) - } - if (bid.mediaTypes != undefined) { - if (bid.mediaTypes.video != undefined) { - raiFormat = 'video'; - } - } - return raiFormat; -} - -function raiGetVideoInfo(bid) { - let videoData; - if (raiGetDemandType(bid) == 'video') { - videoData = { - format: bid.mediaTypes.video.context, - playerSize: bid.mediaTypes.video.playerSize, - mimes: bid.mediaTypes.video.mimes - }; - } else { - videoData = { - format: 'banner' - } - } - return videoData; -} - -function raiSetEids(bid) { - let eids = []; - - if (bid && bid.userId) { - raiSetUserId(bid, eids, 'id5-sync.com', deepAccess(bid, `userId.id5id.uid`)); - raiSetUserId(bid, eids, 'pubcommon', deepAccess(bid, `userId.pubcid`)); - raiSetUserId(bid, eids, 'criteo.com', deepAccess(bid, `userId.criteoId`)); - raiSetUserId(bid, eids, 'liveramp.com', deepAccess(bid, `userId.idl_env`)); - raiSetUserId(bid, eids, 'liveintent.com', deepAccess(bid, `userId.lipb.lipbid`)); - raiSetUserId(bid, eids, 'adserver.org', deepAccess(bid, `userId.tdid`)); - } - - return eids; -} - -function raiSetUserId(bid, eids, source, value) { - if (isStr(value)) { - eids.push({ - userId: value, - source: source - }); - } -} - -function renderer(bid) { - bid.renderer.push(() => { - renderAd(bid) - }); -} - -function renderAd(bid) { - let raOutstreamHBPassback = `${bid.vastXml}`; - let raPlayerHB = { - config: bid.params[0].player != undefined ? { - end: bid.params[0].player.end != null ? bid.params[0].player.end : 'close', - init: bid.params[0].player.init != null ? bid.params[0].player.init : 'close', - skin: bid.params[0].player.skin != null ? bid.params[0].player.skin : 'light', - } : {end: 'close', init: 'close', skin: 'light'}, - pid: bid.params[0].pid, - adUnit: bid.adUnitCode - }; - - window.raParams(raPlayerHB, raOutstreamHBPassback, true); -} - -function raiGetResolution() { - let resolution = ''; - if (typeof window.screen != 'undefined') { - resolution = window.screen.width + 'x' + window.screen.height; - } - return resolution; -} - -function raiSetPbAdSlot(bid) { - let pbAdSlot = ''; - if (deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') != null) { - pbAdSlot = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') - } - return pbAdSlot -} - -function raiGetSyncInclude(config) { - try { - let raConfig = null; - let raiSync = {}; - if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { - raConfig = config.getConfig('userSync').filterSettings - if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { - raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; - } - if (raConfig.image != null && typeof raConfig.image != 'undefined') { - raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; - } - } - return raiSync; - } catch (e) { - return null; - } -} - -function raiGetFloor(bid, config) { - try { - let raiFloor; - if (bid.params.bidfloor != null) { - raiFloor = bid.params.bidfloor; - } else if (typeof bid.getFloor == 'function') { - let floorSpec = bid.getFloor({ - currency: config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD', - mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', - size: '*' - }) - - raiFloor = floorSpec.floor; - } - return raiFloor - } catch (e) { - return 0 - } -} - -function raiGetTimeoutURL(data) { - let {params, timeout} = data[0] - let url = 'https://s.richaudience.com/err/?ec=6&ev=[timeout_publisher]&pla=[placement_hash]&int=PREBID&pltfm=&node=&dm=[domain]'; - - url = url.replace('[timeout_publisher]', timeout) - url = url.replace('[placement_hash]', params[0].pid) - if (document.location.host != null) { - url = url.replace('[domain]', document.location.host) - } - return url -} diff --git a/modules/rasBidAdapter.js b/modules/ringieraxelspringerBidAdapter.js similarity index 89% rename from modules/rasBidAdapter.js rename to modules/ringieraxelspringerBidAdapter.js index 74abd0fb4a1..1fd6e327b9b 100644 --- a/modules/rasBidAdapter.js +++ b/modules/ringieraxelspringerBidAdapter.js @@ -8,7 +8,7 @@ import { import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; -const BIDDER_CODE = 'ras'; +const BIDDER_CODE = 'ringieraxelspringer'; const VERSION = '1.0'; const getEndpoint = (network) => { @@ -106,38 +106,66 @@ function parseOrtbResponse(ad) { return false; } - const { image, Image, title, url, Headline, Thirdpartyclicktracker, imp, impression, impression1, impressionJs1 } = ad.data.fields; + const { image, Image, title, url, Headline, Thirdpartyclicktracker, thirdPartyClickTracker2, imp, impression, impression1, impressionJs1, partner_logo: partnerLogo, adInfo, body } = ad.data.fields; const { dsaurl, height, width, adclick } = ad.data.meta; const emsLink = ad.ems_link; const link = adclick + (url || Thirdpartyclicktracker); const eventtrackers = prepareEventtrackers(emsLink, imp, impression, impression1, impressionJs1); + const clicktrackers = thirdPartyClickTracker2 ? [thirdPartyClickTracker2] : []; + const ortb = { ver: '1.2', assets: [ { - id: 2, + id: 0, + data: { + value: body || '', + type: 2 + }, + }, + { + id: 1, + data: { + value: adInfo || '', + // Body2 type + type: 10 + }, + }, + { + id: 3, img: { - url: image || Image || '', + type: 1, + url: partnerLogo || '', w: width, h: height } }, { id: 4, - title: { - text: title || Headline || '' + img: { + type: 3, + url: image || Image || '', + w: width, + h: height } }, { - id: 3, + id: 5, data: { value: deepAccess(ad, 'data.meta.advertiser_name', null), type: 1 } - } + }, + { + id: 6, + title: { + text: title || Headline || '' + } + }, ], link: { - url: link + url: link, + clicktrackers }, eventtrackers }; @@ -154,7 +182,7 @@ function parseNativeResponse(ad) { return false; } - const { image, Image, title, leadtext, url, Calltoaction, Body, Headline, Thirdpartyclicktracker } = ad.data.fields; + const { image, Image, title, leadtext, url, Calltoaction, Body, Headline, Thirdpartyclicktracker, adInfo, partner_logo: partnerLogo } = ad.data.fields; const { dsaurl, height, width, adclick } = ad.data.meta; const link = adclick + (url || Thirdpartyclicktracker); const nativeResponse = { @@ -165,10 +193,15 @@ function parseNativeResponse(ad) { width, height }, - + icon: { + url: partnerLogo || '', + width, + height + }, clickUrl: link, cta: Calltoaction || '', body: leadtext || Body || '', + body2: adInfo || '', sponsoredBy: deepAccess(ad, 'data.meta.advertiser_name', null) || '', ortb: parseOrtbResponse(ad) }; @@ -192,7 +225,7 @@ const buildBid = (ad, mediaType) => { creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0, netRevenue: true, currency: ad.currency || 'USD', - dealId: null, + dealId: ad.prebid_deal || null, actgMatch: ad.actg_match || 0, meta: { mediaType: BANNER }, mediaType: BANNER, @@ -243,6 +276,8 @@ const getSlots = (bidRequests) => { queryString += `&cre_format${i}=native`; } + queryString += `&kvhb_format${i}=${creFormat === 'native' ? 'native' : 'banner'}`; + if (sizes) { queryString += `&iusizes${i}=${encodeURIComponent(sizes)}`; } @@ -329,7 +364,7 @@ export const spec = { const slotsQuery = getSlots(bidRequests); const contextQuery = getContextParams(bidRequests, bidderRequest); const gdprQuery = getGdprParams(bidderRequest); - const fledgeEligible = Boolean(bidderRequest && bidderRequest.fledgeEnabled); + const fledgeEligible = Boolean(bidderRequest?.paapi?.enabled); const network = bidRequests[0].params.network; const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, @@ -357,7 +392,7 @@ export const spec = { if (fledgeAuctionConfigs) { // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. - return {bids, fledgeAuctionConfigs}; + return {bids, paapi: fledgeAuctionConfigs}; } else { return bids; } diff --git a/modules/rasBidAdapter.md b/modules/ringieraxelspringerBidAdapter.md similarity index 88% rename from modules/rasBidAdapter.md rename to modules/ringieraxelspringerBidAdapter.md index cf169fedb63..b3a716f9f56 100644 --- a/modules/rasBidAdapter.md +++ b/modules/ringieraxelspringerBidAdapter.md @@ -21,7 +21,7 @@ var adUnits = [{ } }, bids: [{ - bidder: 'ras', + bidder: 'ringieraxelspringer', params: { network: '4178463', site: 'test', @@ -36,11 +36,11 @@ var adUnits = [{ | Name | Scope | Type | Description | Example | |------------------------------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| -| network | required | String | Specific identifier provided by RAS | `"4178463"` | -| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"` | +| network | required | String | Specific identifier provided by Ringier Axel Springer | `"4178463"` | +| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by Ringier Axel Springer | `"example_com"` | | area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"` | -| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"` | -| slotSequence | optional | Number | Ad unit sequence position provided by RAS | `1` | +| slot | required | String | Ad unit placement name (case-insensitive) provided by Ringier Axel Springer | `"slot"` | +| slotSequence | optional | Number | Ad unit sequence position provided by Ringier Axel Springer | `1` | | pageContext | optional | Object | Web page context data | `{}` | | pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"` | | pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"` | diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 1cd97696770..7e2a7da3b61 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -114,7 +114,7 @@ export const spec = { let computedEndpointUrl = ENDPOINT_URL; - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { const fledgeConfig = config.getConfig('fledgeConfig') || { seller: FLEDGE_SELLER_URL, decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, @@ -209,7 +209,7 @@ export const spec = { logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids; @@ -250,7 +250,7 @@ function mapImpression(slot, bidderRequest) { imp.bidfloor = bidfloor; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { imp.ext = imp.ext || {}; imp.ext.ae = slot?.ortb2Imp?.ext?.ae } else { diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index 338ba6b4df4..7fcae1299b2 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -69,7 +69,7 @@ Please reach out to pmp@rtbhouse.com to receive your own # Protected Audience API (FLEDGE) support There’s an option to receive demand for Protected Audience API (FLEDGE/PAAPI) ads using RTB House bid adapter. -Prebid’s [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +Prebid’s [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module and Google Ad Manager is currently required. The following steps should be taken to setup Protected Audience for RTB House: @@ -77,15 +77,15 @@ The following steps should be taken to setup Protected Audience for RTB House: 1. Reach out to your RTB House representative for setup coordination. 2. Build and enable FLEDGE module as described in -[fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +[paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module documentation. a. Make sure to enable RTB House bidder to participate in FLEDGE. If there are any other bidders to be allowed for that, add them to the **bidders** array: ```javascript - pbjs.setBidderConfig({ - bidders: ["rtbhouse"], - config: { - fledgeEnabled: true + pbjs.setConfig({ + paapi: { + bidders: ["rtbhouse"], + enabled: true } }); ``` @@ -93,15 +93,15 @@ module documentation. b. If you as a publisher have your own [decisionLogicUrl](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) you may utilize it by setting up a dedicated `fledgeConfig` object: ```javascript - pbjs.setBidderConfig({ - bidders: ["rtbhouse"], - config: { - fledgeEnabled: true, - fledgeConfig: { - seller: 'https://seller.domain', - decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', - sellerTimeout: 100 - } + pbjs.setConfig({ + paapi: { + bidders: ["rtbhouse"], + enabled: true + }, + fledgeConfig: { + seller: 'https://seller.domain', + decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', + sellerTimeout: 100 } }); ``` diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9e47807bdc0..64bcdf78399 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -736,7 +736,7 @@ export const spec = { }); if (fledgeAuctionConfigs) { - return { bids, fledgeAuctionConfigs }; + return { bids, paapi: fledgeAuctionConfigs }; } else { return bids; } diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 590fddca079..92d36b0b699 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -108,7 +108,7 @@ export const sharethroughAdapterSpec = { const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); - if (bidderRequest.fledgeEnabled && bidReq.mediaTypes.banner) { + if (bidderRequest.paapi?.enabled && bidReq.mediaTypes.banner) { mergeDeep(impression, { ext: { ae: 1 } }); // ae = auction environment; if this is 1, ad server knows we have a fledge auction } @@ -242,7 +242,7 @@ export const sharethroughAdapterSpec = { if (fledgeAuctionEnabled) { return { bids: bidsFromExchange, - fledgeAuctionConfigs: body.ext?.auctionConfigs || {}, + paapi: body.ext?.auctionConfigs || {}, }; } else { return bidsFromExchange; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 47fca317de2..993c069ded0 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -336,7 +336,10 @@ function generateBidParameters(bid, bidderRequest) { if (placement) { bidObject.placement = placement; } - + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; + } const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); if (minDuration) { bidObject.minDuration = minDuration; diff --git a/modules/shinezRtbBidAdapter.js b/modules/shinezRtbBidAdapter.js index d1d9f36a569..490ea908960 100644 --- a/modules/shinezRtbBidAdapter.js +++ b/modules/shinezRtbBidAdapter.js @@ -149,15 +149,9 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { key = `uid.${idSystemProviderName}`; switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; case 'lipb': payloadRef[key] = userId.lipbid; break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; case 'id5id': payloadRef[key] = userId.uid; break; diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index bd2706a21d5..062e567a1c1 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -9,6 +9,12 @@ import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +/** + * See https://github.com/prebid/Prebid.js/pull/4222 for details on linting exception + * ShowHeroes only imports after winning a bid + * Also see https://github.com/prebid/Prebid.js/issues/11656 + */ +// eslint-disable-next-line no-restricted-imports import { loadExternalScript } from '../src/adloader.js'; const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; @@ -332,7 +338,7 @@ function createOutstreamEmbedCode(bid) { const fragment = window.document.createDocumentFragment(); - let script = loadExternalScript(urls.pubTag, 'outstream', function () { + let script = loadExternalScript(urls.pubTag, 'showheroes-bs', function () { window.ShowheroesTag = this; }); script.setAttribute('data-player-host', urls.vlHost); diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js deleted file mode 100644 index a9d92b67e24..00000000000 --- a/modules/sigmoidAnalyticsAdapter.js +++ /dev/null @@ -1,293 +0,0 @@ -/* Sigmoid Analytics Adapter for prebid.js v1.1.0-pre -Updated : 2018-03-28 */ -import {includes} from '../src/polyfill.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import { EVENTS } from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {generateUUID, logError, logInfo} from '../src/utils.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; - -const MODULE_CODE = 'sigmoid'; -const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); - -const url = 'https://kinesis.us-east-1.amazonaws.com/'; -const analyticsType = 'endpoint'; - -const auctionInitConst = EVENTS.AUCTION_INIT; -const auctionEndConst = EVENTS.AUCTION_END; -const bidWonConst = EVENTS.BID_WON; -const bidRequestConst = EVENTS.BID_REQUESTED; -const bidAdjustmentConst = EVENTS.BID_ADJUSTMENT; -const bidResponseConst = EVENTS.BID_RESPONSE; - -let initOptions = { publisherIds: [], utmTagData: [], adUnits: [] }; -let bidWon = {options: {}, events: []}; -let eventStack = {options: {}, events: []}; - -let auctionStatus = 'not_started'; - -let localStoragePrefix = 'sigmoid_analytics_'; -let utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; -let utmTimeoutKey = 'utm_timeout'; -let utmTimeout = 60 * 60 * 1000; -let sessionTimeout = 60 * 60 * 1000; -let sessionIdStorageKey = 'session_id'; -let sessionTimeoutKey = 'session_timeout'; - -function getParameterByName(param) { - let vars = {}; - window.location.href.replace(location.hash, '').replace( - /[?&]+([^=&]+)=?([^&]*)?/gi, - function(m, key, value) { - vars[key] = value !== undefined ? value : ''; - } - ); - - return vars[param] ? vars[param] : ''; -} - -function buildSessionIdLocalStorageKey() { - return localStoragePrefix.concat(sessionIdStorageKey); -} - -function buildSessionIdTimeoutLocalStorageKey() { - return localStoragePrefix.concat(sessionTimeoutKey); -} - -function updateSessionId() { - if (isSessionIdTimeoutExpired()) { - let newSessionId = generateUUID(); - storage.setDataInLocalStorage(buildSessionIdLocalStorageKey(), newSessionId); - } - initOptions.sessionId = getSessionId(); - updateSessionIdTimeout(); -} - -function updateSessionIdTimeout() { - storage.setDataInLocalStorage(buildSessionIdTimeoutLocalStorageKey(), Date.now()); -} - -function isSessionIdTimeoutExpired() { - let cpmSessionTimestamp = storage.getDataFromLocalStorage(buildSessionIdTimeoutLocalStorageKey()); - return Date.now() - cpmSessionTimestamp > sessionTimeout; -} - -function getSessionId() { - return storage.getDataFromLocalStorage(buildSessionIdLocalStorageKey()) ? storage.getDataFromLocalStorage(buildSessionIdLocalStorageKey()) : ''; -} - -function updateUtmTimeout() { - storage.setDataInLocalStorage(buildUtmLocalStorageTimeoutKey(), Date.now()); -} - -function isUtmTimeoutExpired() { - let utmTimestamp = storage.getDataFromLocalStorage(buildUtmLocalStorageTimeoutKey()); - return (Date.now() - utmTimestamp) > utmTimeout; -} - -function buildUtmLocalStorageTimeoutKey() { - return localStoragePrefix.concat(utmTimeoutKey); -} - -function buildUtmLocalStorageKey(utmMarkKey) { - return localStoragePrefix.concat(utmMarkKey); -} - -function checkOptions() { - if (typeof initOptions.publisherIds === 'undefined') { - return false; - } - - return initOptions.publisherIds.length > 0; -} - -function checkAdUnitConfig() { - if (typeof initOptions.adUnits === 'undefined') { - return false; - } - - return initOptions.adUnits.length > 0; -} - -function buildBidWon(eventType, args) { - bidWon.options = initOptions; - if (checkAdUnitConfig()) { - if (includes(initOptions.adUnits, args.adUnitCode)) { - bidWon.events = [{ args: args, eventType: eventType }]; - } - } else { - bidWon.events = [{ args: args, eventType: eventType }]; - } -} - -function buildEventStack() { - eventStack.options = initOptions; -} - -function filterBidsByAdUnit(bids) { - var filteredBids = []; - bids.forEach(function (bid) { - if (includes(initOptions.adUnits, bid.placementCode)) { - filteredBids.push(bid); - } - }); - return filteredBids; -} - -function isValidEvent(eventType, adUnitCode) { - if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; - if (!includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType)) { - return false; - } - } - return true; -} - -function isValidEventStack() { - if (eventStack.events.length > 0) { - return eventStack.events.some(function(event) { - return bidRequestConst === event.eventType || bidWonConst === event.eventType; - }); - } - return false; -} - -function isValidBidWon() { - return bidWon.events.length > 0; -} - -function flushEventStack() { - eventStack.events = []; -} - -let sigmoidAdapter = Object.assign(adapter({url, analyticsType}), - { - track({eventType, args}) { - if (!checkOptions()) { - return; - } - - let info = Object.assign({}, args); - - if (info && info.ad) { - info.ad = ''; - } - - if (eventType === auctionInitConst) { - auctionStatus = 'started'; - } - - if (eventType === bidWonConst && auctionStatus === 'not_started') { - updateSessionId(); - buildBidWon(eventType, info); - if (isValidBidWon()) { - send(eventType, bidWon, 'bidWon'); - } - return; - } - - if (eventType === auctionEndConst) { - updateSessionId(); - buildEventStack(); - if (isValidEventStack()) { - send(eventType, eventStack, 'eventStack'); - } - auctionStatus = 'not_started'; - } else { - pushEvent(eventType, info); - } - }, - - }); - -sigmoidAdapter.originEnableAnalytics = sigmoidAdapter.enableAnalytics; - -sigmoidAdapter.enableAnalytics = function (config) { - initOptions = config.options; - initOptions.utmTagData = this.buildUtmTagData(); - logInfo('Sigmoid Analytics enabled with config', initOptions); - sigmoidAdapter.originEnableAnalytics(config); -}; - -sigmoidAdapter.buildUtmTagData = function () { - let utmTagData = {}; - let utmTagsDetected = false; - utmTags.forEach(function(utmTagKey) { - let utmTagValue = getParameterByName(utmTagKey); - if (utmTagValue !== '') { - utmTagsDetected = true; - } - utmTagData[utmTagKey] = utmTagValue; - }); - utmTags.forEach(function(utmTagKey) { - if (utmTagsDetected) { - storage.setDataInLocalStorage(buildUtmLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); - updateUtmTimeout(); - } else { - if (!isUtmTimeoutExpired()) { - utmTagData[utmTagKey] = storage.getDataFromLocalStorage(buildUtmLocalStorageKey(utmTagKey)) ? storage.getDataFromLocalStorage(buildUtmLocalStorageKey(utmTagKey)) : ''; - updateUtmTimeout(); - } - } - }); - return utmTagData; -}; - -function send(eventType, data, sendDataType) { - // eslint-disable-next-line no-undef - AWS.config.credentials = new AWS.Credentials({ - accessKeyId: 'accesskey', secretAccessKey: 'secretkey' - }); - - // eslint-disable-next-line no-undef - AWS.config.region = 'us-east-1'; - // eslint-disable-next-line no-undef - AWS.config.credentials.get(function(err) { - // attach event listener - if (err) { - logError(err); - return; - } - // create kinesis service object - // eslint-disable-next-line no-undef - var kinesis = new AWS.Kinesis({ - apiVersion: '2013-12-02' - }); - var dataList = []; - var jsonData = {}; - jsonData['Data'] = JSON.stringify(data) + '\n'; - jsonData['PartitionKey'] = 'partition-' + Math.random().toString(36).substring(7); - dataList.push(jsonData); - kinesis.putRecords({ - Records: dataList, - StreamName: 'sample-stream' - }); - if (sendDataType === 'eventStack') { - flushEventStack(); - } - }); -}; - -function pushEvent(eventType, args) { - if (eventType === bidRequestConst) { - if (checkAdUnitConfig()) { - args.bids = filterBidsByAdUnit(args.bids); - } - if (args.bids.length > 0) { - eventStack.events.push({ eventType: eventType, args: args }); - } - } else { - if (isValidEvent(eventType, args.adUnitCode)) { - eventStack.events.push({ eventType: eventType, args: args }); - } - } -} - -adapterManager.registerAnalyticsAdapter({ - adapter: sigmoidAdapter, - code: MODULE_CODE, -}); - -export default sigmoidAdapter; diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js index 5403f3bd88c..1d5662f88eb 100644 --- a/modules/silverpushBidAdapter.js +++ b/modules/silverpushBidAdapter.js @@ -128,7 +128,7 @@ export const CONVERTER = ortbConverter({ }); return { bids: response.bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return response.bids diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 8394814365c..483a7a86d73 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -119,12 +119,6 @@ export const spec = { const pos = getBidIdParameter('pos', bid.params) || 1; const api = getBidIdParameter('api', bid.params) || [2]; const protocols = getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; - var contextcustom = deepAccess(bid, 'mediaTypes.video.context'); - var placement = 1; - - if (contextcustom === 'outstream') { - placement = 3; - } let smartxReq = [{ id: bid.bidId, @@ -144,7 +138,6 @@ export const spec = { maxbitrate: maxbitrate, delivery: delivery, pos: pos, - placement: placement, api: api, ext: ext }, diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 6920983e50d..9098bb8f862 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,4 +1,3 @@ -import { logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -74,23 +73,15 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; let placements = []; let request = { 'deviceWidth': winTop.screen.width, 'deviceHeight': winTop.screen.height, 'language': (navigator && navigator.language) ? navigator.language : '', 'secure': 1, - 'host': location.host, - 'page': location.pathname, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements, 'eeid': validBidRequests[0]?.userIdAsEids, diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js deleted file mode 100644 index 8242df7e0c5..00000000000 --- a/modules/sonobiAnalyticsAdapter.js +++ /dev/null @@ -1,275 +0,0 @@ -import { deepClone, logInfo, logError } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import { EVENTS } from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import {ajaxBuilder} from '../src/ajax.js'; - -let ajax = ajaxBuilder(0); - -export const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; -const analyticsType = 'endpoint'; -const QUEUE_TIMEOUT_DEFAULT = 200; -const { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BIDDER_DONE, - BID_WON, - BID_RESPONSE, - BID_TIMEOUT -} = EVENTS; - -let initOptions = {}; -let auctionCache = {}; -let auctionTtl = 60 * 60 * 1000; - -function deleteOldAuctions() { - for (let auctionId in auctionCache) { - let auction = auctionCache[auctionId]; - if (Date.now() - auction.start > auctionTtl) { - delete auctionCache[auctionId]; - } - } -} - -function buildAuctionEntity(args) { - return { - 'id': args.auctionId, - 'start': args.timestamp, - 'timeout': args.timeout, - 'adUnits': {}, - 'stats': {}, - 'queue': [], - 'qTimeout': false - }; -} -function buildAdUnit(data) { - return `/${initOptions.pubId}/${initOptions.siteId}/${data.adUnitCode.toLowerCase()}`; -} -function getLatency(data) { - if (!data.responseTimestamp) { - return -1; - } else { - return data.responseTimestamp - data.requestTimestamp; - } -} -function getBid(data) { - if (data.cpm) { - return Math.round(data.cpm * 100); - } else { - return 0; - } -} -function buildItem(data, response, phase = 1) { - let size = data.width ? {width: data.width, height: data.height} : {width: data.sizes[0][0], height: data.sizes[0][1]}; - return { - 'bidid': data.bidId || data.requestId, - 'p': phase, - 'buyerid': data.bidder.toLowerCase(), - 'bid': getBid(data), - 'adunit_code': buildAdUnit(data), - 's': `${size.width}x${size.height}`, - 'latency': getLatency(data), - 'response': response, - 'jsLatency': getLatency(data), - 'buyername': data.bidder.toLowerCase() - }; -} -function sendQueue(auctionId) { - let auction = auctionCache[auctionId]; - let data = auction.queue; - auction.queue = []; - auction.qTimeout = false; - sonobiAdapter.sendData(auction, data); -} -function addToAuctionQueue(auctionId, id) { - let auction = auctionCache[auctionId]; - auction.queue = auction.queue.filter((item) => { - if (item.bidid !== id) { return true; } - return auction.stats[id].data.p !== item.p; - }); - auction.queue.push(deepClone(auction.stats[id].data)); - if (!auction.qTimeout) { - auction.qTimeout = setTimeout(() => { - sendQueue(auctionId); - }, initOptions.delay) - } -} -function updateBidStats(auctionId, id, data) { - let auction = auctionCache[auctionId]; - auction.stats[id].data = {...auction.stats[id].data, ...data}; - addToAuctionQueue(auctionId, id); - _logInfo('Updated Bid Stats: ', auction.stats[id]); - return auction.stats[id]; -} - -function handleOtherEvents(eventType, args) { - _logInfo('Other Event: ' + eventType, args); -} - -function handlerAuctionInit(args) { - auctionCache[args.auctionId] = buildAuctionEntity(args); - deleteOldAuctions(); - _logInfo('Auction Init', args); -} -function handlerBidRequested(args) { - let auction = auctionCache[args.auctionId]; - let data = []; - let phase = 1; - let response = 1; - args.bids.forEach(function (bidRequest) { - auction = auctionCache[bidRequest.auctionId] - let built = buildItem(bidRequest, response, phase); - auction.stats[built.bidid] = {id: built.bidid, adUnitCode: bidRequest.adUnitCode, data: built}; - addToAuctionQueue(args.auctionId, built.bidid); - }) - - _logInfo('Bids Requested ', data); -} - -function handlerBidAdjustment(args) { - _logInfo('Bid Adjustment', args); -} -function handlerBidderDone(args) { - _logInfo('Bidder Done', args); -} - -function handlerAuctionEnd(args) { - let winners = {}; - args.bidsReceived.forEach((bid) => { - if (!winners[bid.adUnitCode]) { - winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; - } else if (winners[bid.adUnitCode].cpm < bid.cpm) { - winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; - } - }) - args.adUnitCodes.forEach((adUnitCode) => { - if (winners[adUnitCode]) { - let bidId = winners[adUnitCode].bidId; - updateBidStats(args.auctionId, bidId, {response: 4}); - } - }) - _logInfo('Auction End', args); - _logInfo('Auction Cache', auctionCache[args.auctionId].stats); -} -function handlerBidWon(args) { - let {auctionId, requestId} = args; - let res = updateBidStats(auctionId, requestId, {p: 3, response: 6}); - _logInfo('Bid Won ', args); - _logInfo('Bid Update Result: ', res); -} -function handlerBidResponse(args) { - let {auctionId, requestId, cpm, size, timeToRespond} = args; - updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9}); - - _logInfo('Bid Response ', args); -} -function handlerBidTimeout(args) { - let {auctionId, bidId} = args; - _logInfo('Bid Timeout ', args); - updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout}); -} -let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), { - track({eventType, args}) { - switch (eventType) { - case AUCTION_INIT: - handlerAuctionInit(args); - break; - case BID_REQUESTED: - handlerBidRequested(args); - break; - case BID_ADJUSTMENT: - handlerBidAdjustment(args); - break; - case BIDDER_DONE: - handlerBidderDone(args); - break; - case AUCTION_END: - handlerAuctionEnd(args); - break; - case BID_WON: - handlerBidWon(args); - break; - case BID_RESPONSE: - handlerBidResponse(args); - break; - case BID_TIMEOUT: - handlerBidTimeout(args); - break; - default: - handleOtherEvents(eventType, args); - break; - } - }, - -}); - -sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics; - -sonobiAdapter.enableAnalytics = function (config) { - if (this.initConfig(config)) { - _logInfo('Analytics adapter enabled', initOptions); - sonobiAdapter.originEnableAnalytics(config); - } -}; - -sonobiAdapter.initConfig = function (config) { - let isCorrectConfig = true; - initOptions = {}; - initOptions.options = deepClone(config.options); - - initOptions.pubId = initOptions.options.pubId || null; - initOptions.siteId = initOptions.options.siteId || null; - initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT; - if (!initOptions.pubId) { - _logError('"options.pubId" is empty'); - isCorrectConfig = false; - } - if (!initOptions.siteId) { - _logError('"options.siteId" is empty'); - isCorrectConfig = false; - } - - initOptions.server = DEFAULT_EVENT_URL; - initOptions.host = initOptions.options.host || window.location.hostname; - this.initOptions = initOptions; - return isCorrectConfig; -}; - -sonobiAdapter.getOptions = function () { - return initOptions; -}; - -sonobiAdapter.sendData = function (auction, data) { - let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId; - ajax( - url, - function () { _logInfo('Auction [' + auction.id + '] sent ', data); }, - JSON.stringify(data), - { - method: 'POST', - // withCredentials: true, - contentType: 'text/plain' - } - ); -}; - -function _logInfo(message, meta) { - logInfo(buildLogMessage(message), meta); -} - -function _logError(message) { - logError(buildLogMessage(message)); -} - -function buildLogMessage(message) { - return 'Sonobi Prebid Analytics: ' + message; -} - -adapterManager.registerAnalyticsAdapter({ - adapter: sonobiAdapter, - code: 'sonobi' -}); - -export default sonobiAdapter; diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js deleted file mode 100644 index f8329b33f3a..00000000000 --- a/modules/sovrnAnalyticsAdapter.js +++ /dev/null @@ -1,287 +0,0 @@ -import {deepClone, logError, timestamp} from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adaptermanager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; -import {ajaxBuilder} from '../src/ajax.js'; -import {config} from '../src/config.js'; -import {find, includes} from '../src/polyfill.js'; -import {getRefererInfo} from '../src/refererDetection.js'; - -const ajax = ajaxBuilder(0) - -const { - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BID_RESPONSE, - BID_WON -} = EVENTS; - -let pbaUrl = 'https://pba.aws.lijit.com/analytics' -let currentAuctions = {}; -const analyticsType = 'endpoint' - -const rootURL = (() => { - const ref = getRefererInfo(); - // TODO: does the fallback make sense here? - return ref.page || ref.topmostLocation; -})(); - -let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), { - track({ eventType, args }) { - try { - if (eventType === BID_WON) { - new BidWinner(this.sovrnId, args).send(); - return - } - if (args && args.auctionId && currentAuctions[args.auctionId] && currentAuctions[args.auctionId].status === 'complete') { - throw new Error('Event Received after Auction Close Auction Id ' + args.auctionId) - } - if (args && args.auctionId && currentAuctions[args.auctionId] === undefined) { - currentAuctions[args.auctionId] = new AuctionData(this.sovrnId, args.auctionId) - } - switch (eventType) { - case BID_REQUESTED: - currentAuctions[args.auctionId].bidRequested(args) - break - case BID_ADJUSTMENT: - currentAuctions[args.auctionId].originalBid(args) - break - case BID_RESPONSE: - currentAuctions[args.auctionId].adjustedBid(args) - break - case AUCTION_END: - currentAuctions[args.auctionId].send(); - break - } - } catch (e) { - new LogError(e, this.sovrnId, {eventType, args}).send() - } - }, -}) - -sovrnAnalyticsAdapter.getAuctions = function () { - return currentAuctions; -}; - -sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics; - -// override enableAnalytics so we can get access to the config passed in from the page -sovrnAnalyticsAdapter.enableAnalytics = function (config) { - let sovrnId = '' - if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) { - sovrnId = config.options.sovrnId || config.options.affiliateId; - } else { - logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.') - return - } - sovrnAnalyticsAdapter.sovrnId = sovrnId; - if (config.options.pbaUrl) { - pbaUrl = config.options.pbaUrl; - } - sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function -}; - -adaptermanager.registerAnalyticsAdapter({ - adapter: sovrnAnalyticsAdapter, - code: 'sovrn' -}); - -/** Class Representing a Winning Bid */ -class BidWinner { - /** - * Creates a new bid winner - * @param {string} sovrnId - the affiliate id from the analytics config - * @param {*} event - the args object from the auction event - */ - constructor(sovrnId, event) { - this.body = {} - // eslint-disable-next-line no-undef - this.body.prebidVersion = $$REPO_AND_VERSION$$ - this.body.sovrnId = sovrnId - this.body.winningBid = deepClone(event) - this.body.url = rootURL - this.body.payload = 'winner' - delete this.body.winningBid.ad - } - - /** - * Sends the auction to the the ingest server - */ - send() { - this.body.ts = timestamp() - ajax( - pbaUrl, - null, - JSON.stringify(this.body), - { - contentType: 'application/json', - method: 'POST', - } - ) - } -} - -/** Class representing an Auction */ -class AuctionData { - /** - * Create a new auction data collector - * @param {string} sovrnId - the affiliate id from the analytics config - * @param {string} auctionId - the auction id from the auction event - */ - constructor(sovrnId, auctionId) { - this.auction = {} - // eslint-disable-next-line no-undef - this.auction.prebidVersion = $$REPO_AND_VERSION$$ - this.auction.sovrnId = sovrnId - this.auction.auctionId = auctionId - this.auction.payload = 'auction' - this.auction.timeouts = { - buffer: config.getConfig('timeoutBuffer'), - bidder: config.getConfig('bidderTimeout'), - } - this.auction.priceGranularity = config.getConfig('priceGranularity') - this.auction.url = rootURL - this.auction.requests = [] - this.auction.unsynced = [] - this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode'] - - setTimeout(function(id) { - delete currentAuctions[id] - }, 300000, this.auction.auctionId) - } - - /** - * Record a bid request event - * @param {*} event - the args object from the auction event - */ - bidRequested(event) { - const eventCopy = deepClone(event) - delete eventCopy.doneCbCallCount - delete eventCopy.auctionId - this.auction.requests.push(eventCopy) - } - - /** - * Finds the bid from the auction that the event is associated with - * @param {*} event - the args object from the auction event - * @return {*} - the bid - */ - findBid(event) { - const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode)) - if (!bidder) { - this.auction.unsynced.push(deepClone(event)) - } - let bid = find(bidder.bids, b => (b.bidId === event.requestId)) - - if (!bid) { - event.unmatched = true - bidder.bids.push(deepClone(event)) - } - return bid - } - - /** - * Records the original bid before any adjustments have been made - * @param {*} event - the args object from the auction event - * NOTE: the bid adjustment occurs before the bid response - * the bid adjustment seems to be the bid ready to be adjusted - */ - originalBid(event) { - let bid = this.findBid(event) - if (bid) { - Object.assign(bid, deepClone(event)) - this.dropBidFields.forEach((f) => delete bid[f]) - } - } - - /** - * Replaces original values with adjusted values and records the original values for changed values - * in bid.originalValues - * @param {*} event - the args object from the auction event - */ - adjustedBid(event) { - let bid = this.findBid(event) - if (bid) { - bid.originalValues = Object.keys(event).reduce((o, k) => { - if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !includes(this.dropBidFields, k)) { - o[k] = bid[k] - bid[k] = event[k] - } - return o - }, {}) - } - } - - /** - * Sends the auction to the the ingest server - */ - send() { - let maxBids = {} - this.auction.requests.forEach(request => { - request.bids.forEach(bid => { - maxBids[bid.adUnitCode] = maxBids[bid.adUnitCode] || {cpm: 0} - if (bid.cpm > maxBids[bid.adUnitCode].cpm) { - maxBids[bid.adUnitCode] = bid - } - }) - }) - Object.keys(maxBids).forEach(unit => { - maxBids[unit].isAuctionWinner = true - }) - this.auction.ts = timestamp() - ajax( - pbaUrl, - () => { - currentAuctions[this.auction.auctionId] = {status: 'complete', auctionId: this.auction.auctionId} - }, - JSON.stringify(this.auction), - { - contentType: 'application/json', - method: 'POST', - } - ) - } -} -class LogError { - constructor(e, sovrnId, data) { - this.error = {} - this.error.payload = 'error' - this.error.message = e.message - this.error.stack = e.stack - this.error.data = data - // eslint-disable-next-line no-undef - this.error.prebidVersion = $$REPO_AND_VERSION$$ - this.error.sovrnId = sovrnId - this.error.url = rootURL - this.error.userAgent = navigator.userAgent - } - send() { - if (this.error.data && this.error.data.requests) { - this.error.data.requests.forEach(request => { - if (request.bids) { - request.bids.forEach(bid => { - if (bid.ad) { - delete bid.ad - } - }) - } - }) - } - if (ErrorEvent.data && this.error.data.ad) { - delete this.error.data.ad - } - this.error.ts = timestamp() - ajax( - pbaUrl, - null, - JSON.stringify(this.error), - { - contentType: 'application/json', - method: 'POST', - } - ) - } -} - -export default sovrnAnalyticsAdapter; diff --git a/modules/sovrnAnalyticsAdapter.md b/modules/sovrnAnalyticsAdapter.md deleted file mode 100644 index b4fe7c971a2..00000000000 --- a/modules/sovrnAnalyticsAdapter.md +++ /dev/null @@ -1,23 +0,0 @@ -# Overview - -``` -Module Name: Sovrn Analytics Adapter -Module Type: Analytics Adapter -Maintainer: exchange@sovrn.com -``` - -# Description - -Sovrn's analytics adaptor allows you to view detailed auction information in Meridian. - -For more information, visit Sovrn.com. - -# Test Parameters -``` -{ - provider: 'sovrn', - options: { - sovrnId: 'xxxxx', // Sovrn ID (required) you can get this by contacting Sovrn support. - } -} -``` diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index b6563cac4c5..53f6fb2f40d 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -28,6 +28,7 @@ const ORTB_VIDEO_PARAMS = { 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), 'placement': (value) => isInteger(value) && value >= 1 && value <= 5, + 'plcmt': (value) => isInteger(value) && value >= 1 && value <= 4, 'linearity': (value) => [1, 2].indexOf(value) !== -1, 'skip': (value) => [0, 1].indexOf(value) !== -1, 'skipmin': (value) => isInteger(value), @@ -139,7 +140,7 @@ export const spec = { } const auctionEnvironment = bid?.ortb2Imp?.ext?.ae - if (bidderRequest.fledgeEnabled && isInteger(auctionEnvironment)) { + if (bidderRequest.paapi?.enabled && isInteger(auctionEnvironment)) { imp.ext = imp.ext || {} imp.ext.ae = auctionEnvironment } else { @@ -288,7 +289,7 @@ export const spec = { }) return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js deleted file mode 100644 index c1f1c5159fc..00000000000 --- a/modules/spotxBidAdapter.js +++ /dev/null @@ -1,528 +0,0 @@ -import { - logError, - deepAccess, - isArray, - getDNT, - deepSetValue, - isEmpty, - _each, - logMessage, - logWarn, - isBoolean, - isNumber, - isPlainObject, - isFn, - setScriptAttributes, - getBidIdParameter -} from '../src/utils.js'; -import { config } from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { loadExternalScript } from '../src/adloader.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest - */ - -const BIDDER_CODE = 'spotx'; -const URL = 'https://search.spotxchange.com/openrtb/2.3/dados/'; -const ORTB_VERSION = '2.3'; -export const GOOGLE_CONSENT = { consented_providers: ['3', '7', '11', '12', '15', '20', '22', '35', '43', '46', '48', '55', '57', '61', '62', '66', '70', '80', '83', '85', '86', '89', '93', '108', '122', '124', '125', '126', '131', '134', '135', '136', '143', '144', '147', '149', '153', '154', '159', '161', '162', '165', '167', '171', '178', '184', '188', '192', '195', '196', '202', '209', '211', '218', '221', '228', '229', '230', '236', '239', '241', '253', '255', '259', '266', '271', '272', '274', '286', '291', '294', '303', '308', '310', '311', '313', '314', '316', '317', '322', '323', '327', '336', '338', '340', '348', '350', '358', '359', '363', '367', '370', '371', '384', '385', '389', '393', '394', '397', '398', '407', '414', '415', '424', '429', '430', '432', '436', '438', '440', '442', '443', '445', '448', '449', '453', '459', '479', '482', '486', '491', '492', '494', '495', '503', '505', '510', '522', '523', '528', '537', '540', '550', '559', '560', '568', '571', '574', '575', '576', '584', '585', '587', '588', '590', '591', '592', '595', '609', '621', '624', '723', '725', '733', '737', '776', '780', '782', '787', '797', '798', '802', '803', '814', '817', '820', '821', '827', '829', '839', '853', '864', '867', '874', '899', '904', '922', '926', '931', '932', '933', '938', '955', '973', '976', '979', '981', '985', '987', '991', '1003', '1024', '1025', '1027', '1028', '1029', '1033', '1034', '1040', '1047', '1048', '1051', '1052', '1053', '1054', '1062', '1063', '1067', '1072', '1085', '1092', '1095', '1097', '1099', '1100', '1107', '1126', '1127', '1143', '1149', '1152', '1162', '1166', '1167', '1170', '1171', '1172', '1188', '1192', '1199', '1201', '1204', '1205', '1211', '1212', '1215', '1220', '1225', '1226', '1227', '1230', '1232', '1236', '1241', '1248', '1250', '1252', '1268', '1275', '1276', '1284', '1286', '1298', '1301', '1307', '1312', '1313', '1317', '1329', '1336', '1344', '1345', '1356', '1362', '1365', '1375', '1403', '1409', '1411', '1415', '1416', '1419', '1423', '1440', '1442', '1449', '1451', '1455', '1456', '1468', '1496', '1503', '1509', '1512', '1514', '1517', '1520', '1525', '1540', '1547', '1548', '1555', '1558', '1570', '1575', '1577', '1579', '1583', '1584', '1591', '1598', '1603', '1608', '1613', '1616', '1626', '1631', '1633', '1638', '1642', '1648', '1651', '1652', '1653', '1660', '1665', '1667', '1669', '1671', '1674', '1677', '1678', '1682', '1684', '1697', '1703', '1705', '1716', '1720', '1721', '1722', '1725', '1732', '1733', '1735', '1739', '1741', '1745', '1750', '1753', '1760', '1765', '1769', '1776', '1780', '1782', '1786', '1791', '1794', '1799', '1800', '1801', '1810', '1827', '1831', '1832', '1834', '1837', '1840', '1843', '1844', '1845', '1858', '1859', '1863', '1866', '1870', '1872', '1875', '1878', '1880', '1882', '1883', '1889', '1892', '1896', '1898', '1899', '1902', '1905', '1911', '1922', '1928', '1929', '1934', '1942', '1943', '1944', '1945', '1958', '1960', '1962', '1963', '1964', '1967', '1968', '1978', '1985', '1986', '1987', '1998', '2003', '2007', '2012', '2013', '2027', '2035', '2038', '2039', '2044', '2047', '2052', '2056', '2059', '2062', '2064', '2068', '2070', '2072', '2078', '2079', '2084', '2088', '2090', '2095', '2100', '2103', '2107', '2109', '2113', '2115', '2121', '2127', '2130', '2133', '2137', '2140', '2141', '2145', '2147', '2150', '2156', '2166', '2170', '2171', '2176', '2177', '2179', '2183', '2186', '2192', '2198', '2202', '2205', '2214', '2216', '2219', '2220', '2222', '2223', '2224', '2225', '2227', '2228', '2234', '2238', '2247', '2251', '2253', '2262', '2264', '2271', '2276', '2278', '2279', '2282', '2290', '2292', '2295', '2299', '2305', '2306', '2310', '2311', '2312', '2315', '2320', '2325', '2328', '2331', '2334', '2335', '2336', '2337', '2343', '2346', '2354', '2357', '2358', '2359', '2366', '2370', '2373', '2376', '2377', '2380', '2382', '2387', '2389', '2392', '2394', '2400', '2403', '2405', '2406', '2407', '2410', '2411', '2413', '2414', '2415', '2416', '2418', '2422', '2425', '2427', '2435', '2437', '2440', '2441', '2447', '2453', '2459', '2461', '2462', '2464', '2467', '2468', '2472', '2477', '2481', '2484', '2486', '2492', '2493', '2496', '2497', '2498', '2499', '2504', '2506', '2510', '2511', '2512', '2517', '2526', '2527', '2531', '2532', '2534', '2542', '2544', '2552', '2555', '2559', '2563', '2564', '2567', '2568', '2569', '2571', '2572', '2573', '2575', '2577', '2579', '2583', '2584', '2586', '2589', '2595', '2596', '2597', '2601', '2604', '2605', '2609', '2610', '2612', '2614', '2621', '2622', '2624', '2628', '2629', '2632', '2634', '2636', '2639', '2643', '2645', '2646', '2647', '2649', '2650', '2651', '2652', '2656', '2657', '2658', '2660', '2661', '2662', '2663', '2664', '2669', '2670', '2673', '2676', '2677', '2678', '2681', '2682', '2684', '2685', '2686', '2689', '2690', '2691', '2695', '2698', '2699', '2702', '2704', '2705', '2706', '2707', '2709', '2710', '2713', '2714', '2727', '2729', '2739', '2758', '2765', '2766', '2767', '2768', '2770', '2771', '2772', '2776', '2777', '2778', '2779', '2780', '2783', '2784', '2786', '2787', '2791', '2792', '2793', '2797', '2798', '2801', '2802', '2803', '2805', '2808', '2809', '2810', '2811', '2812', '2813', '2814', '2817', '2818', '2824', '2826', '2827', '2829', '2830', '2831', '2832', '2834', '2836', '2838', '2840', '2842', '2843', '2844', '2850', '2851', '2852', '2854', '2858', '2860', '2862', '2864', '2865', '2866', '2867', '2868', '2869', '2871'] }; - -export const spec = { - code: BIDDER_CODE, - gvlid: 165, - supportedMediaTypes: [VIDEO], - - /** - * Determines whether or not the given bid request is valid. - * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid). - * - * @param {object} bid The bid to validate. - * @return {boolean} True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - if (bid && typeof bid.params !== 'object') { - logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); - return false; - } - - if (!deepAccess(bid, 'mediaTypes.video')) { - logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); - return false; - } - - const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!playerSize || !isArray(playerSize)) { - logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); - return false; - } - - if (!getBidIdParameter('channel_id', bid.params)) { - logError(BIDDER_CODE + ': channel_id is not present in bidder params'); - return false; - } - - if (deepAccess(bid, 'mediaTypes.video.context') == 'outstream' || deepAccess(bid, 'params.ad_unit') == 'outstream') { - if (!getBidIdParameter('outstream_function', bid.params)) { - if (!getBidIdParameter('outstream_options', bid.params)) { - logError(BIDDER_CODE + ': please define outstream_options parameter or override the default SpotX outstream rendering by defining your own Outstream function using field outstream_function.'); - return false; - } - if (!getBidIdParameter('slot', bid.params.outstream_options)) { - logError(BIDDER_CODE + ': please define parameter slot in outstream_options object in the configuration.'); - return false; - } - } - } - - return true; - }, - - /** - * Make a server request from the list of BidRequests. - * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @param {object} bidderRequest - The master bidRequest object. - * @return {ServerRequest} Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - // TODO: does the fallback make sense here? - const referer = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const isPageSecure = !!referer.match(/^https:/); - - const siteId = ''; - const spotxRequests = bidRequests.map(function(bid) { - let page; - if (getBidIdParameter('page', bid.params)) { - page = getBidIdParameter('page', bid.params); - } else { - page = referer; - } - - const channelId = getBidIdParameter('channel_id', bid.params); - let pubcid = null; - - const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - const contentWidth = playerSize[0][0]; - const contentHeight = playerSize[0][1]; - - const secure = isPageSecure || (getBidIdParameter('secure', bid.params) ? 1 : 0); - - const ext = { - sdk_name: 'Prebid 1+', - versionOrtb: ORTB_VERSION - }; - - if (getBidIdParameter('hide_skin', bid.params) != '') { - ext.hide_skin = +!!getBidIdParameter('hide_skin', bid.params); - } - - if (getBidIdParameter('ad_volume', bid.params) != '') { - ext.ad_volume = getBidIdParameter('ad_volume', bid.params); - } - - if (getBidIdParameter('ad_unit', bid.params) != '') { - ext.ad_unit = getBidIdParameter('ad_unit', bid.params); - } - - if (getBidIdParameter('outstream_options', bid.params) != '') { - ext.outstream_options = getBidIdParameter('outstream_options', bid.params); - } - - if (getBidIdParameter('outstream_function', bid.params) != '') { - ext.outstream_function = getBidIdParameter('outstream_function', bid.params); - } - - if (getBidIdParameter('custom', bid.params) != '') { - ext.custom = getBidIdParameter('custom', bid.params); - } - - if (getBidIdParameter('pre_market_bids', bid.params) != '' && isArray(getBidIdParameter('pre_market_bids', bid.params))) { - const preMarketBids = getBidIdParameter('pre_market_bids', bid.params); - ext.pre_market_bids = []; - for (let i in preMarketBids) { - const preMarketBid = preMarketBids[i]; - let vastStr = ''; - if (preMarketBid['vast_url']) { - vastStr = '' + preMarketBid['vast_url'] + ''; - } else if (preMarketBid['vast_string']) { - vastStr = preMarketBid['vast_string']; - } - ext.pre_market_bids.push({ - id: preMarketBid['deal_id'], - seatbid: [{ - bid: [{ - impid: Date.now(), - dealid: preMarketBid['deal_id'], - price: preMarketBid['price'], - adm: vastStr - }] - }], - cur: preMarketBid['currency'], - ext: { - event_log: [{}] - } - }); - } - } - - const mimes = getBidIdParameter('mimes', bid.params) || deepAccess(bid, 'mediaTypes.video.mimes') || ['application/javascript', 'video/mp4', 'video/webm']; - - const spotxReq = { - id: bid.bidId, - secure: secure, - video: { - w: contentWidth, - h: contentHeight, - ext: ext, - mimes: mimes - } - }; - - if (isFn(bid.getFloor)) { - let floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: 'video', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - spotxReq.bidfloor = floorInfo.floor; - } - } else if (getBidIdParameter('price_floor', bid.params) != '') { - spotxReq.bidfloor = getBidIdParameter('price_floor', bid.params); - } - - const startdelay = getBidIdParameter('start_delay', bid.params) || deepAccess(bid, 'mediaTypes.video.startdelay'); - if (startdelay) { - spotxReq.video.startdelay = 0 + Boolean(startdelay); - } - - const minduration = getBidIdParameter('min_duration', bid.params) || deepAccess(bid, 'mediaTypes.video.minduration'); - if (minduration) { - spotxReq.video.minduration = minduration; - } - - const maxduration = getBidIdParameter('max_duration', bid.params) || deepAccess(bid, 'mediaTypes.video.maxduration'); - if (maxduration) { - spotxReq.video.maxduration = maxduration; - } - - const placement = getBidIdParameter('placement_type', bid.params) || deepAccess(bid, 'mediaTypes.video.placement'); - if (placement) { - spotxReq.video.ext.placement = placement; - } - - const position = getBidIdParameter('position', bid.params) || deepAccess(bid, 'mediaTypes.video.pos'); - if (position) { - spotxReq.video.ext.pos = position; - } - - if (bid.crumbs && bid.crumbs.pubcid) { - pubcid = bid.crumbs.pubcid; - } - - const language = navigator.language ? 'language' : 'userLanguage'; - const device = { - h: screen.height, - w: screen.width, - dnt: getDNT() ? 1 : 0, - language: navigator[language].split('-')[0], - make: navigator.vendor ? navigator.vendor : '', - ua: navigator.userAgent - }; - - const requestPayload = { - id: channelId, - imp: spotxReq, - site: { - id: siteId, - page: page, - content: 'content', - }, - device: device, - ext: { - wrap_response: 1 - } - }; - - // If the publisher asks to ignore the bidder cache key we need to return the full vast xml - // so that it can be cached on the publishes specified server. - if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { - requestPayload['ext']['wrap_response'] = 0; - } - - if (getBidIdParameter('number_of_ads', bid.params)) { - requestPayload['ext']['number_of_ads'] = getBidIdParameter('number_of_ads', bid.params); - } - - const userExt = {}; - - if (getBidIdParameter('spotx_all_google_consent', bid.params) == 1) { - userExt['consented_providers_settings'] = GOOGLE_CONSENT; - } - - // Add GDPR flag and consent string - if (bidderRequest && bidderRequest.gdprConsent) { - userExt.consent = bidderRequest.gdprConsent.consentString; - - if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - deepSetValue(requestPayload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - deepSetValue(requestPayload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (bid.userIdAsEids) { - userExt.eids = bid.userIdAsEids; - - userExt.eids.forEach(eid => { - if (eid.source === 'uidapi.com') { - eid.uids.forEach(uid => { - uid.ext = uid.ext || {}; - uid.ext.rtiPartner = 'UID2' - }); - } - }); - } - - // Add common id if available - if (pubcid) { - userExt.fpc = pubcid; - } - - // Add schain object if it is present - if (bid && bid.schain) { - requestPayload['source'] = { - ext: { - schain: bid.schain - } - }; - } - - // Only add the user object if it's not empty - if (!isEmpty(userExt)) { - requestPayload.user = { ext: userExt }; - } - const urlQueryParams = 'src_sys=prebid'; - return { - method: 'POST', - url: URL + channelId + '?' + urlQueryParams, - data: requestPayload, - bidRequest: bidderRequest - }; - }); - - return spotxRequests; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidderRequest) { - const bidResponses = []; - const serverResponseBody = serverResponse.body; - - if (serverResponseBody && isArray(serverResponseBody.seatbid)) { - _each(serverResponseBody.seatbid, function(bids) { - _each(bids.bid, function(spotxBid) { - let currentBidRequest = {}; - for (let i in bidderRequest.bidRequest.bids) { - if (spotxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { - currentBidRequest = bidderRequest.bidRequest.bids[i]; - } - } - - /** - * Make sure currency and price are the right ones - * TODO: what about the pre_market_bid partners sizes? - */ - _each(currentBidRequest.params.pre_market_bids, function(pmb) { - if (pmb.deal_id == spotxBid.id) { - spotxBid.price = pmb.price; - serverResponseBody.cur = pmb.currency; - } - }); - - const bid = { - requestId: currentBidRequest.bidId, - currency: serverResponseBody.cur || 'USD', - cpm: spotxBid.price, - creativeId: spotxBid.crid || '', - dealId: spotxBid.dealid || '', - ttl: 360, - netRevenue: true, - channel_id: serverResponseBody.id, - mediaType: VIDEO, - width: spotxBid.w, - height: spotxBid.h - }; - - if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { - bid.vastXml = spotxBid.adm; - } else { - bid.cache_key = spotxBid.ext.cache_key; - bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key; - bid.videoCacheKey = spotxBid.ext.cache_key; - } - - bid.meta = bid.meta || {}; - if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { - bid.meta.advertiserDomains = spotxBid.adomain; - } - - const context1 = deepAccess(currentBidRequest, 'mediaTypes.video.context'); - const context2 = deepAccess(currentBidRequest, 'params.ad_unit'); - if (context1 == 'outstream' || context2 == 'outstream') { - const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); - const renderer = Renderer.install({ - id: 0, - renderNow: true, - url: '/', - config: { - adText: 'SpotX Outstream Video Ad via Prebid.js', - player_width: playersize[0][0], - player_height: playersize[0][1], - content_page_url: deepAccess(bidderRequest, 'data.site.page'), - ad_mute: +!!deepAccess(currentBidRequest, 'params.ad_mute'), - hide_skin: +!!deepAccess(currentBidRequest, 'params.hide_skin'), - outstream_options: deepAccess(currentBidRequest, 'params.outstream_options'), - outstream_function: deepAccess(currentBidRequest, 'params.outstream_function') - } - }); - - try { - renderer.setRender(outstreamRender); - renderer.setEventHandlers({ - impression: function impression() { - return logMessage('SpotX outstream video impression event'); - }, - loaded: function loaded() { - return logMessage('SpotX outstream video loaded event'); - }, - ended: function ended() { - logMessage('SpotX outstream renderer video event'); - } - }); - } catch (err) { - logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); - } - bid.renderer = renderer; - } - - bidResponses.push(bid); - }) - }); - } - - return bidResponses; - } -} - -function createOutstreamScript(bid) { - const script = window.document.createElement('script'); - let dataSpotXParams = createScriptAttributeMap(bid); - - script.type = 'text/javascript'; - script.src = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; - - setScriptAttributes(script, dataSpotXParams); - - return script; -} - -function outstreamRender(bid) { - if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { - const script = createOutstreamScript(bid); - bid.renderer.config.outstream_function(bid, script); - } else { - try { - const inIframe = getBidIdParameter('in_iframe', bid.renderer.config.outstream_options); - const easiUrl = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; - let attributes = createScriptAttributeMap(bid); - if (inIframe && window.document.getElementById(inIframe).nodeName == 'IFRAME') { - const rawframe = window.document.getElementById(inIframe); - let framedoc = rawframe.contentDocument; - if (!framedoc && rawframe.contentWindow) { - framedoc = rawframe.contentWindow.document; - } - loadExternalScript(easiUrl, BIDDER_CODE, undefined, framedoc, attributes); - } else { - loadExternalScript(easiUrl, BIDDER_CODE, undefined, undefined, attributes); - } - } catch (err) { - logError('[SPOTX][renderer] Error:' + err.message); - } - } -} - -function createScriptAttributeMap(bid) { - const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); - logMessage('[SPOTX][renderer] Handle SpotX outstream renderer'); - let dataSpotXParams = {}; - dataSpotXParams['data-spotx_channel_id'] = '' + bid.channel_id; - dataSpotXParams['data-spotx_vast_url'] = '' + bid.vastUrl; - dataSpotXParams['data-spotx_content_page_url'] = bid.renderer.config.content_page_url; - dataSpotXParams['data-spotx_ad_unit'] = 'incontent'; - - logMessage('[SPOTX][renderer] Default behavior'); - if (getBidIdParameter('ad_mute', bid.renderer.config.outstream_options)) { - dataSpotXParams['data-spotx_ad_mute'] = '1'; - } - dataSpotXParams['data-spotx_collapse'] = '0'; - dataSpotXParams['data-spotx_autoplay'] = '1'; - dataSpotXParams['data-spotx_blocked_autoplay_override_mode'] = '1'; - dataSpotXParams['data-spotx_video_slot_can_autoplay'] = '1'; - dataSpotXParams['data-spotx_content_container_id'] = slot; - - const playersizeAutoAdapt = getBidIdParameter('playersize_auto_adapt', bid.renderer.config.outstream_options); - if (playersizeAutoAdapt && isBoolean(playersizeAutoAdapt) && playersizeAutoAdapt === true) { - const ratio = bid.width && isNumber(bid.width) && bid.height && isNumber(bid.height) ? bid.width / bid.height : 4 / 3; - const slotClientWidth = window.document.getElementById(slot).clientWidth; - let playerWidth = bid.renderer.config.player_width; - let playerHeight = bid.renderer.config.player_height; - let contentWidth = 0; - let contentHeight = 0; - if (slotClientWidth < playerWidth) { - playerWidth = slotClientWidth; - playerHeight = playerWidth / ratio; - } - if (ratio <= 1) { - contentWidth = Math.round(playerHeight * ratio); - contentHeight = playerHeight; - } else { - contentWidth = playerWidth; - contentHeight = Math.round(playerWidth / ratio); - } - - dataSpotXParams['data-spotx_content_width'] = '' + contentWidth; - dataSpotXParams['data-spotx_content_height'] = '' + contentHeight; - } - - const customOverride = getBidIdParameter('custom_override', bid.renderer.config.outstream_options); - if (customOverride && isPlainObject(customOverride)) { - logMessage('[SPOTX][renderer] Custom behavior.'); - for (let name in customOverride) { - if (customOverride.hasOwnProperty(name)) { - if (name === 'channel_id' || name === 'vast_url' || name === 'content_page_url' || name === 'ad_unit') { - logWarn('[SPOTX][renderer] Custom behavior: following option cannot be overridden: ' + name); - } else { - dataSpotXParams['data-spotx_' + name] = customOverride[name]; - } - } - } - } - return dataSpotXParams; -} - -registerBidder(spec); diff --git a/modules/spotxBidAdapter.md b/modules/spotxBidAdapter.md deleted file mode 100644 index 0bd1cf71aa1..00000000000 --- a/modules/spotxBidAdapter.md +++ /dev/null @@ -1,136 +0,0 @@ -# Overview - -``` -Module Name: SpotX Bidder Adapter -Module Type: Bidder Adapter -Maintainer: teameighties@spotx.tv -``` - -# Description - -Connect to SpotX for bids. - -This adapter requires setup and approval from the SpotX team. - -# Test Parameters - Use case #1 - outstream with default rendering options -``` - var adUnits = [{ - code: 'something', - mediaTypes: { - video: { - context: 'outstream', // 'instream' or 'outstream' - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'spotx', - params: { - channel_id: 85394, - ad_unit: 'outstream', - outstream_options: { // Needed for the default outstream renderer - fields video_slot/content_width/content_height are mandatory - slot: 'adSlot1', - content_width: 300, - content_height: 250 - } - } - }] - }]; -``` - -# Test Parameters - Use case #2 - outstream with default rendering options + some other options -``` - var adUnits = [{ - code: 'something', - mediaTypes: { - video: { - context: 'outstream', // 'instream' or 'outstream' - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'spotx', - params: { - channel_id: 85394, - ad_unit: 'outstream', - outstream_options: { - slot: 'adSlot1', - custom_override: { // This option is not mandatory though used to override default renderer parameters using EASI player options in here: https://developer.spotxchange.com/content/local/docs/sdkDocs/EASI/README.md - content_width: 300, - content_height: 250, - collapse: '1', - hide_fullscreen: '1', - unmute_on_mouse: '1', - continue_out_of_view: '1', - ad_volume: '100', - content_container_id: 'video1', - hide_skin: '1', - spotx_all_google_consent: '1' - } - } - } - }] - }]; -``` - -# Test Parameters - Use case #3 - outstream with your own outstream redering function -``` - var adUnits = [{ - code: 'something', - mediaTypes: { - video: { - context: 'outstream', // 'instream' or 'outstream' - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'spotx', - params: { - channel_id: 79391, - ad_unit: 'outstream', - outstream_function: myOutstreamFunction // Override the default outstream renderer by this referenced function - } - }] - }]; -``` - -# Sample of a custom outstream rendering function -``` -function myOutstreamFunction(bid) { - const videoDiv = 'video1'; - const playerWidth = 300; - const playerHeight = 250; - - window.console.log('[SPOTX][renderer] Handle SpotX custom outstream renderer'); - let script = window.document.createElement('script'); - script.type = 'text/javascript'; - script.src = '//js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; - script.setAttribute('data-spotx_channel_id', '' + bid.channel_id); - script.setAttribute('data-spotx_vast_url', '' + bid.vastUrl); - script.setAttribute('data-spotx_content_width', playerWidth); - script.setAttribute('data-spotx_content_height', playerHeight); - script.setAttribute('data-spotx_content_page_url', bid.renderer.config.content_page_url); - if (bid.renderer.config.ad_mute) { - script.setAttribute('data-spotx_ad_mute', '0'); - } - script.setAttribute('data-spotx_ad_unit', 'incontent'); - script.setAttribute('data-spotx_collapse', '0'); - script.setAttribute('data-spotx_hide_fullscreen', '1'); - script.setAttribute('data-spotx_autoplay', '1'); - script.setAttribute('data-spotx_blocked_autoplay_override_mode', '1'); - script.setAttribute('data-spotx_video_slot_can_autoplay', '1'); - script.setAttribute('data-spotx_unmute_on_mouse', '1'); - script.setAttribute('data-spotx_click_to_replay', '1'); - script.setAttribute('data-spotx_continue_out_of_view', '1'); - script.setAttribute('data-spotx_ad_volume', '100'); - if (bid.renderer.config.inIframe && window.document.getElementById(bid.renderer.config.inIframe).nodeName == 'IFRAME') { - let rawframe = window.document.getElementById(bid.renderer.config.inIframe); - let framedoc = rawframe.contentDocument; - if (!framedoc && rawframe.contentWindow) { - framedoc = rawframe.contentWindow.document; - } - framedoc.body.appendChild(script); - } else { - window.document.getElementById(videoDiv).appendChild(script); - } -}; -``` diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js deleted file mode 100644 index ac5e86db19d..00000000000 --- a/modules/staqAnalyticsAdapter.js +++ /dev/null @@ -1,433 +0,0 @@ -import { logInfo, logError, parseUrl, _each } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import { EVENTS } from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { ajax } from '../src/ajax.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; - -const MODULE_CODE = 'staq'; -const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); - -const ANALYTICS_VERSION = '1.0.0'; -const DEFAULT_QUEUE_TIMEOUT = 4000; -const DEFAULT_HOST = 'tag.staq.com'; - -let staqAdapterRefWin; - -const STAQ_EVENTS = { - AUCTION_INIT: 'auctionInit', - BID_REQUEST: 'bidRequested', - BID_RESPONSE: 'bidResponse', - BID_WON: 'bidWon', - AUCTION_END: 'auctionEnd', - TIMEOUT: 'adapterTimedOut' -}; - -function buildRequestTemplate(connId) { - // TODO: what should these pick from refererInfo? - const url = staqAdapterRefWin.topmostLocation; - const ref = staqAdapterRefWin.topmostLocation; - const topLocation = staqAdapterRefWin.topmostLocation; - - return { - ver: ANALYTICS_VERSION, - domain: topLocation.hostname, - path: topLocation.pathname, - userAgent: navigator.userAgent, - connId: connId, - env: { - screen: { - w: window.screen.width, - h: window.screen.height - }, - lang: navigator.language - }, - src: getUmtSource(url, ref) - } -} - -let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { - track({ eventType, args }) { - if (!analyticsAdapter.context) { - return; - } - let handler = null; - switch (eventType) { - case EVENTS.AUCTION_INIT: - if (analyticsAdapter.context.queue) { - analyticsAdapter.context.queue.init(); - } - handler = trackAuctionInit; - break; - case EVENTS.BID_REQUESTED: - handler = trackBidRequest; - break; - case EVENTS.BID_RESPONSE: - handler = trackBidResponse; - break; - case EVENTS.BID_WON: - handler = trackBidWon; - break; - case EVENTS.BID_TIMEOUT: - handler = trackBidTimeout; - break; - case EVENTS.AUCTION_END: - handler = trackAuctionEnd; - break; - } - if (handler) { - let events = handler(args); - if (analyticsAdapter.context.queue) { - analyticsAdapter.context.queue.push(events); - if (eventType === EVENTS.BID_WON) { - analyticsAdapter.context.queue.updateWithWins(events); - } - } - if (eventType === EVENTS.AUCTION_END) { - sendAll(); - } - } - } -}); - -analyticsAdapter.context = {}; - -analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; - -analyticsAdapter.enableAnalytics = (config) => { - logInfo('Enabling STAQ Adapter'); - staqAdapterRefWin = getRefererInfo(window); - if (!config.options.connId) { - logError('ConnId is not defined. STAQ Analytics won\'t work'); - return; - } - if (!config.options.url) { - logError('URL is not defined. STAQ Analytics won\'t work'); - return; - } - analyticsAdapter.context = { - host: config.options.host || DEFAULT_HOST, - url: config.options.url, - connectionId: config.options.connId, - requestTemplate: buildRequestTemplate(config.options.connId), - queue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) - }; - analyticsAdapter.originEnableAnalytics(config); -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: analyticsAdapter, - code: MODULE_CODE, -}); - -export default analyticsAdapter; - -function sendAll() { - let events = analyticsAdapter.context.queue.popAll(); - if (events.length !== 0) { - let req = analyticsAdapter.context.requestTemplate; - req.auctionId = analyticsAdapter.context.auctionId; - req.events = events - - analyticsAdapter.ajaxCall(JSON.stringify(req)); - } -} - -analyticsAdapter.ajaxCall = function ajaxCall(data) { - logInfo('SENDING DATA: ' + data); - ajax(`https://${analyticsAdapter.context.url}/prebid/${analyticsAdapter.context.connectionId}`, () => {}, data, { contentType: 'text/plain' }); -}; - -function trackAuctionInit(args) { - analyticsAdapter.context.auctionTimeStart = Date.now(); - analyticsAdapter.context.auctionId = args.auctionId; - const event = createHbEvent(args.auctionId, undefined, STAQ_EVENTS.AUCTION_INIT); - return [event]; -} - -function trackBidRequest(args) { - return args.bids.map(bid => - createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_REQUEST, bid.adUnitCode)); -} - -function trackBidResponse(args) { - const event = createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_RESPONSE, - args.adUnitCode, args.cpm, args.timeToRespond / 1000, false, args); - return [event]; -} - -function trackBidWon(args) { - const event = createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_WON, args.adUnitCode, args.cpm, undefined, true, args); - return [event]; -} - -function trackAuctionEnd(args) { - const event = createHbEvent(args.auctionId, undefined, STAQ_EVENTS.AUCTION_END, undefined, - undefined, (Date.now() - analyticsAdapter.context.auctionTimeStart) / 1000); - return [event]; -} - -function trackBidTimeout(args) { - return args.map(arg => createHbEvent(arg.auctionId, arg.bidderCode, STAQ_EVENTS.TIMEOUT)); -} - -function createHbEvent(auctionId, adapter, event, adUnitCode = undefined, value = 0, time = 0, bidWon = undefined, eventArgs) { - let ev = { event: event }; - if (adapter) { - ev.adapter = adapter; - ev.bidderName = adapter; - } - if (adUnitCode) { - ev.adUnitCode = adUnitCode; - } - if (value) { - ev.cpm = value; - } - if (time) { - ev.timeToRespond = time; - } - if (typeof bidWon !== 'undefined') { - ev.bidWon = bidWon; - } else if (event === 'bidResponse') { - ev.bidWon = false; - } - ev.auctionId = auctionId; - - if (eventArgs) { - if (STAQ_EVENTS.BID_RESPONSE == event || STAQ_EVENTS.BID_WON == event) { - ev.width = eventArgs.width; - ev.height = eventArgs.height; - - ev.adId = eventArgs.adId; - } - } - - return ev; -} - -const UTM_TAGS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', - 'utm_c1', 'utm_c2', 'utm_c3', 'utm_c4', 'utm_c5' -]; -const STAQ_PREBID_KEY = 'staq_analytics'; -const DIRECT = '(direct)'; -const REFERRAL = '(referral)'; -const ORGANIC = '(organic)'; - -export let storage = { - getItem: (name) => { - return storageObj.getDataFromLocalStorage(name); - }, - setItem: (name, value) => { - storageObj.setDataInLocalStorage(name, value); - } -}; - -export function getUmtSource(pageUrl, referrer) { - let prevUtm = getPreviousTrafficSource(); - let currUtm = getCurrentTrafficSource(pageUrl, referrer); - let [updated, actual] = chooseActualUtm(prevUtm, currUtm); - if (updated) { - storeUtm(actual); - } - return actual; - - function getPreviousTrafficSource() { - let val = storage.getItem(STAQ_PREBID_KEY); - if (!val) { - return getDirect(); - } - return JSON.parse(val); - } - - function getCurrentTrafficSource(pageUrl, referrer) { - var source = getUTM(pageUrl); - if (source) { - return source; - } - if (referrer) { - let se = getSearchEngine(referrer); - if (se) { - return asUtm(se, ORGANIC, ORGANIC); - } - let parsedUrl = parseUrl(pageUrl); - let [refHost, refPath] = getReferrer(referrer); - if (refHost && refHost !== parsedUrl.hostname) { - return asUtm(refHost, REFERRAL, REFERRAL, '', refPath); - } - } - return getDirect(); - } - - function getSearchEngine(pageUrl) { - let engines = { - 'google': /^https?\:\/\/(?:www\.)?(?:google\.(?:com?\.)?(?:com|cat|[a-z]{2})|g.cn)\//i, - 'yandex': /^https?\:\/\/(?:www\.)?ya(?:ndex\.(?:com|net)?\.?(?:asia|mobi|org|[a-z]{2})?|\.ru)\//i, - 'bing': /^https?\:\/\/(?:www\.)?bing\.com\//i, - 'duckduckgo': /^https?\:\/\/(?:www\.)?duckduckgo\.com\//i, - 'ask': /^https?\:\/\/(?:www\.)?ask\.com\//i, - 'yahoo': /^https?\:\/\/(?:[-a-z]+\.)?(?:search\.)?yahoo\.com\//i - }; - - for (let engine in engines) { - if (engines.hasOwnProperty(engine) && engines[engine].test(pageUrl)) { - return engine; - } - } - } - - function getReferrer(referrer) { - let ref = parseUrl(referrer); - return [ref.hostname, ref.pathname]; - } - - function getUTM(pageUrl) { - let urlParameters = parseUrl(pageUrl).search; - if (!urlParameters['utm_campaign'] || !urlParameters['utm_source']) { - return; - } - let utmArgs = []; - _each(UTM_TAGS, (utmTagName) => { - let utmValue = urlParameters[utmTagName] || ''; - utmArgs.push(utmValue); - }); - return asUtm.apply(this, utmArgs); - } - - function getDirect() { - return asUtm(DIRECT, DIRECT, DIRECT); - } - - function storeUtm(utm) { - let val = JSON.stringify(utm); - storage.setItem(STAQ_PREBID_KEY, val); - } - - function asUtm(source, medium, campaign, term = '', content = '', c1 = '', c2 = '', c3 = '', c4 = '', c5 = '') { - let result = { - source: source, - medium: medium, - campaign: campaign - }; - if (term) { - result.term = term; - } - if (content) { - result.content = content; - } - if (c1) { - result.c1 = c1; - } - if (c2) { - result.c2 = c2; - } - if (c3) { - result.c3 = c3; - } - if (c4) { - result.c4 = c4; - } - if (c5) { - result.c5 = c5; - } - return result; - } - - function chooseActualUtm(prev, curr) { - if (ord(prev) < ord(curr)) { - return [true, curr]; - } - if (ord(prev) > ord(curr)) { - return [false, prev]; - } else { - if (prev.campaign === REFERRAL && prev.content !== curr.content) { - return [true, curr]; - } else if (prev.campaign === ORGANIC && prev.source !== curr.source) { - return [true, curr]; - } else if (isCampaignTraffic(prev) && (prev.campaign !== curr.campaign || prev.source !== curr.source)) { - return [true, curr]; - } - } - return [false, prev]; - } - - function ord(utm) { - switch (utm.campaign) { - case DIRECT: - return 0; - case ORGANIC: - return 1; - case REFERRAL: - return 2; - default: - return 3; - } - } - - function isCampaignTraffic(utm) { - return [DIRECT, REFERRAL, ORGANIC].indexOf(utm.campaign) === -1; - } -} - -/** - * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. - * @param callback - * @param ttl - * @constructor - */ -export function ExpiringQueue(callback, ttl) { - let queue = []; - let timeoutId; - - this.push = (event) => { - if (event instanceof Array) { - queue.push.apply(queue, event); - } else { - queue.push(event); - } - reset(); - }; - - this.updateWithWins = (winEvents) => { - winEvents.forEach(winEvent => { - queue.forEach(prevEvent => { - if (prevEvent.event === 'bidResponse' && - prevEvent.auctionId == winEvent.auctionId && - prevEvent.adUnitCode == winEvent.adUnitCode && - prevEvent.adId == winEvent.adId && - prevEvent.adapter == winEvent.adapter) { - prevEvent.bidWon = true; - } - }); - }); - } - - this.popAll = () => { - let result = queue; - queue = []; - reset(); - return result; - }; - - /** - * For test/debug purposes only - * @return {Array} - */ - this.peekAll = () => { - return queue; - }; - - this.init = reset; - - function reset() { - if (timeoutId) { - clearTimeout(timeoutId); - } - timeoutId = setTimeout(() => { - if (queue.length) { - callback(); - } - }, ttl); - } -} diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index ab5d5fef139..5fa7f2c8b8e 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -206,7 +206,7 @@ export const spec = { if (fledgeAuctionConfigs.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } return bids; diff --git a/modules/gdprEnforcement.js b/modules/tcfControl.js similarity index 90% rename from modules/gdprEnforcement.js rename to modules/tcfControl.js index caa498c7364..603c91443a3 100644 --- a/modules/gdprEnforcement.js +++ b/modules/tcfControl.js @@ -6,8 +6,8 @@ import {deepAccess, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; -import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../src/consentHandler.js'; +import {EVENTS} from '../src/constants.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; import { MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, @@ -23,10 +23,14 @@ import { import {registerActivityControl} from '../src/activities/rules.js'; import { ACTIVITY_ACCESS_DEVICE, - ACTIVITY_ENRICH_EIDS, ACTIVITY_ENRICH_UFPD, + ACTIVITY_ENRICH_EIDS, + ACTIVITY_ENRICH_UFPD, ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS, - ACTIVITY_SYNC_USER, ACTIVITY_TRANSMIT_EIDS, ACTIVITY_TRANSMIT_PRECISE_GEO, ACTIVITY_TRANSMIT_UFPD + ACTIVITY_SYNC_USER, + ACTIVITY_TRANSMIT_EIDS, + ACTIVITY_TRANSMIT_PRECISE_GEO, + ACTIVITY_TRANSMIT_UFPD } from '../src/activities/activities.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; @@ -37,7 +41,7 @@ export const ACTIVE_RULES = { }; const CONSENT_PATHS = { - purpose: 'purpose.consents', + purpose: false, feature: 'specialFeatureOptins' }; @@ -98,6 +102,7 @@ const RULE_HANDLES = []; // in JS we do not have access to the GVL; assume that everyone declares legitimate interest for basic ads const LI_PURPOSES = [2]; +const PUBLISHER_LI_PURPOSES = [2, 7, 9, 10]; /** * Retrieve a module's GVL ID. @@ -111,7 +116,7 @@ export function getGvlid(moduleType, moduleName, fallbackFn) { if (gvlMapping && gvlMapping[moduleName]) { return gvlMapping[moduleName]; } else if (moduleType === MODULE_TYPE_PREBID) { - return moduleName === 'cdep' ? FIRST_PARTY_GVLID : VENDORLESS_GVLID; + return VENDORLESS_GVLID; } else { let {gvlid, modules} = GDPR_GVLIDS.get(moduleName); if (gvlid == null && Object.keys(modules).length > 0) { @@ -163,15 +168,25 @@ export function shouldEnforce(consentData, purpose, name) { return consentData && consentData.gdprApplies; } -function getConsent(consentData, type, id, gvlId) { - let purpose = !!deepAccess(consentData, `vendorData.${CONSENT_PATHS[type]}.${id}`); - let vendor = !!deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); +function getConsentOrLI(consentData, path, id, acceptLI) { + const data = deepAccess(consentData, `vendorData.${path}`); + return !!data?.consents?.[id] || (acceptLI && !!data?.legitimateInterests?.[id]); +} - if (type === 'purpose' && LI_PURPOSES.includes(id)) { - purpose ||= !!deepAccess(consentData, `vendorData.purpose.legitimateInterests.${id}`); - vendor ||= !!deepAccess(consentData, `vendorData.vendor.legitimateInterests.${gvlId}`); +function getConsent(consentData, type, purposeNo, gvlId) { + let purpose; + if (CONSENT_PATHS[type] !== false) { + purpose = !!deepAccess(consentData, `vendorData.${CONSENT_PATHS[type]}.${purposeNo}`); + } else { + const [path, liPurposes] = gvlId === VENDORLESS_GVLID + ? ['publisher', PUBLISHER_LI_PURPOSES] + : ['purpose', LI_PURPOSES]; + purpose = getConsentOrLI(consentData, path, purposeNo, liPurposes.includes(purposeNo)); + } + return { + purpose, + vendor: getConsentOrLI(consentData, 'vendor', gvlId, LI_PURPOSES.includes(purposeNo)) } - return {purpose, vendor}; } /** @@ -192,14 +207,7 @@ export function validateRules(rule, consentData, currentModule, gvlId) { } const vendorConsentRequred = rule.enforceVendor && !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))); const {purpose, vendor} = getConsent(consentData, ruleOptions.type, ruleOptions.id, gvlId); - - let validation = (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); - - if (gvlId === FIRST_PARTY_GVLID) { - validation = (!rule.enforcePurpose || !!deepAccess(consentData, `vendorData.publisher.consents.${ruleOptions.id}`)); - } - - return validation; + return (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); } function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback = () => null) { diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index be3e8444dae..d99696152ba 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -21,38 +21,6 @@ export function reset() { iframeLoadedURL = []; } -const bidderIframeList = { - maxTopicCaller: 4, - bidders: [{ - bidder: 'pubmatic', - iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' - }, { - bidder: 'rtbhouse', - iframeURL: 'https://topics.authorizedvault.com/topicsapi.html' - }, { - bidder: 'openx', - iframeURL: 'https://pa.openx.net/topics_frame.html' - }, { - bidder: 'improvedigital', - iframeURL: 'https://hb.360yield.com/privacy-sandbox/topics.html' - }, { - bidder: 'onetag', - iframeURL: 'https://onetag-sys.com/static/topicsapi.html' - }, { - bidder: 'taboola', - iframeURL: 'https://cdn.taboola.com/libtrc/static/topics/taboola-prebid-browsing-topics.html' - }, { - bidder: 'discovery', - iframeURL: 'https://api.popin.cc/topic/prebid-topics-frame.html' - }, { - bidder: 'undertone', - iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html' - }, { - bidder: 'vidazoo', - iframeURL: 'https://static.vidazoo.com/topics_api/topics_frame.html' - }] -} - export const coreStorage = getCoreStorageManager(MODULE_NAME); export const topicStorageName = 'prebid:topics'; export const lastUpdated = 'lastUpdated'; @@ -161,7 +129,7 @@ export function processFpd(config, {global}, {data = topicsData} = {}) { */ export function getCachedTopics() { let cachedTopicData = []; - const topics = config.getConfig('userSync.topics') || bidderIframeList; + const topics = config.getConfig('userSync.topics'); const bidderList = topics.bidders || []; let storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); storedSegments && storedSegments.forEach((value, cachedBidder) => { @@ -244,7 +212,7 @@ function listenMessagesFromTopicIframe() { */ export function loadTopicsForBidders(doc = document) { if (!isTopicsSupported(doc)) return; - const topics = config.getConfig('userSync.topics') || bidderIframeList; + const topics = config.getConfig('userSync.topics'); if (topics) { listenMessagesFromTopicIframe(); diff --git a/modules/trafficgateBidAdapter.js b/modules/trafficgateBidAdapter.js index fcd84306099..d30d79ef3a6 100644 --- a/modules/trafficgateBidAdapter.js +++ b/modules/trafficgateBidAdapter.js @@ -2,7 +2,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {deepAccess, mergeDeep} from '../src/utils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'trafficgate'; const URL = 'https://[HOST].bc-plugin.com/prebidjs' @@ -13,7 +12,6 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - transformBidParams, isBannerBid }; @@ -88,14 +86,6 @@ const converter = ortbConverter({ } }); -function transformBidParams(params, isOpenRtb) { - return convertTypes({ - 'customFloor': 'number', - 'placementId': 'number', - 'host': 'string' - }, params); -} - function isBidRequestValid(bidRequest) { const isValid = bidRequest.params.placementId && bidRequest.params.host; if (!isValid) { diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 056ab2b9d19..a665de6140f 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -58,8 +58,8 @@ export const tripleliftAdapterSpec = { tlCall = tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); } - if (bidderRequest && bidderRequest.fledgeEnabled) { - tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.fledgeEnabled); + if (bidderRequest?.paapi?.enabled) { + tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.paapi.enabled); } if (config.getConfig('coppa') === true) { @@ -96,7 +96,7 @@ export const tripleliftAdapterSpec = { logMessage('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs }; } else { return bids; diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index 8b1656ec7a2..9dda76f6518 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -11,7 +11,7 @@ export const spec = { supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { - return (bidRequest.params.site_id && bidRequest.params.bidfloor && + return (bidRequest.params.site_id && deepAccess(bidRequest, 'mediaTypes.banner') && (deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0)); }, @@ -116,8 +116,6 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { adH = adSizes[0][1]; } - let bidFloor = Number(0); - let domain = window.location.host; let page = window.location.host + window.location.pathname + location.search + location.hash; @@ -129,8 +127,7 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { banner: { w: adW, h: adH - }, - bidfloor: bidFloor + } } ], site: { diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js index f509e68f9a2..bee32a19870 100644 --- a/modules/twistDigitalBidAdapter.js +++ b/modules/twistDigitalBidAdapter.js @@ -155,7 +155,7 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout data.gppSid = bidderRequest.ortb2.regs.gpp_sid; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); if (fledge) { data.fledge = fledge; diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index b825003f36f..39d77c81b57 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -226,7 +226,7 @@ export const adapter = { 'options': { 'contentType': 'application/json' }, - 'protectedAudienceEnabled': bidderRequest.fledgeEnabled + 'protectedAudienceEnabled': bidderRequest.paapi?.enabled }, validBidRequests, bidderRequest); }, @@ -261,7 +261,7 @@ export const adapter = { return { bids, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs }; } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index aa1601e95e3..53567032175 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -81,14 +81,6 @@ userIdAsEids = [ }] }, - { - source: 'parrable.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }, - { source: 'liveramp.com', uids: [{ diff --git a/modules/userId/index.js b/modules/userId/index.js index c2d7a9af2d8..2970bad296d 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -166,12 +166,11 @@ import {MODULE_TYPE_UID} from '../../src/activities/modules.js'; import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_ENRICH_EIDS} from '../../src/activities/activities.js'; import {activityParams} from '../../src/activities/activityParams.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../../src/userSync.js'; const MODULE_NAME = 'User ID'; const COOKIE = STORAGE_TYPE_COOKIES; const LOCAL_STORAGE = STORAGE_TYPE_LOCALSTORAGE; -const DEFAULT_SYNC_DELAY = 500; -const NO_AUCTION_DELAY = 0; export const PBJS_USER_ID_OPTOUT_NAME = '_pbjs_id_optout'; export const coreStorage = getCoreStorageManager('userId'); export const dep = { @@ -1160,8 +1159,8 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { ppidSource = userSync.ppid; if (userSync.userIds) { configRegistry = userSync.userIds; - syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; - auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY; + syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : USERSYNC_DEFAULT_CONFIG.syncDelay + auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : USERSYNC_DEFAULT_CONFIG.auctionDelay; updateSubmodules(); updateIdPriority(userSync.idPriority, submodules); initIdSystem({ready: true}); diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 1ec109ff309..9fb53c2c7b3 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -70,12 +70,6 @@ pbjs.setConfig({ params: { url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run } - }, { - name: 'parrableId', - params: { - // Replace partner with comma-separated (if more than one) Parrable Partner Client ID(s) for Parrable-aware bid adapters in use - partner: "30182847-e426-4ff9-b2b5-9ca1324ea09b" - } },{ name: 'identityLink', params: { diff --git a/modules/utiqSystem.js b/modules/utiqIdSystem.js similarity index 96% rename from modules/utiqSystem.js rename to modules/utiqIdSystem.js index 473dc5854a9..8228da3a629 100644 --- a/modules/utiqSystem.js +++ b/modules/utiqIdSystem.js @@ -1,7 +1,7 @@ /** * This module adds Utiq provided by Utiq SA/NV to the User ID module * The {@link module:modules/userId} module is required - * @module modules/utiqSystem + * @module modules/utiqIdSystem * @requires module:modules/userId */ import { logInfo } from '../src/utils.js'; @@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -const MODULE_NAME = 'utiq'; +const MODULE_NAME = 'utiqId'; const LOG_PREFIX = 'Utiq module'; export const storage = getStorageManager({ @@ -56,7 +56,7 @@ function getUtiqFromStorage() { } /** @type {Submodule} */ -export const utiqSubmodule = { +export const utiqIdSubmodule = { /** * Used to link submodule with config * @type {string} @@ -135,4 +135,4 @@ export const utiqSubmodule = { } }; -submodule('userId', utiqSubmodule); +submodule('userId', utiqIdSubmodule); diff --git a/modules/utiqSystem.md b/modules/utiqIdSystem.md similarity index 54% rename from modules/utiqSystem.md rename to modules/utiqIdSystem.md index d2c53480383..c7f4f95827f 100644 --- a/modules/utiqSystem.md +++ b/modules/utiqIdSystem.md @@ -5,7 +5,7 @@ Utiq ID Module. First, make sure to add the utiq submodule to your Prebid.js package with: ``` -gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqSystem +gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqIdSystem ``` ## Parameter Descriptions @@ -15,8 +15,3 @@ gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,ut | name | String | The name of the module | `"utiq"` | | params | Object | Object with configuration parameters for utiq User Id submodule | - | | params.maxDelayTime | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | -| bidders | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | [`"adf"`, `"adformPBS"`, `"ix"`] | -| storage | Object | Local storage configuration object | - | -| storage.type | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | -| storage.name | String | The name of the key in local storage where the user ID will be stored. | `"utiq"` | -| storage.expires | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required. | `1` | diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index ada843a6e45..f375e161f88 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -48,7 +48,6 @@ export const spec = { id: bidRequest.auctionId, mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' }; - bidRequest.params.bidFloor && (payload['bidFloor'] = bidRequest.params.bidFloor); return { method: 'POST', url: ENDPOINT_URL, diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index c5e35c6b138..fd53b684ec0 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -163,7 +163,7 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout data.gppSid = bidderRequest.ortb2.regs.gpp_sid; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); if (fledge) { data.fledge = fledge; @@ -217,15 +217,9 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { _each(userIds, (userId, idSystemProviderName) => { key = `uid.${idSystemProviderName}`; switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; case 'lipb': payloadRef[key] = userId.lipbid; break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; case 'id5id': payloadRef[key] = userId.uid; break; diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js index 8cedf9ac16a..b62474d0c25 100644 --- a/modules/videobyteBidAdapter.js +++ b/modules/videobyteBidAdapter.js @@ -19,6 +19,7 @@ const VIDEO_ORTB_PARAMS = [ 'minduration', 'maxduration', 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -191,16 +192,6 @@ function buildRequestData(bidRequest, bidderRequest) { } }); - // Placement Inference Rules: - // - If no placement is defined then default to 1 (In Stream) - video.placement = video.placement || 2; - - // - If product is instream (for instream context) then override placement to 1 - if (params.context === 'instream') { - video.startdelay = video.startdelay || 0; - video.placement = 1; - } - // bid floor const bidFloorRequest = { currency: bidRequest.params.cur || 'USD', diff --git a/modules/videojsVideoProvider.js b/modules/videojsVideoProvider.js index 7764e8af995..efe518ea495 100644 --- a/modules/videojsVideoProvider.js +++ b/modules/videojsVideoProvider.js @@ -6,7 +6,7 @@ import { } from '../libraries/video/constants/events.js'; // missing events: , AD_BREAK_START, , AD_BREAK_END, VIEWABLE, BUFFER, CAST, PLAYLIST_COMPLETE, RENDITION_UPDATE, PLAY_ATTEMPT_FAILED, AUTOSTART_BLOCKED import { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLCMT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END } from '../libraries/video/constants/ortb.js'; import { VIDEO_JS_VENDOR } from '../libraries/video/constants/vendorCodes.js'; import { submodule } from '../src/hook.js'; @@ -146,8 +146,9 @@ export function VideojsProvider(providerConfig, vjs_, adState_, timeState_, call // ~ Sort of resolved check if the player has a source to tell if the placement is instream // Still cannot reliably check what type of placement the player is if its outstream // i.e. we can't tell if its interstitial, in article, etc. + // update: cannot infer instream ever, always need declarations if (player.src()) { - video.placement = PLACEMENT.INSTREAM; + video.plcmt = PLCMT.ACCOMPANYING_CONTENT; } // Placement according to IQG Guidelines 4.2.8 diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js index e77477c812b..fa54f27e4c0 100644 --- a/modules/visiblemeasuresBidAdapter.js +++ b/modules/visiblemeasuresBidAdapter.js @@ -58,6 +58,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 92b7fc26e4c..c4ca5d299bc 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -147,7 +147,6 @@ const createVideoObject = (videoMediaTypes, videoParams) => { maxduration: getBidIdParameter('maxduration', videoParams) || 500, protocols: getBidIdParameter('protocols', videoParams) || [2, 3, 5, 6], startdelay: getBidIdParameter('startdelay', videoParams) || 0, - placement: getBidIdParameter('placement', videoParams) || videoMediaTypes.context === 'outstream' ? 3 : 1, skip: getBidIdParameter('skip', videoParams) || 1, skipafter: getBidIdParameter('skipafter', videoParams) || 0, minbitrate: getBidIdParameter('minbitrate', videoParams) || 0, diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 6cde0412071..d81227d3606 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -456,7 +456,7 @@ function bidToTag(bid) { } tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords) - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } diff --git a/modules/yahoosspBidAdapter.js b/modules/yahooAdsBidAdapter.js similarity index 99% rename from modules/yahoosspBidAdapter.js rename to modules/yahooAdsBidAdapter.js index 0453350a85a..c25fb677cda 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahooAdsBidAdapter.js @@ -45,7 +45,6 @@ const SUPPORTED_USER_ID_SOURCES = [ 'neustar.biz', 'nextroll.com', 'novatiq.com', - 'parrable.com', 'pubcid.org', 'quantcast.com', 'tapad.com', @@ -371,6 +370,7 @@ function appendImpObject(bid, openRtbObject) { pos: deepAccess(bid, 'params.bidOverride.imp.video.pos') || bid.mediaTypes.video.pos || undefined, playbackmethod: deepAccess(bid, 'params.bidOverride.imp.video.playbackmethod') || bid.mediaTypes.video.playbackmethod || undefined, placement: deepAccess(bid, 'params.bidOverride.imp.video.placement') || bid.mediaTypes.video.placement || undefined, + plcmt: deepAccess(bid, 'params.bidOverride.imp.video.plcmt') || bid.mediaTypes.video.plcmt || undefined, linearity: deepAccess(bid, 'params.bidOverride.imp.video.linearity') || bid.mediaTypes.video.linearity || 1, protocols: deepAccess(bid, 'params.bidOverride.imp.video.protocols') || bid.mediaTypes.video.protocols || [2, 5], startdelay: deepAccess(bid, 'params.bidOverride.imp.video.startdelay') || bid.mediaTypes.video.startdelay || 0, diff --git a/modules/yahoosspBidAdapter.md b/modules/yahooAdsBidAdapter.md similarity index 99% rename from modules/yahoosspBidAdapter.md rename to modules/yahooAdsBidAdapter.md index 62fe0f22a55..df9b71b2314 100644 --- a/modules/yahoosspBidAdapter.md +++ b/modules/yahooAdsBidAdapter.md @@ -581,6 +581,7 @@ Currently the bidOverride object only accepts the following: * pos * playbackmethod * placement + * plcmt * linearity * protocols * rewarded @@ -619,6 +620,7 @@ const adUnits = [{ pos: 1, playbackmethod: 0, placement: 1, + plcmt: 1, linearity: 1, protocols: [2,5], startdelay: 0, diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 25e4dc73b74..1b9ab08cfc1 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -1,5 +1,5 @@ import {buildUrl, generateUUID, getWindowLocation, logError, logInfo, parseSizesInput, parseUrl} from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; +import {ajax, fetch} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS, STATUS } from '../src/constants.js'; @@ -51,10 +51,6 @@ function getParameterByName(param) { return vars[param] ? vars[param] : ''; } -function isNavigatorSendBeaconSupported() { - return ('navigator' in window) && ('sendBeacon' in window.navigator); -} - function updateSessionId() { if (isSessionIdTimeoutExpired()) { let newSessionId = generateUUID(); @@ -89,11 +85,14 @@ function send(data, status) { hostname: 'analytics-prebid.yuktamedia.com', pathname: '/api/bids' }); - if (isNavigatorSendBeaconSupported()) { - window.navigator.sendBeacon(yuktamediaAnalyticsRequestUrl, JSON.stringify(data)); - } else { + fetch(yuktamediaAnalyticsRequestUrl, { + body: JSON.stringify(data), + keepalive: true, + withCredentials: true, + method: 'POST' + }).catch((_e) => { ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); - } + }); } var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { diff --git a/package-lock.json b/package-lock.json index 5819cb88217..24b7da35847 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,20 @@ { "name": "prebid.js", - "version": "8.52.1-pre", + "version": "9.0.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.52.1-pre", + "version": "9.0.0-pre", "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.16.7", + "@babel/core": "^7.24.6", "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", - "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^4.2.0", "dlv": "1.1.3", "dset": "3.1.2", @@ -94,6 +93,7 @@ "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", + "querystring": "^0.2.1", "resolve-from": "^5.0.0", "sinon": "^4.1.3", "through2": "^4.0.2", @@ -111,64 +111,64 @@ "yargs": "^1.3.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", - "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", - "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.6", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helpers": "^7.19.4", - "@babel/parser": "^7.19.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4", - "convert-source-map": "^1.7.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -179,102 +179,89 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", + "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", "dev": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || >=14.0.0" }, "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dependencies": { - "@babel/compat-data": "^7.20.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -284,12 +271,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -299,131 +287,123 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" }, "peerDependencies": { - "@babel/core": "^7.4.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", "dependencies": { - "@babel/types": "^7.18.9" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", - "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.19.4", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -433,121 +413,124 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", - "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dependencies": { - "@babel/types": "^7.19.4" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dependencies": { - "@babel/types": "^7.20.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", - "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -555,12 +538,13 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -569,234 +553,55 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", - "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", - "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", - "dependencies": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "engines": { "node": ">=6.9.0" }, @@ -804,21 +609,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -878,11 +668,11 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -891,6 +681,31 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -996,28 +811,60 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1027,11 +874,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1041,11 +888,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", - "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1054,19 +901,49 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", + "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" }, "engines": { @@ -1077,11 +954,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1091,11 +969,11 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", - "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", + "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1105,12 +983,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1120,11 +998,26 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1134,12 +1027,27 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1149,11 +1057,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1163,13 +1072,28 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1179,11 +1103,26 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { "node": ">=6.9.0" @@ -1193,11 +1132,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1207,12 +1146,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", - "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "dependencies": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1222,13 +1161,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", - "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", + "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-simple-access": "^7.19.4" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1238,14 +1177,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", - "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.19.1" + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1255,12 +1194,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1270,12 +1209,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1285,11 +1224,58 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1299,12 +1285,43 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", + "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { "node": ">=6.9.0" @@ -1314,11 +1331,43 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz", - "integrity": "sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { "node": ">=6.9.0" @@ -1328,11 +1377,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1342,12 +1391,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" }, "engines": { "node": ">=6.9.0" @@ -1357,11 +1406,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1371,16 +1420,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1390,11 +1439,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1404,12 +1453,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1419,11 +1468,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1433,11 +1482,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1447,11 +1496,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", + "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1461,11 +1510,26 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1475,12 +1539,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1489,38 +1553,43 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/preset-env": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", - "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", - "dependencies": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.19.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", + "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -1530,45 +1599,61 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.19.4", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.19.4", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1578,58 +1663,61 @@ } }, "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, "node_modules/@babel/runtime": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", - "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "dependencies": { - "regenerator-runtime": "^0.13.10" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1637,12 +1725,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1658,6 +1746,15 @@ "node": ">=0.1.90" } }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.22.2", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.22.2.tgz", @@ -1709,9 +1806,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1883,6 +1980,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", @@ -1897,6 +1995,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1916,18 +2015,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -1963,21 +2050,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -2132,78 +2204,65 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@ljharb/through": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.12.tgz", - "integrity": "sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.5" + "call-bind": "^1.0.7" }, "engines": { "node": ">= 0.4" @@ -2225,46 +2284,34 @@ "dev": true }, "node_modules/@percy/appium-app": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@percy/appium-app/-/appium-app-2.0.3.tgz", - "integrity": "sha512-6INeUJSyK2LzWV4Cc9bszNqKr3/NLcjFelUC2grjPnm6+jLA29inBF4ZE3PeTfLeCSw/0jyCGWV5fr9AyxtzCA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@percy/appium-app/-/appium-app-2.0.6.tgz", + "integrity": "sha512-0NT8xgaq4UOhcqVc4H3D440M7H5Zko8mDpY5j30TRpjOQ3ctLPJalmUVKOCFv4rSzjd2LmyE2F9KXTPA3zqQsw==", "dev": true, "dependencies": { - "@percy/sdk-utils": "^1.27.0-beta.0", + "@percy/sdk-utils": "^1.28.2", "tmp": "^0.2.1" }, "engines": { "node": ">=14" } }, - "node_modules/@percy/appium-app/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, "node_modules/@percy/sdk-utils": { - "version": "1.27.7", - "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.27.7.tgz", - "integrity": "sha512-E21dIEQ9wwGDno41FdMDYf6jJow5scbWGClqKE/ptB+950W4UF5C4hxhVVQoEJxDdLE/Gy/8ZJR7upvPHShWDg==", + "version": "1.28.7", + "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.7.tgz", + "integrity": "sha512-LIhfHnkcS0fyIdo3gvKn7rwodZjbEtyLkgiDRSRulcBOatI2mhn2Bh269sXXiiFTyAW2BDQjyE3DWc4hkGbsbQ==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@percy/selenium-webdriver": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@percy/selenium-webdriver/-/selenium-webdriver-2.0.3.tgz", - "integrity": "sha512-JfLJVRkwNfqVofe7iGKtoQbOcKSSj9t4pWFbSUk95JfwAA7b9/c+dlBsxgIRrdrMYzLRjnJkYAFSZkJ4F4A19A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@percy/selenium-webdriver/-/selenium-webdriver-2.0.5.tgz", + "integrity": "sha512-bNj52xQm02dY872loFa+8OwyuGcdYHYvCKflmSEsF9EDRiSDj0Wr+XP+DDIgDAl9xXschA7OOdXCLTWV4zEQWA==", "dev": true, "dependencies": { - "@percy/sdk-utils": "^1.27.2", + "@percy/sdk-utils": "^1.28.0", "node-request-interceptor": "^0.6.3" }, "engines": { @@ -2282,11 +2329,32 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, + "node_modules/@promptbook/utils": { + "version": "0.50.0-10", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.50.0-10.tgz", + "integrity": "sha512-Z94YoY/wcZb5m1QoXgmIC1rVeDguGK5bWmUTYdWCqh/LHVifRdJ1C+tBzS0h+HMOD0XzMjZhBQ/mBgTZ/QNW/g==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "dependencies": { + "moment": "2.30.1", + "prettier": "2.8.1", + "spacetrim": "0.11.25" + } + }, "node_modules/@puppeteer/browsers": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", @@ -2308,26 +2376,21 @@ "node": ">=16.3.0" } }, - "node_modules/@puppeteer/browsers/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@puppeteer/browsers/node_modules/yargs": { @@ -2355,21 +2418,21 @@ "dev": true }, "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -2402,21 +2465,21 @@ "dev": true }, "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.0" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" } }, "node_modules/@tootallnate/once": { @@ -2437,21 +2500,21 @@ "dev": true }, "node_modules/@types/aria-query": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "dev": true, "dependencies": { "@types/http-cache-semantics": "*", - "@types/keyv": "*", + "@types/keyv": "^3.1.4", "@types/node": "*", - "@types/responselike": "*" + "@types/responselike": "^1.0.0" } }, "node_modules/@types/cookie": { @@ -2461,27 +2524,27 @@ "dev": true }, "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, "dependencies": { "@types/ms": "*" } }, "node_modules/@types/eslint": { - "version": "8.4.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", - "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2489,9 +2552,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -2499,9 +2562,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/expect": { @@ -2511,9 +2574,9 @@ "dev": true }, "node_modules/@types/extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", - "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.4.tgz", + "integrity": "sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==", "dev": true }, "node_modules/@types/gitconfiglocal": { @@ -2522,19 +2585,23 @@ "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "node_modules/@types/github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==", - "dev": true + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } }, "node_modules/@types/hast": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", - "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", "dev": true, "dependencies": { - "@types/unist": "*" + "@types/unist": "^2" } }, "node_modules/@types/http-cache-semantics": { @@ -2568,9 +2635,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/json5": { @@ -2580,24 +2647,29 @@ "dev": true }, "node_modules/@types/keyv": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-4.2.0.tgz", - "integrity": "sha512-xoBtGl5R9jeKUhc8ZqeYaRDx04qqJ10yhhXYGmJ4Jr8qKpvMsDQQrNUvF/wUJ4klOtmJeJM+p2Xo3zp9uaC3tw==", - "deprecated": "This is a stub types definition. keyv provides its own type definitions, so you do not need this installed.", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", "dev": true, "dependencies": { - "keyv": "*" + "@types/node": "*" } }, "node_modules/@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "dev": true, "dependencies": { - "@types/unist": "*" + "@types/unist": "^2" } }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, "node_modules/@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -2605,30 +2677,36 @@ "dev": true }, "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, "node_modules/@types/node": { - "version": "20.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz", - "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==", + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "dependencies": { "@types/node": "*" @@ -2641,9 +2719,9 @@ "dev": true }, "node_modules/@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", "dev": true }, "node_modules/@types/triple-beam": { @@ -2653,21 +2731,21 @@ "dev": true }, "node_modules/@types/ua-parser-js": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", - "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", "dev": true }, "node_modules/@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "dev": true }, "node_modules/@types/vinyl": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", - "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", "dev": true, "dependencies": { "@types/expect": "^1.20.4", @@ -2675,9 +2753,9 @@ } }, "node_modules/@types/which": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", - "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true }, "node_modules/@types/ws": { @@ -2705,9 +2783,9 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "dependencies": { @@ -2715,17 +2793,17 @@ } }, "node_modules/@videojs/http-streaming": { - "version": "2.14.3", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz", - "integrity": "sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.3.tgz", + "integrity": "sha512-91CJv5PnFBzNBvyEjt+9cPzTK/xoVixARj2g7ZAvItA+5bx8VKdk5RxCz/PP2kdzz9W+NiDUMPkdmTsosmy69Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "3.0.5", "aes-decrypter": "3.1.3", "global": "^4.4.0", - "m3u8-parser": "4.7.1", - "mpd-parser": "0.21.1", + "m3u8-parser": "4.8.0", + "mpd-parser": "^0.22.1", "mux.js": "6.0.1", "video.js": "^6 || ^7" }, @@ -2764,9 +2842,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.1.tgz", - "integrity": "sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -2777,131 +2855,79 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@vue/compiler-core": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", - "integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", "dev": true, "optional": true, "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.41", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", + "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz", - "integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", "dev": true, "optional": true, "dependencies": { - "@vue/compiler-core": "3.2.41", - "@vue/shared": "3.2.41" + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz", - "integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", "dev": true, "optional": true, "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.41", - "@vue/compiler-dom": "3.2.41", - "@vue/compiler-ssr": "3.2.41", - "@vue/reactivity-transform": "3.2.41", - "@vue/shared": "3.2.41", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz", - "integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==", - "dev": true, - "optional": true, - "dependencies": { - "@vue/compiler-dom": "3.2.41", - "@vue/shared": "3.2.41" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz", - "integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", "dev": true, "optional": true, "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.41", - "@vue/shared": "3.2.41", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/shared": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz", - "integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true, "optional": true }, "node_modules/@wdio/browserstack-service": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.29.1.tgz", - "integrity": "sha512-dLEJcdVF0Cu+2REByVOfLUzx9FvMias1VsxSCZpKXeIAGAIWBBdNdooK6Vdc9QdS36S5v/mk0/rTTQhYn4nWjQ==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.38.2.tgz", + "integrity": "sha512-dBvTK97deVbyDskCRdcQ47xuR7QYx3mqNFJUZLWBitwFV/DU5YIpCbGlySLc4gkO4//Zn1A3Gh/TOGWZrigcCQ==", "dev": true, "dependencies": { "@percy/appium-app": "^2.0.1", "@percy/selenium-webdriver": "^2.0.3", "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "8.28.0", - "@wdio/reporter": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "browserstack-local": "^1.5.1", "chalk": "^5.3.0", "csv-writer": "^1.6.0", @@ -2910,9 +2936,9 @@ "gitconfiglocal": "^2.1.0", "got": "^12.6.1", "uuid": "^9.0.0", - "webdriverio": "8.29.1", + "webdriverio": "8.38.2", "winston-transport": "^4.5.0", - "yauzl": "^2.10.0" + "yauzl": "^3.0.0" }, "engines": { "node": "^16.13 || >=18" @@ -2922,20 +2948,16 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" }, @@ -2943,7 +2965,7 @@ "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -2954,71 +2976,98 @@ } } }, - "node_modules/@wdio/browserstack-service/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "node_modules/@wdio/browserstack-service/node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">=14.16" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "node_modules/@wdio/browserstack-service/node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.1" + "debug": "^4.3.4" }, "engines": { - "node": ">=14.16" + "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "node_modules/@wdio/browserstack-service/node_modules/agent-base/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, - "optional": true, - "peer": true + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/browserstack-service/node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@wdio/browserstack-service/node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/browserstack-service/node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "dependencies": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/browserstack-service/node_modules/async": { @@ -3036,31 +3085,37 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "node_modules/@wdio/browserstack-service/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, - "engines": { - "node": ">=14.16" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/@wdio/browserstack-service/node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "node_modules/@wdio/browserstack-service/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, "engines": { - "node": ">=14.16" + "node": ">=8.0.0" } }, "node_modules/@wdio/browserstack-service/node_modules/chalk": { @@ -3076,9 +3131,9 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -3096,64 +3151,74 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/browserstack-service/node_modules/crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/browserstack-service/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "node_modules/@wdio/browserstack-service/node_modules/devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, @@ -3162,139 +3227,134 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "isexe": "^3.1.1" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" }, "bin": { - "node-which": "bin/which.js" + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" + "debug": "4" }, "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/shirshak55" + "node": ">= 6.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", "dev": true, "optional": true, "peer": true, - "engines": { - "node": ">=10" + "dependencies": { + "mitt": "3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/@wdio/browserstack-service/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optional": true, + "peer": true, + "dependencies": { + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/browserstack-service/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "ms": "2.1.2" }, "engines": { - "node": ">=12" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } + "optional": true, + "peer": true }, - "node_modules/@wdio/browserstack-service/node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "node": ">= 6" } }, - "node_modules/@wdio/browserstack-service/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@tootallnate/once": "2", "agent-base": "6", "debug": "4" }, @@ -3302,90 +3362,176 @@ "node": ">= 6" } }, - "node_modules/@wdio/browserstack-service/node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": ">=10.19.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "optional": true, "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, "engines": { - "node": ">=16" + "node": ">= 6" } }, - "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "optional": true, "peer": true, "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "optional": true, "peer": true, "dependencies": { - "ms": "2.0.0" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@wdio/browserstack-service/node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "node_modules/@wdio/browserstack-service/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "optional": true, + "peer": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/@wdio/browserstack-service/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, "engines": { - "node": ">=12" + "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "node_modules/@wdio/browserstack-service/node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/browserstack-service/node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@wdio/browserstack-service/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3425,27 +3571,6 @@ } } }, - "node_modules/@wdio/browserstack-service/node_modules/normalize-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", - "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, "node_modules/@wdio/browserstack-service/node_modules/proxy-agent": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", @@ -3465,61 +3590,44 @@ "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 14" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -3530,33 +3638,49 @@ } } }, - "node_modules/@wdio/browserstack-service/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">= 6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "lowercase-keys": "^3.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@wdio/browserstack-service/node_modules/serialize-error": { @@ -3574,15 +3698,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/@wdio/browserstack-service/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/browserstack-service/node_modules/type-fest": { @@ -3598,9 +3720,9 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -3622,40 +3744,28 @@ "node": "*" } }, - "node_modules/@wdio/browserstack-service/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@wdio/browserstack-service/node_modules/webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -3664,7 +3774,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" + "webdriver": "8.38.2" }, "engines": { "node": "^16.13 || >=18" @@ -3678,104 +3788,6 @@ } } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/@wdio/browserstack-service/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", @@ -3816,33 +3828,33 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/cli": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.29.1.tgz", - "integrity": "sha512-WWRTf0g0O+ovTTvS1kEhZ/svX32M7jERuuMF1MaldKCi7rZwHsQqOyJD+fO1UDjuxqS96LHSGsZn0auwUfCTXA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.38.2.tgz", + "integrity": "sha512-p9y6jxmpmw53OoB9v/uTLwMetmz7Q0K7NewdVONgmeTY/ERpkU15qL3fMw1rXb+E+vrV8dlce4srnXroec6SFA==", "dev": true, "dependencies": { "@types/node": "^20.1.1", "@vitest/snapshot": "^1.2.1", - "@wdio/config": "8.29.1", - "@wdio/globals": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/globals": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", @@ -3855,9 +3867,9 @@ "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "read-pkg-up": "^10.0.0", + "read-pkg-up": "10.0.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.29.1", + "webdriverio": "8.38.2", "yargs": "^17.7.2" }, "bin": { @@ -3868,20 +3880,16 @@ } }, "node_modules/@wdio/cli/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" }, @@ -3889,7 +3897,7 @@ "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -3900,13 +3908,34 @@ } } }, + "node_modules/@wdio/cli/node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/cli/node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@wdio/cli/node_modules/@puppeteer/browsers/node_modules/yargs": { "version": "17.7.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -3920,47 +3949,87 @@ "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "node_modules/@wdio/cli/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "optional": true, - "peer": true + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/cli/node_modules/agent-base/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/cli/node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@wdio/cli/node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/cli/node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "dependencies": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" + } + }, + "node_modules/@wdio/cli/node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@wdio/cli/node_modules/async": { @@ -3978,6 +4047,39 @@ "balanced-match": "^1.0.0" } }, + "node_modules/@wdio/cli/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@wdio/cli/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@wdio/cli/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -3991,9 +4093,9 @@ } }, "node_modules/@wdio/cli/node_modules/chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -4011,64 +4113,86 @@ } }, "node_modules/@wdio/cli/node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" + } + }, + "node_modules/@wdio/cli/node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@wdio/cli/node_modules/crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/cli/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@wdio/cli/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "node_modules/@wdio/cli/node_modules/devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, @@ -4077,166 +4201,111 @@ } }, "node_modules/@wdio/cli/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "isexe": "^3.1.1" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" }, "bin": { - "node-which": "bin/which.js" + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=14.0.0" + "peerDependencies": { + "typescript": ">= 4.7.4" }, - "funding": { - "url": "https://github.com/sponsors/shirshak55" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/cli/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "debug": "4" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">= 6.0.0" } }, - "node_modules/@wdio/cli/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" + "mitt": "3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/@wdio/cli/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/cli/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/cli/node_modules/hosted-git-info": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", - "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.0.1" + "node": ">=6.0" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/cli/node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "optional": true, + "peer": true }, - "node_modules/@wdio/cli/node_modules/http-proxy-agent": { + "node_modules/@wdio/cli/node_modules/devtools/node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", @@ -4252,78 +4321,214 @@ "node": ">= 6" } }, - "node_modules/@wdio/cli/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "optional": true, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 6" } }, - "node_modules/@wdio/cli/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/cli/node_modules/devtools/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, "optional": true, "peer": true, + "dependencies": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + }, "engines": { - "node": ">=16" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/cli/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", - "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 6" } }, - "node_modules/@wdio/cli/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "optional": true, "peer": true, "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/@wdio/cli/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "optional": true, "peer": true, "dependencies": { - "ms": "2.0.0" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/@wdio/cli/node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/@wdio/cli/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/cli/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { - "p-locate": "^6.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@wdio/cli/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/cli/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/cli/node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/cli/node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/cli/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -4331,6 +4536,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wdio/cli/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/@wdio/cli/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@wdio/cli/node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -4344,9 +4570,9 @@ } }, "node_modules/@wdio/cli/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -4386,25 +4612,10 @@ } } }, - "node_modules/@wdio/cli/node_modules/normalize-package-data": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", - "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/@wdio/cli/node_modules/npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { "path-key": "^4.0.0" @@ -4431,76 +4642,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/parse-json": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", - "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "error-ex": "^1.3.2", - "json-parse-even-better-errors": "^3.0.0", - "lines-and-columns": "^2.0.3", - "type-fest": "^3.8.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/parse-json/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/@wdio/cli/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -4532,70 +4673,44 @@ "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "node": ">=6.0" }, - "engines": { - "node": ">= 14" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } + "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@wdio/cli/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -4606,68 +4721,49 @@ } } }, - "node_modules/@wdio/cli/node_modules/read-pkg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", - "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^6.0.0", - "parse-json": "^7.0.0", - "type-fest": "^4.2.0" + "ms": "2.1.2" }, "engines": { - "node": ">=16" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/cli/node_modules/read-pkg-up": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", - "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", - "dev": true, - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^8.1.0", - "type-fest": "^4.2.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true }, - "node_modules/@wdio/cli/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } + "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/@wdio/cli/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@wdio/cli/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@wdio/cli/node_modules/serialize-error": { @@ -4685,18 +4781,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/serialize-error/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/cli/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4709,33 +4793,31 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/@wdio/cli/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/cli/node_modules/type-fest": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", - "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=16" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@wdio/cli/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -4757,42 +4839,28 @@ "node": "*" } }, - "node_modules/@wdio/cli/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "optional": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@wdio/cli/node_modules/webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -4801,7 +4869,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" + "webdriver": "8.38.2" }, "engines": { "node": "^16.13 || >=18" @@ -4815,122 +4883,6 @@ } } }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wdio/cli/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", @@ -4970,40 +4922,28 @@ "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/cli/node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/concise-reporter": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.29.1.tgz", - "integrity": "sha512-dUhClWeq1naL1Qa1nSMDeH8aCVViOKiEzhBhQjgrMOz1Mh3l6O/woqbK2iKDVZDRhfGghtGcV0vpoEUvt8ZKOA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.38.2.tgz", + "integrity": "sha512-wE36By4Z9iCtRzihpYrmCehsmNc8t3gHviBsUxV4tmYh/SQr+WX/dysWnojer6KWIJ2rT0rOzyQPmrwhdFKAFg==", "dev": true, "dependencies": { - "@wdio/reporter": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.0.1", "pretty-ms": "^7.0.1" }, @@ -5024,14 +4964,14 @@ } }, "node_modules/@wdio/config": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.29.1.tgz", - "integrity": "sha512-zNUac4lM429HDKAitO+fdlwUH1ACQU8lww+DNVgUyuEb86xgVdTqHeiJr/3kOMJAq9IATeE7mDtYyyn6HPm1JA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.38.2.tgz", + "integrity": "sha512-xlnapTr1vOA0h5HsHTIqj47729FbG3WjxmgHweDEQvcT4C1g9l+WKf+N3FM7DNNoIsAqxKi6rOHG02rJADQJtw==", "dev": true, "dependencies": { - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.0.0", "glob": "^10.2.2", @@ -5041,78 +4981,309 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/config/node_modules/brace-expansion": { + "node_modules/@wdio/globals": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.38.2.tgz", + "integrity": "sha512-iIrUF1EODfHLh3V/CSNCqbNNxUTe3ND+c86zDjzJcPFjawLX1plvAApsU/eDmtsFShcOS2KHbfSjiydFoqQG1Q==", + "dev": true, + "engines": { + "node": "^16.13 || >=18" + }, + "optionalDependencies": { + "expect-webdriverio": "^4.11.2", + "webdriverio": "8.38.2" + } + }, + "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wdio/globals/node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/globals/node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + }, + "node_modules/@wdio/globals/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/globals/node_modules/agent-base/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/globals/node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + }, + "node_modules/@wdio/globals/node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "optional": true, + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/globals/node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "optional": true, + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/globals/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "optional": true + }, + "node_modules/@wdio/globals/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "optional": true, "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/config/node_modules/decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "node_modules/@wdio/globals/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@wdio/globals/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "optional": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8.0.0" } }, - "node_modules/@wdio/config/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "node_modules/@wdio/globals/node_modules/chrome-launcher": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" }, "bin": { - "glob": "dist/esm/bin.mjs" + "print-chrome-path": "bin/print-chrome-path.js" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12.13.0" + } + }, + "node_modules/@wdio/globals/node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "optional": true, + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/globals/node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "optional": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/globals/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "optional": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@wdio/globals/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/@wdio/config/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/@wdio/globals/node_modules/devtools": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "brace-expansion": "^2.0.1" + "@types/node": "^20.1.0", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.37", + "uuid": "^9.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/globals": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.29.1.tgz", - "integrity": "sha512-F+fPnX75f44/crZDfQ2FYSino/IMIdbnQGLIkaH0VnoljVJIHuxnX4y5Zqr4yRgurL9DsZaH22cLHrPXaHUhPg==", + "node_modules/@wdio/globals/node_modules/devtools-protocol": { + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", "dev": true, - "engines": { - "node": "^16.13 || >=18" - }, - "optionalDependencies": { - "expect-webdriverio": "^4.9.3", - "webdriverio": "8.29.1" - } + "optional": true }, - "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { + "node_modules/@wdio/globals/node_modules/devtools/node_modules/@puppeteer/browsers": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", @@ -5145,197 +5316,184 @@ } } }, - "node_modules/@wdio/globals/node_modules/@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@wdio/globals/node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, + "peer": true, "dependencies": { - "archiver-utils": "^4.0.1", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "debug": "4" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 6.0.0" } }, - "node_modules/@wdio/globals/node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", "dev": true, "optional": true, + "peer": true, "dependencies": { - "glob": "^8.0.0", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "mitt": "3.0.0" }, - "engines": { - "node": ">= 12.0.0" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/@wdio/globals/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "optional": true - }, - "node_modules/@wdio/globals/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, "optional": true, + "peer": true, "dependencies": { - "balanced-match": "^1.0.0" + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/globals/node_modules/chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" + "ms": "2.1.2" }, "engines": { - "node": ">=12.13.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/globals/node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true, "optional": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 12.0.0" - } + "peer": true }, - "node_modules/@wdio/globals/node_modules/crc32-stream": { + "node_modules/@wdio/globals/node_modules/devtools/node_modules/http-proxy-agent": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "optional": true, + "peer": true, "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 6" } }, - "node_modules/@wdio/globals/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/@wdio/globals/node_modules/devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/globals/node_modules/devtools/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^9.0.0", - "which": "^4.0.0" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/globals/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "optional": true, - "peer": true + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "optional": true, "peer": true, "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/@wdio/globals/node_modules/edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" }, "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/shirshak55" + "node": ">=6" } }, "node_modules/@wdio/globals/node_modules/escape-string-regexp": { @@ -5352,64 +5510,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/globals/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/@wdio/globals/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "optional": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/@wdio/globals/node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "dependencies": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">=10" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/globals/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/globals/node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } + "optional": true }, - "node_modules/@wdio/globals/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@wdio/globals/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "optional": true, - "peer": true, "engines": { - "node": ">=16" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@wdio/globals/node_modules/lighthouse-logger": { @@ -5424,17 +5574,6 @@ "marky": "^1.2.2" } }, - "node_modules/@wdio/globals/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/@wdio/globals/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -5446,9 +5585,9 @@ } }, "node_modules/@wdio/globals/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "optional": true, "dependencies": { @@ -5510,64 +5649,47 @@ "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "dependencies": { - "debug": "^4.3.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "optional": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "node": ">=6.0" }, - "engines": { - "node": ">= 14" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "optional": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } + "optional": true }, "node_modules/@wdio/globals/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -5578,19 +5700,53 @@ } } }, + "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + }, + "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + }, "node_modules/@wdio/globals/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "optional": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@wdio/globals/node_modules/serialize-error": { @@ -5609,16 +5765,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/globals/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/@wdio/globals/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "optional": true, "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/globals/node_modules/type-fest": { @@ -5635,9 +5789,9 @@ } }, "node_modules/@wdio/globals/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -5659,43 +5813,29 @@ "node": "*" } }, - "node_modules/@wdio/globals/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "optional": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@wdio/globals/node_modules/webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "optional": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -5704,7 +5844,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" + "webdriver": "8.38.2" }, "engines": { "node": "^16.13 || >=18" @@ -5718,111 +5858,6 @@ } } }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "optional": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true, - "optional": true - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "optional": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/@wdio/globals/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", @@ -5865,31 +5900,31 @@ } }, "node_modules/@wdio/globals/node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "optional": true, "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/local-runner": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.29.1.tgz", - "integrity": "sha512-Z3QAgxe1uQ97C7NS1CdMhgmHaLu/sbb47HTbw1yuuLk+SwsBIQGhNpTSA18QVRSUXq70G3bFvjACwqyap1IEQg==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.38.2.tgz", + "integrity": "sha512-syW+R5VUHJ3GBkQGFcNYe6MYwWRgklc9W7A83xQDTvKWFNHCetLvc8AtKZ54vs8MItBejjU+Oh94ZNbNX1pBcg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", + "@wdio/logger": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/runner": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/runner": "8.38.2", + "@wdio/types": "8.38.2", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" @@ -5899,30 +5934,18 @@ } }, "node_modules/@wdio/logger": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", - "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.38.0.tgz", + "integrity": "sha512-kcHL86RmNbcQP+Gq/vQUGlArfU6IIcbbnNp32rRIraitomZow+iEoc519rdQmSVusDozMS5DZthkgDdxK+vz6Q==", "dev": true, "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/@wdio/logger/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" + "strip-ansi": "^7.1.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": "^16.13 || >=18" } }, "node_modules/@wdio/logger/node_modules/chalk": { @@ -5937,32 +5960,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/logger/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@wdio/mocha-framework": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.29.1.tgz", - "integrity": "sha512-R9dKMNqWgtUvZo33ORjUQV8Z/WLX5h/pg9u/xIvZSGXuNSw1h+5DWF6UiNFscxBFblL9UvBi6V9ila2LHgE4ew==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.38.2.tgz", + "integrity": "sha512-qJmRL5E6/ypjCUACH4hvCAAmTdU4YUrUlp9o/IKvTIAHMnZPE0/HgUFixCeu8Mop+rdzTPVBrbqxpRDdSnraYA==", "dev": true, "dependencies": { "@types/mocha": "^10.0.0", "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "mocha": "^10.0.0" }, "engines": { @@ -5970,9 +5978,9 @@ } }, "node_modules/@wdio/protocols": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz", - "integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.38.0.tgz", + "integrity": "sha512-7BPi7aXwUtnXZPeWJRmnCNFjyDvGrXlBmN9D4Pi58nILkyjVRQKEY9/qv/pcdyB0cvmIvw++Kl/1Lg+RxG++UA==", "dev": true }, "node_modules/@wdio/repl": { @@ -5988,14 +5996,14 @@ } }, "node_modules/@wdio/reporter": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.29.1.tgz", - "integrity": "sha512-LZeYHC+HHJRYiFH9odaotDazZh0zNhu4mTuL/T/e3c/Q3oPSQjLvfQYhB3Ece1QA9PKjP1VPmr+g9CvC0lMixA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.38.2.tgz", + "integrity": "sha512-R78UdAtAnkaV22NYlCCcbPPhmYweiDURiw64LYhlVIQrKNuXUQcafR2kRlWKy31rZc9thSLs5LzrZDReENUlFQ==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", "diff": "^5.0.0", "object-inspect": "^1.12.0" }, @@ -6004,42 +6012,38 @@ } }, "node_modules/@wdio/runner": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.29.1.tgz", - "integrity": "sha512-MvYFf4RgRmzxjAzy6nxvaDG1ycBRvoz772fT06csjxuaVYm57s8mlB8X+U1UQMx/IzujAb53fSeAmNcyU3FNEA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.38.2.tgz", + "integrity": "sha512-5lPnKSX2BBLI2AbYW+hoGPiEUAJXj8F8I6NC2LaBVzf1CLN+w2HWZ7lUiqS14XT0b5/hlSUX6+JYwUXlDbpuuw==", "dev": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/globals": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "deepmerge-ts": "^5.0.0", - "expect-webdriverio": "^4.9.3", - "gaze": "^1.1.2", - "webdriver": "8.29.1", - "webdriverio": "8.29.1" + "@types/node": "^20.11.28", + "@wdio/config": "8.38.2", + "@wdio/globals": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "deepmerge-ts": "^5.1.0", + "expect-webdriverio": "^4.12.0", + "gaze": "^1.1.3", + "webdriver": "8.38.2", + "webdriverio": "8.38.2" }, "engines": { "node": "^16.13 || >=18" } }, "node_modules/@wdio/runner/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" }, @@ -6047,7 +6051,7 @@ "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -6058,47 +6062,98 @@ } } }, - "node_modules/@wdio/runner/node_modules/@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "node_modules/@wdio/runner/node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "optional": true, - "peer": true + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/runner/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/runner/node_modules/agent-base/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@wdio/runner/node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/runner/node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "dependencies": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/runner/node_modules/async": { @@ -6116,10 +6171,43 @@ "balanced-match": "^1.0.0" } }, + "node_modules/@wdio/runner/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@wdio/runner/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@wdio/runner/node_modules/chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -6137,64 +6225,74 @@ } }, "node_modules/@wdio/runner/node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/runner/node_modules/crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/runner/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@wdio/runner/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "node_modules/@wdio/runner/node_modules/devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, @@ -6203,94 +6301,111 @@ } }, "node_modules/@wdio/runner/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "isexe": "^3.1.1" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" }, "bin": { - "node-which": "bin/which.js" + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/runner/node_modules/edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" + "debug": "4" }, "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/shirshak55" + "node": ">= 6.0.0" } }, - "node_modules/@wdio/runner/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", "dev": true, "optional": true, "peer": true, - "engines": { - "node": ">=10" + "dependencies": { + "mitt": "3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/@wdio/runner/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/runner/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">=10" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/runner/node_modules/http-proxy-agent": { + "node_modules/@wdio/runner/node_modules/devtools/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/runner/node_modules/devtools/node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", @@ -6306,38 +6421,176 @@ "node": ">= 6" } }, - "node_modules/@wdio/runner/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "optional": true, "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=16" + "node": ">= 6" } }, - "node_modules/@wdio/runner/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/runner/node_modules/devtools/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, "optional": true, "peer": true, "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/runner/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@wdio/runner/node_modules/devtools/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "ms": "2.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@wdio/runner/node_modules/devtools/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/@wdio/runner/node_modules/devtools/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@wdio/runner/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/runner/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/runner/node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@wdio/runner/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/runner/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" } }, "node_modules/@wdio/runner/node_modules/lru-cache": { @@ -6350,9 +6603,9 @@ } }, "node_modules/@wdio/runner/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -6411,61 +6664,44 @@ "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "node": ">=6.0" }, - "engines": { - "node": ">= 14" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } + "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@wdio/runner/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -6476,18 +6712,49 @@ } } }, + "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@wdio/runner/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@wdio/runner/node_modules/serialize-error": { @@ -6505,15 +6772,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/@wdio/runner/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/runner/node_modules/type-fest": { @@ -6529,9 +6794,9 @@ } }, "node_modules/@wdio/runner/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -6553,42 +6818,28 @@ "node": "*" } }, - "node_modules/@wdio/runner/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "optional": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@wdio/runner/node_modules/webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -6597,7 +6848,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" + "webdriver": "8.38.2" }, "engines": { "node": "^16.13 || >=18" @@ -6611,104 +6862,6 @@ } } }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/@wdio/runner/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", @@ -6749,27 +6902,27 @@ } }, "node_modules/@wdio/runner/node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/@wdio/spec-reporter": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.29.1.tgz", - "integrity": "sha512-tuDHihrTjCxFCbSjT0jMvAarLA1MtatnCnhv0vguu3ZWXELR1uESX2KzBmpJ+chGZz3oCcKszT8HOr6Pg2a1QA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.38.2.tgz", + "integrity": "sha512-Dntk+lmrp+0I3HRRWkkXED+smshvgsW5cdLKwJhEJ1liI48MdBpdNGf9IHTVckE6nfxcWDyFI4icD9qYv/5bFA==", "dev": true, "dependencies": { - "@wdio/reporter": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.1.2", "easy-table": "^1.2.0", "pretty-ms": "^7.0.0" @@ -6791,9 +6944,9 @@ } }, "node_modules/@wdio/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.29.1.tgz", - "integrity": "sha512-rZYzu+sK8zY1PjCEWxNu4ELJPYKDZRn7HFcYNgR122ylHygfldwkb5TioI6Pn311hQH/S+663KEeoq//Jb0f8A==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.38.2.tgz", + "integrity": "sha512-+wj1c1OSLdnN4WO5b44Ih4263dTl/eSwMGSI4/pCgIyXIuYQH38JQ+6WRa+c8vJEskUzboq2cSgEQumVZ39ozQ==", "dev": true, "dependencies": { "@types/node": "^20.1.0" @@ -6803,18 +6956,18 @@ } }, "node_modules/@wdio/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-Dm91DKL/ZKeZ2QogWT8Twv0p+slEgKyB/5x9/kcCG0Q2nNa+tZedTjOhryzrsPiWc+jTSBmjGE4katRXpJRFJg==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.38.2.tgz", + "integrity": "sha512-y5AnBwsGcu/XuCBGCgKmlvKdwEIFyzLA+Cr+denySxY3jbWDONtPUcGaVdFALwsIa5jcIjcATqGmZcCPGnkd7g==", "dev": true, "dependencies": { "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.3.5", - "geckodriver": "^4.2.0", + "edgedriver": "^5.5.0", + "geckodriver": "^4.3.1", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", "locate-app": "^2.1.0", @@ -6826,168 +6979,156 @@ "node": "^16.13 || >=18" } }, - "node_modules/@wdio/utils/node_modules/decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, "node_modules/@xmldom/xmldom": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.8.tgz", - "integrity": "sha512-PrJx38EfpitFhwmILRl37jAdBlsww6AZ6rRVK4QS7T7RHLhX7mSs647sTmgr9GIxe3qjXdesmomEgbgaokrVFg==", + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -7005,12 +7146,35 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.45", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.45.tgz", + "integrity": "sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow==", + "dev": true, + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7045,9 +7209,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -7201,9 +7365,9 @@ } }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", @@ -7226,16 +7390,16 @@ } }, "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "dependencies": { "archiver-utils": "^2.1.0", - "async": "^3.2.3", + "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", + "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" }, @@ -7264,16 +7428,37 @@ "node": ">= 6" } }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/archiver/node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "node_modules/archiver/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -7284,6 +7469,22 @@ "node": ">= 6" } }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -7299,12 +7500,12 @@ } }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/arr-diff": { @@ -7371,6 +7572,22 @@ "node": ">=0.10.0" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -7401,15 +7618,16 @@ "dev": true }, "node_modules/array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -7503,15 +7721,35 @@ "node": ">=0.10.0" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -7521,6 +7759,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -7531,15 +7809,16 @@ } }, "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "dev": true, "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, "node_modules/assert-plus": { @@ -7581,9 +7860,9 @@ } }, "node_modules/ast-types/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, "node_modules/astral-regex": { @@ -7617,10 +7896,16 @@ } }, "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] }, "node_modules/async-exit-hook": { "version": "2.0.1", @@ -7662,10 +7947,13 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -7683,15 +7971,15 @@ } }, "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, "node_modules/babel-code-frame": { @@ -7793,6 +8081,12 @@ "source-map": "^0.5.7" } }, + "node_modules/babel-core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/babel-core/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -7853,9 +8147,9 @@ } }, "node_modules/babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", "dev": true, "dependencies": { "find-cache-dir": "^3.3.1", @@ -7897,39 +8191,39 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-register": { @@ -7955,18 +8249,6 @@ "dev": true, "hasInstallScript": true }, - "node_modules/babel-register/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -8111,6 +8393,52 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", + "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, "node_modules/base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", @@ -8189,9 +8517,9 @@ "dev": true }, "node_modules/basic-ftp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", - "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, "engines": { "node": ">=10.0.0" @@ -8222,9 +8550,9 @@ } }, "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", "dev": true, "engines": { "node": ">=0.6" @@ -8253,12 +8581,15 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/binaryextensions": { @@ -8295,9 +8626,9 @@ } }, "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -8309,9 +8640,9 @@ } }, "node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body": { "version": "5.1.0", @@ -8403,12 +8734,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -8421,9 +8752,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "funding": [ { "type": "opencollective", @@ -8432,13 +8763,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -8457,9 +8792,9 @@ } }, "node_modules/browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.5.tgz", + "integrity": "sha512-jKne7yosrMcptj3hqxp36TP9k0ZW2sCqhyurX24rUL4G3eT7OLgv+CSQN8iq5dtkv5IK+g+v8fWvsiC/S9KxMg==", "dev": true, "dependencies": { "agent-base": "^6.0.2", @@ -8680,55 +9015,57 @@ } }, "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, "engines": { - "node": ">=10.6.0" + "node": ">=14.16" } }, "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" } }, "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8759,9 +9096,9 @@ "dev": true }, "node_modules/caniuse-lite": { - "version": "1.0.30001429", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", - "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==", + "version": "1.0.30001633", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001633.tgz", + "integrity": "sha512-6sT0yf/z5jqf8tISAgpJDrmwOpLsrpnyCdD/lOZKvKkkJK4Dn0X5i7KF7THEZhOq+30bmhwBlNEaqPUiHiKtZg==", "funding": [ { "type": "opencollective", @@ -8770,6 +9107,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -8790,18 +9131,18 @@ } }, "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" }, "engines": { "node": ">=4" @@ -8869,25 +9210,22 @@ "dev": true }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -8900,6 +9238,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -8911,9 +9252,9 @@ "dev": true }, "node_modules/chrome-launcher": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.1.tgz", - "integrity": "sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "dev": true, "dependencies": { "@types/node": "*", @@ -8941,21 +9282,19 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "engines": { "node": ">=6.0" } }, "node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "mitt": "3.0.0" }, @@ -9014,72 +9353,17 @@ "node": ">=0.10.0" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/cli-cursor": { @@ -9107,12 +9391,12 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { @@ -9129,6 +9413,68 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -9159,6 +9505,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clone-response/node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", @@ -9256,9 +9611,9 @@ } }, "node_modules/comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "dev": true, "funding": { "type": "github", @@ -9287,15 +9642,18 @@ "dev": true }, "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "dependencies": { "buffer-crc32": "^0.2.13", @@ -9308,9 +9666,9 @@ } }, "node_modules/compress-commons/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -9476,9 +9834,9 @@ "dev": true }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "0.6.0", @@ -9513,9 +9871,9 @@ } }, "node_modules/core-js": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", - "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==", + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -9523,11 +9881,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", - "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", "dependencies": { - "browserslist": "^4.21.4" + "browserslist": "^4.23.0" }, "funding": { "type": "opencollective", @@ -9535,9 +9893,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==", + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -9594,9 +9952,9 @@ } }, "node_modules/crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "dependencies": { "crc-32": "^1.2.0", @@ -9607,9 +9965,9 @@ } }, "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -9620,11 +9978,6 @@ "node": ">= 6" } }, - "node_modules/criteo-direct-rsa-validate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", - "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" - }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -9634,6 +9987,26 @@ "node-fetch": "2.6.7" } }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9648,6 +10021,27 @@ "node": ">= 8" } }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", @@ -9726,13 +10120,16 @@ "dev": true }, "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dev": true, "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/dashdash": { @@ -9748,12 +10145,63 @@ } }, "node_modules/data-uri-to-buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", - "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, "engines": { - "node": ">= 14" + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/date-format": { @@ -9781,10 +10229,16 @@ "dev": true, "optional": true }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { "ms": "2.1.2" }, @@ -9818,12 +10272,12 @@ } }, "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9879,38 +10333,44 @@ } }, "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "dependencies": { "type-detect": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=6" } }, "node_modules/deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", - "object-is": "^1.1.4", + "object-is": "^1.1.5", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9923,9 +10383,9 @@ "dev": true }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { "node": ">=0.10.0" @@ -9992,24 +10452,28 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -10166,21 +10630,21 @@ } }, "node_modules/devtools": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.25.4.tgz", - "integrity": "sha512-R6/S/dCqxoX4Y6PxIGM9JFAuSRZzUeV5r+CoE/frhmno6mTe7dEEgwkJlfit3LkKRoul8n4DsL2A3QtWOvq5IA==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.35.0.tgz", + "integrity": "sha512-7HMZMcJSCK/PaBCWVs4n4ZhtBNdUQj10iPwXvj/JDkqPreEXN/XW9GJAoMuLPFmCEKfxe+LrIbgs8ocGJ6rp/A==", "dev": true, "dependencies": { "@types/node": "^18.0.0", "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.25.4", - "@wdio/logger": "7.19.0", - "@wdio/protocols": "7.22.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@wdio/config": "7.33.0", + "@wdio/logger": "7.26.0", + "@wdio/protocols": "7.27.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "chrome-launcher": "^0.15.0", "edge-paths": "^2.1.0", - "puppeteer-core": "^13.1.3", + "puppeteer-core": "13.1.3", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", "uuid": "^9.0.0" @@ -10190,26 +10654,60 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1061995", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1061995.tgz", - "integrity": "sha512-pKZZWTjWa/IF4ENCg6GN8bu/AxSZgdhjSa26uc23wz38Blt2Tnm9icOPcSG3Cht55rMq35in1w3rWVPcZ60ArA==", + "version": "0.0.1260888", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz", + "integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==", "dev": true }, + "node_modules/devtools/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/devtools/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/devtools/node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", + "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/devtools/node_modules/@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, "node_modules/devtools/node_modules/@wdio/config": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.25.4.tgz", - "integrity": "sha512-vb0emDtD9FbFh/yqW6oNdo2iuhQp8XKj6GX9fyy9v4wZgg3B0HPMVJxhIfcoHz7LMBWlHSo9YdvhFI5EQHRLBA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", + "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", "dev": true, "dependencies": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@types/glob": "^8.1.0", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "deepmerge": "^4.0.0", "glob": "^8.0.3" }, @@ -10218,9 +10716,9 @@ } }, "node_modules/devtools/node_modules/@wdio/logger": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.19.0.tgz", - "integrity": "sha512-xR7SN/kGei1QJD1aagzxs3KMuzNxdT/7LYYx+lt6BII49+fqL/SO+5X0FDCZD0Ds93AuQvvz9eGyzrBI2FFXmQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", + "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", "dev": true, "dependencies": { "chalk": "^4.0.0", @@ -10233,18 +10731,18 @@ } }, "node_modules/devtools/node_modules/@wdio/protocols": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.22.0.tgz", - "integrity": "sha512-8EXRR+Ymdwousm/VGtW3H1hwxZ/1g1H99A1lF0U4GuJ5cFWHCd0IVE5H31Z52i8ZruouW8jueMkGZPSo2IIUSQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", + "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", "dev": true, "engines": { "node": ">=12.0.0" } }, "node_modules/devtools/node_modules/@wdio/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.25.4.tgz", - "integrity": "sha512-muvNmq48QZCvocctnbe0URq2FjJjUPIG4iLoeMmyF0AQgdbjaUkMkw3BHYNHVTbSOU9WMsr2z8alhj/I2H6NRQ==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", + "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", "dev": true, "dependencies": { "@types/node": "^18.0.0", @@ -10263,13 +10761,13 @@ } }, "node_modules/devtools/node_modules/@wdio/utils": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.25.4.tgz", - "integrity": "sha512-8iwQDk+foUqSzKZKfhLxjlCKOkfRJPNHaezQoevNgnrTq/t0ek+ldZCATezb9B8jprAuP4mgS9xi22akc6RkzA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", + "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", "dev": true, "dependencies": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", "p-iteration": "^1.1.8" }, "engines": { @@ -10300,6 +10798,33 @@ "balanced-match": "^1.0.0" } }, + "node_modules/devtools/node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/devtools/node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/devtools/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -10334,10 +10859,59 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/devtools/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/devtools/node_modules/devtools-protocol": { + "version": "0.0.948846", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", + "integrity": "sha512-5fGyt9xmMqUl2VI7+rnUkKCiAQIpLns8sfQtTENy5L70ktbNw0Z3TFJ1JoFNYdx/jffz4YXU45VF75wKZD7sZQ==", + "dev": true + }, + "node_modules/devtools/node_modules/edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "dev": true, + "dependencies": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + } + }, + "node_modules/devtools/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/devtools/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -10353,6 +10927,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/devtools/node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/devtools/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10362,10 +10961,51 @@ "node": ">=8" } }, + "node_modules/devtools/node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/devtools/node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/devtools/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/devtools/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/devtools/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10374,6 +11014,108 @@ "node": ">=10" } }, + "node_modules/devtools/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/devtools/node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/devtools/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools/node_modules/puppeteer-core": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.1.3.tgz", + "integrity": "sha512-96pzvVBzq5lUGt3L/QrIH3mxn3NfZylHeusNhq06xBAHPI0Upc0SC/9u7tXjL0oRnmcExeVRJivr1lj7Ah/yDQ==", + "dev": true, + "dependencies": { + "debug": "4.3.2", + "devtools-protocol": "0.0.948846", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.7", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/devtools/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/devtools/node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/devtools/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/devtools/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10386,10 +11128,38 @@ "node": ">=8" } }, + "node_modules/devtools/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/devtools/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", - "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -10399,19 +11169,50 @@ { "type": "paypal", "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" } ], "engines": { "node": "*" } }, - "node_modules/devtools/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "node_modules/devtools/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, "bin": { - "uuid": "dist/bin/uuid" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/devtools/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/di": { @@ -10421,9 +11222,9 @@ "dev": true }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -10468,9 +11269,9 @@ } }, "node_modules/documentation": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", - "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz", + "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==", "dev": true, "dependencies": { "@babel/core": "^7.18.10", @@ -10538,9 +11339,9 @@ } }, "node_modules/documentation/node_modules/chalk": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz", - "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -10549,10 +11350,27 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/documentation/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/documentation/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -10568,6 +11386,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/documentation/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/documentation/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -10580,10 +11410,49 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/documentation/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/documentation/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/documentation/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/documentation/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10592,10 +11461,147 @@ "node": ">=10" } }, + "node_modules/documentation/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/documentation/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/documentation/node_modules/read-pkg": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/read-pkg-up": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/documentation/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/documentation/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/documentation/node_modules/yargs": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", - "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -10604,7 +11610,7 @@ "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" @@ -10684,15 +11690,15 @@ } }, "node_modules/dotenv": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", - "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/dset": { @@ -10743,21 +11749,21 @@ "dev": true }, "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "dev": true, "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" + "stream-shift": "^1.0.2" } }, "node_modules/duplexify/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -10818,71 +11824,13 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/edge-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", - "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", - "dev": true, - "dependencies": { - "@types/which": "^1.3.2", - "which": "^2.0.2" - } - }, - "node_modules/edgedriver": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.9.tgz", - "integrity": "sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@wdio/logger": "^8.16.17", - "decamelize": "^6.0.0", - "edge-paths": "^3.0.5", - "node-fetch": "^3.3.2", - "unzipper": "^0.10.14", - "which": "^4.0.0" - }, - "bin": { - "edgedriver": "bin/edgedriver.js" - } - }, - "node_modules/edgedriver/node_modules/@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, - "node_modules/edgedriver/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/edgedriver/node_modules/decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/edgedriver/node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/edgedriver/node_modules/edge-paths": { + "node_modules/edge-paths": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", @@ -10898,7 +11846,13 @@ "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/edgedriver/node_modules/edge-paths/node_modules/which": { + "node_modules/edge-paths/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/edge-paths/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", @@ -10913,64 +11867,22 @@ "node": ">= 8" } }, - "node_modules/edgedriver/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/edgedriver/node_modules/unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "node_modules/edgedriver/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/edgedriver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.0.tgz", + "integrity": "sha512-IeJXEczG+DNYBIa9gFgVYTqrawlxmc9SUqUsWU2E98jOsO/amA7wzabKOS8Bwgr/3xWoyXCJ6yGFrbFKrilyyQ==", "dev": true, + "hasInstallScript": true, "dependencies": { - "isexe": "^3.1.1" + "@wdio/logger": "^8.28.0", + "@zip.js/zip.js": "^2.7.44", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "node-fetch": "^3.3.2", + "which": "^4.0.0" }, "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/edgedriver/node_modules/which/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" + "edgedriver": "bin/edgedriver.js" } }, "node_modules/ee-first": { @@ -10994,9 +11906,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + "version": "1.4.802", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", + "integrity": "sha512-TnTMUATbgNdPXVSHsxvNVSG0uEd6cSZsANjm8c9HbvflZVVn1yTRcmVXYT1Ma95/ssB/Dcd30AHweH2TE+dNpA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -11031,9 +11943,9 @@ } }, "node_modules/engine.io": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", - "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -11044,17 +11956,17 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.2.1", "ws": "~8.11.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -11070,9 +11982,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -11083,17 +11995,30 @@ } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" } }, + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", @@ -11143,35 +12068,57 @@ } }, "node_modules/es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -11180,38 +12127,84 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", + "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", "dev": true }, - "node_modules/es-shim-unscopables": { + "node_modules/es-object-atoms": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -11232,14 +12225,15 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -11266,12 +12260,6 @@ "es6-symbol": "^3.1.1" } }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", - "dev": true - }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -11287,13 +12275,16 @@ } }, "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "dev": true, "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/es6-weak-map": { @@ -11309,9 +12300,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "engines": { "node": ">=6" } @@ -11495,13 +12486,14 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "dependencies": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -11514,9 +12506,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -11559,24 +12551,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -11586,12 +12582,12 @@ } }, "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "node_modules/eslint-plugin-import/node_modules/doctrine": { @@ -11606,12 +12602,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "node_modules/eslint-plugin-jsdoc": { "version": "38.1.6", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-38.1.6.tgz", @@ -11647,13 +12637,10 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -11682,9 +12669,9 @@ } }, "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -11832,9 +12819,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -11856,13 +12843,10 @@ } }, "node_modules/eslint/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -11870,6 +12854,18 @@ "node": ">=10" } }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11906,6 +12902,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -11943,9 +12954,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -12047,6 +13058,15 @@ "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", "dev": true }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -12096,6 +13116,12 @@ "node": ">=4.8" } }, + "node_modules/execa/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/execa/node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -12198,72 +13224,17 @@ "node": ">=0.10.0" } }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/expand-brackets/node_modules/is-extendable": { @@ -12310,12 +13281,12 @@ } }, "node_modules/expect-webdriverio": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.9.3.tgz", - "integrity": "sha512-ASHsFc/QaK5ipF4ct3e8hd3elm8wNXk/Qa3EemtYDmfUQ4uzwqDf75m/QFQpwVNCjEpkNP7Be/6X9kz7bN0P9Q==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.15.0.tgz", + "integrity": "sha512-CIBSLEhDmjZ7kKZq6ItBM7V1jLH/w4JCuKGu3WmR4FscOPvOnp9JN4Zi26SZGeQ73E0dy+YPUL6SIvTNoP/XdQ==", "dev": true, "dependencies": { - "@vitest/snapshot": "^1.2.1", + "@vitest/snapshot": "^1.2.2", "expect": "^29.7.0", "jest-matcher-utils": "^29.7.0", "lodash.isequal": "^4.5.0" @@ -12324,26 +13295,23 @@ "node": ">=16 || >=18 || >=20" }, "optionalDependencies": { - "@wdio/globals": "^8.27.0", - "@wdio/logger": "^8.24.12", - "webdriverio": "^8.27.0" + "@wdio/globals": "^8.29.3", + "@wdio/logger": "^8.28.0", + "webdriverio": "^8.29.3" } }, "node_modules/expect-webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, "optional": true, - "peer": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" }, @@ -12351,7 +13319,7 @@ "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -12362,49 +13330,105 @@ } } }, - "node_modules/expect-webdriverio/node_modules/@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "node_modules/expect-webdriverio/node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "optional": true, - "peer": true + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + }, + "node_modules/expect-webdriverio/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/expect-webdriverio/node_modules/agent-base/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true }, "node_modules/expect-webdriverio/node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "optional": true, "dependencies": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/expect-webdriverio/node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "optional": true, "dependencies": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/expect-webdriverio/node_modules/async": { @@ -12424,10 +13448,45 @@ "balanced-match": "^1.0.0" } }, + "node_modules/expect-webdriverio/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/expect-webdriverio/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/expect-webdriverio/node_modules/chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -12445,66 +13504,77 @@ } }, "node_modules/expect-webdriverio/node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "optional": true, "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/expect-webdriverio/node_modules/crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "optional": true, "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/expect-webdriverio/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "optional": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/expect-webdriverio/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "node_modules/expect-webdriverio/node_modules/devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, @@ -12513,6 +13583,104 @@ } }, "node_modules/expect-webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true, + "optional": true + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "node-fetch": "^2.6.11" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/devtools-protocol": { "version": "0.0.1120988", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", @@ -12520,39 +13688,118 @@ "optional": true, "peer": true }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, "optional": true, "peer": true, "dependencies": { - "isexe": "^3.1.1" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, - "bin": { - "node-which": "bin/which.js" + "engines": { + "node": ">= 6" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">= 6" } }, - "node_modules/expect-webdriverio/node_modules/edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/shirshak55" + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/expect-webdriverio/node_modules/escape-string-regexp": { @@ -12569,64 +13816,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect-webdriverio/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/expect-webdriverio/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "optional": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 14" } }, - "node_modules/expect-webdriverio/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/expect-webdriverio/node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "dependencies": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" }, "engines": { - "node": ">=10" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/expect-webdriverio/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/expect-webdriverio/node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } + "optional": true }, - "node_modules/expect-webdriverio/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/expect-webdriverio/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "optional": true, - "peer": true, "engines": { - "node": ">=16" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/expect-webdriverio/node_modules/lighthouse-logger": { @@ -12641,17 +13880,6 @@ "marky": "^1.2.2" } }, - "node_modules/expect-webdriverio/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/expect-webdriverio/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -12663,9 +13891,9 @@ } }, "node_modules/expect-webdriverio/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "optional": true, "dependencies": { @@ -12727,64 +13955,47 @@ "node": ">= 14" } }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "dependencies": { - "debug": "^4.3.4" + "ms": "2.1.2" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "optional": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "node": ">=6.0" }, - "engines": { - "node": ">= 14" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "optional": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } + "optional": true }, "node_modules/expect-webdriverio/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.3.0" }, "peerDependencies": { "typescript": ">= 4.7.4" @@ -12795,19 +14006,53 @@ } } }, + "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + }, + "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + }, "node_modules/expect-webdriverio/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "optional": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/expect-webdriverio/node_modules/serialize-error": { @@ -12826,16 +14071,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect-webdriverio/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "node_modules/expect-webdriverio/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "optional": true, "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "node_modules/expect-webdriverio/node_modules/type-fest": { @@ -12852,9 +14095,9 @@ } }, "node_modules/expect-webdriverio/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "funding": [ { @@ -12876,43 +14119,29 @@ "node": "*" } }, - "node_modules/expect-webdriverio/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "optional": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/expect-webdriverio/node_modules/webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "optional": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -12921,7 +14150,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" + "webdriver": "8.38.2" }, "engines": { "node": "^16.13 || >=18" @@ -12935,111 +14164,6 @@ } } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "optional": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true, - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "optional": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/expect-webdriverio/node_modules/ws": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", @@ -13082,18 +14206,18 @@ } }, "node_modules/expect-webdriverio/node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "optional": true, "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/express": { @@ -13159,12 +14283,6 @@ "type": "^2.7.2" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -13206,6 +14324,18 @@ "node": ">=4" } }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -13293,6 +14423,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/extract-zip/node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -13398,24 +14538,37 @@ } }, "node_modules/fetch-blob/node_modules/web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true, "engines": { "node": ">= 8" } }, "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", "dev": true, "dependencies": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" }, "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13471,9 +14624,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -13780,12 +14933,13 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -13793,9 +14947,9 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/flush-write-stream": { @@ -13865,9 +15019,9 @@ "dev": true }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.0.tgz", + "integrity": "sha512-CrWQNaEl1/6WeZoarcM9LHupTo3RpZO2Pdk1vktwzPiQTsJnAKJmm3TACKeG5UZbWDfaH2AbvYxzP96y0MT7fA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -13996,17 +15150,32 @@ "dev": true }, "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", + "integrity": "sha512-5rU898vl/Z948L+kkJedbmo/iltzmiF5bn/eEk0j/SgrPpI+Ydau9xlJPicV7Av2CHYBGz5LAlwTnBU80j1zPQ==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" + "jsonfile": "~1.0.1", + "mkdirp": "0.3.x", + "ncp": "~0.4.2", + "rimraf": "~2.2.0" + } + }, + "node_modules/fs-extra/node_modules/mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true + }, + "node_modules/fs-extra/node_modules/rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "bin": { + "rimraf": "bin.js" } }, "node_modules/fs-mkdirp-stream": { @@ -14054,24 +15223,6 @@ "node": "*" } }, - "node_modules/fs.extra/node_modules/fs-extra": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", - "integrity": "sha512-5rU898vl/Z948L+kkJedbmo/iltzmiF5bn/eEk0j/SgrPpI+Ydau9xlJPicV7Av2CHYBGz5LAlwTnBU80j1zPQ==", - "dev": true, - "dependencies": { - "jsonfile": "~1.0.1", - "mkdirp": "0.3.x", - "ncp": "~0.4.2", - "rimraf": "~2.2.0" - } - }, - "node_modules/fs.extra/node_modules/jsonfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", - "integrity": "sha512-KbsDJNRfRPF5v49tMNf9sqyyGqGLBcz1v5kZT01kG5ns5mQSltwxCKVmUzVKtEinkUnTDtSrp6ngWpV7Xw0ZlA==", - "dev": true - }, "node_modules/fs.extra/node_modules/mkdirp": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", @@ -14079,15 +15230,6 @@ "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", "dev": true }, - "node_modules/fs.extra/node_modules/rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "dev": true, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -14095,9 +15237,9 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -14111,6 +15253,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", "dev": true, "dependencies": { "graceful-fs": "^4.1.2", @@ -14122,22 +15265,32 @@ "node": ">=0.6" } }, - "node_modules/fstream/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/fstream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { - "minimist": "^1.2.6" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/fstream/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -14163,15 +15316,15 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -14208,19 +15361,19 @@ } }, "node_modules/geckodriver": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.1.tgz", - "integrity": "sha512-ol7JLsj55o5k+z7YzeSy2mdJROXMAxIa+uzr3A1yEMr5HISqQOTslE3ZeARcxR4jpAY3fxmHM+sq32qbe/eXfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.1.tgz", + "integrity": "sha512-nnAdIrwLkMcDu4BitWXF23pEMeZZ0Cj7HaWWFdSpeedBP9z6ft150JYiGO2mwzw6UiR823Znk1JeIf07RyzloA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@wdio/logger": "^8.24.12", + "@wdio/logger": "^8.28.0", + "@zip.js/zip.js": "^2.7.44", "decamelize": "^6.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "node-fetch": "^3.3.2", - "tar-fs": "^3.0.4", - "unzipper": "^0.10.14", + "tar-fs": "^3.0.6", "which": "^4.0.0" }, "bin": { @@ -14231,9 +15384,9 @@ } }, "node_modules/geckodriver/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -14242,40 +15395,10 @@ "node": ">= 14" } }, - "node_modules/geckodriver/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/geckodriver/node_modules/decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/geckodriver/node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } - }, "node_modules/geckodriver/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -14285,86 +15408,18 @@ "node": ">= 14" } }, - "node_modules/geckodriver/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/geckodriver/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/geckodriver/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^3.1.5" - } - }, - "node_modules/geckodriver/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/geckodriver/node_modules/unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "node_modules/geckodriver/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" }, - "engines": { - "node": "^16.13.0 || >=18.0.0" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/gensync": { @@ -14385,24 +15440,28 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -14417,9 +15476,9 @@ } }, "node_modules/get-port": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", - "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", "dev": true, "engines": { "node": ">=16" @@ -14441,13 +15500,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -14457,52 +15517,55 @@ } }, "node_modules/get-uri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", - "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", "dev": true, "dependencies": { "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.0", + "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4", - "fs-extra": "^8.1.0" + "fs-extra": "^11.2.0" }, "engines": { "node": ">= 14" } }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/get-uri/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14.14" } }, "node_modules/get-uri/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, - "node_modules/get-uri/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -14541,9 +15604,9 @@ } }, "node_modules/git-url-parse": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", - "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.1.tgz", + "integrity": "sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==", "dev": true, "dependencies": { "git-up": "^7.0.0" @@ -14571,20 +15634,22 @@ "dev": true }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14623,6 +15688,27 @@ "node": ">= 0.10" } }, + "node_modules/glob-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-stream/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -14784,7 +15870,7 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", "dev": true, "hasInstallScript": true, "optional": true, @@ -14969,6 +16055,30 @@ "node": ">=0.10.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/global": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", @@ -15015,6 +16125,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/global-prefix/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -15041,6 +16157,22 @@ "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", "dev": true }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globule": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", @@ -15059,6 +16191,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -15111,34 +16244,46 @@ } }, "node_modules/got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=10.19.0" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sindresorhus/got?sponsor=1" } }, + "node_modules/got/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -15180,10 +16325,32 @@ "node": ">=0.9" } }, + "node_modules/gulp-clean/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gulp-clean/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -15644,15 +16811,6 @@ "node": ">=0.10.0" } }, - "node_modules/gulp-eslint/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/gulp-eslint/node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -15680,6 +16838,15 @@ "node": ">=4" } }, + "node_modules/gulp-eslint/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/gulp-eslint/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -15804,18 +16971,6 @@ "node": ">=4" } }, - "node_modules/gulp-eslint/node_modules/eslint/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/gulp-eslint/node_modules/espree": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", @@ -15843,6 +16998,21 @@ "node": ">=0.10.0" } }, + "node_modules/gulp-eslint/node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gulp-eslint/node_modules/file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -15875,6 +17045,27 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "node_modules/gulp-eslint/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gulp-eslint/node_modules/globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -15954,6 +17145,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/gulp-eslint/node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gulp-eslint/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -15963,6 +17166,12 @@ "node": ">=4" } }, + "node_modules/gulp-eslint/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/gulp-eslint/node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -15976,17 +17185,11 @@ "node": ">= 0.8.0" } }, - "node_modules/gulp-eslint/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } + "node_modules/gulp-eslint/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true }, "node_modules/gulp-eslint/node_modules/optionator": { "version": "0.8.3", @@ -16051,6 +17254,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -16059,6 +17263,27 @@ "rimraf": "bin.js" } }, + "node_modules/gulp-eslint/node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/gulp-eslint/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, "node_modules/gulp-eslint/node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -16094,6 +17319,27 @@ "node": ">=6" } }, + "node_modules/gulp-eslint/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-eslint/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/gulp-eslint/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -16147,18 +17393,6 @@ "node": ">=6" } }, - "node_modules/gulp-eslint/node_modules/table/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/gulp-eslint/node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -16275,12 +17509,12 @@ } }, "node_modules/gulp-replace": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", - "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.4.tgz", + "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", "dev": true, "dependencies": { - "@types/node": "^14.14.41", + "@types/node": "*", "@types/vinyl": "^2.0.4", "istextorbinary": "^3.0.0", "replacestream": "^4.0.3", @@ -16290,12 +17524,6 @@ "node": ">=10" } }, - "node_modules/gulp-replace/node_modules/@types/node": { - "version": "14.18.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", - "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", - "dev": true - }, "node_modules/gulp-shell": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/gulp-shell/-/gulp-shell-0.8.0.tgz", @@ -16482,6 +17710,12 @@ "node": ">=0.4.0" } }, + "node_modules/gulp-sourcemaps/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/gulp-sourcemaps/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -16845,13 +18079,13 @@ } }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -16898,12 +18132,9 @@ } }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "engines": { "node": ">= 0.4.0" } @@ -16959,20 +18190,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -16992,12 +18223,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -17076,9 +18307,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -17086,14 +18317,55 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-is-element": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", - "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", "dev": true, "dependencies": { "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0" + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", @@ -17101,9 +18373,9 @@ } }, "node_modules/hast-util-sanitize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.0.0.tgz", - "integrity": "sha512-pw56+69jq+QSr/coADNvWTmBPDy+XsmwaF5KnUys4/wM1jt/fZdl7GPxhXXXYdXnz3Gj3qMkbUCH2uKjvX0MgQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz", + "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==", "dev": true, "dependencies": { "@types/hast": "^2.0.0" @@ -17114,21 +18386,40 @@ } }, "node_modules/hast-util-to-html": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", - "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", "dev": true, "dependencies": { "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", - "hast-util-is-element": "^2.0.0", + "hast-util-raw": "^7.0.0", "hast-util-whitespace": "^2.0.0", "html-void-elements": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.2", - "unist-util-is": "^5.0.0" + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", @@ -17136,15 +18427,32 @@ } }, "node_modules/hast-util-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", - "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -17161,9 +18469,9 @@ "dev": true }, "node_modules/highlight.js": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", - "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", "dev": true, "engines": { "node": ">=12.0.0" @@ -17195,15 +18503,24 @@ } }, "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" } }, "node_modules/html-escaper": { @@ -17264,9 +18581,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -17277,9 +18594,9 @@ } }, "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -17304,13 +18621,13 @@ } }, "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "resolve-alpn": "^1.2.0" }, "engines": { "node": ">=10.19.0" @@ -17378,6 +18695,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -17404,9 +18727,9 @@ } }, "node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true, "funding": { "type": "github", @@ -17432,6 +18755,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -17478,21 +18802,6 @@ "node": ">=14.18.0" } }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/inquirer/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -17505,128 +18814,26 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inquirer/node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/inquirer/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/inquirer/node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/inquirer/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -17660,10 +18867,23 @@ "node": ">=0.10.0" } }, - "node_modules/ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ipaddr.js": { @@ -17696,24 +18916,15 @@ } }, "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", "dev": true, "dependencies": { - "kind-of": "^6.0.0" + "hasown": "^2.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" } }, "node_modules/is-arguments": { @@ -17732,6 +18943,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -17814,35 +19041,41 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", "dev": true, "dependencies": { - "kind-of": "^6.0.0" + "hasown": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-date-object": { @@ -17861,26 +19094,16 @@ } }, "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/is-docker": { @@ -17993,10 +19216,13 @@ } }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -18027,9 +19253,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -18124,21 +19350,27 @@ "dev": true }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18193,16 +19425,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -18230,12 +19458,12 @@ } }, "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -18257,10 +19485,13 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -18278,13 +19509,16 @@ } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18330,10 +19564,13 @@ } }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } }, "node_modules/isobject": { "version": "3.0.1", @@ -18376,9 +19613,9 @@ } }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" @@ -18401,17 +19638,17 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-report/node_modules/has-flag": { @@ -18423,6 +19660,33 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18459,9 +19723,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -18475,6 +19739,7 @@ "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "inflight": "^1.0.4", @@ -18496,17 +19761,11 @@ "node": ">=0.10.0" } }, - "node_modules/istanbul/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } + "node_modules/istanbul/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/istanbul/node_modules/resolve": { "version": "1.1.7", @@ -18555,9 +19814,9 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -18573,9 +19832,9 @@ } }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", "dev": true, "dependencies": { "async": "^3.2.3", @@ -19099,9 +20358,9 @@ } }, "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true }, "node_modules/jsdoc-type-pratt-parser": { @@ -19131,10 +20390,13 @@ "dev": true }, "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/json-schema": { "version": "0.4.0", @@ -19161,9 +20423,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -19172,16 +20434,10 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha512-KbsDJNRfRPF5v49tMNf9sqyyGqGLBcz1v5kZT01kG5ns5mQSltwxCKVmUzVKtEinkUnTDtSrp6ngWpV7Xw0ZlA==", + "dev": true }, "node_modules/jsprim": { "version": "1.4.2", @@ -19198,6 +20454,18 @@ "node": ">=0.6.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -19211,9 +20479,9 @@ "dev": true }, "node_modules/karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", + "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", "dev": true, "dependencies": { "@colors/colors": "1.5.0", @@ -19235,7 +20503,7 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^4.4.1", + "socket.io": "^4.7.2", "source-map": "^0.6.1", "tmp": "^0.2.1", "ua-parser-js": "^0.7.30", @@ -19285,14 +20553,20 @@ } }, "node_modules/karma-chrome-launcher": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", - "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", "dev": true, "dependencies": { "which": "^1.2.1" } }, + "node_modules/karma-chrome-launcher/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "node_modules/karma-chrome-launcher/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -19306,9 +20580,9 @@ } }, "node_modules/karma-coverage": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", - "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.2.0", @@ -19338,6 +20612,27 @@ "url": "https://github.com/sponsors/mattlewis92" } }, + "node_modules/karma-coverage-istanbul-reporter/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", @@ -19389,6 +20684,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -19425,13 +20721,34 @@ } }, "node_modules/karma-firefox-launcher": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", - "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.3.tgz", + "integrity": "sha512-LMM2bseebLbYjODBOVt7TCPP9OI2vZIXCavIXhkO9m+10Uj5l7u/SKoeRmYx8FYHTVGZSpk6peX+3BMHC1WwNw==", "dev": true, "dependencies": { "is-wsl": "^2.2.0", - "which": "^2.0.1" + "which": "^3.0.0" + } + }, + "node_modules/karma-firefox-launcher/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/karma-firefox-launcher/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/karma-ie-launcher": { @@ -19552,22 +20869,94 @@ } }, "node_modules/karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", + "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", "dev": true, "dependencies": { "glob": "^7.1.3", - "minimatch": "^3.0.4", + "minimatch": "^9.0.3", "webpack-merge": "^4.1.5" }, "engines": { - "node": ">= 6" + "node": ">= 18" }, "peerDependencies": { "webpack": "^5.0.0" } }, + "node_modules/karma-webpack/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/karma-webpack/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma-webpack/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/karma-webpack/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/karma/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/karma/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -19579,16 +20968,43 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/karma/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/karma/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "minimist": "^1.2.6" + "color-name": "~1.1.4" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/karma/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/karma/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/karma/node_modules/source-map": { @@ -19600,16 +21016,33 @@ "node": ">=0.10.0" } }, - "node_modules/karma/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/karma/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "rimraf": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8.17.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/karma/node_modules/yargs": { @@ -19773,6 +21206,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -19805,9 +21247,9 @@ } }, "node_modules/lighthouse-logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.3.0.tgz", - "integrity": "sha512-BbqAKApLb9ywUli+0a+PcV04SyJ/N1q/8qgCNe6U97KbPCS1BTksEuHFLYdvc8DltuhfxIUBqDZsC0bBGtl3lA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "dev": true, "dependencies": { "debug": "^2.6.9", @@ -19830,10 +21272,13 @@ "dev": true }, "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } }, "node_modules/listenercount": { "version": "1.0.1", @@ -19940,12 +21385,22 @@ } }, "node_modules/locate-app": { - "version": "2.2.13", - "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.13.tgz", - "integrity": "sha512-1jp6iRFrHKBj9vq6Idb0cSjly+KnCIMbxZ2BBKSEzIC4ZJosv47wnLoiJu2EgOAdjhGvNcy/P2fbDCS/WziI8g==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.4.15.tgz", + "integrity": "sha512-oAGHATXPUHSQ74Om+3dXBRNYtCzU7Wzuhlj/WIZchqHb/5/TGJRzLEtHipMDOak0UZG9U365RMXyBzgV/fhOww==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing" + } + ], "dependencies": { - "n12": "1.8.16", + "@promptbook/utils": "0.50.0-10", "type-fest": "2.13.0", "userhome": "1.0.0" } @@ -20213,16 +21668,16 @@ } }, "node_modules/log4js": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", - "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "flatted": "^3.2.7", "rfdc": "^1.3.0", - "streamroller": "^3.1.3" + "streamroller": "^3.1.5" }, "engines": { "node": ">=8.0" @@ -20255,9 +21710,9 @@ } }, "node_modules/loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", "dev": true, "engines": { "node": ">= 0.6.0" @@ -20280,9 +21735,9 @@ "dev": true }, "node_modules/longest-streak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", - "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "dev": true, "funding": { "type": "github", @@ -20302,33 +21757,32 @@ } }, "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "dependencies": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/lru-queue": { @@ -20341,9 +21795,9 @@ } }, "node_modules/m3u8-parser": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz", - "integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz", + "integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -20352,13 +21806,12 @@ } }, "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, - "optional": true, "dependencies": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/make-dir": { @@ -20425,9 +21878,9 @@ } }, "node_modules/markdown-table": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz", - "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", "dev": true, "funding": { "type": "github", @@ -20659,9 +22112,9 @@ } }, "node_modules/mdast-util-definitions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", - "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20674,11 +22127,12 @@ } }, "node_modules/mdast-util-find-and-replace": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", - "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", "dev": true, "dependencies": { + "@types/mdast": "^3.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.0.0" @@ -20701,9 +22155,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", - "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20725,19 +22179,22 @@ } }, "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", - "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", "dev": true, "dependencies": { "mdast-util-from-markdown": "^1.0.0", @@ -20754,9 +22211,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", - "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20770,9 +22227,9 @@ } }, "node_modules/mdast-util-gfm-footnote": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", - "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20785,9 +22242,9 @@ } }, "node_modules/mdast-util-gfm-strikethrough": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz", - "integrity": "sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20799,9 +22256,9 @@ } }, "node_modules/mdast-util-gfm-table": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz", - "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20815,9 +22272,9 @@ } }, "node_modules/mdast-util-gfm-task-list-item": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", - "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -20837,10 +22294,24 @@ "mdast-util-to-string": "^1.0.0" } }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-hast": { - "version": "12.2.4", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.4.tgz", - "integrity": "sha512-a21xoxSef1l8VhHxS1Dnyioz6grrJkoaCUgGzMD/7dWHvboYX3VW53esRUfB5tgTyz4Yos1n25SPcj35dJqmAg==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", "dev": true, "dependencies": { "@types/hast": "^2.0.0", @@ -20848,7 +22319,6 @@ "mdast-util-definitions": "^5.0.0", "micromark-util-sanitize-uri": "^1.1.0", "trim-lines": "^3.0.0", - "unist-builder": "^3.0.0", "unist-util-generated": "^2.0.0", "unist-util-position": "^4.0.0", "unist-util-visit": "^4.0.0" @@ -20859,14 +22329,15 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz", - "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", "mdast-util-to-string": "^3.0.0", "micromark-util-decode-string": "^1.0.0", "unist-util-visit": "^4.0.0", @@ -20878,10 +22349,13 @@ } }, "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -20898,58 +22372,37 @@ } }, "node_modules/mdast-util-toc": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.0.tgz", - "integrity": "sha512-0PuqZELXZl4ms1sF7Lqigrqik4Ll3UhbI+jdTrfw7pZ9QPawgl7LD4GQ8MkU7bT/EwiVqChNTbifa2jLLKo76A==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.1.tgz", + "integrity": "sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==", "dev": true, "dependencies": { "@types/extend": "^3.0.0", - "@types/github-slugger": "^1.0.0", "@types/mdast": "^3.0.0", "extend": "^3.0.0", - "github-slugger": "^1.0.0", + "github-slugger": "^2.0.0", "mdast-util-to-string": "^3.1.0", "unist-util-is": "^5.0.0", - "unist-util-visit": "^3.0.0" + "unist-util-visit": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-toc/node_modules/unist-util-visit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", - "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/mdast-util-toc/node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true }, - "node_modules/mdast-util-toc/node_modules/unist-util-visit-parents": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", - "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "dev": true, "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" + "@types/mdast": "^3.0.0" }, "funding": { "type": "opencollective", @@ -20965,19 +22418,22 @@ } }, "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", "dev": true, "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", + "d": "^1.0.2", + "es5-ext": "^0.10.64", "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", "is-promise": "^2.2.2", "lru-queue": "^0.1.0", "next-tick": "^1.1.0", "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/memory-fs": { @@ -21013,9 +22469,9 @@ } }, "node_modules/micromark": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", - "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", "dev": true, "funding": [ { @@ -21048,9 +22504,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", - "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", "dev": true, "funding": [ { @@ -21082,9 +22538,9 @@ } }, "node_modules/micromark-extension-gfm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", - "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", "dev": true, "dependencies": { "micromark-extension-gfm-autolink-literal": "^1.0.0", @@ -21102,16 +22558,15 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", - "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", "dev": true, "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-types": "^1.0.0" }, "funding": { "type": "opencollective", @@ -21119,9 +22574,9 @@ } }, "node_modules/micromark-extension-gfm-footnote": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", - "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", "dev": true, "dependencies": { "micromark-core-commonmark": "^1.0.0", @@ -21139,9 +22594,9 @@ } }, "node_modules/micromark-extension-gfm-strikethrough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", - "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", "dev": true, "dependencies": { "micromark-util-chunked": "^1.0.0", @@ -21157,9 +22612,9 @@ } }, "node_modules/micromark-extension-gfm-table": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", - "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", "dev": true, "dependencies": { "micromark-factory-space": "^1.0.0", @@ -21174,9 +22629,9 @@ } }, "node_modules/micromark-extension-gfm-tagfilter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", - "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", "dev": true, "dependencies": { "micromark-util-types": "^1.0.0" @@ -21187,9 +22642,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", - "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", "dev": true, "dependencies": { "micromark-factory-space": "^1.0.0", @@ -21204,9 +22659,9 @@ } }, "node_modules/micromark-factory-destination": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", - "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", "dev": true, "funding": [ { @@ -21225,9 +22680,9 @@ } }, "node_modules/micromark-factory-label": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", - "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", "dev": true, "funding": [ { @@ -21247,9 +22702,9 @@ } }, "node_modules/micromark-factory-space": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", - "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", "dev": true, "funding": [ { @@ -21267,9 +22722,9 @@ } }, "node_modules/micromark-factory-title": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", - "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", "dev": true, "funding": [ { @@ -21285,14 +22740,13 @@ "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-types": "^1.0.0" } }, "node_modules/micromark-factory-whitespace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", - "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", "dev": true, "funding": [ { @@ -21312,9 +22766,9 @@ } }, "node_modules/micromark-util-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", - "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "dev": true, "funding": [ { @@ -21332,9 +22786,9 @@ } }, "node_modules/micromark-util-chunked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", - "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", "dev": true, "funding": [ { @@ -21351,9 +22805,9 @@ } }, "node_modules/micromark-util-classify-character": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", - "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", "dev": true, "funding": [ { @@ -21372,9 +22826,9 @@ } }, "node_modules/micromark-util-combine-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", - "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", "dev": true, "funding": [ { @@ -21392,9 +22846,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", - "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", "dev": true, "funding": [ { @@ -21411,9 +22865,9 @@ } }, "node_modules/micromark-util-decode-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", - "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", "dev": true, "funding": [ { @@ -21433,9 +22887,9 @@ } }, "node_modules/micromark-util-encode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", - "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", "dev": true, "funding": [ { @@ -21449,9 +22903,9 @@ ] }, "node_modules/micromark-util-html-tag-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", - "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", "dev": true, "funding": [ { @@ -21465,9 +22919,9 @@ ] }, "node_modules/micromark-util-normalize-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", - "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", "dev": true, "funding": [ { @@ -21484,9 +22938,9 @@ } }, "node_modules/micromark-util-resolve-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", - "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", "dev": true, "funding": [ { @@ -21503,9 +22957,9 @@ } }, "node_modules/micromark-util-sanitize-uri": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", - "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", "dev": true, "funding": [ { @@ -21524,9 +22978,9 @@ } }, "node_modules/micromark-util-subtokenize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", - "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", "dev": true, "funding": [ { @@ -21546,9 +23000,9 @@ } }, "node_modules/micromark-util-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", - "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "dev": true, "funding": [ { @@ -21562,9 +23016,9 @@ ] }, "node_modules/micromark-util-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", - "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "dev": true, "funding": [ { @@ -21578,12 +23032,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -21631,12 +23085,15 @@ } }, "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/min-document": { @@ -21661,18 +23118,18 @@ } }, "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -21697,6 +23154,18 @@ "node": ">=0.10.0" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -21704,9 +23173,9 @@ "dev": true }, "node_modules/mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -21716,13 +23185,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -21737,10 +23205,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha/node_modules/ansi-colors": { @@ -21773,6 +23237,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/mocha/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -21801,6 +23274,33 @@ "node": ">=8" } }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -21830,6 +23330,29 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/mocha/node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -21868,37 +23391,25 @@ } }, "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -21908,6 +23419,18 @@ "node": ">=8" } }, + "node_modules/mocha/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mocha/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -21963,15 +23486,6 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -22008,6 +23522,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mocha/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -22035,6 +23561,23 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/mocha/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -22062,6 +23605,27 @@ "node": ">=10" } }, + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -22106,14 +23670,14 @@ } }, "node_modules/mpd-parser": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz", - "integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==", + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz", + "integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.5", - "@xmldom/xmldom": "^0.7.2", + "@xmldom/xmldom": "^0.8.3", "global": "^4.4.0" }, "bin": { @@ -22130,9 +23694,9 @@ } }, "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "dev": true, "engines": { "node": ">=10" @@ -22162,10 +23726,13 @@ } }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/mux.js": { "version": "6.0.1", @@ -22184,24 +23751,25 @@ "npm": ">=5" } }, - "node_modules/n12": { - "version": "1.8.16", - "resolved": "https://registry.npmjs.org/n12/-/n12-1.8.16.tgz", - "integrity": "sha512-CZqHAqbzS0UsaUGkMsL+lMaYLyFr1+/ea+pD8dMziqSjkcuWVWDtgWx9phyfT7C3llqQ2+LwnStSb5afggBMfA==", - "dev": true - }, "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", "dev": true, "optional": true }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -22379,29 +23947,27 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-html-parser": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.6.tgz", - "integrity": "sha512-C/MGDQ2NjdjzUq41bW9kW00MPZecAe/oo89vZEFLDfWoQVDk/DdML1yuxVVKLDMFIFax2VTq6Vpfzyn7z5yYgQ==", + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", "dev": true, "dependencies": { "css-select": "^5.1.0", @@ -22409,9 +23975,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/node-request-interceptor": { "version": "0.6.3", @@ -22450,28 +24016,25 @@ } }, "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", + "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", "dev": true, "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -22489,12 +24052,12 @@ } }, "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -22598,57 +24161,23 @@ "node": ">=0.10.0" } }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-copy/node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/object-copy/node_modules/kind-of": { @@ -22664,21 +24193,21 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -22709,13 +24238,13 @@ } }, "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" }, @@ -22741,6 +24270,38 @@ "node": ">=0.10.0" } }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", @@ -22780,14 +24341,14 @@ } }, "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -22871,9 +24432,9 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { "deep-is": "^0.1.3", @@ -22881,7 +24442,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -22968,6 +24529,18 @@ "node": ">=8" } }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ora/node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -22984,6 +24557,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ora/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23036,12 +24621,12 @@ } }, "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12.20" } }, "node_modules/p-finally": { @@ -23118,9 +24703,9 @@ } }, "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -23130,9 +24715,9 @@ } }, "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -23143,19 +24728,24 @@ } }, "node_modules/pac-resolver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", - "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "dependencies": { "degenerator": "^5.0.0", - "ip": "^1.1.8", "netmask": "^2.0.2" }, "engines": { "node": ">= 14" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -23183,18 +24773,31 @@ } }, "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -23245,6 +24848,12 @@ "parse-path": "^7.0.0" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -23322,25 +24931,25 @@ } }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -23411,9 +25020,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -23509,10 +25118,19 @@ "node": ">=0.10.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -23522,31 +25140,22 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "optional": true, "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "optional": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -23556,6 +25165,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -23639,9 +25263,9 @@ } }, "node_modules/property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "dev": true, "funding": { "type": "github", @@ -23686,9 +25310,9 @@ } }, "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -23698,9 +25322,9 @@ } }, "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -23796,9 +25420,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -23827,12 +25451,71 @@ "node": ">=10.18.1" } }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/puppeteer-core/node_modules/devtools-protocol": { "version": "0.0.981744", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", "dev": true }, + "node_modules/puppeteer-core/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/puppeteer-core/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/puppeteer-core/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/puppeteer-core/node_modules/ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", @@ -23858,6 +25541,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "dev": true, "engines": { "node": ">=0.6.0", @@ -23888,15 +25572,15 @@ } }, "node_modules/query-selector-shadow-dom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.0.tgz", - "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", "dev": true }, "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", "dev": true, "engines": { @@ -23959,41 +25643,41 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, "node_modules/read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "dev": true, "dependencies": { "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" }, "engines": { - "node": ">=12.20" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.0.0.tgz", + "integrity": "sha512-jgmKiS//w2Zs+YbX039CorlkOp8FIVbSAN8r8GJHDsGlmNPXo+VeHkqAwCiQVTTx5/LwLZTcEw59z3DvcLbr0g==", "dev": true, "dependencies": { "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" + "read-pkg": "^8.0.0", + "type-fest": "^3.12.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -24016,9 +25700,9 @@ } }, "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", - "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "dependencies": { "p-locate": "^6.0.0" @@ -24070,45 +25754,33 @@ } }, "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "dev": true, "engines": { - "node": ">=12.20" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.0.tgz", + "integrity": "sha512-MBh+PHUHHisjXf4tlx0CFWoMdjx8zCMLJHOjnV1prABYZFHqtFOyauCIK2/7w4oIfwkF8iNhLtnJEfVY2vn3iw==", "dev": true, "engines": { - "node": ">=12.20" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -24130,9 +25802,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/readdir-glob": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", - "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "dependencies": { "minimatch": "^5.1.0" @@ -24148,9 +25820,9 @@ } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -24201,9 +25873,9 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", "dependencies": { "regenerate": "^1.4.2" }, @@ -24212,14 +25884,14 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -24251,14 +25923,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -24280,16 +25953,16 @@ } }, "node_modules/regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "dependencies": { + "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" + "unicode-match-property-value-ecmascript": "^2.1.0" }, "engines": { "node": ">=4" @@ -24304,11 +25977,6 @@ "node": ">=0.1.14" } }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" - }, "node_modules/regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -24329,9 +25997,9 @@ } }, "node_modules/remark": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.2.tgz", - "integrity": "sha512-A3ARm2V4BgiRXaUo5K0dRvJ1lbogrbXnhkJRmD0yw092/Yl0kOCZt1k9ZeElEwkZsWGsMumz6qL5MfNJH9nOBA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", + "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -24361,9 +26029,9 @@ } }, "node_modules/remark-html": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.1.tgz", - "integrity": "sha512-7ta5UPRqj8nP0GhGMYUAghZ/DRno7dgq7alcW90A7+9pgJsXzGJlFgwF8HOP1b1tMgT3WwbeANN+CaTimMfyNQ==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.2.tgz", + "integrity": "sha512-/CIOI7wzHJzsh48AiuIyIe1clxVkUtreul73zcCXLub0FmnevQE0UMFDQm7NUx8/3rl/4zCshlMfqBdWScQthw==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -24378,9 +26046,9 @@ } }, "node_modules/remark-parse": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", - "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -24408,9 +26076,9 @@ } }, "node_modules/remark-stringify": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.2.tgz", - "integrity": "sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz", + "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", @@ -24591,6 +26259,16 @@ "node": ">=0.6" } }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -24622,11 +26300,11 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -24685,21 +26363,24 @@ "dev": true }, "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "dependencies": { - "lowercase-keys": "^2.0.0" + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/resq": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.2.tgz", - "integrity": "sha512-HmgVS3j+FLrEDBTDYysPdPVF9/hioDMJ/otOiQDKqk77YfZeeLOj0qi34yObumcud1gBpk+wpBTEg4kMicD++A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", "dev": true, "dependencies": { "fast-deep-equal": "^2.0.1" @@ -24734,9 +26415,9 @@ } }, "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "node_modules/rgb2hex": { @@ -24749,6 +26430,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -24760,10 +26442,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, "engines": { "node": ">=0.12.0" @@ -24779,17 +26482,20 @@ } }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -24808,6 +26514,24 @@ "integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==", "dev": true }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -24843,15 +26567,18 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -25118,15 +26845,31 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -25213,13 +26956,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -25235,6 +26982,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "deprecated": "16.1.1", "dev": true, "hasInstallScript": true, "dependencies": { @@ -25257,14 +27005,14 @@ } }, "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dev": true, "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" }, "engines": { "node": ">= 10" @@ -25447,72 +27195,17 @@ "node": ">=0.10.0" } }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/snapdragon/node_modules/is-extendable": { @@ -25545,35 +27238,37 @@ } }, "node_modules/socket.io": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", - "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", + "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.4.1", + "engine.io": "~6.5.2", "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" + "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", "dev": true, "dependencies": { + "debug": "~4.3.4", "ws": "~8.11.0" } }, "node_modules/socket.io-parser": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz", - "integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -25584,26 +27279,26 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks-proxy-agent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", - "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.7.1" }, @@ -25612,9 +27307,9 @@ } }, "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -25623,12 +27318,6 @@ "node": ">= 14" } }, - "node_modules/socks/node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", - "dev": true - }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -25645,9 +27334,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "optional": true, "engines": { @@ -25681,23 +27370,32 @@ "deprecated": "See https://github.com/lydell/source-map-url#deprecated", "dev": true }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true, - "optional": true - }, "node_modules/space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spacetrim": { + "version": "0.11.25", + "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.25.tgz", + "integrity": "sha512-SWxXDROciuJs9YEYXUBjot5k/cqNGPPbT3QmkInFne4AGc1y+76It+jqU8rfsXKt57RRiunzZn1m9+KfuuNklw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing" + } + ] + }, "node_modules/sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", @@ -25708,9 +27406,9 @@ } }, "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", @@ -25718,9 +27416,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -25734,9 +27432,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "node_modules/split": { @@ -25791,9 +27489,9 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -25815,6 +27513,12 @@ "node": ">=0.10.0" } }, + "node_modules/sshpk/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -25870,72 +27574,17 @@ "node": ">=0.10.0" } }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, "node_modules/statuses": { @@ -25946,6 +27595,18 @@ "node": ">= 0.8" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", @@ -25971,15 +27632,15 @@ "dev": true }, "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "dev": true }, "node_modules/streamroller": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", - "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, "dependencies": { "date-format": "^4.0.14", @@ -26023,13 +27684,17 @@ } }, "node_modules/streamx": { - "version": "2.15.6", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", - "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/strict-event-emitter": { @@ -26086,38 +27751,83 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/stringify-entities": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", - "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dev": true, "dependencies": { "character-entities-html4": "^2.0.0", @@ -26129,15 +27839,18 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi-cjs": { @@ -26153,6 +27866,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -26193,9 +27918,9 @@ } }, "node_modules/strip-json-comments": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", - "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", "dev": true, "engines": { "node": ">=14.16" @@ -26237,9 +27962,9 @@ } }, "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "dependencies": { "ajv": "^8.0.1", @@ -26253,15 +27978,15 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", @@ -26274,6 +27999,18 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -26284,45 +28021,25 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, "dependencies": { - "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/temp-fs": { @@ -26337,10 +28054,32 @@ "node": ">=0.8.0" } }, + "node_modules/temp-fs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/temp-fs/node_modules/rimraf": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.0.5" @@ -26372,13 +28111,13 @@ } }, "node_modules/terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", "dev": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -26390,16 +28129,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -26440,9 +28179,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -26457,10 +28196,19 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/terser/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -26502,6 +28250,36 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -26556,9 +28334,9 @@ } }, "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -26579,13 +28357,16 @@ } }, "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", "dev": true, "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/tiny-hashes": { @@ -26617,15 +28398,12 @@ } }, "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/to-absolute-glob": { @@ -26750,9 +28528,9 @@ } }, "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "engines": { "node": ">=6" @@ -26815,9 +28593,9 @@ } }, "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "funding": { "type": "github", @@ -26830,21 +28608,21 @@ "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==" }, "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -26878,9 +28656,9 @@ "dev": true }, "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", "dev": true }, "node_modules/type-check": { @@ -26928,27 +28706,85 @@ "node": ">= 0.6" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } }, - "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=4.2.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, "node_modules/typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", @@ -26971,9 +28807,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", - "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", "dev": true, "funding": [ { @@ -26983,6 +28819,10 @@ { "type": "paypal", "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" } ], "engines": { @@ -26990,9 +28830,9 @@ } }, "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", "dev": true, "optional": true, "bin": { @@ -27099,9 +28939,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", "engines": { "node": ">=4" } @@ -27177,9 +29017,9 @@ } }, "node_modules/unist-builder": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", - "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", + "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", "dev": true, "dependencies": { "@types/unist": "^2.0.0" @@ -27190,9 +29030,9 @@ } }, "node_modules/unist-util-generated": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", - "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", "dev": true, "funding": { "type": "opencollective", @@ -27200,19 +29040,22 @@ } }, "node_modules/unist-util-is": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", - "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-position": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", - "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0" @@ -27223,9 +29066,9 @@ } }, "node_modules/unist-util-stringify-position": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", - "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0" @@ -27236,9 +29079,9 @@ } }, "node_modules/unist-util-visit": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", - "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -27251,9 +29094,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", - "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -27265,9 +29108,9 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -27352,6 +29195,12 @@ "setimmediate": "~1.0.4" } }, + "node_modules/unzipper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, "node_modules/unzipper/node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -27372,9 +29221,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "funding": [ { "type": "opencollective", @@ -27383,14 +29232,18 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -27413,13 +29266,13 @@ "dev": true }, "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dev": true, "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "punycode": "^1.4.1", + "qs": "^6.11.2" } }, "node_modules/url-parse": { @@ -27439,11 +29292,26 @@ "dev": true }, "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, + "node_modules/url/node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -27489,13 +29357,16 @@ } }, "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/uvu": { @@ -27517,9 +29388,9 @@ } }, "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", "dev": true }, "node_modules/v8flags": { @@ -27582,9 +29453,9 @@ "dev": true }, "node_modules/vfile": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", - "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -27597,10 +29468,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", - "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", @@ -27612,15 +29497,17 @@ } }, "node_modules/vfile-reporter": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.4.tgz", - "integrity": "sha512-4cWalUnLrEnbeUQ+hARG5YZtaHieVK3Jp4iG5HslttkVl+MHunSGNAIrODOTLbtjWsNZJRMCkL66AhvZAYuJ9A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.5.tgz", + "integrity": "sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw==", "dev": true, "dependencies": { "@types/supports-color": "^8.0.0", "string-width": "^5.0.0", "supports-color": "^9.0.0", "unist-util-stringify-position": "^3.0.0", + "vfile": "^5.0.0", + "vfile-message": "^3.0.0", "vfile-sort": "^3.0.0", "vfile-statistics": "^2.0.0" }, @@ -27629,18 +29516,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-reporter/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/vfile-reporter/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -27664,25 +29539,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vfile-reporter/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/vfile-reporter/node_modules/supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true, "engines": { "node": ">=12" @@ -27692,11 +29552,12 @@ } }, "node_modules/vfile-sort": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.0.tgz", - "integrity": "sha512-fJNctnuMi3l4ikTVcKpxTbzHeCgvDhnI44amA3NVDvA6rTC6oKCFpCVyT5n2fFMr3ebfr+WVQZedOCd73rzSxg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.1.tgz", + "integrity": "sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw==", "dev": true, "dependencies": { + "vfile": "^5.0.0", "vfile-message": "^3.0.0" }, "funding": { @@ -27705,11 +29566,12 @@ } }, "node_modules/vfile-statistics": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.0.tgz", - "integrity": "sha512-foOWtcnJhKN9M2+20AOTlWi2dxNfAoeNIoxD5GXcO182UJyId4QrXa41fWrgcfV3FWTjdEDy3I4cpLVcQscIMA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.1.tgz", + "integrity": "sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg==", "dev": true, "dependencies": { + "vfile": "^5.0.0", "vfile-message": "^3.0.0" }, "funding": { @@ -27718,24 +29580,24 @@ } }, "node_modules/video.js": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.3.tgz", - "integrity": "sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==", + "version": "7.21.6", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.6.tgz", + "integrity": "sha512-m41TbODrUCToVfK1aljVd296CwDQnCRewpIm5tTXMuV87YYSGw1H+VDOaV45HlpcWSsTWWLF++InDgGJfthfUw==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "2.14.3", + "@videojs/http-streaming": "2.16.3", "@videojs/vhs-utils": "^3.0.4", "@videojs/xhr": "2.6.0", "aes-decrypter": "3.1.3", "global": "^4.4.0", "keycode": "^2.2.0", - "m3u8-parser": "4.7.1", - "mpd-parser": "0.21.1", + "m3u8-parser": "4.8.0", + "mpd-parser": "0.22.1", "mux.js": "6.0.1", "safe-json-parse": "4.0.0", "videojs-font": "3.2.0", - "videojs-vtt.js": "^0.15.4" + "videojs-vtt.js": "^0.15.5" } }, "node_modules/video.js/node_modules/safe-json-parse": { @@ -27788,22 +29650,22 @@ } }, "node_modules/videojs-playlist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.0.0.tgz", - "integrity": "sha512-TM9bytwKqkE05wdWPEKDpkwMGhS/VgMCIsEuNxmX1J1JO9zaTIl4Wm3egf5j1dhIw19oWrqGUV/nK0YNIelCpA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.2.tgz", + "integrity": "sha512-8YgNq/iL17RLTXpfWAkuhM0Sq4w/x5YPVaNbUycjfqqGL/bp3Nrmc2W0qkPfh0ryB7r4cHfJbtHYP7zlW3ZkdQ==", "dev": true, "dependencies": { "global": "^4.3.2", - "video.js": "^6 || ^7" + "video.js": "^6 || ^7 || ^8" }, "engines": { "node": ">=4.4.0" } }, "node_modules/videojs-vtt.js": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz", - "integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==", + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", "dev": true, "dependencies": { "global": "^4.3.1" @@ -27890,6 +29752,12 @@ "node": ">= 0.10" } }, + "node_modules/vinyl-sourcemap/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/vinyl-sourcemap/node_modules/normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -27930,9 +29798,9 @@ } }, "node_modules/vue-template-compiler": { - "version": "2.7.13", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.13.tgz", - "integrity": "sha512-jYM6TClwDS9YqP48gYrtAtaOhRKkbYmbzE+Q51gX5YDr777n7tNI/IZk4QV4l/PjQPNh/FVa/E92sh/RqKMrog==", + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", "dev": true, "optional": true, "dependencies": { @@ -28046,9 +29914,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -28067,6 +29935,16 @@ "defaults": "^1.0.3" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -28077,18 +29955,18 @@ } }, "node_modules/webdriver": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.29.1.tgz", - "integrity": "sha512-D3gkbDUxFKBJhNHRfMriWclooLbNavVQC1MRvmENAgPNKaHnFn+M+WtP9K2sEr0XczLGNlbOzT7CKR9K5UXKXA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.38.2.tgz", + "integrity": "sha512-NGfjW0BDYwFgOIzeojOcWGn3tYloQdvHr+Y2xKKYVqa9Rs0x1mzlTjU1kWtC4DaV8DltskwaPa7o+s8hTNpuyA==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "deepmerge-ts": "^5.1.0", "got": "^12.6.1", "ky": "^0.33.0", @@ -28098,220 +29976,87 @@ "node": "^16.13 || >=18" } }, - "node_modules/webdriver/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/webdriver/node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/webdriver/node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/webdriver/node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/webdriver/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webdriver/node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/webdriver/node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/webdriver/node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webdriver/node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webdriver/node_modules/normalize-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", - "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webdriver/node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/webdriver/node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webdriverio": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.25.4.tgz", - "integrity": "sha512-agkgwn2SIk5cAJ03uue1GnGZcUZUDN3W4fUMY9/VfO8bVJrPEgWg31bPguEWPu+YhEB/aBJD8ECxJ3OEKdy4qQ==", + "version": "7.36.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.36.0.tgz", + "integrity": "sha512-OTYmKBF7eFKBX39ojUIEzw7AlE1ZRJiFoMTtEQaPMuPzZCP2jUBq6Ey38nuZrYXLkXn3/le9a14pNnKSM0n56w==", "dev": true, "dependencies": { "@types/aria-query": "^5.0.0", "@types/node": "^18.0.0", - "@wdio/config": "7.25.4", - "@wdio/logger": "7.19.0", - "@wdio/protocols": "7.22.0", - "@wdio/repl": "7.25.4", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@wdio/config": "7.33.0", + "@wdio/logger": "7.26.0", + "@wdio/protocols": "7.27.0", + "@wdio/repl": "7.33.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "archiver": "^5.0.0", - "aria-query": "^5.0.0", + "aria-query": "^5.2.1", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.25.4", - "devtools-protocol": "^0.0.1061995", - "fs-extra": "^10.0.0", + "devtools": "7.35.0", + "devtools-protocol": "^0.0.1260888", + "fs-extra": "^11.1.1", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^5.0.0", + "minimatch": "^6.0.4", "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "7.25.4" + "webdriver": "7.33.0" }, "engines": { "node": ">=12.0.0" } }, + "node_modules/webdriverio/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/webdriverio/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/webdriverio/node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", - "dev": true + "version": "18.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", + "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/webdriverio/node_modules/@wdio/config": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.25.4.tgz", - "integrity": "sha512-vb0emDtD9FbFh/yqW6oNdo2iuhQp8XKj6GX9fyy9v4wZgg3B0HPMVJxhIfcoHz7LMBWlHSo9YdvhFI5EQHRLBA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", + "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", "dev": true, "dependencies": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@types/glob": "^8.1.0", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "deepmerge": "^4.0.0", "glob": "^8.0.3" }, @@ -28320,9 +30065,9 @@ } }, "node_modules/webdriverio/node_modules/@wdio/logger": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.19.0.tgz", - "integrity": "sha512-xR7SN/kGei1QJD1aagzxs3KMuzNxdT/7LYYx+lt6BII49+fqL/SO+5X0FDCZD0Ds93AuQvvz9eGyzrBI2FFXmQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", + "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", "dev": true, "dependencies": { "chalk": "^4.0.0", @@ -28335,30 +30080,30 @@ } }, "node_modules/webdriverio/node_modules/@wdio/protocols": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.22.0.tgz", - "integrity": "sha512-8EXRR+Ymdwousm/VGtW3H1hwxZ/1g1H99A1lF0U4GuJ5cFWHCd0IVE5H31Z52i8ZruouW8jueMkGZPSo2IIUSQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", + "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", "dev": true, "engines": { "node": ">=12.0.0" } }, "node_modules/webdriverio/node_modules/@wdio/repl": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.25.4.tgz", - "integrity": "sha512-kYhj9gLsUk4HmlXLqkVre+gwbfvw9CcnrHjqIjrmMS4mR9D8zvBb5CItb3ZExfPf9jpFzIFREbCAYoE9x/kMwg==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz", + "integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==", "dev": true, "dependencies": { - "@wdio/utils": "7.25.4" + "@wdio/utils": "7.33.0" }, "engines": { "node": ">=12.0.0" } }, "node_modules/webdriverio/node_modules/@wdio/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.25.4.tgz", - "integrity": "sha512-muvNmq48QZCvocctnbe0URq2FjJjUPIG4iLoeMmyF0AQgdbjaUkMkw3BHYNHVTbSOU9WMsr2z8alhj/I2H6NRQ==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", + "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", "dev": true, "dependencies": { "@types/node": "^18.0.0", @@ -28377,13 +30122,13 @@ } }, "node_modules/webdriverio/node_modules/@wdio/utils": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.25.4.tgz", - "integrity": "sha512-8iwQDk+foUqSzKZKfhLxjlCKOkfRJPNHaezQoevNgnrTq/t0ek+ldZCATezb9B8jprAuP4mgS9xi22akc6RkzA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", + "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", "dev": true, "dependencies": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", "p-iteration": "^1.1.8" }, "engines": { @@ -28414,6 +30159,33 @@ "balanced-match": "^1.0.0" } }, + "node_modules/webdriverio/node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/webdriverio/node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/webdriverio/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -28448,10 +30220,40 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/webdriverio/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/webdriverio/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webdriverio/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -28467,6 +30269,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/webdriverio/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webdriverio/node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/webdriverio/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -28476,6 +30315,31 @@ "node": ">=8" } }, + "node_modules/webdriverio/node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/webdriverio/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/webdriverio/node_modules/ky": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", @@ -28488,16 +30352,73 @@ "url": "https://github.com/sindresorhus/ky?sponsor=1" } }, + "node_modules/webdriverio/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/webdriverio/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", + "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webdriverio/node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webdriverio/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webdriverio/node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webdriverio/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/webdriverio/node_modules/supports-color": { @@ -28513,17 +30434,17 @@ } }, "node_modules/webdriverio/node_modules/webdriver": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.25.4.tgz", - "integrity": "sha512-6nVDwenh0bxbtUkHASz9B8T9mB531Fn1PcQjUGj2t5dolLPn6zuK1D7XYVX40hpn6r3SlYzcZnEBs4X0az5Txg==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz", + "integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==", "dev": true, "dependencies": { "@types/node": "^18.0.0", - "@wdio/config": "7.25.4", - "@wdio/logger": "7.19.0", - "@wdio/protocols": "7.22.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@wdio/config": "7.33.0", + "@wdio/logger": "7.26.0", + "@wdio/protocols": "7.27.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "got": "^11.0.2", "ky": "0.30.0", "lodash.merge": "^4.6.1" @@ -28539,34 +30460,34 @@ "dev": true }, "node_modules/webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", + "version": "5.92.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz", + "integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -28586,19 +30507,22 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dev": true, "dependencies": { + "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "html-escaper": "^2.0.2", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { @@ -28609,9 +30533,9 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -28620,55 +30544,6 @@ "node": ">=0.4.0" } }, - "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/webpack-bundle-analyzer/node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -28678,25 +30553,16 @@ "node": ">= 10" } }, - "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=10" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { @@ -28881,9 +30747,9 @@ } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -28892,10 +30758,10 @@ "node": ">=0.4.0" } }, - "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "node_modules/webpack/node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -28917,10 +30783,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -28969,18 +30841,18 @@ } }, "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^16.13.0 || >=18.0.0" } }, "node_modules/which-boxed-primitive": { @@ -29000,15 +30872,18 @@ } }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -29021,17 +30896,16 @@ "dev": true }, "node_modules/which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -29041,9 +30915,9 @@ } }, "node_modules/winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", "dev": true, "dependencies": { "logform": "^2.3.2", @@ -29069,9 +30943,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -29090,9 +30964,9 @@ "dev": true }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { "ansi-styles": "^4.0.0", @@ -29100,10 +30974,7 @@ "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/wrap-ansi-cjs": { @@ -29157,6 +31028,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -29190,6 +31073,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -29208,18 +31103,6 @@ "node": ">=4" } }, - "node_modules/write/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", @@ -29260,10 +31143,9 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yargs": { "version": "1.3.3", @@ -29307,6 +31189,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yargs-unparser/node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -29317,45 +31211,90 @@ } }, "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.1.3.tgz", + "integrity": "sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==", "dev": true, "dependencies": { "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" }, "engines": { "node": ">= 10" } }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/zip-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -29367,9 +31306,9 @@ } }, "node_modules/zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "dev": true, "funding": { "type": "github", @@ -29385,481 +31324,345 @@ }, "dependencies": { "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", - "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==" }, "@babel/core": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", - "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.6", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helpers": "^7.19.4", - "@babel/parser": "^7.19.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4", - "convert-source-map": "^1.7.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" } }, "@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", + "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", "dev": true, "requires": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" + "semver": "^6.3.1" } }, "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "requires": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } } }, "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "requires": { - "@babel/compat-data": "^7.20.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" } }, "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" } }, "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", "requires": { - "@babel/types": "^7.18.9" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "requires": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-module-transforms": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", - "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.19.4", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.6", - "@babel/types": "^7.19.4" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" } }, "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.24.7" } }, "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" } }, "@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" } }, "@babel/helper-simple-access": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", - "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "requires": { - "@babel/types": "^7.19.4" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", - "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "requires": { - "@babel/types": "^7.20.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==" }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==" }, "@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helpers": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", - "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "requires": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", - "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==" }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", - "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", - "requires": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.24.7" } }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" } }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "requires": {} }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -29902,11 +31705,27 @@ } }, "@babel/plugin-syntax-import-assertions": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", - "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "requires": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-json-strings": { @@ -29981,333 +31800,485 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/plugin-transform-arrow-functions": { + "@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" } }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "requires": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", - "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", "requires": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", + "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" } }, "@babel/plugin-transform-destructuring": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", - "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", + "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", "requires": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" } }, "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", - "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "requires": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", - "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", + "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", "requires": { - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-simple-access": "^7.19.4" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", - "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.19.1" + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "requires": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" } }, "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", + "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-transform-parameters": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz", - "integrity": "sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "requires": { + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", "requires": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-runtime": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", - "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "requires": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", + "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/preset-env": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", - "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", - "requires": { - "@babel/compat-data": "^7.19.4", - "@babel/helper-compilation-targets": "^7.19.3", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.19.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", + "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "requires": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -30317,101 +32288,120 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.19.4", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.19.4", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.4", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" } }, "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" } }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, "@babel/runtime": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", - "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "requires": { - "regenerator-runtime": "^0.13.10" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } }, @@ -30421,6 +32411,12 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, "@es-joy/jsdoccomment": { "version": "0.22.2", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.22.2.tgz", @@ -30462,9 +32458,9 @@ } }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -30623,12 +32619,6 @@ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, "ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -30652,15 +32642,6 @@ "strip-ansi": "^7.0.1" } }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -30777,68 +32758,56 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@ljharb/through": { - "version": "2.3.12", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.12.tgz", - "integrity": "sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", "dev": true, "requires": { - "call-bind": "^1.0.5" + "call-bind": "^1.0.7" } }, "@nicolo-ribaudo/eslint-scope-5-internals": { @@ -30857,39 +32826,28 @@ "dev": true }, "@percy/appium-app": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@percy/appium-app/-/appium-app-2.0.3.tgz", - "integrity": "sha512-6INeUJSyK2LzWV4Cc9bszNqKr3/NLcjFelUC2grjPnm6+jLA29inBF4ZE3PeTfLeCSw/0jyCGWV5fr9AyxtzCA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@percy/appium-app/-/appium-app-2.0.6.tgz", + "integrity": "sha512-0NT8xgaq4UOhcqVc4H3D440M7H5Zko8mDpY5j30TRpjOQ3ctLPJalmUVKOCFv4rSzjd2LmyE2F9KXTPA3zqQsw==", "dev": true, "requires": { - "@percy/sdk-utils": "^1.27.0-beta.0", + "@percy/sdk-utils": "^1.28.2", "tmp": "^0.2.1" - }, - "dependencies": { - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - } } }, "@percy/sdk-utils": { - "version": "1.27.7", - "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.27.7.tgz", - "integrity": "sha512-E21dIEQ9wwGDno41FdMDYf6jJow5scbWGClqKE/ptB+950W4UF5C4hxhVVQoEJxDdLE/Gy/8ZJR7upvPHShWDg==", + "version": "1.28.7", + "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.7.tgz", + "integrity": "sha512-LIhfHnkcS0fyIdo3gvKn7rwodZjbEtyLkgiDRSRulcBOatI2mhn2Bh269sXXiiFTyAW2BDQjyE3DWc4hkGbsbQ==", "dev": true }, "@percy/selenium-webdriver": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@percy/selenium-webdriver/-/selenium-webdriver-2.0.3.tgz", - "integrity": "sha512-JfLJVRkwNfqVofe7iGKtoQbOcKSSj9t4pWFbSUk95JfwAA7b9/c+dlBsxgIRrdrMYzLRjnJkYAFSZkJ4F4A19A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@percy/selenium-webdriver/-/selenium-webdriver-2.0.5.tgz", + "integrity": "sha512-bNj52xQm02dY872loFa+8OwyuGcdYHYvCKflmSEsF9EDRiSDj0Wr+XP+DDIgDAl9xXschA7OOdXCLTWV4zEQWA==", "dev": true, "requires": { - "@percy/sdk-utils": "^1.27.2", + "@percy/sdk-utils": "^1.28.0", "node-request-interceptor": "^0.6.3" } }, @@ -30901,11 +32859,22 @@ "optional": true }, "@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, + "@promptbook/utils": { + "version": "0.50.0-10", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.50.0-10.tgz", + "integrity": "sha512-Z94YoY/wcZb5m1QoXgmIC1rVeDguGK5bWmUTYdWCqh/LHVifRdJ1C+tBzS0h+HMOD0XzMjZhBQ/mBgTZ/QNW/g==", + "dev": true, + "requires": { + "moment": "2.30.1", + "prettier": "2.8.1", + "spacetrim": "0.11.25" + } + }, "@puppeteer/browsers": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", @@ -30921,26 +32890,13 @@ "yargs": "17.7.2" }, "dependencies": { - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "ms": "2.1.2" } }, "yargs": { @@ -30967,15 +32923,15 @@ "dev": true }, "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true }, "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -31008,18 +32964,18 @@ "dev": true }, "@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "requires": { - "defer-to-connect": "^2.0.0" + "defer-to-connect": "^2.0.1" } }, "@tootallnate/once": { @@ -31037,21 +32993,21 @@ "dev": true }, "@types/aria-query": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, "@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "dev": true, "requires": { "@types/http-cache-semantics": "*", - "@types/keyv": "*", + "@types/keyv": "^3.1.4", "@types/node": "*", - "@types/responselike": "*" + "@types/responselike": "^1.0.0" } }, "@types/cookie": { @@ -31061,27 +33017,27 @@ "dev": true }, "@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dev": true, "requires": { "@types/node": "*" } }, "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, "requires": { "@types/ms": "*" } }, "@types/eslint": { - "version": "8.4.9", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", - "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, "requires": { "@types/estree": "*", @@ -31089,9 +33045,9 @@ } }, "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "requires": { "@types/eslint": "*", @@ -31099,9 +33055,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "@types/expect": { @@ -31111,9 +33067,9 @@ "dev": true }, "@types/extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", - "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.4.tgz", + "integrity": "sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==", "dev": true }, "@types/gitconfiglocal": { @@ -31122,19 +33078,23 @@ "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "@types/github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==", - "dev": true + "@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dev": true, + "requires": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } }, "@types/hast": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", - "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", "dev": true, "requires": { - "@types/unist": "*" + "@types/unist": "^2" } }, "@types/http-cache-semantics": { @@ -31168,9 +33128,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/json5": { @@ -31180,23 +33140,29 @@ "dev": true }, "@types/keyv": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-4.2.0.tgz", - "integrity": "sha512-xoBtGl5R9jeKUhc8ZqeYaRDx04qqJ10yhhXYGmJ4Jr8qKpvMsDQQrNUvF/wUJ4klOtmJeJM+p2Xo3zp9uaC3tw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", "dev": true, "requires": { - "keyv": "*" + "@types/node": "*" } }, "@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "dev": true, "requires": { - "@types/unist": "*" + "@types/unist": "^2" } }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, "@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -31204,30 +33170,36 @@ "dev": true }, "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, "@types/node": { - "version": "20.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz", - "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==", + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, "requires": { "undici-types": "~5.26.4" } }, "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "requires": { "@types/node": "*" @@ -31240,9 +33212,9 @@ "dev": true }, "@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", "dev": true }, "@types/triple-beam": { @@ -31252,21 +33224,21 @@ "dev": true }, "@types/ua-parser-js": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", - "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", "dev": true }, "@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "dev": true }, "@types/vinyl": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", - "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", "dev": true, "requires": { "@types/expect": "^1.20.4", @@ -31274,9 +33246,9 @@ } }, "@types/which": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", - "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true }, "@types/ws": { @@ -31304,9 +33276,9 @@ "dev": true }, "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "requires": { @@ -31314,17 +33286,17 @@ } }, "@videojs/http-streaming": { - "version": "2.14.3", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz", - "integrity": "sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.3.tgz", + "integrity": "sha512-91CJv5PnFBzNBvyEjt+9cPzTK/xoVixARj2g7ZAvItA+5bx8VKdk5RxCz/PP2kdzz9W+NiDUMPkdmTsosmy69Q==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "3.0.5", "aes-decrypter": "3.1.3", "global": "^4.4.0", - "m3u8-parser": "4.7.1", - "mpd-parser": "0.21.1", + "m3u8-parser": "4.8.0", + "mpd-parser": "^0.22.1", "mux.js": "6.0.1", "video.js": "^6 || ^7" } @@ -31352,138 +33324,89 @@ } }, "@vitest/snapshot": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.1.tgz", - "integrity": "sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", "dev": true, "requires": { "magic-string": "^0.30.5", "pathe": "^1.1.1", "pretty-format": "^29.7.0" - }, - "dependencies": { - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - } } }, "@vue/compiler-core": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", - "integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", "dev": true, "optional": true, "requires": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.41", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", + "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } + "source-map-js": "^1.2.0" } }, "@vue/compiler-dom": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz", - "integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", "dev": true, "optional": true, "requires": { - "@vue/compiler-core": "3.2.41", - "@vue/shared": "3.2.41" + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" } }, "@vue/compiler-sfc": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz", - "integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", "dev": true, "optional": true, "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.41", - "@vue/compiler-dom": "3.2.41", - "@vue/compiler-ssr": "3.2.41", - "@vue/reactivity-transform": "3.2.41", - "@vue/shared": "3.2.41", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "@vue/compiler-ssr": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz", - "integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", "dev": true, "optional": true, "requires": { - "@vue/compiler-dom": "3.2.41", - "@vue/shared": "3.2.41" - } - }, - "@vue/reactivity-transform": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz", - "integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==", - "dev": true, - "optional": true, - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.41", - "@vue/shared": "3.2.41", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" } }, "@vue/shared": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz", - "integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true, "optional": true }, "@wdio/browserstack-service": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.29.1.tgz", - "integrity": "sha512-dLEJcdVF0Cu+2REByVOfLUzx9FvMias1VsxSCZpKXeIAGAIWBBdNdooK6Vdc9QdS36S5v/mk0/rTTQhYn4nWjQ==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.38.2.tgz", + "integrity": "sha512-dBvTK97deVbyDskCRdcQ47xuR7QYx3mqNFJUZLWBitwFV/DU5YIpCbGlySLc4gkO4//Zn1A3Gh/TOGWZrigcCQ==", "dev": true, "requires": { "@percy/appium-app": "^2.0.1", "@percy/selenium-webdriver": "^2.0.3", "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "8.28.0", - "@wdio/reporter": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "browserstack-local": "^1.5.1", "chalk": "^5.3.0", "csv-writer": "^1.6.0", @@ -31492,80 +33415,97 @@ "gitconfiglocal": "^2.1.0", "got": "^12.6.1", "uuid": "^9.0.0", - "webdriverio": "8.29.1", + "webdriverio": "8.38.2", "winston-transport": "^4.5.0", - "yauzl": "^2.10.0" + "yauzl": "^3.0.0" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "optional": true, - "peer": true, "requires": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { - "defer-to-connect": "^2.0.1" + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", - "dev": true, - "optional": true, - "peer": true - }, "archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "requires": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" } }, "archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "requires": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "async": { @@ -31583,27 +33523,22 @@ "balanced-match": "^1.0.0" } }, - "cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true - }, - "cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true + }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -31611,9 +33546,9 @@ "dev": true }, "chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -31625,94 +33560,241 @@ } }, "compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "requires": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" } }, "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "requires": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, "dependencies": { - "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, "optional": true, "peer": true, "requires": { - "isexe": "^3.1.1" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4" + } + }, + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } } } }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - } + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", @@ -31722,85 +33804,38 @@ "optional": true, "peer": true }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, - "got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - } - }, - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "optional": true, - "peer": true + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true }, "lighthouse-logger": { "version": "2.0.1", @@ -31812,43 +33847,18 @@ "requires": { "debug": "^2.6.9", "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } } }, - "lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true }, - "mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true - }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -31871,18 +33881,6 @@ "whatwg-url": "^5.0.0" } }, - "normalize-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", - "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", - "dev": true - }, - "p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true - }, "proxy-agent": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", @@ -31899,71 +33897,71 @@ "socks-proxy-agent": "^8.0.1" }, "dependencies": { - "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "ms": "2.1.2" } }, - "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "requires": { - "lowercase-keys": "^3.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, "serialize-error": { @@ -31975,15 +33973,13 @@ "type-fest": "^2.12.2" } }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "type-fest": { @@ -31993,40 +33989,35 @@ "dev": true }, "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "optional": true, "peer": true }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true - }, "webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -32035,81 +34026,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "webdriver": "8.38.2" } }, "ws": { @@ -32135,32 +34052,32 @@ } }, "zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "requires": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" } } } }, "@wdio/cli": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.29.1.tgz", - "integrity": "sha512-WWRTf0g0O+ovTTvS1kEhZ/svX32M7jERuuMF1MaldKCi7rZwHsQqOyJD+fO1UDjuxqS96LHSGsZn0auwUfCTXA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.38.2.tgz", + "integrity": "sha512-p9y6jxmpmw53OoB9v/uTLwMetmz7Q0K7NewdVONgmeTY/ERpkU15qL3fMw1rXb+E+vrV8dlce4srnXroec6SFA==", "dev": true, "requires": { "@types/node": "^20.1.1", "@vitest/snapshot": "^1.2.1", - "@wdio/config": "8.29.1", - "@wdio/globals": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/globals": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", @@ -32173,38 +34090,47 @@ "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "read-pkg-up": "^10.0.0", + "read-pkg-up": "10.0.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.29.1", + "webdriverio": "8.38.2", "yargs": "^17.7.2" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "optional": true, - "peer": true, "requires": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "yargs": { "version": "17.7.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, - "optional": true, - "peer": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -32217,41 +34143,68 @@ } } }, - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "optional": true, - "peer": true + "requires": { + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } }, "archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "requires": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" } }, "archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "requires": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + } } }, "async": { @@ -32269,6 +34222,22 @@ "balanced-match": "^1.0.0" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true + }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -32276,9 +34245,9 @@ "dev": true }, "chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -32290,94 +34259,266 @@ } }, "compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "requires": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + } } }, "crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" } }, "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "requires": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, "dependencies": { - "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4" + } + }, + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "optional": true, "peer": true, "requires": { - "isexe": "^3.1.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" } } } }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - } + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", @@ -32404,96 +34545,45 @@ "strip-final-newline": "^3.0.0" } }, - "find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "requires": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - } - }, "get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" } - } - } - }, - "hosted-git-info": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", - "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", - "dev": true, - "requires": { - "lru-cache": "^10.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, "is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true }, - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "optional": true, - "peer": true - }, - "json-parse-even-better-errors": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", - "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", - "dev": true - }, "lighthouse-logger": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", @@ -32504,36 +34594,14 @@ "requires": { "debug": "^2.6.9", "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } } }, - "lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true }, - "locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "requires": { - "p-locate": "^6.0.0" - } - }, "mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -32541,9 +34609,9 @@ "dev": true }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -32566,22 +34634,10 @@ "whatwg-url": "^5.0.0" } }, - "normalize-package-data": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", - "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", - "dev": true, - "requires": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - } - }, "npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "requires": { "path-key": "^4.0.0" @@ -32596,51 +34652,6 @@ "mimic-fn": "^4.0.0" } }, - "p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "requires": { - "p-limit": "^4.0.0" - } - }, - "parse-json": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", - "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.21.4", - "error-ex": "^1.3.2", - "json-parse-even-better-errors": "^3.0.0", - "lines-and-columns": "^2.0.3", - "type-fest": "^3.8.0" - }, - "dependencies": { - "type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", - "dev": true - } - } - }, - "path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true - }, "path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -32663,100 +34674,71 @@ "socks-proxy-agent": "^8.0.1" }, "dependencies": { - "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { - "agent-base": "^7.0.2", - "debug": "4" + "ms": "2.1.2" } }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" - } - }, - "read-pkg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", - "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^6.0.0", - "parse-json": "^7.0.0", - "type-fest": "^4.2.0" - } - }, - "read-pkg-up": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", - "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", - "dev": true, - "requires": { - "find-up": "^6.3.0", - "read-pkg": "^8.1.0", - "type-fest": "^4.2.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, "serialize-error": { @@ -32766,14 +34748,6 @@ "dev": true, "requires": { "type-fest": "^2.12.2" - }, - "dependencies": { - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true - } } }, "signal-exit": { @@ -32782,60 +34756,51 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "type-fest": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", - "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true }, "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "optional": true, "peer": true }, "webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -32844,96 +34809,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - } + "webdriver": "8.38.2" } }, "ws": { @@ -32958,33 +34834,27 @@ "yargs-parser": "^21.1.1" } }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true - }, "zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "requires": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" } } } }, "@wdio/concise-reporter": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.29.1.tgz", - "integrity": "sha512-dUhClWeq1naL1Qa1nSMDeH8aCVViOKiEzhBhQjgrMOz1Mh3l6O/woqbK2iKDVZDRhfGghtGcV0vpoEUvt8ZKOA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.38.2.tgz", + "integrity": "sha512-wE36By4Z9iCtRzihpYrmCehsmNc8t3gHviBsUxV4tmYh/SQr+WX/dysWnojer6KWIJ2rT0rOzyQPmrwhdFKAFg==", "dev": true, "requires": { - "@wdio/reporter": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.0.1", "pretty-ms": "^7.0.1" }, @@ -32998,125 +34868,124 @@ } }, "@wdio/config": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.29.1.tgz", - "integrity": "sha512-zNUac4lM429HDKAitO+fdlwUH1ACQU8lww+DNVgUyuEb86xgVdTqHeiJr/3kOMJAq9IATeE7mDtYyyn6HPm1JA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.38.2.tgz", + "integrity": "sha512-xlnapTr1vOA0h5HsHTIqj47729FbG3WjxmgHweDEQvcT4C1g9l+WKf+N3FM7DNNoIsAqxKi6rOHG02rJADQJtw==", "dev": true, "requires": { - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.0.0", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true - }, - "glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } } }, "@wdio/globals": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.29.1.tgz", - "integrity": "sha512-F+fPnX75f44/crZDfQ2FYSino/IMIdbnQGLIkaH0VnoljVJIHuxnX4y5Zqr4yRgurL9DsZaH22cLHrPXaHUhPg==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.38.2.tgz", + "integrity": "sha512-iIrUF1EODfHLh3V/CSNCqbNNxUTe3ND+c86zDjzJcPFjawLX1plvAApsU/eDmtsFShcOS2KHbfSjiydFoqQG1Q==", "dev": true, "requires": { - "expect-webdriverio": "^4.9.3", - "webdriverio": "8.29.1" + "expect-webdriverio": "^4.11.2", + "webdriverio": "8.38.2" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, "optional": true, - "peer": true, "requires": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } } }, - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "optional": true, - "peer": true + "requires": { + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } + } }, "archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "optional": true, "requires": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" } }, "archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "optional": true, "requires": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "async": { @@ -33136,10 +35005,28 @@ "balanced-match": "^1.0.0" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "optional": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "optional": true + }, "chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -33151,96 +35038,245 @@ } }, "compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "optional": true, "requires": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "optional": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" } }, "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "optional": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "requires": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, "dependencies": { - "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4" + } + }, + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "optional": true, "peer": true, "requires": { - "isexe": "^3.1.1" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } } } }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - } + "optional": true }, "escape-string-regexp": { "version": "4.0.0", @@ -33250,52 +35286,42 @@ "optional": true, "peer": true }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "requires": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true } } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "lighthouse-logger": { "version": "2.0.1", @@ -33307,19 +35333,6 @@ "requires": { "debug": "^2.6.9", "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } } }, "lru-cache": { @@ -33330,9 +35343,9 @@ "optional": true }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "optional": true, "requires": { @@ -33374,66 +35387,78 @@ "socks-proxy-agent": "^8.0.1" }, "dependencies": { - "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "optional": true, - "requires": { - "debug": "^4.3.4" - } - }, - "http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "ms": "2.1.2" } }, - "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } + "optional": true } } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "optional": true, - "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } } }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "optional": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, "serialize-error": { @@ -33446,16 +35471,14 @@ "type-fest": "^2.12.2" } }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "optional": true, "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "type-fest": { @@ -33466,43 +35489,36 @@ "optional": true }, "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "optional": true, "peer": true }, "webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "optional": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -33511,88 +35527,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "optional": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true, - "optional": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "optional": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "webdriver": "8.38.2" } }, "ws": { @@ -33620,39 +35555,39 @@ } }, "zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "optional": true, "requires": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" } } } }, "@wdio/local-runner": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.29.1.tgz", - "integrity": "sha512-Z3QAgxe1uQ97C7NS1CdMhgmHaLu/sbb47HTbw1yuuLk+SwsBIQGhNpTSA18QVRSUXq70G3bFvjACwqyap1IEQg==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.38.2.tgz", + "integrity": "sha512-syW+R5VUHJ3GBkQGFcNYe6MYwWRgklc9W7A83xQDTvKWFNHCetLvc8AtKZ54vs8MItBejjU+Oh94ZNbNX1pBcg==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", + "@wdio/logger": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/runner": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/runner": "8.38.2", + "@wdio/types": "8.38.2", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" } }, "@wdio/logger": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", - "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.38.0.tgz", + "integrity": "sha512-kcHL86RmNbcQP+Gq/vQUGlArfU6IIcbbnNp32rRIraitomZow+iEoc519rdQmSVusDozMS5DZthkgDdxK+vz6Q==", "dev": true, "requires": { "chalk": "^5.1.2", @@ -33661,47 +35596,32 @@ "strip-ansi": "^7.1.0" }, "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } } } }, "@wdio/mocha-framework": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.29.1.tgz", - "integrity": "sha512-R9dKMNqWgtUvZo33ORjUQV8Z/WLX5h/pg9u/xIvZSGXuNSw1h+5DWF6UiNFscxBFblL9UvBi6V9ila2LHgE4ew==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.38.2.tgz", + "integrity": "sha512-qJmRL5E6/ypjCUACH4hvCAAmTdU4YUrUlp9o/IKvTIAHMnZPE0/HgUFixCeu8Mop+rdzTPVBrbqxpRDdSnraYA==", "dev": true, "requires": { "@types/mocha": "^10.0.0", "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "mocha": "^10.0.0" } }, "@wdio/protocols": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz", - "integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.38.0.tgz", + "integrity": "sha512-7BPi7aXwUtnXZPeWJRmnCNFjyDvGrXlBmN9D4Pi58nILkyjVRQKEY9/qv/pcdyB0cvmIvw++Kl/1Lg+RxG++UA==", "dev": true }, "@wdio/repl": { @@ -33714,91 +35634,123 @@ } }, "@wdio/reporter": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.29.1.tgz", - "integrity": "sha512-LZeYHC+HHJRYiFH9odaotDazZh0zNhu4mTuL/T/e3c/Q3oPSQjLvfQYhB3Ece1QA9PKjP1VPmr+g9CvC0lMixA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.38.2.tgz", + "integrity": "sha512-R78UdAtAnkaV22NYlCCcbPPhmYweiDURiw64LYhlVIQrKNuXUQcafR2kRlWKy31rZc9thSLs5LzrZDReENUlFQ==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", "diff": "^5.0.0", "object-inspect": "^1.12.0" } }, "@wdio/runner": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.29.1.tgz", - "integrity": "sha512-MvYFf4RgRmzxjAzy6nxvaDG1ycBRvoz772fT06csjxuaVYm57s8mlB8X+U1UQMx/IzujAb53fSeAmNcyU3FNEA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.38.2.tgz", + "integrity": "sha512-5lPnKSX2BBLI2AbYW+hoGPiEUAJXj8F8I6NC2LaBVzf1CLN+w2HWZ7lUiqS14XT0b5/hlSUX6+JYwUXlDbpuuw==", "dev": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/globals": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "deepmerge-ts": "^5.0.0", - "expect-webdriverio": "^4.9.3", - "gaze": "^1.1.2", - "webdriver": "8.29.1", - "webdriverio": "8.29.1" + "@types/node": "^20.11.28", + "@wdio/config": "8.38.2", + "@wdio/globals": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "deepmerge-ts": "^5.1.0", + "expect-webdriverio": "^4.12.0", + "gaze": "^1.1.3", + "webdriver": "8.38.2", + "webdriverio": "8.38.2" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, - "optional": true, - "peer": true, "requires": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "optional": true, - "peer": true + "requires": { + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } }, "archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "requires": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" } }, "archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "requires": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "async": { @@ -33816,10 +35768,26 @@ "balanced-match": "^1.0.0" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true + }, "chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -33831,94 +35799,241 @@ } }, "compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "requires": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" } }, "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "requires": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, "dependencies": { - "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4" + } + }, + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "optional": true, "peer": true, "requires": { - "isexe": "^3.1.1" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } } } }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - } + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true }, "escape-string-regexp": { "version": "4.0.0", @@ -33928,50 +36043,38 @@ "optional": true, "peer": true }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "optional": true, - "peer": true + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true }, "lighthouse-logger": { "version": "2.0.1", @@ -33983,19 +36086,6 @@ "requires": { "debug": "^2.6.9", "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } } }, "lru-cache": { @@ -34005,9 +36095,9 @@ "dev": true }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -34046,62 +36136,71 @@ "socks-proxy-agent": "^8.0.1" }, "dependencies": { - "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "ms": "2.1.2" } }, - "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, "serialize-error": { @@ -34113,15 +36212,13 @@ "type-fest": "^2.12.2" } }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "type-fest": { @@ -34131,42 +36228,35 @@ "dev": true }, "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "optional": true, "peer": true }, "webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -34175,81 +36265,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "webdriver": "8.38.2" } }, "ws": { @@ -34275,26 +36291,26 @@ } }, "zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "requires": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" } } } }, "@wdio/spec-reporter": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.29.1.tgz", - "integrity": "sha512-tuDHihrTjCxFCbSjT0jMvAarLA1MtatnCnhv0vguu3ZWXELR1uESX2KzBmpJ+chGZz3oCcKszT8HOr6Pg2a1QA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.38.2.tgz", + "integrity": "sha512-Dntk+lmrp+0I3HRRWkkXED+smshvgsW5cdLKwJhEJ1liI48MdBpdNGf9IHTVckE6nfxcWDyFI4icD9qYv/5bFA==", "dev": true, "requires": { - "@wdio/reporter": "8.29.1", - "@wdio/types": "8.29.1", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.1.2", "easy-table": "^1.2.0", "pretty-ms": "^7.0.0" @@ -34309,193 +36325,185 @@ } }, "@wdio/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.29.1.tgz", - "integrity": "sha512-rZYzu+sK8zY1PjCEWxNu4ELJPYKDZRn7HFcYNgR122ylHygfldwkb5TioI6Pn311hQH/S+663KEeoq//Jb0f8A==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.38.2.tgz", + "integrity": "sha512-+wj1c1OSLdnN4WO5b44Ih4263dTl/eSwMGSI4/pCgIyXIuYQH38JQ+6WRa+c8vJEskUzboq2cSgEQumVZ39ozQ==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-Dm91DKL/ZKeZ2QogWT8Twv0p+slEgKyB/5x9/kcCG0Q2nNa+tZedTjOhryzrsPiWc+jTSBmjGE4katRXpJRFJg==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.38.2.tgz", + "integrity": "sha512-y5AnBwsGcu/XuCBGCgKmlvKdwEIFyzLA+Cr+denySxY3jbWDONtPUcGaVdFALwsIa5jcIjcATqGmZcCPGnkd7g==", "dev": true, "requires": { "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.3.5", - "geckodriver": "^4.2.0", + "edgedriver": "^5.5.0", + "geckodriver": "^4.3.1", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", "locate-app": "^2.1.0", "safaridriver": "^0.1.0", "split2": "^4.2.0", "wait-port": "^1.0.4" - }, - "dependencies": { - "decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true - } } }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, "@xmldom/xmldom": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.8.tgz", - "integrity": "sha512-PrJx38EfpitFhwmILRl37jAdBlsww6AZ6rRVK4QS7T7RHLhX7mSs647sTmgr9GIxe3qjXdesmomEgbgaokrVFg==", + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "dev": true }, "@xtuc/ieee754": { @@ -34510,12 +36518,27 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zip.js/zip.js": { + "version": "2.7.45", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.45.tgz", + "integrity": "sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow==", + "dev": true + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -34539,9 +36562,9 @@ "requires": {} }, "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true }, "aes-decrypter": { @@ -34653,9 +36676,9 @@ "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==" }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -34672,36 +36695,49 @@ } }, "archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "requires": { "archiver-utils": "^2.1.0", - "async": "^3.2.3", + "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", + "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" }, "dependencies": { "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } } } }, @@ -34721,6 +36757,22 @@ "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "archy": { @@ -34738,12 +36790,12 @@ } }, "aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "requires": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "arr-diff": { @@ -34794,6 +36846,16 @@ "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", "dev": true }, + "array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + } + }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -34818,15 +36880,16 @@ "dev": true }, "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, @@ -34894,18 +36957,60 @@ "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", "dev": true }, + "array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + } + }, "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" } }, + "arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + } + }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -34916,15 +37021,16 @@ } }, "assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "dev": true, "requires": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, "assert-plus": { @@ -34954,9 +37060,9 @@ }, "dependencies": { "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true } } @@ -34986,9 +37092,9 @@ } }, "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", "dev": true }, "async-exit-hook": { @@ -35019,10 +37125,13 @@ "dev": true }, "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } }, "aws-sign2": { "version": "0.7.0", @@ -35031,15 +37140,15 @@ "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, "b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, "babel-code-frame": { @@ -35128,6 +37237,12 @@ "source-map": "^0.5.7" }, "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -35186,9 +37301,9 @@ } }, "babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", @@ -35220,30 +37335,30 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" + "@babel/helper-define-polyfill-provider": "^0.6.2" } }, "babel-register": { @@ -35266,15 +37381,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "dev": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } } } }, @@ -35410,6 +37516,52 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "requires": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "bare-os": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", + "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", + "dev": true, + "optional": true + }, + "bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "requires": { + "bare-os": "^2.1.0" + } + }, + "bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "requires": { + "streamx": "^2.18.0" + } + }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", @@ -35466,9 +37618,9 @@ } }, "basic-ftp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", - "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true }, "batch": { @@ -35493,9 +37645,9 @@ "dev": true }, "big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", "dev": true }, "big.js": { @@ -35515,9 +37667,9 @@ } }, "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true }, "binaryextensions": { @@ -35548,9 +37700,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -35561,9 +37713,9 @@ } }, "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "body": { "version": "5.1.0", @@ -35652,12 +37804,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -35667,14 +37819,14 @@ "dev": true }, "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" } }, "browserstack": { @@ -35717,9 +37869,9 @@ } }, "browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.5.tgz", + "integrity": "sha512-jKne7yosrMcptj3hqxp36TP9k0ZW2sCqhyurX24rUL4G3eT7OLgv+CSQN8iq5dtkv5IK+g+v8fWvsiC/S9KxMg==", "dev": true, "requires": { "agent-base": "^6.0.2", @@ -35863,45 +38015,44 @@ } }, "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true }, "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "dependencies": { "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true } } }, "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -35923,9 +38074,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001429", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", - "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==" + "version": "1.0.30001633", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001633.tgz", + "integrity": "sha512-6sT0yf/z5jqf8tISAgpJDrmwOpLsrpnyCdD/lOZKvKkkJK4Dn0X5i7KF7THEZhOq+30bmhwBlNEaqPUiHiKtZg==" }, "caseless": { "version": "0.12.0", @@ -35940,18 +38091,18 @@ "dev": true }, "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "requires": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" } }, "chainsaw": { @@ -35998,15 +38149,18 @@ "dev": true }, "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "requires": { + "get-func-name": "^2.0.2" + } }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -36026,9 +38180,9 @@ "dev": true }, "chrome-launcher": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.1.tgz", - "integrity": "sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "dev": true, "requires": { "@types/node": "*", @@ -36046,18 +38200,16 @@ } }, "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true }, "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, - "optional": true, - "peer": true, "requires": { "mitt": "3.0.0" } @@ -36095,61 +38247,14 @@ "is-descriptor": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } } } @@ -36170,9 +38275,9 @@ "dev": true }, "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true }, "cliui": { @@ -36184,6 +38289,52 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } } }, "clone": { @@ -36205,6 +38356,14 @@ "dev": true, "requires": { "mimic-response": "^1.0.0" + }, + "dependencies": { + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + } } }, "clone-stats": { @@ -36286,9 +38445,9 @@ } }, "comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "dev": true }, "commander": { @@ -36310,15 +38469,15 @@ "dev": true }, "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true }, "compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "requires": { "buffer-crc32": "^0.2.13", @@ -36328,9 +38487,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -36468,9 +38627,9 @@ "dev": true }, "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "cookie": { "version": "0.6.0", @@ -36499,22 +38658,22 @@ } }, "core-js": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", - "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==" }, "core-js-compat": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", - "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", "requires": { - "browserslist": "^4.21.4" + "browserslist": "^4.23.0" } }, "core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==" + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==" }, "core-util-is": { "version": "1.0.3", @@ -36551,9 +38710,9 @@ "dev": true }, "crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "requires": { "crc-32": "^1.2.0", @@ -36561,9 +38720,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -36573,11 +38732,6 @@ } } }, - "criteo-direct-rsa-validate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", - "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" - }, "cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -36585,6 +38739,17 @@ "dev": true, "requires": { "node-fetch": "2.6.7" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "cross-spawn": { @@ -36596,6 +38761,23 @@ "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "crypto-js": { @@ -36666,13 +38848,13 @@ "dev": true }, "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dev": true, "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" } }, "dashdash": { @@ -36685,11 +38867,44 @@ } }, "data-uri-to-buffer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", - "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true }, + "data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, + "data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, + "data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, "date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -36709,10 +38924,16 @@ "dev": true, "optional": true }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "requires": { "ms": "2.1.2" } @@ -36740,9 +38961,9 @@ } }, "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true }, "decode-named-character-reference": { @@ -36778,35 +38999,38 @@ } }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "requires": { "type-detect": "^4.0.0" } }, "deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", - "object-is": "^1.1.4", + "object-is": "^1.1.5", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" + "which-typed-array": "^1.1.13" } }, "deep-is": { @@ -36816,9 +39040,9 @@ "dev": true }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, "deepmerge-ts": { @@ -36866,21 +39090,22 @@ "dev": true }, "define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "requires": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } @@ -36983,49 +39208,74 @@ "dev": true }, "devtools": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.25.4.tgz", - "integrity": "sha512-R6/S/dCqxoX4Y6PxIGM9JFAuSRZzUeV5r+CoE/frhmno6mTe7dEEgwkJlfit3LkKRoul8n4DsL2A3QtWOvq5IA==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.35.0.tgz", + "integrity": "sha512-7HMZMcJSCK/PaBCWVs4n4ZhtBNdUQj10iPwXvj/JDkqPreEXN/XW9GJAoMuLPFmCEKfxe+LrIbgs8ocGJ6rp/A==", "dev": true, "requires": { "@types/node": "^18.0.0", "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.25.4", - "@wdio/logger": "7.19.0", - "@wdio/protocols": "7.22.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@wdio/config": "7.33.0", + "@wdio/logger": "7.26.0", + "@wdio/protocols": "7.27.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "chrome-launcher": "^0.15.0", "edge-paths": "^2.1.0", - "puppeteer-core": "^13.1.3", + "puppeteer-core": "13.1.3", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", "uuid": "^9.0.0" }, "dependencies": { + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "version": "18.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", + "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, "@wdio/config": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.25.4.tgz", - "integrity": "sha512-vb0emDtD9FbFh/yqW6oNdo2iuhQp8XKj6GX9fyy9v4wZgg3B0HPMVJxhIfcoHz7LMBWlHSo9YdvhFI5EQHRLBA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", + "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", "dev": true, "requires": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@types/glob": "^8.1.0", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "deepmerge": "^4.0.0", "glob": "^8.0.3" } }, "@wdio/logger": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.19.0.tgz", - "integrity": "sha512-xR7SN/kGei1QJD1aagzxs3KMuzNxdT/7LYYx+lt6BII49+fqL/SO+5X0FDCZD0Ds93AuQvvz9eGyzrBI2FFXmQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", + "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -37035,15 +39285,15 @@ } }, "@wdio/protocols": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.22.0.tgz", - "integrity": "sha512-8EXRR+Ymdwousm/VGtW3H1hwxZ/1g1H99A1lF0U4GuJ5cFWHCd0IVE5H31Z52i8ZruouW8jueMkGZPSo2IIUSQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", + "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", "dev": true }, "@wdio/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.25.4.tgz", - "integrity": "sha512-muvNmq48QZCvocctnbe0URq2FjJjUPIG4iLoeMmyF0AQgdbjaUkMkw3BHYNHVTbSOU9WMsr2z8alhj/I2H6NRQ==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", + "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", "dev": true, "requires": { "@types/node": "^18.0.0", @@ -37051,13 +39301,13 @@ } }, "@wdio/utils": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.25.4.tgz", - "integrity": "sha512-8iwQDk+foUqSzKZKfhLxjlCKOkfRJPNHaezQoevNgnrTq/t0ek+ldZCATezb9B8jprAuP4mgS9xi22akc6RkzA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", + "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", "dev": true, "requires": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", "p-iteration": "^1.1.8" } }, @@ -37079,6 +39329,27 @@ "balanced-match": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true + }, + "cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -37104,10 +39375,44 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.948846", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", + "integrity": "sha512-5fGyt9xmMqUl2VI7+rnUkKCiAQIpLns8sfQtTENy5L70ktbNw0Z3TFJ1JoFNYdx/jffz4YXU45VF75wKZD7sZQ==", + "dev": true + }, + "edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "dev": true, + "requires": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -37117,21 +39422,142 @@ "once": "^1.3.0" } }, + "got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true + }, + "puppeteer-core": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.1.3.tgz", + "integrity": "sha512-96pzvVBzq5lUGt3L/QrIH3mxn3NfZylHeusNhq06xBAHPI0Upc0SC/9u7tXjL0oRnmcExeVRJivr1lj7Ah/yDQ==", + "dev": true, + "requires": { + "debug": "4.3.2", + "devtools-protocol": "0.0.948846", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.7", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -37141,24 +39567,59 @@ "has-flag": "^4.0.0" } }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "ua-parser-js": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", - "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "requires": {} } } }, "devtools-protocol": { - "version": "0.0.1061995", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1061995.tgz", - "integrity": "sha512-pKZZWTjWa/IF4ENCg6GN8bu/AxSZgdhjSa26uc23wz38Blt2Tnm9icOPcSG3Cht55rMq35in1w3rWVPcZ60ArA==", + "version": "0.0.1260888", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz", + "integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==", "dev": true }, "di": { @@ -37168,9 +39629,9 @@ "dev": true }, "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, "diff-sequences": { @@ -37203,9 +39664,9 @@ } }, "documentation": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", - "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz", + "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==", "dev": true, "requires": { "@babel/core": "^7.18.10", @@ -37265,15 +39726,25 @@ } }, "chalk": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz", - "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, + "find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "requires": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + } + }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -37283,6 +39754,15 @@ "once": "^1.3.0" } }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -37292,19 +39772,138 @@ "argparse": "^2.0.1" } }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "requires": { + "p-locate": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "requires": { + "p-limit": "^4.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true + }, + "read-pkg": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "dev": true, + "requires": { + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" + } + }, + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yargs": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", - "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -37313,7 +39912,7 @@ "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" } } } @@ -37374,9 +39973,9 @@ } }, "dotenv": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", - "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true }, "dset": { @@ -37426,21 +40025,21 @@ } }, "duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "dev": true, "requires": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" + "stream-shift": "^1.0.2" }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -37495,128 +40094,57 @@ "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" + }, + "dependencies": { + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + } } }, "edge-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", - "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, "requires": { - "@types/which": "^1.3.2", + "@types/which": "^2.0.1", "which": "^2.0.2" - } - }, - "edgedriver": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.9.tgz", - "integrity": "sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==", - "dev": true, - "requires": { - "@wdio/logger": "^8.16.17", - "decamelize": "^6.0.0", - "edge-paths": "^3.0.5", - "node-fetch": "^3.3.2", - "unzipper": "^0.10.14", - "which": "^4.0.0" }, "dependencies": { - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", - "dev": true - }, - "data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true - }, - "decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", - "dev": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - }, - "unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "dev": true, - "requires": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^3.1.1" - }, - "dependencies": { - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true - } + "isexe": "^2.0.0" } } } }, + "edgedriver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.0.tgz", + "integrity": "sha512-IeJXEczG+DNYBIa9gFgVYTqrawlxmc9SUqUsWU2E98jOsO/amA7wzabKOS8Bwgr/3xWoyXCJ6yGFrbFKrilyyQ==", + "dev": true, + "requires": { + "@wdio/logger": "^8.28.0", + "@zip.js/zip.js": "^2.7.44", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "node-fetch": "^3.3.2", + "which": "^4.0.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -37632,9 +40160,9 @@ } }, "electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + "version": "1.4.802", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", + "integrity": "sha512-TnTMUATbgNdPXVSHsxvNVSG0uEd6cSZsANjm8c9HbvflZVVn1yTRcmVXYT1Ma95/ssB/Dcd30AHweH2TE+dNpA==" }, "emoji-regex": { "version": "8.0.0", @@ -37663,9 +40191,9 @@ } }, "engine.io": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", - "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -37676,7 +40204,7 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.2.1", "ws": "~8.11.0" }, "dependencies": { @@ -37689,15 +40217,15 @@ } }, "engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", "dev": true }, "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -37705,12 +40233,24 @@ } }, "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "requires": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "ent": { @@ -37753,66 +40293,122 @@ } }, "es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + } + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" } }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", "dev": true, "requires": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", + "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", "dev": true }, - "es-shim-unscopables": { + "es-object-atoms": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", "dev": true, "requires": { - "has": "^1.0.3" + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + } + }, + "es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" } }, "es-to-primitive": { @@ -37827,13 +40423,14 @@ } }, "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, @@ -37854,12 +40451,6 @@ "es6-symbol": "^3.1.1" } }, - "es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", - "dev": true - }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -37875,13 +40466,13 @@ } }, "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "dev": true, "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" + "d": "^1.0.2", + "ext": "^1.7.0" } }, "es6-weak-map": { @@ -37897,9 +40488,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==" }, "escape-html": { "version": "1.0.3", @@ -38079,9 +40670,9 @@ "dev": true }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -38094,12 +40685,18 @@ "dev": true }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "ansi-regex": "^5.0.1" } }, "strip-json-comments": { @@ -38133,13 +40730,14 @@ "requires": {} }, "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "requires": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" }, "dependencies": { "debug": { @@ -38154,9 +40752,9 @@ } }, "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "requires": { "debug": "^3.2.7" @@ -38184,33 +40782,37 @@ } }, "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "doctrine": { @@ -38221,12 +40823,6 @@ "requires": { "esutils": "^2.0.2" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true } } }, @@ -38253,13 +40849,10 @@ "dev": true }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true } } }, @@ -38278,9 +40871,9 @@ }, "dependencies": { "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true } } @@ -38335,6 +40928,18 @@ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + } + }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -38361,9 +40966,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -38450,6 +41055,12 @@ } } }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -38490,6 +41101,12 @@ "which": "^1.2.9" } }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -38570,61 +41187,14 @@ "is-extendable": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } }, "is-extendable": { @@ -38664,76 +41234,114 @@ } }, "expect-webdriverio": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.9.3.tgz", - "integrity": "sha512-ASHsFc/QaK5ipF4ct3e8hd3elm8wNXk/Qa3EemtYDmfUQ4uzwqDf75m/QFQpwVNCjEpkNP7Be/6X9kz7bN0P9Q==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.15.0.tgz", + "integrity": "sha512-CIBSLEhDmjZ7kKZq6ItBM7V1jLH/w4JCuKGu3WmR4FscOPvOnp9JN4Zi26SZGeQ73E0dy+YPUL6SIvTNoP/XdQ==", "dev": true, "requires": { - "@vitest/snapshot": "^1.2.1", - "@wdio/globals": "^8.27.0", - "@wdio/logger": "^8.24.12", + "@vitest/snapshot": "^1.2.2", + "@wdio/globals": "^8.29.3", + "@wdio/logger": "^8.28.0", "expect": "^29.7.0", "jest-matcher-utils": "^29.7.0", "lodash.isequal": "^4.5.0", - "webdriverio": "^8.27.0" + "webdriverio": "^8.29.3" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, "optional": true, - "peer": true, "requires": { "debug": "4.3.4", "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", "unbzip2-stream": "1.4.3", "yargs": "17.7.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } } }, - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "optional": true, - "peer": true + "requires": { + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } + } }, "archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "optional": true, "requires": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" } }, "archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "optional": true, "requires": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "async": { @@ -38753,10 +41361,28 @@ "balanced-match": "^1.0.0" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "optional": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "optional": true + }, "chrome-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", - "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", + "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", "dev": true, "optional": true, "peer": true, @@ -38768,96 +41394,245 @@ } }, "compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "optional": true, "requires": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" } }, "crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "optional": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" } }, "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "optional": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "peer": true, "requires": { - "node-fetch": "^2.6.11" + "ms": "2.0.0" } }, "devtools": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", - "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.38.2.tgz", + "integrity": "sha512-8b+naOPzYqzsiYtZZKYJnUnSqSOIg5orvna2SlWT2kFhkggbAJ1bbMzW7rps6onLfxp93wCNEIvngb9JuxwDsg==", "dev": true, "optional": true, "peer": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "chrome-launcher": "^1.0.0", "edge-paths": "^3.0.5", "import-meta-resolve": "^4.0.0", "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", + "ua-parser-js": "^1.0.37", "uuid": "^9.0.0", "which": "^4.0.0" }, "dependencies": { - "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, "optional": true, "peer": true, "requires": { - "isexe": "^3.1.1" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4" + } + }, + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true, + "peer": true + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" } } } }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - } + "optional": true }, "escape-string-regexp": { "version": "4.0.0", @@ -38867,52 +41642,42 @@ "optional": true, "peer": true }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "agent-base": "^7.0.2", + "debug": "4" }, "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "requires": { - "brace-expansion": "^2.0.1" + "ms": "2.1.2" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true } } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "lighthouse-logger": { "version": "2.0.1", @@ -38924,19 +41689,6 @@ "requires": { "debug": "^2.6.9", "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } } }, "lru-cache": { @@ -38947,9 +41699,9 @@ "optional": true }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "optional": true, "requires": { @@ -38991,66 +41743,78 @@ "socks-proxy-agent": "^8.0.1" }, "dependencies": { - "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "optional": true, - "requires": { - "debug": "^4.3.4" - } - }, - "http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "optional": true, "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "ms": "2.1.2" } }, - "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } + "optional": true } } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "optional": true, - "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", + "devtools-protocol": "0.0.1147663", "ws": "8.13.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } } }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "optional": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, "serialize-error": { @@ -39063,16 +41827,14 @@ "type-fest": "^2.12.2" } }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "optional": true, "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "safe-buffer": "~5.2.0" } }, "type-fest": { @@ -39083,43 +41845,36 @@ "optional": true }, "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", + "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", "dev": true, "optional": true, "peer": true }, "webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.38.2.tgz", + "integrity": "sha512-r09y5UfivyYh5JOzT2SpJJ1zDmQl/R4OTH12opUqkjvp21BibCQm/uu1mrxGy4lzSHljrvqSVrrcGI+6UA1O8w==", "dev": true, "optional": true, "requires": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", - "archiver": "^6.0.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "archiver": "^7.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1302984", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", "minimatch": "^9.0.0", @@ -39128,88 +41883,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "optional": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", - "dev": true, - "optional": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "optional": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "webdriver": "8.38.2" } }, "ws": { @@ -39237,15 +41911,15 @@ } }, "zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "optional": true, "requires": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" } } } @@ -39310,14 +41984,6 @@ "dev": true, "requires": { "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - } } }, "extend": { @@ -39352,6 +42018,17 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "extglob": { @@ -39416,6 +42093,16 @@ "requires": { "pump": "^3.0.0" } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } } } }, @@ -39502,20 +42189,29 @@ }, "dependencies": { "web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true } } }, "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + } } }, "file-entry-cache": { @@ -39564,9 +42260,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -39816,19 +42512,20 @@ "dev": true }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "flush-write-stream": { @@ -39878,9 +42575,9 @@ "dev": true }, "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.0.tgz", + "integrity": "sha512-CrWQNaEl1/6WeZoarcM9LHupTo3RpZO2Pdk1vktwzPiQTsJnAKJmm3TACKeG5UZbWDfaH2AbvYxzP96y0MT7fA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -39975,14 +42672,29 @@ "dev": true }, "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", + "integrity": "sha512-5rU898vl/Z948L+kkJedbmo/iltzmiF5bn/eEk0j/SgrPpI+Ydau9xlJPicV7Av2CHYBGz5LAlwTnBU80j1zPQ==", "dev": true, "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "jsonfile": "~1.0.1", + "mkdirp": "0.3.x", + "ncp": "~0.4.2", + "rimraf": "~2.2.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", + "dev": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", + "dev": true + } } }, "fs-mkdirp-stream": { @@ -40026,35 +42738,11 @@ "walk": "^2.3.9" }, "dependencies": { - "fs-extra": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", - "integrity": "sha512-5rU898vl/Z948L+kkJedbmo/iltzmiF5bn/eEk0j/SgrPpI+Ydau9xlJPicV7Av2CHYBGz5LAlwTnBU80j1zPQ==", - "dev": true, - "requires": { - "jsonfile": "~1.0.1", - "mkdirp": "0.3.x", - "ncp": "~0.4.2", - "rimraf": "~2.2.0" - } - }, - "jsonfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", - "integrity": "sha512-KbsDJNRfRPF5v49tMNf9sqyyGqGLBcz1v5kZT01kG5ns5mQSltwxCKVmUzVKtEinkUnTDtSrp6ngWpV7Xw0ZlA==", - "dev": true - }, "mkdirp": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", "dev": true - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "dev": true } } }, @@ -40065,9 +42753,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "optional": true }, "fstream": { @@ -40082,13 +42770,18 @@ "rimraf": "2" }, "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { - "minimist": "^1.2.6" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "rimraf": { @@ -40116,15 +42809,15 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" } }, "functional-red-black-tree": { @@ -40149,126 +42842,51 @@ } }, "geckodriver": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.1.tgz", - "integrity": "sha512-ol7JLsj55o5k+z7YzeSy2mdJROXMAxIa+uzr3A1yEMr5HISqQOTslE3ZeARcxR4jpAY3fxmHM+sq32qbe/eXfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.1.tgz", + "integrity": "sha512-nnAdIrwLkMcDu4BitWXF23pEMeZZ0Cj7HaWWFdSpeedBP9z6ft150JYiGO2mwzw6UiR823Znk1JeIf07RyzloA==", "dev": true, "requires": { - "@wdio/logger": "^8.24.12", + "@wdio/logger": "^8.28.0", + "@zip.js/zip.js": "^2.7.44", "decamelize": "^6.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "node-fetch": "^3.3.2", - "tar-fs": "^3.0.4", - "unzipper": "^0.10.14", + "tar-fs": "^3.0.6", "which": "^4.0.0" }, "dependencies": { "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { "debug": "^4.3.4" } }, - "data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true - }, - "decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "4" } }, - "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true - }, - "node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - }, "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "requires": { - "mkdirp-classic": "^0.5.2", + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", "pump": "^3.0.0", "tar-stream": "^3.1.5" } - }, - "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "unzipper": { - "version": "0.10.14", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", - "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", - "dev": true, - "requires": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "requires": { - "isexe": "^3.1.1" - } } } }, @@ -40284,16 +42902,17 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -40307,9 +42926,9 @@ "dev": true }, "get-port": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", - "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", "dev": true }, "get-stream": { @@ -40322,52 +42941,54 @@ } }, "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" } }, "get-uri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", - "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", "dev": true, "requires": { "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.0", + "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4", - "fs-extra": "^8.1.0" + "fs-extra": "^11.2.0" }, "dependencies": { + "data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true + }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true } } }, @@ -40403,9 +43024,9 @@ } }, "git-url-parse": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", - "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.1.tgz", + "integrity": "sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==", "dev": true, "requires": { "git-up": "^7.0.0" @@ -40435,17 +43056,36 @@ "dev": true }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "glob-parent": { @@ -40475,6 +43115,20 @@ "unique-stream": "^2.0.2" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -40800,6 +43454,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -40822,6 +43482,16 @@ "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", "dev": true }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + } + }, "globule": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", @@ -40876,28 +43546,36 @@ } }, "got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "dependencies": { + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + } } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "grapheme-splitter": { "version": "1.0.4", @@ -40930,6 +43608,20 @@ "vinyl": "^2.1.0" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -41324,12 +44016,6 @@ "ansi-wrap": "^0.1.0" } }, - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -41348,6 +44034,12 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -41433,17 +44125,6 @@ "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } } }, "eslint-utils": { @@ -41482,6 +44163,15 @@ "is-extendable": "^1.0.1" } }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -41508,6 +44198,20 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -41562,6 +44266,15 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } } } }, @@ -41571,6 +44284,12 @@ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -41581,14 +44300,11 @@ "type-check": "~0.3.2" } }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true }, "optionator": { "version": "0.8.3", @@ -41643,6 +44359,21 @@ "glob": "^7.1.3" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -41669,6 +44400,23 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + } + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -41706,15 +44454,6 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -41828,24 +44567,16 @@ "dev": true }, "gulp-replace": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", - "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.4.tgz", + "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", "dev": true, "requires": { - "@types/node": "^14.14.41", + "@types/node": "*", "@types/vinyl": "^2.0.4", "istextorbinary": "^3.0.0", "replacestream": "^4.0.3", "yargs-parser": ">=5.0.0-security.0" - }, - "dependencies": { - "@types/node": { - "version": "14.18.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", - "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", - "dev": true - } } }, "gulp-shell": { @@ -41991,6 +44722,12 @@ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -42285,13 +45022,13 @@ } }, "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "requires": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" @@ -42322,12 +45059,9 @@ } }, "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==" }, "has-ansi": { "version": "2.0.0", @@ -42367,17 +45101,17 @@ } }, "has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" } }, "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" }, "has-symbols": { "version": "1.0.3", @@ -42385,12 +45119,12 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "requires": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" } }, "has-value": { @@ -42452,56 +45186,117 @@ } }, "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "requires": { "function-bind": "^1.1.2" } }, - "hast-util-is-element": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", - "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", + "hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", "dev": true, "requires": { "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0" + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + } + }, + "hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0" + } + }, + "hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" } }, "hast-util-sanitize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.0.0.tgz", - "integrity": "sha512-pw56+69jq+QSr/coADNvWTmBPDy+XsmwaF5KnUys4/wM1jt/fZdl7GPxhXXXYdXnz3Gj3qMkbUCH2uKjvX0MgQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz", + "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==", "dev": true, "requires": { "@types/hast": "^2.0.0" } }, "hast-util-to-html": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", - "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", "dev": true, "requires": { "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", - "hast-util-is-element": "^2.0.0", + "hast-util-raw": "^7.0.0", "hast-util-whitespace": "^2.0.0", "html-void-elements": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.2", - "unist-util-is": "^5.0.0" + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + } + }, + "hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" } }, "hast-util-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", - "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", "dev": true }, + "hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -42515,9 +45310,9 @@ "dev": true }, "highlight.js": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", - "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", "dev": true }, "home-or-tmp": { @@ -42540,12 +45335,20 @@ } }, "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "lru-cache": "^10.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true + } } }, "html-escaper": { @@ -42596,9 +45399,9 @@ } }, "http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "requires": { "agent-base": "^7.1.0", @@ -42606,9 +45409,9 @@ }, "dependencies": { "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { "debug": "^4.3.4" @@ -42628,13 +45431,13 @@ } }, "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "requires": { "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "resolve-alpn": "^1.2.0" } }, "https-proxy-agent": { @@ -42673,6 +45476,12 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -42692,9 +45501,9 @@ } }, "import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true }, "imurmurhash": { @@ -42753,112 +45562,31 @@ "wrap-ansi": "^6.2.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - }, - "figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "requires": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - } - }, - "is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true - }, - "mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true - }, - "run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true - }, - "rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "tslib": "^2.1.0" - } - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-regex": "^5.0.1" } } } }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "hasown": "^2.0.0", "side-channel": "^1.0.4" } }, @@ -42883,11 +45611,23 @@ "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", "dev": true }, - "ip": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "dev": true + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + } + } }, "ipaddr.js": { "version": "1.9.1", @@ -42910,20 +45650,12 @@ } }, "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", "dev": true, "requires": { - "kind-of": "^6.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } + "hasown": "^2.0.0" } }, "is-arguments": { @@ -42936,6 +45668,16 @@ "has-tostringtag": "^1.0.0" } }, + "is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -42983,28 +45725,29 @@ "dev": true }, "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "requires": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", "dev": true, "requires": { - "kind-of": "^6.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } + "hasown": "^2.0.0" + } + }, + "is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "requires": { + "is-typed-array": "^1.1.13" } }, "is-date-object": { @@ -43017,22 +45760,13 @@ } }, "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } }, "is-docker": { @@ -43108,9 +45842,9 @@ "dev": true }, "is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true }, "is-nan": { @@ -43130,9 +45864,9 @@ "dev": true }, "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true }, "is-number": { @@ -43194,18 +45928,18 @@ "dev": true }, "is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true }, "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" } }, "is-ssh": { @@ -43242,16 +45976,12 @@ } }, "is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.14" } }, "is-typedarray": { @@ -43270,9 +46000,9 @@ } }, "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true }, "is-utf8": { @@ -43288,9 +46018,9 @@ "dev": true }, "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true }, "is-weakref": { @@ -43303,13 +46033,13 @@ } }, "is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" } }, "is-windows": { @@ -43340,9 +46070,9 @@ "dev": true }, "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true }, "isobject": { @@ -43397,14 +46127,11 @@ "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", "dev": true }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "resolve": { "version": "1.1.7", @@ -43433,9 +46160,9 @@ } }, "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true }, "istanbul-lib-instrument": { @@ -43452,13 +46179,13 @@ } }, "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "dependencies": { @@ -43468,6 +46195,21 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -43499,9 +46241,9 @@ } }, "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -43519,9 +46261,9 @@ } }, "jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", @@ -43529,9 +46271,9 @@ } }, "jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", "dev": true, "requires": { "async": "^3.2.3", @@ -43918,9 +46660,9 @@ } }, "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true }, "jsdoc-type-pratt-parser": { @@ -43941,9 +46683,9 @@ "dev": true }, "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true }, "json-schema": { @@ -43971,19 +46713,15 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha512-KbsDJNRfRPF5v49tMNf9sqyyGqGLBcz1v5kZT01kG5ns5mQSltwxCKVmUzVKtEinkUnTDtSrp6ngWpV7Xw0ZlA==", + "dev": true }, "jsprim": { "version": "1.4.2", @@ -43997,6 +46735,18 @@ "verror": "1.10.0" } }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -44010,9 +46760,9 @@ "dev": true }, "karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", + "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", "dev": true, "requires": { "@colors/colors": "1.5.0", @@ -44034,13 +46784,22 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^4.4.1", + "socket.io": "^4.7.2", "source-map": "^0.6.1", "tmp": "^0.2.1", "ua-parser-js": "^0.7.30", "yargs": "^16.1.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -44052,13 +46811,33 @@ "wrap-ansi": "^7.0.0" } }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "minimist": "^1.2.6" + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "source-map": { @@ -44067,13 +46846,24 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "rimraf": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "yargs": { @@ -44125,14 +46915,20 @@ "requires": {} }, "karma-chrome-launcher": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", - "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", "dev": true, "requires": { "which": "^1.2.1" }, "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -44145,9 +46941,9 @@ } }, "karma-coverage": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", - "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.2.0", @@ -44171,6 +46967,20 @@ "minimatch": "^3.0.4" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "istanbul-lib-source-maps": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", @@ -44241,13 +47051,30 @@ } }, "karma-firefox-launcher": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", - "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.3.tgz", + "integrity": "sha512-LMM2bseebLbYjODBOVt7TCPP9OI2vZIXCavIXhkO9m+10Uj5l7u/SKoeRmYx8FYHTVGZSpk6peX+3BMHC1WwNw==", "dev": true, "requires": { "is-wsl": "^2.2.0", - "which": "^2.0.1" + "which": "^3.0.0" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "karma-ie-launcher": { @@ -44343,14 +47170,61 @@ } }, "karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", + "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", "dev": true, "requires": { "glob": "^7.1.3", - "minimatch": "^3.0.4", + "minimatch": "^9.0.3", "webpack-merge": "^4.1.5" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + } } }, "keycode": { @@ -44454,6 +47328,15 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -44482,9 +47365,9 @@ } }, "lighthouse-logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.3.0.tgz", - "integrity": "sha512-BbqAKApLb9ywUli+0a+PcV04SyJ/N1q/8qgCNe6U97KbPCS1BTksEuHFLYdvc8DltuhfxIUBqDZsC0bBGtl3lA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "dev": true, "requires": { "debug": "^2.6.9", @@ -44509,9 +47392,9 @@ } }, "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", "dev": true }, "listenercount": { @@ -44597,12 +47480,12 @@ } }, "locate-app": { - "version": "2.2.13", - "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.13.tgz", - "integrity": "sha512-1jp6iRFrHKBj9vq6Idb0cSjly+KnCIMbxZ2BBKSEzIC4ZJosv47wnLoiJu2EgOAdjhGvNcy/P2fbDCS/WziI8g==", + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.4.15.tgz", + "integrity": "sha512-oAGHATXPUHSQ74Om+3dXBRNYtCzU7Wzuhlj/WIZchqHb/5/TGJRzLEtHipMDOak0UZG9U365RMXyBzgV/fhOww==", "dev": true, "requires": { - "n12": "1.8.16", + "@promptbook/utils": "0.50.0-10", "type-fest": "2.13.0", "userhome": "1.0.0" }, @@ -44857,16 +47740,16 @@ } }, "log4js": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", - "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, "requires": { "date-format": "^4.0.14", "debug": "^4.3.4", "flatted": "^3.2.7", "rfdc": "^1.3.0", - "streamroller": "^3.1.3" + "streamroller": "^3.1.5" } }, "logform": { @@ -44892,9 +47775,9 @@ } }, "loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", "dev": true }, "loglevel-plugin-prefix": { @@ -44910,9 +47793,9 @@ "dev": true }, "longest-streak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", - "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "dev": true }, "loose-envify": { @@ -44925,27 +47808,26 @@ } }, "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "requires": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true }, "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { - "yallist": "^4.0.0" + "yallist": "^3.0.2" } }, "lru-queue": { @@ -44958,9 +47840,9 @@ } }, "m3u8-parser": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz", - "integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz", + "integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", @@ -44969,13 +47851,12 @@ } }, "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, - "optional": true, "requires": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "make-dir": { @@ -45026,9 +47907,9 @@ } }, "markdown-table": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz", - "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", "dev": true }, "marky": { @@ -45216,9 +48097,9 @@ } }, "mdast-util-definitions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", - "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45227,11 +48108,12 @@ } }, "mdast-util-find-and-replace": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", - "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", "dev": true, "requires": { + "@types/mdast": "^3.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.0.0" @@ -45246,9 +48128,9 @@ } }, "mdast-util-from-markdown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", - "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45266,17 +48148,20 @@ }, "dependencies": { "mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0" + } } } }, "mdast-util-gfm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", - "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", "dev": true, "requires": { "mdast-util-from-markdown": "^1.0.0", @@ -45289,9 +48174,9 @@ } }, "mdast-util-gfm-autolink-literal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", - "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45301,9 +48186,9 @@ } }, "mdast-util-gfm-footnote": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", - "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45312,9 +48197,9 @@ } }, "mdast-util-gfm-strikethrough": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz", - "integrity": "sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45322,9 +48207,9 @@ } }, "mdast-util-gfm-table": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz", - "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45334,9 +48219,9 @@ } }, "mdast-util-gfm-task-list-item": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", - "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -45352,10 +48237,20 @@ "mdast-util-to-string": "^1.0.0" } }, + "mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + } + }, "mdast-util-to-hast": { - "version": "12.2.4", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.4.tgz", - "integrity": "sha512-a21xoxSef1l8VhHxS1Dnyioz6grrJkoaCUgGzMD/7dWHvboYX3VW53esRUfB5tgTyz4Yos1n25SPcj35dJqmAg==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", "dev": true, "requires": { "@types/hast": "^2.0.0", @@ -45363,21 +48258,21 @@ "mdast-util-definitions": "^5.0.0", "micromark-util-sanitize-uri": "^1.1.0", "trim-lines": "^3.0.0", - "unist-builder": "^3.0.0", "unist-util-generated": "^2.0.0", "unist-util-position": "^4.0.0", "unist-util-visit": "^4.0.0" } }, "mdast-util-to-markdown": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz", - "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", "dev": true, "requires": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", "mdast-util-to-string": "^3.0.0", "micromark-util-decode-string": "^1.0.0", "unist-util-visit": "^4.0.0", @@ -45385,10 +48280,13 @@ }, "dependencies": { "mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0" + } } } }, @@ -45399,46 +48297,33 @@ "dev": true }, "mdast-util-toc": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.0.tgz", - "integrity": "sha512-0PuqZELXZl4ms1sF7Lqigrqik4Ll3UhbI+jdTrfw7pZ9QPawgl7LD4GQ8MkU7bT/EwiVqChNTbifa2jLLKo76A==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.1.tgz", + "integrity": "sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==", "dev": true, "requires": { "@types/extend": "^3.0.0", - "@types/github-slugger": "^1.0.0", "@types/mdast": "^3.0.0", "extend": "^3.0.0", - "github-slugger": "^1.0.0", + "github-slugger": "^2.0.0", "mdast-util-to-string": "^3.1.0", "unist-util-is": "^5.0.0", - "unist-util-visit": "^3.0.0" + "unist-util-visit": "^4.0.0" }, "dependencies": { - "mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", + "github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "dev": true }, - "unist-util-visit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", - "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^4.0.0" - } - }, - "unist-util-visit-parents": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", - "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "dev": true, "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" + "@types/mdast": "^3.0.0" } } } @@ -45449,13 +48334,13 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", "dev": true, "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", + "d": "^1.0.2", + "es5-ext": "^0.10.64", "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", "is-promise": "^2.2.2", @@ -45491,9 +48376,9 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromark": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", - "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", "dev": true, "requires": { "@types/debug": "^4.0.0", @@ -45516,9 +48401,9 @@ } }, "micromark-core-commonmark": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", - "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", "dev": true, "requires": { "decode-named-character-reference": "^1.0.0", @@ -45540,9 +48425,9 @@ } }, "micromark-extension-gfm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", - "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", "dev": true, "requires": { "micromark-extension-gfm-autolink-literal": "^1.0.0", @@ -45556,22 +48441,21 @@ } }, "micromark-extension-gfm-autolink-literal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", - "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-types": "^1.0.0" } }, "micromark-extension-gfm-footnote": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", - "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", "dev": true, "requires": { "micromark-core-commonmark": "^1.0.0", @@ -45585,9 +48469,9 @@ } }, "micromark-extension-gfm-strikethrough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", - "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", "dev": true, "requires": { "micromark-util-chunked": "^1.0.0", @@ -45599,9 +48483,9 @@ } }, "micromark-extension-gfm-table": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", - "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", "dev": true, "requires": { "micromark-factory-space": "^1.0.0", @@ -45612,18 +48496,18 @@ } }, "micromark-extension-gfm-tagfilter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", - "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", "dev": true, "requires": { "micromark-util-types": "^1.0.0" } }, "micromark-extension-gfm-task-list-item": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", - "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", "dev": true, "requires": { "micromark-factory-space": "^1.0.0", @@ -45634,9 +48518,9 @@ } }, "micromark-factory-destination": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", - "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", @@ -45645,9 +48529,9 @@ } }, "micromark-factory-label": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", - "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", @@ -45657,9 +48541,9 @@ } }, "micromark-factory-space": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", - "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", @@ -45667,22 +48551,21 @@ } }, "micromark-factory-title": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", - "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", "dev": true, "requires": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-types": "^1.0.0" } }, "micromark-factory-whitespace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", - "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", "dev": true, "requires": { "micromark-factory-space": "^1.0.0", @@ -45692,9 +48575,9 @@ } }, "micromark-util-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", - "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "dev": true, "requires": { "micromark-util-symbol": "^1.0.0", @@ -45702,18 +48585,18 @@ } }, "micromark-util-chunked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", - "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", "dev": true, "requires": { "micromark-util-symbol": "^1.0.0" } }, "micromark-util-classify-character": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", - "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", @@ -45722,9 +48605,9 @@ } }, "micromark-util-combine-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", - "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", "dev": true, "requires": { "micromark-util-chunked": "^1.0.0", @@ -45732,18 +48615,18 @@ } }, "micromark-util-decode-numeric-character-reference": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", - "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", "dev": true, "requires": { "micromark-util-symbol": "^1.0.0" } }, "micromark-util-decode-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", - "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", "dev": true, "requires": { "decode-named-character-reference": "^1.0.0", @@ -45753,39 +48636,39 @@ } }, "micromark-util-encode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", - "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", "dev": true }, "micromark-util-html-tag-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", - "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", "dev": true }, "micromark-util-normalize-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", - "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", "dev": true, "requires": { "micromark-util-symbol": "^1.0.0" } }, "micromark-util-resolve-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", - "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", "dev": true, "requires": { "micromark-util-types": "^1.0.0" } }, "micromark-util-sanitize-uri": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", - "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", @@ -45794,9 +48677,9 @@ } }, "micromark-util-subtokenize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", - "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", "dev": true, "requires": { "micromark-util-chunked": "^1.0.0", @@ -45806,24 +48689,24 @@ } }, "micromark-util-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", - "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "dev": true }, "micromark-util-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", - "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -45853,9 +48736,9 @@ "dev": true }, "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true }, "min-document": { @@ -45877,15 +48760,15 @@ } }, "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true }, "mitt": { @@ -45904,6 +48787,15 @@ "is-extendable": "^1.0.1" } }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -45911,9 +48803,9 @@ "dev": true }, "mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dev": true, "requires": { "ansi-colors": "4.1.1", @@ -45923,13 +48815,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -45960,6 +48851,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -45981,6 +48881,22 @@ } } }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -46007,6 +48923,23 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -46030,28 +48963,16 @@ } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } + "minimatch": "^5.0.1", + "once": "^1.3.0" } }, "has-flag": { @@ -46060,6 +48981,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -46095,17 +49022,6 @@ "dev": true, "requires": { "brace-expansion": "^2.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } } }, "ms": { @@ -46132,6 +49048,15 @@ "p-limit": "^3.0.2" } }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -46147,6 +49072,17 @@ "has-flag": "^4.0.0" } }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -46167,9 +49103,21 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } }, + "moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true + }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -46210,14 +49158,14 @@ } }, "mpd-parser": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz", - "integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==", + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz", + "integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", "@videojs/vhs-utils": "^3.0.5", - "@xmldom/xmldom": "^0.7.2", + "@xmldom/xmldom": "^0.8.3", "global": "^4.4.0" } }, @@ -46228,9 +49176,9 @@ "dev": true }, "mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", "dev": true }, "ms": { @@ -46254,9 +49202,9 @@ "dev": true }, "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true }, "mux.js": { @@ -46269,24 +49217,19 @@ "global": "^4.4.0" } }, - "n12": { - "version": "1.8.16", - "resolved": "https://registry.npmjs.org/n12/-/n12-1.8.16.tgz", - "integrity": "sha512-CZqHAqbzS0UsaUGkMsL+lMaYLyFr1+/ea+pD8dMziqSjkcuWVWDtgWx9phyfT7C3llqQ2+LwnStSb5afggBMfA==", - "dev": true - }, "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", "dev": true, "optional": true }, "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "optional": true }, "nanomatch": { "version": "1.2.13", @@ -46428,18 +49371,20 @@ "dev": true }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dev": true, "requires": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "node-html-parser": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.6.tgz", - "integrity": "sha512-C/MGDQ2NjdjzUq41bW9kW00MPZecAe/oo89vZEFLDfWoQVDk/DdML1yuxVVKLDMFIFax2VTq6Vpfzyn7z5yYgQ==", + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", "dev": true, "requires": { "css-select": "^5.1.0", @@ -46447,9 +49392,9 @@ } }, "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node-request-interceptor": { "version": "0.6.3", @@ -46482,25 +49427,22 @@ } }, "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", + "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", "dev": true, "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "dependencies": { "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true } } }, @@ -46511,9 +49453,9 @@ "dev": true }, "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true }, "now-and-later": { @@ -46589,47 +49531,20 @@ "is-descriptor": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } }, "kind-of": { @@ -46644,18 +49559,18 @@ } }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" } }, "object-keys": { @@ -46674,13 +49589,13 @@ } }, "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } @@ -46697,6 +49612,29 @@ "isobject": "^3.0.0" } }, + "object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + } + }, + "object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + } + }, "object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", @@ -46727,14 +49665,14 @@ } }, "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, "on-finished": { @@ -46793,9 +49731,9 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -46803,7 +49741,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "ora": { @@ -46863,6 +49801,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -46873,6 +49817,15 @@ "is-unicode-supported": "^0.1.0" } }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -46915,9 +49868,9 @@ "dev": true }, "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true }, "p-finally": { @@ -46973,18 +49926,18 @@ }, "dependencies": { "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { "debug": "^4.3.4" } }, "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "requires": { "agent-base": "^7.0.2", @@ -46994,16 +49947,21 @@ } }, "pac-resolver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", - "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, "requires": { "degenerator": "^5.0.0", - "ip": "^1.1.8", "netmask": "^2.0.2" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -47025,15 +49983,24 @@ } }, "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "dependencies": { + "type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true + } } }, "parse-ms": { @@ -47072,6 +50039,12 @@ "parse-path": "^7.0.0" } }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -47128,19 +50101,19 @@ "dev": true }, "path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "requires": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "dependencies": { "lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true } } @@ -47203,9 +50176,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "picomatch": { "version": "2.3.1", @@ -47271,25 +50244,22 @@ "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", "dev": true }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true + }, "postcss": { - "version": "8.4.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", - "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "optional": true, "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "dependencies": { - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "optional": true - } + "source-map-js": "^1.2.0" } }, "prelude-ls": { @@ -47298,6 +50268,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true + }, "pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -47356,9 +50332,9 @@ "dev": true }, "property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "dev": true }, "protocols": { @@ -47393,18 +50369,18 @@ }, "dependencies": { "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { "debug": "^4.3.4" } }, "https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "requires": { "agent-base": "^7.0.2", @@ -47492,9 +50468,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "puppeteer-core": { @@ -47517,12 +50493,57 @@ "ws": "8.5.0" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "devtools-protocol": { "version": "0.0.981744", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", "dev": true }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, "ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", @@ -47553,15 +50574,15 @@ } }, "query-selector-shadow-dom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.0.tgz", - "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", "dev": true }, "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", "dev": true }, "querystringify": { @@ -47608,40 +50629,40 @@ } }, "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, "read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "dev": true, "requires": { "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" }, "dependencies": { "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.0.tgz", + "integrity": "sha512-MBh+PHUHHisjXf4tlx0CFWoMdjx8zCMLJHOjnV1prABYZFHqtFOyauCIK2/7w4oIfwkF8iNhLtnJEfVY2vn3iw==", "dev": true } } }, "read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.0.0.tgz", + "integrity": "sha512-jgmKiS//w2Zs+YbX039CorlkOp8FIVbSAN8r8GJHDsGlmNPXo+VeHkqAwCiQVTTx5/LwLZTcEw59z3DvcLbr0g==", "dev": true, "requires": { "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" + "read-pkg": "^8.0.0", + "type-fest": "^3.12.0" }, "dependencies": { "find-up": { @@ -47655,9 +50676,9 @@ } }, "locate-path": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", - "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "requires": { "p-locate": "^6.0.0" @@ -47688,23 +50709,17 @@ "dev": true }, "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true - }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "dev": true } } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -47728,9 +50743,9 @@ } }, "readdir-glob": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", - "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "requires": { "minimatch": "^5.1.0" @@ -47746,9 +50761,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -47789,22 +50804,22 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", "requires": { "regenerate": "^1.4.2" } }, "regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "requires": { "@babel/runtime": "^7.8.4" } @@ -47832,14 +50847,15 @@ } }, "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" } }, "regexpp": { @@ -47849,16 +50865,16 @@ "dev": true }, "regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "requires": { + "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" + "unicode-match-property-value-ecmascript": "^2.1.0" } }, "regextras": { @@ -47867,11 +50883,6 @@ "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", "dev": true }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" - }, "regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -47888,9 +50899,9 @@ } }, "remark": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.2.tgz", - "integrity": "sha512-A3ARm2V4BgiRXaUo5K0dRvJ1lbogrbXnhkJRmD0yw092/Yl0kOCZt1k9ZeElEwkZsWGsMumz6qL5MfNJH9nOBA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", + "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -47912,9 +50923,9 @@ } }, "remark-html": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.1.tgz", - "integrity": "sha512-7ta5UPRqj8nP0GhGMYUAghZ/DRno7dgq7alcW90A7+9pgJsXzGJlFgwF8HOP1b1tMgT3WwbeANN+CaTimMfyNQ==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.2.tgz", + "integrity": "sha512-/CIOI7wzHJzsh48AiuIyIe1clxVkUtreul73zcCXLub0FmnevQE0UMFDQm7NUx8/3rl/4zCshlMfqBdWScQthw==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -47925,9 +50936,9 @@ } }, "remark-parse": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", - "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -47947,9 +50958,9 @@ } }, "remark-stringify": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.2.tgz", - "integrity": "sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz", + "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==", "dev": true, "requires": { "@types/mdast": "^3.0.0", @@ -48097,6 +51108,12 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -48125,11 +51142,11 @@ "dev": true }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -48172,18 +51189,18 @@ "dev": true }, "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "requires": { - "lowercase-keys": "^2.0.0" + "lowercase-keys": "^3.0.0" } }, "resq": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.2.tgz", - "integrity": "sha512-HmgVS3j+FLrEDBTDYysPdPVF9/hioDMJ/otOiQDKqk77YfZeeLOj0qi34yObumcud1gBpk+wpBTEg4kMicD++A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1" @@ -48214,9 +51231,9 @@ "dev": true }, "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "rgb2hex": { @@ -48232,12 +51249,28 @@ "dev": true, "requires": { "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true }, "rust-result": { @@ -48250,12 +51283,20 @@ } }, "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "requires": { - "tslib": "^1.9.0" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + } } }, "sade": { @@ -48273,6 +51314,18 @@ "integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==", "dev": true }, + "safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -48294,13 +51347,13 @@ } }, "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" } }, @@ -48519,15 +51572,28 @@ "dev": true }, "set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "requires": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" } }, "set-value": { @@ -48595,13 +51661,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { @@ -48634,14 +51701,14 @@ } }, "sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dev": true, "requires": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" } }, "slash": { @@ -48736,61 +51803,14 @@ "is-extendable": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } }, "is-extendable": { @@ -48869,32 +51889,34 @@ } }, "socket.io": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", - "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", + "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.4.1", + "engine.io": "~6.5.2", "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" + "socket.io-parser": "~4.2.4" } }, "socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", "dev": true, "requires": { + "debug": "~4.3.4", "ws": "~8.11.0" } }, "socket.io-parser": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz", - "integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "requires": { "@socket.io/component-emitter": "~3.1.0", @@ -48902,38 +51924,30 @@ } }, "socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "requires": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" - }, - "dependencies": { - "ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", - "dev": true - } } }, "socks-proxy-agent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", - "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", "dev": true, "requires": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.7.1" }, "dependencies": { "agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { "debug": "^4.3.4" @@ -48954,9 +51968,9 @@ "dev": true }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "optional": true }, @@ -48985,17 +51999,16 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true, - "optional": true - }, "space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true + }, + "spacetrim": { + "version": "0.11.25", + "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.25.tgz", + "integrity": "sha512-SWxXDROciuJs9YEYXUBjot5k/cqNGPPbT3QmkInFne4AGc1y+76It+jqU8rfsXKt57RRiunzZn1m9+KfuuNklw==", "dev": true }, "sparkles": { @@ -49005,9 +52018,9 @@ "dev": true }, "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -49015,9 +52028,9 @@ } }, "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "spdx-expression-parse": { @@ -49031,9 +52044,9 @@ } }, "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "split": { @@ -49078,9 +52091,9 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -49092,6 +52105,14 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "dependencies": { + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + } } }, "stack-trace": { @@ -49136,61 +52157,14 @@ "is-descriptor": "^0.1.0" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } } } @@ -49200,6 +52174,15 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, "stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", @@ -49222,15 +52205,15 @@ "dev": true }, "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "dev": true }, "streamroller": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", - "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, "requires": { "date-format": "^4.0.14", @@ -49267,13 +52250,15 @@ } }, "streamx": { - "version": "2.15.6", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", - "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "requires": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" } }, "strict-event-emitter": { @@ -49312,6 +52297,17 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "string-width-cjs": { @@ -49323,34 +52319,57 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" } }, "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, "stringify-entities": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", - "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dev": true, "requires": { "character-entities-html4": "^2.0.0", @@ -49358,12 +52377,20 @@ } }, "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + } } }, "strip-ansi-cjs": { @@ -49400,9 +52427,9 @@ "dev": true }, "strip-json-comments": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", - "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", "dev": true }, "supports-color": { @@ -49429,9 +52456,9 @@ } }, "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "requires": { "ajv": "^8.0.1", @@ -49442,15 +52469,15 @@ }, "dependencies": { "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" } }, "json-schema-traverse": { @@ -49458,6 +52485,15 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } } } }, @@ -49468,41 +52504,25 @@ "dev": true }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, "requires": { - "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" } }, "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "temp-fs": { @@ -49514,6 +52534,20 @@ "rimraf": "~2.5.2" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "rimraf": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", @@ -49550,21 +52584,21 @@ } }, "terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", "dev": true, "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "dependencies": { "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, "source-map": { @@ -49586,16 +52620,16 @@ } }, "terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "dependencies": { "ajv": { @@ -49611,15 +52645,24 @@ } }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } + }, + "serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } } } }, @@ -49632,6 +52675,31 @@ "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "requires": { + "b4a": "^1.6.4" } }, "text-table": { @@ -49662,9 +52730,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -49703,13 +52771,13 @@ "dev": true }, "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", "dev": true, "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" } }, "tiny-hashes": { @@ -49743,13 +52811,10 @@ } }, "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true }, "to-absolute-glob": { "version": "2.0.2", @@ -49852,9 +52917,9 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true }, "tough-cookie": { @@ -49898,9 +52963,9 @@ "dev": true }, "trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true }, "tryit": { @@ -49909,21 +52974,21 @@ "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==" }, "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -49953,9 +53018,9 @@ "dev": true }, "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", "dev": true }, "type-check": { @@ -49988,20 +53053,64 @@ "mime-types": "~2.1.24" } }, + "typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, - "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, - "optional": true, - "peer": true - }, "typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", @@ -50024,15 +53133,15 @@ } }, "ua-parser-js": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", - "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", "dev": true }, "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", "dev": true, "optional": true }, @@ -50117,9 +53226,9 @@ } }, "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" }, "unicode-property-aliases-ecmascript": { "version": "2.1.0", @@ -50178,48 +53287,51 @@ } }, "unist-builder": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", - "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", + "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", "dev": true, "requires": { "@types/unist": "^2.0.0" } }, "unist-util-generated": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", - "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", "dev": true }, "unist-util-is": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", - "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", - "dev": true + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } }, "unist-util-position": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", - "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", "dev": true, "requires": { "@types/unist": "^2.0.0" } }, "unist-util-stringify-position": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", - "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", "dev": true, "requires": { "@types/unist": "^2.0.0" } }, "unist-util-visit": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", - "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -50228,9 +53340,9 @@ } }, "unist-util-visit-parents": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", - "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -50238,9 +53350,9 @@ } }, "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true }, "unpipe": { @@ -50311,6 +53423,12 @@ "setimmediate": "~1.0.4" }, "dependencies": { + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -50329,12 +53447,12 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" } }, "uri-js": { @@ -50353,20 +53471,29 @@ "dev": true }, "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dev": true, "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "punycode": "^1.4.1", + "qs": "^6.11.2" }, "dependencies": { "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true + }, + "qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.6" + } } } }, @@ -50422,9 +53549,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true }, "uvu": { @@ -50440,9 +53567,9 @@ } }, "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", "dev": true }, "v8flags": { @@ -50495,9 +53622,9 @@ } }, "vfile": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", - "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -50506,10 +53633,20 @@ "vfile-message": "^3.0.0" } }, + "vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + } + }, "vfile-message": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", - "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -50517,25 +53654,21 @@ } }, "vfile-reporter": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.4.tgz", - "integrity": "sha512-4cWalUnLrEnbeUQ+hARG5YZtaHieVK3Jp4iG5HslttkVl+MHunSGNAIrODOTLbtjWsNZJRMCkL66AhvZAYuJ9A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.5.tgz", + "integrity": "sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw==", "dev": true, "requires": { "@types/supports-color": "^8.0.0", "string-width": "^5.0.0", "supports-color": "^9.0.0", "unist-util-stringify-position": "^3.0.0", + "vfile": "^5.0.0", + "vfile-message": "^3.0.0", "vfile-sort": "^3.0.0", "vfile-statistics": "^2.0.0" }, "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -50553,60 +53686,53 @@ "strip-ansi": "^7.0.1" } }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, "supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true } } }, "vfile-sort": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.0.tgz", - "integrity": "sha512-fJNctnuMi3l4ikTVcKpxTbzHeCgvDhnI44amA3NVDvA6rTC6oKCFpCVyT5n2fFMr3ebfr+WVQZedOCd73rzSxg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.1.tgz", + "integrity": "sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw==", "dev": true, "requires": { + "vfile": "^5.0.0", "vfile-message": "^3.0.0" } }, "vfile-statistics": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.0.tgz", - "integrity": "sha512-foOWtcnJhKN9M2+20AOTlWi2dxNfAoeNIoxD5GXcO182UJyId4QrXa41fWrgcfV3FWTjdEDy3I4cpLVcQscIMA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.1.tgz", + "integrity": "sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg==", "dev": true, "requires": { + "vfile": "^5.0.0", "vfile-message": "^3.0.0" } }, "video.js": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.3.tgz", - "integrity": "sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==", + "version": "7.21.6", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.6.tgz", + "integrity": "sha512-m41TbODrUCToVfK1aljVd296CwDQnCRewpIm5tTXMuV87YYSGw1H+VDOaV45HlpcWSsTWWLF++InDgGJfthfUw==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "2.14.3", + "@videojs/http-streaming": "2.16.3", "@videojs/vhs-utils": "^3.0.4", "@videojs/xhr": "2.6.0", "aes-decrypter": "3.1.3", "global": "^4.4.0", "keycode": "^2.2.0", - "m3u8-parser": "4.7.1", - "mpd-parser": "0.21.1", + "m3u8-parser": "4.8.0", + "mpd-parser": "0.22.1", "mux.js": "6.0.1", "safe-json-parse": "4.0.0", "videojs-font": "3.2.0", - "videojs-vtt.js": "^0.15.4" + "videojs-vtt.js": "^0.15.5" }, "dependencies": { "safe-json-parse": { @@ -50651,19 +53777,19 @@ } }, "videojs-playlist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.0.0.tgz", - "integrity": "sha512-TM9bytwKqkE05wdWPEKDpkwMGhS/VgMCIsEuNxmX1J1JO9zaTIl4Wm3egf5j1dhIw19oWrqGUV/nK0YNIelCpA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.2.tgz", + "integrity": "sha512-8YgNq/iL17RLTXpfWAkuhM0Sq4w/x5YPVaNbUycjfqqGL/bp3Nrmc2W0qkPfh0ryB7r4cHfJbtHYP7zlW3ZkdQ==", "dev": true, "requires": { "global": "^4.3.2", - "video.js": "^6 || ^7" + "video.js": "^6 || ^7 || ^8" } }, "videojs-vtt.js": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz", - "integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==", + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", "dev": true, "requires": { "global": "^4.3.1" @@ -50751,6 +53877,12 @@ "vinyl": "^2.0.0" }, "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -50778,9 +53910,9 @@ "dev": true }, "vue-template-compiler": { - "version": "2.7.13", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.13.tgz", - "integrity": "sha512-jYM6TClwDS9YqP48gYrtAtaOhRKkbYmbzE+Q51gX5YDr777n7tNI/IZk4QV4l/PjQPNh/FVa/E92sh/RqKMrog==", + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", "dev": true, "optional": true, "requires": { @@ -50866,9 +53998,9 @@ } }, "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -50884,6 +54016,12 @@ "defaults": "^1.0.3" } }, + "web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true + }, "web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -50891,188 +54029,101 @@ "dev": true }, "webdriver": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.29.1.tgz", - "integrity": "sha512-D3gkbDUxFKBJhNHRfMriWclooLbNavVQC1MRvmENAgPNKaHnFn+M+WtP9K2sEr0XczLGNlbOzT7CKR9K5UXKXA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.38.2.tgz", + "integrity": "sha512-NGfjW0BDYwFgOIzeojOcWGn3tYloQdvHr+Y2xKKYVqa9Rs0x1mzlTjU1kWtC4DaV8DltskwaPa7o+s8hTNpuyA==", "dev": true, "requires": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.29.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/config": "8.38.2", + "@wdio/logger": "8.38.0", + "@wdio/protocols": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "deepmerge-ts": "^5.1.0", "got": "^12.6.1", "ky": "^0.33.0", "ws": "^8.8.0" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.1" - } - }, - "cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true - }, - "cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - } - }, - "http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - } - }, - "lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true - }, - "mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true - }, - "normalize-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", - "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", - "dev": true - }, - "p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true - }, - "responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "requires": { - "lowercase-keys": "^3.0.0" - } - } } }, "webdriverio": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.25.4.tgz", - "integrity": "sha512-agkgwn2SIk5cAJ03uue1GnGZcUZUDN3W4fUMY9/VfO8bVJrPEgWg31bPguEWPu+YhEB/aBJD8ECxJ3OEKdy4qQ==", + "version": "7.36.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.36.0.tgz", + "integrity": "sha512-OTYmKBF7eFKBX39ojUIEzw7AlE1ZRJiFoMTtEQaPMuPzZCP2jUBq6Ey38nuZrYXLkXn3/le9a14pNnKSM0n56w==", "dev": true, "requires": { "@types/aria-query": "^5.0.0", "@types/node": "^18.0.0", - "@wdio/config": "7.25.4", - "@wdio/logger": "7.19.0", - "@wdio/protocols": "7.22.0", - "@wdio/repl": "7.25.4", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@wdio/config": "7.33.0", + "@wdio/logger": "7.26.0", + "@wdio/protocols": "7.27.0", + "@wdio/repl": "7.33.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "archiver": "^5.0.0", - "aria-query": "^5.0.0", + "aria-query": "^5.2.1", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.25.4", - "devtools-protocol": "^0.0.1061995", - "fs-extra": "^10.0.0", + "devtools": "7.35.0", + "devtools-protocol": "^0.0.1260888", + "fs-extra": "^11.1.1", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", "lodash.isobject": "^3.0.2", "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^5.0.0", + "minimatch": "^6.0.4", "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "7.25.4" + "webdriver": "7.33.0" }, "dependencies": { - "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/node": { + "version": "18.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", + "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, "@wdio/config": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.25.4.tgz", - "integrity": "sha512-vb0emDtD9FbFh/yqW6oNdo2iuhQp8XKj6GX9fyy9v4wZgg3B0HPMVJxhIfcoHz7LMBWlHSo9YdvhFI5EQHRLBA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", + "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", "dev": true, "requires": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@types/glob": "^8.1.0", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "deepmerge": "^4.0.0", "glob": "^8.0.3" } }, "@wdio/logger": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.19.0.tgz", - "integrity": "sha512-xR7SN/kGei1QJD1aagzxs3KMuzNxdT/7LYYx+lt6BII49+fqL/SO+5X0FDCZD0Ds93AuQvvz9eGyzrBI2FFXmQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", + "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -51082,24 +54133,24 @@ } }, "@wdio/protocols": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.22.0.tgz", - "integrity": "sha512-8EXRR+Ymdwousm/VGtW3H1hwxZ/1g1H99A1lF0U4GuJ5cFWHCd0IVE5H31Z52i8ZruouW8jueMkGZPSo2IIUSQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", + "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", "dev": true }, "@wdio/repl": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.25.4.tgz", - "integrity": "sha512-kYhj9gLsUk4HmlXLqkVre+gwbfvw9CcnrHjqIjrmMS4mR9D8zvBb5CItb3ZExfPf9jpFzIFREbCAYoE9x/kMwg==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz", + "integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==", "dev": true, "requires": { - "@wdio/utils": "7.25.4" + "@wdio/utils": "7.33.0" } }, "@wdio/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.25.4.tgz", - "integrity": "sha512-muvNmq48QZCvocctnbe0URq2FjJjUPIG4iLoeMmyF0AQgdbjaUkMkw3BHYNHVTbSOU9WMsr2z8alhj/I2H6NRQ==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", + "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", "dev": true, "requires": { "@types/node": "^18.0.0", @@ -51107,13 +54158,13 @@ } }, "@wdio/utils": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.25.4.tgz", - "integrity": "sha512-8iwQDk+foUqSzKZKfhLxjlCKOkfRJPNHaezQoevNgnrTq/t0ek+ldZCATezb9B8jprAuP4mgS9xi22akc6RkzA==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", + "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", "dev": true, "requires": { - "@wdio/logger": "7.19.0", - "@wdio/types": "7.25.4", + "@wdio/logger": "7.26.0", + "@wdio/types": "7.33.0", "p-iteration": "^1.1.8" } }, @@ -51135,6 +54186,27 @@ "balanced-match": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true + }, + "cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -51160,10 +54232,30 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -51171,6 +54263,36 @@ "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, "has-flag": { @@ -51179,21 +54301,77 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, "ky": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", "dev": true }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", + "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -51204,17 +54382,17 @@ } }, "webdriver": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.25.4.tgz", - "integrity": "sha512-6nVDwenh0bxbtUkHASz9B8T9mB531Fn1PcQjUGj2t5dolLPn6zuK1D7XYVX40hpn6r3SlYzcZnEBs4X0az5Txg==", + "version": "7.33.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz", + "integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==", "dev": true, "requires": { "@types/node": "^18.0.0", - "@wdio/config": "7.25.4", - "@wdio/logger": "7.19.0", - "@wdio/protocols": "7.22.0", - "@wdio/types": "7.25.4", - "@wdio/utils": "7.25.4", + "@wdio/config": "7.33.0", + "@wdio/logger": "7.26.0", + "@wdio/protocols": "7.27.0", + "@wdio/types": "7.33.0", + "@wdio/utils": "7.33.0", "got": "^11.0.2", "ky": "0.30.0", "lodash.merge": "^4.6.1" @@ -51229,47 +54407,47 @@ "dev": true }, "webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", + "version": "5.92.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz", + "integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "dependencies": { "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "requires": {} }, @@ -51285,10 +54463,16 @@ "uri-js": "^4.2.2" } }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", @@ -51299,60 +54483,29 @@ } }, "webpack-bundle-analyzer": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", - "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dev": true, "requires": { + "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "html-escaper": "^2.0.2", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "dependencies": { "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, "commander": { @@ -51361,21 +54514,12 @@ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, - "has-flag": { + "escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", @@ -51532,12 +54676,12 @@ } }, "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" } }, "which-boxed-primitive": { @@ -51554,15 +54698,15 @@ } }, "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" } }, "which-module": { @@ -51572,23 +54716,22 @@ "dev": true }, "which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" } }, "winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", "dev": true, "requires": { "logform": "^2.3.2", @@ -51610,9 +54753,9 @@ } }, "word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wordwrap": { @@ -51628,9 +54771,9 @@ "dev": true }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -51661,6 +54804,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } } } }, @@ -51698,6 +54850,15 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } } } }, @@ -51714,17 +54875,6 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - } } }, "ws": { @@ -51747,10 +54897,9 @@ "dev": true }, "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { "version": "1.3.3", @@ -51782,6 +54931,12 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -51791,36 +54946,68 @@ } }, "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.1.3.tgz", + "integrity": "sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==", "dev": true, "requires": { "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "pend": "~1.2.0" } }, "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true }, "zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" }, "dependencies": { + "archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "requires": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -51831,9 +55018,9 @@ } }, "zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "dev": true } } diff --git a/package.json b/package.json index 53211bd3e0e..43edec4be1f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { "name": "prebid.js", - "version": "8.52.1-pre", + "version": "9.0.0-pre", "description": "Header Bidding Management Library", - "main": "src/prebid.js", + "main": "src/prebid.public.js", + "exports": { + ".": "./src/prebid.public.js", + "./prebid.js": "./src/prebid.public.js", + "./prebid": "./src/prebid.public.js", + "./.babelrc.js": "./.babelrc.js", + "./babelConfig.js": "./babelConfig.js", + "./modules/*": "./modules/*", + "./modules/*.js": "./modules/*.js" + }, "scripts": { "serve": "gulp serve", "test": "gulp test", @@ -30,10 +39,10 @@ ], "globalVarName": "pbjs", "defineGlobal": true, - "author": "the prebid.js contributors", + "author": "The prebid.js contributors", "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -104,6 +113,7 @@ "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", + "querystring": "^0.2.1", "resolve-from": "^5.0.0", "sinon": "^4.1.3", "through2": "^4.0.2", @@ -121,13 +131,12 @@ "yargs": "^1.3.1" }, "dependencies": { - "@babel/core": "^7.16.7", + "@babel/core": "^7.24.6", "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", - "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^4.2.0", "dlv": "1.1.3", "dset": "3.1.2", diff --git a/plugins/eslint/index.js b/plugins/eslint/index.js new file mode 100644 index 00000000000..5041675f6ad --- /dev/null +++ b/plugins/eslint/index.js @@ -0,0 +1,76 @@ +const _ = require('lodash'); +const { flagErrors } = require('./validateImports.js'); + +module.exports = { + rules: { + 'no-outerText': { + meta: { + docs: { + description: '.outerText property on DOM elements should not be used due to performance issues' + }, + messages: { + noInnerText: 'Use of `.outerText` is not allowed. Use `.textContent` instead.', + } + }, + create: function(context) { + return { + MemberExpression(node) { + if (node.property && node.property.name === 'outerText') { + context.report({ + node: node.property, + messageId: 'noOuterText', + }); + } + } + } + } + }, + 'no-innerText': { + meta: { + docs: { + description: '.innerText property on DOM elements should not be used due to performance issues' + }, + messages: { + noInnerText: 'Use of `.innerText` is not allowed. Use `.textContent` instead.', + } + }, + create: function(context) { + return { + MemberExpression(node) { + if (node.property && node.property.name === 'innerText') { + context.report({ + node: node.property, + messageId: 'noInnerText', + }); + } + } + } + } + }, + 'validate-imports': { + meta: { + docs: { + description: 'validates module imports can be found without custom webpack resolvers, are in module whitelist, and not module entry points' + } + }, + create: function(context) { + return { + "CallExpression[callee.name='require']"(node) { + let importPath = _.get(node, ['arguments', 0, 'value']); + if (importPath) { + flagErrors(context, node, importPath); + } + }, + ImportDeclaration(node) { + let importPath = node.source.value.trim(); + flagErrors(context, node, importPath); + }, + 'ExportNamedDeclaration[source]'(node) { + let importPath = node.source.value.trim(); + flagErrors(context, node, importPath); + } + } + } + } + } +}; diff --git a/plugins/eslint/package.json b/plugins/eslint/package.json index fa18ad83718..446f63945fa 100644 --- a/plugins/eslint/package.json +++ b/plugins/eslint/package.json @@ -2,7 +2,7 @@ "name": "eslint-plugin-prebid", "version": "1.0.0", "description": "validates module imports can be found without custom webpack resolvers, are in module whitelist, and not module entry points", - "main": "validateImports.js", + "main": "index.js", "author": "the prebid.js contributors", "license": "Apache-2.0" } diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index b936f44aee7..e38f0532238 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -53,31 +53,5 @@ function flagErrors(context, node, importPath) { } module.exports = { - rules: { - 'validate-imports': { - meta: { - docs: { - description: 'validates module imports can be found without custom webpack resolvers, are in module whitelist, and not module entry points' - } - }, - create: function(context) { - return { - "CallExpression[callee.name='require']"(node) { - let importPath = _.get(node, ['arguments', 0, 'value']); - if (importPath) { - flagErrors(context, node, importPath); - } - }, - ImportDeclaration(node) { - let importPath = node.source.value.trim(); - flagErrors(context, node, importPath); - }, - 'ExportNamedDeclaration[source]'(node) { - let importPath = node.source.value.trim(); - flagErrors(context, node, importPath); - } - } - } - } - } -}; + flagErrors +} diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index dae0902d4cc..ac26883ae99 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -361,18 +361,7 @@ export function newBidder(spec) { } } -// Transition from 'fledge' to 'paapi' -// TODO: remove this in prebid 9 -const PAAPI_PROPS = ['paapi', 'fledgeAuctionConfigs']; -const RESPONSE_PROPS = ['bids'].concat(PAAPI_PROPS); - -function getPaapiConfigs(adapterResponse) { - const [paapi, fledge] = PAAPI_PROPS.map(prop => adapterResponse[prop]); - if (paapi != null && fledge != null) { - throw new Error(`Adapter response should use ${PAAPI_PROPS[0]} over ${PAAPI_PROPS[1]}, not both`); - } - return paapi ?? fledge; -} +const RESPONSE_PROPS = ['bids', 'paapi'] /** * Run a set of bid requests - that entails converting them to HTTP requests, sending @@ -443,7 +432,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe let bids, paapiConfigs; if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { bids = response.bids; - paapiConfigs = getPaapiConfigs(response); + paapiConfigs = response.paapi; } else { bids = response; } diff --git a/src/adloader.js b/src/adloader.js index b746c59a1cc..12b552373a0 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,36 +4,38 @@ import { logError, logWarn, insertElement, setScriptAttributes } from './utils.j const _requestCache = new WeakMap(); // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + // Prebid maintained modules: 'debugging', - 'adloox', - 'criteo', 'outstream', + // Bid Modules - only exception is on rendering edge cases, to clean up in Prebid 10: + 'improvedigital', + 'showheroes-bs', + // RTD modules: + 'aaxBlockmeter', 'adagio', - 'spotx', + 'adloox', + 'akamaidap', + 'arcspan', + 'airgrid', 'browsi', 'brandmetrics', - 'justtag', - 'tncId', - 'akamaidap', - 'ftrackId', - 'inskin', + 'clean.io', + 'confiant', + 'contxtful', 'hadron', + 'mediafilter', 'medianet', - 'improvedigital', 'azerionedge', - 'aaxBlockmeter', - 'confiant', - 'arcspan', - 'airgrid', - 'clean.io', 'a1Media', 'geoedge', - 'mediafilter', 'qortex', 'dynamicAdBoost', - 'contxtful', - 'id5', '51Degrees', + // UserId Submodules + 'justtag', + 'tncId', + 'ftrackId', + 'id5', ]; /** diff --git a/src/config.js b/src/config.js index 21c34cf34d2..1fc9b4ea895 100644 --- a/src/config.js +++ b/src/config.js @@ -40,6 +40,8 @@ const DEFAULT_MAXBID_VALUE = 5000 const DEFAULT_TIMEOUTBUFFER = 400; +const DEFAULT_IFRAMES_CONFIG = {}; + export const RANDOM = 'random'; const FIXED = 'fixed'; @@ -163,7 +165,10 @@ export function newConfig() { maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, // default max bid - maxBid: DEFAULT_MAXBID_VALUE + maxBid: DEFAULT_MAXBID_VALUE, + userSync: { + topics: DEFAULT_IFRAMES_CONFIG + } }; Object.defineProperties(newConfig, diff --git a/src/consentHandler.js b/src/consentHandler.js index 19137d9a422..87d1e1a6e23 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -10,14 +10,6 @@ import {config} from './config.js'; */ export const VENDORLESS_GVLID = Object.freeze({}); -/** - * Placeholder gvlid for when device.ext.cdep is present (Privacy Sandbox cookie deprecation label). When this value is used as gvlid, the gdpr - * enforcement module will look to see that publisher consent was given. - * - * see https://github.com/prebid/Prebid.js/issues/10516 - */ -export const FIRST_PARTY_GVLID = Object.freeze({}); - export class ConsentHandler { #enabled; #data; @@ -108,7 +100,6 @@ class UspConsentHandler extends ConsentHandler { const consentData = this.getConsentData(); if (consentData && this.generatedTime) { return { - usp: consentData, generatedAt: this.generatedTime }; } diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 65c3db65974..5024a0ee184 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -33,7 +33,6 @@ export const enrichFPD = hook('sync', (fpd) => { return GreedyPromise.all(promArr) .then(([ortb2, sua, cdep]) => { const ri = dep.getRefererInfo(); - mergeLegacySetConfigs(ortb2); Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { const data = getEnrichments(ortb2, ri); if (data && Object.keys(data).length > 0) { @@ -64,17 +63,6 @@ export const enrichFPD = hook('sync', (fpd) => { }); }); -function mergeLegacySetConfigs(ortb2) { - // merge in values from "legacy" setConfig({app, site, device}) - // TODO: deprecate these eventually - ['app', 'site', 'device'].forEach(prop => { - const cfg = config.getConfig(prop); - if (cfg != null) { - ortb2[prop] = mergeDeep({}, cfg, ortb2[prop]); - } - }) -} - function winFallback(fn) { try { return fn(dep.getWindowTop()); diff --git a/src/prebid.js b/src/prebid.js index df8ce019ed1..4c09966482e 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -138,10 +138,29 @@ function validateVideoMediaType(adUnit) { } function validateNativeMediaType(adUnit) { + function err(msg) { + logError(`Error in adUnit "${adUnit.code}": ${msg}. Removing native request from ad unit`, adUnit); + delete validatedAdUnit.mediaTypes.native; + return validatedAdUnit; + } + function checkDeprecated(onDeprecated) { + for (const key of ['sendTargetingKeys', 'types']) { + if (native.hasOwnProperty(key)) { + const res = onDeprecated(key); + if (res) return res; + } + } + } const validatedAdUnit = deepClone(adUnit); const native = validatedAdUnit.mediaTypes.native; // if native assets are specified in OpenRTB format, remove legacy assets and print a warn. if (native.ortb) { + if (native.ortb.assets?.some(asset => !isNumber(asset.id) || asset.id < 0 || asset.id % 1 !== 0)) { + return err('native asset ID must be a nonnegative integer'); + } + if (checkDeprecated(key => err(`ORTB native requests cannot specify "${key}"`))) { + return validatedAdUnit; + } const legacyNativeKeys = Object.keys(NATIVE_KEYS).filter(key => NATIVE_KEYS[key].includes('hb_native_')); const nativeKeys = Object.keys(native); const intersection = nativeKeys.filter(nativeKey => legacyNativeKeys.includes(nativeKey)); @@ -149,6 +168,8 @@ function validateNativeMediaType(adUnit) { logError(`when using native OpenRTB format, you cannot use legacy native properties. Deleting ${intersection} keys from request.`); intersection.forEach(legacyKey => delete validatedAdUnit.mediaTypes.native[legacyKey]); } + } else { + checkDeprecated(key => `mediaTypes.native.${key} is deprecated, consider using native ORTB instead`, adUnit); } if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); @@ -402,26 +423,7 @@ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { logError('window.googletag is not defined on the page'); return; } - - // get our ad unit codes - let targetingSet = targeting.getAllTargeting(adUnit); - - // first reset any old targeting - targeting.resetPresetTargeting(adUnit, customSlotMatching); - - // now set new targeting keys - targeting.setTargetingForGPT(targetingSet, customSlotMatching); - - Object.keys(targetingSet).forEach((adUnitCode) => { - Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => { - if (targetingKey === 'hb_adid') { - auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET); - } - }); - }); - - // emit event - events.emit(SET_TARGETING, targetingSet); + targeting.setTargetingForGPT(adUnit, customSlotMatching); }; /** @@ -641,7 +643,7 @@ export function executeCallbacks(fn, reqBidsConfigObj) { } } -// This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that gdprEnforcement module is added or not +// This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that tcfControl module is added or not pbjsInstance.requestBids.before(executeCallbacks, 49); /** diff --git a/src/prebid.public.js b/src/prebid.public.js new file mode 100644 index 00000000000..f05e671ac24 --- /dev/null +++ b/src/prebid.public.js @@ -0,0 +1 @@ +export {default} from './prebid.js'; diff --git a/src/targeting.js b/src/targeting.js index d3fb3878248..0c4874fc50b 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -21,9 +21,18 @@ import {ADPOD} from './mediaTypes.js'; import {hook} from './hook.js'; import {bidderSettings} from './bidderSettings.js'; import {find, includes} from './polyfill.js'; -import { BID_STATUS, JSON_MAPPING, DEFAULT_TARGETING_KEYS, TARGETING_KEYS, NATIVE_KEYS, STATUS } from './constants.js'; +import { + BID_STATUS, + DEFAULT_TARGETING_KEYS, + EVENTS, + JSON_MAPPING, + NATIVE_KEYS, + STATUS, + TARGETING_KEYS +} from './constants.js'; import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; import {getTTL} from './bidTTL.js'; +import * as events from './events.js'; var pbTargetingKeys = []; @@ -124,6 +133,22 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { } } +/** + * Return a map where each code in `adUnitCodes` maps to a list of GPT slots that match it. + * + * @param {Array} adUnitCodes + * @param customSlotMatching + * @param getSlots + * @return {{[p: string]: any}} + */ +export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()) { + return getSlots().reduce((auToSlots, slot) => { + const customMatch = isFn(customSlotMatching) && customSlotMatching(slot); + Object.keys(auToSlots).filter(isFn(customMatch) ? customMatch : isAdUnitCodeMatchingSlot(slot)).forEach(au => auToSlots[au].push(slot)); + return auToSlots; + }, Object.fromEntries(adUnitCodes.map(au => [au, []]))); +} + /** * @typedef {Object.} targeting * @property {string} targeting_key @@ -144,22 +169,13 @@ export function newTargeting(auctionManager) { targeting.resetPresetTargeting = function(adUnitCode, customSlotMatching) { if (isGptPubadsDefined()) { const adUnitCodes = getAdUnitCodes(adUnitCode); - const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code)); let unsetKeys = pbTargetingKeys.reduce((reducer, key) => { reducer[key] = null; return reducer; }, {}); - window.googletag.pubads().getSlots().forEach(slot => { - let customSlotMatchingFunc = isFn(customSlotMatching) && customSlotMatching(slot); - // reset only registered adunits - adUnits.forEach(unit => { - if (unit.code === slot.getAdUnitPath() || - unit.code === slot.getSlotElementId() || - (isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) { - slot.updateTargetingFromMap(unsetKeys); - } - }); - }); + Object.values(getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching)).forEach((slots) => { + slots.forEach(slot => slot.updateTargetingFromMap(unsetKeys)) + }) } }; @@ -415,27 +431,39 @@ export function newTargeting(auctionManager) { return targetingObj; } - /** - * Sets targeting for DFP - * @param {Object.>} targetingConfig - */ - targeting.setTargetingForGPT = function(targetingConfig, customSlotMatching) { - window.googletag.pubads().getSlots().forEach(slot => { - Object.keys(targetingConfig).filter(customSlotMatching ? customSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)) - .forEach(targetId => { - Object.keys(targetingConfig[targetId]).forEach(key => { - let value = targetingConfig[targetId][key]; - if (typeof value === 'string' && value.indexOf(',') !== -1) { - // due to the check the array will be formed only if string has ',' else plain string will be assigned as value - value = value.split(','); - } - targetingConfig[targetId][key] = value; - }); - logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingConfig[targetId]); - slot.updateTargetingFromMap(targetingConfig[targetId]) - }) + targeting.setTargetingForGPT = hook('sync', function (adUnit, customSlotMatching) { + // get our ad unit codes + let targetingSet = targeting.getAllTargeting(adUnit); + + let resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null])); + + Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => { + slots.forEach(slot => { + // now set new targeting keys + Object.keys(targetingSet[targetId]).forEach(key => { + let value = targetingSet[targetId][key]; + if (typeof value === 'string' && value.indexOf(',') !== -1) { + // due to the check the array will be formed only if string has ',' else plain string will be assigned as value + value = value.split(','); + } + targetingSet[targetId][key] = value; + }); + logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingSet[targetId]); + slot.updateTargetingFromMap(Object.assign({}, resetMap, targetingSet[targetId])) + }) }) - }; + + Object.keys(targetingSet).forEach((adUnitCode) => { + Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => { + if (targetingKey === 'hb_adid') { + auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET); + } + }); + }); + + // emit event + events.emit(EVENTS.SET_TARGETING, targetingSet); + }, 'setTargetingForGPT'); /** * normlizes input to a `adUnit.code` array diff --git a/src/userSync.js b/src/userSync.js index 1b684de6de0..d8f2238007d 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -25,7 +25,7 @@ export const USERSYNC_DEFAULT_CONFIG = { }, syncsPerBidder: 5, syncDelay: 3000, - auctionDelay: 0 + auctionDelay: 500 }; // Set userSync default values diff --git a/test/mocks/ortbConverter.js b/test/mocks/ortbConverter.js new file mode 100644 index 00000000000..446fac4629a --- /dev/null +++ b/test/mocks/ortbConverter.js @@ -0,0 +1,8 @@ +import {defaultProcessors} from '../../libraries/ortbConverter/converter.js'; +import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; + +beforeEach(() => { + // disable caching of default processors so that tests do not freeze a subset for other tests + defaultProcessors.clear(); + pbsExtensions.clear(); +}); diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index b775ec76e9b..fcc388c2d3b 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -23,19 +23,19 @@ describe('adLoader', function () { }); it('only allows whitelisted vendors to load scripts', function () { - adLoader.loadExternalScript('someURL', 'criteo'); + adLoader.loadExternalScript('someURL', 'debugging'); expect(utilsLogErrorStub.called).to.be.false; expect(utilsinsertElementStub.called).to.be.true; }); it('should not load cached script again', function() { - adLoader.loadExternalScript('someURL', 'criteo'); + adLoader.loadExternalScript('someURL', 'debugging'); expect(utilsinsertElementStub.called).to.be.false; }); it('callback function can be passed to the function', function() { let callback = function() {}; - adLoader.loadExternalScript('someURL1', 'criteo', callback); + adLoader.loadExternalScript('someURL1', 'debugging', callback); expect(utilsinsertElementStub.called).to.be.true; }); @@ -61,11 +61,11 @@ describe('adLoader', function () { } const doc1 = getDocSpec(); const doc2 = getDocSpec(); - adLoader.loadExternalScript('someURL', 'criteo', () => {}, doc1); - adLoader.loadExternalScript('someURL', 'criteo', () => {}, doc1); - adLoader.loadExternalScript('someURL', 'criteo', () => {}, doc1); - adLoader.loadExternalScript('someURL', 'criteo', () => {}, doc2); - adLoader.loadExternalScript('someURL', 'criteo', () => {}, doc2); + adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc1); + adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc1); + adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc1); + adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc2); + adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc2); expect(utilsinsertElementStub.callCount).to.equal(2); }); }); @@ -88,7 +88,7 @@ describe('adLoader', function () { } }, attrs = {'z': 'A', 'y': 2}; - let script = adLoader.loadExternalScript('someUrl', 'criteo', undefined, doc, attrs); + let script = adLoader.loadExternalScript('someUrl', 'debugging', undefined, doc, attrs); expect(script.z).to.equal('A'); expect(script.y).to.equal(2); }); diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 80ee0dd6cd2..7fa9075e802 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -147,26 +147,6 @@ describe('FPD enrichment', () => { expect(ortb2.site.publisher.domain).to.eql('pub.com'); }); }); - - it('respects config set through setConfig({site})', () => { - sandbox.stub(dep, 'getRefererInfo').callsFake(() => ({ - page: 'www.example.com', - ref: 'referrer.com', - })); - config.setConfig({ - site: { - ref: 'override.com', - priority: 'lower' - } - }); - return fpd({site: {priority: 'highest'}}).then(ortb2 => { - sinon.assert.match(ortb2.site, { - page: 'www.example.com', - ref: 'override.com', - priority: 'highest' - }) - }) - }) }); describe('device', () => { @@ -214,44 +194,9 @@ describe('FPD enrichment', () => { expect(ortb2.device.language).to.eql('lang'); }) }); - - it('respects setConfig({device})', () => { - win.navigator.userAgent = 'ua'; - win.navigator.language = 'lang'; - config.setConfig({ - device: { - language: 'override', - priority: 'lower' - } - }); - return fpd({device: {priority: 'highest'}}).then(ortb2 => { - sinon.assert.match(ortb2.device, { - language: 'override', - priority: 'highest', - ua: 'ua' - }) - }) - }); }); }); - describe('app', () => { - it('respects setConfig({app})', () => { - config.setConfig({ - app: { - priority: 'lower', - prop: 'value' - } - }); - return fpd({app: {priority: 'highest'}}).then(ortb2 => { - sinon.assert.match(ortb2.app, { - priority: 'highest', - prop: 'value' - }) - }) - }) - }) - describe('regs', () => { describe('gpc', () => { let win; diff --git a/test/spec/fpd/gdpr_spec.js b/test/spec/fpd/gdpr_spec.js index 8fc04815112..68303657939 100644 --- a/test/spec/fpd/gdpr_spec.js +++ b/test/spec/fpd/gdpr_spec.js @@ -1,5 +1,5 @@ import {gdprDataHandler} from '../../../src/adapterManager.js'; -import {enrichFPDHook} from '../../../modules/consentManagement.js'; +import {enrichFPDHook} from '../../../modules/consentManagementTcf.js'; describe('GDPR FPD enrichment', () => { let sandbox, consent; diff --git a/test/spec/fpd/oneClient.js b/test/spec/fpd/oneClient_spec.js similarity index 100% rename from test/spec/fpd/oneClient.js rename to test/spec/fpd/oneClient_spec.js diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 936e7cee074..35c8e31ecfe 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -1539,7 +1539,6 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - ttxRequest.imp[0].video.plcmt = 1; ttxRequest.imp[0].video.startdelay = 0; const serverRequest = new ServerRequestBuilder() @@ -1558,7 +1557,7 @@ describe('33acrossBidAdapter:', function () { ); const ttxRequest = new TtxRequestBuilder() - .withVideo({startdelay: -2, plcmt: 1}) + .withVideo({startdelay: -2}) .withProduct('instream') .build(); @@ -1567,36 +1566,8 @@ describe('33acrossBidAdapter:', function () { expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); - it('overrides the placement value', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({ - plcmt: 2, // Incorrect placement value for an instream video - placement: 2, // Placement specified in the DEPRECATED field. - context: 'instream' - }) - .build() - ); - - const ttxRequest = new TtxRequestBuilder() - .withVideo() - .withProduct('instream') - .build(); - - ttxRequest.imp[0].video.plcmt = 1; - ttxRequest.imp[0].video.placement = 1; - ttxRequest.imp[0].video.startdelay = 0; - - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - - expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); - }); - context('when the placement is still specified in the DEPRECATED `placement` field', function() { - it('overwrites its value and sets it in the recent `plcmt` field as well', function() { + it('does not overwrite its value and does not set it in the recent `plcmt` field as well', function() { const bidRequests = ( new BidRequestsBuilder() .withVideo({ @@ -1611,8 +1582,7 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - ttxRequest.imp[0].video.plcmt = 1; - ttxRequest.imp[0].video.placement = 1; + ttxRequest.imp[0].video.placement = 2; ttxRequest.imp[0].video.startdelay = 0; const serverRequest = new ServerRequestBuilder() diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 2454d790f90..6ec3554d353 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -53,27 +53,149 @@ describe('33acrossIdSystem', () => { expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; }); - context('if the use of a first-party ID has been enabled', () => { - context('and the response includes a first-party ID', () => { - context('and the enabled storage types include "cookie"', () => { - it('should store the provided first-party ID in a cookie', () => { + const additionalOptions = { + 'by an option': { storeFpid: true, storeTpid: true }, + 'by default': { } // No storeFpid, default value should be true + }; + + Object.entries(additionalOptions).forEach(([caseTitle, opts]) => { + context(`if the use of a first-party ID has been enabled ${caseTitle}`, () => { + context('and the response includes a first-party ID', () => { + context('and the enabled storage types include "cookie"', () => { + it('should store the provided first-party ID in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); + + context('and the enabled storage types include "html5"', () => { + it('should store the provided first-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided first-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); + }); + }); + + context('and the response lacks a first-party ID', () => { + it('should wipe any existing first-party ID from storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + ...opts }, - enabledStorageTypes: [ 'cookie' ], - storage: { expires: 30 } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); const [request] = server.requests; + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { @@ -81,66 +203,149 @@ describe('33acrossIdSystem', () => { }, JSON.stringify({ succeeded: true, data: { - envelope: 'foo', - fp: 'bar' + envelope: 'foo' // no 'fp' field }, expires: 1645667805067 })); - expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdFp')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); }); }); + }); - context('and the enabled storage types include "html5"', () => { - it('should store the provided first-party ID in local storage', () => { - const completeCallback = () => {}; - - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ - params: { - pid: '12345', - storeFpid: true - }, - enabledStorageTypes: [ 'html5' ], - storage: {} + context(`if the use of a supplemental third-party ID has been enabled ${caseTitle}`, () => { + context('and the response includes a third-party ID', () => { + context('and the enabled storage type include "cookie"', () => { + it('should store the provided third-party ID in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); }); + }); - callback(completeCallback); + context('and the enabled storage types include "html5"', () => { + it('should store the provided third-party ID in local storage', () => { + const completeCallback = () => {}; - const [request] = server.requests; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); - const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + callback(completeCallback); - request.respond(200, { - 'Content-Type': 'application/json' - }, JSON.stringify({ - succeeded: true, - data: { - envelope: 'foo', - fp: 'bar' - }, - expires: 1645667805067 - })); + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.true; - expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.true; + setDataInLocalStorage.restore(); + }); + }); - setDataInLocalStorage.restore(); + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided third-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); }); }); - context('and the enabled storage types are "cookie" and "html5"', () => { - it('should store the provided first-party ID in each storage type', () => { + context('and the response lacks a third-party ID', () => { + it('should wipe any existing third-party ID from storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + ...opts }, - enabledStorageTypes: [ 'cookie', 'html5' ], + enabledStorageTypes: [ 'html5' ], storage: {} }); @@ -148,53 +353,52 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); - const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ succeeded: true, data: { - envelope: 'foo', - fp: 'bar' + envelope: 'foo' // no 'tp' field }, expires: 1645667805067 })); - expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; - expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.true; + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdTp')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdTp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); - setDataInLocalStorage.restore(); }); }); }); + }); - context('and the response lacks a first-party ID', () => { - it('should wipe any existing first-party ID from storage', () => { + context('if the use of a first-party ID has been disabled', () => { + context('and the response includes a first-party ID', () => { + it('should not store the provided first-party ID in a cookie', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + storeFpid: false }, - enabledStorageTypes: [ 'html5' ], - storage: {} + enabledStorageTypes: [ 'cookie' ], + storage: { + expires: 30 + } }); callback(completeCallback); const [request] = server.requests; - const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { @@ -202,31 +406,63 @@ describe('33acrossIdSystem', () => { }, JSON.stringify({ succeeded: true, data: { - envelope: 'foo' // no 'fp' field + envelope: 'foo', + fp: 'bar' }, expires: 1645667805067 })); - expect(removeDataFromLocalStorage.calledOnceWithExactly('33acrossIdFp')).to.be.true; - expect(setCookie.calledWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.false; - removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); }); + + it('should not store the provided first-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: false + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.false; + + setDataInLocalStorage.restore(); + }); }); }); - context('if the use of a first-party ID has been disabled (default value)', () => { - context('and the response includes a first-party ID', () => { - it('should not store the provided first-party ID in a cookie', () => { + context('if the use of a supplemental third-party ID has been disabled', () => { + context('and the response includes a third-party ID', () => { + it('should not store the provided third-party ID in a cookie', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { - pid: '12345' - // no storeFpid param + pid: '12345', + storeTpid: false }, enabledStorageTypes: [ 'cookie' ], storage: { @@ -239,8 +475,6 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); - sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -248,25 +482,23 @@ describe('33acrossIdSystem', () => { succeeded: true, data: { envelope: 'foo', - fp: 'bar' + tp: 'bar' }, expires: 1645667805067 })); - expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.false; + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax')).to.be.false; setCookie.restore(); - cookiesAreEnabled.restore(); - domainUtils.domainOverride.restore(); }); - it('should not store the provided first-party ID in local storage', () => { + it('should not store the provided third-party ID in local storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { - pid: '12345' - // no storeFpid param + pid: '12345', + storeTpid: false }, enabledStorageTypes: [ 'html5' ], storage: {} @@ -284,12 +516,12 @@ describe('33acrossIdSystem', () => { succeeded: true, data: { envelope: 'foo', - fp: 'bar' + tp: 'bar' }, expires: 1645667805067 })); - expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.false; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.false; setDataInLocalStorage.restore(); }); @@ -314,7 +546,6 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { @@ -332,7 +563,6 @@ describe('33acrossIdSystem', () => { removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); }); }); @@ -590,36 +820,75 @@ describe('33acrossIdSystem', () => { }); }); - context('when a first-party ID is present only in one of the enabled storage types', () => { - it('should call endpoint with the first-party ID found', () => { + context('when a first-party ID is not present in storage', () => { + it('should not call endpoint with the first-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).not.to.contain('fp='); + }); + }); + + context('when a third-party ID is present in local storage', () => { + it('should call endpoint with the encoded third-party ID included', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' }, - enabledStorageTypes: [ 'cookie', 'html5' ], + enabledStorageTypes: [ 'html5' ], storage: {} }); - sinon.stub(storage, 'getCookie') - .withArgs('33acrossIdFp') - .returns(''); sinon.stub(storage, 'getDataFromLocalStorage') - .withArgs('33acrossIdFp') - .returns('33acrossIdFpValue'); + .withArgs('33acrossIdTp') + .returns('33acrossIdTpValue+'); callback(completeCallback); const [request] = server.requests; - expect(request.url).to.contain('fp=33acrossIdFpValue'); + expect(request.url).to.contain('tp=33acrossIdTpValue%2B'); + + storage.getDataFromLocalStorage.restore(); + }); + }); + + context('when a third-party ID is present in cookie storage', () => { + it('should call endpoint with the third-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie' ], + storage: {} + }); + + sinon.stub(storage, 'getCookie') + .withArgs('33acrossIdTp') + .returns('33acrossIdTpValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('tp=33acrossIdTpValue'); storage.getCookie.restore(); }); }); - context('when a first-party ID is not present in storage', () => { - it('should not call endpoint with the first-party ID included', () => { + context('when a third-party ID is not present in storage', () => { + it('should not call endpoint with the third-party ID included', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { @@ -631,7 +900,7 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; - expect(request.url).not.to.contain('fp='); + expect(request.url).not.to.contain('tp='); }); }); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/AsteriobidPbmAnalyticsAdapter_spec.js similarity index 98% rename from test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js rename to test/spec/modules/AsteriobidPbmAnalyticsAdapter_spec.js index 9241fda8c81..57fb5b9a32b 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/AsteriobidPbmAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import prebidmanagerAnalytics, {storage} from 'modules/prebidmanagerAnalyticsAdapter.js'; +import prebidmanagerAnalytics, {storage} from 'modules/AsteriobidPbmAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; diff --git a/test/spec/modules/BTBidAdapter_spec.js b/test/spec/modules/BTBidAdapter_spec.js index e0306abb7f0..2ec0acc424e 100644 --- a/test/spec/modules/BTBidAdapter_spec.js +++ b/test/spec/modules/BTBidAdapter_spec.js @@ -7,11 +7,10 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/consentManagementGpp.js'; -import 'modules/enrichmentFpdModule.js'; -import 'modules/gdprEnforcement.js'; +import 'modules/tcfControl.js'; import 'modules/gppControl_usnat.js'; import 'modules/schain.js'; diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index c14393e267b..663da9c4fb8 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -182,7 +182,7 @@ const AUCTION_ID_ADAGIO = '6fc53663-bde5-427b-ab63-baa9ed296f47' const AUCTION_ID_CACHE = 'b43d24a0-13d4-406d-8176-3181402bafc4'; const AUCTION_ID_CACHE_ADAGIO = 'a9cae98f-efb5-477e-9259-27350044f8db'; -const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { +const BID_ADAGIO = { bidder: 'adagio', auctionId: AUCTION_ID, adUnitCode: '/19968336/header-bid-tag-1', @@ -215,9 +215,9 @@ const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { sid: '42', e_pba_test: true } -}); +}; -const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { +const BID_ANOTHER = { bidder: 'another', auctionId: AUCTION_ID, adUnitCode: '/19968336/header-bid-tag-1', @@ -246,7 +246,7 @@ const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { meta: { advertiserDomains: ['example.com'] } -}); +}; const BID_CACHED = Object.assign({}, BID_ADAGIO, { auctionId: AUCTION_ID_CACHE, diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index ec8486f62ad..4f942e21c0e 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,22 +1,16 @@ -import { expect } from 'chai'; +import * as utils from '../../../src/utils.js'; import { - _features, - internal as adagio, - adagioScriptFromLocalStorageCb, - getAdagioScript, - storage, - setExtraParam, - spec, + BB_RENDERER_URL, ENDPOINT, VERSION, - BB_RENDERER_URL, - GlobalExchange + _internal, + setExtraParam, + spec } from '../../../modules/adagioBidAdapter.js'; -import { loadExternalScript } from '../../../src/adloader.js'; -import * as utils from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; import { NATIVE } from '../../../src/mediaTypes.js'; +import { config } from '../../../src/config.js'; import { executeRenderer } from '../../../src/Renderer.js'; +import { expect } from 'chai'; import { userSync } from '../../../src/userSync.js'; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -73,7 +67,6 @@ const BidderRequestBuilder = function BidderRequestBuilder(options) { }; describe('Adagio bid adapter', () => { - let adagioMock; let utilsMock; let sandbox; let fakeRenderer; @@ -119,17 +112,9 @@ describe('Adagio bid adapter', () => { window.ADAGIO.versions.adagioBidderAdapter = VERSION; window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; - GlobalExchange.clearFeatures(); - GlobalExchange.clearExchangeData(); - - $$PREBID_GLOBAL$$.bidderSettings = { - adagio: { - storageAllowed: true - } - }; + utilsMock = sinon.mock(utils); sandbox = sinon.createSandbox(); - adagioMock = sandbox.mock(adagio); utilsMock = sandbox.mock(utils); }); @@ -137,6 +122,8 @@ describe('Adagio bid adapter', () => { window.ADAGIO = undefined; $$PREBID_GLOBAL$$.bidderSettings = {}; + utilsMock.restore(); + sandbox.restore(); }); @@ -188,7 +175,7 @@ describe('Adagio bid adapter', () => { }); it('should compute organizationId and site params from global BidderSettings config', function() { - sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: true }); + sandbox.stub(_internal, 'getRefererInfo').returns({ reachedTop: true }); sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ siteId: '1000:SITE-NAME' }); @@ -262,11 +249,9 @@ describe('Adagio bid adapter', () => { 'user', 'schain', 'prebidVersion', - 'featuresVersion', 'hasRtd', 'data', 'usIfr', - 'adgjs', ]; it('groups requests by organizationId', function() { @@ -290,10 +275,10 @@ describe('Adagio bid adapter', () => { }); it('should send bid request to ENDPOINT_PB via POST', function() { - sandbox.stub(adagio, 'getDevice').returns({ a: 'a' }); - sandbox.stub(adagio, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); - sandbox.stub(adagio, 'getPageviewId').returns('1234-567'); - sandbox.stub(utils, 'generateUUID').returns('blabla'); + sandbox.stub(_internal, 'getDevice').returns({ a: 'a' }); + sandbox.stub(_internal, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); + // sandbox.stub(_internal, 'getPageviewId').returns('1234-567'); + // sandbox.stub(utils, 'generateUUID').returns('blabla'); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); @@ -323,7 +308,7 @@ describe('Adagio bid adapter', () => { const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' const expectedPageviewId = '56befc26-8cf0-472d-b105-73896df8eb89'; sandbox.stub(utils, 'generateUUID').returns(expectedAuctionId); - sandbox.stub(adagio, 'getPageviewId').returns(expectedPageviewId); + sandbox.stub(_internal, 'getAdagioNs').returns({ pageviewId: expectedPageviewId }); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); @@ -334,78 +319,6 @@ describe('Adagio bid adapter', () => { expect(bid01.params.pageviewId).eq(expectedPageviewId); }); - it('should enqueue computed features for collect usage', function() { - sandbox.stub(Date, 'now').returns(12345); - - const bid01 = new BidRequestBuilder().withParams().build(); - const bidderRequest = new BidderRequestBuilder().build(); - - adagioMock.expects('enqueue').withArgs(sinon.match({ action: 'features' })).atLeast(1); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data).to.have.all.keys(expectedDataKeys); - - adagioMock.verify(); - }); - - describe('with Adagio Rtd Provider', function() { - it('it dont enqueue features from the bidder adapter', function() { - sandbox.stub(adagio, 'hasRtd').returns(true); - const bid01 = new BidRequestBuilder().withParams().build(); - const bidderRequest = new BidderRequestBuilder().build(); - spec.buildRequests([bid01], bidderRequest); - adagioMock.expects('enqueue').withArgs(sinon.match({ action: 'features' })).never(); - adagioMock.verify(); - }); - - it('get feature from ortb2', function() { - sandbox.stub(adagio, 'hasRtd').returns(true); - const bid01 = new BidRequestBuilder().withParams().build(); - bid01.ortb2Imp = { - ext: { data: {adg_rtd: {adunit_position: '1x1'}} } - }; - bid01.ortb2 = { - site: { - ext: - { - data: { - adg_rtd: { features: {} } - } - } - } - }; - const bidderRequest = new BidderRequestBuilder().build(); - const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.adUnits[0].features).to.exist; - expect(requests[0].data.adUnits[0].features.adunit_position).to.equal('1x1'); - }); - }); - - it('should filter some props in case refererDetection.reachedTop is false', function() { - const bid01 = new BidRequestBuilder().withParams().build(); - const bidderRequest = new BidderRequestBuilder({ - refererInfo: { - numIframes: 2, - reachedTop: false, - referer: 'http://example.com/iframe1.html', - stack: [ - null, - 'http://example.com/iframe1.html', - 'http://example.com/iframe2.html' - ], - canonicalUrl: '' - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0].data).to.have.all.keys(expectedDataKeys); - expect(requests[0].data.adUnits[0].features).to.exist; - expect(requests[0].data.adUnits[0].features.url).to.not.exist; - }); - it('should force split keyword param into a string', function() { const bid01 = new BidRequestBuilder().withParams({ splitKeyword: 1234 @@ -476,10 +389,102 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[3].params.dataLayer).to.not.exist; }); + describe('with adagioRtdProvider enrichments', function() { + const adUnitRtdEnrichments = { + ortb2: { + site: { + ext: { + data: { + adg_rtd: { + features: { + page_dimensions: '1024x768', + viewport_dimensions: '1024x768', + user_timestamp: '111111111', + dom_loading: '111111111', + } + } + }}} + }, + ortb2Imp: { + ext: { + data: { + adg_rtd: { + adunit_position: '1x1' + } + } + } + } + } + const rtdEnrichments = { + ortb2: { + site: { + ext: { + data: { + adg_rtd: { + session: { + new: true, + rnd: 0.0666 + }, + } + } + } + } + } + } + + it('should add features and data to the request if exists', function() { + const bid01 = new BidRequestBuilder(adUnitRtdEnrichments).withParams().build(); + const bidderRequest = new BidderRequestBuilder(rtdEnrichments).build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.data).to.deep.equal({ + session: { + new: true, + rnd: 0.0666 + } + }); + + expect(requests[0].data.adUnits[0].features).to.deep.equal({ + page_dimensions: '1024x768', + viewport_dimensions: '1024x768', + user_timestamp: '111111111', + dom_loading: '111111111', + adunit_position: '1x1', + print_number: '1' + }) + }); + + it('should add an only "print_number" in features object if ortb2 is not properly defined', function() { + const bid01 = new BidRequestBuilder({ + ortb2: {}, + bidderRequestsCount: 2 + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.adUnits[0].features).to.deep.equal({ + print_number: '2' + }); + }); + + it('should send data.session with default if the ortb2 ext is not properly defined', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + sandbox.stub(Math, 'random').returns(0.444); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.data.session).to.exist; + expect(requests[0].data.data.session.new).to.equal(true); + expect(requests[0].data.data.session.rnd).to.equal(0.444); + }); + }); + describe('With video mediatype', function() { context('Outstream video', function() { - it('should logWarn if user does not set renderer.backupOnly: true', function() { - sandbox.spy(utils, 'logWarn'); + it('should set playerName = "other" if user does not set renderer.backupOnly: true', function() { const bid01 = new BidRequestBuilder({ adUnitCode: 'adunit-code-01', mediaTypes: { @@ -498,7 +503,39 @@ describe('Adagio bid adapter', () => { const request = spec.buildRequests([bid01], bidderRequest)[0]; expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('other'); - sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.'); + }); + + it('should set playerName = "adagio" if user does not set a renderer or set `renderer.backupOnly: true`', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + } + }, + }).withParams().build(); + const bid02 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-02', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + renderer: { + url: 'https://url.tld', + render: () => true, + backupOnly: true + } + } + }, + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bid01, bid02], bidderRequest)[0]; + + expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('adagio'); + expect(request.data.adUnits[1].mediaTypes.video.playerName).to.equal('adagio'); }); }); @@ -1173,25 +1210,24 @@ describe('Adagio bid adapter', () => { it('should populate ADAGIO queue with ssp-data', function() { sandbox.stub(Date, 'now').returns(12345); + sandbox.stub(_internal, 'hasRtd').returns(true); + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + spec.interpretResponse(serverResponse, bidRequest); - adagioMock.expects('enqueue').withExactArgs({ + expect(spy.withArgs({ action: 'ssp-data', ts: 12345, data: serverResponse.body.data - }).once(); - - spec.interpretResponse(serverResponse, bidRequest); - - adagioMock.verify(); + }).calledOnce).to.be.true; }); it('should properly try-catch an exception and return an empty array', function() { - sandbox.stub(adagio, 'enqueue').throws(); - utilsMock.expects('logError').once(); - + sandbox.stub(_internal, 'hasRtd').returns(true); + sandbox.stub(_internal, 'getAdagioNs').returns({ queue: () => { throw new Error('test') } }); + const spy = sandbox.spy(utils, 'logError'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array').length(0); - - utilsMock.verify(); + expect(spy.calledOnce).to.be.true; }); describe('Response with video outstream', function() { @@ -1495,188 +1531,6 @@ describe('Adagio bid adapter', () => { }); }); - describe('Adagio features when prebid in top.window', function() { - it('should return all expected features when all expected bidder params are available', function() { - sandbox.stub(window.top.document, 'getElementById').returns( - fixtures.getElementById() - ); - sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); - sandbox.stub(utils, 'inIframe').returns(false); - - const bidRequest = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] } - } - }).withParams().build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.adunit_position).to.match(/^[\d]+x[\d]+$/); - expect(result.page_dimensions).to.match(/^[\d]+x[\d]+$/); - expect(result.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); - expect(result.print_number).to.be.a('String'); - expect(result.dom_loading).to.be.a('String'); - expect(result.user_timestamp).to.be.a('String'); - expect(result.url).to.not.exist; - expect(result.device).to.not.exist; - expect(result.os).to.not.exist; - expect(result.browser).to.not.exist; - }); - - it('should return all expected features when `adUnitElementId` param is not available', function() { - sandbox.stub(utils, 'inIframe').returns(false); - - const bidRequest = new BidRequestBuilder({ - params: { - organizationId: '1000', - placement: 'PAVE_ATF', - site: 'SITE-NAME' - }, - 'mediaTypes': { - banner: { sizes: [[300, 250]] } - } - }).build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.adunit_position).to.not.exist; - expect(result.page_dimensions).to.be.a('String'); - expect(result.viewport_dimensions).to.be.a('String'); - expect(result.print_number).to.be.a('String'); - expect(result.dom_loading).to.be.a('String'); - expect(result.user_timestamp).to.be.a('String'); - }); - - it('should return `adunit_position` feature when the slot is hidden with value 0x0', function () { - const elem = fixtures.getElementById('0', '0', '0', '0'); - sandbox.stub(window.top.document, 'getElementById').returns(elem); - sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'none' }); - sandbox.stub(utils, 'inIframe').returns(false); - - const bidRequest = new BidRequestBuilder({ - mediaTypes: { - banner: { sizes: [[300, 250]] }, - }, - }) - .withParams() - .build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.adunit_position).to.equal('0x0'); - }); - }); - - describe('Adagio features when prebid in Safeframe', function() { - beforeEach(function () { - window.$sf = $sf; - }); - - afterEach(function () { - delete window.$sf; - }); - - it('should return all expected features when prebid is in safeframe iframe', function() { - sandbox.stub(window.$sf.ext, 'geom').returns({ - win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, - self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, - }); - - const bidRequest = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] } - } - }).withParams().build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.page_dimensions).to.not.exist; - expect(result.viewport_dimensions).to.be.a('String'); - expect(result.print_number).to.be.a('String'); - expect(result.dom_loading).to.be.a('String'); - expect(result.user_timestamp).to.be.a('String'); - expect(result.adunit_position).to.exist; - }); - - it('should return all expected features when prebid safeframe api not properly implemented', function() { - const bidRequest = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] } - } - }).withParams().build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.page_dimensions).to.not.exist; - expect(result.viewport_dimensions).to.not.exist; - expect(result.print_number).to.be.a('String'); - expect(result.dom_loading).to.be.a('String'); - expect(result.user_timestamp).to.be.a('String'); - expect(result.adunit_position).to.not.exist; - }); - - it('should return all expected features when prebid safeframe api not properly implemented bis', function() { - window.$sf.ext.geom = undefined; - - const bidRequest = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] } - } - }).withParams().build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.page_dimensions).to.not.exist; - expect(result.viewport_dimensions).to.not.exist; - expect(result.print_number).to.be.a('String'); - expect(result.dom_loading).to.be.a('String'); - expect(result.user_timestamp).to.be.a('String'); - expect(result.adunit_position).to.not.exist; - }); - }); - - describe('Adagio features when prebid in crossdomain iframe', function() { - it('should return all expected features', function() { - sandbox.stub(utils, 'canAccessWindowTop').returns(false); - - const bidRequest = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] } - } - }).withParams().build(); - - const bidderRequest = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bidRequest], bidderRequest); - const result = requests[0].data.adUnits[0].features; - - expect(result.page_dimensions).to.not.exist; - expect(result.viewport_dimensions).to.not.exist; - expect(result.print_number).to.be.a('String'); - expect(result.dom_loading).to.be.a('String'); - expect(result.user_timestamp).to.be.a('String'); - expect(result.adunit_position).to.not.exist; - }); - }); - describe('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { const bidderRequest = new BidderRequestBuilder({ @@ -1690,7 +1544,7 @@ describe('Adagio bid adapter', () => { } }).build(); - expect(adagio.getSite(bidderRequest)).to.deep.equal({ + expect(_internal.getSite(bidderRequest)).to.deep.equal({ domain: 'test.io', page: 'https://test.io/article/a.html', referrer: 'https://google.com', @@ -1725,7 +1579,7 @@ describe('Adagio bid adapter', () => { refererInfo: info }).build(); - expect(adagio.getSite(bidderRequest)).to.deep.equal({ + expect(_internal.getSite(bidderRequest)).to.deep.equal({ domain: 'level.io', page: 'http://level.io/', referrer: 'https://google.com', @@ -1755,119 +1609,11 @@ describe('Adagio bid adapter', () => { refererInfo: info }).build(); - const s = adagio.getSite(bidderRequest) + const s = _internal.getSite(bidderRequest) expect(s.domain).equal('example.com') expect(s.page).equal('http://example.com/iframe1.html') expect(s.referrer).match(/^https?:\/\/.+/); expect(s.top).equal(false) }); }); - - describe('adagioScriptFromLocalStorageCb()', function() { - const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; - const INVALID_HASH = 'invalid'; - const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; - const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; - const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; - - beforeEach(function() { - localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); - }); - - describe('getAdagioScript', function() { - it('should run storage.getDataFromLocalStorage callback and call adagioScriptFromLocalStorageCb() ', function() { - sandbox.spy(adagio, 'adagioScriptFromLocalStorageCb'); - const getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsArg(1); - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - - getAdagioScript(); - - sinon.assert.callCount(getDataFromLocalStorageStub, 1); - sinon.assert.callCount(adagio.adagioScriptFromLocalStorageCb, 1); - }); - - it('should load external script if the user consent', function() { - sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true); - getAdagioScript(); - - expect(loadExternalScript.called).to.be.true; - }); - - it('should not load external script if the user does not consent', function() { - sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); - getAdagioScript(); - - expect(loadExternalScript.called).to.be.false; - }); - - it('should remove the localStorage key if exists and the user does not consent', function() { - sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, 'the script'); - - getAdagioScript(); - - expect(loadExternalScript.called).to.be.false; - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - }); - }); - - it('should verify valid hash with valid script', function () { - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - - utilsMock.expects('logInfo').withExactArgs('Adagio: start script.').once(); - utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); - utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); - - adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.equals('// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.verify(); - }); - - it('should verify valid hash with invalid script', function () { - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT); - - utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); - utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); - utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); - - adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify invalid hash with valid script', function () { - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - - utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); - utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never(); - utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once(); - - adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify missing hash', function () { - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); - - utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never(); - utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').once(); - utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never(); - - adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should return false if content script does not exist in localStorage', function() { - sandbox.spy(utils, 'logWarn'); - expect(adagioScriptFromLocalStorageCb(null)).to.be.undefined; - sinon.assert.callCount(utils.logWarn, 1); - sinon.assert.calledWith(utils.logWarn, 'Adagio: script not found.'); - }); - }); }); diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js deleted file mode 100755 index 3f26cd7749f..00000000000 --- a/test/spec/modules/adbookpspBidAdapter_spec.js +++ /dev/null @@ -1,1344 +0,0 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { - spec, - storage, - DEFAULT_BIDDER_CONFIG, - VERSION, - common, -} from '../../../modules/adbookpspBidAdapter.js'; - -describe('adbookpsp bid adapter', () => { - let sandbox; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - - sandbox - .stub(common, 'generateUUID') - .returns('54444444-5444-4444-9444-544444444444'); - sandbox.stub(common, 'getWindowDimensions').returns({ - innerWidth: 100, - innerHeight: 100, - }); - }); - - afterEach(function () { - sandbox.restore(); - }); - - describe('isBidRequestValid()', () => { - it('should return false when there is no banner in mediaTypes', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when orgId and placementId is not defined', () => { - const bid = utils.deepClone(bannerBid); - delete bid.params.placementId; - delete bid.params.orgId; - - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - - it('should return true when orgId is set in config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params.placementId; - delete bid.params.orgId; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('129576'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - expect(spec.isBidRequestValid(mixedBid)).to.equal(true); - }); - - it('should return false when sizes for banner are not specified', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner.sizes; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when sizes for banner are invalid', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner.sizes; - - bid.mediaTypes.banner.sizes = [['123', 'foo']]; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true if player size is set via playerSize', () => { - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); - - it('should return true if player size is set via w and h', () => { - const bid = utils.deepClone(videoBid); - delete bid.mediaTypes.video.playerSize; - - bid.mediaTypes.video.w = 400; - bid.mediaTypes.video.h = 300; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should reutrn false if player size is not set', () => { - const bid = utils.deepClone(videoBid); - delete bid.mediaTypes.video.playerSize; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests()', () => { - it('should build correct request for banner bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns(undefined) - .withArgs('adbookpsp.exchangeUrl') - .returns('https://ex.fattail.com/openrtb2'); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.equal(bannerExchangeRequest); - }); - - it('should build correct request for video bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp') - .returns(DEFAULT_BIDDER_CONFIG) - .withArgs('adbookpsp.exchangeUrl') - .returns(DEFAULT_BIDDER_CONFIG.exchangeUrl) - .withArgs('adbookpsp.orgId') - .returns(undefined); - - const requests = spec.buildRequests([videoBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.include({ - ...videoExchangeRequest, - ext: { - adbook: { - config: DEFAULT_BIDDER_CONFIG, - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, - }); - }); - - it('should build correct request for video bid with w and h', () => { - const bid = utils.deepClone(videoBid); - - delete bid.mediaTypes.video.playerSize; - - bid.mediaTypes.video.w = 400; - bid.mediaTypes.video.h = 300; - - const [request] = spec.buildRequests([bid], bidderRequest); - const requestData = JSON.parse(request.data); - - expect(requestData.imp[0].video.w).to.equal(400); - expect(requestData.imp[0].video.h).to.equal(300); - }); - - it('should build correct request for video bid with both w, h and playerSize', () => { - const bid = utils.deepClone(videoBid); - - bid.mediaTypes.video.w = 640; - bid.mediaTypes.video.h = 480; - - const [request] = spec.buildRequests([bid], bidderRequest); - const requestData = JSON.parse(request.data); - - expect(requestData.imp[0].video.w).to.equal(640); - expect(requestData.imp[0].video.h).to.equal(480); - }); - - it('should build correct request for mixed bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns(undefined) - .withArgs('adbookpsp.exchangeUrl') - .returns('https://ex.fattail.com/openrtb2'); - - const requests = spec.buildRequests([mixedBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.include( - mixedExchangeRequest - ); - }); - - it('should use orgId from config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('129576'); - - const requests = spec.buildRequests([bid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0].ext).to.deep.include({ - adbook: { - orgId: '129576', - }, - }); - }); - - it('should use orgId from adUnit when orgId is also set in config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params.placementId; - - bid.params.orgId = 'adUnitOrgId'; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('configOrgId'); - - const requests = spec.buildRequests([bid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0].ext).to.deep.include({ - adbook: { - orgId: 'adUnitOrgId', - }, - }); - }); - - it('should include in request GDPR options if available', () => { - const request = utils.deepClone(bidderRequest); - - delete request.uspConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - }, - }, - }); - }); - - it('should include in request USP (CPPA) options if available', () => { - const request = utils.deepClone(bidderRequest); - - delete request.gdprConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 0, - ext: { - us_privacy: 'uspConsentString', - }, - }, - }); - }); - - it('should pass valid coppa flag based on config', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const request = utils.deepClone(bidderRequest); - - delete request.gdprConsent; - delete request.uspConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 1, - }, - }); - }); - - it('should pass GDPR, USP (CCPA) and COPPA options', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 1, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - }); - }); - - it('should generate and pass user id when is not present in cookie and local storage is not enabled', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - - expect(rtbRequest.user.id).to.have.lengthOf(36); - }); - - it('should pass user id when is present in cookie', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - sandbox - .stub(storage, 'getCookie') - .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - - expect(rtbRequest.user.id).to.equal( - 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' - ); - }); - - it('should pass user id if is present in local storage', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox - .stub(storage, 'getDataFromLocalStorage') - .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - expect(rtbRequest.user.id).to.equal( - 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' - ); - }); - - it('should regenerate user id if it is invalid', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox.stub(storage, 'getDataFromLocalStorage').returns('foo'); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - expect(rtbRequest.user.id).to.have.lengthOf(36); - }); - - it('should pass schain if available', () => { - const bid = utils.deepClone(bannerBid); - const schain = { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'exchange1.com', - sid: '1234', - hp: 1, - rid: 'bid-request-1', - name: 'publisher', - domain: 'publisher.com', - }, - ], - }; - - bid.schain = schain; - - const requests = spec.buildRequests([bid], bidderRequest); - - expect(JSON.parse(requests[0].data).source).to.deep.include({ - ext: { - schain, - }, - }); - }); - - it('return empty array if there are no valid bid requests', () => { - const requests = spec.buildRequests([], bidderRequest); - - expect(requests).to.deep.equal([]); - }); - - it('should prioritize device information set in config', () => { - const ua = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1'; - - sandbox.stub(common, 'getConfig').withArgs('device').returns({ - ua, - }); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - - expect(JSON.parse(requests[0].data).device.ua).to.equal(ua); - }); - - it('should include bidder config', () => { - const bidderConfig = { - bidTTL: 500, - defaultCurrency: 'USD', - exchangeUrl: 'https://exsb.fattail.com/openrtb2', - winTrackingEnabled: true, - winTrackingUrl: 'https://evsb.fattail.com/wins', - orgId: '129576', - }; - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp') - .returns(bidderConfig); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.ext).to.deep.include({ - adbook: { - config: bidderConfig, - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }); - }); - - it('should use bidder video params if they are set', () => { - const videoBidWithParams = utils.deepClone(videoBid); - const bidderVideoParams = { - api: [1, 2], - mimes: ['video/mp4', 'video/x-flv'], - playbackmethod: [3, 4], - protocols: [5, 6], - minduration: 10, - maxduration: 30, - }; - videoBidWithParams.params.video = bidderVideoParams; - - const requests = spec.buildRequests([videoBidWithParams], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0]).to.deep.include({ - video: { - ...bidderVideoParams, - w: videoBidWithParams.mediaTypes.video.playerSize[0][0], - h: videoBidWithParams.mediaTypes.video.playerSize[0][1], - }, - }); - }); - }); - - describe('interpretResponse()', () => { - it('should correctly interpret valid response', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.defaultCurrency') - .returns(DEFAULT_BIDDER_CONFIG.defaultCurrency) - .withArgs('adbookpsp.bidTTL') - .returns(DEFAULT_BIDDER_CONFIG.bidTTL); - - const response = utils.deepClone(exchangeResponse); - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([ - { - bidderRequestId: '999ccceeee11', - requestId: '9873kfse', - bidId: 'bid123456', - width: 300, - height: 250, - ttl: 300, - cpm: 0.5, - currency: 'USD', - creativeId: '123456789', - mediaType: 'banner', - meta: { - advertiserDomains: ['advertiser.com'], - mediaType: 'banner', - primaryCatId: 'IAB2-1', - secondaryCatIds: ['IAB2-2', 'IAB2-3'], - }, - netRevenue: true, - nurl: 'http://win.example.url', - adUnitCode: 'div-gpt-ad-837465923534-0', - ad: '
ad
', - adId: '5', - adserverTargeting: { - hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index - hb_adid_c_adbookpsp: '5', - hb_deal_adbookpsp: 'werwetwerw', - hb_liid_adbookpsp: '2342345', - hb_ordid_adbookpsp: '567843', - }, - referrer: 'http://prebid-test-page.io:8080/banner.html', - lineItemId: '2342345', - }, - { - ad: '', - adId: '10', - adUnitCode: 'div-gpt-ad-837465923534-0', - adserverTargeting: { - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }, - bidId: 'bid4321', - bidderRequestId: '999ccceeee11', - cpm: 0.45, - creativeId: '543123', - currency: 'USD', - height: 250, - lineItemId: '2121221', - mediaType: 'video', - meta: { - advertiserDomains: ['advertiser.com', 'campaign.advertiser.com'], - mediaType: 'video', - primaryCatId: 'IAB2-3', - secondaryCatIds: [], - }, - netRevenue: true, - nurl: 'http://win.example.url', - referrer: 'http://prebid-test-page.io:8080/banner.html', - requestId: '120kfeske', - ttl: 300, - vastXml: - '', - width: 300, - }, - ]); - }); - - it('should place valid GAM targeting for all bids when multiple bids are present for multiple impressions', () => { - const response = utils.deepClone(exchangeResponse); - - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.have.length(2); - expect(bids[0].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '5', - hb_deal_adbookpsp: 'werwetwerw', - hb_liid_adbookpsp: '2342345', - hb_ordid_adbookpsp: '567843', - }); - expect(bids[1].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }); - }); - - it('should place valid GAM targeting for all bids when multiple bids are present for single impression', () => { - const response = utils.deepClone(exchangeResponse); - - response.seatbid[1].bid[0].impid = '9873kfse'; - - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.have.length(2); - for (const bid of bids) { - expect(bid.adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0,1_0', - hb_adid_c_adbookpsp: '5,10', - hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc', - hb_liid_adbookpsp: '2342345,2121221', - hb_ordid_adbookpsp: '567843,5678234', - }); - } - }); - - it('should return no bids if response id does not match bidderRequestId', () => { - const body = utils.deepClone(exchangeResponse); - body.id = '999'; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should return no bids if response does not include seatbid', () => { - const body = utils.deepClone(exchangeResponse); - delete body.seatbid; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should return no bids if response does not include any bids', () => { - const body = utils.deepClone(exchangeResponse); - body.seatbid = []; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should exclude invalid video bids', () => { - const body = utils.deepClone(exchangeResponse); - - body.seatbid.shift(); - body.seatbid[0].bid[0].adid = 34; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should exclude invalid banner bids', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - body.seatbid.pop(); - - delete body.seatbid[0].bid[0].w; - delete body.seatbid[0].bid[0].h; - - request.imp[0].banner.format.push({ w: 300, h: 600 }); - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(request) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should not include invalid banner bids in targeting map', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - body.seatbid[0].bid[0].h = '600'; - - request.imp[0].banner.format.push({ w: 300, h: 600 }); - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids[0].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }); - }); - - it('should not validate banner bid dimensions if bid request has single size', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - delete body.seatbid[1]; - delete body.seatbid[0].bid[0].h; - delete body.seatbid[0].bid[0].w; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(request) } - ); - - expect(bids.length).to.equal(1); - }); - }); - - describe('getUserSyncs()', () => { - it('should return user syncs if there are included in the response and syncs are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: true, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should not return user syncs if syncs are disabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: false, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([]); - }); - - it('should return image syncs if they are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: false, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should return iframe syncs if they are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should append COPPA status to sync url', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?coppa=1', - }, - ]); - }); - - it('should append GDPR consent data to url', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(false); - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - { gdprApplies: true, consentString: 'gdprConsentString' } - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString', - }, - ]); - }); - - it('should append USP (CCPA) consent string to url', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - undefined, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?us_privacy=uspConsentString', - }, - ]); - }); - - it('should append COPPA, GDPR and USP (CCPA) url params', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - { gdprApplies: true, consentString: 'gdprConsentString' }, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - ]); - }); - - it('should respect url param syntax when appending params', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const response = utils.deepClone(exchangeResponse); - - response.ext.sync[0] = { - type: 'image', - url: 'http://sometest.com/sync/1234567?horseCount=4', - }; - - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: false, - }, - [{ body: response }], - { gdprApplies: true, consentString: 'gdprConsentString' }, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567?horseCount=4&gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - ]); - }); - }); - - describe('onBidWon()', () => { - it('should track win if win tracking is enabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(true) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - }); - - expect( - spy.calledWith( - 'https://ev.fattail.com/wins?impId=requestId&reqId=bidderRequestId&bidId=bidId' - ) - ).to.equal(true); - }); - it('should call bid.nurl if win tracking is enabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(true) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - nurl: 'http://win.example.url', - }); - - expect(spy.calledWith('http://win.example.url')).to.equal(true); - }); - it('should not track win nor call bid.nurl if win tracking is disabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(false) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - nurl: 'http://win.example.url', - }); - - expect(spy.notCalled).to.equal(true); - }); - }); -}); - -const bidderRequest = { - auctionId: 'aaccee333311', - bidderRequestId: '999ccceeee11', - timeout: 200, - refererInfo: { - page: 'http://mock-page.com', - domain: 'mock-page.com', - ref: 'http://example-domain.com/foo', - }, - gdprConsent: { - gdprApplies: 1, - consentString: 'gdprConsentString', - }, - uspConsent: 'uspConsentString', - ortb2: { - source: { - tid: 'aaccee333311' - } - } -}; - -const bannerBid = { - bidder: 'adbookpsp', - params: { - placementId: '12390123', - }, - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600], - ], - }, - }, - adUnitCode: 'div-gpt-ad-837465923534-0', - transactionId: 'sfsf89e-mck3-asf3-fe45-feksjfi123mfs', - bidId: '9873kfse', - bidderRequestId: '999ccceeee11', - auctionId: 'aaccee333311', - lineItemId: 123123123, -}; - -const bannerExchangeRequest = { - id: '999ccceeee11', - device: { - h: 100, - w: 100, - js: true, - ua: navigator.userAgent, - dnt: 0, - }, - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - site: { - domain: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - banner: { - format: [ - { - w: 300, - h: 250, - }, - { - w: 300, - h: 600, - }, - ], - w: 300, - h: 250, - topframe: 0, - pos: 0, - }, - ext: { - adbook: { - placementId: '12390123', - }, - }, - id: '9873kfse', - tagid: 'div-gpt-ad-837465923534-0', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const videoBid = { - bidder: 'adbookpsp', - params: { - placementId: '129576', - }, - mediaTypes: { - video: { - api: [1, 2, 4, 6], - mimes: ['video/mp4'], - playbackmethod: [2, 4, 6], - playerSize: [[400, 300]], - protocols: [3, 4, 7, 8, 10], - }, - }, - adUnitCode: 'div-gpt-ad-9383743831-6', - transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj', - bidId: '120kfeske', - bidderRequestId: '999ccceeee11', - auctionId: 'aaccee333311', - lineItemId: 321321321, -}; - -const videoExchangeRequest = { - id: '999ccceeee11', - device: { - h: 100, - w: 100, - js: true, - ua: navigator.userAgent, - dnt: 0, - }, - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - site: { - domain: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - video: { - api: [1, 2, 4, 6], - h: 300, - mimes: ['video/mp4'], - playbackmethod: [2, 4, 6], - protocols: [3, 4, 7, 8, 10], - w: 400, - }, - ext: { - adbook: { - placementId: '129576', - }, - }, - id: '120kfeske', - tagid: 'div-gpt-ad-9383743831-6', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const mixedBid = { - bidder: 'adbookpsp', - params: { - orgId: '129576', - }, - mediaTypes: { - banner: { - sizes: [[300, 600]], - }, - video: { - mimes: ['video/mp4'], - playerSize: [[300, 600]], - }, - }, - adUnitCode: 'div-gpt-ad-9383743831-5', - transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj', - bidId: '120kfeske', - bidderRequestId: '999ccceeee11', - auctionId: 'aaccee333311', - lineItemId: 12341234, -}; - -const mixedExchangeRequest = { - id: '999ccceeee11', - device: { - h: 100, - w: 100, - js: true, - ua: navigator.userAgent, - dnt: 0, - }, - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - site: { - domain: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - banner: { - format: [ - { - w: 300, - h: 600, - }, - ], - w: 300, - h: 600, - topframe: 0, - pos: 0, - }, - video: { - h: 600, - mimes: ['video/mp4'], - w: 300, - }, - ext: { - adbook: { - orgId: '129576', - }, - }, - id: '120kfeske', - tagid: 'div-gpt-ad-9383743831-5', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const exchangeBidRequest = { - id: '999ccceeee11', - tmax: 200, - imp: [ - { - id: '9873kfse', - banner: { - format: [ - { - w: 300, - h: 250, - }, - ], - }, - video: { - w: 300, - h: 250, - }, - tagid: 'div-gpt-ad-837465923534-0', - }, - { - id: '120kfeske', - banner: { - format: [ - { - w: 300, - h: 250, - }, - ], - }, - video: { - w: 300, - h: 250, - }, - tagid: 'div-gpt-ad-837465923534-0', - }, - ], - source: { - fd: 1, - tid: 'aaccee333311', - }, - site: { - domain: location.hostname, - page: location.href, - ref: 'http://prebid-test-page.io:8080/banner.html', - }, -}; - -const exchangeResponse = { - id: '999ccceeee11', - seatbid: [ - { - seat: 'adbookpsp', - group: 0, - bid: [ - { - id: 'bid123456', - w: 300, - h: 250, - impid: '9873kfse', - price: 0.5, - exp: 300, - crid: '123456789', - adm: '
ad
', - adid: '5', - dealid: 'werwetwerw', - nurl: 'http://win.example.url', - ext: { - liid: '2342345', - ordid: '567843', - }, - cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'], - adomain: ['advertiser.com'], - }, - ], - }, - { - seat: 'adbookpsp', - group: 0, - bid: [ - { - id: 'bid4321', - impid: '120kfeske', - price: 0.45, - exp: 300, - crid: '543123', - adm: '', - adid: '10', - dealid: 'dsfxcxcvxc', - nurl: 'http://win.example.url', - ext: { - liid: '2121221', - ordid: '5678234', - }, - cat: ['IAB2-3'], - adomain: ['advertiser.com', 'campaign.advertiser.com'], - }, - ], - }, - ], - ext: { - sync: [ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ], - }, -}; diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 9a3bf61fe23..7a95d4272fb 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('AdgenerationAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index a9413860072..3cff5816356 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -134,7 +134,7 @@ describe('AdmanAdapter', function () { for (let i = 0; i < placements.length; i++) { let placement = placements[i]; expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'schain', 'bidFloor', - 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'ext'); expect(placement.ext).to.be.an('object') expect(placement.ext).to.have.key('tid') diff --git a/test/spec/modules/admaruBidAdapter_spec.js b/test/spec/modules/admaruBidAdapter_spec.js index 813a4ed8b29..05ec9eca67f 100644 --- a/test/spec/modules/admaruBidAdapter_spec.js +++ b/test/spec/modules/admaruBidAdapter_spec.js @@ -39,12 +39,12 @@ describe('Admaru Adapter', function () { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing pub_id or adspace_id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index e254d2f2ff7..4b70f0cec00 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -61,20 +61,20 @@ describe('AdmixerAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { placementId: 0, }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when params required by WL are not passed', function () { - let wlBid = Object.assign({}, wlBid); - delete wlBid.params; - wlBid.params = { + let invalidBid = Object.assign({}, wlBid); + delete invalidBid.params; + invalidBid.params = { clientId: 0, }; - expect(spec.isBidRequestValid(wlBid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index 080b5bd5d1d..cff5e77d95b 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -36,13 +36,13 @@ describe('AdoceanAdapter', function () { }); it('should return false when required params are not passed', function () { - const bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'masterId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js deleted file mode 100644 index 703e6ed8992..00000000000 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ /dev/null @@ -1,253 +0,0 @@ -import adomikAnalytics from 'modules/adomikAnalyticsAdapter.js'; -import { expect } from 'chai'; -import {EVENTS} from 'src/constants.js'; - -let events = require('src/events'); -let adapterManager = require('src/adapterManager').default; - -describe('Adomik Prebid Analytic', function () { - let sendEventStub; - let sendWonEventStub; - let clock; - - beforeEach(function () { - clock = sinon.useFakeTimers(); - sinon.spy(adomikAnalytics, 'track'); - sendEventStub = sinon.stub(adomikAnalytics, 'sendTypedEvent'); - sendWonEventStub = sinon.stub(adomikAnalytics, 'sendWonEvent'); - sinon.stub(events, 'getEvents').returns([]); - adomikAnalytics.currentContext = undefined; - - adapterManager.registerAnalyticsAdapter({ - code: 'adomik', - adapter: adomikAnalytics - }); - }); - - afterEach(function () { - adomikAnalytics.disableAnalytics(); - clock.restore(); - adomikAnalytics.track.restore(); - sendEventStub.restore(); - sendWonEventStub.restore(); - events.getEvents.restore(); - }); - - describe('adomikAnalytics.enableAnalytics', function () { - it('should catch all events', function (done) { - const initOptions = { - id: '123456', - url: 'testurl' - }; - - const bid = { - bidderCode: 'adomik_test_bid', - width: 10, - height: 10, - statusMessage: 'Bid available', - adId: '1234', - auctionId: '', - responseTimestamp: 1496410856397, - requestTimestamp: 1496410856295, - cpm: 0.1, - bidder: 'biddertest', - adUnitCode: '0000', - timeToRespond: 100, - placementCode: 'placementtest' - } - - // Step 1: Initialize adapter - adapterManager.enableAnalytics({ - provider: 'adomik', - options: initOptions - }); - expect(adomikAnalytics.currentContext).to.deep.equal({ - uid: '123456', - url: 'testurl', - sampling: undefined, - id: '', - timeouted: false - }); - - // Step 2: Send init auction event - events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); - - expect(adomikAnalytics.currentContext).to.deep.equal({ - uid: '123456', - url: 'testurl', - sampling: undefined, - id: 'test-test-test', - timeouted: false - }); - - // Step 3: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, { bids: [bid] }); - - expect(adomikAnalytics.bucketEvents.length).to.equal(1); - expect(adomikAnalytics.bucketEvents[0]).to.deep.equal({ - type: 'request', - event: { - bidder: 'BIDDERTEST', - placementCode: '0000', - } - }); - - // Step 4: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bid); - - expect(adomikAnalytics.bucketEvents.length).to.equal(2); - expect(adomikAnalytics.bucketEvents[1]).to.deep.equal({ - type: 'response', - event: { - bidder: 'ADOMIK_TEST_BID', - placementCode: '0000', - id: '1234', - status: 'VALID', - cpm: 0.1, - size: { - width: 10, - height: 10 - }, - timeToRespond: 100, - afterTimeout: false, - } - }); - - // Step 5: Send bid won event - events.emit(EVENTS.BID_WON, bid); - - expect(adomikAnalytics.bucketEvents.length).to.equal(2); - - // Step 6: Send bid timeout event - events.emit(EVENTS.BID_TIMEOUT, {}); - - expect(adomikAnalytics.currentContext.timeouted).to.equal(true); - - // Step 7: Send auction end event - events.emit(EVENTS.AUCTION_END, {}); - - setTimeout(function() { - sinon.assert.callCount(sendEventStub, 1); - sinon.assert.callCount(sendWonEventStub, 1); - done(); - }, 3000); - - clock.tick(5000); - - sinon.assert.callCount(adomikAnalytics.track, 6); - }); - - describe('when sampling is undefined', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ - provider: 'adomik', - options: { id: '123456', url: 'testurl' } - }); - }); - - it('is enabled', function () { - expect(adomikAnalytics.currentContext).is.not.null; - }); - }); - - describe('when sampling is 0', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ - provider: 'adomik', - options: { id: '123456', url: 'testurl', sampling: 0 } - }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - - describe('when sampling is 1', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ - provider: 'adomik', - options: { id: '123456', url: 'testurl', sampling: 1 } - }); - }); - - it('is enabled', function () { - expect(adomikAnalytics.currentContext).is.not.null; - }); - }); - - describe('when options is not defined', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ provider: 'adomik' }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - - describe('when id is not defined in options', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ provider: 'adomik', url: 'xxx' }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - - describe('when url is not defined in options', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ provider: 'adomik', id: 'xxx' }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - }); - - describe('adomikAnalytics.getKeyValues', function () { - it('returns [undefined, undefined]', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal(undefined); - expect(testValue).to.equal(undefined); - }); - - describe('when test is in scope', function () { - beforeEach(function () { - sessionStorage.setItem(window.location.hostname + '_AdomikTestInScope', true); - }); - - it('returns [undefined, undefined]', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal(undefined); - expect(testValue).to.equal(undefined); - }); - - describe('when key values are defined', function () { - beforeEach(function () { - sessionStorage.setItem(window.location.hostname + '_AdomikTest', '{"testId":"12345","testOptionLabel":"1000"}'); - }); - - it('returns key values', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal('12345'); - expect(testValue).to.equal('1000'); - }); - - describe('when preventTest is on', function () { - beforeEach(function () { - sessionStorage.setItem(window.location.hostname + '_NoAdomikTest', true); - }); - - it('returns [undefined, undefined]', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal(undefined); - expect(testValue).to.equal(undefined); - }); - }); - }); - }); - }); -}); diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 7f24176e850..db637663f39 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -34,12 +34,12 @@ describe('AdrelevantisAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index bafa031cd25..367fc62c719 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -619,19 +619,19 @@ describe('Adyoulike Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.size; + let invalidBid = Object.assign({}, bid); + delete invalidBid.sizes; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placement': 0 }; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index dbc72d113f4..bd2bdd3e407 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -24,12 +24,12 @@ describe('AjaAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'asi': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index a84a30a945a..5769afa1b2b 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -442,7 +442,6 @@ describe('AmxBidAdapter', () => { it('will collect & forward RTI user IDs', () => { const randomRTI = `greatRTI${Math.floor(Math.random() * 100)}`; const userId = { - britepoolid: 'sample-britepool', criteoId: 'sample-criteo', digitrustid: { data: { id: 'sample-digitrust' } }, id5id: { uid: 'sample-id5' }, diff --git a/test/spec/modules/anPspParamsConverter_spec.js b/test/spec/modules/anPspParamsConverter_spec.js new file mode 100644 index 00000000000..0d01d0e78a9 --- /dev/null +++ b/test/spec/modules/anPspParamsConverter_spec.js @@ -0,0 +1,134 @@ +import { expect } from 'chai'; + +import {convertAnParams} from '../../../modules/anPspParamsConverter'; +import { config } from '../../../src/config.js'; +import { deepClone } from '../../../src/utils'; +import adapterManager from '../../../src/adapterManager.js'; + +describe('anPspParamsConverter', function () { + let configStub; + let resolveAliasStub; + let didHookRun = false; + + const bidderRequests = [{ + bidderCode: 'appnexus', + bids: [{ + bidder: 'appnexus', + src: 's2s', + params: { + member: 958, + invCode: 12345, + placementId: '10001', + keywords: { + music: 'rock', + genre: ['80s', '90s'] + }, + publisherId: '111', + use_payment_rule: true + } + }] + }]; + + beforeEach(function () { + configStub = sinon.stub(config, 'getConfig'); + resolveAliasStub = sinon.stub(adapterManager, 'resolveAlias').callsFake(function (tarBidder) { + return (tarBidder === 'rubicon') ? 'rubicon' : 'appnexus'; + }); + }); + + afterEach(function () { + didHookRun = false; + configStub.restore(); + resolveAliasStub.restore(); + }); + + it('does not modify params when appnexus is not in s2sconfig', function () { + configStub.callsFake(function () { + return { + bidders: ['rubicon'] + }; + }); + + const testBidderRequests = deepClone(bidderRequests); + + debugger; //eslint-disable-line + convertAnParams(function () { + didHookRun = true; + }, testBidderRequests); + + expect(didHookRun).to.equal(true); + const resultParams = testBidderRequests[0].bids[0].params; + expect(resultParams.member).to.equal(958); + expect(resultParams.invCode).to.equal(12345); + expect(resultParams.placementId).to.equal('10001'); + expect(resultParams.keywords).to.deep.equal({ + music: 'rock', + genre: ['80s', '90s'] + }); + expect(resultParams.publisherId).to.equal('111'); + expect(resultParams.use_payment_rule).to.equal(true); + }); + + const tests = [{ + testName: 'modifies params when appnexus is the bidder', + fakeConfigFn: function () { + return { + bidders: ['appnexus'] + }; + }, + applyBidderRequestChanges: function () { + const testBidderRequests = deepClone(bidderRequests); + + return testBidderRequests; + } + }, { + testName: 'modifies params when a registered appnexus alias is used', + fakeConfigFn: function () { + return { + bidders: ['beintoo'] + }; + }, + applyBidderRequestChanges: function () { + const testBidderRequests = deepClone(bidderRequests); + testBidderRequests.bidderCode = 'beintoo'; + testBidderRequests[0].bids[0].bidder = 'beintoo'; + + return testBidderRequests; + } + }, { + testName: 'modifies params when pbjs.aliasBidder alias is used', + fakeConfigFn: function () { + return { + bidders: ['aliasBidderTest'], + }; + }, + applyBidderRequestChanges: function () { + const testBidderRequests = deepClone(bidderRequests); + testBidderRequests.bidderCode = 'aliasBidderTest'; + testBidderRequests[0].bids[0].bidder = 'aliasBidderTest'; + + return testBidderRequests; + } + }]; + + tests.forEach((testCfg) => { + it(testCfg.testName, function () { + configStub.callsFake(testCfg.fakeConfigFn); + + const testBidderRequests = testCfg.applyBidderRequestChanges(); + + convertAnParams(function () { + didHookRun = true; + }, testBidderRequests); + + expect(didHookRun).to.equal(true); + const resultParams = testBidderRequests[0].bids[0].params; + expect(resultParams.member).to.equal('958'); + expect(resultParams.inv_code).to.equal('12345'); + expect(resultParams.placement_id).to.equal(10001); + expect(resultParams.keywords).to.equal('music=rock,genre=80s,genre=90s'); + expect(resultParams.publisher_id).to.equal(111); + expect(resultParams.use_pmt_rule).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index a9498af046c..0b261c80848 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -31,12 +31,12 @@ describe('ANIVIEW Bid Adapter Test', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { something: 'is wrong' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index 98d07575ee7..d005934d062 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -201,8 +201,7 @@ describe('ApacdexBidAdapter', function () { }, 'bidder': 'apacdex', 'params': { - 'siteId': '1a2b3c4d5e6f1a2b3c4d', - 'geo': { 'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60 } + 'siteId': '1a2b3c4d5e6f1a2b3c4d' }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -321,10 +320,6 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) }); - it('should fail to return a properly formatted request with geo defined', function () { - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.geo).to.not.deep.equal(bidRequest[0].params.geo) - }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); diff --git a/test/spec/modules/appierBidAdapter_spec.js b/test/spec/modules/appierBidAdapter_spec.js index 8b6ad5c2f6f..0ad14b1ec61 100644 --- a/test/spec/modules/appierBidAdapter_spec.js +++ b/test/spec/modules/appierBidAdapter_spec.js @@ -30,17 +30,17 @@ describe('AppierAdapter', function () { }); it('should return false when required param zoneId is missing', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required param zoneId has wrong type', function () { - let bid = Object.assign({}, bid); - bid.params = { + let invalidBid = Object.assign({}, bid); + invalidBid.params = { 'hzid': null }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 393768c3063..c8be99f7161 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/appnexusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; import { auctionManager } from 'src/auctionManager.js'; import { deepClone } from 'src/utils.js'; import * as utils from 'src/utils.js'; @@ -76,21 +75,21 @@ describe('AppNexusAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placement_id': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -2184,54 +2183,54 @@ describe('AppNexusAdapter', function () { }); }); - describe('transformBidParams', function () { - let gcStub; - let adUnit = { bids: [{ bidder: 'appnexus' }] }; ; - - before(function () { - gcStub = sinon.stub(config, 'getConfig'); - }); - - after(function () { - gcStub.restore(); - }); - - it('convert keywords param differently for psp endpoint with single s2sConfig', function () { - gcStub.withArgs('s2sConfig').returns({ - bidders: ['appnexus'], - endpoint: { - p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' - } - }); - - const oldParams = { - keywords: { - genre: ['rock', 'pop'], - pets: 'dog' - } - }; - - const newParams = spec.transformBidParams(oldParams, true, adUnit); - expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); - }); - - it('convert keywords param differently for psp endpoint with array s2sConfig', function () { - gcStub.withArgs('s2sConfig').returns([{ - bidders: ['appnexus'], - endpoint: { - p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' - } - }]); - - const oldParams = { - keywords: { - genre: ['rock', 'pop'], - pets: 'dog' - } - }; - - const newParams = spec.transformBidParams(oldParams, true, adUnit); - expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); - }); - }); + // describe('transformBidParams', function () { + // let gcStub; + // let adUnit = { bids: [{ bidder: 'appnexus' }] }; ; + + // before(function () { + // gcStub = sinon.stub(config, 'getConfig'); + // }); + + // after(function () { + // gcStub.restore(); + // }); + + // it('convert keywords param differently for psp endpoint with single s2sConfig', function () { + // gcStub.withArgs('s2sConfig').returns({ + // bidders: ['appnexus'], + // endpoint: { + // p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' + // } + // }); + + // const oldParams = { + // keywords: { + // genre: ['rock', 'pop'], + // pets: 'dog' + // } + // }; + + // const newParams = spec.transformBidParams(oldParams, true, adUnit); + // expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); + // }); + + // it('convert keywords param differently for psp endpoint with array s2sConfig', function () { + // gcStub.withArgs('s2sConfig').returns([{ + // bidders: ['appnexus'], + // endpoint: { + // p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' + // } + // }]); + + // const oldParams = { + // keywords: { + // genre: ['rock', 'pop'], + // pets: 'dog' + // } + // }; + + // const newParams = spec.transformBidParams(oldParams, true, adUnit); + // expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); + // }); + // }); }); diff --git a/test/spec/modules/asealBidAdapter_spec.js b/test/spec/modules/asealBidAdapter_spec.js index 2dc1b47b7d0..900bda11390 100644 --- a/test/spec/modules/asealBidAdapter_spec.js +++ b/test/spec/modules/asealBidAdapter_spec.js @@ -87,10 +87,10 @@ describe('asealBidAdapter', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index e317a8828e7..7839e0ef227 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -6,7 +6,7 @@ import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; import {parseUrl} from '../../../src/utils'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; describe('Adserver.Online bidding adapter', function () { diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 5c736345068..65349409e5e 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -60,22 +60,22 @@ describe('AudienceRun bid adapter tests', function () { }); it('should return true when zoneId is valid', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { zoneId: '12345abcde', }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; - bid.params = {}; + invalidBid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/bedigitechBidAdapter_spec.js b/test/spec/modules/bedigitechBidAdapter_spec.js index 20d4e86e0c4..336559e2812 100644 --- a/test/spec/modules/bedigitechBidAdapter_spec.js +++ b/test/spec/modules/bedigitechBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/bedigitechBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import {BANNER} from 'src/mediaTypes.js'; +import { BANNER } from 'src/mediaTypes.js'; describe('BedigitechAdapter', function () { const adapter = newBidder(spec); @@ -34,13 +34,13 @@ describe('BedigitechAdapter', function () { }); it('should return false when required params are not passed', function () { - const bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'masterId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -126,7 +126,9 @@ describe('BedigitechAdapter', function () { } else if (k === 'meta') { expect(result[0][k]).to.deep.equal(expectedResponse[0][k]); } else { - expect(result[0][k]).to.equal(expectedResponse[0][k]); + if (k !== 'requestId') { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } } }); }); diff --git a/test/spec/modules/bidglassAdapter_spec.js b/test/spec/modules/bidglassAdapter_spec.js index 7b007f7cc1f..e0f85364933 100644 --- a/test/spec/modules/bidglassAdapter_spec.js +++ b/test/spec/modules/bidglassAdapter_spec.js @@ -23,10 +23,10 @@ describe('Bid Glass Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/blastoBidAdapter_spec.js similarity index 97% rename from test/spec/modules/bizzclickBidAdapter_spec.js rename to test/spec/modules/blastoBidAdapter_spec.js index f8e66caf657..671f99fa938 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/blastoBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/bizzclickBidAdapter'; +import { spec } from 'modules/blastoBidAdapter'; import 'modules/priceFloors.js'; import { newBidder } from 'src/adapters/bidderFactory'; import { config } from '../../../src/config.js'; @@ -11,12 +11,12 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; const SIMPLE_BID_REQUEST = { - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: 'testAccountId', sourceId: 'testSourceId', @@ -46,7 +46,7 @@ const SIMPLE_BID_REQUEST = { } const BANNER_BID_REQUEST = { - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: 'testAccountId', sourceId: 'testSourceId', @@ -85,7 +85,7 @@ const VIDEO_BID_REQUEST = { protocols: [1, 2, 4] } }, - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: '123', sourceId: '123', @@ -128,7 +128,7 @@ const NATIVE_BID_REQUEST = { } } }, - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: 'testAccountId', sourceId: 'testSourceId', @@ -158,7 +158,7 @@ const gdprConsent = { addtlConsent: '1~1.35.41.101', } -describe('bizzclickAdapter', function () { +describe('blastoAdapter', function () { const adapter = newBidder(spec); describe('inherited functions', function () { it('exists and is a function', function () { @@ -251,7 +251,7 @@ describe('bizzclickAdapter', function () { beforeEach(function () { bidRequests = [{ 'bidId': '28ffdk2B952532', - 'bidder': 'bizzclick', + 'bidder': 'blasto', 'userId': { 'freepassId': { 'userIp': '172.21.0.1', diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js deleted file mode 100644 index b6fb11c4750..00000000000 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ /dev/null @@ -1,1094 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/bluebillywigBidAdapter.js'; -import {deepAccess, deepClone} from 'src/utils.js'; -import {config} from 'src/config.js'; -import {VIDEO} from 'src/mediaTypes.js'; - -const BB_CONSTANTS = { - BIDDER_CODE: 'bluebillywig', - AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', - SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', - RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', - DEFAULT_TIMEOUT: 5000, - DEFAULT_TTL: 300, - DEFAULT_WIDTH: 768, - DEFAULT_HEIGHT: 432, - DEFAULT_NET_REVENUE: true -}; - -describe('BlueBillywigAdapter', () => { - describe('isBidRequestValid', () => { - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: 'bbprebid.dev', - rendererCode: 'glorious_renderer', - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(baseValidBid)).to.equal(true); - }); - - it('should return false when params missing', () => { - const bid = deepClone(baseValidBid); - delete bid.params; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when publicationName is missing', () => { - const bid = deepClone(baseValidBid); - delete bid.params.publicationName; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when publicationName is not a string', () => { - const bid = deepClone(baseValidBid); - - bid.params.publicationName = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when publicationName is formatted poorly', () => { - const bid = deepClone(baseValidBid); - - bid.params.publicationName = 'bb.'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = 'bb-test'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = '?'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when renderer is not specified', () => { - const bid = deepClone(baseValidBid); - - delete bid.params.rendererCode; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when renderer is not a string', () => { - const bid = deepClone(baseValidBid); - - bid.params.rendererCode = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when renderer is formatted poorly', () => { - const bid = deepClone(baseValidBid); - - bid.params.rendererCode = 'bb.'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = 'bb-test'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = '?'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when accountId is not specified', () => { - const bid = deepClone(baseValidBid); - - delete bid.params.accountId; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when connections is not specified', () => { - const bid = deepClone(baseValidBid); - - delete bid.params.connections; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when connections is not an array', () => { - const bid = deepClone(baseValidBid); - - bid.params.connections = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = 'string'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when a connection is missing', () => { - const bid = deepClone(baseValidBid); - - bid.params.connections.push('potatoes'); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections.pop(); - - delete bid.params[BB_CONSTANTS.BIDDER_CODE]; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if bid has no mediaTypes', () => { - const bid = deepClone(baseValidBid); - - delete bid.mediaTypes; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if bid has no mediaTypes.video', () => { - const bid = deepClone(baseValidBid); - - delete bid.mediaTypes[VIDEO]; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if bid has no mediaTypes.video.context', () => { - const bid = deepClone(baseValidBid); - - delete bid.mediaTypes[VIDEO].context; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if mediaTypes.video.context is not "outstream"', () => { - const bid = deepClone(baseValidBid); - - bid.mediaTypes[VIDEO].context = 'instream'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if video is specified but is not an object', () => { - const bid = deepClone(baseValidBid); - - bid.params.video = null; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.video = 'string'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.video = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.video = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.video = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if rendererSettings is specified but is not an object', () => { - const bid = deepClone(baseValidBid); - - bid.params.rendererSettings = null; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererSettings = 'string'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererSettings = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererSettings = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererSettings = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', () => { - const publicationName = 'bbprebid.dev'; - const rendererCode = 'glorious_renderer'; - - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: publicationName, - rendererCode: rendererCode, - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - const baseValidBidRequests = [baseValidBid]; - - const validBidderRequest = { - ortb2: { - source: { - tid: '12abc345-67d8-9012-e345-6f78901a2b34', - } - }, - auctionStart: 1585918458868, - bidderCode: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - bids: [{ - adUnitCode: 'ad-unit-test', - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - bidId: '1234ab567c89de0', - bidRequestsCount: 1, - bidder: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - params: baseValidBid.params, - sizes: [[768, 432], [640, 480], [630, 360]], - transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' - }], - start: 11585918458869, - timeout: 3000 - }; - - it('sends bid request to AUCTION_URL via POST', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - expect(request.url).to.equal(`https://pbs.bluebillywig.com/openrtb2/auction?pub=${publicationName}`); - expect(request.method).to.equal('POST'); - }); - - it('sends data as a string', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - expect(request.data).to.be.a('string'); - }); - - it('sends all bid parameters', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - - it('builds the base request properly', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.id).to.exist; - expect(payload.source).to.be.an('object'); - expect(payload.source.tid).to.equal(validBidderRequest.ortb2.source.tid); - expect(payload.tmax).to.equal(3000); - expect(payload.imp).to.be.an('array'); - expect(payload.test).to.be.a('number'); - expect(payload).to.have.nested.property('ext.prebid.targeting'); - expect(payload.ext.prebid.targeting).to.be.an('object'); - expect(payload.ext.prebid.targeting.includewinners).to.equal(true); - expect(payload.ext.prebid.targeting.includebidderkeys).to.equal(false); - }); - - it('adds an impression to the payload', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.imp.length).to.equal(1); - }); - - it('adds connections to ext', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.imp[0].ext).to.have.all.keys(['bluebillywig']); - }); - - it('adds gdpr when present', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.gdprConsent = { - consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', - gdprApplies: true - }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.ext.gdpr'); - expect(payload.regs.ext.gdpr).to.be.a('number'); - expect(payload.regs.ext.gdpr).to.equal(1); - expect(payload).to.have.nested.property('user.ext.consent'); - expect(payload.user.ext.consent).to.equal(newValidBidderRequest.gdprConsent.consentString); - }); - - it('sets gdpr to 0 when explicitly gdprApplies: false', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.gdprConsent = { - gdprApplies: false - }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.ext.gdpr'); - expect(payload.regs.ext.gdpr).to.be.a('number'); - expect(payload.regs.ext.gdpr).to.equal(0); - }); - - it('adds usp_consent when present', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.uspConsent = '1YYY'; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.ext.us_privacy'); - expect(payload.regs.ext.us_privacy).to.equal(newValidBidderRequest.uspConsent); - }); - - it('sets coppa to 1 when specified in config', () => { - config.setConfig({'coppa': true}); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.coppa'); - expect(payload.regs.coppa).to.equal(1); - - config.resetConfig(); - }); - - it('does not set coppa when disabled in the config', () => { - config.setConfig({'coppa': false}); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; - - config.resetConfig(); - }); - - it('does not set coppa when not specified in config', () => { - config.resetConfig(); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; - }); - - it('should add window size to request by default', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('device.w'); - expect(payload).to.have.nested.property('device.h'); - expect(payload.device.w).to.be.a('number'); - expect(payload.device.h).to.be.a('number'); - }); - - it('should add site when specified in config', () => { - config.setConfig({ site: { name: 'Blue Billywig', domain: 'bluebillywig.com', page: 'https://bluebillywig.com/', publisher: { id: 'abc', name: 'Blue Billywig', domain: 'bluebillywig.com' } } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('site'); - expect(payload).to.have.nested.property('site.name'); - expect(payload).to.have.nested.property('site.domain'); - expect(payload).to.have.nested.property('site.page'); - expect(payload).to.have.nested.property('site.publisher'); - expect(payload).to.have.nested.property('site.publisher.id'); - expect(payload).to.have.nested.property('site.publisher.name'); - expect(payload).to.have.nested.property('site.publisher.domain'); - - config.resetConfig(); - }); - - it('should add app when specified in config', () => { - config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('app'); - expect(payload).to.have.nested.property('app.bundle'); - expect(payload).to.have.nested.property('app.domain'); - expect(payload.app.bundle).to.equal('org.prebid.mobile.demoapp'); - expect(payload.app.domain).to.equal('prebid.org'); - - config.resetConfig(); - }); - - it('should add referrerInfo as site when no app is set', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - - newValidBidderRequest.refererInfo = { page: 'https://www.bluebillywig.com' }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('site.page'); - expect(payload.site.page).to.equal('https://www.bluebillywig.com'); - }); - - it('should not add referrerInfo as site when app is set', () => { - config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); - - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.site).to.be.undefined; - config.resetConfig(); - }); - - it('should add device size to request when specified in config', () => { - config.setConfig({ device: { w: 1, h: 1 } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('device.w'); - expect(payload).to.have.nested.property('device.h'); - expect(payload.device.w).to.be.a('number'); - expect(payload.device.h).to.be.a('number'); - expect(payload.device.w).to.equal(1); - expect(payload.device.h).to.equal(1); - - config.resetConfig(); - }); - - it('should set schain on the request when set on config', () => { - const schain = { - validation: 'lax', - config: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1 - } - ] - } - }; - - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].schain = schain; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('source.ext.schain'); - expect(payload.source.ext.schain).to.deep.equal(schain); - }); - - it('should add currency when specified on the config', () => { - config.setConfig({ currency: { adServerCurrency: 'USD' } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('cur'); - expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally - - config.resetConfig(); - }); - - it('should also take in array for currency on the config', () => { - config.setConfig({ currency: { adServerCurrency: ['USD', 'PHP'] } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('cur'); - expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally - - config.resetConfig(); - }); - - it('should not set cur when currency is not specified on the config', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.cur).to.be.undefined; - }); - - it('should set user ids when present', () => { - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userIdAsEids = [ {} ]; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('user.ext.eids'); - expect(payload.user.ext.eids).to.be.an('array'); - expect(payload.user.ext.eids.length).to.equal(1); - }); - - it('should not set user ids when none present', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined; - }); - - it('should set imp.0.video.[w|h|placement] by default', () => { - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'imp.0.video.w')).to.equal(768); - expect(deepAccess(payload, 'imp.0.video.h')).to.equal(432); - expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(3); - }); - - it('should update imp0.video.[w|h] when present in config', () => { - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].mediaTypes.video.playerSize = [1, 1]; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'imp.0.video.w')).to.equal(1); - expect(deepAccess(payload, 'imp.0.video.h')).to.equal(1); - }); - - it('should allow overriding any imp0.video key through params.video', () => { - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].params.video = { - w: 2, - h: 2, - placement: 1, - minduration: 15, - maxduration: 30 - }; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'imp.0.video.w')).to.equal(2); - expect(deepAccess(payload, 'imp.0.video.h')).to.equal(2); - expect(deepAccess(payload, 'imp.0.video.placement')).to.equal(1); - expect(deepAccess(payload, 'imp.0.video.minduration')).to.equal(15); - expect(deepAccess(payload, 'imp.0.video.maxduration')).to.equal(30); - }); - - it('should not allow placing any non-OpenRTB 2.5 keys on imp.0.video through params.video', () => { - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].params.video = { - 'true': true, - 'testing': 'some', - 123: {}, - '': 'values' - }; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(request, 'imp.0.video.true')).to.be.undefined; - expect(deepAccess(payload, 'imp.0.video.testing')).to.be.undefined; - expect(deepAccess(payload, 'imp.0.video.123')).to.be.undefined; - expect(deepAccess(payload, 'imp.0.video.')).to.be.undefined; - }); - }); - describe('interpretResponse', () => { - const publicationName = 'bbprebid.dev'; - const rendererCode = 'glorious_renderer'; - - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: publicationName, - rendererCode: rendererCode, - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - const baseValidBidRequests = [baseValidBid]; - - const validBidderRequest = { - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - auctionStart: 1585918458868, - bidderCode: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - bids: [{ - adUnitCode: 'ad-unit-test', - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - bidId: '1234ab567c89de0', - bidRequestsCount: 1, - bidder: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - params: baseValidBid.params, - sizes: [[640, 480], [630, 360]], - transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' - }], - start: 11585918458869, - timeout: 3000 - }; - - const validResponse = { - id: 'a12abc345-67d8-9012-e345-6f78901a2b34', - seatbid: [ - { - bid: [ - { - id: '1', - impid: '1234ab567c89de0', - price: 1, - adm: '\r\nBB Adserver00:00:51', - adid: '67069817', - adomain: [ - 'bluebillywig.com' - ], - cid: '3535', - crid: '67069817', - w: 1, - h: 1, - publicationName: 'bbprebid', - accountId: 123, - ext: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '1.00', - hb_size: '1x1' - }, - type: 'video' - }, - bidder: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '10.00', - hb_size: '1x1' - }, - type: 'video', - video: { - duration: 51, - primary_category: '' - } - }, - bidder: { - bluebillywig: { - brand_id: 1, - auction_id: 1, - bid_ad_type: 1, - creative_info: { - video: { - duration: 51, - mimes: [ - 'video/x-flv', - 'video/mp4', - 'video/webm' - ] - } - } - } - } - } - } - } - ], - seat: 'bluebillywig' - } - ], - cur: 'USD', - ext: { - responsetimemillis: { - bluebillywig: 0 - }, - tmaxrequest: 5000 - } - }; - - const serverResponse = { body: validResponse }; - - it('should build bid array', () => { - const response = deepClone(serverResponse); - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(1); - }); - - it('should have all relevant fields', () => { - const response = deepClone(serverResponse); - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - const bid = result[0]; - - // BB_HELPERS.transformRTBToPrebidProps - expect(bid.cpm).to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(bid.bidId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); - expect(bid.requestId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); - expect(bid.width).to.equal(serverResponse.body.seatbid[0].bid[0].w || BB_CONSTANTS.DEFAULT_WIDTH); - expect(bid.height).to.equal(serverResponse.body.seatbid[0].bid[0].h || BB_CONSTANTS.DEFAULT_HEIGHT); - expect(bid.ad).to.equal(serverResponse.body.seatbid[0].bid[0].adm); - expect(bid.netRevenue).to.equal(BB_CONSTANTS.DEFAULT_NET_REVENUE); - expect(bid.creativeId).to.equal(serverResponse.body.seatbid[0].bid[0].crid); - expect(bid.currency).to.equal(serverResponse.body.cur); - expect(bid.ttl).to.equal(BB_CONSTANTS.DEFAULT_TTL); - - expect(bid).to.have.property('meta'); - expect(bid.meta).to.have.property('advertiserDomains'); - expect(bid.meta.advertiserDomains[0]).to.equal('bluebillywig.com'); - - expect(bid.publicationName).to.equal(validBidderRequest.bids[0].params.publicationName); - expect(bid.rendererCode).to.equal(validBidderRequest.bids[0].params.rendererCode); - expect(bid.accountId).to.equal(validBidderRequest.bids[0].params.accountId); - }); - - it('should not give anything when seatbid is an empty array', () => { - const seatbidEmptyArray = deepClone(serverResponse); - seatbidEmptyArray.body.seatbid = []; - - const response = seatbidEmptyArray; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - it('should not give anything when seatbid is missing', () => { - const seatbidMissing = deepClone(serverResponse); - delete seatbidMissing.body.seatbid; - - const response = seatbidMissing; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - const seatbidNotArrayResponse = deepClone(serverResponse); - it('should not give anything when seatbid is not an array', () => { - const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; - - for (const invalidValue of invalidValues) { - seatbidNotArrayResponse.body.seatbid = invalidValue - const response = deepClone(seatbidNotArrayResponse); // interpretResponse is destructive - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - } - }); - - it('should not give anything when seatbid.bid is an empty array', () => { - const seatbidBidEmpty = deepClone(serverResponse); - seatbidBidEmpty.body.seatbid[0].bid = []; - - const response = seatbidBidEmpty; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - it('should not give anything when seatbid.bid is missing', () => { - const seatbidBidMissing = deepClone(serverResponse); - delete seatbidBidMissing.body.seatbid[0].bid; - - const response = seatbidBidMissing; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - it('should not give anything when seatbid.bid is not an array', () => { - const seatbidBidNotArray = deepClone(serverResponse); - - const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; - - for (const invalidValue of invalidValues) { - seatbidBidNotArray.body.seatbid[0].bid = invalidValue; - - const response = deepClone(seatbidBidNotArray); // interpretResponse is destructive - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - } - }); - - it('should take default width and height when w/h not present', () => { - const bidSizesMissing = deepClone(serverResponse); - - delete bidSizesMissing.body.seatbid[0].bid[0].w; - delete bidSizesMissing.body.seatbid[0].bid[0].h; - - const response = bidSizesMissing; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(deepAccess(result, '0.width')).to.equal(768); - expect(deepAccess(result, '0.height')).to.equal(432); - }); - - it('should take nurl value when adm not present', () => { - const bidAdmMissing = deepClone(serverResponse); - - delete bidAdmMissing.body.seatbid[0].bid[0].adm; - bidAdmMissing.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; - - const response = bidAdmMissing; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(deepAccess(result, '0.vastXml')).to.be.undefined; - expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); - }); - - it('should not take nurl value when adm present', () => { - const bidAdmNurlPresent = deepClone(serverResponse); - - bidAdmNurlPresent.body.seatbid[0].bid[0].nurl = 'https://bluebillywig.com'; - - const response = bidAdmNurlPresent; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(deepAccess(result, '0.vastXml')).to.equal(bidAdmNurlPresent.body.seatbid[0].bid[0].adm); - expect(deepAccess(result, '0.vastUrl')).to.be.undefined; - }); - - it('should take ext.prebid.cache data when present, ignore ext.prebid.targeting and nurl', () => { - const bidExtPrebidCache = deepClone(serverResponse); - - delete bidExtPrebidCache.body.seatbid[0].bid[0].adm; - bidExtPrebidCache.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; - - bidExtPrebidCache.body.seatbid[0].bid[0].ext = { - prebid: { - cache: { - vastXml: { - url: 'https://bluebillywig.com', - cacheId: '12345' - } - }, - targeting: { - hb_uuid: '23456', - hb_cache_host: 'bluebillywig.com', - hb_cache_path: '/cache' - } - } - }; - - const response = bidExtPrebidCache; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com'); - expect(deepAccess(result, '0.videoCacheKey')).to.equal('12345'); - }); - - it('should take ext.prebid.targeting data when ext.prebid.cache not present, and ignore nurl', () => { - const bidExtPrebidTargeting = deepClone(serverResponse); - - delete bidExtPrebidTargeting.body.seatbid[0].bid[0].adm; - bidExtPrebidTargeting.body.seatbid[0].bid[0].nurl = 'https://notnurl.com'; - - bidExtPrebidTargeting.body.seatbid[0].bid[0].ext = { - prebid: { - targeting: { - hb_uuid: '34567', - hb_cache_host: 'bluebillywig.com', - hb_cache_path: '/cache' - } - } - }; - - const response = bidExtPrebidTargeting; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(deepAccess(result, '0.vastUrl')).to.equal('https://bluebillywig.com/cache?uuid=34567'); - expect(deepAccess(result, '0.videoCacheKey')).to.equal('34567'); - }); - }); - describe('getUserSyncs', () => { - const publicationName = 'bbprebid.dev'; - const rendererCode = 'glorious_renderer'; - - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: publicationName, - rendererCode: rendererCode, - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - const validBidRequests = [baseValidBid]; - - const validBidderRequest = { - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - auctionStart: 1585918458868, - bidderCode: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - bids: [{ - adUnitCode: 'ad-unit-test', - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - bidId: '1234ab567c89de0', - bidRequestsCount: 1, - bidder: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - params: baseValidBid.params, - sizes: [[768, 432], [640, 480], [630, 360]], - transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' - }], - start: 11585918458869, - timeout: 3000 - }; - const validResponse = { - id: 'a12abc345-67d8-9012-e345-6f78901a2b34', - seatbid: [ - { - bid: [ - { - id: '1', - impid: '1234ab567c89de0', - price: 1, - adm: '\r\nBB Adserver00:00:51', - adid: '67069817', - adomain: [ - 'bluebillywig.com' - ], - cid: '3535', - crid: '67069817', - w: 1, - h: 1, - publicationName: 'bbprebid', - accountId: 123, - ext: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '1.00', - hb_size: '1x1' - }, - type: 'video' - }, - bidder: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '10.00', - hb_size: '1x1' - }, - type: 'video', - video: { - duration: 51, - primary_category: '' - } - }, - bidder: { - bluebillywig: { - brand_id: 1, - auction_id: 1, - bid_ad_type: 1, - creative_info: { - video: { - duration: 51, - mimes: [ - 'video/x-flv', - 'video/mp4', - 'video/webm' - ] - } - } - } - } - } - } - } - ], - seat: 'bluebillywig' - } - ], - cur: 'USD', - ext: { - responsetimemillis: { - bluebillywig: 0 - }, - tmaxrequest: 5000 - } - }; - - const serverResponse = { body: validResponse }; - - const gdpr = { - consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAA AAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', - gdprApplies: true - }; - - it('should return empty if no server response', function () { - const result = spec.getUserSyncs({}, false, gdpr); - expect(result).to.be.empty; - }); - - it('should return empty if server response is empty', function () { - const result = spec.getUserSyncs({}, [], gdpr); - expect(result).to.be.empty; - }); - - it('should return empty if iframeEnabled is not true', () => { - const result = spec.getUserSyncs({iframeEnabled: false}, [serverResponse], gdpr); - expect(result).to.be.empty; - }); - - it('should append the various values if they exist', function() { - // push data to syncStore - spec.buildRequests(validBidRequests, validBidderRequest); - - const result = spec.getUserSyncs({iframeEnabled: true}, [serverResponse], gdpr); - - expect(result).to.not.be.empty; - - expect(result[0].url).to.include('gdpr=1'); - expect(result[0].url).to.include(gdpr.consentString); - expect(result[0].url).to.include('accountId=123'); - expect(result[0].url).to.include(`bidders=${btoa(JSON.stringify(validBidRequests[0].params.connections))}`); - expect(result[0].url).to.include('cb='); - }); - }); -}); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js deleted file mode 100644 index 1ae73708d00..00000000000 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ /dev/null @@ -1,411 +0,0 @@ -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { spec } from 'modules/brightcomBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import {config} from '../../../src/config'; - -const URL = 'https://brightcombid.marphezis.com/hb'; - -describe('brightcomBidAdapter', function() { - const adapter = newBidder(spec); - let element, win; - let bidRequests; - let sandbox; - - beforeEach(function() { - element = { - x: 0, - y: 0, - - width: 0, - height: 0, - - getBoundingClientRect: () => { - return { - width: element.width, - height: element.height, - - left: element.x, - top: element.y, - right: element.x + element.width, - bottom: element.y + element.height - }; - } - }; - win = { - document: { - visibilityState: 'visible' - }, - - innerWidth: 800, - innerHeight: 600 - }; - bidRequests = [{ - 'bidder': 'brightcom', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - } - ] - }, - }]; - - sandbox = sinon.sandbox.create(); - sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); - sandbox.stub(utils, 'getWindowTop').returns(win); - sandbox.stub(utils, 'getWindowSelf').returns(win); - }); - - afterEach(function() { - sandbox.restore(); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'brightcom', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when publisherId not passed correctly', function () { - bid.params.publisherId = undefined; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - it('sends bid request to our endpoint via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.method).to.equal('POST'); - }); - - it('request url should match our endpoint url', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(URL); - }); - - it('sets the proper banner object', function() { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - }); - - it('accepts a single array as a size', function() { - bidRequests[0].mediaTypes.banner.sizes = [300, 250]; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); - }); - - it('sends bidfloor param if present', function () { - bidRequests[0].params.bidFloor = 0.05; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].bidfloor).to.equal(0.05); - }); - - it('sends tagid', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].tagid).to.equal('adunit-code'); - }); - - it('sends publisher id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.site.publisher.id).to.equal(1234567); - }); - - it('sends gdpr info if exists', function () { - const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { - 'bidderCode': 'brightcom', - 'auctionId': '1d1a030790a437', - 'bidderRequestId': '22edbae2744bf5', - 'timeout': 3000, - gdprConsent: { - consentString: consentString, - gdprApplies: true - }, - refererInfo: { - page: 'http://example.com/page.html', - domain: 'example.com', - } - }; - bidderRequest.bids = bidRequests; - - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); - - expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); - expect(data.regs.ext.gdpr).to.equal(1); - expect(data.user.ext.consent).to.exist.and.to.be.a('string'); - expect(data.user.ext.consent).to.equal(consentString); - }); - - it('sends us_privacy', function () { - const bidderRequest = { - uspConsent: '1YYY' - }; - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) - - expect(data.regs).to.not.equal(null); - expect(data.regs.ext).to.not.equal(null); - expect(data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('sends coppa', function () { - sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); - - const data = JSON.parse(spec.buildRequests(bidRequests).data) - expect(data.regs).to.not.be.undefined; - expect(data.regs.coppa).to.equal(1); - }); - - it('sends schain', function () { - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data).to.not.be.undefined; - expect(data.source).to.not.be.undefined; - expect(data.source.ext).to.not.be.undefined; - expect(data.source.ext.schain).to.not.be.undefined; - expect(data.source.ext.schain.complete).to.equal(1); - expect(data.source.ext.schain.ver).to.equal('1.0'); - expect(data.source.ext.schain.nodes).to.not.be.undefined; - expect(data.source.ext.schain.nodes).to.lengthOf(1); - expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); - expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); - expect(data.source.ext.schain.nodes[0].hp).to.equal(1); - expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); - expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); - expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); - }); - - it('sends user eid parameters', function () { - bidRequests[0].userIdAsEids = [{ - source: 'pubcid.org', - uids: [{ - id: 'userid_pubcid' - }] - }, { - source: 'adserver.org', - uids: [{ - id: 'userid_ttd', - ext: { - rtiPartner: 'TDID' - } - }] - } - ]; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.eids).to.not.be.undefined; - expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); - }); - - it('sends user id parameters', function () { - const userId = { - sharedid: { - id: '01*******', - third: '01E*******' - } - }; - - bidRequests[0].userId = userId; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.ids).is.deep.equal(userId); - }); - - context('when element is fully in view', function() { - it('returns 100', function() { - Object.assign(element, { width: 600, height: 400 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(100); - }); - }); - - context('when element is out of view', function() { - it('returns 0', function() { - Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(0); - }); - }); - - context('when element is partially in view', function() { - it('returns percentage', function() { - Object.assign(element, { width: 800, height: 800 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(75); - }); - }); - - context('when width or height of the element is zero', function() { - it('try to use alternative values', function() { - Object.assign(element, { width: 0, height: 0 }); - bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(25); - }); - }); - - context('when nested iframes', function() { - it('returns \'na\'', function() { - Object.assign(element, { width: 600, height: 400 }); - - utils.getWindowTop.restore(); - utils.getWindowSelf.restore(); - sandbox.stub(utils, 'getWindowTop').returns(win); - sandbox.stub(utils, 'getWindowSelf').returns({}); - - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal('na'); - }); - }); - - context('when tab is inactive', function() { - it('returns 0', function() { - Object.assign(element, { width: 600, height: 400 }); - - utils.getWindowTop.restore(); - win.document.visibilityState = 'hidden'; - sandbox.stub(utils, 'getWindowTop').returns(win); - - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(0); - }); - }); - }); - - describe('interpretResponse', function () { - let response; - beforeEach(function () { - response = { - body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': '376874781', - 'impid': '283a9f4cd2415d', - 'price': 0.35743275, - 'nurl': '', - 'adm': '', - 'w': 300, - 'h': 250, - 'adomain': ['example.com'] - }] - }] - } - }; - }); - - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '283a9f4cd2415d', - 'cpm': 0.35743275, - 'width': 300, - 'height': 250, - 'creativeId': '376874781', - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': `
`, - 'ttl': 60, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('crid should default to the bid id if not on the response', function () { - let expectedResponse = [{ - 'requestId': '283a9f4cd2415d', - 'cpm': 0.35743275, - 'width': 300, - 'height': 250, - 'creativeId': response.body.seatbid[0].bid[0].id, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': `
`, - 'ttl': 60, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('handles empty bid response', function () { - let response = { - body: '' - }; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs ', () => { - let syncOptions = {iframeEnabled: true, pixelEnabled: true}; - - it('should not return', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); - expect(returnStatement).to.be.empty; - }); - }); -}); diff --git a/test/spec/modules/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js deleted file mode 100644 index 6f35a7a290b..00000000000 --- a/test/spec/modules/brightcomSSPBidAdapter_spec.js +++ /dev/null @@ -1,411 +0,0 @@ -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { spec } from 'modules/brightcomSSPBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import {config} from '../../../src/config'; - -const URL = 'https://rt.marphezis.com/hb'; - -describe('brightcomSSPBidAdapter', function() { - const adapter = newBidder(spec); - let element, win; - let bidRequests; - let sandbox; - - beforeEach(function() { - element = { - x: 0, - y: 0, - - width: 0, - height: 0, - - getBoundingClientRect: () => { - return { - width: element.width, - height: element.height, - - left: element.x, - top: element.y, - right: element.x + element.width, - bottom: element.y + element.height - }; - } - }; - win = { - document: { - visibilityState: 'visible' - }, - - innerWidth: 800, - innerHeight: 600 - }; - bidRequests = [{ - 'bidder': 'bcmssp', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - } - ] - }, - }]; - - sandbox = sinon.sandbox.create(); - sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); - sandbox.stub(utils, 'getWindowTop').returns(win); - sandbox.stub(utils, 'getWindowSelf').returns(win); - }); - - afterEach(function() { - sandbox.restore(); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'bcmssp', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when publisherId not passed correctly', function () { - bid.params.publisherId = undefined; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - it('sends bid request to our endpoint via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.method).to.equal('POST'); - }); - - it('request url should match our endpoint url', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(URL); - }); - - it('sets the proper banner object', function() { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - }); - - it('accepts a single array as a size', function() { - bidRequests[0].mediaTypes.banner.sizes = [300, 250]; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); - }); - - it('sends bidfloor param if present', function () { - bidRequests[0].params.bidFloor = 0.05; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].bidfloor).to.equal(0.05); - }); - - it('sends tagid', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].tagid).to.equal('adunit-code'); - }); - - it('sends publisher id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.site.publisher.id).to.equal(1234567); - }); - - it('sends gdpr info if exists', function () { - const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { - 'bidderCode': 'bcmssp', - 'auctionId': '1d1a030790a437', - 'bidderRequestId': '22edbae2744bf5', - 'timeout': 3000, - gdprConsent: { - consentString: consentString, - gdprApplies: true - }, - refererInfo: { - page: 'http://example.com/page.html', - domain: 'example.com', - } - }; - bidderRequest.bids = bidRequests; - - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); - - expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); - expect(data.regs.ext.gdpr).to.equal(1); - expect(data.user.ext.consent).to.exist.and.to.be.a('string'); - expect(data.user.ext.consent).to.equal(consentString); - }); - - it('sends us_privacy', function () { - const bidderRequest = { - uspConsent: '1YYY' - }; - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) - - expect(data.regs).to.not.equal(null); - expect(data.regs.ext).to.not.equal(null); - expect(data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('sends coppa', function () { - sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); - - const data = JSON.parse(spec.buildRequests(bidRequests).data) - expect(data.regs).to.not.be.undefined; - expect(data.regs.coppa).to.equal(1); - }); - - it('sends schain', function () { - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data).to.not.be.undefined; - expect(data.source).to.not.be.undefined; - expect(data.source.ext).to.not.be.undefined; - expect(data.source.ext.schain).to.not.be.undefined; - expect(data.source.ext.schain.complete).to.equal(1); - expect(data.source.ext.schain.ver).to.equal('1.0'); - expect(data.source.ext.schain.nodes).to.not.be.undefined; - expect(data.source.ext.schain.nodes).to.lengthOf(1); - expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); - expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); - expect(data.source.ext.schain.nodes[0].hp).to.equal(1); - expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); - expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); - expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); - }); - - it('sends user eid parameters', function () { - bidRequests[0].userIdAsEids = [{ - source: 'pubcid.org', - uids: [{ - id: 'userid_pubcid' - }] - }, { - source: 'adserver.org', - uids: [{ - id: 'userid_ttd', - ext: { - rtiPartner: 'TDID' - } - }] - } - ]; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.eids).to.not.be.undefined; - expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); - }); - - it('sends user id parameters', function () { - const userId = { - sharedid: { - id: '01*******', - third: '01E*******' - } - }; - - bidRequests[0].userId = userId; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.ids).is.deep.equal(userId); - }); - - context('when element is fully in view', function() { - it('returns 100', function() { - Object.assign(element, { width: 600, height: 400 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(100); - }); - }); - - context('when element is out of view', function() { - it('returns 0', function() { - Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(0); - }); - }); - - context('when element is partially in view', function() { - it('returns percentage', function() { - Object.assign(element, { width: 800, height: 800 }); - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(75); - }); - }); - - context('when width or height of the element is zero', function() { - it('try to use alternative values', function() { - Object.assign(element, { width: 0, height: 0 }); - bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(25); - }); - }); - - context('when nested iframes', function() { - it('returns \'na\'', function() { - Object.assign(element, { width: 600, height: 400 }); - - utils.getWindowTop.restore(); - utils.getWindowSelf.restore(); - sandbox.stub(utils, 'getWindowTop').returns(win); - sandbox.stub(utils, 'getWindowSelf').returns({}); - - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal('na'); - }); - }); - - context('when tab is inactive', function() { - it('returns 0', function() { - Object.assign(element, { width: 600, height: 400 }); - - utils.getWindowTop.restore(); - win.document.visibilityState = 'hidden'; - sandbox.stub(utils, 'getWindowTop').returns(win); - - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.imp[0].banner.ext.viewability).to.equal(0); - }); - }); - }); - - describe('interpretResponse', function () { - let response; - beforeEach(function () { - response = { - body: { - 'id': '37386aade21a71', - 'seatbid': [{ - 'bid': [{ - 'id': '376874781', - 'impid': '283a9f4cd2415d', - 'price': 0.35743275, - 'nurl': '', - 'adm': '', - 'w': 300, - 'h': 250, - 'adomain': ['example.com'] - }] - }] - } - }; - }); - - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '283a9f4cd2415d', - 'cpm': 0.35743275, - 'width': 300, - 'height': 250, - 'creativeId': '376874781', - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': `
`, - 'ttl': 60, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('crid should default to the bid id if not on the response', function () { - let expectedResponse = [{ - 'requestId': '283a9f4cd2415d', - 'cpm': 0.35743275, - 'width': 300, - 'height': 250, - 'creativeId': response.body.seatbid[0].bid[0].id, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': `
`, - 'ttl': 60, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('handles empty bid response', function () { - let response = { - body: '' - }; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); - }); - }); - - describe('getUserSyncs ', () => { - let syncOptions = {iframeEnabled: true, pixelEnabled: true}; - - it('should not return', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); - expect(returnStatement).to.be.empty; - }); - }); -}); diff --git a/test/spec/modules/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js deleted file mode 100644 index ab00c3015d4..00000000000 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ /dev/null @@ -1,148 +0,0 @@ -import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; -import * as utils from '../../../src/utils.js'; -import {attachIdSystem} from '../../../modules/userId/index.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; -import {expect} from 'chai/index.mjs'; - -describe('BritePool Submodule', () => { - const api_key = '1111'; - const aaid = '4421ea96-34a9-45df-a4ea-3c41a48a18b1'; - const idfa = '2d1c4fac-5507-4e28-991c-ca544e992dba'; - const bpid = '279c0161-5152-487f-809e-05d7f7e653fd'; - const url_override = 'https://override'; - const getter_override = function(params) { - return JSON.stringify({ 'primaryBPID': bpid }); - }; - const getter_callback_override = function(params) { - return callback => { - callback(JSON.stringify({ 'primaryBPID': bpid })); - }; - }; - - let triggerPixelStub; - - beforeEach(function (done) { - triggerPixelStub = sinon.stub(utils, 'triggerPixel'); - done(); - }); - - afterEach(function () { - triggerPixelStub.restore(); - }); - - it('trigger id resolution pixel when no identifiers set', () => { - britepoolIdSubmodule.getId({ params: {} }); - expect(triggerPixelStub.called).to.be.true; - }); - - it('trigger id resolution pixel when no identifiers set with api_key param', () => { - britepoolIdSubmodule.getId({ params: { api_key } }); - expect(triggerPixelStub.called).to.be.true; - }); - - it('does not trigger id resolution pixel when identifiers set', () => { - britepoolIdSubmodule.getId({ params: { api_key, aaid } }); - expect(triggerPixelStub.called).to.be.false; - }); - - it('sends x-api-key in header and one identifier', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); - assert(errors.length === 0, errors); - expect(headers['x-api-key']).to.equal(api_key); - expect(params).to.eql({ aaid }); - }); - - it('sends x-api-key in header and two identifiers', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, idfa }); - assert(errors.length === 0, errors); - expect(headers['x-api-key']).to.equal(api_key); - expect(params).to.eql({ aaid, idfa }); - }); - - it('allows call without api_key', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ aaid, idfa }); - expect(params).to.eql({ aaid, idfa }); - expect(errors.length).to.equal(0); - }); - - it('test url override', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override }); - expect(url).to.equal(url_override); - // Making sure it did not become part of params - expect(params.url).to.be.undefined; - }); - - it('test gdpr consent string in url', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: true, consentString: 'expectedConsentString' }); - expect(url).to.equal('https://api.britepool.com/v1/britepool/id?gdprString=expectedConsentString'); - }); - - it('test gdpr consent string not in url if gdprApplies false', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: false, consentString: 'expectedConsentString' }); - expect(url).to.equal('https://api.britepool.com/v1/britepool/id'); - }); - - it('test gdpr consent string not in url if consent string undefined', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }, { gdprApplies: true, consentString: undefined }); - expect(url).to.equal('https://api.britepool.com/v1/britepool/id'); - }); - - it('dynamic pub params should be added to params', () => { - window.britepool_pubparams = { ppid: '12345' }; - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); - expect(params).to.eql({ aaid, ppid: '12345' }); - window.britepool_pubparams = undefined; - }); - - it('dynamic pub params should override submodule params', () => { - window.britepool_pubparams = { ppid: '67890' }; - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, ppid: '12345' }); - expect(params).to.eql({ ppid: '67890' }); - window.britepool_pubparams = undefined; - }); - - it('if dynamic pub params undefined do nothing', () => { - window.britepool_pubparams = undefined; - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); - expect(params).to.eql({ aaid }); - window.britepool_pubparams = undefined; - }); - - it('test getter override with value', () => { - const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_override }); - expect(getter).to.equal(getter_override); - // Making sure it did not become part of params - expect(params.getter).to.be.undefined; - const response = britepoolIdSubmodule.getId({ params: { api_key, aaid, url: url_override, getter: getter_override } }); - assert.deepEqual(response, { id: { 'primaryBPID': bpid } }); - }); - - it('test getter override with callback', done => { - const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_callback_override }); - expect(getter).to.equal(getter_callback_override); - // Making sure it did not become part of params - expect(params.getter).to.be.undefined; - const response = britepoolIdSubmodule.getId({ params: { api_key, aaid, url: url_override, getter: getter_callback_override } }); - expect(response.callback).to.not.be.undefined; - response.callback(result => { - assert.deepEqual(result, { 'primaryBPID': bpid }); - done(); - }); - }); - describe('eid', () => { - before(() => { - attachIdSystem(britepoolIdSubmodule); - }); - it('britepoolId', function() { - const userId = { - britepoolid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'britepool.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - }) -}); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js index 315680cba26..c93b43d571b 100644 --- a/test/spec/modules/c1xBidAdapter_spec.js +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -31,9 +31,9 @@ describe('C1XAdapter', () => { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(c1xAdapter.isBidRequestValid(invalidBid)).to.equal(false); }); }); describe('buildRequests', () => { diff --git a/test/spec/modules/clickforceBidAdapter_spec.js b/test/spec/modules/clickforceBidAdapter_spec.js index c4c4d77e954..99aef433890 100644 --- a/test/spec/modules/clickforceBidAdapter_spec.js +++ b/test/spec/modules/clickforceBidAdapter_spec.js @@ -31,12 +31,12 @@ describe('ClickforceAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js index b7256545c5e..1b61e1a92b4 100644 --- a/test/spec/modules/colombiaBidAdapter_spec.js +++ b/test/spec/modules/colombiaBidAdapter_spec.js @@ -33,9 +33,9 @@ describe('colombiaBidAdapter', function() { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index ebe1e9be4d4..22a98df633f 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -257,7 +257,6 @@ describe('ColossussspAdapter', function () { describe('buildRequests with user ids', function () { var clonedBid = JSON.parse(JSON.stringify(bid)); clonedBid.userId = {} - clonedBid.userId.britepoolid = 'britepoolid123'; clonedBid.userId.idl_env = 'idl_env123'; clonedBid.userId.tdid = 'tdid123'; clonedBid.userId.id5id = { uid: 'id5id123' }; @@ -303,11 +302,11 @@ describe('ColossussspAdapter', function () { let placement = placements[i]; expect(placement).to.have.property('eids') expect(placement.eids).to.be.an('array') - expect(placement.eids.length).to.be.equal(8) + expect(placement.eids.length).to.be.equal(7) for (let index in placement.eids) { let v = placement.eids[index]; expect(v).to.have.all.keys('source', 'uids') - expect(v.source).to.be.oneOf(['pubcid.org', 'adserver.org', 'neustar.biz', 'britepool.com', 'identityLink', 'id5-sync.com', 'adserver.org', 'uidapi.com']) + expect(v.source).to.be.oneOf(['pubcid.org', 'adserver.org', 'neustar.biz', 'identityLink', 'id5-sync.com', 'adserver.org', 'uidapi.com']) expect(v.uids).to.be.an('array'); expect(v.uids.length).to.be.equal(1) expect(v.uids[0]).to.have.property('id') diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js index 93a876d0233..fb27bf4818c 100644 --- a/test/spec/modules/consentManagementGpp_spec.js +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -141,170 +141,33 @@ describe('consentManagementGpp', function () { }); }); }); - describe('GPPClient.ping', () => { - function mkPingData(gppVersion) { - return { - gppVersion - } - } - Object.entries({ - 'unknown': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData(), - apiVersion: '1.1', - client({callback}) { - callback(this.pingData); - } - }, - '1.0': { - expectedMode: MODE_MIXED, - pingData: mkPingData('1.0'), - apiVersion: '1.0', - client() { - return this.pingData; - } - }, - '1.1 that runs callback immediately': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData('1.1'), - apiVersion: '1.1', - client({callback}) { - callback(this.pingData); - } - }, - '1.1 that defers callback': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData('1.1'), - apiVersion: '1.1', - client({callback}) { - setTimeout(() => callback(this.pingData), 10); - } - }, - '> 1.1': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData('1.2'), - apiVersion: '1.1', - client({callback}) { - setTimeout(() => callback(this.pingData), 10); - } - } - }).forEach(([t, scenario]) => { - describe(`using CMP version ${t}`, () => { - let clients, mkClient; - beforeEach(() => { - clients = []; - mkClient = ({mode}) => { - const mockClient = function (args) { - if (args.command === 'ping') { - return Promise.resolve(scenario.client(args)); - } - } - mockClient.mode = mode; - mockClient.close = sinon.stub(); - clients.push(mockClient); - return mockClient; - } - }); - - it('should resolve to client with the correct mode', () => { - return GPPClient.ping(mkClient).then(([client]) => { - expect(client.cmp.mode).to.eql(scenario.expectedMode); - }); - }); - - it('should resolve to pingData', () => { - return GPPClient.ping(mkClient).then(([_, pingData]) => { - expect(pingData).to.eql(scenario.pingData); - }); - }); - - it('should .close the probing client', () => { - return GPPClient.ping(mkClient).then(([client]) => { - sinon.assert.called(clients[0].close); - sinon.assert.notCalled(client.cmp.close); - }) - }); - - it('should .tag the client with version', () => { - return GPPClient.ping(mkClient).then(([client]) => { - expect(client.apiVersion).to.eql(scenario.apiVersion); - }) - }) - }) - }); - - it('should reject when mkClient returns null (CMP not found)', () => { - return GPPClient.ping(() => null).catch((err) => { - expect(err.message).to.match(/not found/); - }); - }); - - it('should reject when client rejects', () => { - const err = {some: 'prop'}; - const mockClient = () => Promise.reject(err); - mockClient.close = sinon.stub(); - return GPPClient.ping(() => mockClient).catch((result) => { - expect(result).to.eql(err); - sinon.assert.called(mockClient.close); - }); - }); - - it('should reject when callback is invoked with success = false', () => { - const err = 'error'; - const mockClient = ({callback}) => callback(err, false); - mockClient.close = sinon.stub(); - return GPPClient.ping(() => mockClient).catch((result) => { - expect(result).to.eql(err); - sinon.assert.called(mockClient.close); - }) - }) - }); - describe('GPPClient.init', () => { - let makeCmp, cmpCalls, cmpResult; + describe('GPPClient.get', () => { + let makeCmp; beforeEach(() => { - cmpResult = {signalStatus: 'ready', gppString: 'mock-str'}; - cmpCalls = []; makeCmp = sinon.stub().callsFake(() => { - function mockCmp(args) { - cmpCalls.push(args); - return GreedyPromise.resolve(cmpResult); - } - mockCmp.close = sinon.stub(); - return mockCmp; + return sinon.stub() }); }); - it('should re-use same client', (done) => { - GPPClient.init(makeCmp).then(([client]) => { - GPPClient.init(makeCmp).then(([client2, consentPm]) => { - expect(client2).to.equal(client); - expect(cmpCalls.filter((el) => el.command === 'ping').length).to.equal(2) // recycled client should be refreshed - consentPm.then((consent) => { - expect(consent.gppString).to.eql('mock-str'); - done() - }) - }); - }); + it('should re-use same client', () => { + expect(GPPClient.get(makeCmp)).to.equal(GPPClient.get(makeCmp)); + sinon.assert.calledOnce(makeCmp); }); - it('should not re-use errors', (done) => { - cmpResult = GreedyPromise.reject(new Error()); - GPPClient.init(makeCmp).catch(() => { - cmpResult = {signalStatus: 'ready'}; - return GPPClient.init(makeCmp).then(([client]) => { - expect(client).to.exist; - done() - }) - }) + it('should not re-use errors', () => { + try { + GPPClient.get(sinon.stub().throws(new Error())); + } catch (e) {} + expect(GPPClient.get(makeCmp)).to.exist; }) }) describe('GPP client', () => { const CHANGE_EVENTS = ['sectionChange', 'signalStatus']; - let gppClient, gppData, cmpReady, eventListener; + let gppClient, gppData, eventListener; function mockClient(apiVersion = '1.1', cmpVersion = '1.1') { const mockCmp = sinon.stub().callsFake(function ({command, callback}) { @@ -314,10 +177,8 @@ describe('consentManagementGpp', function () { throw new Error('unexpected command: ' + command); } }) - const client = new GPPClient(cmpVersion, mockCmp); + const client = new GPPClient(mockCmp); client.apiVersion = apiVersion; - client.getGPPData = sinon.stub().callsFake(() => Promise.resolve(gppData)); - client.isCMPReady = sinon.stub().callsFake(() => cmpReady); client.events = CHANGE_EVENTS; return client; } @@ -325,7 +186,6 @@ describe('consentManagementGpp', function () { beforeEach(() => { gppDataHandler.reset(); eventListener = null; - cmpReady = true; gppData = { applicableSections: [7], gppString: 'mock-string', @@ -346,7 +206,7 @@ describe('consentManagementGpp', function () { describe('updateConsent', () => { it('should update data handler with consent data', () => { - return gppClient.updateConsent().then(data => { + return gppClient.updateConsent(gppData).then(data => { sinon.assert.match(data, gppData); sinon.assert.match(gppDataHandler.getConsentData(), gppData); expect(gppDataHandler.ready).to.be.true; @@ -358,8 +218,7 @@ describe('consentManagementGpp', function () { 'missing': null }).forEach(([t, data]) => { it(`should not update, and reject promise, when gpp data is ${t}`, (done) => { - gppData = data; - gppClient.updateConsent().catch(err => { + gppClient.updateConsent(data).catch(err => { expect(err.message).to.match(/empty/); expect(err.args).to.eql(data == null ? [] : [data]); expect(gppDataHandler.ready).to.be.false; @@ -368,15 +227,6 @@ describe('consentManagementGpp', function () { }); }) - it('should not update when gpp data rejects', (done) => { - gppData = Promise.reject(new Error('err')); - gppClient.updateConsent().catch(err => { - expect(gppDataHandler.ready).to.be.false; - expect(err.message).to.eql('err'); - done(); - }) - }); - describe('consent data validation', () => { Object.entries({ applicableSections: { @@ -394,7 +244,7 @@ describe('consentManagementGpp', function () { describe(t, () => { it('should not update', (done) => { Object.assign(gppData, {[prop]: value}); - gppClient.updateConsent().catch(err => { + gppClient.updateConsent(gppData).catch(err => { expect(err.message).to.match(/unexpected/); expect(err.args).to.eql([gppData]); expect(gppDataHandler.ready).to.be.false; @@ -409,23 +259,14 @@ describe('consentManagementGpp', function () { }); describe('init', () => { - beforeEach(() => { - gppClient.isCMPReady = function (pingData) { - return pingData.ready; - } - gppClient.getGPPData = function (pingData) { - return Promise.resolve(pingData); - } - }) - it('does not use initial pingData if CMP is not ready', () => { - gppClient.init({...gppData, ready: false}); + gppClient.init({...gppData, signalStatus: 'not ready'}); expect(eventListener).to.exist; expect(gppDataHandler.ready).to.be.false; }); it('uses initial pingData (and resolves promise) if CMP is ready', () => { - return gppClient.init({...gppData, ready: true}).then(data => { + return gppClient.init({...gppData, signalStatus: 'ready'}).then(data => { expect(eventListener).to.exist; sinon.assert.match(data, gppData); sinon.assert.match(gppDataHandler.getConsentData(), gppData); @@ -433,7 +274,7 @@ describe('consentManagementGpp', function () { }); it('rejects promise when CMP errors out', (done) => { - gppClient.init({ready: false}).catch((err) => { + gppClient.init({signalStatus: 'not ready'}).catch((err) => { expect(err.message).to.match(/error/); expect(err.args).to.eql(['error']) done(); @@ -447,7 +288,7 @@ describe('consentManagementGpp', function () { 'irrelevant': {eventName: 'irrelevant'} }).forEach(([t, evt]) => { it(`ignores ${t} events`, () => { - let pm = gppClient.init({ready: false}).catch((err) => err.args[0] !== 'done' && Promise.reject(err)); + let pm = gppClient.init({signalStatus: 'not ready'}).catch((err) => err.args[0] !== 'done' && Promise.reject(err)); eventListener(evt); eventListener('done', false); return pm; @@ -456,7 +297,7 @@ describe('consentManagementGpp', function () { it('rejects the promise when cmpStatus is "error"', (done) => { const evt = {eventName: 'other', pingData: {cmpStatus: 'error'}}; - gppClient.init({ready: false}).catch(err => { + gppClient.init({signalStatus: 'not ready'}).catch(err => { expect(err.message).to.match(/error/); expect(err.args).to.eql([evt]); done(); @@ -479,31 +320,30 @@ describe('consentManagementGpp', function () { }); it('does not fire consent data updates if the CMP is not ready', (done) => { - gppClient.init({ready: false}).catch(() => { + gppClient.init({signalStatus: 'not ready'}).catch(() => { expect(gppDataHandler.ready).to.be.false; done(); }); - eventListener({...gppData2, ready: false}); + eventListener({...gppData2, signalStatus: 'not ready'}); eventListener('done', false); }) it('fires consent data updates (and resolves promise) if CMP is ready', (done) => { - gppClient.init({ready: false}).then(data => { + gppClient.init({signalStatus: 'not ready'}).then(data => { sinon.assert.match(data, gppData2); done() }); - cmpReady = true; - eventListener(makeEvent({...gppData2, ready: true})); + eventListener(makeEvent({...gppData2, signalStatus: 'ready'})); }); it('keeps updating consent data on new events', () => { - let pm = gppClient.init({ready: false}).then(data => { + let pm = gppClient.init({signalStatus: 'not ready'}).then(data => { sinon.assert.match(data, gppData); sinon.assert.match(gppDataHandler.getConsentData(), gppData); }); - eventListener(makeEvent({...gppData, ready: true})); + eventListener(makeEvent({...gppData, signalStatus: 'ready'})); return pm.then(() => { - eventListener(makeEvent({...gppData2, ready: true})) + eventListener(makeEvent({...gppData2, signalStatus: 'ready'})) }).then(() => { sinon.assert.match(gppDataHandler.getConsentData(), gppData2); }); @@ -513,137 +353,11 @@ describe('consentManagementGpp', function () { }); }); - describe('GPP 1.0 protocol', () => { - let mockCmp, gppClient; - beforeEach(() => { - mockCmp = sinon.stub(); - gppClient = new (GPPClient.getClient('1.0'))('1.0', mockCmp); - }); - - describe('isCMPReady', () => { - Object.entries({ - 'loaded': [true, 'loaded'], - 'other': [false, 'other'], - 'undefined': [false, undefined] - }).forEach(([t, [expected, cmpStatus]]) => { - it(`should be ${expected} when cmpStatus is ${t}`, () => { - expect(gppClient.isCMPReady(Object.assign({}, {cmpStatus}))).to.equal(expected); - }); - }); - }); - - describe('getGPPData', () => { - let gppData, pingData; - beforeEach(() => { - gppData = { - gppString: 'mock-string', - supportedAPIs: ['usnat'], - applicableSections: [7, 8] - } - pingData = { - supportedAPIs: gppData.supportedAPIs - }; - }); - - function mockCmpCommands(commands) { - mockCmp.callsFake(({command, parameter}) => { - if (commands.hasOwnProperty((command))) { - return Promise.resolve(commands[command](parameter)); - } else { - return Promise.reject(new Error(`unrecognized command ${command}`)) - } - }) - } - - it('should retrieve consent string and applicableSections', () => { - mockCmpCommands({ - getGPPData: () => gppData - }) - return gppClient.getGPPData(pingData).then(data => { - sinon.assert.match(data, gppData); - }) - }); - - it('should reject when getGPPData rejects', (done) => { - mockCmpCommands({ - getGPPData: () => Promise.reject(new Error('err')) - }); - gppClient.getGPPData(pingData).catch(err => { - expect(err.message).to.eql('err'); - done(); - }); - }); - - it('should not choke if supportedAPIs is missing', () => { - [gppData, pingData].forEach(ob => { delete ob.supportedAPIs; }) - mockCmpCommands({ - getGPPData: () => gppData - }); - return gppClient.getGPPData(pingData).then(res => { - expect(res.gppString).to.eql(gppData.gppString); - expect(res.parsedSections).to.eql({}); - }) - }) - - describe('section data', () => { - let usnat, parsedUsnat; - - function mockSections(sections) { - mockCmpCommands({ - getGPPData: () => gppData, - getSection: (api) => (sections[api]) - }); - }; - - beforeEach(() => { - usnat = { - MockField: 'val', - OtherField: 'o', - Gpc: true - }; - parsedUsnat = [ - { - MockField: 'val', - OtherField: 'o' - }, - { - SubsectionType: 1, - Gpc: true - } - ] - }); - - it('retrieves section data', () => { - mockSections({usnat}); - return gppClient.getGPPData(pingData).then(data => { - expect(data.parsedSections).to.eql({usnat: parsedUsnat}) - }); - }); - - it('does not choke if a section is missing', () => { - mockSections({usnat}); - gppData.supportedAPIs = ['usnat', 'missing']; - return gppClient.getGPPData(pingData).then(data => { - expect(data.parsedSections).to.eql({usnat: parsedUsnat}); - }) - }); - - it('does not choke if a section fails', () => { - mockSections({usnat, err: Promise.reject(new Error('err'))}); - gppData.supportedAPIs = ['usnat', 'err']; - return gppClient.getGPPData(pingData).then(data => { - expect(data.parsedSections).to.eql({usnat: parsedUsnat}); - }) - }); - }) - }); - }); - describe('GPP 1.1 protocol', () => { let mockCmp, gppClient; beforeEach(() => { mockCmp = sinon.stub(); - gppClient = new (GPPClient.getClient('1.1'))('1.1', mockCmp); + gppClient = new GPPClient(mockCmp); }); describe('isCMPReady', () => { @@ -657,82 +371,6 @@ describe('consentManagementGpp', function () { }); }); }); - - it('gets GPPData from pingData', () => { - mockCmp.throws(new Error()); - const pingData = { - 'gppVersion': '1.1', - 'cmpStatus': 'loaded', - 'cmpDisplayStatus': 'disabled', - 'supportedAPIs': [ - '5:tcfcav1', - '7:usnat', - '8:usca', - '9:usva', - '10:usco', - '11:usut', - '12:usct' - ], - 'signalStatus': 'ready', - 'cmpId': 31, - 'sectionList': [ - 7 - ], - 'applicableSections': [ - 7 - ], - 'gppString': 'DBABL~BAAAAAAAAgA.QA', - 'parsedSections': { - 'usnat': [ - { - 'Version': 1, - 'SharingNotice': 0, - 'SaleOptOutNotice': 0, - 'SharingOptOutNotice': 0, - 'TargetedAdvertisingOptOutNotice': 0, - 'SensitiveDataProcessingOptOutNotice': 0, - 'SensitiveDataLimitUseNotice': 0, - 'SaleOptOut': 0, - 'SharingOptOut': 0, - 'TargetedAdvertisingOptOut': 0, - 'SensitiveDataProcessing': [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], - 'KnownChildSensitiveDataConsents': [ - 0, - 0 - ], - 'PersonalDataConsents': 0, - 'MspaCoveredTransaction': 2, - 'MspaOptOutOptionMode': 0, - 'MspaServiceProviderMode': 0 - }, - { - 'SubsectionType': 1, - 'Gpc': false - } - ] - } - }; - return gppClient.getGPPData(pingData).then((gppData) => { - sinon.assert.match(gppData, { - gppString: pingData.gppString, - applicableSections: pingData.applicableSections, - parsedSections: pingData.parsedSections - }) - }) - }) }) describe('requestBidsHook tests:', function () { diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index c372c66f7f0..5f589ee5fe7 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -493,7 +493,6 @@ describe('consentManagement', function () { sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); - expect(consentMeta.usp).to.equal(testConsentData.uspString); expect(consentMeta.generatedAt).to.be.above(1644367751709); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index c1ed042a2c8..73db5600ba6 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -8,7 +8,7 @@ import { setConsentConfig, staticConsentData, userCMP -} from 'modules/consentManagement.js'; +} from 'modules/consentManagementTcf.js'; import {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 39e66316ec1..c0560e08431 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -9,7 +9,7 @@ import 'src/prebid.js' import 'modules/currency.js'; import 'modules/userId/index.js'; // handles eids import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; // handles schain import {hook} from '../../../src/hook.js' diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index dfdbebde738..aeb17f37161 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -40,21 +40,21 @@ describe('craftAdapter', function () { }); it('should return false when params.sitekey not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { placementId: '1234abcd' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when params.placementId not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { sitekey: 'craft-prebid-example' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when AMP cotext found', function () { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index def35b13955..079357ab4fe 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,17 +1,20 @@ import { expect } from 'chai'; import { - tryGetCriteoFastBid, spec, storage, - PROFILE_ID_PUBLISHERTAG, - ADAPTER_VERSION, - canFastBid, getFastBidUrl, FAST_BID_VERSION_CURRENT } from 'modules/criteoBidAdapter.js'; import * as utils from 'src/utils.js'; import * as refererDetection from 'src/refererDetection.js'; import * as ajax from 'src/ajax.js'; import { config } from '../../../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import 'modules/userId/index.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/schain.js'; +import {hook} from '../../../src/hook'; describe('The Criteo bidding adapter', function () { let utilsMock, sandbox, ajaxStub; @@ -157,14 +160,6 @@ describe('The Criteo bidding adapter', function () { removeDataFromLocalStorageStub.restore(); }); - it('should not trigger sync if publisher is using fast bid', function () { - getConfigStub.withArgs('criteo.fastBidVersion').returns('latest'); - - const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); - - expect(userSyncs).to.eql([]); - }); - it('should not trigger sync if publisher did not enable iframe based syncs', function () { const userSyncs = spec.getUserSyncs({ iframeEnabled: false @@ -602,8 +597,8 @@ describe('The Criteo bidding adapter', function () { }, timeout: 3000, gdprConsent: { - gdprApplies: 1, - consentString: 'concentDataString', + gdprApplies: true, + consentString: 'consentDataString', vendorData: { vendorConsents: { '91': 1 @@ -615,6 +610,10 @@ describe('The Criteo bidding adapter', function () { let localStorageIsEnabledStub; + before(() => { + hook.ready(); + }); + this.beforeEach(function () { localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); localStorageIsEnabledStub.returns(true); @@ -628,13 +627,11 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request using random uuid as auction id', function () { const generateUUIDStub = sinon.stub(utils, 'generateUUID'); generateUUIDStub.returns('def'); - const bidderRequest = { - }; + const bidderRequest = {}; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -643,7 +640,7 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.id).to.equal('def'); generateUUIDStub.restore(); @@ -661,7 +658,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -670,7 +666,7 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.source.tid).to.equal('abc'); }); @@ -689,49 +685,18 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); }); it('should properly transmit bidId if available', function () { - const bidderRequest = { - ortb2: { - source: { - tid: 'abc' - } - } - }; - const bidRequests = [ - { - bidId: 'bidId', - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: {} - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].slotid).to.equal('bidId'); - }); - - it('should properly build a request if refererInfo is not provided', function () { const bidderRequest = {}; const bidRequests = [ { + bidId: 'bidId', bidder: 'criteo', adUnitCode: 'bid-123', - ortb2Imp: { - ext: { - tid: 'transaction-123', - }, - }, mediaTypes: { banner: { sizes: [[728, 90]] @@ -740,9 +705,9 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(''); + expect(ortbRequest.imp[0].id).to.equal('bidId'); }); it('should properly build a zoneId request', function () { @@ -763,77 +728,24 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 123, publisherSubId: '123', - nativeCallback: function () { }, integrationMode: 'amp' }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=1&im=1&debug=1&nolog=1/); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&im=1&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(refererUrl); - expect(ortbRequest.slots).to.have.lengthOf(1); - expect(ortbRequest.slots[0].impid).to.equal('bid-123'); - expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('728x90'); - expect(ortbRequest.slots[0].zoneid).to.equal(123); - expect(ortbRequest.gdprConsent.consentData).to.equal('concentDataString'); - expect(ortbRequest.gdprConsent.gdprApplies).to.equal(true); - expect(ortbRequest.gdprConsent.version).to.equal(1); - }); - - it('should keep undefined sizes for non native banner', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [[undefined, undefined]] - } - }, - params: {}, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); - }); - - it('should keep undefined size for non native banner', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [undefined, undefined] - } - }, - params: {}, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); - }); - - it('should properly detect and forward native flag', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [[undefined, undefined]] - } - }, - params: { - nativeCallback: function () { } - }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].native).to.equal(true); + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + expect(ortbRequest.regs.ext.gdprversion).to.equal(1); }); it('should properly forward eids', function () { @@ -841,7 +753,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -859,7 +770,7 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.user.ext.eids).to.deep.equal([ { @@ -872,79 +783,81 @@ describe('The Criteo bidding adapter', function () { ]); }); - it('should properly detect and forward native flag', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [undefined, undefined] - } - }, - params: { - nativeCallback: function () { } + if (FEATURES.NATIVE) { + it('should properly build a native request without assets', function () { + const bidRequests = [ + { + mediaTypes: { + native: {} + }, + params: {} }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].native).to.equal(true); - }); + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].native.request_native).to.not.be.null; + expect(ortbRequest.imp[0].native.request_native.assets).to.be.undefined; + }); + } - it('should map ortb native assets to slot ext assets', function () { - const assets = [{ - required: 1, - id: 1, - img: { - type: 3, - wmin: 100, - hmin: 100, - } - }, - { - required: 1, - id: 2, - title: { - len: 140, - } - }, - { - required: 1, - id: 3, - data: { - type: 1, - } - }, - { - required: 0, - id: 4, - data: { - type: 2, - } - }, - { - required: 0, - id: 5, - img: { - type: 1, - wmin: 20, - hmin: 20, - } - }]; - const bidRequests = [ + if (FEATURES.NATIVE) { + it('should properly build a native request with assets', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, { - nativeOrtbRequest: { - assets: assets - }, - params: { - nativeCallback: function () { } - }, + required: 1, + id: 2, + title: { + len: 140, + } }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].native).to.equal(true); - expect(ortbRequest.slots[0].ext.assets).to.deep.equal(assets); - }); + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }]; + const bidRequests = [ + { + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: assets + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].native.request_native).to.not.be.null; + expect(ortbRequest.imp[0].native.request_native.assets).to.deep.equal(assets); + }); + } it('should properly build a networkId request', function () { const bidderRequest = { @@ -954,7 +867,7 @@ describe('The Criteo bidding adapter', function () { }, timeout: 3000, gdprConsent: { - gdprApplies: 0, + gdprApplies: false, consentString: undefined, vendorData: { vendorConsents: { @@ -982,23 +895,23 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(refererUrl); - expect(ortbRequest.publisher.networkid).to.equal(456); - expect(ortbRequest.slots).to.have.lengthOf(1); - expect(ortbRequest.slots[0].impid).to.equal('bid-123'); - expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(2); - expect(ortbRequest.slots[0].sizes[0]).to.equal('300x250'); - expect(ortbRequest.slots[0].sizes[1]).to.equal('728x90'); - expect(ortbRequest.gdprConsent.consentData).to.equal(undefined); - expect(ortbRequest.gdprConsent.gdprApplies).to.equal(false); - }); - - it('should properly build a mixed request', function () { + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(2); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(90); + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); + expect(ortbRequest.regs.ext.gdpr).to.equal(0); + }); + + it('should properly build a mixed request with both a zoneId and a networkId', function () { const bidderRequest = { refererInfo: { page: refererUrl, @@ -1042,23 +955,26 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(refererUrl); - expect(ortbRequest.publisher.networkid).to.equal(456); - expect(ortbRequest.slots).to.have.lengthOf(2); - expect(ortbRequest.slots[0].impid).to.equal('bid-123'); - expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('728x90'); - expect(ortbRequest.slots[1].impid).to.equal('bid-234'); - expect(ortbRequest.slots[1].transactionid).to.equal('transaction-234'); - expect(ortbRequest.slots[1].sizes).to.have.lengthOf(2); - expect(ortbRequest.slots[1].sizes[0]).to.equal('300x250'); - expect(ortbRequest.slots[1].sizes[1]).to.equal('728x90'); - expect(ortbRequest.gdprConsent).to.equal(undefined); + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(2); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.tid).to.equal('transaction-123'); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.imp[1].tagid).to.equal('bid-234'); + expect(ortbRequest.imp[1].banner.format).to.have.lengthOf(2); + expect(ortbRequest.imp[1].banner.format[0].w).to.equal(300); + expect(ortbRequest.imp[1].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[1].banner.format[1].w).to.equal(728); + expect(ortbRequest.imp[1].banner.format[1].h).to.equal(90); + expect(ortbRequest.imp[1].ext.tid).to.equal('transaction-234'); + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); }); it('should properly build a request with undefined gdpr consent fields when they are not provided', function () { @@ -1066,7 +982,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1082,9 +997,9 @@ describe('The Criteo bidding adapter', function () { gdprConsent: {}, }; - const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; - expect(ortbRequest.gdprConsent.consentData).to.equal(undefined); - expect(ortbRequest.gdprConsent.gdprApplies).to.equal(undefined); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); + expect(ortbRequest.regs?.ext?.gdpr).to.equal(undefined); }); it('should properly build a request with ccpa consent field', function () { @@ -1092,7 +1007,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1108,42 +1022,43 @@ describe('The Criteo bidding adapter', function () { uspConsent: '1YNY', }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user).to.not.be.null; - expect(request.data.user.uspIab).to.equal('1YNY'); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.ext.us_privacy).to.equal('1YNY'); }); - it('should properly build a request with site and app ortb fields', function () { - const bidRequests = []; - let app = { - publisher: { - id: 'appPublisherId' - } - }; - let site = { - publisher: { - id: 'sitePublisherId' - } - }; + it('should properly build a request with overridden tmax', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; const bidderRequest = { - ortb2: { - app: app, - site: site - } + timeout: 1234 }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.app).to.equal(app); - expect(request.data.site).to.equal(site); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.tmax).to.equal(1234); }); it('should properly build a request with device sua field', function () { - const sua = {} + const sua = { + platform: { + brand: 'abc' + } + } const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1164,9 +1079,9 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user.ext.sua).to.not.be.null; - expect(request.data.user.ext.sua).to.equal(sua); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.device.ext.sua).not.to.be.null; + expect(ortbRequest.device.ext.sua.platform.brand).to.equal('abc'); }); it('should properly build a request with gpp consent field', function () { @@ -1174,7 +1089,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1192,10 +1106,9 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); - expect(request.data.regs).to.not.be.null; - expect(request.data.regs.gpp).to.equal('gpp_consent_string'); - expect(request.data.regs.gpp_sid).to.deep.equal([0, 1, 2]); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string'); + expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); }); it('should properly build a request with dsa object', function () { @@ -1203,7 +1116,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1234,10 +1146,8 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); - expect(request.data.regs).to.not.be.null; - expect(request.data.regs.ext).to.not.be.null; - expect(request.data.regs.ext.dsa).to.deep.equal(dsa); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.ext.dsa).to.deep.equal(dsa); }); it('should properly build a request with schain object', function () { @@ -1249,7 +1159,6 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', schain: expectedSchain, adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1261,8 +1170,8 @@ describe('The Criteo bidding adapter', function () { }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.source.ext.schain).to.equal(expectedSchain); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.source.ext.schain).to.equal(expectedSchain); }); it('should properly build a request with bcat field', function () { @@ -1271,7 +1180,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1288,9 +1196,8 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bcat).to.not.be.null; - expect(request.data.bcat).to.equal(bcat); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bcat).to.deep.equal(bcat); }); it('should properly build a request with badv field', function () { @@ -1299,7 +1206,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1316,9 +1222,8 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.badv).to.not.be.null; - expect(request.data.badv).to.equal(badv); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.badv).to.deep.equal(badv); }); it('should properly build a request with bapp field', function () { @@ -1327,7 +1232,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1344,331 +1248,263 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bapp).to.not.be.null; - expect(request.data.bapp).to.equal(bapp); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bapp).to.deep.equal(bapp); }); - it('should properly build a request with if ccpa consent field is not provided', function () { + if (FEATURES.VIDEO) { + it('should properly build a video request', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [640, 480], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 480, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + } + }, + params: { + zoneId: 123, + video: { + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + expect(ortbRequest.imp[0].video.w).to.equal(640); + expect(ortbRequest.imp[0].video.h).to.equal(480); + expect(ortbRequest.imp[0].video.linearity).to.equal(1); + expect(ortbRequest.imp[0].video.skipmin).to.equal(30); + expect(ortbRequest.imp[0].video.skipafter).to.equal(30); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(10000); + expect(ortbRequest.imp[0].video.maxbitrate).to.equal(48000); + expect(ortbRequest.imp[0].video.delivery).to.deep.equal([1, 2, 3]); + expect(ortbRequest.imp[0].video.pos).to.equal(1); + expect(ortbRequest.imp[0].video.playbackend).to.equal(1); + expect(ortbRequest.imp[0].video.ext.context).to.equal('inbanner'); + expect(ortbRequest.imp[0].video.ext.playersizes).to.deep.equal(['640x480']); + expect(ortbRequest.imp[0].video.ext.plcmt).to.equal(3); + expect(ortbRequest.imp[0].video.ext.poddur).to.equal(30); + expect(ortbRequest.imp[0].video.ext.rqddurs).to.deep.equal([1, 30]); + }); + } + + if (FEATURES.VIDEO) { + it('should properly build a video request with more than one player size', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + sizes: [[640, 480], [800, 600]], + mediaTypes: { + video: { + playerSize: [[640, 480], [800, 600]], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3] + } + }, + params: { + zoneId: 123, + video: { + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + expect(ortbRequest.imp[0].video.w).to.equal(640); + expect(ortbRequest.imp[0].video.h).to.equal(480); + expect(ortbRequest.imp[0].video.ext.playersizes).to.deep.equal(['640x480', '800x600']); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + }); + } + + if (FEATURES.VIDEO) { + it('should properly build a video request when mediaTypes.video.skip=0', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + mimes: ['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg'], + minduration: 1, + maxduration: 30, + playbackmethod: [2, 3, 4, 5, 6], + api: [1, 2, 3, 4, 5, 6], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 0 + } + }, + params: { + networkId: 456 + } + } + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg']); + expect(ortbRequest.imp[0].video.minduration).to.equal(1); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([2, 3, 4, 5, 6]); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2, 3, 4, 5, 6]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([1, 2, 3, 4, 5, 6, 7, 8]); + expect(ortbRequest.imp[0].video.skip).to.equal(0); + expect(ortbRequest.imp[0].video.w).to.equal(300); + expect(ortbRequest.imp[0].video.h).to.equal(250); + expect(ortbRequest.imp[0].video.ext.playersizes).to.deep.equal(['300x250']); + }); + } + + it('should properly build a request without first party data', function () { const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - }, + zoneId: 123 + } }, ]; - const bidderRequest = { - timeout: 3000 - }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user).to.not.be.null; - expect(request.data.user.uspIab).to.equal(undefined); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2: {} })).data; + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + expect(ortbRequest.regs.ext.gdprversion).to.equal(1); }); - it('should properly build a video request', function () { + it('should properly build a request with first party data', function () { + const siteData = { + keywords: ['power tools'], + content: { + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }] + }, + ext: { + data: { + pageType: 'article' + } + } + }; + const userData = { + gender: 'M', + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }], + ext: { + data: { + registered: true + } + } + }; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[640, 480]], mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4', 'video/x-flv'], - maxduration: 30, - api: [1, 2], - protocols: [2, 3], - plcmt: 3, - w: 640, - h: 480, - linearity: 1, - skipmin: 30, - skipafter: 30, - minbitrate: 10000, - maxbitrate: 48000, - delivery: [1, 2, 3], - pos: 1, - playbackend: 1, - adPodDurationSec: 30, - durationRangeSec: [1, 30], + banner: { + sizes: [[728, 90]] } }, params: { zoneId: 123, - video: { - skip: 1, - minduration: 5, - startdelay: 5, - playbackmethod: [1, 3], - placement: 2 + ext: { + bidfloor: 0.75 } }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].video.context).to.equal('instream'); - expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(ortbRequest.slots[0].sizes).to.deep.equal([]); - expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480']); - expect(ortbRequest.slots[0].video.maxduration).to.equal(30); - expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); - expect(ortbRequest.slots[0].video.protocols).to.deep.equal([2, 3]); - expect(ortbRequest.slots[0].video.skip).to.equal(1); - expect(ortbRequest.slots[0].video.minduration).to.equal(5); - expect(ortbRequest.slots[0].video.startdelay).to.equal(5); - expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); - expect(ortbRequest.slots[0].video.placement).to.equal(2); - expect(ortbRequest.slots[0].video.plcmt).to.equal(3); - expect(ortbRequest.slots[0].video.w).to.equal(640); - expect(ortbRequest.slots[0].video.h).to.equal(480); - expect(ortbRequest.slots[0].video.linearity).to.equal(1); - expect(ortbRequest.slots[0].video.skipmin).to.equal(30); - expect(ortbRequest.slots[0].video.skipafter).to.equal(30); - expect(ortbRequest.slots[0].video.minbitrate).to.equal(10000); - expect(ortbRequest.slots[0].video.maxbitrate).to.equal(48000); - expect(ortbRequest.slots[0].video.delivery).to.deep.equal([1, 2, 3]); - expect(ortbRequest.slots[0].video.pos).to.equal(1); - expect(ortbRequest.slots[0].video.playbackend).to.equal(1); - expect(ortbRequest.slots[0].video.adPodDurationSec).to.equal(30); - expect(ortbRequest.slots[0].video.durationRangeSec).to.deep.equal([1, 30]); - }); - - it('should properly build a video request with more than one player size', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[640, 480], [800, 600]], - mediaTypes: { - video: { - playerSize: [[640, 480], [800, 600]], - mimes: ['video/mp4', 'video/x-flv'], - maxduration: 30, - api: [1, 2], - protocols: [2, 3] - } - }, - params: { - zoneId: 123, - video: { - skip: 1, - minduration: 5, - startdelay: 5, - playbackmethod: [1, 3], - placement: 2 - } - }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.deep.equal([]); - expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480', '800x600']); - expect(ortbRequest.slots[0].video.maxduration).to.equal(30); - expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); - expect(ortbRequest.slots[0].video.protocols).to.deep.equal([2, 3]); - expect(ortbRequest.slots[0].video.skip).to.equal(1); - expect(ortbRequest.slots[0].video.minduration).to.equal(5); - expect(ortbRequest.slots[0].video.startdelay).to.equal(5); - expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); - expect(ortbRequest.slots[0].video.placement).to.equal(2); - }); - - it('should properly build a video request when mediaTypes.video.skip=0', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[300, 250]], - mediaTypes: { - video: { - playerSize: [[300, 250]], - mimes: ['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg'], - minduration: 1, - maxduration: 30, - playbackmethod: [2, 3, 4, 5, 6], - api: [1, 2, 3, 4, 5, 6], - protocols: [1, 2, 3, 4, 5, 6, 7, 8], - skip: 0 - } - }, - params: { - networkId: 123 - } - } - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.deep.equal([]); - expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['300x250']); - expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg']); - expect(ortbRequest.slots[0].video.minduration).to.equal(1); - expect(ortbRequest.slots[0].video.maxduration).to.equal(30); - expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([2, 3, 4, 5, 6]); - expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2, 3, 4, 5, 6]); - expect(ortbRequest.slots[0].video.protocols).to.deep.equal([1, 2, 3, 4, 5, 6, 7, 8]); - expect(ortbRequest.slots[0].video.skip).to.equal(0); - }); - - it('should properly build a request with ceh', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123, - }, - }, - ]; - config.setConfig({ - criteo: { - ceh: 'hashedemail' - } - }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user).to.not.be.null; - expect(request.data.user.ceh).to.equal('hashedemail'); - }); - - it('should properly build a request without first party data', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123 - } - }, - ]; - - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2: {} }); - expect(request.data.publisher.ext).to.equal(undefined); - expect(request.data.user.ext).to.equal(undefined); - expect(request.data.slots[0].ext).to.equal(undefined); - }); - - it('should properly build a request with criteo specific ad unit first party data', function () { - // TODO: this test does not do what it says - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } - } - }, - ]; - - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2: {} }); - expect(request.data.slots[0].ext).to.deep.equal({ - bidfloor: 0.75, - }); - }); - - it('should properly build a request with first party data', function () { - const siteData = { - keywords: ['power tools'], - content: { - data: [{ - name: 'some_provider', - ext: { - segtax: 3 - }, - segment: [ - { 'id': '1001' }, - { 'id': '1002' } - ] - }] - }, - ext: { - data: { - pageType: 'article' - } - } - }; - const userData = { - gender: 'M', - data: [{ - name: 'some_provider', - ext: { - segtax: 3 - }, - segment: [ - { 'id': '1001' }, - { 'id': '1002' } - ] - }], - ext: { - data: { - registered: true - } - } - }; - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } - }, - ortb2Imp: { - ext: { - data: { - someContextAttribute: 'abc' - } - } - } + ortb2Imp: { + ext: { + data: { + someContextAttribute: 'abc' + } + } + } }, ]; @@ -1677,41 +1513,34 @@ describe('The Criteo bidding adapter', function () { user: userData }; - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); - expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); - expect(request.data.user).to.deep.equal(userData); - expect(request.data.site).to.deep.equal(siteData); - expect(request.data.slots[0].ext).to.deep.equal({ - bidfloor: 0.75, - data: { - someContextAttribute: 'abc' - } - }); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.user).to.deep.equal({ ...userData, ext: { ...userData.ext, consent: 'consentDataString' } }); + expect(ortbRequest.site).to.deep.equal({ ...siteData, page: refererUrl, domain: 'criteo.com', publisher: { ...ortbRequest.site.publisher, domain: 'criteo.com' } }); + expect(ortbRequest.imp[0].ext.bidfloor).to.equal(0.75); + expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.equal('abc') }); it('should properly build a request when coppa flag is true', function () { const bidRequests = []; const bidderRequest = {}; config.setConfig({ coppa: true }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.regs.coppa).to.not.be.undefined; - expect(request.data.regs.coppa).to.equal(1); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(1); }); it('should properly build a request when coppa flag is false', function () { const bidRequests = []; const bidderRequest = {}; config.setConfig({ coppa: false }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.regs.coppa).to.not.be.undefined; - expect(request.data.regs.coppa).to.equal(0); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(0); }); it('should properly build a request when coppa flag is not defined', function () { const bidRequests = []; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.regs.coppa).to.be.undefined; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs?.coppa).to.be.undefined; }); it('should properly build a banner request with floors', function () { @@ -1719,7 +1548,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -1747,8 +1575,8 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.floors).to.deep.equal({ + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } @@ -1761,7 +1589,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -1775,8 +1602,8 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.floors).to.deep.equal({ + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { '300x250': { 'currency': 'EUR', 'floor': 1 }, '728x90': { 'currency': 'EUR', 'floor': 1 } @@ -1789,7 +1616,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { video: { playerSize: [[300, 250], [728, 90]] @@ -1817,8 +1643,8 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.floors).to.deep.equal({ + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'video': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } @@ -1826,75 +1652,79 @@ describe('The Criteo bidding adapter', function () { }); }); - it('should properly build a multi format request with floors', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] + if (FEATURES.VIDEO && FEATURES.NATIVE) { + it('should properly build a multi format request with floors', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + }, + video: { + playerSize: [640, 480], + }, + native: {} }, - video: { - playerSize: [640, 480], + params: { + networkId: 456, }, - native: {} - }, - params: { - networkId: 456, - }, - ortb2Imp: { - ext: { - data: { - someContextAttribute: 'abc' + ortb2Imp: { + ext: { + data: { + someContextAttribute: 'abc' + } } - } - }, + }, - getFloor: inputParams => { - if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { - return { - currency: 'USD', - floor: 1.0 - }; - } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { - return { - currency: 'USD', - floor: 2.0 - }; - } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 640 && inputParams.size[1] === 480) { - return { - currency: 'EUR', - floor: 3.2 - }; - } else if (inputParams.mediaType === NATIVE && inputParams.size === '*') { - return { - currency: 'YEN', - floor: 4.99 - }; - } else { - return {} + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 640 && inputParams.size[1] === 480) { + return { + currency: 'EUR', + floor: 3.2 + }; + } else if (inputParams.mediaType === NATIVE && inputParams.size === '*') { + return { + currency: 'YEN', + floor: 4.99 + }; + } else { + return {} + } } + }, + ]; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].video).not.to.be.null; + expect(ortbRequest.imp[0].native.request_native).not.to.be.null; + expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.deep.equal('abc'); + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + }, + 'video': { + '640x480': { 'currency': 'EUR', 'floor': 3.2 } + }, + 'native': { + '*': { 'currency': 'YEN', 'floor': 4.99 } } - }, - ]; - const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.data.someContextAttribute).to.deep.equal('abc'); - expect(request.data.slots[0].ext.floors).to.deep.equal({ - 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } - }, - 'video': { - '640x480': { 'currency': 'EUR', 'floor': 3.2 } - }, - 'native': { - '*': { 'currency': 'YEN', 'floor': 4.99 } - } + }); }); - }); + } it('should properly build a request when imp.rwdd is present', function () { const bidderRequest = {}; @@ -1902,32 +1732,22 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { - rwdd: 1, - ext: { - data: { - someContextAttribute: 'abc' - } - } + rwdd: 1 } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].rwdd).to.be.not.null; - expect(request.data.slots[0].rwdd).to.equal(1); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.rwdd).to.equal(1); }); it('should properly build a request when imp.rwdd is false', function () { @@ -1936,95 +1756,86 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { - rwdd: 0, - ext: { - data: { - someContextAttribute: 'abc' - } - } + rwdd: 0 } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].rwdd).to.be.undefined; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext?.rwdd).to.equal(0); }); it('should properly build a request when FLEDGE is enabled', function () { const bidderRequest = { - fledgeEnabled: true, + paapi: { + enabled: true + } }; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { ext: { - ae: 1 + igs: { + ae: 1 + } } } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.ae).to.equal(1); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.igs.ae).to.equal(1); }); it('should properly build a request when FLEDGE is disabled', function () { const bidderRequest = { - fledgeEnabled: false, + paapi: { + enabled: false + }, }; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { ext: { - ae: 1 + igs: { + ae: 1 + } } } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext).to.not.have.property('ae'); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.igs?.ae).to.be.undefined; }); it('should properly transmit the pubid and slot uid if available', function () { @@ -2075,12 +1886,11 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; - expect(ortbRequest.publisher.id).to.be.undefined; expect(ortbRequest.site.publisher.id).to.equal('pub-888'); - expect(request.data.slots[0].ext.bidder).to.be.undefined; - expect(request.data.slots[1].ext.bidder.uid).to.equal(888); + expect(ortbRequest.imp[0].ext.bidder.uid).to.be.undefined; + expect(ortbRequest.imp[1].ext.bidder.uid).to.equal(888); }); it('should properly transmit device.ext.cdep if available', function () { @@ -2094,136 +1904,243 @@ describe('The Criteo bidding adapter', function () { } }; const bidRequests = []; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.device.ext.cdep).to.equal('cookieDeprecationLabel'); }); }); describe('interpretResponse', function () { - it('should return an empty array when parsing a no bid response', function () { + const refererUrl = 'https://criteo.com?pbt_debug=1&pbt_nolog=1'; + const bidderRequest = { + refererInfo: { + page: refererUrl, + topmostLocation: refererUrl + }, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '91': 1 + }, + }, + apiVersion: 1, + }, + }; + + function mockResponse(winningBidId, mediaType) { + return { + id: 'test-requestId', + seatbid: [ + { + seat: 'criteo', + bid: [ + { + id: 'test-bidderId', + impid: winningBidId, + price: 1.23, + adomain: ['criteo.com'], + bundle: '', + iurl: 'http://some_image/', + cid: '123456', + crid: 'test-crId', + dealid: 'deal-code', + w: 728, + h: 90, + adm: 'test-ad', + adm_native: mediaType === NATIVE ? { + ver: '1.2', + assets: [ + { + id: 10, + title: { + text: 'Some product' + } + }, + { + id: 11, + img: { + type: 3, + url: 'https://main_image_url.com', + w: 400, + h: 400 + } + }, + { + id: 12, + data: { + value: 'Some product' + } + }, + { + id: 13, + data: { + value: '1,499 TL' + } + }, + { + id: 15, + data: { + value: 'CTA' + }, + link: { + url: 'https://cta_url.com' + } + }, + { + id: 17, + img: { + type: 1, + url: 'https://main_image_url.com', + w: 200, + h: 200 + }, + link: { + url: 'https://icon_image_url.com' + } + }, + { + id: 16, + data: { + value: 'Some brand' + } + } + ], + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventtrackers.com' + }, + { + event: 1, + method: 1, + url: 'https://test_in_isolation.criteo.com/tpd?dd=HTlW9l9xTEZqRHVlSHFiSWx5Q2VQMlEwSTJhNCUyQkxNazQ1Y29LR3ZmS2VTSDFsUGdkRHNoWjQ2UWp0SGtVZ1RTbHI0TFRpTlVqNWxiUkZOeGVFNjVraW53R0loRVJQNDJOY2R1eWxVdjBBQ1BEdVFvTyUyRlg3aWJaeUFha3UyemNNVGpmJTJCS1prc0FwRjZRJTJCQ2dpaFBJeVhZRmQlMkZURVZocUFRdm03OTdFZHZSbURNZWt4Uzh2M1NSUUxmTmhaTnNnRXd4VkZlOTdJOXdnNGZjaVolMkZWYmdYVjJJMkQ0eGxQaFIwQmVtWk1sQ09tNXlGY0Nwc09GTDladzExJTJGVExGNXJsdGpneERDeTMlMkJuNUlUcEU4NDFLMTZPc2ZoWFUwMmpGbDFpVjBPZUVtTlEwaWNOeHRyRFYyenRKd0lpJTJGTTElMkY1WGZ3Smo3aTh0bUJzdzZRdlZUSXppanNkamo3ekZNZjhKdjl2VDJ5eHV1YnVzdmdRdk5iWnprNXVFMVdmbGs0QU1QY0ozZQ' + } + ], + privacy: 'https://cta_url.com', + ext: { + privacy: { + imageurl: 'https://icon_image_url.com', + clickurl: 'https://cta_url.com', + longlegaltext: '' + } + } + } : undefined, + ext: { + mediatype: mediaType, + displayurl: mediaType === VIDEO ? 'http://test-ad' : undefined, + dsa: { + adrender: 1 + }, + meta: { + networkName: 'Criteo' + }, + videoPlayerType: mediaType === VIDEO ? 'RadiantMediaPlayer' : undefined, + videoPlayerConfig: mediaType === VIDEO ? {} : undefined, + cur: 'CUR' + } + } + ] + } + ] + }; + } + + it('should return an empty array when parsing an empty bid response', function () { + const bidRequests = []; const response = {}; - const request = { bidRequests: [] }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(0); }); - it('should properly parse a bid response with a networkId', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - networkId: 456, + it('should return an empty array when parsing a well-formed no bid response', function () { + const bidRequests = []; + const response = { seatbid: [] }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a banner bid response', function () { + const bidRequests = [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + banner: { + sizes: [[728, 90]] } - }] - }; - const bids = spec.interpretResponse(response, request); + }, + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId', BANNER); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(BANNER); expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].ad).to.equal('test-ad'); - expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].currency).to.equal('CUR'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); - expect(bids[0].dealId).to.equal('myDealCode'); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); }); - it('should properly parse a bid response with dsa', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - ext: { - dsa: { - adrender: 1 - }, - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.VIDEO) { + it('should properly parse a bid response with a video', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { - banner: { - sizes: [[728, 90]] + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] } }, params: { - networkId: 456, - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].meta.dsa.adrender).to.equal(1); - }); + zoneId: 123, + }, + }]; + const response = mockResponse('test-bidId', VIDEO); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].vastXml).to.equal('test-ad'); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(480); + expect(bids[0].renderer).to.equal(undefined); + }); + } - it('should properly parse a bid response with a networkId with twin ad unit banner win', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.VIDEO) { + it('should properly parse a bid response with an outstream video', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { video: { - context: 'instream', + context: 'outstream', mimes: ['video/mpeg'], playerSize: [640, 480], protocols: [5, 6], @@ -2234,68 +2151,123 @@ describe('The Criteo bidding adapter', function () { params: { networkId: 456, }, - }, { + }]; + const response = mockResponse('test-bidId', VIDEO); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].vastXml).to.equal('test-ad'); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(480); + expect(bids[0].renderer.url).to.equal('https://static.criteo.net/js/ld/publishertag.renderer.js'); + expect(typeof bids[0].renderer.config.documentResolver).to.equal('function'); + expect(typeof bids[0].renderer._render).to.equal('function'); + }); + } + + if (FEATURES.NATIVE) { + it('should properly parse a native bid response', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', - bidId: 'test-bidId2', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, + bidId: 'test-bidId', params: { - networkId: 456, + zoneId: '123', + }, + native: true, + }]; + const response = mockResponse('test-bidId', NATIVE); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(NATIVE); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ad).to.equal(undefined); + expect(bids[0].native.ortb).not.to.be.null; + expect(bids[0].native.ortb).to.equal(response.seatbid[0].bid[0].adm); // adm_native field was moved to adm + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); + expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); + }); + } + + it('should properly parse a bid response when banner win with twin ad units', function () { + const bidRequests = [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] } - }] - }; - const bids = spec.interpretResponse(response, request); + }, + params: { + networkId: 456, + }, + }, { + adUnitCode: 'test-requestId', + bidId: 'test-bidId2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId2', BANNER); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(BANNER); expect(bids[0].requestId).to.equal('test-bidId2'); + expect(bids[0].seatBidId).to.equal('test-bidderId') expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].ad).to.equal('test-ad'); - expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].currency).to.equal('CUR'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); - expect(bids[0].dealId).to.equal('myDealCode'); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); }); - it('should properly parse a bid response with a networkId with twin ad unit video win', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - displayurl: 'http://test-ad', - width: 728, - height: 90, - zoneid: 123, - video: true, - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.VIDEO) { + it('should properly parse a bid response when video win with twin ad units', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { video: { context: 'instream', mimes: ['video/mpeg'], - playerSize: [728, 90], + playerSize: [640, 480], protocols: [5, 6], maxduration: 30, api: [1, 2] } }, params: { - networkId: 456, + zoneId: '123' }, }, { adUnitCode: 'test-requestId', @@ -2308,63 +2280,27 @@ describe('The Criteo bidding adapter', function () { params: { networkId: 456, } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].vastUrl).to.equal('http://test-ad'); - expect(bids[0].mediaType).to.equal(VIDEO); - }); + }]; + const response = mockResponse('test-bidId', VIDEO); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].vastXml).to.equal('test-ad'); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(480); + expect(bids[0].renderer).to.equal(undefined); + }); + } - it('should properly parse a bid response with a networkId with twin ad unit native win', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - native: { - 'products': [{ - 'sendTargetingKeys': false, - 'title': 'Product title', - 'description': 'Product desc', - 'price': '100', - 'click_url': 'https://product.click', - 'image': { - 'url': 'https://publisherdirect.criteo.com/publishertag/preprodtest/creative.png', - 'height': 300, - 'width': 300 - }, - 'call_to_action': 'Try it now!' - }], - 'advertiser': { - 'description': 'sponsor', - 'domain': 'criteo.com', - 'logo': { 'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300 } - }, - 'privacy': { - 'optout_click_url': 'https://info.criteo.com/privacy/informations', - 'optout_image_url': 'https://static.criteo.net/flash/icon/nai_small.png', - }, - 'impression_pixels': [{ 'url': 'https://my-impression-pixel/test/impression' }, { 'url': 'https://cas.com/lg.com' }] - }, - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.NATIVE) { + it('should properly parse a bid response when native win with twin ad units', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { @@ -2377,299 +2313,145 @@ describe('The Criteo bidding adapter', function () { adUnitCode: 'test-requestId', bidId: 'test-bidId2', mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - networkId: 456, - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].mediaType).to.equal(NATIVE); - }); - - it('should properly parse a bid response with a zoneId', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - creative: 'test-ad', - width: 728, - height: 90, - zoneid: 123, - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, - }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].ad).to.equal('test-ad'); - expect(bids[0].width).to.equal(728); - expect(bids[0].height).to.equal(90); - }); - - it('should properly parse a bid response with a video', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - displayurl: 'http://test-ad', - width: 728, - height: 90, - zoneid: 123, - video: true - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, - }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].vastUrl).to.equal('http://test-ad'); - expect(bids[0].mediaType).to.equal(VIDEO); - }); - - it('should properly parse a bid response with a outstream video', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - displayurl: 'http://test-ad', - width: 728, - height: 90, - zoneid: 123, - video: true, - ext: { - videoPlayerType: 'RadiantMediaPlayer', - videoPlayerConfig: { - - } - } - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].vastUrl).to.equal('http://test-ad'); - expect(bids[0].renderer.url).to.equal('https://static.criteo.net/js/ld/publishertag.renderer.js'); - expect(typeof bids[0].renderer.config.documentResolver).to.equal('function'); - expect(typeof bids[0].renderer._render).to.equal('function'); - }); - - it('should properly parse a bid response with native', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - width: 728, - height: 90, - zoneid: 123, - native: { - 'products': [{ - 'sendTargetingKeys': false, - 'title': 'Product title', - 'description': 'Product desc', - 'price': '100', - 'click_url': 'https://product.click', - 'image': { - 'url': 'https://publisherdirect.criteo.com/publishertag/preprodtest/creative.png', - 'height': 300, - 'width': 300 - }, - 'call_to_action': 'Try it now!' - }], - 'advertiser': { - 'description': 'sponsor', - 'domain': 'criteo.com', - 'logo': { 'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300 } - }, - 'privacy': { - 'optout_click_url': 'https://info.criteo.com/privacy/informations', - 'optout_image_url': 'https://static.criteo.net/flash/icon/nai_small.png', - }, - 'impression_pixels': [{ 'url': 'https://my-impression-pixel/test/impression' }, { 'url': 'https://cas.com/lg.com' }] - } - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, - }, - native: true, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].mediaType).to.equal(NATIVE); - }); - - it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { - const bidderRequest = {}; - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[728, 90]], - params: { - zoneId: 123, - publisherSubId: '123', - nativeCallback: function () { } - }, - }, - { - bidder: 'criteo', - adUnitCode: 'bid-456', - transactionId: 'transaction-456', - sizes: [[728, 90]], - params: { - zoneId: 456, - publisherSubId: '456', - nativeCallback: function () { } + banner: { + sizes: [[728, 90]] + } }, - }, - ]; + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId', NATIVE); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(NATIVE); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ad).to.equal(undefined); + expect(bids[0].native.ortb).not.to.be.null; + expect(bids[0].native.ortb).to.equal(response.seatbid[0].bid[0].adm); // adm_native field was moved to adm + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); + expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); + }); + } - const nativeParamsWithSendTargetingKeys = [ - { - nativeParams: { - image: { - sendTargetingKeys: true + if (FEATURES.NATIVE) { + it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + mediaTypes: { + native: {} }, - } - }, - { - nativeParams: { - icon: { - sendTargetingKeys: true + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] }, - } - }, - { - nativeParams: { - clickUrl: { - sendTargetingKeys: true + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + publisherSubId: '123' }, - } - }, - { - nativeParams: { - displayUrl: { - sendTargetingKeys: true + }, + { + bidder: 'criteo', + adUnitCode: 'bid-456', + mediaTypes: { + native: {} }, - } - }, - { - nativeParams: { - privacyLink: { - sendTargetingKeys: true + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] }, - } - }, - { - nativeParams: { - privacyIcon: { - sendTargetingKeys: true + transactionId: 'transaction-456', + sizes: [[728, 90]], + params: { + zoneId: 456, + publisherSubId: '456' }, + }, + ]; + + const nativeParamsWithSendTargetingKeys = [ + { + nativeParams: { + image: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + icon: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + clickUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + displayUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyLink: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyIcon: { + sendTargetingKeys: true + }, + } } - } - ]; + ]; - utilsMock.expects('logWarn') - .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') - .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); - nativeParamsWithSendTargetingKeys.forEach(nativeParams => { - let transformedBidRequests = { ...bidRequests }; - transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; - spec.buildRequests(transformedBidRequests, bidderRequest); + utilsMock.expects('logWarn') + .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') + .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); + nativeParamsWithSendTargetingKeys.forEach(nativeParams => { + let transformedBidRequests = { ...bidRequests }; + transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; + spec.buildRequests(transformedBidRequests, syncAddFPDToBidderRequest(bidderRequest)); + }); + utilsMock.verify(); }); - utilsMock.verify(); - }); - - it('should properly parse a bid response with a zoneId passed as a string', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - width: 728, - height: 90, - zoneid: 123, - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: '123', - }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].ad).to.equal('test-ad'); - expect(bids[0].width).to.equal(728); - expect(bids[0].height).to.equal(90); - }); + } it('should properly parse a bid response with FLEDGE auction configs', function () { let auctionConfig1 = { @@ -2750,34 +2532,6 @@ describe('The Criteo bidding adapter', function () { }, sellerCurrency: '???' }; - const response = { - body: { - ext: { - igi: [{ - impid: 'test-bidId', - igs: [{ - impid: 'test-bidId', - bidId: 'test-bidId', - config: auctionConfig1 - }] - }, { - impid: 'test-bidId-2', - igs: [{ - impid: 'test-bidId-2', - bidId: 'test-bidId-2', - config: auctionConfig2 - }] - }] - }, - }, - }; - const bidderRequest = { - ortb2: { - source: { - tid: 'abc' - } - } - }; const bidRequests = [ { bidId: 'test-bidId', @@ -2810,18 +2564,37 @@ describe('The Criteo bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const interpretedResponse = spec.interpretResponse(response, request); + const response = { + ext: { + igi: [{ + impid: 'test-bidId', + igs: [{ + impid: 'test-bidId', + bidId: 'test-bidId', + config: auctionConfig1 + }] + }, { + impid: 'test-bidId-2', + igs: [{ + impid: 'test-bidId-2', + bidId: 'test-bidId-2', + config: auctionConfig2 + }] + }] + }, + }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const interpretedResponse = spec.interpretResponse({ body: response }, request); expect(interpretedResponse).to.have.property('bids'); - expect(interpretedResponse).to.have.property('fledgeAuctionConfigs'); + expect(interpretedResponse).to.have.property('paapi'); expect(interpretedResponse.bids).to.have.lengthOf(0); - expect(interpretedResponse.fledgeAuctionConfigs).to.have.lengthOf(2); - expect(interpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal({ + expect(interpretedResponse.paapi).to.have.lengthOf(2); + expect(interpretedResponse.paapi[0]).to.deep.equal({ bidId: 'test-bidId', impid: 'test-bidId', config: auctionConfig1, }); - expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ + expect(interpretedResponse.paapi[1]).to.deep.equal({ bidId: 'test-bidId-2', impid: 'test-bidId-2', config: auctionConfig2, @@ -2847,148 +2620,65 @@ describe('The Criteo bidding adapter', function () { hasBidResponseLevelPafData: false, hasBidResponseBidLevelPafData: false, shouldContainsBidMetaPafData: false - }].forEach(testCase => { - const bidPafContentId = 'abcdef'; - const pafTransmission = { - version: '12' - }; - const response = { - slots: [ - { - width: 300, - height: 250, - cpm: 10, - impid: 'adUnitId', - ext: (testCase.hasBidResponseBidLevelPafData ? { - paf: { - content_id: bidPafContentId - } - } : undefined) - } - ], - ext: (testCase.hasBidResponseLevelPafData ? { - paf: { - transmission: pafTransmission - } - } : undefined) - }; - - const request = { - bidRequests: [{ + }].forEach(testCase => + it('should properly forward or not meta paf data', () => { + const bidPafContentId = 'abcdef'; + const pafTransmission = { + version: '12' + }; + const bidRequests = [{ + bidId: 'test-bidId', adUnitCode: 'adUnitId', sizes: [[300, 250]], params: { networkId: 456, } - }] - }; - - const bids = spec.interpretResponse(response, request); - - expect(bids).to.have.lengthOf(1); + }]; + const response = { + id: 'test-requestId', + seatbid: [{ + seat: 'criteo', + bid: [ + { + id: 'test-bidderId', + impid: 'test-bidId', + w: 728, + h: 90, + ext: { + mediatype: BANNER, + paf: testCase.hasBidResponseBidLevelPafData ? { + content_id: bidPafContentId + } : undefined + } + } + ] + }], + ext: (testCase.hasBidResponseLevelPafData ? { + paf: { + transmission: pafTransmission + } + } : undefined) + }; - const theoreticalBidMetaPafData = { - paf: { - content_id: bidPafContentId, - transmission: pafTransmission - } - }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); - if (testCase.shouldContainsBidMetaPafData) { - expect(bids[0].meta).to.deep.equal(theoreticalBidMetaPafData); - } else { - expect(bids[0].meta).not.to.deep.equal(theoreticalBidMetaPafData); - } - }); - }); + expect(bids).to.have.lengthOf(1); - describe('canFastBid', function () { - it('should properly detect if can do fastbid', function () { - const testCasesAndExpectedResult = [['none', false], ['', true], [undefined, true], [123, true]]; - testCasesAndExpectedResult.forEach(testCase => { - const result = canFastBid(testCase[0]); - expect(result).to.equal(testCase[1]); - }) - }); - }); + const expectedBidMetaPafData = { + paf: { + content_id: bidPafContentId, + transmission: pafTransmission + } + }; - describe('getFastBidUrl', function () { - it('should properly detect the version of fastbid', function () { - const testCasesAndExpectedResult = [ - ['', 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], - [undefined, 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], - [null, 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], - [NaN, 'https://static.criteo.net/js/ld/publishertag.prebid.' + FAST_BID_VERSION_CURRENT + '.js'], - [123, 'https://static.criteo.net/js/ld/publishertag.prebid.123.js'], - ['123', 'https://static.criteo.net/js/ld/publishertag.prebid.123.js'], - ['latest', 'https://static.criteo.net/js/ld/publishertag.prebid.js'] - ]; - testCasesAndExpectedResult.forEach(testCase => { - const result = getFastBidUrl(testCase[0]); - expect(result).to.equal(testCase[1]); + if (testCase.shouldContainsBidMetaPafData) { + expect(bids[0].meta).to.deep.equal(expectedBidMetaPafData); + } else { + expect(bids[0].meta).not.to.deep.equal(expectedBidMetaPafData); + } }) - }); - }); - - describe('tryGetCriteoFastBid', function () { - const VALID_HASH = 'vBeD8Q7GU6lypFbzB07W8hLGj7NL+p7dI9ro2tCxkrmyv0F6stNuoNd75Us33iNKfEoW+cFWypelr6OJPXxki2MXWatRhJuUJZMcK4VBFnxi3Ro+3a0xEfxE4jJm4eGe98iC898M+/YFHfp+fEPEnS6pEyw124ONIFZFrcejpHU='; - const INVALID_HASH = 'invalid'; - const VALID_PUBLISHER_TAG = 'test'; - const INVALID_PUBLISHER_TAG = 'test invalid'; - - const FASTBID_LOCAL_STORAGE_KEY = 'criteo_fast_bid'; - - it('should verify valid hash with valid publisher tag', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + VALID_HASH + '\n' + VALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').once(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.equals('// Hash: ' + VALID_HASH + '\n' + VALID_PUBLISHER_TAG); - utilsMock.verify(); - }); - - it('should verify valid hash with invalid publisher tag', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + VALID_HASH + '\n' + INVALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').once(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify invalid hash with valid publisher tag', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + INVALID_HASH + '\n' + VALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').once(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify missing hash', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, VALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').once(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); + ) }); describe('when pubtag prebid adapter is not available', function () { @@ -2998,12 +2688,24 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', sizes: [[728, 90]], + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, params: { zoneId: 123, - publisherSubId: '123', - nativeCallback: function () { } + publisherSubId: '123' }, nativeParams: { image: { @@ -3029,7 +2731,7 @@ describe('The Criteo bidding adapter', function () { ]; utilsMock.expects('logWarn').withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)').never(); - const request = spec.buildRequests(bidRequestsWithSendId, bidderRequest); + const request = spec.buildRequests(bidRequestsWithSendId, syncAddFPDToBidderRequest(bidderRequest)); utilsMock.verify(); }); @@ -3039,23 +2741,46 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, params: { zoneId: 123, - publisherSubId: '123', - nativeCallback: function () { } + publisherSubId: '123' }, }, { bidder: 'criteo', adUnitCode: 'bid-456', transactionId: 'transaction-456', - sizes: [[728, 90]], + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, params: { zoneId: 456, - publisherSubId: '456', - nativeCallback: function () { } + publisherSubId: '456' }, }, ]; @@ -3111,132 +2836,9 @@ describe('The Criteo bidding adapter', function () { nativeParamsWithoutSendId.forEach(nativeParams => { let transformedBidRequests = { ...bidRequests }; transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; - spec.buildRequests(transformedBidRequests, bidderRequest); + spec.buildRequests(transformedBidRequests, syncAddFPDToBidderRequest(bidderRequest)); }); utilsMock.verify(); }); }); - - describe('when pubtag prebid adapter is available', function () { - it('should forward response to pubtag when calling interpretResponse', () => { - const response = {}; - const request = {}; - - const adapter = { interpretResponse: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('interpretResponse').withExactArgs(response, request).once().returns('ok'); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(request).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - expect(spec.interpretResponse(response, request)).equal('ok'); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should forward bid to pubtag when calling onBidWon', () => { - const bid = { auctionId: 123 }; - - const adapter = { handleBidWon: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('handleBidWon').withExactArgs(bid).once(); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - spec.onBidWon(bid); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should forward bid to pubtag when calling onSetTargeting', () => { - const bid = { auctionId: 123 }; - - const adapter = { handleSetTargeting: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('handleSetTargeting').withExactArgs(bid).once(); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - spec.onSetTargeting(bid); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should forward bid to pubtag when calling onTimeout', () => { - const timeoutData = [{ auctionId: 123 }]; - - const adapter = { handleBidTimeout: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('handleBidTimeout').once(); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(timeoutData[0].auctionId).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - spec.onTimeout(timeoutData); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should return a POST method with url & data from pubtag', () => { - const bidRequests = {}; - const bidderRequest = {}; - - const prebidAdapter = { buildCdbUrl: function () { }, buildCdbRequest: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('buildCdbUrl').once().returns('cdbUrl'); - prebidAdapterMock.expects('buildCdbRequest').once().returns('cdbRequest'); - - const adapters = { Prebid: function () { } }; - const adaptersMock = sinon.mock(adapters); - adaptersMock.expects('Prebid').withExactArgs(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$', sinon.match.any).once().returns(prebidAdapter); - - global.Criteo = { - PubTag: { - Adapters: adapters - } - }; - - const buildRequestsResult = spec.buildRequests(bidRequests, bidderRequest); - expect(buildRequestsResult.method).equal('POST'); - expect(buildRequestsResult.url).equal('cdbUrl'); - expect(buildRequestsResult.data).equal('cdbRequest'); - - adaptersMock.verify(); - prebidAdapterMock.verify(); - }); - }); }); diff --git a/test/spec/modules/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js index f347d6cec5b..ab75264d951 100644 --- a/test/spec/modules/dailyhuntBidAdapter_spec.js +++ b/test/spec/modules/dailyhuntBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('DailyhuntAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); describe('buildRequests', function() { diff --git a/test/spec/modules/datawrkzBidAdapter_spec.js b/test/spec/modules/datawrkzBidAdapter_spec.js index 5524e318600..e78d2f68d91 100644 --- a/test/spec/modules/datawrkzBidAdapter_spec.js +++ b/test/spec/modules/datawrkzBidAdapter_spec.js @@ -36,26 +36,26 @@ describe('datawrkzAdapterTests', function () { }); it('should return false when params not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required site_id param not found', function () { - let bid = Object.assign({}, bid); - bid.params = {'bidfloor': '1.0'} - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {'bidfloor': '1.0'} + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when adunit is adpod video', function () { - let bid = Object.assign({}, bid); - bid.params = {'bidfloor': '1.0', 'site_id': SITE_ID}; - bid.mediaTypes = { + let invalidBid = Object.assign({}, bid); + invalidBid.params = {'bidfloor': '1.0', 'site_id': SITE_ID}; + invalidBid.mediaTypes = { 'video': { 'context': 'adpod' } } - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 39713c2b51a..092cd1ff0f3 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,16 +1,15 @@ import {expect} from 'chai'; import parse from 'url-parse'; -import {buildAdpodVideoUrl, buildDfpVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; +import {buildDfpVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; import AD_UNIT from 'test/fixtures/video/adUnit.json'; import * as utils from 'src/utils.js'; import {deepClone} from 'src/utils.js'; import {config} from 'src/config.js'; import {targeting} from 'src/targeting.js'; import {auctionManager} from 'src/auctionManager.js'; -import {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; -import * as adpod from 'modules/adpod.js'; -import {server} from 'test/mocks/xhr.js'; +import {gdprDataHandler} from 'src/adapterManager.js'; + import * as adServer from 'src/adserver.js'; import {hook} from '../../../src/hook.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; @@ -707,252 +706,4 @@ describe('The DFP video support module', function () { expect(customParams).to.have.property('other_key', 'other_value'); expect(customParams).to.have.property('hb_rand', 'random'); }); - - describe('adpod unit tests', function () { - let amStub; - let amGetAdUnitsStub; - - before(function () { - let adUnits = [{ - code: 'adUnitCode-1', - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 60, - durationRangeSec: [15, 30], - requireExactDuration: true - } - }, - bids: [ - { - bidder: 'appnexus', - params: { - placementId: 14542875, - } - } - ] - }]; - - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); - amGetAdUnitsStub.returns(adUnits); - amStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - beforeEach(function () { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: false - } - }); - }) - - afterEach(function() { - config.resetConfig(); - }); - - after(function () { - amGetAdUnitsStub.restore(); - amStub.restore(); - }); - - it('should return masterTag url', function() { - amStub.returns(getBidsReceived()); - let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); - uspDataHandlerStub.returns('1YYY'); - let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - gdprDataHandlerStub.returns({ - gdprApplies: true, - consentString: 'consent', - addtlConsent: 'moreConsent' - }); - let url; - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - function handleResponse(err, masterTag) { - if (err) { - return; - } - url = parse(masterTag); - - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('securepubads.g.doubleclick.net'); - - const queryParams = utils.parseQS(url.query); - expect(queryParams).to.have.property('correlator'); - expect(queryParams).to.have.property('description_url', 'someUrl.com'); - expect(queryParams).to.have.property('env', 'vp'); - expect(queryParams).to.have.property('gdfp_req', '1'); - expect(queryParams).to.have.property('iu', 'my/adUnit'); - expect(queryParams).to.have.property('output', 'vast'); - expect(queryParams).to.have.property('sz', '640x480'); - expect(queryParams).to.have.property('unviewed_position_start', '1'); - expect(queryParams).to.have.property('url'); - expect(queryParams).to.have.property('cust_params'); - expect(queryParams).to.have.property('gdpr', '1'); - expect(queryParams).to.have.property('gdpr_consent', 'consent'); - expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); - - const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); - expect(custParams).to.have.property('hb_cache_id', '123'); - expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); - uspDataHandlerStub.restore(); - gdprDataHandlerStub.restore(); - } - }); - - it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: false, - } - }); - function getBids() { - let bids = [ - createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), - createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), - createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), - ]; - bids.forEach((bid) => { - delete bid.meta; - }); - return bids; - } - amStub.returns(getBids()); - let url; - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - function handleResponse(err, masterTag) { - if (err) { - return; - } - url = parse(masterTag); - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('securepubads.g.doubleclick.net'); - - const queryParams = utils.parseQS(url.query); - expect(queryParams).to.have.property('correlator'); - expect(queryParams).to.have.property('description_url', 'someUrl.com'); - expect(queryParams).to.have.property('env', 'vp'); - expect(queryParams).to.have.property('gdfp_req', '1'); - expect(queryParams).to.have.property('iu', 'my/adUnit'); - expect(queryParams).to.have.property('output', 'xml_vast3'); - expect(queryParams).to.have.property('sz', '640x480'); - expect(queryParams).to.have.property('unviewed_position_start', '1'); - expect(queryParams).to.have.property('url'); - expect(queryParams).to.have.property('cust_params'); - - const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); - expect(custParams).to.have.property('hb_cache_id', '123'); - expect(custParams).to.have.property('hb_pb_cat_dur', '10.00_15s,15.00_15s,15.00_30s'); - } - }); - - it('should handle error when cache fails', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: true - } - }); - amStub.returns(getBidsReceived()); - - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - server.requests[0].respond(503, { - 'Content-Type': 'plain/text', - }, 'The server could not save anything at the moment.'); - - function handleResponse(err, masterTag) { - expect(masterTag).to.be.null; - expect(err).to.be.an('error'); - } - }); - }) }); - -function getBidsReceived() { - return [ - createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), - createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), - createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), - ] -} - -function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { - return { - 'bidderCode': 'appnexus', - 'width': 640, - 'height': 360, - 'statusMessage': 'Bid available', - 'adId': '28f24ced14586c', - 'mediaType': 'video', - 'source': 'client', - 'requestId': '28f24ced14586c', - 'cpm': cpm, - 'creativeId': 97517771, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 3600, - 'adUnitCode': adUnitCode, - 'video': { - 'context': 'adpod', - 'durationBucket': durationBucket - }, - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'vastUrl': 'http://some-vast-url.com', - 'vastImpUrl': 'http://some-vast-imp-url.com', - 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', - 'responseTimestamp': 1548442460888, - 'requestTimestamp': 1548442460827, - 'bidder': 'appnexus', - 'timeToRespond': 61, - 'pbLg': '5.00', - 'pbMg': '5.00', - 'pbHg': '5.00', - 'pbAg': '5.00', - 'pbDg': '5.00', - 'pbCg': '', - 'size': '640x360', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '28f24ced14586c', - 'hb_pb': hbpb, - 'hb_size': '640x360', - 'hb_source': 'client', - 'hb_format': 'video', - 'hb_pb_cat_dur': priceIndustryDuration, - 'hb_cache_id': uuid - }, - 'customCacheKey': `${priceIndustryDuration}_${uuid}`, - 'meta': { - 'primaryCatId': 'iab-1', - 'adServerCatId': label - }, - 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' - } -} diff --git a/test/spec/modules/dfpAdpod_spec.js b/test/spec/modules/dfpAdpod_spec.js new file mode 100644 index 00000000000..33d724dac26 --- /dev/null +++ b/test/spec/modules/dfpAdpod_spec.js @@ -0,0 +1,257 @@ +import {auctionManager} from '../../../src/auctionManager.js'; +import {config} from '../../../src/config.js'; +import {gdprDataHandler, uspDataHandler} from '../../../src/consentHandler.js'; +import parse from 'url-parse'; +import {buildAdpodVideoUrl} from '../../../modules/dfpAdpod.js'; +import {expect} from 'chai/index.js'; +import * as utils from '../../../src/utils.js'; +import {server} from '../../mocks/xhr.js'; +import * as adpod from 'modules/adpod.js'; + +describe('dfpAdpod', function () { + let amStub; + let amGetAdUnitsStub; + + before(function () { + let adUnits = [{ + code: 'adUnitCode-1', + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 60, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 14542875, + } + } + ] + }]; + + amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); + amGetAdUnitsStub.returns(adUnits); + amStub = sinon.stub(auctionManager, 'getBidsReceived'); + }); + + beforeEach(function () { + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: false + } + }); + }) + + afterEach(function() { + config.resetConfig(); + }); + + after(function () { + amGetAdUnitsStub.restore(); + amStub.restore(); + }); + + function getBidsReceived() { + return [ + createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), + createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), + createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), + ] + } + + function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { + return { + 'bidderCode': 'appnexus', + 'width': 640, + 'height': 360, + 'statusMessage': 'Bid available', + 'adId': '28f24ced14586c', + 'mediaType': 'video', + 'source': 'client', + 'requestId': '28f24ced14586c', + 'cpm': cpm, + 'creativeId': 97517771, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 3600, + 'adUnitCode': adUnitCode, + 'video': { + 'context': 'adpod', + 'durationBucket': durationBucket + }, + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', + 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', + 'responseTimestamp': 1548442460888, + 'requestTimestamp': 1548442460827, + 'bidder': 'appnexus', + 'timeToRespond': 61, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '640x360', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '28f24ced14586c', + 'hb_pb': hbpb, + 'hb_size': '640x360', + 'hb_source': 'client', + 'hb_format': 'video', + 'hb_pb_cat_dur': priceIndustryDuration, + 'hb_cache_id': uuid + }, + 'customCacheKey': `${priceIndustryDuration}_${uuid}`, + 'meta': { + 'primaryCatId': 'iab-1', + 'adServerCatId': label + }, + 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' + } + } + + it('should return masterTag url', function() { + amStub.returns(getBidsReceived()); + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); + let url; + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + function handleResponse(err, masterTag) { + if (err) { + return; + } + url = parse(masterTag); + + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); + + const queryParams = utils.parseQS(url.query); + expect(queryParams).to.have.property('correlator'); + expect(queryParams).to.have.property('description_url', 'someUrl.com'); + expect(queryParams).to.have.property('env', 'vp'); + expect(queryParams).to.have.property('gdfp_req', '1'); + expect(queryParams).to.have.property('iu', 'my/adUnit'); + expect(queryParams).to.have.property('output', 'vast'); + expect(queryParams).to.have.property('sz', '640x480'); + expect(queryParams).to.have.property('unviewed_position_start', '1'); + expect(queryParams).to.have.property('url'); + expect(queryParams).to.have.property('cust_params'); + expect(queryParams).to.have.property('gdpr', '1'); + expect(queryParams).to.have.property('gdpr_consent', 'consent'); + expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); + + const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); + expect(custParams).to.have.property('hb_cache_id', '123'); + expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); + uspDataHandlerStub.restore(); + gdprDataHandlerStub.restore(); + } + }); + + it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: false, + } + }); + function getBids() { + let bids = [ + createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), + createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), + createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), + ]; + bids.forEach((bid) => { + delete bid.meta; + }); + return bids; + } + amStub.returns(getBids()); + let url; + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + function handleResponse(err, masterTag) { + if (err) { + return; + } + url = parse(masterTag); + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); + + const queryParams = utils.parseQS(url.query); + expect(queryParams).to.have.property('correlator'); + expect(queryParams).to.have.property('description_url', 'someUrl.com'); + expect(queryParams).to.have.property('env', 'vp'); + expect(queryParams).to.have.property('gdfp_req', '1'); + expect(queryParams).to.have.property('iu', 'my/adUnit'); + expect(queryParams).to.have.property('output', 'xml_vast3'); + expect(queryParams).to.have.property('sz', '640x480'); + expect(queryParams).to.have.property('unviewed_position_start', '1'); + expect(queryParams).to.have.property('url'); + expect(queryParams).to.have.property('cust_params'); + + const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); + expect(custParams).to.have.property('hb_cache_id', '123'); + expect(custParams).to.have.property('hb_pb_cat_dur', '10.00_15s,15.00_15s,15.00_30s'); + } + }); + + it('should handle error when cache fails', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: true + } + }); + amStub.returns(getBidsReceived()); + + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + server.requests[0].respond(503, { + 'Content-Type': 'plain/text', + }, 'The server could not save anything at the moment.'); + + function handleResponse(err, masterTag) { + expect(masterTag).to.be.null; + expect(err).to.be.an('error'); + } + }); +}) diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 841fc087613..2d9e05cca25 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -33,12 +33,12 @@ describe('dspxAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js deleted file mode 100644 index 1c46381500f..00000000000 --- a/test/spec/modules/ebdrBidAdapter_spec.js +++ /dev/null @@ -1,245 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/ebdrBidAdapter.js'; -import { VIDEO, BANNER } from 'src/mediaTypes.js'; -import * as utils from 'src/utils.js'; - -describe('ebdrBidAdapter', function () { - let bidRequests; - - beforeEach(function () { - bidRequests = [ - { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - bidder: 'ebdr', - params: { - zoneid: '99999', - bidfloor: '1.00', - IDFA: 'xxx-xxx', - ADID: 'xxx-xxx', - latitude: '34.089811', - longitude: '-118.392805' - }, - bidId: '2c5e8a1a84522d', - bidderRequestId: '1d0c4017f02458', - auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' - }, { - adUnitCode: 'div-gpt-ad-1460505748561-1', - mediaTypes: { - video: { - context: 'instream', - playerSize: [300, 250] - } - }, - bidder: 'ebdr', - params: { - zoneid: '99998', - bidfloor: '1.00', - IDFA: 'xxx-xxx', - ADID: 'xxx-xxx', - latitude: '34.089811', - longitude: '-118.392805' - }, - bidId: '23a01e95856577', - bidderRequestId: '1d0c4017f02458', - auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' - } - ]; - }); - - describe('spec.isBidRequestValid', function () { - it('should return true when the required params are passed', function () { - const bidRequest = bidRequests[0]; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return true when the only required param is missing', function () { - const bidRequest = bidRequests[0]; - bidRequest.params = { - zoneid: '99998', - bidfloor: '1.00', - }; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return true when the "bidfloor" param is missing', function () { - const bidRequest = bidRequests[0]; - bidRequest.params = { - zoneid: '99998', - }; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return false when no bid params are passed', function () { - const bidRequest = bidRequests[0]; - bidRequest.params = {}; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - - it('should return false when a bid request is not passed', function () { - expect(spec.isBidRequestValid()).to.equal(false); - expect(spec.isBidRequestValid({})).to.equal(false); - }); - }); - - describe('spec.buildRequests', function () { - describe('for banner bids', function () { - it('must handle an empty bid size', function () { - bidRequests[0].mediaTypes = { banner: {} }; - const requests = spec.buildRequests(bidRequests); - const bidRequest = {}; - bidRequest['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: null, h: null }; - expect(requests.bids['2c5e8a1a84522d']).to.deep.equals(bidRequest['2c5e8a1a84522d']); - }); - it('should create a single GET', function () { - bidRequests[0].mediaTypes = { banner: {} }; - bidRequests[1].mediaTypes = { banner: {} }; - const requests = spec.buildRequests(bidRequests); - expect(requests.method).to.equal('GET'); - }); - it('must parse bid size from a nested array', function () { - const width = 640; - const height = 480; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {sizes: [[ width, height ]]} }; - const requests = spec.buildRequests([ bidRequest ]); - const data = {}; - data['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: width, h: height }; - expect(requests.bids['2c5e8a1a84522d']).to.deep.equal(data['2c5e8a1a84522d']); - }); - }); - describe('for video bids', function () { - it('must handle an empty bid size', function () { - bidRequests[1].mediaTypes = { video: {} }; - const requests = spec.buildRequests(bidRequests); - const bidRequest = {}; - bidRequest['23a01e95856577'] = { mediaTypes: VIDEO, w: null, h: null }; - expect(requests.bids['23a01e95856577']).to.deep.equals(bidRequest['23a01e95856577']); - }); - - it('should create a GET request for each bid', function () { - const bidRequest = bidRequests[1]; - const requests = spec.buildRequests([ bidRequest ]); - expect(requests.method).to.equal('GET'); - }); - }); - }); - - describe('spec.interpretResponse', function () { - describe('for video bids', function () { - it('should return no bids if the response is not valid', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { video: {} }; - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return a valid video bid response', function () { - const ebdrReq = {bids: {}}; - bidRequests.forEach(bid => { - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - ebdrReq.bids[bid.bidId] = {mediaTypes: _mediaTypes, - w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], - h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] - }; - }); - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '23a01e95856577', impid: '23a01e95856577', price: 0.81, adid: 'abcde-12345', nurl: 'https://cdn0.bnmla.com/vtest.xml', adm: '\nStatic VASTStatic VAST Tag00:00:15https//www.engagebdr.com/c', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD'}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); - expect(bidResponse[0]).to.deep.equal({ - requestId: bidRequests[1].bidId, - vastXml: serverResponse.seatbid[0].bid[0].adm, - mediaType: 'video', - creativeId: serverResponse.seatbid[0].bid[0].crid, - cpm: serverResponse.seatbid[0].bid[0].price, - width: serverResponse.seatbid[0].bid[0].w, - height: serverResponse.seatbid[0].bid[0].h, - currency: 'USD', - netRevenue: true, - ttl: 3600, - vastUrl: serverResponse.seatbid[0].bid[0].nurl, - meta: { - advertiserDomains: [ - 'advertiserdomain.com' - ] - } - }); - }); - }); - - describe('for banner bids', function () { - it('should return no bids if the response is not valid', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {} }; - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return no bids if the response is empty', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {} }; - const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return valid banner bid responses', function () { - const ebdrReq = {bids: {}}; - bidRequests.forEach(bid => { - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - ebdrReq.bids[bid.bidId] = {mediaTypes: _mediaTypes, - w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], - h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] - }; - }); - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '
', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); - expect(bidResponse[0]).to.deep.equal({ - requestId: bidRequests[ 0 ].bidId, - ad: serverResponse.seatbid[0].bid[0].adm, - mediaType: 'banner', - creativeId: serverResponse.seatbid[0].bid[0].crid, - cpm: serverResponse.seatbid[0].bid[0].price, - width: serverResponse.seatbid[0].bid[0].w, - height: serverResponse.seatbid[0].bid[0].h, - currency: 'USD', - netRevenue: true, - ttl: 3600, - meta: { - advertiserDomains: [ - 'advertiserdomain.com' - ] - }, - }); - }); - }); - }); - describe('spec.getUserSyncs', function () { - let syncOptions - beforeEach(function () { - syncOptions = { - enabledBidders: ['ebdr'], // only these bidders are allowed to sync - pixelEnabled: true - } - }); - it('sucess with usersync url', function () { - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '
', adomain: ['advertiserdomain.com'], iurl: 'https://match.bnmla.com/usersync?sspid=59&redir=', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; - const result = []; - result.push({type: 'image', url: 'https://match.bnmla.com/usersync?sspid=59&redir='}); - expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); - }); - - it('sucess without usersync url', function () { - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '
', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; - const result = []; - expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); - }); - it('empty response', function () { - const serverResponse = {}; - const result = []; - expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); - }); - }); -}); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js deleted file mode 100644 index dddc248b409..00000000000 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ /dev/null @@ -1,164 +0,0 @@ -import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter.js'; -import {includes} from 'src/polyfill.js'; -import { expect } from 'chai'; -import { parseUrl } from 'src/utils.js'; -import { server } from 'test/mocks/xhr.js'; -import { EVENTS } from 'src/constants.js'; - -let adapterManager = require('src/adapterManager').default; -let events = require('src/events'); - -describe('eplanning analytics adapter', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - events.getEvents.restore(); - eplAnalyticsAdapter.disableAnalytics(); - }); - - describe('track', function () { - it('builds and sends auction data', function () { - sinon.spy(eplAnalyticsAdapter, 'track'); - - let auctionTimestamp = 1496510254313; - let pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; - let initOptions = { - host: 'https://ads.ar.e-planning.net/hba/1/', - ci: '12345' - }; - let pbidderCode = 'adapter'; - - const bidRequest = { - bidderCode: pbidderCode, - auctionId: pauctionId, - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: pbidderCode, - placementCode: 'container-1', - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: pauctionId, - startTime: 1509369418389, - sizes: [[300, 250]], - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }; - - const bidResponse = { - bidderCode: pbidderCode, - adId: '208750227436c1', - cpm: 0.015, - auctionId: pauctionId, - responseTimestamp: 1509369418832, - requestTimestamp: 1509369418389, - bidder: pbidderCode, - timeToRespond: 443, - size: '300x250', - width: 300, - height: 250, - }; - - let bidTimeout = [ - { - bidId: '208750227436c1', - bidder: pbidderCode, - auctionId: pauctionId - } - ]; - - adapterManager.registerAnalyticsAdapter({ - code: 'eplanning', - adapter: eplAnalyticsAdapter - }); - - adapterManager.enableAnalytics({ - provider: 'eplanning', - options: initOptions - }); - - // Emit the events with the "real" arguments - - // Step 1: Send auction init event - events.emit(EVENTS.AUCTION_INIT, { - auctionId: pauctionId, - timestamp: auctionTimestamp - }); - - // Step 2: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, bidRequest); - - // Step 3: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bidResponse); - - // Step 4: Send bid time out event - events.emit(EVENTS.BID_TIMEOUT, bidTimeout); - - // Step 5: Send auction bid won event - events.emit(EVENTS.BID_WON, { - adId: 'adIdData', - ad: 'adContent', - auctionId: pauctionId, - width: 300, - height: 250 - }); - - // Step 6: Send auction end event - events.emit(EVENTS.AUCTION_END, { auctionId: pauctionId }); - - // Step 7: Find the request data sent (filtering other hosts) - let requests = server.requests.filter(req => { - return req.url.indexOf(initOptions.host) > -1; - }); - expect(requests.length).to.equal(1); - - expect(includes([initOptions.host + initOptions.ci], requests[0].url)); - expect(includes(['https://ads.ar.e-planning.net/hba/1/12345?d='], requests[0].url)); - - let info = requests[0].url; - let purl = parseUrl(info); - let eplData = JSON.parse(decodeURIComponent(purl.search.d)); - - // Step 8 check that 6 events were sent - expect(eplData.length).to.equal(6); - - // Step 9 verify that we only receive the parameters we need - let expectedEventValues = [ - // AUCTION INIT - { - ec: EVENTS.AUCTION_INIT, - p: {auctionId: pauctionId, time: auctionTimestamp}}, - // BID REQ - { - ec: EVENTS.BID_REQUESTED, - p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, - // BID RESP - { - ec: EVENTS.BID_RESPONSE, - p: {auctionId: pauctionId, bidder: pbidderCode, cpm: 0.015, size: '300x250', time: 1509369418832}}, - // BID T.O. - { - ec: EVENTS.BID_TIMEOUT, - p: [{auctionId: pauctionId, bidder: pbidderCode}]}, - // BID WON - { - ec: EVENTS.BID_WON, - p: {auctionId: pauctionId, size: '300x250'}}, - // AUCTION END - { - ec: EVENTS.AUCTION_END, - p: {auctionId: pauctionId}} - ]; - - for (let evid = 0; evid < eplData.length; evid++) { - expect(eplData[evid]).to.deep.equal(expectedEventValues[evid]); - } - - // Step 10 check that the host to send the ajax request is configurable via options - expect(eplAnalyticsAdapter.context.host).to.equal(initOptions.host); - }); - }); -}); diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index f01f2a15f03..aff1e0535ae 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -1,12 +1,12 @@ import {attachIdSystem, coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import {euidIdSubmodule} from 'modules/euidIdSystem.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'src/prebid.js'; import * as utils from 'src/utils.js'; import {apiHelpers, cookieHelpers, runAuction, setGdprApplies} from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; -import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; import {server} from 'test/mocks/xhr'; import {createEidsArray} from '../../../modules/userId/eids.js'; @@ -50,7 +50,7 @@ describe('EUID module', function() { const configureEuidCstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); before(function() { - uninstallGdprEnforcement(); + uninstallTcfControl(); hook.ready(); suiteSandbox = sinon.sandbox.create(); if (typeof window.crypto.subtle === 'undefined') { diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js deleted file mode 100644 index aa513f931db..00000000000 --- a/test/spec/modules/fledgeForGpt_spec.js +++ /dev/null @@ -1,206 +0,0 @@ -import { - getPAAPISizeHook, - onAuctionConfigFactory, - setPAAPIConfigFactory, - slotConfigurator -} from 'modules/fledgeForGpt.js'; -import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; -import 'modules/appnexusBidAdapter.js'; -import 'modules/rubiconBidAdapter.js'; -import {deepSetValue} from '../../../src/utils.js'; -import {config} from 'src/config.js'; - -describe('fledgeForGpt module', () => { - let sandbox, fledgeAuctionConfig; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - fledgeAuctionConfig = { - seller: 'bidder', - mock: 'config' - }; - }); - afterEach(() => { - sandbox.restore(); - }); - - describe('slotConfigurator', () => { - let mockGptSlot, setGptConfig; - beforeEach(() => { - mockGptSlot = { - setConfig: sinon.stub(), - getAdUnitPath: () => 'mock/gpt/au' - }; - sandbox.stub(gptUtils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); - setGptConfig = slotConfigurator(); - }); - it('should set GPT slot config', () => { - setGptConfig('au', [fledgeAuctionConfig]); - sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au'); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 'bidder', - auctionConfig: fledgeAuctionConfig, - }] - }); - }); - - describe('when reset = true', () => { - it('should reset GPT slot config', () => { - setGptConfig('au', [fledgeAuctionConfig]); - mockGptSlot.setConfig.resetHistory(); - gptUtils.getGptSlotForAdUnitCode.resetHistory(); - setGptConfig('au', [], true); - sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au'); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 'bidder', - auctionConfig: null - }] - }); - }); - - it('should reset only sellers with no fresh config', () => { - setGptConfig('au', [{seller: 's1'}, {seller: 's2'}]); - mockGptSlot.setConfig.resetHistory(); - setGptConfig('au', [{seller: 's1'}], true); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 's1', - auctionConfig: {seller: 's1'} - }, { - configKey: 's2', - auctionConfig: null - }] - }) - }); - - it('should not reset sellers that were already reset', () => { - setGptConfig('au', [{seller: 's1'}]); - setGptConfig('au', [], true); - mockGptSlot.setConfig.resetHistory(); - setGptConfig('au', [], true); - sinon.assert.notCalled(mockGptSlot.setConfig); - }) - - it('should keep track of configuration history by slot', () => { - setGptConfig('au1', [{seller: 's1'}]); - setGptConfig('au1', [{seller: 's2'}], false); - setGptConfig('au2', [{seller: 's3'}]); - mockGptSlot.setConfig.resetHistory(); - setGptConfig('au1', [], true); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 's1', - auctionConfig: null - }, { - configKey: 's2', - auctionConfig: null - }] - }); - }) - }); - }); - describe('onAuctionConfig', () => { - [ - 'fledgeForGpt', - 'paapi.gpt' - ].forEach(namespace => { - describe(`using ${namespace} config`, () => { - Object.entries({ - 'omitted': [undefined, true], - 'enabled': [true, true], - 'disabled': [false, false] - }).forEach(([t, [autoconfig, shouldSetConfig]]) => { - describe(`when autoconfig is ${t}`, () => { - beforeEach(() => { - const cfg = {}; - deepSetValue(cfg, `${namespace}.autoconfig`, autoconfig); - config.setConfig(cfg); - }); - afterEach(() => { - config.resetConfig(); - }); - - it(`should ${shouldSetConfig ? '' : 'NOT'} set GPT slot configuration`, () => { - const auctionConfig = {componentAuctions: [{seller: 'mock1'}, {seller: 'mock2'}]}; - const setGptConfig = sinon.stub(); - const markAsUsed = sinon.stub(); - onAuctionConfigFactory(setGptConfig)('aid', {au1: auctionConfig, au2: null}, markAsUsed); - if (shouldSetConfig) { - sinon.assert.calledWith(setGptConfig, 'au1', auctionConfig.componentAuctions); - sinon.assert.calledWith(setGptConfig, 'au2', []); - sinon.assert.calledWith(markAsUsed, 'au1'); - } else { - sinon.assert.notCalled(setGptConfig); - sinon.assert.notCalled(markAsUsed); - } - }); - }) - }) - }) - }) - }); - describe('setPAAPIConfigForGpt', () => { - let getPAAPIConfig, setGptConfig, setPAAPIConfigForGPT; - beforeEach(() => { - getPAAPIConfig = sinon.stub(); - setGptConfig = sinon.stub(); - setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig); - }); - - Object.entries({ - missing: null, - empty: {} - }).forEach(([t, configs]) => { - it(`does not set GPT slot config when config is ${t}`, () => { - getPAAPIConfig.returns(configs); - setPAAPIConfigForGPT('mock-filters'); - sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); - sinon.assert.notCalled(setGptConfig); - }) - }); - - it('sets GPT slot config for each ad unit that has PAAPI config, and resets the rest', () => { - const cfg = { - au1: { - componentAuctions: [{seller: 's1'}, {seller: 's2'}] - }, - au2: { - componentAuctions: [{seller: 's3'}] - }, - au3: null - } - getPAAPIConfig.returns(cfg); - setPAAPIConfigForGPT('mock-filters'); - sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); - Object.entries(cfg).forEach(([au, config]) => { - sinon.assert.calledWith(setGptConfig, au, config?.componentAuctions ?? [], true); - }) - }); - }); - - describe('getPAAPISizeHook', () => { - let next; - beforeEach(() => { - next = sinon.stub(); - next.bail = sinon.stub(); - }); - - it('should pick largest supported size over larger unsupported size', () => { - getPAAPISizeHook(next, [[999, 999], [300, 250], [300, 600], [1234, 4321]]); - sinon.assert.calledWith(next.bail, [300, 600]); - }); - - Object.entries({ - 'present': [], - 'supported': [[123, 4], [321, 5]], - 'defined': undefined, - }).forEach(([t, sizes]) => { - it(`should defer to next when no size is ${t}`, () => { - getPAAPISizeHook(next, sizes); - sinon.assert.calledWith(next, sizes); - }) - }) - }) -}); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index ff6f8562a4e..32ca99ecd76 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -26,30 +26,30 @@ describe('fluctAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return true when dfpUnitCode is not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { tagId: '10000:100000001', groupId: '1000000002', }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when groupId is not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { dfpUnitCode: '/1000/dfp_unit_code', tagId: '10000:100000001', }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 90ebe0b80ee..94b7f04b637 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -41,12 +41,12 @@ describe('freewheelSSP BidAdapter Test', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing zone id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -73,12 +73,12 @@ describe('freewheelSSP BidAdapter Test', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing zone id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/gammaBidAdapter_spec.js b/test/spec/modules/gammaBidAdapter_spec.js index f3a28c08576..2c83c3912e3 100644 --- a/test/spec/modules/gammaBidAdapter_spec.js +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -28,9 +28,9 @@ describe('gammaBidAdapter', function() { }); it('should return false when require params are not passed', () => { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when params not passed correctly', () => { diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 8c3aa6c94cb..77644b136db 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('GmosspAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js index f1af3b71103..8e2cfadc96b 100644 --- a/test/spec/modules/gnetBidAdapter_spec.js +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -32,10 +32,10 @@ describe('gnetAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js index 93956d2caf9..6ea84ed6931 100644 --- a/test/spec/modules/goldbachBidAdapter_spec.js +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -36,23 +36,23 @@ describe('GoldbachXandrAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index fa2236f77c6..5caa95404dc 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -188,7 +188,7 @@ describe('GPT pre-auction module', () => { customGptSlotMatching: false, customPbAdSlot: false, customPreAuction: false, - useDefaultPreAuction: false + useDefaultPreAuction: true }); }); }); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index efd7b06685f..4e13b1957b5 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -30,12 +30,12 @@ describe('TheMediaGrid Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'uid': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 75d7ffd6bc7..3424d172775 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -44,9 +44,9 @@ describe('gumgumAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'inSlot': '789' }; @@ -54,33 +54,33 @@ describe('gumgumAdapter', function () { }); it('should return true when inslot sends sizes and trackingid', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'inSlot': '789', 'sizes': [[0, 1], [2, 3], [4, 5], [6, 7]] }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when no unit type is specified', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when bidfloor is not a number', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'inSlot': '789', 'bidfloor': '0.50' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false if invalid request id is found', function () { diff --git a/test/spec/modules/hadronRtdProvider_spec.js b/test/spec/modules/hadronRtdProvider_spec.js index b9e07c97f84..140855194c5 100644 --- a/test/spec/modules/hadronRtdProvider_spec.js +++ b/test/spec/modules/hadronRtdProvider_spec.js @@ -1,7 +1,5 @@ -// TODO: this and hadronRtdProvider_spec are a copy-paste of each other - import {config} from 'src/config.js'; -import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js'; +import {HADRONID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; @@ -737,7 +735,7 @@ describe('hadronRtdProvider', function() { } }; - getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHadronId1'); + getDataFromLocalStorageStub.withArgs(HADRONID_LOCAL_NAME).returns('testHadronId1'); getRealTimeData(bidConfig, () => {}, rtdConfig, {}); let request = server.requests[0]; diff --git a/test/spec/modules/id5AnalyticsAdapter_spec.js b/test/spec/modules/id5AnalyticsAdapter_spec.js index c9d21daa4e0..7616052dbe7 100644 --- a/test/spec/modules/id5AnalyticsAdapter_spec.js +++ b/test/spec/modules/id5AnalyticsAdapter_spec.js @@ -102,7 +102,7 @@ describe('ID5 analytics adapter', () => { server.respond(); // Why 3? 1: config, 2: tcfEnforcement, 3: auctionEnd - // tcfEnforcement? yes, gdprEnforcement module emits in reaction to auctionEnd + // tcfEnforcement? yes, tcfControl module emits in reaction to auctionEnd expect(server.requests).to.have.length(3); const body1 = JSON.parse(server.requests[1].requestBody); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 707560f2f4e..7a2756dff9e 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1007,6 +1007,7 @@ describe('ID5 ID System', function () { id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; + id5Config.userSync.auctionDelay = 0; // do not trigger callback before auction init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(id5Config); diff --git a/test/spec/modules/idWardRtdProvider_spec.js b/test/spec/modules/idWardRtdProvider_spec.js deleted file mode 100644 index d1601f058ff..00000000000 --- a/test/spec/modules/idWardRtdProvider_spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import {config} from 'src/config.js'; -import {getRealTimeData, idWardRtdSubmodule, storage} from 'modules/idWardRtdProvider.js'; - -describe('idWardRtdProvider', function() { - let getDataFromLocalStorageStub; - - const testReqBidsConfigObj = { - adUnits: [ - { - bids: ['bid1', 'bid2'] - } - ] - }; - - const onDone = function() { return true }; - - const cmoduleConfig = { - 'name': 'idWard', - 'params': { - 'cohortStorageKey': 'cohort_ids' - } - } - - beforeEach(function() { - config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') - }); - - afterEach(function () { - getDataFromLocalStorageStub.restore(); - }); - - describe('idWardRtdSubmodule', function() { - it('successfully instantiates', function () { - expect(idWardRtdSubmodule.init()).to.equal(true); - }); - }); - - describe('Get Real-Time Data', function() { - it('gets rtd from local storage', function() { - const rtdConfig = { - params: { - cohortStorageKey: 'cohort_ids', - segtax: 503 - } - }; - - const bidConfig = { - ortb2Fragments: { - global: {} - } - }; - - const rtdUserObj1 = { - name: 'anonymised.io', - ext: { - segtax: 503 - }, - segment: [ - { - id: 'TCZPQOWPEJG3MJOTUQUF793A' - }, - { - id: '93SUG3H540WBJMYNT03KX8N3' - } - ] - }; - - getDataFromLocalStorageStub.withArgs('cohort_ids') - .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); - - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); - }); - - it('do not set rtd if local storage empty', function() { - const rtdConfig = { - params: { - cohortStorageKey: 'cohort_ids', - segtax: 503 - } - }; - - const bidConfig = {}; - - getDataFromLocalStorageStub.withArgs('cohort_ids') - .returns(null); - - expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2).to.be.undefined; - }); - - it('do not set rtd if local storage has incorrect value', function() { - const rtdConfig = { - params: { - cohortStorageKey: 'cohort_ids', - segtax: 503 - } - }; - - const bidConfig = {}; - - getDataFromLocalStorageStub.withArgs('cohort_ids') - .returns('wrong cohort ids value'); - - expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2).to.be.undefined; - }); - - it('should initialize and return with config', function () { - expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) - }); - }); -}); diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 9b702c027f9..c689307416f 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -17,7 +17,7 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'exchange'; @@ -510,8 +510,6 @@ describe('IlluminBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 78e938dd074..215e0b8ac98 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -10,7 +10,7 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; @@ -32,6 +32,7 @@ describe('Improve Digital Adapter Tests', function () { const simpleBidRequest = { bidder: 'improvedigital', params: { + publisherId: 1234, placementId: 1053688 }, adUnitCode: 'div-gpt-ad-1499748733608-0', @@ -59,6 +60,7 @@ describe('Improve Digital Adapter Tests', function () { const instreamBidRequest = { bidder: 'improvedigital', params: { + publisherId: 1234, placementId: 123456 }, adUnitCode: 'video1', @@ -107,17 +109,6 @@ describe('Improve Digital Adapter Tests', function () { } }; - const simpleSmartTagBidRequest = { - mediaTypes: {}, - bidder: 'improvedigital', - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - publisherId: 1032, - placementKey: 'data_team_test_hb_smoke_test' - } - }; - const bidderRequest = { ortb2: { source: { @@ -174,6 +165,10 @@ describe('Improve Digital Adapter Tests', function () { return bidRequests; } + function formatPublisherUrl(baseUrl, publisherId) { + return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`; + } + before(() => { hook.ready(); }); @@ -188,12 +183,7 @@ describe('Improve Digital Adapter Tests', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when both placementId and placementKey + publisherId are missing', function () { - const bid = { 'params': {} }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when only one of placementKey and publisherId is present', function () { + it('should return false when only one of placementId or publisherId is present', function () { let bid = { params: { publisherId: 1234 @@ -202,19 +192,15 @@ describe('Improve Digital Adapter Tests', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); bid = { params: { - placementKey: 'xyz' + placementId: 1234 } }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return true when placementId is passed', function () { + it('should return true when both placementId and publisherId are passed', function () { expect(spec.isBidRequestValid(simpleBidRequest)).to.equal(true); }); - - it('should return true when both placementKey and publisherId are passed', function () { - expect(spec.isBidRequestValid(simpleSmartTagBidRequest)).to.equal(true); - }); }); describe('buildRequests', function () { @@ -228,12 +214,10 @@ describe('Improve Digital Adapter Tests', function () { }); it('should make a well-formed request objects', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(AD_SERVER_URL); + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const payload = JSON.parse(request.data); expect(payload).to.be.an('object'); @@ -264,12 +248,10 @@ describe('Improve Digital Adapter Tests', function () { }); it('should make a well-formed request object for multi-format ad unit', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests(updateNativeParams([multiFormatBidRequest]), multiFormatBidderRequest)[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(AD_SERVER_URL); + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const payload = JSON.parse(request.data); expect(payload).to.be.an('object'); @@ -344,12 +326,6 @@ describe('Improve Digital Adapter Tests', function () { }); } - it('should set placementKey and publisherId for smart tags', function () { - const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); - expect(payload.imp[0].ext.bidder.publisherId).to.equal(1032); - expect(payload.imp[0].ext.bidder.placementKey).to.equal('data_team_test_hb_smoke_test'); - }); - it('should add keyValues', function () { const bidRequest = Object.assign({}, simpleBidRequest); const keyValues = { @@ -585,7 +561,7 @@ describe('Improve Digital Adapter Tests', function () { it('should return 2 requests', function () { const requests = spec.buildRequests([ simpleBidRequest, - simpleSmartTagBidRequest + instreamBidRequest ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); @@ -597,7 +573,7 @@ describe('Improve Digital Adapter Tests', function () { const requests = spec.buildRequests([ simpleBidRequest, instreamBidRequest ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(AD_SERVER_URL); + expect(requests[0].url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const request = JSON.parse(requests[0].data); expect(request.imp.length).to.equal(2); expect(request.imp[0].banner).to.exist; @@ -611,7 +587,7 @@ describe('Improve Digital Adapter Tests', function () { expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); expect(requests[0].url).to.equal(EXTEND_URL); - expect(requests[1].url).to.equal(AD_SERVER_URL); + expect(requests[1].url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const adServerRequest = JSON.parse(requests[1].data); expect(adServerRequest.imp.length).to.equal(2); expect(adServerRequest.imp[0].banner).to.exist; @@ -619,8 +595,6 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set Prebid sizes in bid request', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); sinon.assert.match(payload.imp[0].banner, { @@ -632,8 +606,6 @@ describe('Improve Digital Adapter Tests', function () { }); it('should not add single size filter when using Prebid sizes', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const bidRequest = Object.assign({}, simpleBidRequest); const size = { w: 800, @@ -659,32 +631,9 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.app.content).does.exist.and.equal('XYZ'); }); - it('should not set site when app is defined in CONFIG', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('app').returns({ content: 'XYZ' }); - let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; - let payload = JSON.parse(request.data); - expect(payload.site).does.not.exist; - expect(payload.app).does.exist; - expect(payload.app.content).does.exist.and.equal('XYZ'); - }); - it('should set correct site params', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('site').returns({ - content: 'XYZ', - page: 'https://improveditigal.com/', - domain: 'improveditigal.com' - }); let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; let payload = JSON.parse(request.data); - expect(payload.site.content).does.exist.and.equal('XYZ'); - expect(payload.site.page).does.exist.and.equal('https://improveditigal.com/'); - expect(payload.site.domain).does.exist.and.equal('improveditigal.com'); - getConfigStub.reset(); - - request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; - payload = JSON.parse(request.data); expect(payload.site.content).does.not.exist; expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); @@ -697,23 +646,13 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.site.domain).does.exist.and.equal('blah.com'); }); - it('should set site when app not available', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('app').returns(undefined); - getConfigStub.withArgs('site').returns({}); - let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; - let payload = JSON.parse(request.data); - expect(payload.site).does.exist; - expect(payload.app).does.not.exist; - }); - it('should call basic ads endpoint when no consent for purpose 1', function () { const consent = deepClone(gdprConsent); deepSetValue(consent, 'vendorData.purpose.consents.1', false); const bidderRequestWithConsent = deepClone(bidderRequest); bidderRequestWithConsent.gdprConsent = consent; const request = spec.buildRequests([simpleBidRequest], bidderRequestWithConsent)[0]; - expect(request.url).to.equal(BASIC_ADS_URL); + expect(request.url).to.equal(formatPublisherUrl(BASIC_ADS_BASE_URL, 1234)); }); it('should set extend params when extend mode enabled from global configuration', function () { @@ -732,6 +671,7 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].ext.bidder).to.not.exist; expect(payload.imp[0].ext.prebid.bidder.improvedigital).to.deep.equal({ placementId: 1053688, + publisherId: 1234, keyValues }); expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal('1053688'); @@ -757,18 +697,15 @@ describe('Improve Digital Adapter Tests', function () { bidRequest.params.extend = false; getConfigStub.withArgs('improvedigital.extend').returns(true); request = spec.buildRequests([bidRequest], { bids: [bidRequest] })[0]; - expect(request.url).to.equal(AD_SERVER_URL); + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const requests = spec.buildRequests([bidRequest, instreamBidRequest], { bids: [bidRequest, instreamBidRequest] }); expect(requests.length).to.equal(2); - expect(requests[0].url).to.equal(AD_SERVER_URL); + expect(requests[0].url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); expect(requests[1].url).to.equal(EXTEND_URL); }); it('should add publisherId to request URL when available in request params', function() { - function formatPublisherUrl(baseUrl, publisherId) { - return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`; - } const bidRequest = deepClone(simpleBidRequest); bidRequest.params.publisherId = 1000; let request = spec.buildRequests([bidRequest], bidderRequest)[0]; @@ -817,10 +754,6 @@ describe('Improve Digital Adapter Tests', function () { bidderRequestWithConsent.gdprConsent = consent; request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000)); - - delete bidRequest.params.publisherId; - request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; - expect(request.url).to.equal(AD_SERVER_URL); }); }); @@ -1047,7 +980,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1057,7 +990,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', }) ]; @@ -1070,7 +1003,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '479163', dealId: 320896, netRevenue: false, diff --git a/test/spec/modules/iqmBidAdapter_spec.js b/test/spec/modules/iqmBidAdapter_spec.js deleted file mode 100644 index 2f8b5811b2f..00000000000 --- a/test/spec/modules/iqmBidAdapter_spec.js +++ /dev/null @@ -1,414 +0,0 @@ -import { expect } from 'chai'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; -import {spec} from 'modules/iqmBidAdapter'; - -const ENDPOINT = 'https://pbd.bids.iqm.com'; - -describe('iqmAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = - { - bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - bidfloor: 0.50 - }, - - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return false when no bid', function () { - expect(spec.isBidRequestValid()).to.equal(false); - }); - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - it('should return false when it is video and mimes and protcol are not present', function () { - const bid = { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: 'a0aca162-e3d0-44db-a465-5c96a64fa5fb', - bidId: '2cbdc9b506be33', - bidRequestsCount: 1, - bidder: 'iqm', - bidderRequestId: '185c3a4c7f88ec', - bidderRequestsCount: 1, - bidderWinsCount: 0, - crumbs: {pubcid: 'f56a553d-370d-4cea-b31a-7214a3d8f8e1'}, - mediaTypes: { - video: { - context: 'instream', - playerSize: [ - [ - 640, - 480 - ] - ] - } - }, - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - geo: { - country: 'USA' - }, - - bidfloor: 0.50, - video: { - placement: 2, - mimes: null, - protocols: null, - skipppable: true, - playback_method: ['auto_play_sound_off'] - } - }, - src: 'client', - transactionId: 'a57d06fd-cc6d-4a90-87af-c10727998f0b' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - it('should return false when required params are not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - placementId: 0, - publisherId: null - - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let validBidRequests = [ - {bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - bidfloor: 0.5}, - crumbs: { - pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, - ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, - mediaTypes: { - banner: { - sizes: [[300, 250]]}}, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', - sizes: [[300, 250]], - bidId: '266d810da21904', - bidderRequestId: '13c05d264c7ffe', - auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0}]; - - let bidderRequest = { - bidderCode: 'iqm', - auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', - bidderRequestId: '13c05d264c7ffe', - bids: [{ - bidder: 'iqm', - params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, - crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, - ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, - mediaTypes: {banner: {sizes: [[300, 250]]}}, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', - sizes: [[300, 250]], - bidId: '266d810da21904', - bidderRequestId: '13c05d264c7ffe', - auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }], - auctionStart: 1615205942159, - timeout: 7000, - refererInfo: { - page: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', - domain: 'test.localhost.com:9999', - ref: null, - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], - canonicalUrl: null - }, - start: 1615205942162 - }; - - it('should parse out sizes', function () { - let temp = []; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = request[0].data; - - expect(payload.sizes).to.exist; - expect(payload.sizes[0]).to.deep.equal([300, 250]); - }); - - it('should populate the ad_types array on all requests', function () { - // const bidRequest = Object.assign({}, bidRequests[0]); - - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = request[0].data; - - expect(payload.imp.mediatype).to.deep.equal('banner'); - }); - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request[0].url).to.equal(ENDPOINT); - expect(request[0].method).to.equal('POST'); - }); - it('should attach valid video params to the tag', function () { - let validBidRequests_video = [{ - bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - bidfloor: 0.5, - video: { - placement: 2, - mimes: ['video/mp4'], - protocols: [2, 5], - skipppable: true, - playback_method: ['auto_play_sound_off'] - } - }, - crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, - ortb2Imp: {ext: {data: {'pbadslot': 'video1'}}}, - mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, - adUnitCode: 'video1', - transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', - sizes: [[640, 480]], - bidId: '28bfb7e2d12897', - bidderRequestId: '16e1ce8481bc6d', - auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }]; - let bidderRequest_video = { - bidderCode: 'iqm', - auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', - bidderRequestId: '16e1ce8481bc6d', - bids: [{ - bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - bidfloor: 0.5, - video: { - placement: 2, - mimes: ['video/mp4'], - protocols: [2, 5], - skipppable: true, - playback_method: ['auto_play_sound_off'] - } - }, - crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, - fpd: {context: {pbAdSlot: 'video1'}}, - mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, - adUnitCode: 'video1', - transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', - sizes: [[640, 480]], - bidId: '28bfb7e2d12897', - bidderRequestId: '16e1ce8481bc6d', - auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }], - auctionStart: 1615271191985, - timeout: 3000, - refererInfo: { - page: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', - domain: 'test.localhost.com:9999', - ref: null, - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], - canonicalUrl: null - }, - start: 1615271191988 - }; - const request = spec.buildRequests(validBidRequests_video, bidderRequest_video); - const payload = request[0].data; - expect(payload.imp.id).to.exist; - expect(payload.imp.displaymanager).to.exist; - expect(payload.imp.displaymanagerver).to.exist; - - expect(payload.imp.video).to.deep.equal({ - context: 'instream', - w: 640, - h: 480, - mimes: ['video/mp4'], - placement: 1, - protocols: [2, 5], - startdelay: 0 - }); - }); - - it('should add referer info to payload', function () { - // TODO: this is wrong on multiple levels - // The payload contains everything in `bidderRequest`; that is sometimes not even serializable - // this should not be testing the validity of internal Prebid structures - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = request[0].data; - - expect(payload.bidderRequest.refererInfo).to.exist; - }); - }) - - describe('interpretResponse', function () { - let tempResult = {requestId: '2d9601dd8328f8', currency: 'USD', cpm: 4.5, netRevenue: true, creativeId: 'cr-121004', adUnitCode: 'div-gpt-ad-1460505748561-0', 'auctionId': '22a4f3d8-511f-46ba-91be-53b9949e4b48', mediaType: 'banner', ttl: 3000, ad: " ", width: 844, height: 617}; - let validBidRequests_temp = [ - {bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - bidfloor: 0.5}, - crumbs: { - pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, - ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, - mediaTypes: { - banner: { - sizes: [[300, 250]]}}, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', - sizes: [[300, 250]], - bidId: '266d810da21904', - bidderRequestId: '13c05d264c7ffe', - auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0}]; - let bidderRequest = { - bidderCode: 'iqm', - auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', - bidderRequestId: '13c05d264c7ffe', - bids: [{ - bidder: 'iqm', - params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, - crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, - ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, - mediaTypes: {banner: {sizes: [[300, 250]]}}, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', - sizes: [[300, 250]], - bidId: '266d810da21904', - bidderRequestId: '13c05d264c7ffe', - auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }], - auctionStart: 1615205942159, - timeout: 7000, - refererInfo: { - page: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', - domain: 'test.localhost.com:9999', - ref: null, - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], - canonicalUrl: null - }, - start: 1615205942162 - }; - let response = { - - id: '5bdbab92aae961cfbdf7465d', - seatbid: [{bid: [{id: 'bid-5bdbab92aae961cfbdf7465d-5bdbab92aae961cfbdf74653', impid: '5bdbab92aae961cfbdf74653', price: 9.9, nurl: 'https://winn.stage.iqm.com/smaato?raw=w9XViV4dovBHrxujHhBj-l-uWB08CUOMW_oR-EUxZbaWLL0ENzcMlP3CJFEURN6FgRp_HdjAjxTYHR7uG4S6h6dl_vjU_YNABiPd607-iTqxOCl-2cKLo-hhQus4sMw01VIqyqrPmzOTHTwJm4vTjUIoWMPZbARgQvUnBzjRH9xeYS-Bv3kgAW9NSBfgBZeLyT3WJJ_3VKIE_Iurt8OjpA%3D%3D&req_id=5bdbab92aae961cfbdf7465d&ap=${AUCTION_PRICE}', adm: " ", adomain: ['click.iqm.com'], iurl: 'https://d3jme5si7t6llb.cloudfront.net/image/1/404/owVo6mc_1588902031079.png', cid: '169218', crid: 'cr-301435', attr: [], h: 250, w: 250}]}], - bidid: '5bdbab92aae961cfbdf7465d' - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - {requestId: '49ad5f21156efd', currency: 'USD', cpm: 9.9, netRevenue: true, creativeId: 'cr-301435', adUnitCode: '/19968336/header-bid-tag-0', auctionId: '853cddf1-8d13-4482-bd88-f5ef927d5ab3', mediaType: 'banner', ttl: 3000, ad: " ", width: 250, height: 250} - ]; - let temprequest = spec.buildRequests(validBidRequests_temp, bidderRequest); - - let result = spec.interpretResponse({ body: response }, temprequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); - - let validBidRequests_temp_video = - [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: 'cd86c3ff-d630-40e6-83ab-420e9e800594'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '8335b266-7a41-45f9-86a2-92fdc7cf0cd9', sizes: [[640, 480]], bidId: '26274beff25455', bidderRequestId: '17c5d8c3168761', auctionId: '2c592dcf-7dfc-4823-8203-dd1ebab77fe0', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; - let bidderRequest_video = { - bidderCode: 'iqm', - auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', - bidderRequestId: '16e1ce8481bc6d', - bids: [{ - bidder: 'iqm', - params: { - publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', - placementId: 23451, - bidfloor: 0.5, - video: { - placement: 2, - mimes: ['video/mp4'], - protocols: [2, 5], - skipppable: true, - playback_method: ['auto_play_sound_off'] - } - }, - crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, - ortb2Imp: {ext: {data: {'pbadslot': 'video1'}}}, - mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, - adUnitCode: 'video1', - transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', - sizes: [[640, 480]], - bidId: '28bfb7e2d12897', - bidderRequestId: '16e1ce8481bc6d', - auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }], - auctionStart: 1615271191985, - timeout: 3000, - refererInfo: { - page: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', - domain: 'test.localhost.com:9999', - ref: '', - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], - canonicalUrl: null - }, - start: 1615271191988 - }; - - it('handles non-banner media responses', function () { - let response = {id: '2341234', seatbid: [{bid: [{id: 'bid-2341234-1', impid: '1', price: 9, nurl: 'https://frontend.stage.iqm.com/static/vast-01.xml', adm: 'http://cdn.iqm.com/pbd?raw=312730_203cf73dc83fb_2824348636878_pbd', adomain: ['app1.stage.iqm.com'], cid: '168900', crid: 'cr-304503', attr: []}]}], bidid: '2341234'}; - - let temprequest_video = spec.buildRequests(validBidRequests_temp_video, bidderRequest_video); - - let result = spec.interpretResponse({ body: response }, temprequest_video[0]); - expect(result[0]).to.have.property('vastUrl'); - }); - }); -}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index e5b2fbc359a..42c0c2afdf5 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -821,8 +821,9 @@ describe('IndexexchangeAdapter', function () { tid: 'mock-tid' } }, - fledgeEnabled: true, - defaultForSlots: 1 + paapi: { + enabled: true + }, }; const DEFAULT_OPTION_FLEDGE_ENABLED = { @@ -843,7 +844,9 @@ describe('IndexexchangeAdapter', function () { tid: 'mock-tid' } }, - fledgeEnabled: true + paapi: { + enabled: true + } }; const DEFAULT_IDENTITY_RESPONSE = { @@ -1348,34 +1351,6 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('Roundel alias adapter', function () { - const vaildBids = [DEFAULT_BANNER_VALID_BID, DEFAULT_VIDEO_VALID_BID, DEFAULT_MULTIFORMAT_BANNER_VALID_BID, DEFAULT_MULTIFORMAT_VIDEO_VALID_BID]; - const ALIAS_OPTIONS = Object.assign({ - bidderCode: 'roundel' - }, DEFAULT_OPTION); - - it('should not build requests for mediaTypes if liveramp data is unavaliable', function () { - vaildBids.forEach((validBid) => { - const request = spec.buildRequests(validBid, ALIAS_OPTIONS); - expect(request).to.be.an('array'); - expect(request).to.have.lengthOf(0); - }); - }); - - it('should build requests for mediaTypes if liveramp data is avaliable', function () { - vaildBids.forEach((validBid) => { - const cloneValidBid = utils.deepClone(validBid); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); - const payload = extractPayload(request[0]); - expect(request).to.be.an('array'); - expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(11); - expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); - }); - }); - }); - describe('buildRequestsIdentity', function () { let request; let payload; @@ -3464,16 +3439,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.ext.ae).to.equal(1); }); - it('impression should have ae=1 in ext when fledge module is enabled globally and default is set through setConfig', function () { - const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED_GLOBALLY); - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; - const impression = extractPayload(requestBidFloor).imp[0]; - - expect(impression.ext.ae).to.equal(1); - }); - - it('impression should have ae=1 in ext when fledge module is enabled globally but no default set through setConfig but set at ad unit level', function () { + it('impression should have ae=1 in ext when request has paapi.enabled = true and ext.ae = 1', function () { const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; @@ -4180,7 +4146,7 @@ describe('IndexexchangeAdapter', function () { beforeEach(() => { bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; - bidderRequestWithFledgeEnabled.fledgeEnabled = true; + bidderRequestWithFledgeEnabled.paapi = {enabled: true}; serverResponseWithoutFledgeConfigs = { body: { @@ -4244,17 +4210,17 @@ describe('IndexexchangeAdapter', function () { } } ]; - expect(result.fledgeAuctionConfigs).to.deep.equal(expectedOutput); + expect(result.paapi).to.deep.equal(expectedOutput); }); it('should correctly interpret response without auction configs', () => { const result = spec.interpretResponse(serverResponseWithoutFledgeConfigs, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.paapi).to.be.undefined; }); it('should handle malformed auction configs gracefully', () => { const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfig, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.empty; + expect(result.paapi).to.be.empty; }); it('should log warning for malformed auction configs', () => { @@ -4266,7 +4232,7 @@ describe('IndexexchangeAdapter', function () { it('should return bids when protected audience auction conigs is malformed', () => { const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfigs, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.paapi).to.be.undefined; expect(result.length).to.be.greaterThan(0); }); }); @@ -4285,7 +4251,7 @@ describe('IndexexchangeAdapter', function () { }; bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; - bidderRequestWithFledgeEnabled.fledgeEnabled = true; + bidderRequestWithFledgeEnabled.paapi = {enabled: true}; bidderRequestWithoutFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index c57c8a685e7..36794ceeae3 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -629,7 +629,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2Fragments.global).to.have.property('site'); expect(ortb2Fragments.global.site).to.have.property('content'); - expect(ortb2Fragments.global.site.content).to.have.property('id', 'jw_' + testIdForSuccess); + expect(ortb2Fragments.global.site.content).to.have.property('id', 'randomContentId'); expect(ortb2Fragments.global.site.content).to.have.property('data'); const data = ortb2Fragments.global.site.content.data; expect(data).to.have.length(3); @@ -801,7 +801,7 @@ describe('jwplayerRtdProvider', function() { describe(' Add Ortb Site Content', function () { beforeEach(() => { setOverrides({ - overrideContentId: 'always', + overrideContentId: 'whenEmpty', overrideContentUrl: 'whenEmpty', overrideContentTitle: 'whenEmpty', overrideContentDescription: 'whenEmpty' @@ -865,16 +865,16 @@ describe('jwplayerRtdProvider', function() { } }; - const expectedId = 'expectedId'; + const newId = 'newId'; const expectedUrl = 'expectedUrl'; const expectedTitle = 'expectedTitle'; const expectedDescription = 'expectedDescription'; const expectedData = { datum: 'datum' }; - addOrtbSiteContent(ortb2, expectedId, expectedData, expectedTitle, expectedDescription, expectedUrl); + addOrtbSiteContent(ortb2, newId, expectedData, expectedTitle, expectedDescription, expectedUrl); expect(ortb2).to.have.nested.property('site.random.random_sub', 'randomSub'); expect(ortb2).to.have.nested.property('app.content.id', 'appId'); expect(ortb2).to.have.nested.property('site.content.ext.random_field', 'randomField'); - expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); expect(ortb2).to.have.nested.property('site.content.title', expectedTitle); expect(ortb2).to.have.nested.property('site.content.ext.description', expectedDescription); @@ -889,7 +889,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); - it('should override content id by default', function () { + it('should keep old content id by default', function () { const ortb2 = { site: { content: { @@ -898,9 +898,8 @@ describe('jwplayerRtdProvider', function() { } }; - const expectedId = 'expectedId'; - addOrtbSiteContent(ortb2, expectedId); - expect(ortb2).to.have.nested.property('site.content.id', expectedId); + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); }); it('should keep previous content id when new value is not available', function () { diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 510b5979333..590d98969c3 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1832,7 +1832,7 @@ describe('kargo adapter tests', function() { }); }); - it('should return fledgeAuctionConfigs if provided in bid response', function () { + it('should return paapi if provided in bid response', function () { const auctionConfig = { seller: 'https://kargo.com', decisionLogicUrl: 'https://kargo.com/decision_logic.js', @@ -1865,11 +1865,11 @@ describe('kargo adapter tests', function() { expect(bid).to.have.property('meta').that.is.an('object'); }); - // Test properties of fledgeAuctionConfigs - expect(result.fledgeAuctionConfigs).to.have.lengthOf(3); + // Test properties of paapi + expect(result.paapi).to.have.lengthOf(3); const expectedBidIds = ['1', '3', '5']; // Expected bidIDs - result.fledgeAuctionConfigs.forEach(config => { + result.paapi.forEach(config => { expect(config).to.have.property('bidId'); expect(expectedBidIds).to.include(config.bidId); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 86437180e94..16f87394df9 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -80,7 +80,7 @@ describe('KrushmediabBidAdapter', function () { let placement = data['placements'][0]; expect(placement).to.be.an('object'); expect(placement).to.have.keys('key', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'bidFloor', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); expect(placement.traffic).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index ad4040c0452..15dee20e566 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -62,14 +62,14 @@ describe('lassoBidAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when there are extra params', function () { - const bid = Object.assign({}, bid, { + const invalidBid = Object.assign({}, bid, { params: { adUnitId: 123456, zone: 1, publisher: 'test' } }) - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when there are no params', function () { const invalidBid = { ...bid }; diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 4ff69ce5e2a..1e05b9deeb3 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -46,12 +46,12 @@ describe('lkqdBidAdapter', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing zone id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/loganBidAdapter_spec.js b/test/spec/modules/loganBidAdapter_spec.js index a9859bbd4ae..f51f22580e2 100644 --- a/test/spec/modules/loganBidAdapter_spec.js +++ b/test/spec/modules/loganBidAdapter_spec.js @@ -79,7 +79,7 @@ describe('LoganBidAdapter', function () { expect(data).to.be.an('object'); let placement = data['placements'][0]; expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'wPlayer', 'hPlayer', 'schain', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'bidfloor'); + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'wPlayer', 'hPlayer', 'schain', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'bidfloor'); expect(placement.adFormat).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); expect(placement.hPlayer).to.equal(playerSize[1]); diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index 12e8ca31cbb..5c86ffc9325 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -182,7 +182,9 @@ describe('LogicadAdapter', function () { stack: [] }, auctionStart: 1563337198010, - fledgeEnabled: true + paapi: { + enabled: true + } }; const serverResponse = { body: { @@ -388,8 +390,8 @@ describe('LogicadAdapter', function () { const paapiRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; const paapiInterpretedResponse = spec.interpretResponse(paapiServerResponse, paapiRequest); expect(paapiInterpretedResponse).to.have.property('bids'); - expect(paapiInterpretedResponse).to.have.property('fledgeAuctionConfigs'); - expect(paapiInterpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); + expect(paapiInterpretedResponse).to.have.property('paapi'); + expect(paapiInterpretedResponse.paapi[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); // native const nativeRequest = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 064bad74835..664c888b45e 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -21,12 +21,12 @@ describe('luponmediaBidAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'siteId': 12345 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/madvertiseBidAdapter_spec.js b/test/spec/modules/madvertiseBidAdapter_spec.js index 466d30acdd3..8128bcc2d42 100644 --- a/test/spec/modules/madvertiseBidAdapter_spec.js +++ b/test/spec/modules/madvertiseBidAdapter_spec.js @@ -118,7 +118,7 @@ describe('madvertise adapater', () => { expect(req[0].url).to.contain(`&zoneId=test`); expect(req[0].url).to.contain(`&sizes[0]=728x90`); expect(req[0].url).to.contain(`&gdpr=1`); - expect(req[0].url).to.contain(`&consent[0][format]=IAB`); + expect(req[0].url).to.contain(`&consent[0][format]=iab`); expect(req[0].url).to.contain(`&consent[0][value]=CO_5mtSPHOmEIAsAkBFRBOCsAP_AAH_AAAqIHQgB7SrERyNAYWB5gusAKYlfQAQCA2AABAYdASgJQQBAMJYEkGAIuAnAACAKAAAEIHQAAAAlCCmABAEAAIABBSGMAQgABZAAIiAEEAATAABACAABGYCSCAIQjIAAAAEAgEKEAAoAQGBAAAEgBABAAAogACADAgXmACIKkQBAkBAYAkAYQAogAhAAAAAIAAAAAAAKAABAAAghAAQQAAAAAAAAAgAAAAABAAAAAAAAQAAAAAAAAABAAgAAAAAAAAAIAAAAAAAAAAAAAAAABAAAAAAAAAAAQCAKCgBgEQALgAqkJADAIgAXABVIaACAAERABAACKgAgABA`) }); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js index 579f41e620d..f0f453d32a0 100644 --- a/test/spec/modules/mantisBidAdapter_spec.js +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -35,10 +35,10 @@ describe('MantisAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/mediafuseBidAdapter_spec.js b/test/spec/modules/mediafuseBidAdapter_spec.js index dd2b5df70bd..1fb09265d56 100644 --- a/test/spec/modules/mediafuseBidAdapter_spec.js +++ b/test/spec/modules/mediafuseBidAdapter_spec.js @@ -35,23 +35,23 @@ describe('MediaFuseAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index cc1a15fd733..bdfc86e7148 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1768,12 +1768,12 @@ describe('Media.net bid adapter', function () { }); it('should have valid payload when PAAPI is enabled', function () { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAAPI); }); it('should send whatever is set in ortb2imp.ext.ae in all bid requests when PAAPI is enabled', function () { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); let data = JSON.parse(bidReq.data); expect(data).to.deep.equal(VALID_PAYLOAD_PAAPI); expect(data.imp[0].ext).to.have.property('ae'); @@ -1955,30 +1955,30 @@ describe('Media.net bid adapter', function () { expect(bids).to.deep.equal(validBids); }); - it('should return fledgeAuctionConfigs if PAAPI response is received', function() { + it('should return paapi if PAAPI response is received', function() { let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); + expect(response).to.have.property('paapi'); + expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); }); - it('should return fledgeAuctionConfigs if openRTB PAAPI response received', function () { + it('should return paapi if openRTB PAAPI response received', function () { let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) + expect(response).to.have.property('paapi'); + expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) }); - it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id', function() { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); - expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) }); - it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id for openRTB response', function() { - let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id for openRTB response', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); - expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) }); }); diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js index 9eb36d2fa6c..ac1738685db 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -301,10 +301,6 @@ describe('microadBidAdapter', () => { userId: {novatiq: {snowflake: 'novatiq-sample'}}, expected: {aids: JSON.stringify([{type: 10, id: 'novatiq-sample'}])} }, - 'Parrable ID': { - userId: {parrableId: {eid: 'parrable-sample'}}, - expected: {aids: JSON.stringify([{type: 11, id: 'parrable-sample'}])} - }, 'AudienceOne User ID': { userId: {dacId: {id: 'audience-one-sample'}}, expected: {aids: JSON.stringify([{type: 12, id: 'audience-one-sample'}])} diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js deleted file mode 100644 index 5101f015b0e..00000000000 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ /dev/null @@ -1,654 +0,0 @@ -import {expect} from 'chai'; -import { - spec as adapter, - createDomain, - hashCode, - extractPID, - extractCID, - extractSubDomain, - getStorageItem, - setStorageItem, - tryParseJSON, - getUniqueDealId, -} from 'modules/minutemediaplusBidAdapter.js'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; - -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; - -const SUB_DOMAIN = 'exchange'; - -const BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': 'div-gpt-ad-12345-0', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '59db6b3b4ffaa70004f45cdc', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1, - 'ext': { - 'param1': 'loremipsum', - 'param2': 'dolorsitamet' - } - }, - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [[300, 250], [300, 600]], - 'bidderRequestId': '1fdb5ff1b6eaa7', - 'auctionId': 'auction_id', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER], - 'ortb2Imp': { - 'ext': { - 'gpid': '1234567890', - tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', - } - } -}; - -const VIDEO_BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', - 'bidderRequestId': '12a8ae9ada9c13', - 'auctionId': 'auction_id', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - ortb2Imp: { - ext: { - tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - } - }, - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '635509f7ff6642d368cb9837', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1 - }, - 'sizes': [[545, 307]], - 'mediaTypes': { - 'video': { - 'playerSize': [[545, 307]], - 'context': 'instream', - 'mimes': [ - 'video/mp4', - 'application/javascript' - ], - 'protocols': [2, 3, 5, 6], - 'maxduration': 60, - 'minduration': 0, - 'startdelay': 0, - 'linearity': 1, - 'api': [2], - 'placement': 1 - } - } -} - -const BIDDER_REQUEST = { - 'gdprConsent': { - 'consentString': 'consent_string', - 'gdprApplies': true - }, - 'gppString': 'gpp_string', - 'gppSid': [7], - 'uspConsent': 'consent_string', - 'refererInfo': { - 'page': 'https://www.greatsite.com', - 'ref': 'https://www.somereferrer.com' - }, - 'ortb2': { - 'regs': { - 'gpp': 'gpp_string', - 'gpp_sid': [7] - }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } - }, -}; - -const SERVER_RESPONSE = { - body: { - cid: 'testcid123', - results: [{ - 'ad': '', - 'price': 0.8, - 'creativeId': '12610997325162499419', - 'exp': 30, - 'width': 300, - 'height': 250, - 'advertiserDomains': ['securepubads.g.doubleclick.net'], - 'cookies': [{ - 'src': 'https://sync.com', - 'type': 'iframe' - }, { - 'src': 'https://sync.com', - 'type': 'img' - }] - }] - } -}; - -const VIDEO_SERVER_RESPONSE = { - body: { - 'cid': '635509f7ff6642d368cb9837', - 'results': [{ - 'ad': '', - 'advertiserDomains': ['minutemedia-prebid.com'], - 'exp': 60, - 'width': 545, - 'height': 307, - 'mediaType': 'video', - 'creativeId': '12610997325162499419', - 'price': 2, - 'cookies': [] - }] - } -}; - -const REQUEST = { - data: { - width: 300, - height: 250, - bidId: '2d52001cabd527' - } -}; - -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - -describe('MinuteMediaPlus Bid Adapter', function () { - describe('validtae spec', function () { - it('exists and is a function', function () { - expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.buildRequests).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); - }); - - it('exists and is a string', function () { - expect(adapter.code).to.exist.and.to.be.a('string'); - }); - - it('exists and contains media types', function () { - expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); - expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); - }); - }); - - describe('validate bid requests', function () { - it('should require cId', function () { - const isValid = adapter.isBidRequestValid({ - params: { - pId: 'pid' - } - }); - expect(isValid).to.be.false; - }); - - it('should require pId', function () { - const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid' - } - }); - expect(isValid).to.be.false; - }); - - it('should validate correctly', function () { - const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - pId: 'pid' - } - }); - expect(isValid).to.be.true; - }); - }); - - describe('build requests', function () { - let sandbox; - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { - storageAllowed: true - } - }; - sandbox = sinon.sandbox.create(); - sandbox.stub(Date, 'now').returns(1000); - }); - - it('should build video request', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); - config.setConfig({ - bidderTimeout: 3000 - }); - const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); - expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, - data: { - adUnitCode: '63550ad1ff6642d368cba59dh5884270560', - bidFloor: 0.1, - bidId: '2d52001cabd527', - bidderVersion: adapter.version, - bidderRequestId: '12a8ae9ada9c13', - cb: 1000, - gdpr: 1, - gdprConsent: 'consent_string', - usPrivacy: 'consent_string', - gppString: 'gpp_string', - gppSid: [7], - prebidVersion: version, - transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - auctionId: 'auction_id', - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - publisherId: '59ac17c192832d0011283fe3', - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - res: `${window.top.screen.width}x${window.top.screen.height}`, - schain: VIDEO_BID.schain, - sizes: ['545x307'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - uniqueDealId: `${hashUrl}_${Date.now().toString()}`, - uqs: getTopWindowQueryParams(), - mediaTypes: { - video: { - api: [2], - context: 'instream', - linearity: 1, - maxduration: 60, - mimes: [ - 'video/mp4', - 'application/javascript' - ], - minduration: 0, - placement: 1, - playerSize: [[545, 307]], - protocols: [2, 3, 5, 6], - startdelay: 0 - } - }, - gpid: '' - } - }); - }); - - it('should build banner request for each size', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); - config.setConfig({ - bidderTimeout: 3000 - }); - const requests = adapter.buildRequests([BID], BIDDER_REQUEST); - expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, - data: { - gdprConsent: 'consent_string', - gdpr: 1, - gppString: 'gpp_string', - gppSid: [7], - usPrivacy: 'consent_string', - transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', - auctionId: 'auction_id', - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - bidderRequestId: '1fdb5ff1b6eaa7', - sizes: ['300x250', '300x600'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - cb: 1000, - bidFloor: 0.1, - bidId: '2d52001cabd527', - adUnitCode: 'div-gpt-ad-12345-0', - publisherId: '59ac17c192832d0011283fe3', - uniqueDealId: `${hashUrl}_${Date.now().toString()}`, - bidderVersion: adapter.version, - prebidVersion: version, - schain: BID.schain, - res: `${window.top.screen.width}x${window.top.screen.height}`, - mediaTypes: [BANNER], - gpid: '1234567890', - uqs: getTopWindowQueryParams(), - 'ext.param1': 'loremipsum', - 'ext.param2': 'dolorsitamet', - } - }); - }); - - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - sandbox.restore(); - }); - }); - describe('getUserSyncs', function () { - it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); - - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' - }]); - }); - - it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' - }]); - }); - - it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); - - expect(result).to.deep.equal([{ - 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', - 'type': 'image' - }]); - }) - - it('should generate url with consent data', function () { - const gdprConsent = { - gdprApplies: true, - consentString: 'consent_string' - }; - const uspConsent = 'usp_string'; - const gppConsent = { - gppString: 'gpp_string', - applicableSections: [7] - } - - const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); - - expect(result).to.deep.equal([{ - 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&gpp=gpp_string&gpp_sid=7', - 'type': 'image' - }]); - }); - }); - - describe('interpret response', function () { - it('should return empty array when there is no response', function () { - const responses = adapter.interpretResponse(null); - expect(responses).to.be.empty; - }); - - it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({price: 1, ad: ''}); - expect(responses).to.be.empty; - }); - - it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); - expect(responses).to.be.empty; - }); - - it('should return an array of interpreted banner responses', function () { - const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0]).to.deep.equal({ - requestId: '2d52001cabd527', - cpm: 0.8, - width: 300, - height: 250, - creativeId: '12610997325162499419', - currency: 'USD', - netRevenue: true, - ttl: 30, - ad: '', - meta: { - advertiserDomains: ['securepubads.g.doubleclick.net'] - } - }); - }); - - it('should get meta from response metaData', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - serverResponse.body.results[0].metaData = { - advertiserDomains: ['minutemedia-prebid.com'], - agencyName: 'Agency Name', - }; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses[0].meta).to.deep.equal({ - advertiserDomains: ['minutemedia-prebid.com'], - agencyName: 'Agency Name' - }); - }); - - it('should return an array of interpreted video responses', function () { - const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0]).to.deep.equal({ - requestId: '2d52001cabd527', - cpm: 2, - width: 545, - height: 307, - mediaType: 'video', - creativeId: '12610997325162499419', - currency: 'USD', - netRevenue: true, - ttl: 60, - vastXml: '', - meta: { - advertiserDomains: ['minutemedia-prebid.com'] - } - }); - }); - - it('should take default TTL', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - delete serverResponse.body.results[0].exp; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0].ttl).to.equal(300); - }); - }); - - describe('user id system', function () { - TEST_ID_SYSTEMS.forEach((idSystemProvider) => { - const id = Date.now().toString(); - const bid = utils.deepClone(BID); - - const userId = (function () { - switch (idSystemProvider) { - case 'lipb': - return {lipbid: id}; - case 'parrableId': - return {eid: id}; - case 'id5id': - return {uid: id}; - default: - return id; - } - })(); - - bid.userId = { - [idSystemProvider]: userId - }; - - it(`should include 'uid.${idSystemProvider}' in request params`, function () { - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); - }); - }); - }); - - describe('alternate param names extractors', function () { - it('should return undefined when param not supported', function () { - const cid = extractCID({'c_id': '1'}); - const pid = extractPID({'p_id': '1'}); - const subDomain = extractSubDomain({'sub_domain': 'prebid'}); - expect(cid).to.be.undefined; - expect(pid).to.be.undefined; - expect(subDomain).to.be.undefined; - }); - - it('should return value when param supported', function () { - const cid = extractCID({'cID': '1'}); - const pid = extractPID({'Pid': '2'}); - const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); - expect(cid).to.be.equal('1'); - expect(pid).to.be.equal('2'); - expect(subDomain).to.be.equal('prebid'); - }); - }); - - describe('unique deal id', function () { - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { - storageAllowed: true - } - }; - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - const key = 'myKey'; - let uniqueDealId; - beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); - }) - - it('should get current unique deal id', function (done) { - // waiting some time so `now` will become past - setTimeout(() => { - const current = getUniqueDealId(key); - expect(current).to.be.equal(uniqueDealId); - done(); - }, 200); - }); - - it('should get new unique deal id on expiration', function (done) { - setTimeout(() => { - const current = getUniqueDealId(key, 100); - expect(current).to.not.be.equal(uniqueDealId); - done(); - }, 200) - }); - }); - - describe('storage utils', function () { - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { - storageAllowed: true - } - }; - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - it('should get value from storage with create param', function () { - const now = Date.now(); - const clock = useFakeTimers({ - shouldAdvanceTime: true, - now - }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); - expect(created).to.be.equal(now); - expect(value).to.be.equal(2020); - expect(typeof value).to.be.equal('number'); - expect(typeof created).to.be.equal('number'); - clock.restore(); - }); - - it('should get external stored value', function () { - const value = 'superman' - window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); - expect(item).to.be.equal(value); - }); - - it('should parse JSON value', function () { - const data = JSON.stringify({event: 'send'}); - const {event} = tryParseJSON(data); - expect(event).to.be.equal('send'); - }); - - it('should get original value on parse fail', function () { - const value = 21; - const parsed = tryParseJSON(value); - expect(typeof parsed).to.be.equal('number'); - expect(parsed).to.be.equal(value); - }); - }); -}); diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index a4e58afbd1b..ad88f18eb4c 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -83,7 +83,7 @@ describe('MobfoxHBBidAdapter', function () { let placement = data['placements'][0]; expect(placement).to.be.an('object'); expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'playerSize', 'wPlayer', 'hPlayer', 'schain', 'bidfloor', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', + 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); expect(placement.traffic).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); diff --git a/test/spec/modules/mytargetBidAdapter_spec.js b/test/spec/modules/mytargetBidAdapter_spec.js deleted file mode 100644 index 8880efd3d7c..00000000000 --- a/test/spec/modules/mytargetBidAdapter_spec.js +++ /dev/null @@ -1,199 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/mytargetBidAdapter'; - -describe('MyTarget Adapter', function() { - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - let validBid = { - bidder: 'mytarget', - params: { - placementId: '1' - } - }; - - expect(spec.isBidRequestValid(validBid)).to.equal(true); - }); - - it('should return false for when required params are not passed', function () { - let invalidBid = { - bidder: 'mytarget', - params: {} - }; - - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - bidId: 'bid1', - bidder: 'mytarget', - params: { - placementId: '1' - } - }, - { - bidId: 'bid2', - bidder: 'mytarget', - params: { - placementId: '2', - position: 1, - response: 1, - bidfloor: 10000 - } - } - ]; - let bidderRequest = { - refererInfo: { - page: 'https://example.com?param=value' - } - }; - - let bidRequest = spec.buildRequests(bidRequests, bidderRequest); - - it('should build single POST request for multiple bids', function() { - expect(bidRequest.method).to.equal('POST'); - expect(bidRequest.url).to.equal('//ad.mail.ru/hbid_prebid/'); - expect(bidRequest.data).to.be.an('object'); - expect(bidRequest.data.places).to.be.an('array'); - expect(bidRequest.data.places).to.have.lengthOf(2); - }); - - it('should pass bid parameters', function() { - let place1 = bidRequest.data.places[0]; - let place2 = bidRequest.data.places[1]; - - expect(place1.placementId).to.equal('1'); - expect(place2.placementId).to.equal('2'); - expect(place1.id).to.equal('bid1'); - expect(place2.id).to.equal('bid2'); - }); - - it('should pass default position and response type', function() { - let place = bidRequest.data.places[0]; - - expect(place.position).to.equal(0); - expect(place.response).to.equal(0); - }); - - it('should pass provided position and response type', function() { - let place = bidRequest.data.places[1]; - - expect(place.position).to.equal(1); - expect(place.response).to.equal(1); - }); - - it('should not pass default bidfloor', function() { - let place = bidRequest.data.places[0]; - - expect(place.bidfloor).not.to.exist; - }); - - it('should not pass provided bidfloor', function() { - let place = bidRequest.data.places[1]; - - expect(place.bidfloor).to.exist; - expect(place.bidfloor).to.equal(10000); - }); - - it('should pass site parameters', function() { - let site = bidRequest.data.site; - - expect(site).to.be.an('object'); - expect(site.sitename).to.equal('example.com'); - expect(site.page).to.equal('https://example.com?param=value'); - }); - - it('should pass settings', function() { - let settings = bidRequest.data.settings; - - expect(settings).to.be.an('object'); - expect(settings.currency).to.equal('RUB'); - expect(settings.windowSize).to.be.an('object'); - expect(settings.windowSize.width).to.equal(window.screen.width); - expect(settings.windowSize.height).to.equal(window.screen.height); - }); - }); - - describe('interpretResponse', function () { - let serverResponse = { - body: { - 'bidder_status': - [ - { - 'bidder': 'mail.ru', - 'response_time_ms': 100, - 'num_bids': 2 - } - ], - 'bids': - [ - { - 'displayUrl': 'https://ad.mail.ru/hbid_imp/12345', - 'size': - { - 'height': '400', - 'width': '240' - }, - 'id': '1', - 'currency': 'RUB', - 'price': 100, - 'ttl': 360, - 'creativeId': '123456' - }, - { - 'adm': '

Ad

', - 'size': - { - 'height': '250', - 'width': '300' - }, - 'id': '2', - 'price': 200 - } - ] - } - }; - - let bids = spec.interpretResponse(serverResponse); - - it('should return empty array for response with no bids', function() { - let emptyBids = spec.interpretResponse({ body: {} }); - - expect(emptyBids).to.have.lengthOf(0); - }); - - it('should parse all bids from response', function() { - expect(bids).to.have.lengthOf(2); - }); - - it('should parse bid with ad url', function() { - expect(bids[0].requestId).to.equal('1'); - expect(bids[0].cpm).to.equal(100); - expect(bids[0].width).to.equal('240'); - expect(bids[0].height).to.equal('400'); - expect(bids[0].ttl).to.equal(360); - expect(bids[0].currency).to.equal('RUB'); - expect(bids[0]).to.have.property('creativeId'); - expect(bids[0].creativeId).to.equal('123456'); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adUrl).to.equal('https://ad.mail.ru/hbid_imp/12345'); - expect(bids[0]).to.not.have.property('ad'); - }); - - it('should parse bid with ad markup', function() { - expect(bids[1].requestId).to.equal('2'); - expect(bids[1].cpm).to.equal(200); - expect(bids[1].width).to.equal('300'); - expect(bids[1].height).to.equal('250'); - expect(bids[1].ttl).to.equal(180); - expect(bids[1].currency).to.equal('RUB'); - expect(bids[1]).to.have.property('creativeId'); - expect(bids[1].creativeId).not.to.equal('123456'); - expect(bids[1].netRevenue).to.equal(true); - expect(bids[1].ad).to.equal('

Ad

'); - expect(bids[1]).to.not.have.property('adUrl'); - }); - }); -}); diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index 10a9c4c946c..18b878acac3 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -107,9 +107,9 @@ describe('omsBidAdapter', function () { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 3ceaec13cd5..a6edaaabe79 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -436,13 +436,15 @@ describe('onetag', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'fledgeEnabled': true + 'paapi': { + 'enabled': true + } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.paapi.enabled); }); it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { let bidderRequest = { @@ -450,13 +452,15 @@ describe('onetag', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'fledgeEnabled': false + paapi: { + enabled: false + } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.paapi.enabled); }); it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { let bidderRequest = { @@ -485,7 +489,7 @@ describe('onetag', function () { expect(fledgeInterpretedResponse.bids).to.satisfy(function (value) { return value === null || Array.isArray(value); }); - expect(fledgeInterpretedResponse.fledgeAuctionConfigs).to.be.an('array').that.is.not.empty; + expect(fledgeInterpretedResponse.paapi).to.be.an('array').that.is.not.empty; for (let i = 0; i < interpretedResponse.length; i++) { let dataItem = interpretedResponse[i]; expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index 6ddc0edd477..c636542c9c9 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -92,9 +92,9 @@ describe('onomagicBidAdapter', function() { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index 34f92a76c42..f6f6ad22476 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -25,7 +25,8 @@ describe('openwebAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [['640', '480']], 'params': { - 'org': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001', + 'placementId': '123' } }; @@ -33,7 +34,7 @@ describe('openwebAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required params are not found', function () { + it('should return false when org param is not found', function () { const newBid = Object.assign({}, bid); delete newBid.params; newBid.params = { @@ -41,6 +42,15 @@ describe('openwebAdapter', function () { }; expect(spec.isBidRequestValid(newBid)).to.equal(false); }); + + it('should return false when placementId param is not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'placementId': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -50,7 +60,8 @@ describe('openwebAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[640, 480]], 'params': { - 'org': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001', + 'placementId': '123' }, 'bidId': '299ffc8cca0b87', 'loop': 1, @@ -103,15 +114,13 @@ describe('openwebAdapter', function () { const bidderRequest = { bidderCode: 'openweb', } - const placementId = '12345678'; const api = [1, 2]; const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; const protocols = [2, 3, 5, 6]; it('sends the placementId to ENDPOINT via POST', function () { - bidRequests[0].params.placementId = placementId; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[0].placementId).to.equal(placementId); + expect(request.data.bids[0].placementId).to.equal('123'); }); it('sends the plcmt to ENDPOINT via POST', function () { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 25862eac83f..ad4ee1e74ce 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -10,14 +10,15 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/paapi.js'; + import {deepClone} from 'src/utils.js'; import {version} from 'package.json'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; - const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -187,9 +188,9 @@ describe('OpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); describe('and request config uses both delDomain and platform', () => { @@ -216,9 +217,9 @@ describe('OpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); describe('and request config uses mediaType', () => { @@ -241,10 +242,10 @@ describe('OpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); - delete videoBidWithMediaType.params; - videoBidWithMediaType.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + let invalidVideoBidWithMediaType = Object.assign({}, videoBidWithMediaType); + delete invalidVideoBidWithMediaType.params; + invalidVideoBidWithMediaType.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaType)).to.equal(false); }); }); }); @@ -1037,7 +1038,9 @@ describe('OpenxRtbAdapter', function () { it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } }); expect(request[0].data.imp[0].ext.ae).to.equal(2); }); @@ -1503,13 +1506,13 @@ describe('OpenxRtbAdapter', function () { it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test-bid-id'); }); it('should inject ortb2Imp in auctionSignals', function () { - const auctionConfig = response.fledgeAuctionConfigs[0].config; + const auctionConfig = response.paapi[0].config; expect(auctionConfig).to.deep.include({ auctionSignals: { ortb2Imp: { diff --git a/test/spec/modules/optableBidAdapter_spec.js b/test/spec/modules/optableBidAdapter_spec.js index d7f2230328e..ef04474c270 100644 --- a/test/spec/modules/optableBidAdapter_spec.js +++ b/test/spec/modules/optableBidAdapter_spec.js @@ -78,10 +78,10 @@ describe('optableBidAdapter', function() { } }; - it('maps fledgeAuctionConfigs from ext.optable.fledge.auctionconfigs', function() { + it('maps paapi from ext.optable.fledge.auctionconfigs', function() { const request = spec.buildRequests([validBid], bidderRequest); const result = spec.interpretResponse(response, request); - expect(result.fledgeAuctionConfigs).to.deep.equal([ + expect(result.paapi).to.deep.equal([ { bidId: 'bid123', config: { seller: 'https://ads.optable.co' } } ]); }); diff --git a/test/spec/modules/paapiForGpt_spec.js b/test/spec/modules/paapiForGpt_spec.js new file mode 100644 index 00000000000..9a6637f82aa --- /dev/null +++ b/test/spec/modules/paapiForGpt_spec.js @@ -0,0 +1,216 @@ +import { + getPAAPISizeHook, + onAuctionConfigFactory, + setPAAPIConfigFactory, setTargetingHookFactory, + slotConfigurator +} from 'modules/paapiForGpt.js'; +import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import {deepSetValue} from '../../../src/utils.js'; +import {config} from 'src/config.js'; + +describe('paapiForGpt module', () => { + let sandbox, fledgeAuctionConfig; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + fledgeAuctionConfig = { + seller: 'bidder', + mock: 'config' + }; + }); + afterEach(() => { + sandbox.restore(); + }); + + describe('slotConfigurator', () => { + let setGptConfig; + function mockGptSlot(auPath) { + return { + setConfig: sinon.stub(), + getAdUnitPath: () => auPath + } + } + beforeEach(() => { + setGptConfig = slotConfigurator(); + }); + + Object.entries({ + 'single slot': [mockGptSlot('mock/gpt/au')], + 'multiple slots': [mockGptSlot('mock/gpt/au'), mockGptSlot('mock/gpt/au2')] + }).forEach(([t, gptSlots]) => { + describe(`when ad unit code matches ${t}`, () => { + it('should set GPT slot config', () => { + setGptConfig('au', gptSlots, [fledgeAuctionConfig]); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 'bidder', + auctionConfig: fledgeAuctionConfig, + }] + }); + }) + }); + describe('when reset = true', () => { + it('should reset GPT slot config', () => { + setGptConfig('au', gptSlots, [fledgeAuctionConfig]); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au', gptSlots, [], true); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 'bidder', + auctionConfig: null + }] + }); + }) + }); + + it('should reset only sellers with no fresh config', () => { + setGptConfig('au', gptSlots, [{seller: 's1'}, {seller: 's2'}]); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au', gptSlots, [{seller: 's1'}], true); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 's1', + auctionConfig: {seller: 's1'} + }, { + configKey: 's2', + auctionConfig: null + }] + }) + }) + }); + + it('should not reset sellers that were already reset', () => { + setGptConfig('au', gptSlots, [{seller: 's1'}]); + setGptConfig('au', gptSlots, [], true); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au', gptSlots, [], true); + gptSlots.forEach(slot => sinon.assert.notCalled(slot.setConfig)); + }) + + it('should keep track of configuration history by ad unit', () => { + setGptConfig('au1', gptSlots, [{seller: 's1'}]); + setGptConfig('au1', gptSlots, [{seller: 's2'}], false); + setGptConfig('au2', gptSlots, [{seller: 's3'}]); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au1', gptSlots, [], true); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 's1', + auctionConfig: null + }, { + configKey: 's2', + auctionConfig: null + }] + }); + }) + }) + }); + }) + }) + }); + describe('setTargeting hook', () => { + let setPaapiConfig, setTargetingHook, next; + beforeEach(() => { + setPaapiConfig = sinon.stub() + setTargetingHook = setTargetingHookFactory(setPaapiConfig); + next = sinon.stub(); + }); + function expectFilters(...filters) { + expect(setPaapiConfig.args.length).to.eql(filters.length) + filters.forEach(filter => { + sinon.assert.calledWith(setPaapiConfig, filter, 'mock-matcher') + }) + } + function runHook(adUnit) { + setTargetingHook(next, adUnit, 'mock-matcher'); + sinon.assert.calledWith(next, adUnit, 'mock-matcher'); + } + it('should invoke with no filters when adUnit is undef', () => { + runHook(); + expectFilters(undefined); + }); + it('should invoke once when adUnit is a string', () => { + runHook('mock-au'); + expectFilters({adUnitCode: 'mock-au'}) + }); + it('should invoke once per ad unit when an array', () => { + runHook(['au1', 'au2']); + expectFilters({adUnitCode: 'au1'}, {adUnitCode: 'au2'}); + }) + }) + describe('setPAAPIConfigForGpt', () => { + let getPAAPIConfig, setGptConfig, getSlots, setPAAPIConfigForGPT; + beforeEach(() => { + getPAAPIConfig = sinon.stub(); + setGptConfig = sinon.stub(); + getSlots = sinon.stub().callsFake((codes) => Object.fromEntries(codes.map(code => [code, ['mock-slot']]))) + setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig, getSlots); + }); + + Object.entries({ + missing: null, + empty: {} + }).forEach(([t, configs]) => { + it(`does not set GPT slot config when config is ${t}`, () => { + getPAAPIConfig.returns(configs); + setPAAPIConfigForGPT('mock-filters'); + sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); + sinon.assert.notCalled(setGptConfig); + }) + }); + + it('passes customSlotMatching to getSlots', () => { + getPAAPIConfig.returns({au1: {}}); + setPAAPIConfigForGPT('mock-filters', 'mock-custom-matching'); + sinon.assert.calledWith(getSlots, ['au1'], 'mock-custom-matching'); + }) + + it('sets GPT slot config for each ad unit that has PAAPI config, and resets the rest', () => { + const cfg = { + au1: { + componentAuctions: [{seller: 's1'}, {seller: 's2'}] + }, + au2: { + componentAuctions: [{seller: 's3'}] + }, + au3: null + } + getPAAPIConfig.returns(cfg); + setPAAPIConfigForGPT('mock-filters'); + sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); + Object.entries(cfg).forEach(([au, config]) => { + sinon.assert.calledWith(setGptConfig, au, ['mock-slot'], config?.componentAuctions ?? [], true); + }) + }); + }); + + describe('getPAAPISizeHook', () => { + let next; + beforeEach(() => { + next = sinon.stub(); + next.bail = sinon.stub(); + }); + + it('should pick largest supported size over larger unsupported size', () => { + getPAAPISizeHook(next, [[999, 999], [300, 250], [300, 600], [1234, 4321]]); + sinon.assert.calledWith(next.bail, [300, 600]); + }); + + Object.entries({ + 'present': [], + 'supported': [[123, 4], [321, 5]], + 'defined': undefined, + }).forEach(([t, sizes]) => { + it(`should defer to next when no size is ${t}`, () => { + getPAAPISizeHook(next, sizes); + sinon.assert.calledWith(next, sizes); + }) + }) + }) +}); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 768e2ba8853..7814c09ea61 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -40,824 +40,804 @@ describe('paapi module', () => { reset(); }); - [ - 'fledgeForGpt', - 'paapi' - ].forEach(configNS => { - describe(`using ${configNS} for configuration`, () => { - let getPAAPISizeStub; - - function getPAAPISizeHook(next, sizes) { - next.bail(getPAAPISizeStub(sizes)); - } + describe(`using paapi configuration`, () => { + let getPAAPISizeStub; - before(() => { - getPAAPISize.before(getPAAPISizeHook, 100); - }); + function getPAAPISizeHook(next, sizes) { + next.bail(getPAAPISizeStub(sizes)); + } - after(() => { - getPAAPISize.getHooks({hook: getPAAPISizeHook}).remove(); - }); + before(() => { + getPAAPISize.before(getPAAPISizeHook, 100); + }); + after(() => { + getPAAPISize.getHooks({hook: getPAAPISizeHook}).remove(); + }); + + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + }); + + describe('getPAAPIConfig', function () { + let nextFnSpy, auctionConfig, paapiConfig; + before(() => { + config.setConfig({paapi: {enabled: true}}); + }); beforeEach(() => { - getPAAPISizeStub = sinon.stub(); + auctionConfig = { + seller: 'bidder', + mock: 'config' + }; + paapiConfig = { + config: auctionConfig + }; + nextFnSpy = sinon.spy(); }); - describe('getPAAPIConfig', function () { - let nextFnSpy, auctionConfig, paapiConfig; - before(() => { - config.setConfig({[configNS]: {enabled: true}}); + describe('on a single auction', function () { + const auctionId = 'aid'; + beforeEach(function () { + sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); }); - beforeEach(() => { - auctionConfig = { - seller: 'bidder', - mock: 'config' - }; - paapiConfig = { - config: auctionConfig - }; - nextFnSpy = sinon.spy(); + + it('should call next()', function () { + const request = {auctionId, adUnitCode: 'auc'}; + addPaapiConfigHook(nextFnSpy, request, paapiConfig); + sinon.assert.calledWith(nextFnSpy, request, paapiConfig); }); - describe('on a single auction', function () { - const auctionId = 'aid'; - beforeEach(function () { - sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); + describe('igb', () => { + let igb1, igb2, buyerAuctionConfig; + beforeEach(() => { + igb1 = { + origin: 'buyer1' + }; + igb2 = { + origin: 'buyer2' + }; + buyerAuctionConfig = { + seller: 'seller', + decisionLogicURL: 'seller-decision-logic' + }; + config.mergeConfig({ + paapi: { + componentSeller: { + auctionConfig: buyerAuctionConfig + } + } + }); }); - it('should call next()', function () { - const request = {auctionId, adUnitCode: 'auc'}; - addPaapiConfigHook(nextFnSpy, request, paapiConfig); - sinon.assert.calledWith(nextFnSpy, request, paapiConfig); + function addIgb(request, igb) { + addPaapiConfigHook(nextFnSpy, Object.assign({auctionId}, request), {igb}); + } + + it('should be collected into an auction config', () => { + addIgb({adUnitCode: 'au1'}, igb1); + addIgb({adUnitCode: 'au1'}, igb2); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + const buyerConfig = getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + sinon.assert.match(buyerConfig, { + interestGroupBuyers: [igb1.origin, igb2.origin], + ...buyerAuctionConfig + }); }); - describe('igb', () => { - let igb1, igb2, buyerAuctionConfig; + describe('FPD', () => { + let ortb2, ortb2Imp; beforeEach(() => { - igb1 = { - origin: 'buyer1' - }; - igb2 = { - origin: 'buyer2' - }; - buyerAuctionConfig = { - seller: 'seller', - decisionLogicURL: 'seller-decision-logic' - }; - config.mergeConfig({ - [configNS]: { - componentSeller: { - auctionConfig: buyerAuctionConfig - } - } - }); + ortb2 = {'fpd': 1}; + ortb2Imp = {'fpd': 2}; }); - function addIgb(request, igb) { - addPaapiConfigHook(nextFnSpy, Object.assign({auctionId}, request), {igb}); + function getBuyerAuctionConfig() { + addIgb({adUnitCode: 'au1', ortb2, ortb2Imp}, igb1); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; } - it('should be collected into an auction config', () => { - addIgb({adUnitCode: 'au1'}, igb1); - addIgb({adUnitCode: 'au1'}, igb2); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - const buyerConfig = getPAAPIConfig({auctionId}).au1.componentAuctions[0]; - sinon.assert.match(buyerConfig, { - interestGroupBuyers: [igb1.origin, igb2.origin], - ...buyerAuctionConfig + it('should be added to auction config', () => { + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: { + ortb2, + ortb2Imp + } }); }); - describe('FPD', () => { - let ortb2, ortb2Imp; - beforeEach(() => { - ortb2 = {'fpd': 1}; - ortb2Imp = {'fpd': 2}; + it('should not override existing perBuyerSignals', () => { + const original = { + ortb2: { + fpd: 'original' + } + }; + igb1.pbs = { + prebid: deepClone(original) + }; + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: original }); + }); + }); + }); - function getBuyerAuctionConfig() { - addIgb({adUnitCode: 'au1', ortb2, ortb2Imp}, igb1); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; - } - - it('should be added to auction config', () => { - sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { - prebid: { - ortb2, - ortb2Imp - } - }); - }); + describe('should collect auction configs', () => { + let cf1, cf2; + beforeEach(() => { + cf1 = {...auctionConfig, id: 1, seller: 'b1'}; + cf2 = {...auctionConfig, id: 2, seller: 'b2'}; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, {config: cf1}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, {config: cf2}); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + }); - it('should not override existing perBuyerSignals', () => { - const original = { - ortb2: { - fpd: 'original' - } - }; - igb1.pbs = { - prebid: deepClone(original) - }; - sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { - prebid: original - }); - }); + it('and make them available at end of auction', () => { + sinon.assert.match(getPAAPIConfig({auctionId}), { + au1: { + componentAuctions: [cf1] + }, + au2: { + componentAuctions: [cf2] + } }); }); - describe('should collect auction configs', () => { - let cf1, cf2; - beforeEach(() => { - cf1 = {...auctionConfig, id: 1, seller: 'b1'}; - cf2 = {...auctionConfig, id: 2, seller: 'b2'}; - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, {config: cf1}); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, {config: cf2}); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + it('and filter them by ad unit', () => { + const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); + expect(Object.keys(cfg)).to.have.members(['au1']); + sinon.assert.match(cfg.au1, { + componentAuctions: [cf1] }); + }); - it('and make them available at end of auction', () => { - sinon.assert.match(getPAAPIConfig({auctionId}), { - au1: { - componentAuctions: [cf1] - }, - au2: { - componentAuctions: [cf2] - } - }); - }); + it('and not return them again', () => { + getPAAPIConfig(); + const cfg = getPAAPIConfig(); + expect(cfg).to.eql({}); + }); - it('and filter them by ad unit', () => { - const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); - expect(Object.keys(cfg)).to.have.members(['au1']); - sinon.assert.match(cfg.au1, { - componentAuctions: [cf1] + describe('includeBlanks = true', () => { + it('includes all ad units', () => { + const cfg = getPAAPIConfig({}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); + }); + it('includes the targeted adUnit', () => { + expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ + au3: null }); }); - - it('and not return them again', () => { - getPAAPIConfig(); - const cfg = getPAAPIConfig(); - expect(cfg).to.eql({}); + it('includes the targeted auction', () => { + const cfg = getPAAPIConfig({auctionId}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); }); - - describe('includeBlanks = true', () => { - it('includes all ad units', () => { - const cfg = getPAAPIConfig({}, true); - expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); - expect(cfg.au3).to.eql(null); - }); - it('includes the targeted adUnit', () => { - expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ - au3: null - }); - }); - it('includes the targeted auction', () => { - const cfg = getPAAPIConfig({auctionId}, true); - expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); - expect(cfg.au3).to.eql(null); - }); - it('does not include non-existing ad units', () => { - expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); - }); - it('does not include non-existing auctions', () => { - expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); - }); + it('does not include non-existing ad units', () => { + expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); + }); + it('does not include non-existing auctions', () => { + expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); }); }); + }); - it('should drop auction configs after end of auction', () => { - events.emit(EVENTS.AUCTION_END, {auctionId}); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId}); - expect(getPAAPIConfig({auctionId})).to.eql({}); - }); + it('should drop auction configs after end of auction', () => { + events.emit(EVENTS.AUCTION_END, {auctionId}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId}); + expect(getPAAPIConfig({auctionId})).to.eql({}); + }); - describe('FPD', () => { - let ortb2, ortb2Imp; - beforeEach(() => { - ortb2 = {fpd: 1}; - ortb2Imp = {fpd: 2}; - }); + describe('FPD', () => { + let ortb2, ortb2Imp; + beforeEach(() => { + ortb2 = {fpd: 1}; + ortb2Imp = {fpd: 2}; + }); - function getComponentAuctionConfig() { - addPaapiConfigHook(nextFnSpy, { - auctionId, - adUnitCode: 'au1', - ortb2: {fpd: 1}, - ortb2Imp: {fpd: 2} - }, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId}); - return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; - } + function getComponentAuctionConfig() { + addPaapiConfigHook(nextFnSpy, { + auctionId, + adUnitCode: 'au1', + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + }, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } - it('should be added to auctionSignals', () => { - sinon.assert.match(getComponentAuctionConfig().auctionSignals, { - prebid: {ortb2, ortb2Imp} - }); + it('should be added to auctionSignals', () => { + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: {ortb2, ortb2Imp} }); - it('should not override existing auctionSignals', () => { - auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}}; - sinon.assert.match(getComponentAuctionConfig().auctionSignals, { - prebid: { - ortb2: {fpd: 'original'}, - ortb2Imp - } - }); + }); + it('should not override existing auctionSignals', () => { + auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}}; + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: { + ortb2: {fpd: 'original'}, + ortb2Imp + } }); + }); - it('should be added to perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; - const pbs = getComponentAuctionConfig().perBuyerSignals; - sinon.assert.match(pbs, { - buyer1: {prebid: {ortb2, ortb2Imp}}, - buyer2: {prebid: {ortb2, ortb2Imp}} - }); + it('should be added to perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; + const pbs = getComponentAuctionConfig().perBuyerSignals; + sinon.assert.match(pbs, { + buyer1: {prebid: {ortb2, ortb2Imp}}, + buyer2: {prebid: {ortb2, ortb2Imp}} }); + }); - it('should not override existing perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer']; - const original = { - prebid: { - ortb2: { - fpd: 'original' - } + it('should not override existing perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer']; + const original = { + prebid: { + ortb2: { + fpd: 'original' } - }; - auctionConfig.perBuyerSignals = { - buyer: deepClone(original) - }; - sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); - }); + } + }; + auctionConfig.perBuyerSignals = { + buyer: deepClone(original) + }; + sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); }); + }); - describe('submodules', () => { - let submods; - beforeEach(() => { - submods = [1, 2].map(i => ({ - name: `test${i}`, - onAuctionConfig: sinon.stub() - })); - submods.forEach(registerSubmodule); - }); + describe('submodules', () => { + let submods; + beforeEach(() => { + submods = [1, 2].map(i => ({ + name: `test${i}`, + onAuctionConfig: sinon.stub() + })); + submods.forEach(registerSubmodule); + }); - describe('onAuctionConfig', () => { - const auctionId = 'aid'; - it('is invoked with null configs when there\'s no config', () => { - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); - submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); - }); - it('is invoked with relevant configs', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); - submods.forEach(submod => { - sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { - au1: {componentAuctions: [auctionConfig]}, - au2: {componentAuctions: [auctionConfig]}, - au3: null - }); - }); - }); - it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { - submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { - markAsUsed('au1'); + describe('onAuctionConfig', () => { + const auctionId = 'aid'; + it('is invoked with null configs when there\'s no config', () => { + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); + submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); + }); + it('is invoked with relevant configs', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + submods.forEach(submod => { + sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { + au1: {componentAuctions: [auctionConfig]}, + au2: {componentAuctions: [auctionConfig]}, + au3: null }); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - expect(getPAAPIConfig()).to.eql({}); }); - it('keeps them available if they do not', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - expect(getPAAPIConfig()).to.not.be.empty; + }); + it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { + submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { + markAsUsed('au1'); }); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.eql({}); + }); + it('keeps them available if they do not', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.not.be.empty; }); }); + }); - describe('floor signal', () => { - before(() => { - if (!getGlobal().convertCurrency) { - getGlobal().convertCurrency = () => null; - getGlobal().convertCurrency.mock = true; - } - }); - after(() => { - if (getGlobal().convertCurrency.mock) { - delete getGlobal().convertCurrency; - } - }); + describe('floor signal', () => { + before(() => { + if (!getGlobal().convertCurrency) { + getGlobal().convertCurrency = () => null; + getGlobal().convertCurrency.mock = true; + } + }); + after(() => { + if (getGlobal().convertCurrency.mock) { + delete getGlobal().convertCurrency; + } + }); - beforeEach(() => { - sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { - if (from === to) return amount; - if (from === 'USD' && to === 'JPY') return amount * 100; - if (from === 'JPY' && to === 'USD') return amount / 100; - throw new Error('unexpected currency conversion'); - }); + beforeEach(() => { + sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { + if (from === to) return amount; + if (from === 'USD' && to === 'JPY') return amount * 100; + if (from === 'JPY' && to === 'USD') return amount / 100; + throw new Error('unexpected currency conversion'); }); + }); - Object.entries({ - 'bids': (payload, values) => { - payload.bidsReceived = values - .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) - .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); - }, - 'no bids': (payload, values) => { - payload.bidderRequests = values - .map((val) => ({ - bids: [{ - adUnitCode: 'au', - getFloor: () => ({floor: val.amount, currency: val.cur}) - }] - })) - .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); - } - }).forEach(([tcase, setup]) => { - describe(`when auction has ${tcase}`, () => { - Object.entries({ - 'no currencies': { - values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], - 'bids': { - bidfloor: 100, - bidfloorcur: undefined - }, - 'no bids': { - bidfloor: 1, - bidfloorcur: undefined, - } + Object.entries({ + 'bids': (payload, values) => { + payload.bidsReceived = values + .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) + .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); + }, + 'no bids': (payload, values) => { + payload.bidderRequests = values + .map((val) => ({ + bids: [{ + adUnitCode: 'au', + getFloor: () => ({floor: val.amount, currency: val.cur}) + }] + })) + .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); + } + }).forEach(([tcase, setup]) => { + describe(`when auction has ${tcase}`, () => { + Object.entries({ + 'no currencies': { + values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], + 'bids': { + bidfloor: 100, + bidfloorcur: undefined }, - 'only zero values': { - values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], - 'bids': { - bidfloor: undefined, - bidfloorcur: undefined, - }, - 'no bids': { - bidfloor: undefined, - bidfloorcur: undefined, - } + 'no bids': { + bidfloor: 1, + bidfloorcur: undefined, + } + }, + 'only zero values': { + values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], + 'bids': { + bidfloor: undefined, + bidfloorcur: undefined, }, - 'matching currencies': { - values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], - 'bids': { - bidfloor: 100, - bidfloorcur: 'JPY', - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } + 'no bids': { + bidfloor: undefined, + bidfloorcur: undefined, + } + }, + 'matching currencies': { + values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], + 'bids': { + bidfloor: 100, + bidfloorcur: 'JPY', }, - 'mixed currencies': { - values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], - 'bids': { - bidfloor: 10, - bidfloorcur: 'USD' - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + }, + 'mixed currencies': { + values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], + 'bids': { + bidfloor: 10, + bidfloorcur: 'USD' + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', } - }).forEach(([t, testConfig]) => { - const values = testConfig.values; - const {bidfloor, bidfloorcur} = testConfig[tcase]; - - describe(`with ${t}`, () => { - let payload; - beforeEach(() => { - payload = {auctionId}; - setup(payload, values); - }); - - it('should populate bidfloor/bidfloorcur', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, payload); - const cfg = getPAAPIConfig({auctionId}).au; - const signals = cfg.auctionSignals; - sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); - expect(signals?.prebid?.bidfloor).to.eql(bidfloor); - expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); - }); + } + }).forEach(([t, testConfig]) => { + const values = testConfig.values; + const {bidfloor, bidfloorcur} = testConfig[tcase]; + + describe(`with ${t}`, () => { + let payload; + beforeEach(() => { + payload = {auctionId}; + setup(payload, values); + }); + + it('should populate bidfloor/bidfloorcur', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, payload); + const cfg = getPAAPIConfig({auctionId}).au; + const signals = cfg.auctionSignals; + sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); + expect(signals?.prebid?.bidfloor).to.eql(bidfloor); + expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); }); }); }); }); }); + }); - describe('requestedSize', () => { - let adUnit; - beforeEach(() => { - adUnit = { - code: 'au', - }; - }); + describe('requestedSize', () => { + let adUnit; + beforeEach(() => { + adUnit = { + code: 'au', + }; + }); - function getConfig() { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: adUnit.code}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: [adUnit.code], adUnits: [adUnit]}); - return getPAAPIConfig()[adUnit.code]; - } + function getConfig() { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: adUnit.code}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: [adUnit.code], adUnits: [adUnit]}); + return getPAAPIConfig()[adUnit.code]; + } - Object.entries({ - 'adUnit.ortb2Imp.ext.paapi.requestedSize'() { - adUnit.ortb2Imp = { - ext: { - paapi: { - requestedSize: { - width: 123, - height: 321 - } + Object.entries({ + 'adUnit.ortb2Imp.ext.paapi.requestedSize'() { + adUnit.ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 } } - }; - }, - 'largest size'() { - getPAAPISizeStub.returns([123, 321]); - } - }).forEach(([t, setup]) => { - describe(`should be set from ${t}`, () => { - beforeEach(setup); - - it('without overriding component auctions, if set', () => { - auctionConfig.requestedSize = {width: '1px', height: '2px'}; - expect(getConfig().componentAuctions[0].requestedSize).to.eql({ - width: '1px', - height: '2px' - }); + } + }; + }, + 'largest size'() { + getPAAPISizeStub.returns([123, 321]); + } + }).forEach(([t, setup]) => { + describe(`should be set from ${t}`, () => { + beforeEach(setup); + + it('without overriding component auctions, if set', () => { + auctionConfig.requestedSize = {width: '1px', height: '2px'}; + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: '1px', + height: '2px' }); + }); - it('on component auction, if missing', () => { - expect(getConfig().componentAuctions[0].requestedSize).to.eql({ - width: 123, - height: 321 - }); + it('on component auction, if missing', () => { + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: 123, + height: 321 }); + }); - it('on top level auction', () => { - expect(getConfig().requestedSize).to.eql({ - width: 123, - height: 321, - }); + it('on top level auction', () => { + expect(getConfig().requestedSize).to.eql({ + width: 123, + height: 321, }); }); }); }); }); + }); - describe('with multiple auctions', () => { - const AUCTION1 = 'auction1'; - const AUCTION2 = 'auction2'; + describe('with multiple auctions', () => { + const AUCTION1 = 'auction1'; + const AUCTION2 = 'auction2'; - function mockAuction(auctionId) { - return { - getAuctionId() { - return auctionId; - } - }; - } + function mockAuction(auctionId) { + return { + getAuctionId() { + return auctionId; + } + }; + } - function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { - expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); - Object.entries(actualConfig).forEach(([au, cfg]) => { - cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); - }); - } + function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { + expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); + Object.entries(actualConfig).forEach(([au, cfg]) => { + cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); + }); + } - let configs; - beforeEach(() => { - const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; - sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); - configs = {[AUCTION1]: {}, [AUCTION2]: {}}; - Object.entries({ - [AUCTION1]: [['au1', 'au2'], ['missing-1']], - [AUCTION2]: [['au2', 'au3'], []], - }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { - adUnitCodes.forEach(adUnitCode => { - const cfg = {...auctionConfig, auctionId, adUnitCode}; - configs[auctionId][adUnitCode] = cfg; - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); - }); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); + let configs; + beforeEach(() => { + const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; + sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); + configs = {[AUCTION1]: {}, [AUCTION2]: {}}; + Object.entries({ + [AUCTION1]: [['au1', 'au2'], ['missing-1']], + [AUCTION2]: [['au2', 'au3'], []], + }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { + adUnitCodes.forEach(adUnitCode => { + const cfg = {...auctionConfig, auctionId, adUnitCode}; + configs[auctionId][adUnitCode] = cfg; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); }); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); }); + }); - it('should filter by auction', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); - }); + it('should filter by auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); + }); - it('should filter by auction and ad unit', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); - }); + it('should filter by auction and ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); + }); - it('should use last auction for each ad unit', () => { - expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); - }); + it('should use last auction for each ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); + }); - it('should filter by ad unit and use latest auction', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); - }); + it('should filter by ad unit and use latest auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); + }); - it('should keep track of which configs were returned', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); - expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); - expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); - }); + it('should keep track of which configs were returned', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); + expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); + }); - describe('includeBlanks = true', () => { - Object.entries({ - 'auction with blanks': { - filters: {auctionId: AUCTION1}, - expected: {au1: true, au2: true, 'missing-1': false} - }, - 'blank adUnit in an auction': { - filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, - expected: {'missing-1': false} - }, - 'non-existing auction': { - filters: {auctionId: 'other'}, - expected: {} - }, - 'non-existing adUnit in an auction': { - filters: {auctionId: AUCTION2, adUnitCode: 'other'}, - expected: {} - }, - 'non-existing ad unit': { - filters: {adUnitCode: 'other'}, - expected: {}, - }, - 'non existing ad unit in a non-existing auction': { - filters: {adUnitCode: 'other', auctionId: 'other'}, - expected: {} - }, - 'all ad units': { - filters: {}, - expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} - } - }).forEach(([t, {filters, expected}]) => { - it(t, () => { - const cfg = getPAAPIConfig(filters, true); - expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); - Object.entries(expected).forEach(([au, shouldBeFilled]) => { - if (shouldBeFilled) { - expect(cfg[au]).to.not.be.null; - } else { - expect(cfg[au]).to.be.null; - } - }); + describe('includeBlanks = true', () => { + Object.entries({ + 'auction with blanks': { + filters: {auctionId: AUCTION1}, + expected: {au1: true, au2: true, 'missing-1': false} + }, + 'blank adUnit in an auction': { + filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, + expected: {'missing-1': false} + }, + 'non-existing auction': { + filters: {auctionId: 'other'}, + expected: {} + }, + 'non-existing adUnit in an auction': { + filters: {auctionId: AUCTION2, adUnitCode: 'other'}, + expected: {} + }, + 'non-existing ad unit': { + filters: {adUnitCode: 'other'}, + expected: {}, + }, + 'non existing ad unit in a non-existing auction': { + filters: {adUnitCode: 'other', auctionId: 'other'}, + expected: {} + }, + 'all ad units': { + filters: {}, + expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} + } + }).forEach(([t, {filters, expected}]) => { + it(t, () => { + const cfg = getPAAPIConfig(filters, true); + expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); + Object.entries(expected).forEach(([au, shouldBeFilled]) => { + if (shouldBeFilled) { + expect(cfg[au]).to.not.be.null; + } else { + expect(cfg[au]).to.be.null; + } }); }); }); }); }); + }); - describe('markForFledge', function () { - const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); - let adUnits; + describe('markForFledge', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + let adUnits; - before(function () { - // navigator.runAdAuction & co may not exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - Object.keys(navProps).forEach(p => { - navigator[p] = sinon.stub(); - }); - hook.ready(); - config.resetConfig(); + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { + navigator[p] = sinon.stub(); }); + hook.ready(); + config.resetConfig(); + }); - after(function () { - Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); - }); + after(function () { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }); - beforeEach(() => { - getPAAPISizeStub = sinon.stub(); - adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - }); + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + }); - afterEach(function () { - config.resetConfig(); - }); + afterEach(function () { + config.resetConfig(); + }); - function mark() { - return Object.fromEntries( - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ).map(b => [b.bidderCode, b]) - ); - } + function mark() { + return Object.fromEntries( + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ).map(b => [b.bidderCode, b]) + ); + } - function expectFledgeFlags(...enableFlags) { - const bidRequests = mark(); - expect(bidRequests.appnexus.fledgeEnabled).to.eql(enableFlags[0].enabled); - expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); - bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); - - expect(bidRequests.rubicon.fledgeEnabled).to.eql(enableFlags[1].enabled); - expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); - bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); - - Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { - if (bid.ortb2Imp?.ext?.ae) { - sinon.assert.match(bid.ortb2Imp.ext.igs, { - ae: bid.ortb2Imp.ext.ae, - biddable: 1 - }); - } - }); - } + function expectFledgeFlags(...enableFlags) { + const bidRequests = mark(); + expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); + bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); - describe('with setBidderConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - defaultForSlots: 1, - fledgeEnabled: true - } + expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); + bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); + + Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { + if (bid.ortb2Imp?.ext?.ae) { + sinon.assert.match(bid.ortb2Imp.ext.igs, { + ae: bid.ortb2Imp.ext.ae, + biddable: 1 }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); + } + }); + } + + describe('with setConfig()', () => { + it('should set paapi.enabled correctly per bidder', function () { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + bidders: ['appnexus'], + defaultForSlots: 1, + } }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); }); - describe('with setConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true, - bidders: ['appnexus'], - defaultForSlots: 1, - } - }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); + it('should set paapi.enabled correctly for all bidders', function () { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + defaultForSlots: 1, + } }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + }); - it('should set fledgeEnabled correctly for all bidders', function () { + Object.entries({ + 'not set': { + cfg: {}, + componentSeller: false + }, + 'set': { + cfg: { + componentSeller: { + auctionConfig: { + decisionLogicURL: 'publisher.example' + } + } + }, + componentSeller: true + } + }).forEach(([t, {cfg, componentSeller}]) => { + it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { + paapi: { enabled: true, defaultForSlots: 1, + ...cfg } }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); }); + }); - Object.entries({ - 'not set': { - cfg: {}, - componentSeller: false - }, - 'set': { - cfg: { - componentSeller: { - auctionConfig: { - decisionLogicURL: 'publisher.example' - } - } - }, - componentSeller: true + it('should not override pub-defined ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + defaultForSlots: 1, } - }).forEach(([t, {cfg, componentSeller}]) => { - it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { - config.setConfig({ - [configNS]: { - enabled: true, - defaultForSlots: 1, - ...cfg - } - }); - Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); - }); }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); + expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + }); - it('should not override pub-defined ext.ae', () => { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true, - defaultForSlots: 1, - } - }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); - expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + it('should populate ext.igs when request has ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true + } }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); + expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); + }); - it('should populate ext.igs when request has ext.ae', () => { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true - } - }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); - expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); + it('should not override pub-defined ext.igs', () => { + config.setConfig({ + paapi: { + enabled: true + } }); - - it('should not override pub-defined ext.igs', () => { - config.setConfig({ - [configNS]: { - enabled: true - } - }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); - const bidReqs = mark(); - Object.values(bidReqs).flatMap(req => req.bids).forEach(bid => { - sinon.assert.match(bid.ortb2Imp.ext, { + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); + const bidReqs = mark(); + Object.values(bidReqs).flatMap(req => req.bids).forEach(bid => { + sinon.assert.match(bid.ortb2Imp.ext, { + ae: 1, + igs: { ae: 1, - igs: { - ae: 1, - biddable: 0 - } - }); + biddable: 0 + } }); }); + }); - it('should fill ext.ae from ext.igs, if defined', () => { - config.setConfig({ - [configNS]: { - enabled: true - } - }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + it('should fill ext.ae from ext.igs, if defined', () => { + config.setConfig({ + paapi: { + enabled: true + } }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); }); + }); - describe('ortb2Imp.ext.paapi.requestedSize', () => { - beforeEach(() => { - config.setConfig({ - [configNS]: { - enabled: true, - defaultForSlots: 1, - } - }); + describe('ortb2Imp.ext.paapi.requestedSize', () => { + beforeEach(() => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + } }); + }); - it('should default to value returned by getPAAPISize', () => { - getPAAPISizeStub.returns([123, 321]); - Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { - sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { - requestedSize: { - width: 123, - height: 321 - } - }); + it('should default to value returned by getPAAPISize', () => { + getPAAPISizeStub.returns([123, 321]); + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + requestedSize: { + width: 123, + height: 321 + } }); }); + }); - it('should not be overridden, if provided by the pub', () => { - adUnits[0].ortb2Imp = { - ext: { - paapi: { - requestedSize: { - width: '123px', - height: '321px' - } - } - } - }; - Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { - sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + it('should not be overridden, if provided by the pub', () => { + adUnits[0].ortb2Imp = { + ext: { + paapi: { requestedSize: { width: '123px', height: '321px' } - }); + } + } + }; + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + requestedSize: { + width: '123px', + height: '321px' + } }); - sinon.assert.notCalled(getPAAPISizeStub); }); + sinon.assert.notCalled(getPAAPISizeStub); + }); - it('should not be set if adUnit has no banner sizes', () => { - adUnits[0].mediaTypes = { - video: {} - }; - Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { - expect(bidRequest.ortb2Imp?.ext?.paapi?.requestedSize).to.not.exist; - }); + it('should not be set if adUnit has no banner sizes', () => { + adUnits[0].mediaTypes = { + video: {} + }; + Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { + expect(bidRequest.ortb2Imp?.ext?.paapi?.requestedSize).to.not.exist; }); }); }); @@ -1093,7 +1073,7 @@ describe('paapi module', () => { }); it('imp.ext.ae should be left intact if fledge is enabled', () => { const imp = {ext: {ae: 2, igs: {biddable: 0}}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + setImpExtAe(imp, {}, {bidderRequest: {paapi: {enabled: true}}}); expect(imp.ext).to.eql({ ae: 2, igs: { diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js deleted file mode 100644 index 6886fa827c0..00000000000 --- a/test/spec/modules/parrableIdSystem_spec.js +++ /dev/null @@ -1,784 +0,0 @@ -import { expect } from 'chai'; -import {find} from 'src/polyfill.js'; -import { config } from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { newStorageManager } from 'src/storageManager.js'; -import { getRefererInfo } from 'src/refererDetection.js'; -import { uspDataHandler } from 'src/adapterManager.js'; -import {attachIdSystem, init, requestBidsHook, setSubmoduleRegistry} from 'modules/userId/index.js'; -import { parrableIdSubmodule } from 'modules/parrableIdSystem.js'; -import { server } from 'test/mocks/xhr.js'; -import {mockGdprConsent} from '../../helpers/consentData.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; -import 'src/prebid.js'; -import {merkleIdSubmodule} from '../../../modules/merkleIdSystem.js'; - -const storage = newStorageManager(); - -const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; -const EXPIRE_COOKIE_TIME = 864000000; -const P_COOKIE_NAME = '_parrable_id'; -const P_COOKIE_EID = '01.1563917337.test-eid'; -const P_XHR_EID = '01.1588030911.test-new-eid' -const P_CONFIG_MOCK = { - name: 'parrableId', - params: { - partners: 'parrable_test_partner_123,parrable_test_partner_456' - } -}; -const RESPONSE_HEADERS = { 'Content-Type': 'application/json' }; - -function getConfigMock() { - return { - userSync: { - syncDelay: 0, - userIds: [P_CONFIG_MOCK] - } - } -} - -function getAdUnitMock(code = 'adUnit-code') { - return { - code, - mediaTypes: {banner: {}, native: {}}, - sizes: [ - [300, 200], - [300, 600] - ], - bids: [{ - bidder: 'sampleBidder', - params: { placementId: 'banner-only-bidder' } - }] - }; -} - -function serializeParrableId(parrableId) { - let str = ''; - if (parrableId.eid) { - str += 'eid:' + parrableId.eid; - } - if (parrableId.ibaOptout) { - str += ',ibaOptout:1'; - } - if (parrableId.ccpaOptout) { - str += ',ccpaOptout:1'; - } - if (parrableId.tpc !== undefined) { - const tpcSupportComponent = parrableId.tpc === true ? 'tpc:1' : 'tpc:0'; - str += `,${tpcSupportComponent}`; - str += `,tpcUntil:${parrableId.tpcUntil}`; - } - if (parrableId.filteredUntil) { - str += `,filteredUntil:${parrableId.filteredUntil}`; - str += `,filterHits:${parrableId.filterHits}`; - } - return str; -} - -function writeParrableCookie(parrableId) { - let cookieValue = encodeURIComponent(serializeParrableId(parrableId)); - storage.setCookie( - P_COOKIE_NAME, - cookieValue, - (new Date(Date.now() + EXPIRE_COOKIE_TIME).toUTCString()), - 'lax' - ); -} - -function removeParrableCookie() { - storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); -} - -function decodeBase64UrlSafe(encBase64) { - const DEC = { - '-': '+', - '_': '/', - '.': '=' - }; - return encBase64.replace(/[-_.]/g, (m) => DEC[m]); -} - -describe('Parrable ID System', function() { - after(() => { - // reset ID system to avoid delayed callbacks in other tests - config.resetConfig(); - init(config); - }); - - describe('parrableIdSystem.getId()', function() { - describe('response callback function', function() { - let logErrorStub; - let callbackSpy = sinon.spy(); - - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - callbackSpy.resetHistory(); - writeParrableCookie({ eid: P_COOKIE_EID }); - }); - - afterEach(function() { - removeParrableCookie(); - logErrorStub.restore(); - }) - - it('creates xhr to Parrable that synchronizes the ID', function() { - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - - getIdResult.callback(callbackSpy); - - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); - - expect(getIdResult.callback).to.be.a('function'); - expect(request.url).to.contain('h.parrable.com'); - - expect(queryParams).to.not.have.property('us_privacy'); - expect(data).to.deep.equal({ - eid: P_COOKIE_EID, - trackers: P_CONFIG_MOCK.params.partners.split(','), - url: getRefererInfo().page, - prebidVersion: '$prebid.version$', - isIframe: true - }); - - server.requests[0].respond(200, - { 'Content-Type': 'text/plain' }, - JSON.stringify({ eid: P_XHR_EID }) - ); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ - eid: P_XHR_EID - }); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID) - ); - }); - - it('xhr passes the uspString to Parrable', function() { - let uspString = '1YNN'; - uspDataHandler.setConsentData(uspString); - parrableIdSubmodule.getId( - P_CONFIG_MOCK, - null, - null - ).callback(callbackSpy); - uspDataHandler.setConsentData(null); - expect(server.requests[0].url).to.contain('us_privacy=' + uspString); - }); - - it('xhr base64 safely encodes url data object', function() { - const urlSafeBase64EncodedData = '-_.'; - const btoaStub = sinon.stub(window, 'btoa').returns('+/='); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - - getIdResult.callback(callbackSpy); - - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - expect(queryParams.data).to.equal(urlSafeBase64EncodedData); - btoaStub.restore(); - }); - - it('should log an error and continue to callback if ajax request errors', function () { - let callBackSpy = sinon.spy(); - let submoduleCallback = parrableIdSubmodule.getId({ params: {partners: 'prebid'} }).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url).to.contain('h.parrable.com'); - request.respond( - 503, - null, - 'Unavailable' - ); - expect(logErrorStub.calledOnce).to.be.true; - expect(callBackSpy.calledOnce).to.be.true; - }); - }); - - describe('response id', function() { - it('provides the stored Parrable values if a cookie exists', function() { - writeParrableCookie({ eid: P_COOKIE_EID }); - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - removeParrableCookie(); - - expect(getIdResult.id).to.deep.equal({ - eid: P_COOKIE_EID - }); - }); - - it('provides the stored legacy Parrable ID values if cookies exist', function() { - let oldEid = '01.111.old-eid'; - let oldEidCookieName = '_parrable_eid'; - let oldOptoutCookieName = '_parrable_optout'; - - storage.setCookie(oldEidCookieName, oldEid); - storage.setCookie(oldOptoutCookieName, 'true'); - - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - expect(getIdResult.id).to.deep.equal({ - eid: oldEid, - ibaOptout: true - }); - - // The ID system is expected to migrate old cookies to the new format - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + oldEid + ',ibaOptout:1') - ); - expect(storage.getCookie(oldEidCookieName)).to.equal(null); - expect(storage.getCookie(oldOptoutCookieName)).to.equal(null); - removeParrableCookie(); - }); - }); - - describe('GDPR consent', () => { - let callbackSpy = sinon.spy(); - - const config = { - params: { - partner: 'partner' - } - }; - - const gdprConsentTestCases = [ - { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: { gdpr: 1, gdpr_consent: 'expectedConsentString' } }, - { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, - { consentData: { gdprApplies: true, consentString: undefined }, expected: { gdpr: 1, gdpr_consent: '' } }, - { consentData: { gdprApplies: 'yes', consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, - { consentData: undefined, expected: { gdpr: 0 } } - ]; - - gdprConsentTestCases.forEach((testCase, index) => { - it(`should call user sync url with the gdprConsent - case ${index}`, () => { - parrableIdSubmodule.getId(config, testCase.consentData).callback(callbackSpy); - - if (testCase.expected.gdpr === 1) { - expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); - expect(server.requests[0].url).to.contain('gdpr_consent=' + testCase.expected.gdpr_consent); - } else { - expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); - expect(server.requests[0].url).to.not.contain('gdpr_consent'); - } - }) - }); - }); - - describe('third party cookie support', function () { - let logErrorStub; - let callbackSpy = sinon.spy(); - - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - callbackSpy.resetHistory(); - removeParrableCookie(); - }); - - afterEach(function() { - logErrorStub.restore(); - }); - - describe('when getting tpcSupport from XHR response', function () { - let request; - let dateNowStub; - const dateNowMock = Date.now(); - const tpcSupportTtl = 1; - - before(() => { - dateNowStub = sinon.stub(Date, 'now').returns(dateNowMock); - }); - - after(() => { - dateNowStub.restore(); - }); - - it('should set tpcSupport: true and tpcUntil in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID, tpcSupport: true, tpcSupportTtl }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID + ',tpc:1,tpcUntil:' + Math.floor((dateNowMock / 1000) + tpcSupportTtl)) - ); - }); - - it('should set tpcSupport: false and tpcUntil in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID, tpcSupport: false, tpcSupportTtl }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID + ',tpc:0,tpcUntil:' + Math.floor((dateNowMock / 1000) + tpcSupportTtl)) - ); - }); - - it('should not set tpcSupport in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID) - ); - }); - }); - }); - - describe('request-filter status', function () { - let logErrorStub; - let callbackSpy = sinon.spy(); - - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - callbackSpy.resetHistory(); - removeParrableCookie(); - }); - - afterEach(function() { - logErrorStub.restore(); - }); - - describe('when getting filterTtl from XHR response', function () { - let request; - let dateNowStub; - const dateNowMock = Date.now(); - const filterTtl = 1000; - - before(() => { - dateNowStub = sinon.stub(Date, 'now').returns(dateNowMock); - }); - - after(() => { - dateNowStub.restore(); - }); - - it('should set filteredUntil in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID, filterTtl }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent( - 'eid:' + P_XHR_EID + - ',filteredUntil:' + Math.floor((dateNowMock / 1000) + filterTtl) + - ',filterHits:0') - ); - }); - - it('should increment filterHits in the cookie', function () { - writeParrableCookie({ - eid: P_XHR_EID, - filteredUntil: Math.floor((dateNowMock / 1000) + filterTtl), - filterHits: 0 - }); - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent( - 'eid:' + P_XHR_EID + - ',filteredUntil:' + Math.floor((dateNowMock / 1000) + filterTtl) + - ',filterHits:1') - ); - }); - - it('should send filterHits in the XHR', function () { - const filterHits = 1; - writeParrableCookie({ - eid: P_XHR_EID, - filteredUntil: Math.floor(dateNowMock / 1000), - filterHits - }); - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); - - expect(data.filterHits).to.equal(filterHits); - }); - }); - }); - }); - - describe('parrableIdSystem.decode()', function() { - it('provides the Parrable ID (EID) from a stored object', function() { - let eid = '01.123.4567890'; - let parrableId = { - eid, - ibaOptout: true - }; - - expect(parrableIdSubmodule.decode(parrableId)).to.deep.equal({ - parrableId - }); - }); - }); - - describe('timezone filtering', function() { - before(function() { - sinon.stub(Intl, 'DateTimeFormat'); - }); - - after(function() { - Intl.DateTimeFormat.restore(); - }); - - it('permits an impression when no timezoneFilter is configured', function() { - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - } })).to.have.property('callback'); - }); - - it('permits an impression from a blocked timezone when a cookie exists', function() { - const blockedZone = 'Antarctica/South_Pole'; - const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - writeParrableCookie({ eid: P_COOKIE_EID }); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - blockedZones: [ blockedZone ] - } - } })).to.have.property('callback'); - expect(resolvedOptions.called).to.equal(false); - - removeParrableCookie(); - }) - - it('permits an impression from an allowed timezone', function() { - const allowedZone = 'America/New_York'; - const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - allowedZones: [ allowedZone ] - } - } })).to.have.property('callback'); - expect(resolvedOptions.called).to.equal(true); - }); - - it('permits an impression from a lower cased allowed timezone', function() { - const allowedZone = 'America/New_York'; - const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', - timezoneFilter: { - allowedZones: [ allowedZone.toLowerCase() ] - } - } })).to.have.property('callback'); - expect(resolvedOptions.called).to.equal(true); - }); - - it('permits an impression from a timezone that is not blocked', function() { - const blockedZone = 'America/New_York'; - const resolvedOptions = sinon.stub().returns({ timeZone: 'Iceland' }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - blockedZones: [ blockedZone ] - } - } })).to.have.property('callback'); - expect(resolvedOptions.called).to.equal(true); - }); - - it('does not permit an impression from a blocked timezone', function() { - const blockedZone = 'America/New_York'; - const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - blockedZones: [ blockedZone ] - } - } })).to.equal(null); - expect(resolvedOptions.called).to.equal(true); - }); - - it('does not permit an impression from a lower cased blocked timezone', function() { - const blockedZone = 'America/New_York'; - const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', - timezoneFilter: { - blockedZones: [ blockedZone.toLowerCase() ] - } - } })).to.equal(null); - expect(resolvedOptions.called).to.equal(true); - }); - - it('does not permit an impression from a blocked timezone even when also allowed', function() { - const timezone = 'America/New_York'; - const resolvedOptions = sinon.stub().returns({ timeZone: timezone }); - Intl.DateTimeFormat.returns({ resolvedOptions }); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - allowedZones: [ timezone ], - blockedZones: [ timezone ] - } - } })).to.equal(null); - expect(resolvedOptions.called).to.equal(true); - }); - }); - - describe('timezone offset filtering', function() { - before(function() { - sinon.stub(Date.prototype, 'getTimezoneOffset'); - }); - - afterEach(function() { - Date.prototype.getTimezoneOffset.reset(); - }) - - after(function() { - Date.prototype.getTimezoneOffset.restore(); - }); - - it('permits an impression from a blocked offset when a cookie exists', function() { - const blockedOffset = -4; - Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); - - writeParrableCookie({ eid: P_COOKIE_EID }); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - blockedOffsets: [ blockedOffset ] - } - } })).to.have.property('callback'); - - removeParrableCookie(); - }); - - it('permits an impression from an allowed offset', function() { - const allowedOffset = -5; - Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - allowedOffsets: [ allowedOffset ] - } - } })).to.have.property('callback'); - expect(Date.prototype.getTimezoneOffset.called).to.equal(true); - }); - - it('permits an impression from an offset that is not blocked', function() { - const allowedOffset = -5; - const blockedOffset = 5; - Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - blockedOffsets: [ blockedOffset ] - } - }})).to.have.property('callback'); - expect(Date.prototype.getTimezoneOffset.called).to.equal(true); - }); - - it('does not permit an impression from a blocked offset', function() { - const blockedOffset = -5; - Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - blockedOffsets: [ blockedOffset ] - } - } })).to.equal(null); - expect(Date.prototype.getTimezoneOffset.called).to.equal(true); - }); - - it('does not permit an impression from a blocked offset even when also allowed', function() { - const offset = -5; - Date.prototype.getTimezoneOffset.returns(offset * 60); - - expect(parrableIdSubmodule.getId({ params: { - partners: 'prebid-test', - timezoneFilter: { - allowedOffset: [ offset ], - blockedOffsets: [ offset ] - } - } })).to.equal(null); - expect(Date.prototype.getTimezoneOffset.called).to.equal(true); - }); - }); - - describe('userId requestBids hook', function() { - let adUnits; - let sandbox; - - beforeEach(function() { - sandbox = sinon.sandbox.create(); - mockGdprConsent(sandbox); - adUnits = [getAdUnitMock()]; - writeParrableCookie({ eid: P_COOKIE_EID, ibaOptout: true }); - init(config); - setSubmoduleRegistry([parrableIdSubmodule]); - }); - - afterEach(function() { - removeParrableCookie(); - storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); - sandbox.restore(); - }); - - it('when a stored Parrable ID exists it is added to bids', function(done) { - config.setConfig(getConfigMock()); - requestBidsHook(function() { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.parrableId'); - expect(bid.userId.parrableId.eid).to.equal(P_COOKIE_EID); - expect(bid.userId.parrableId.ibaOptout).to.equal(true); - const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); - expect(parrableIdAsEid).to.deep.equal({ - source: 'parrable.com', - uids: [{ - id: P_COOKIE_EID, - atype: 1, - ext: { - ibaOptout: true - } - }] - }); - }); - }); - done(); - }, { adUnits }); - }); - - it('supplies an optout reason when the EID is missing due to CCPA non-consent', function(done) { - // the ID system itself will not write a cookie with an EID when CCPA=true - writeParrableCookie({ ccpaOptout: true }); - config.setConfig(getConfigMock()); - - requestBidsHook(function() { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.parrableId'); - expect(bid.userId.parrableId).to.not.have.property('eid'); - expect(bid.userId.parrableId.ccpaOptout).to.equal(true); - const parrableIdAsEid = find(bid.userIdAsEids, e => e.source == 'parrable.com'); - expect(parrableIdAsEid).to.deep.equal({ - source: 'parrable.com', - uids: [{ - id: '', - atype: 1, - ext: { - ccpaOptout: true - } - }] - }); - }); - }); - done(); - }, { adUnits }); - }); - }); - - describe('partners parsing', function () { - let callbackSpy = sinon.spy(); - - const partnersTestCase = [ - { - name: '"partners" as an array', - config: { params: { partners: ['parrable_test_partner_123', 'parrable_test_partner_456'] } }, - expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] - }, - { - name: '"partners" as a string list', - config: { params: { partners: 'parrable_test_partner_123,parrable_test_partner_456' } }, - expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] - }, - { - name: '"partners" as a string', - config: { params: { partners: 'parrable_test_partner_123' } }, - expected: ['parrable_test_partner_123'] - }, - { - name: '"partner" as a string list', - config: { params: { partner: 'parrable_test_partner_123,parrable_test_partner_456' } }, - expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] - }, - { - name: '"partner" as string', - config: { params: { partner: 'parrable_test_partner_123' } }, - expected: ['parrable_test_partner_123'] - }, - ]; - partnersTestCase.forEach(testCase => { - it(`accepts config property ${testCase.name}`, () => { - parrableIdSubmodule.getId(testCase.config).callback(callbackSpy); - - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); - - expect(data.trackers).to.deep.equal(testCase.expected); - }); - }); - }); - - describe('eid', () => { - before(() => { - attachIdSystem(merkleIdSubmodule); - }) - it('parrableId', function() { - const userId = { - parrableId: { - eid: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'parrable.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - }) -}); diff --git a/test/spec/modules/pirIdSystem_spec.js b/test/spec/modules/pirIdSystem_spec.js deleted file mode 100644 index 5acc5a5eb9c..00000000000 --- a/test/spec/modules/pirIdSystem_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import { pirIdSubmodule, storage, readId } from 'modules/pirIdSystem.js'; -import sinon from 'sinon'; - -describe('pirIdSystem', () => { - let sandbox; - let getCookieStub; - let getDataFromLocalStorageStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - getCookieStub = sandbox.stub(storage, 'getCookie'); - getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('getId', () => { - it('should return an object with id when pirIdToken is found', () => { - getDataFromLocalStorageStub.returns('testToken'); - getCookieStub.returns('testToken'); - - const result = pirIdSubmodule.getId(); - - expect(result).to.deep.equal({ id: 'testToken' }); - }); - - it('should return undefined when pirIdToken is not found', () => { - const result = pirIdSubmodule.getId(); - - expect(result).to.be.undefined; - }); - }); - - describe('decode', () => { - it('should return an object with pirId when value is a string', () => { - const result = pirIdSubmodule.decode('testId'); - - expect(result).to.deep.equal({ pirId: 'testId' }); - }); - - it('should return undefined when value is not a string', () => { - const result = pirIdSubmodule.decode({}); - - expect(result).to.be.undefined; - }); - }); - - describe('readId', () => { - it('should return data from local storage when it exists', () => { - getDataFromLocalStorageStub.returns('local_storage_data'); - - const result = readId(); - - expect(result).to.equal('local_storage_data'); - }); - - it('should return data from cookie when local storage data does not exist', () => { - getDataFromLocalStorageStub.returns(null); - getCookieStub.returns('cookie_data'); - - const result = readId(); - - expect(result).to.equal('cookie_data'); - }); - - it('should return null when neither local storage data nor cookie data exists', () => { - getDataFromLocalStorageStub.returns(null); - getCookieStub.returns(null); - - const result = readId(); - - expect(result).to.be.null; - }); - }); -}); diff --git a/test/spec/modules/pixfutureBidAdapter_spec.js b/test/spec/modules/pixfutureBidAdapter_spec.js index a236478c9b4..bdf40fbb06b 100644 --- a/test/spec/modules/pixfutureBidAdapter_spec.js +++ b/test/spec/modules/pixfutureBidAdapter_spec.js @@ -43,12 +43,12 @@ describe('PixFutureAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'pix_id': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 3b433d34955..2b4cd025515 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -21,7 +21,7 @@ import 'modules/currency.js'; // adServerCurrency test import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; import 'modules/paapi.js'; @@ -42,7 +42,6 @@ let CONFIG = { accountId: '1', enabled: true, bidders: ['appnexus'], - timeout: 1000, cacheMarkup: 2, endpoint: { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', @@ -765,6 +764,7 @@ describe('S2S Adapter', function () { it('should set tmaxmax correctly when publisher has specified it', () => { const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}) // publisher has specified a tmaxmax in their setup const ortb2Fragments = { @@ -785,8 +785,9 @@ describe('S2S Adapter', function () { it('should set tmaxmax correctly when publisher has not specified it', () => { const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}) - // publisher has not specified a tmaxmax in their setup - so we should be + // publisher has not specified a tmaxmax in their setup - so we should be // falling back to requestBidsTimeout const ortb2Fragments = {}; const s2sCfg = {...REQUEST, cfg}; @@ -799,6 +800,32 @@ describe('S2S Adapter', function () { expect(req.ext.tmaxmax).to.eql(808); }); + describe('default tmax', () => { + [null, 3000].forEach(maxTimeout => { + describe(`when maxTimeout is ${maxTimeout}`, () => { + let cfg; + + beforeEach(() => { + cfg = {accountId: '1', endpoint: 'mock-endpoint', maxTimeout}; + config.setConfig({s2sConfig: cfg}); + maxTimeout = maxTimeout ?? s2sDefaultConfig.maxTimeout + }); + + it('should cap tmax to maxTimeout', () => { + adapter.callBids({...REQUEST, requestBidsTimeout: maxTimeout * 2, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.tmax).to.eql(maxTimeout); + }); + + it('should be set to 0.75 * requestTimeout, if lower than maxTimeout', () => { + adapter.callBids({...REQUEST, requestBidsTimeout: maxTimeout / 2}, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.tmax).to.eql(maxTimeout / 2 * 0.75); + }) + }) + }) + }) + it('should block request if config did not define p1Consent URL in endpoint object config', function () { let badConfig = utils.deepClone(CONFIG); badConfig.endpoint = { noP1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }; @@ -864,22 +891,6 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].video).to.exist; }); - it('should default video placement if not defined and instream', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - config.setConfig({ s2sConfig: ortb2Config }); - - let videoBid = utils.deepClone(VIDEO_REQUEST); - videoBid.ad_units[0].mediaTypes.video.context = 'instream'; - adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); - - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.not.exist; - expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); - }); - it('converts video mediaType properties into openRTB format', function () { let ortb2Config = utils.deepClone(CONFIG); ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; @@ -893,7 +904,6 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].banner).to.not.exist; expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); expect(requestBid.imp[0].video.w).to.equal(640); expect(requestBid.imp[0].video.h).to.equal(480); expect(requestBid.imp[0].video.playerSize).to.be.undefined; @@ -1049,12 +1059,18 @@ describe('S2S Adapter', function () { it('adds device and app objects to request', function () { const _config = { s2sConfig: CONFIG, - device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, - app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sreq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + } + } + }, BID_REQUESTS) + adapter.callBids(s2sreq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', @@ -1073,15 +1089,20 @@ describe('S2S Adapter', function () { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' } }); - const _config = { s2sConfig: s2sConfig, - device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, - app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + } + } + }, BID_REQUESTS) + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', @@ -1440,9 +1461,7 @@ describe('S2S Adapter', function () { it('adds device.w and device.h even if the config lacks a device object', function () { const _config = { s2sConfig: CONFIG, - app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); @@ -1450,10 +1469,6 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - sinon.assert.match(requestBid.app, { - bundle: 'com.test.app', - publisher: { 'id': '1' } - }); expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); @@ -1537,19 +1552,26 @@ describe('S2S Adapter', function () { it('adds site if app is not present', function () { const _config = { s2sConfig: CONFIG, - site: { - publisher: { - id: '1234', - domain: 'test.com' - }, - content: { - language: 'en' - } - } }; config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + } + } + }, BID_REQUESTS); + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); expect(requestBid.site.publisher).to.exist.and.to.be.a('object'); @@ -1574,23 +1596,31 @@ describe('S2S Adapter', function () { it('site should not be present when app is present', function () { const _config = { s2sConfig: CONFIG, - app: { bundle: 'com.test.app' }, - site: { - publisher: { - id: '1234', - domain: 'test.com' - }, - content: { - language: 'en' - } - } }; config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + } + } + }, BID_REQUESTS) + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.not.exist; - expect(requestBid.app).to.exist.and.to.be.a('object'); + expect(requestBid.app.bundle).to.eql('com.test.app'); }); it('adds appnexus aliases to request', function () { @@ -1754,39 +1784,6 @@ describe('S2S Adapter', function () { }); }); - it('converts appnexus params to expected format for PBS', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - } - }); - config.setConfig({ s2sConfig: s2sConfig }); - - Object.assign(BID_REQUESTS[0].bids[0].params, { - usePaymentRule: true, - keywords: { - foo: ['bar', 'baz'], - fizz: ['buzz'] - } - }) - - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - - const requestParams = requestBid.imp[0].ext.prebid.bidder; - expect(requestParams.appnexus).to.exist; - expect(requestParams.appnexus.placement_id).to.exist.and.to.equal(10433394); - expect(requestParams.appnexus.use_pmt_rule).to.exist.and.to.be.true; - expect(requestParams.appnexus.member).to.exist; - expect(requestParams.appnexus.keywords).to.exist.and.to.deep.equal([{ - key: 'foo', - value: ['bar', 'baz'] - }, { - key: 'fizz', - value: ['buzz'] - }]); - }); - describe('cookie sync', () => { let s2sConfig, bidderReqs; @@ -2004,15 +2001,21 @@ describe('S2S Adapter', function () { it('and overrides publisher and page', function () { config.setConfig({ s2sConfig: s2sConfig, - site: { - domain: 'nytimes.com', - page: 'http://www.nytimes.com', - publisher: { id: '2' } - }, - device: device }); - - adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...s2sBidRequest, + ortb2Fragments: { + global: { + site: { + domain: 'nytimes.com', + page: 'http://www.nytimes.com', + publisher: { id: '2' } + }, + device, + } + } + }, BID_REQUESTS); + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -2025,13 +2028,19 @@ describe('S2S Adapter', function () { it('and merges domain and page with the config site value', function () { config.setConfig({ s2sConfig: s2sConfig, - site: { - foo: 'bar' - }, - device: device }); - - adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...s2sBidRequest, + ortb2Fragments: { + global: { + site: { + foo: 'bar' + }, + device: device + } + } + }, BID_REQUESTS); + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -3524,12 +3533,15 @@ describe('S2S Adapter', function () { beforeEach(function () { fledgeStub = sinon.stub(); - config.setConfig({CONFIG}); + config.setConfig({ + s2sConfig: CONFIG, + }); bidderRequests = deepClone(BID_REQUESTS); - AU bidderRequests.forEach(req => { Object.assign(req, { - fledgeEnabled: true, + paapi: { + enabled: true + }, ortb2: { fpd: 1 } @@ -3537,7 +3549,7 @@ describe('S2S Adapter', function () { req.bids.forEach(bid => { Object.assign(bid, { ortb2Imp: { - fpd: 2 + fpd: 2, } }) }) @@ -3548,8 +3560,8 @@ describe('S2S Adapter', function () { function expectFledgeCalls() { const auctionId = bidderRequests[0].auctionId; - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), {config: {id: 1}}) - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), {config: {id: 2}}) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), sinon.match({config: {id: 1}})) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), sinon.match({config: {id: 2}})) } it('calls addComponentAuction alongside addBidResponse', function () { @@ -3712,181 +3724,135 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: options }); sinon.assert.calledOnce(logErrorSpy); }); + describe('vendor: appnexuspsp', () => { + it('should configure the s2sConfig object with appnexuspsp vendor defaults unless specified by user', function () { + const options = { + accountId: '123', + bidders: ['appnexus'], + defaultVendor: 'appnexuspsp', + timeout: 750 + }; - it('should configure the s2sConfig object with appnexuspsp vendor defaults unless specified by user', function () { - const options = { - accountId: '123', - bidders: ['appnexus'], - defaultVendor: 'appnexuspsp', - timeout: 750 - }; - - config.setConfig({ s2sConfig: options }); - sinon.assert.notCalled(logErrorSpy); - - let vendorConfig = config.getConfig('s2sConfig'); - expect(vendorConfig).to.have.property('accountId', '123'); - expect(vendorConfig).to.have.property('adapter', 'prebidServer'); - expect(vendorConfig.bidders).to.deep.equal(['appnexus']); - expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({ - p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', - noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' - }); - expect(vendorConfig.syncEndpoint).to.deep.equal({ - p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', - noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', '123'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['appnexus']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({ + p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', + noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' + }); + expect(vendorConfig.syncEndpoint).to.deep.equal({ + p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + }); + expect(vendorConfig).to.have.property('timeout', 750); }); - expect(vendorConfig).to.have.property('timeout', 750); - }); - - it('should configure the s2sConfig object with rubicon vendor defaults unless specified by user', function () { - const options = { - accountId: 'abc', - bidders: ['rubicon'], - defaultVendor: 'rubicon', - timeout: 750 - }; + }) - config.setConfig({ s2sConfig: options }); - sinon.assert.notCalled(logErrorSpy); - - let vendorConfig = config.getConfig('s2sConfig'); - expect(vendorConfig).to.have.property('accountId', 'abc'); - expect(vendorConfig).to.have.property('adapter', 'prebidServer'); - expect(vendorConfig.bidders).to.deep.equal(['rubicon']); - expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({ - p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', - noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' - }); - expect(vendorConfig.syncEndpoint).to.deep.equal({ - p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', - noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' - }); - expect(vendorConfig).to.have.property('timeout', 750); - }); + describe('vendor: rubicon', () => { + it('should configure the s2sConfig object with rubicon vendor defaults unless specified by user', function () { + const options = { + accountId: 'abc', + bidders: ['rubicon'], + defaultVendor: 'rubicon', + timeout: 750 + }; - it('should return proper defaults', function () { - const options = { - accountId: 'abc', - bidders: ['rubicon'], - defaultVendor: 'rubicon', - timeout: 750 - }; + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); - config.setConfig({ s2sConfig: options }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - 'accountId': 'abc', - 'adapter': 'prebidServer', - 'bidders': ['rubicon'], - 'defaultVendor': 'rubicon', - 'enabled': true, - 'endpoint': { + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', 'abc'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['rubicon']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({ p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' - }, - 'syncEndpoint': { + }); + expect(vendorConfig.syncEndpoint).to.deep.equal({ p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' - }, - 'timeout': 750 - }) - }); - - it('should return default adapterOptions if not set', function () { - config.setConfig({ - s2sConfig: { + }); + expect(vendorConfig).to.have.property('timeout', 750); + }); + it('should return proper defaults', function () { + const options = { accountId: 'abc', bidders: ['rubicon'], defaultVendor: 'rubicon', timeout: 750 - } - }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - enabled: true, - timeout: 750, - adapter: 'prebidServer', - accountId: 'abc', - bidders: ['rubicon'], - defaultVendor: 'rubicon', - endpoint: { - p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', - noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' - }, - syncEndpoint: { - p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', - noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' - }, - }) - }); - - it('should configure the s2sConfig object with openwrap vendor defaults unless specified by user', function () { - const options = { - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap' - }; + }; - config.setConfig({ s2sConfig: options }); - sinon.assert.notCalled(logErrorSpy); - - let vendorConfig = config.getConfig('s2sConfig'); - expect(vendorConfig).to.have.property('accountId', '1234'); - expect(vendorConfig).to.have.property('adapter', 'prebidServer'); - expect(vendorConfig.bidders).to.deep.equal(['pubmatic']); - expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({ - p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', - noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + config.setConfig({ s2sConfig: options }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + 'accountId': 'abc', + 'adapter': 'prebidServer', + 'bidders': ['rubicon'], + 'defaultVendor': 'rubicon', + 'enabled': true, + 'endpoint': { + p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' + }, + 'syncEndpoint': { + p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', + noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' + }, + 'timeout': 750, + maxTimeout: 500, + }) }); - expect(vendorConfig).to.have.property('timeout', 500); - }); + }) - it('should return proper defaults', function () { - const options = { - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap', - timeout: 500 - }; + describe('vendor: openwrap', () => { + it('should configure the s2sConfig object with openwrap vendor defaults unless specified by user', function () { + const options = { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap' + }; - config.setConfig({ s2sConfig: options }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - 'accountId': '1234', - 'adapter': 'prebidServer', - 'bidders': ['pubmatic'], - 'defaultVendor': 'openwrap', - 'enabled': true, - 'endpoint': { + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', '1234'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['pubmatic']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({ p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' - }, - 'timeout': 500 - }) - }); - - it('should return default adapterOptions if not set', function () { - config.setConfig({ - s2sConfig: { + }); + }); + it('should return proper defaults', function () { + const options = { accountId: '1234', bidders: ['pubmatic'], defaultVendor: 'openwrap', timeout: 500 - } + }; + + config.setConfig({ s2sConfig: options }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + 'accountId': '1234', + 'adapter': 'prebidServer', + 'bidders': ['pubmatic'], + 'defaultVendor': 'openwrap', + 'enabled': true, + 'endpoint': { + p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', + noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + }, + 'timeout': 500, + maxTimeout: 500, + }) }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - enabled: true, - timeout: 500, - adapter: 'prebidServer', - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap', - endpoint: { - p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', - noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' - }, - }) }); it('should set adapterOptions', function () { diff --git a/test/spec/modules/prismaBidAdapter_spec.js b/test/spec/modules/prismaBidAdapter_spec.js index be1c16c9059..b0d068e5614 100644 --- a/test/spec/modules/prismaBidAdapter_spec.js +++ b/test/spec/modules/prismaBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec} from 'modules/prismaBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; +import { requestBidsHook } from 'modules/consentManagementTcf.js'; describe('Prisma bid adapter tests', function () { const DISPLAY_BID_REQUEST = [{ diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 86c8794dc4c..e1d579aaa4a 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -383,7 +383,6 @@ describe('pubGENIUS adapter', () => { w: 200, h: 100, startdelay: -1, - placement: 1, skip: 1, skipafter: 1, playbackmethod: [3, 4], diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 745def57f4e..7d42f407448 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2948,7 +2948,7 @@ describe('PubMatic adapter', function () { bidRequest[0].ortb2Imp = { ext: { ae: 1 } }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: false }); + const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: false} }); let data = JSON.parse(req.data); if (data.imp[0].ext) { expect(data.imp[0].ext).to.not.have.property('ae'); @@ -2961,7 +2961,7 @@ describe('PubMatic adapter', function () { bidRequest[0].ortb2Imp = { ext: { ae: 1 } }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: true }); + const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: true} }); let data = JSON.parse(req.data); expect(data.imp[0].ext.ae).to.equal(1); }); @@ -3786,9 +3786,9 @@ describe('PubMatic adapter', function () { response = spec.interpretResponse({ body: bidResponse }, bidRequest); it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test_bid_id'); }); }); diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index b387264bf91..38efccac2a6 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -26,10 +26,10 @@ describe('pubxAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/pxyzBidAdapter_spec.js b/test/spec/modules/pxyzBidAdapter_spec.js index 36e7a1e9ad6..87dc5ff0783 100644 --- a/test/spec/modules/pxyzBidAdapter_spec.js +++ b/test/spec/modules/pxyzBidAdapter_spec.js @@ -39,12 +39,12 @@ describe('pxyzBidAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index d10fea829bc..fdde8d290f4 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -181,7 +181,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, // optional playbackmethod: [1], // optional delivery: [1], // optional - placement: 1, // optional api: [2, 3] // optional }, { context: 'instream', @@ -205,7 +204,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, playbackmethod: [1], delivery: [1], - placement: 1, api: [2, 3], w: 600, h: 300 @@ -242,7 +240,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, // optional playbackmethod: [1], // optional delivery: [1], // optional - placement: 1, // optional api: [2, 3], // optional context: 'instream', playerSize: [600, 300] @@ -265,7 +262,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, playbackmethod: [1], delivery: [1], - placement: 1, api: [2, 3], w: 600, h: 300 diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index 3ad7ada2ae7..4a64e2922f1 100644 --- a/test/spec/modules/radsBidAdapter_spec.js +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -32,12 +32,12 @@ describe('radsAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js index 15b22afbe29..2a9fcb9f83b 100644 --- a/test/spec/modules/rakutenBidAdapter_spec.js +++ b/test/spec/modules/rakutenBidAdapter_spec.js @@ -40,10 +40,10 @@ describe('rakutenBidAdapter', function() { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false) + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false) }) }); diff --git a/test/spec/modules/retailspotBidAdapter_spec.js b/test/spec/modules/retailspotBidAdapter_spec.js index 39cddb323b8..f1fb5ae3fd3 100644 --- a/test/spec/modules/retailspotBidAdapter_spec.js +++ b/test/spec/modules/retailspotBidAdapter_spec.js @@ -286,19 +286,19 @@ describe('RetailSpot Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.size; + let invalidBid = Object.assign({}, bid); + delete invalidBid.sizes; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placement': 0 }; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js deleted file mode 100644 index d2b173f53df..00000000000 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ /dev/null @@ -1,1304 +0,0 @@ -// import or require modules necessary for the test, e.g.: -import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' -import { - spec -} from 'modules/richaudienceBidAdapter.js'; -import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; -import sinon from 'sinon'; - -describe('Richaudience adapter tests', function () { - var DEFAULT_PARAMS_NEW_SIZES = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - mediaTypes: { - banner: { - sizes: [ - [300, 250], [300, 600], [728, 90], [970, 250]] - } - }, - bidder: 'richaudience', - params: { - bidfloor: 0.5, - pid: 'ADb1f40rmi', - supplyType: 'site', - keywords: 'key1=value1;key2=value2' - }, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - ortb2Imp: { - ext: { - tid: '29df2112-348b-4961-8863-1b33684d95e6', - } - }, - user: {} - }]; - - var DEFAULT_PARAMS_NEW_SIZES_GPID = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - ortb2Imp: { - ext: { - gpid: '/19968336/header-bid-tag-1#example-2', - data: { - pbadslot: '/19968336/header-bid-tag-1#example-2' - } - } - }, - mediaTypes: { - banner: { - sizes: [ - [300, 250], [300, 600], [728, 90], [970, 250]] - } - }, - bidder: 'richaudience', - params: { - bidfloor: 0.5, - pid: 'ADb1f40rmi', - supplyType: 'site', - keywords: 'key1=value1;key2=value2' - }, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6', - user: {} - }]; - - var DEFAULT_PARAMS_VIDEO_TIMEOUT = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4'] - } - }, - bidder: 'richaudience', - params: [{ - bidfloor: 0.5, - pid: 'ADb1f40rmi', - supplyType: 'site' - }], - timeout: 3000, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6', - user: {} - }] - - var DEFAULT_PARAMS_VIDEO_IN = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4'] - } - }, - bidder: 'richaudience', - params: { - bidfloor: 0.5, - pid: 'ADb1f40rmi', - supplyType: 'site' - }, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6', - user: {} - }]; - - var DEFAULT_PARAMS_VIDEO_OUT = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] - } - }, - bidder: 'richaudience', - params: { - bidfloor: 0.5, - pid: 'ADb1f40rmi', - supplyType: 'site' - }, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6', - user: {} - }]; - - var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - mediaTypes: { - banner: { - sizes: [[300, 250], [600, 300]] - } - }, - bidder: 'richaudience', - params: { - bidfloor: 0.5, - pid: 'ADb1f40rmi', - supplyType: 'site' - }, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6', - user: {} - }]; - - var DEFAULT_PARAMS_APP = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - sizes: [ - [300, 250], - [300, 600], - [728, 90], - [970, 250] - ], - bidder: 'richaudience', - params: { - bidfloor: 0.5, - ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', - pid: 'ADb1f40rmi', - supplyType: 'app', - }, - auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6' - }]; - - var DEFAULT_PARAMS_WO_OPTIONAL = [{ - adUnitCode: 'test-div', - bidId: '2c7c8e9c900244', - sizes: [ - [300, 250], - [300, 600], - [728, 90], - [970, 250] - ], - bidder: 'richaudience', - params: { - pid: 'ADb1f40rmi', - supplyType: 'site', - }, - auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', - bidRequestsCount: 1, - bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6' - }]; - - var BID_RESPONSE = { - body: { - cpm: 1.50, - adm: '', - media_type: 'js', - width: 300, - height: 250, - creative_id: '189198063', - netRevenue: true, - currency: 'USD', - ttl: 300, - dealId: 'dealId', - adomain: 'richaudience.com' - } - }; - - var BID_RESPONSE_VIDEO = { - body: { - cpm: 1.50, - media_type: 'video', - width: 1, - height: 1, - creative_id: '189198063', - netRevenue: true, - currency: 'USD', - ttl: 300, - vastXML: '', - dealId: 'dealId', - adomain: 'richaudience.com' - } - }; - - var DEFAULT_PARAMS_GDPR = { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'http://domain.com', - numIframes: 0 - } - } - - it('Referer undefined', function() { - config.setConfig({ - 'currency': {'adServerCurrency': 'USD'} - }) - - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: {} - }) - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('referer').and.to.equal(null); - expect(requestContent).to.have.property('referer').and.to.equal(null); - }) - - it('Verify build request to prebid 3.0 display test', function() { - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('bidfloor').and.to.equal(0.5); - expect(requestContent).to.have.property('pid').and.to.equal('ADb1f40rmi'); - expect(requestContent).to.have.property('supplyType').and.to.equal('site'); - expect(requestContent).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); - expect(requestContent).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); - expect(requestContent).to.have.property('BidRequestsCount').and.to.equal(1); - expect(requestContent).to.have.property('bidder').and.to.equal('richaudience'); - expect(requestContent).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); - expect(requestContent).to.have.property('tagId').and.to.equal('test-div'); - expect(requestContent).to.have.property('referer').and.to.equal('https%3A%2F%2Fdomain.com'); - expect(requestContent).to.have.property('sizes'); - expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); - expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); - expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); - expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); - expect(requestContent.sizes[2]).to.have.property('w').and.to.equal(728); - expect(requestContent.sizes[2]).to.have.property('h').and.to.equal(90); - expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); - expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); - expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); - expect(requestContent).to.have.property('timeout').and.to.equal(600); - expect(requestContent).to.have.property('numIframes').and.to.equal(0); - expect(typeof requestContent.scr_rsl === 'string') - expect(typeof requestContent.cpuc === 'number') - expect(typeof requestContent.gpid === 'string') - expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); - }) - - it('Verify build request to prebid video inestream', function() { - const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.have.property('demand').and.to.equal('video'); - expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - }) - - it('Verify build request to prebid video outstream', function() { - const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.have.property('demand').and.to.equal('video'); - expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); - }) - - describe('gdpr test', function () { - it('Verify build request with GDPR', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'USD' - }, - consentManagement: { - cmpApi: 'iab', - timeout: 8000, - allowAuctionWithoutConsent: true - } - }); - - const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); - }); - - it('Verify adding ifa when supplyType equal to app', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_APP, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - }); - - it('Verify build request with GDPR without gdprApplies', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - }, - consentManagement: { - cmp: 'iab', - consentRequired: true, - timeout: 8000, - allowAuctionWithoutConsent: true - } - }); - const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA' - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); - }); - }); - - describe('UID test', function () { - config.setConfig({ - consentManagement: { - cmpApi: 'iab', - timeout: 5000, - allowAuctionWithoutConsent: true - }, - userSync: { - userIds: [{ - name: 'id5Id', - params: { - partner: 173, // change to the Partner Number you received from ID5 - pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this - }, - storage: { - type: 'html5', // "html5" is the required storage type - name: 'id5id', // "id5id" is the required storage name - expires: 90, // storage lasts for 90 days - refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh - } - }], - auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules - } - }); - it('Verify build id5', function () { - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build pubCommonId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 'pub_common_user_id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'pub_common_user_id', - 'source': 'pubcommon' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build criteoId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 'criteo-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'criteo-user-id', - 'source': 'criteo.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build identityLink', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'identity-link-user-id', - 'source': 'liveramp.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - it('Verify build liveIntentId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data) - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'identity-link-user-id', - 'source': 'liveramp.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - it('Verify build TradeDesk', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.tdid = 'tdid-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data) - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'tdid-user-id', - 'source': 'adserver.org' - }]); - - request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - }); - - it('Verify interprete response', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - const bids = spec.interpretResponse(BID_RESPONSE, request[0]); - expect(bids).to.have.lengthOf(1); - const bid = bids[0]; - expect(bid.cpm).to.equal(1.50); - expect(bid.ad).to.equal(''); - expect(bid.mediaType).to.equal('js'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.creativeId).to.equal('189198063'); - expect(bid.netRevenue).to.equal(true); - expect(bid.currency).to.equal('USD'); - expect(bid.ttl).to.equal(300); - expect(bid.dealId).to.equal('dealId'); - expect(bid.meta).to.equal('richaudience.com'); - }); - - it('no banner media response inestream', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); - expect(bids).to.have.lengthOf(1); - const bid = bids[0]; - expect(bid.cpm).to.equal(1.50); - expect(bid.mediaType).to.equal('video'); - expect(bid.vastXml).to.equal(''); - expect(bid.cpm).to.equal(1.50); - expect(bid.width).to.equal(1); - expect(bid.height).to.equal(1); - expect(bid.creativeId).to.equal('189198063'); - expect(bid.netRevenue).to.equal(true); - expect(bid.currency).to.equal('USD'); - expect(bid.ttl).to.equal(300); - expect(bid.dealId).to.equal('dealId'); - expect(bid.meta).to.equal('richaudience.com'); - }); - - it('no banner media response outstream', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); - expect(bids).to.have.lengthOf(1); - const bid = bids[0]; - expect(bid.cpm).to.equal(1.50); - expect(bid.mediaType).to.equal('video'); - expect(bid.vastXml).to.equal(''); - expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); - expect(bid.cpm).to.equal(1.50); - expect(bid.width).to.equal(1); - expect(bid.height).to.equal(1); - expect(bid.creativeId).to.equal('189198063'); - expect(bid.netRevenue).to.equal(true); - expect(bid.currency).to.equal('USD'); - expect(bid.ttl).to.equal(300); - expect(bid.dealId).to.equal('dealId'); - }); - - it('banner media and response VAST', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - page: 'https://domain.com', - numIframes: 0 - } - }); - - const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); - const bid = bids[0]; - expect(bid.mediaType).to.equal('video'); - expect(bid.vastXml).to.equal(''); - expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); - }); - - it('Verifies bidder_code', function () { - expect(spec.code).to.equal('richaudience'); - }); - - it('Verifies bidder aliases', function () { - expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('ra'); - }); - - it('Verifies bidder gvlid', function () { - expect(spec.gvlid).to.equal(108); - }); - - it('Verifies bidder supportedMediaTypes', function () { - expect(spec.supportedMediaTypes).to.have.lengthOf(2); - expect(spec.supportedMediaTypes[0]).to.equal('banner'); - expect(spec.supportedMediaTypes[1]).to.equal('video'); - }); - - it('Verifies if bid request is valid', function () { - expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); - expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ - params: {} - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - pid: 'ADb1f40rmi' - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - supplyType: 'site' - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - supplyType: 'app' - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - pid: 'ADb1f40rmi', - supplyType: 'site' - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - pid: ['1gCB5ZC4XL', '1a40xk8qSV'], - supplyType: 'site' - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - pid: 'ADb1f40rmi', - supplyType: 'site' - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - pid: 'ADb1f40rmi', - supplyType: 'app', - ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - pid: 'ADb1f40rmi', - supplyType: 'site', - bidfloor: 0.50, - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - pid: 'ADb1f40rmi', - supplyType: 'site', - bidfloor: 0.50, - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - pid: ['1gCB5ZC4XL', '1a40xk8qSV'], - bidfloor: 0.50, - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - pid: ['1gCB5ZC4XL', '1a40xk8qSV'], - supplyType: 'site', - bidfloor: 0.50, - } - })).to.equal(true); - expect(spec.isBidRequestValid({ - params: { - supplyType: 'site', - bidfloor: 0.50, - ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - pid: ['1gCB5ZC4XL', '1a40xk8qSV'], - supplyType: 'site', - bidfloor: 0.50, - ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', - } - })).to.equal(true); - }); - - it('should pass schain', function() { - let schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [{ - 'asi': 'richaudience.com', - 'sid': '00001', - 'hp': 1 - }, { - 'asi': 'richaudience-2.com', - 'sid': '00002', - 'hp': 1 - }] - } - - DEFAULT_PARAMS_NEW_SIZES[0].schain = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [{ - 'asi': 'richaudience.com', - 'sid': '00001', - 'hp': 1 - }, { - 'asi': 'richaudience-2.com', - 'sid': '00002', - 'hp': 1 - }] - } - - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: {} - }) - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('schain').to.deep.equal(schain); - }) - - it('should pass gpid', function() { - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: {} - }) - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); - }) - - describe('onTimeout', function () { - beforeEach(function() { - sinon.stub(utils, 'triggerPixel'); - }); - - afterEach(function() { - utils.triggerPixel.restore(); - }); - it('onTimeout exist as a function', () => { - expect(spec.onTimeout).to.exist.and.to.be.a('function'); - }); - it('should send timeouts', function () { - spec.onTimeout(DEFAULT_PARAMS_VIDEO_TIMEOUT); - expect(utils.triggerPixel.called).to.equal(true); - expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=localhost:9876'); - }); - }); - - describe('userSync', function () { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - afterEach(function() { - sandbox.restore(); - }); - it('Verifies user syncs iframe include', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, - ); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true, - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: true, - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); - }); - it('Verifies user syncs iframe exclude', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, - ); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true, - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: true, - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); - }); - - it('Verifies user syncs image include', function () { - config.setConfig({ - 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: false - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); - - it('Verifies user syncs image exclude', function () { - config.setConfig({ - 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: false - }) - expect(syncs).to.have.lengthOf(0); - }); - - it('Verifies user syncs iframe/image include', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, - ); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true, - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); - }); - - it('Verifies user syncs iframe/image exclude', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, - ); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true, - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); - }); - - it('Verifies user syncs iframe exclude / image include', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, - ); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true, - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); - }); - - it('Verifies user syncs iframe include / image exclude', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} - }) - - var syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, - ); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true, - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }); - expect(syncs).to.have.lengthOf(0); - - syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [], {consentString: '', gdprApplies: false}); - expect(syncs).to.have.lengthOf(1); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: false - }, [], {consentString: '', gdprApplies: true}); - expect(syncs).to.have.lengthOf(0); - }); - - it('Verifies user syncs iframe/image include with GPP', function () { - config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} - }) - - var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE], { - gppString: 'DBABL~BVVqAAEABgA.QA', - applicableSections: [7]}, - ); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - - config.setConfig({ - 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} - }) - - var syncs = spec.getUserSyncs({pixelEnabled: true}, [BID_RESPONSE], { - gppString: 'DBABL~BVVqAAEABgA.QA', - applicableSections: [7, 5]}, - ); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); - - it('Verifies user syncs URL image include with GPP', function () { - const gppConsent = { gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', applicableSections: [0] }; - const result = spec.getUserSyncs({pixelEnabled: true}, undefined, undefined, undefined, gppConsent); - expect(result).to.deep.equal([{ - type: 'image', url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` - }]); - }); - }) -}); diff --git a/test/spec/modules/rasBidAdapter_spec.js b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js similarity index 89% rename from test/spec/modules/rasBidAdapter_spec.js rename to test/spec/modules/ringieraxelspringerBidAdapter_spec.js index f172d192221..3539dad9362 100644 --- a/test/spec/modules/rasBidAdapter_spec.js +++ b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { spec } from 'modules/rasBidAdapter.js'; +import { spec } from 'modules/ringieraxelspringerBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; const CSR_ENDPOINT = 'https://csr.onet.pl/4178463/csr-006/csr.json?nid=4178463&'; -describe('rasBidAdapter', function () { +describe('ringieraxelspringerBidAdapter', function () { const adapter = newBidder(spec); describe('inherited functions', function () { @@ -17,7 +17,7 @@ describe('rasBidAdapter', function () { it('should return true when required params found', function () { const bid = { sizes: [[300, 250], [300, 600]], - bidder: 'ras', + bidder: 'ringieraxelspringer', params: { slot: 'slot', area: 'areatest', @@ -31,7 +31,7 @@ describe('rasBidAdapter', function () { it('should return false when required params not found', function () { const failBid = { sizes: [[300, 250], [300, 300]], - bidder: 'ras', + bidder: 'ringieraxelspringer', params: { site: 'test', network: '4178463' @@ -43,7 +43,7 @@ describe('rasBidAdapter', function () { it('should return nothing when bid request is malformed', function () { const failBid = { sizes: [[300, 250], [300, 300]], - bidder: 'ras', + bidder: 'ringieraxelspringer', }; expect(spec.isBidRequestValid(failBid)).to.equal(undefined); }); @@ -52,7 +52,7 @@ describe('rasBidAdapter', function () { describe('buildRequests', function () { const bid = { sizes: [[300, 250], [300, 600]], - bidder: 'ras', + bidder: 'ringieraxelspringer', bidId: 1, params: { slot: 'test', @@ -81,7 +81,7 @@ describe('rasBidAdapter', function () { }; const bid2 = { sizes: [[750, 300]], - bidder: 'ras', + bidder: 'ringieraxelspringer', bidId: 2, params: { slot: 'test2', @@ -157,8 +157,10 @@ describe('rasBidAdapter', function () { expect(requests[0].url).to.have.string('id0=1'); expect(requests[0].url).to.have.string('iusizes0=300x250%2C300x600'); expect(requests[0].url).to.have.string('slot1=test2'); + expect(requests[0].url).to.have.string('kvhb_format0=banner'); expect(requests[0].url).to.have.string('id1=2'); expect(requests[0].url).to.have.string('iusizes1=750x300'); + expect(requests[0].url).to.have.string('kvhb_format1=banner'); expect(requests[0].url).to.have.string('site=test'); expect(requests[0].url).to.have.string('area=areatest'); expect(requests[0].url).to.have.string('cre_format=html'); @@ -299,14 +301,14 @@ describe('rasBidAdapter', function () { } }]; const resp = spec.interpretResponse({body: {gctx: '1234567890'}}, bidRequest); - expect(resp).to.deep.equal({bids: [], fledgeAuctionConfigs: auctionConfigs}); + expect(resp).to.deep.equal({bids: [], paapi: auctionConfigs}); }); }); describe('buildNativeRequests', function () { const bid = { sizes: 'fluid', - bidder: 'ras', + bidder: 'ringieraxelspringer', bidId: 1, params: { slot: 'nativestd', @@ -365,6 +367,7 @@ describe('rasBidAdapter', function () { expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); expect(requests[0].url).to.have.string('test=name%3Dvalue'); expect(requests[0].url).to.have.string('cre_format0=native'); + expect(requests[0].url).to.have.string('kvhb_format0=native'); expect(requests[0].url).to.have.string('iusizes0=fluid'); }); }); @@ -407,6 +410,8 @@ describe('rasBidAdapter', function () { title: 'Headline', image: '//img.url', url: '//link.url', + partner_logo: '//logo.url', + adInfo: 'REKLAMA', impression: '//impression.url', impression1: '//impression1.url', impressionJs1: '//impressionJs1.url' @@ -444,9 +449,10 @@ describe('rasBidAdapter', function () { Calltoaction: 'Calltoaction', Headline: 'Headline', Image: '//img.url', - Sponsorlabel: 'nie', + adInfo: 'REKLAMA', Thirdpartyclicktracker: '//link.url', - imp: '//imp.url' + imp: '//imp.url', + thirdPartyClickTracker2: '//thirdPartyClickTracker.url' }, meta: { slot: 'nativestd', @@ -465,29 +471,54 @@ describe('rasBidAdapter', function () { ver: '1.2', assets: [ { - id: 2, + id: 0, + data: { + value: '', + type: 2 + }, + }, + { + id: 1, + data: { + value: 'REKLAMA', + type: 10 + }, + }, + { + id: 3, img: { - url: '//img.url', + type: 1, + url: '//logo.url', w: 1, h: 1 } }, { id: 4, - title: { - text: 'Headline' + img: { + type: 3, + url: '//img.url', + w: 1, + h: 1 } }, { - id: 3, + id: 5, data: { value: 'Test Onet', type: 1 + }, + }, + { + id: 6, + title: { + text: 'Headline' } - } + }, ], link: { - url: '//adclick.url//link.url' + url: '//adclick.url//link.url', + clicktrackers: [] }, eventtrackers: [ { @@ -521,9 +552,15 @@ describe('rasBidAdapter', function () { width: 1, height: 1 }, + icon: { + url: '//logo.url', + width: 1, + height: 1 + }, clickUrl: '//adclick.url//link.url', cta: '', body: 'BODY', + body2: 'REKLAMA', sponsoredBy: 'Test Onet', ortb: expectedTeaserStandardOrtbResponse, privacyLink: '//dsa.url' @@ -532,29 +569,54 @@ describe('rasBidAdapter', function () { ver: '1.2', assets: [ { - id: 2, + id: 0, + data: { + value: '', + type: 2 + }, + }, + { + id: 1, + data: { + value: 'REKLAMA', + type: 10 + }, + }, + { + id: 3, img: { - url: '//img.url', + type: 1, + url: '', w: 1, h: 1 } }, { id: 4, - title: { - text: 'Headline' + img: { + type: 3, + url: '//img.url', + w: 1, + h: 1 } }, { - id: 3, + id: 5, data: { value: 'Test Onet', type: 1 + }, + }, + { + id: 6, + title: { + text: 'Headline' } - } + }, ], link: { - url: '//adclick.url//link.url' + url: '//adclick.url//link.url', + clicktrackers: ['//thirdPartyClickTracker.url'] }, eventtrackers: [ { @@ -578,9 +640,15 @@ describe('rasBidAdapter', function () { width: 1, height: 1 }, + icon: { + url: '', + width: 1, + height: 1 + }, clickUrl: '//adclick.url//link.url', cta: 'Calltoaction', body: 'BODY', + body2: 'REKLAMA', sponsoredBy: 'Test Onet', ortb: expectedNativeInFeedOrtbResponse, privacyLink: '//dsa.url' diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 77b746b9b69..cc303dc2f96 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -43,12 +43,12 @@ describe('RTBHouseAdapter', () => { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -460,7 +460,7 @@ describe('RTBHouseAdapter', () => { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: true }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids'); expect(request.method).to.equal('POST'); }); @@ -470,7 +470,7 @@ describe('RTBHouseAdapter', () => { delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: false }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, {...bidderRequest, paapi: {enabled: true}}); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -490,7 +490,7 @@ describe('RTBHouseAdapter', () => { decisionLogicUrl: 'https://sellers.domain/decision.url' } }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, {...bidderRequest, paapi: {enabled: true}}); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -506,7 +506,7 @@ describe('RTBHouseAdapter', () => { bidRequest[0].ortb2Imp = { ext: { ae: 2 } }; - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: false }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: false} }); let data = JSON.parse(request.data); if (data.imp[0].ext) { expect(data.imp[0].ext).to.not.have.property('ae'); @@ -519,7 +519,7 @@ describe('RTBHouseAdapter', () => { bidRequest[0].ortb2Imp = { ext: { ae: 2 } }; - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); let data = JSON.parse(request.data); expect(data.imp[0].ext.ae).to.equal(2); }); @@ -782,9 +782,9 @@ describe('RTBHouseAdapter', () => { it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test-bid-id'); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 494943f9f7d..9e25300e10b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -14,7 +14,7 @@ import * as utils from 'src/utils.js'; import {find} from 'src/polyfill.js'; import {createEidsArray} from 'modules/userId/eids.js'; import 'modules/schain.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/userId/index.js'; import 'modules/priceFloors.js'; @@ -3695,14 +3695,14 @@ describe('the rubicon adapter', function () { }] }; - let {bids, fledgeAuctionConfigs} = spec.interpretResponse({body: response}, { + let {bids, paapi} = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); expect(bids).to.be.lengthOf(1); - expect(fledgeAuctionConfigs[0].bidId).to.equal('5432'); - expect(fledgeAuctionConfigs[0].config.random).to.equal('value'); - expect(fledgeAuctionConfigs[1].bidId).to.equal('6789'); + expect(paapi[0].bidId).to.equal('5432'); + expect(paapi[0].config.random).to.equal('value'); + expect(paapi[1].bidId).to.equal('6789'); }); it('should handle an error', function () { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index ab099d87429..8fc29a2cef3 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -755,7 +755,7 @@ describe('sharethrough adapter spec', function () { const EXPECTED_AE_VALUE = 1; // ACT - bidderRequest['fledgeEnabled'] = true; + bidderRequest.paapi = {enabled: true}; const builtRequests = spec.buildRequests(bidRequests, bidderRequest); const ACTUAL_AE_VALUE = builtRequests[0].data.imp[0].ext.ae; diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 3965cd69c5f..93864064dd8 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -18,7 +18,7 @@ import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; import {deepAccess} from 'src/utils.js'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId', 'digitrustid']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'exchange'; @@ -511,12 +511,8 @@ describe('ShinezRtbBidAdapter', function () { const userId = (function () { switch (idSystemProvider) { - case 'digitrustid': - return {data: {id}}; case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: diff --git a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js deleted file mode 100644 index 1d8e38f19ec..00000000000 --- a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import sigmoidAnalytic from 'modules/sigmoidAnalyticsAdapter.js'; -import {expect} from 'chai'; -import {expectEvents} from '../../helpers/analytics.js'; - -let events = require('src/events'); -let adapterManager = require('src/adapterManager').default; - -describe('sigmoid Prebid Analytic', function () { - after(function () { - sigmoidAnalytic.disableAnalytics(); - }); - - describe('enableAnalytics', function () { - beforeEach(function () { - sinon.spy(sigmoidAnalytic, 'track'); - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - sigmoidAnalytic.track.restore(); - events.getEvents.restore(); - }); - it('should catch all events', function () { - adapterManager.registerAnalyticsAdapter({ - code: 'sigmoid', - adapter: sigmoidAnalytic - }); - - adapterManager.enableAnalytics({ - provider: 'sigmoid', - options: { - publisherIds: ['test_sigmoid_prebid_analytid_publisher_id'] - } - }); - - expectEvents().to.beTrackedBy(sigmoidAnalytic.track); - }); - }); - describe('build utm tag data', function () { - beforeEach(function () { - localStorage.setItem('sigmoid_analytics_utm_source', 'utm_source'); - localStorage.setItem('sigmoid_analytics_utm_medium', 'utm_medium'); - localStorage.setItem('sigmoid_analytics_utm_campaign', ''); - localStorage.setItem('sigmoid_analytics_utm_term', ''); - localStorage.setItem('sigmoid_analytics_utm_content', ''); - localStorage.setItem('sigmoid_analytics_utm_timeout', Date.now()); - }); - it('should build utm data from local storage', function () { - let utmTagData = sigmoidAnalytic.buildUtmTagData(); - expect(utmTagData.utm_source).to.equal('utm_source'); - expect(utmTagData.utm_medium).to.equal('utm_medium'); - expect(utmTagData.utm_campaign).to.equal(''); - expect(utmTagData.utm_term).to.equal(''); - expect(utmTagData.utm_content).to.equal(''); - }); - }); -}); diff --git a/test/spec/modules/silvermobBidAdapter_spec.js b/test/spec/modules/silvermobBidAdapter_spec.js index 7d7fbacc04e..3ff3dfbfe2d 100644 --- a/test/spec/modules/silvermobBidAdapter_spec.js +++ b/test/spec/modules/silvermobBidAdapter_spec.js @@ -11,7 +11,7 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; diff --git a/test/spec/modules/slimcutBidAdapter_spec.js b/test/spec/modules/slimcutBidAdapter_spec.js index da0fee48936..64ddac71899 100644 --- a/test/spec/modules/slimcutBidAdapter_spec.js +++ b/test/spec/modules/slimcutBidAdapter_spec.js @@ -35,26 +35,26 @@ describe('slimcutBidAdapter', function() { expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when placementId is not valid (letters)', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when placementId < 0', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': -1 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); describe('buildRequests', function() { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 802fa8c254b..b7a9a43b6a0 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -8,7 +8,7 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index c3d0711632e..d8ddf7a398b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -178,7 +178,6 @@ describe('The smartx adapter', function () { 2, 3, 5, 6 ], startdelay: 0, - placement: 1, pos: 1 }); @@ -208,10 +207,6 @@ describe('The smartx adapter', function () { sdk_name: 'Prebid 1+' }); - expect(request.data.imp[0].video).to.contain({ - placement: 1 - }); - bid.mediaTypes.video.context = 'outstream'; bid.params = { @@ -251,10 +246,6 @@ describe('The smartx adapter', function () { expect(request.data.imp[0].video.startdelay).to.equal(1); - expect(request.data.imp[0].video).to.contain({ - placement: 3 - }); - expect(request.data.imp[0].bidfloor).to.equal(55); expect(request.data.imp[0].bidfloorcur).to.equal('foo'); diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js deleted file mode 100644 index c34de91dd9f..00000000000 --- a/test/spec/modules/sonobiAnalyticsAdapter_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import sonobiAnalytics, {DEFAULT_EVENT_URL} from 'modules/sonobiAnalyticsAdapter.js'; -import {expect} from 'chai'; -import {server} from 'test/mocks/xhr.js'; -import { EVENTS } from 'src/constants.js'; - -let events = require('src/events'); -let adapterManager = require('src/adapterManager').default; - -describe('Sonobi Prebid Analytic', function () { - var clock; - - describe('enableAnalytics', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - clock = sinon.useFakeTimers(Date.now()); - }); - - afterEach(function () { - events.getEvents.restore(); - clock.restore(); - }); - - after(function () { - sonobiAnalytics.disableAnalytics(); - }); - - it('should catch all events', function (done) { - const initOptions = { - pubId: 'A3B254F', - siteId: '1234', - delay: 100 - }; - - sonobiAnalytics.enableAnalytics(initOptions) - - const bid = { - bidderCode: 'sonobi_test_bid', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '1234', - auctionId: '13', - responseTimestamp: 1496410856397, - requestTimestamp: 1496410856295, - cpm: 1.13, - bidder: 'sonobi', - adUnitCode: 'dom-sample-id', - timeToRespond: 100, - placementCode: 'placementtest' - }; - - // Step 1: Initialize adapter - adapterManager.enableAnalytics({ - provider: 'sonobi', - options: initOptions - }); - - // Step 2: Send init auction event - events.emit(EVENTS.AUCTION_INIT, { config: initOptions, auctionId: '13', timestamp: Date.now() }); - - expect(sonobiAnalytics.initOptions).to.have.property('pubId', 'A3B254F'); - expect(sonobiAnalytics.initOptions).to.have.property('siteId', '1234'); - expect(sonobiAnalytics.initOptions).to.have.property('delay', 100); - // Step 3: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); - - // Step 4: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bid); - - // Step 5: Send bid won event - events.emit(EVENTS.BID_WON, bid); - - // Step 6: Send bid timeout event - events.emit(EVENTS.BID_TIMEOUT, { auctionId: '13' }); - - // Step 7: Send auction end event - events.emit(EVENTS.AUCTION_END, { auctionId: '13', bidsReceived: [bid] }); - - clock.tick(5000); - const req = server.requests.find(req => req.url.indexOf(DEFAULT_EVENT_URL) !== -1); - expect(JSON.parse(req.requestBody)).to.have.length(3) - done(); - }); - }); -}); diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js deleted file mode 100644 index 7945bdc9910..00000000000 --- a/test/spec/modules/sovrnAnalyticsAdapter_spec.js +++ /dev/null @@ -1,530 +0,0 @@ -import sovrnAnalyticsAdapter from '../../../modules/sovrnAnalyticsAdapter.js'; -import {expect} from 'chai'; -import {config} from 'src/config.js'; -import adaptermanager from 'src/adapterManager.js'; -import {server} from 'test/mocks/xhr.js'; -import {expectEvents, fireEvents} from '../../helpers/analytics.js'; -import { EVENTS } from 'src/constants.js'; - -var assert = require('assert'); - -let events = require('src/events'); - -/** - * Emit analytics events - * @param {Array} eventType - array of objects to define the events that will fire - * @param {object} event - key is eventType, value is event - * @param {string} auctionId - the auction id to attached to the events - */ -function emitEvent(eventType, event, auctionId) { - event.auctionId = auctionId; - events.emit(EVENTS[eventType], event); -} - -let auctionStartTimestamp = Date.now(); -let timeout = 3000; -let auctionInit = { - timestamp: auctionStartTimestamp, - timeout: timeout -}; -let bidderCode = 'sovrn'; -let bidderRequestId = '123bri'; -let adUnitCode = 'div'; -let adUnitCode2 = 'div2'; -let bidId = 'bidid'; -let bidId2 = 'bidid2'; -let tId = '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a'; -let tId2 = '99dca3ee-a80a-46d7-a4a0-cbcba463d97e'; -let bidRequested = { - auctionStart: auctionStartTimestamp, - bidderCode: bidderCode, - bidderRequestId: bidderRequestId, - bids: [ - { - adUnitCode: adUnitCode, - bidId: bidId, - bidder: bidderCode, - bidderRequestId: '10340af0c7dc72', - sizes: [[300, 250]], - startTime: auctionStartTimestamp + 100, - transactionId: tId - }, - { - adUnitCode: adUnitCode2, - bidId: bidId2, - bidder: bidderCode, - bidderRequestId: '10340af0c7dc72', - sizes: [[300, 250]], - startTime: auctionStartTimestamp + 100, - transactionId: tId2 - } - ], - doneCbCallCount: 1, - start: auctionStartTimestamp, - timeout: timeout -}; -let bidResponse = { - bidderCode: bidderCode, - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '3870e27a5752fb', - mediaType: 'banner', - source: 'client', - requestId: bidId, - cpm: 0.8584999918937682, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ad: '
divvy mcdiv
', - ttl: 60000, - responseTimestamp: auctionStartTimestamp + 150, - requestTimestamp: auctionStartTimestamp + 100, - bidder: bidderCode, - adUnitCode: adUnitCode, - timeToRespond: 50, - pbLg: '0.50', - pbMg: '0.80', - pbHg: '0.85', - pbAg: '0.85', - pbDg: '0.85', - pbCg: '', - size: '300x250', - adserverTargeting: { - hb_bidder: bidderCode, - hb_adid: '3870e27a5752fb', - hb_pb: '0.85' - }, - status: 'rendered' -}; - -let bidResponse2 = { - bidderCode: bidderCode, - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '9999e27a5752fb', - mediaType: 'banner', - source: 'client', - requestId: bidId2, - cpm: 0.12, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ad: '
divvy mcdiv
', - ttl: 60000, - responseTimestamp: auctionStartTimestamp + 150, - requestTimestamp: auctionStartTimestamp + 100, - bidder: bidderCode, - adUnitCode: adUnitCode2, - timeToRespond: 50, - pbLg: '0.10', - pbMg: '0.10', - pbHg: '0.10', - pbAg: '0.10', - pbDg: '0.10', - pbCg: '', - size: '300x250', - adserverTargeting: { - hb_bidder: bidderCode, - hb_adid: '9999e27a5752fb', - hb_pb: '0.10' - }, - status: 'rendered' -}; -let bidAdjustment = {}; -for (var k in bidResponse) bidAdjustment[k] = bidResponse[k]; -bidAdjustment.cpm = 0.8; -let bidAdjustmentNoMatchingRequest = { - bidderCode: 'not-sovrn', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '1', - mediaType: 'banner', - source: 'client', - requestId: '1', - cpm: 0.10, - creativeId: '', - dealId: null, - currency: 'USD', - netRevenue: true, - ad: '
divvy mcdiv
', - ttl: 60000, - responseTimestamp: auctionStartTimestamp + 150, - requestTimestamp: auctionStartTimestamp + 100, - bidder: 'not-sovrn', - adUnitCode: '', - timeToRespond: 50, - pbLg: '0.00', - pbMg: '0.10', - pbHg: '0.10', - pbAg: '0.10', - pbDg: '0.10', - pbCg: '', - size: '300x250', - adserverTargeting: { - hb_bidder: 'not-sovrn', - hb_adid: '1', - hb_pb: '0.10' - }, -}; -let bidResponseNoMatchingRequest = bidAdjustmentNoMatchingRequest; - -describe('Sovrn Analytics Adapter', function () { - beforeEach(() => { - sinon.stub(events, 'getEvents').returns([]); - }); - afterEach(() => { - events.getEvents.restore(); - }); - - describe('enableAnalytics ', function () { - beforeEach(() => { - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - - it('should catch all events if affiliate id present', function () { - adaptermanager.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - expectEvents().to.beTrackedBy(sovrnAnalyticsAdapter.track); - }); - - it('should catch no events if no affiliate id', function () { - adaptermanager.enableAnalytics({ - provider: 'sovrn', - options: { - } - }); - fireEvents(); - sinon.assert.callCount(sovrnAnalyticsAdapter.track, 0); - }); - }); - - describe('sovrnAnalyticsAdapter ', function() { - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should have correct type', function () { - assert.equal(sovrnAnalyticsAdapter.getAdapterType(), 'endpoint') - }) - }); - - describe('auction data collector ', function() { - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should create auctiondata record from init ', function () { - let auctionId = '123.123.123.123'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - assert(currentAuction); - let expectedTimeOutData = { - buffer: config.getConfig('timeoutBuffer'), - bidder: config.getConfig('bidderTimeout'), - }; - expect(currentAuction.auction.timeouts).to.deep.equal(expectedTimeOutData); - assert.equal(currentAuction.auction.payload, 'auction'); - assert.equal(currentAuction.auction.priceGranularity, config.getConfig('priceGranularity')) - assert.equal(currentAuction.auction.auctionId, auctionId); - assert.equal(currentAuction.auction.sovrnId, 123); - }); - it('should create a bidrequest object ', function() { - let auctionId = '234.234.234.234'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - assert(currentAuction); - let requests = currentAuction.auction.requests; - assert(requests); - assert.equal(requests.length, 1); - assert.equal(requests[0].bidderCode, bidderCode); - assert.equal(requests[0].bidderRequestId, bidderRequestId); - assert.equal(requests[0].timeout, timeout); - let bids = requests[0].bids; - assert(bids); - assert.equal(bids.length, 2); - assert.equal(bids[0].bidId, bidId); - assert.equal(bids[0].bidder, bidderCode); - assert.equal(bids[0].transactionId, tId); - assert.equal(bids[0].sizes.length, 1); - assert.equal(bids[0].sizes[0][0], 300); - assert.equal(bids[0].sizes[0][1], 250); - expect(requests[0]).to.not.have.property('doneCbCallCount'); - expect(requests[0]).to.not.have.property('auctionId'); - }); - it('should add results to the bid with response ', function () { - let auctionId = '345.345.345.345'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_RESPONSE', bidResponse, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - let returnedBid = currentAuction.auction.requests[0].bids[0]; - assert.equal(returnedBid.bidId, bidId); - assert.equal(returnedBid.bidder, bidderCode); - assert.equal(returnedBid.transactionId, tId); - assert.equal(returnedBid.sizes.length, 1); - assert.equal(returnedBid.sizes[0][0], 300); - assert.equal(returnedBid.sizes[0][1], 250); - assert.equal(returnedBid.adserverTargeting.hb_adid, '3870e27a5752fb'); - assert.equal(returnedBid.adserverTargeting.hb_bidder, bidderCode); - assert.equal(returnedBid.adserverTargeting.hb_pb, '0.85'); - assert.equal(returnedBid.cpm, 0.8584999918937682); - }); - it('should add new unsynced bid if no request exists for response ', function () { - let auctionId = '456.456.456.456'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_RESPONSE', bidResponseNoMatchingRequest, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - let requests = currentAuction.auction.requests; - assert(requests); - assert.equal(requests.length, 1); - let bidRequest = requests[0].bids[0]; - expect(bidRequest).to.not.have.property('adserverTargeting'); - expect(bidRequest).to.not.have.property('cpm'); - expect(currentAuction.auction.unsynced[0]).to.deep.equal(bidResponseNoMatchingRequest); - }); - it('should adjust the bid ', function () { - let auctionId = '567.567.567.567'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_ADJUSTMENT', bidResponse, auctionId); - emitEvent('BID_RESPONSE', bidAdjustment, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - let returnedBid = currentAuction.auction.requests[0].bids[0]; - assert.equal(returnedBid.cpm, 0.8); - assert.equal(returnedBid.originalValues.cpm, 0.8584999918937682); - }); - }); - describe('auction data send ', function() { - let expectedPostBody = { - sovrnId: 123, - auctionId: '678.678.678.678', - payload: 'auction', - priceGranularity: 'medium', - }; - let expectedRequests = { - bidderCode: 'sovrn', - bidderRequestId: '123bri', - timeout: 3000 - }; - let expectedBids = { - adUnitCode: 'div', - bidId: 'bidid', - bidder: 'sovrn', - bidderRequestId: '10340af0c7dc72', - transactionId: '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '3870e27a5752fb', - mediaType: 'banner', - source: 'client', - cpm: 0.8584999918937682, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ttl: 60000, - timeToRespond: 50, - size: '300x250', - status: 'rendered', - isAuctionWinner: true - }; - let SecondAdUnitExpectedBids = { - adUnitCode: 'div2', - bidId: 'bidid2', - bidder: 'sovrn', - bidderRequestId: '10340af0c7dc72', - transactionId: '99dca3ee-a80a-46d7-a4a0-cbcba463d97e', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '9999e27a5752fb', - mediaType: 'banner', - source: 'client', - cpm: 0.12, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ttl: 60000, - timeToRespond: 50, - size: '300x250', - status: 'rendered', - isAuctionWinner: true - }; - let expectedAdServerTargeting = { - hb_bidder: 'sovrn', - hb_adid: '3870e27a5752fb', - hb_pb: '0.85' - }; - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should send auction data ', function () { - let auctionId = '678.678.678.678'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_RESPONSE', bidResponse, auctionId); - emitEvent('BID_RESPONSE', bidResponse2, auctionId) - emitEvent('AUCTION_END', {}, auctionId); - let requestBody = JSON.parse(server.requests[0].requestBody); - let requestsFromRequestBody = requestBody.requests[0]; - let bidsFromRequests = requestsFromRequestBody.bids[0]; - expect(requestBody).to.deep.include(expectedPostBody); - expect(requestBody.timeouts).to.deep.equal({buffer: 400, bidder: 3000}); - expect(requestsFromRequestBody).to.deep.include(expectedRequests); - expect(bidsFromRequests).to.deep.include(expectedBids); - let bidsFromRequests2 = requestsFromRequestBody.bids[1]; - expect(bidsFromRequests2).to.deep.include(SecondAdUnitExpectedBids); - expect(bidsFromRequests.adserverTargeting).to.deep.include(expectedAdServerTargeting); - }); - }); - describe('bid won data send ', function() { - let auctionId = '789.789.789.789'; - let creativeId = 'cridprebidrtb'; - let requestId = 'requestId69'; - let bidWonEvent = { - ad: 'html', - adId: 'adId', - adUnitCode: adUnitCode, - auctionId: auctionId, - bidder: bidderCode, - bidderCode: bidderCode, - cpm: 1.01, - creativeId: creativeId, - currency: 'USD', - height: 250, - mediaType: 'banner', - requestId: requestId, - size: '300x250', - source: 'client', - status: 'rendered', - statusMessage: 'Bid available', - timeToRespond: 421, - ttl: 60, - width: 300 - }; - let expectedBidWonBody = { - sovrnId: 123, - payload: 'winner' - }; - let expectedWinningBid = { - bidderCode: bidderCode, - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: 'adId', - mediaType: 'banner', - source: 'client', - requestId: requestId, - cpm: 1.01, - creativeId: creativeId, - currency: 'USD', - ttl: 60, - auctionId: auctionId, - bidder: bidderCode, - adUnitCode: adUnitCode, - timeToRespond: 421, - size: '300x250', - }; - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should send bid won data ', function () { - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_WON', bidWonEvent, auctionId); - let requestBody = JSON.parse(server.requests[0].requestBody); - expect(requestBody).to.deep.include(expectedBidWonBody); - expect(requestBody.winningBid).to.deep.include(expectedWinningBid); - }); - }); - describe('Error Tracking', function() { - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics() - sovrnAnalyticsAdapter.track.restore() - }); - it('should send an error message when a bid is received for a closed auction', function() { - let auctionId = '678.678.678.678'; - emitEvent('AUCTION_INIT', auctionInit, auctionId) - emitEvent('BID_REQUESTED', bidRequested, auctionId) - emitEvent('AUCTION_END', {}, auctionId) - server.requests[0].respond(200) - emitEvent('BID_RESPONSE', bidResponse, auctionId) - let requestBody = JSON.parse(server.requests[1].requestBody) - expect(requestBody.payload).to.equal('error') - expect(requestBody.message).to.include('Event Received after Auction Close Auction Id') - }) - }) -}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 10f5ab8e89d..2d6af1f964f 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -243,7 +243,7 @@ describe('sovrnBidAdapter', function() { it('when FLEDGE is enabled, should send ortb2imp.ext.ae', function () { const bidderRequest = { ...baseBidderRequest, - fledgeEnabled: true + paapi: {enabled: true} } const bidRequest = { ...baseBidRequest, @@ -273,7 +273,9 @@ describe('sovrnBidAdapter', function() { it('when FLEDGE is enabled, but env is malformed, should not send ortb2imp.ext.ae', function () { const bidderRequest = { ...baseBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } } const bidRequest = { ...baseBidRequest, @@ -968,9 +970,9 @@ describe('sovrnBidAdapter', function() { it('should return valid fledge auction configs alongside bids', function () { const result = spec.interpretResponse(fledgeResponse) expect(result).to.have.property('bids') - expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(2) - expect(result.fledgeAuctionConfigs).to.deep.equal(expectedFledgeResponse) + expect(result).to.have.property('paapi') + expect(result.paapi.length).to.equal(2) + expect(result.paapi).to.deep.equal(expectedFledgeResponse) }) it('should ignore empty fledge auction configs array', function () { const result = spec.interpretResponse(emptyFledgeResponse) diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js deleted file mode 100644 index ec99d0f7142..00000000000 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ /dev/null @@ -1,711 +0,0 @@ -import {expect} from 'chai'; -import {config} from 'src/config.js'; -import {loadExternalScript} from '../../../src/adloader'; -import {isRendererRequired} from '../../../src/Renderer'; -import {spec, GOOGLE_CONSENT} from 'modules/spotxBidAdapter.js'; - -describe('the spotx adapter', function () { - function getValidBidObject() { - return { - bidId: 123, - mediaTypes: { - video: { - playerSize: [['300', '200']] - } - }, - params: { - channel_id: 12345, - } - }; - }; - - describe('isBidRequestValid', function() { - let bid; - - beforeEach(function() { - bid = getValidBidObject(); - }); - - it('should fail validation if the bid isn\'t defined or not an object', function() { - let result = spec.isBidRequestValid(); - - expect(result).to.equal(false); - - result = spec.isBidRequestValid('not an object'); - - expect(result).to.equal(false); - }); - - it('should succeed validation with all the right parameters', function() { - expect(spec.isBidRequestValid(getValidBidObject())).to.equal(true); - }); - - it('should succeed validation with mediaType and outstream_function or outstream_options', function() { - bid.mediaType = 'video'; - bid.params.outstream_function = 'outstream_func'; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - - delete bid.params.outstream_function; - bid.params.outstream_options = { - slot: 'elemID' - }; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should succeed with ad_unit outstream and outstream function set', function() { - bid.params.ad_unit = 'outstream'; - bid.params.outstream_function = function() {}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should succeed with ad_unit outstream, options set for outstream and slot provided', function() { - bid.params.ad_unit = 'outstream'; - bid.params.outstream_options = {slot: 'ad_container_id'}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should fail without a channel_id', function() { - delete bid.params.channel_id; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail without playerSize', function() { - delete bid.mediaTypes.video.playerSize; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail without video', function() { - delete bid.mediaTypes.video; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail with ad_unit outstream but no options set for outstream', function() { - bid.params.ad_unit = 'outstream'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail with ad_unit outstream, options set for outstream but no slot provided', function() { - bid.params.ad_unit = 'outstream'; - bid.params.outstream_options = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function() { - let bid, bidRequestObj; - - beforeEach(function() { - bid = getValidBidObject(); - bidRequestObj = { - refererInfo: { - page: 'prebid.js' - } - }; - }); - - it('should build a very basic request', function() { - let request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345?src_sys=prebid'); - expect(request.bidRequest).to.equal(bidRequestObj); - expect(request.data.id).to.equal(12345); - expect(request.data.ext.wrap_response).to.equal(1); - expect(request.data.imp.id).to.match(/\d+/); - expect(request.data.imp.secure).to.equal(0); - expect(request.data.imp.video).to.deep.equal({ - ext: { - sdk_name: 'Prebid 1+', - versionOrtb: '2.3' - }, - h: '200', - mimes: [ - 'application/javascript', - 'video/mp4', - 'video/webm' - ], - w: '300' - }); - expect(request.data.site).to.deep.equal({ - content: 'content', - id: '', - page: 'prebid.js' - }); - }); - - it('should change request parameters based on options sent', function() { - let request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.ext).to.deep.equal({ - sdk_name: 'Prebid 1+', - versionOrtb: '2.3' - }); - - bid.params = { - channel_id: 54321, - ad_mute: 1, - hide_skin: 1, - ad_volume: 1, - ad_unit: 'incontent', - outstream_options: {foo: 'bar'}, - outstream_function: '987', - custom: {bar: 'foo'}, - start_delay: true, - number_of_ads: 2, - spotx_all_google_consent: 1, - min_duration: 5, - max_duration: 10, - placement_type: 1, - position: 1 - }; - - bid.userIdAsEids = [{ - source: 'adserver.org', - uids: [{id: 'tdid_1', atype: 1, ext: {rtiPartner: 'TDID'}}] - }, - { - source: 'id5-sync.com', - uids: [{id: 'id5id_1', ext: {}}] - }, - { - source: 'uidapi.com', - uids: [{ - id: 'uid_1', - atype: 3 - }] - } - ]; - - bid.crumbs = { - pubcid: 'pubcid_1' - }; - - bid.schain = { - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1 - } - ] - } - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.id).to.equal(54321); - expect(request.data.imp.video).to.contain({ - minduration: 5, - maxduration: 10 - }) - expect(request.data.imp.video.ext).to.deep.equal({ - ad_volume: 1, - hide_skin: 1, - ad_unit: 'incontent', - outstream_options: {foo: 'bar'}, - outstream_function: '987', - custom: {bar: 'foo'}, - sdk_name: 'Prebid 1+', - versionOrtb: '2.3', - placement: 1, - pos: 1 - }); - - expect(request.data.imp.video.startdelay).to.equal(1); - expect(request.data.ext).to.deep.equal({ - number_of_ads: 2, - wrap_response: 1 - }); - expect(request.data.user.ext).to.deep.equal({ - consented_providers_settings: GOOGLE_CONSENT, - eids: [{ - source: 'adserver.org', - uids: [{ - id: 'tdid_1', - atype: 1, - ext: { - rtiPartner: 'TDID' - } - }] - }, { - source: 'id5-sync.com', - uids: [{ - id: 'id5id_1', - ext: {} - }] - }, - { - source: 'uidapi.com', - uids: [{ - id: 'uid_1', - atype: 3, - ext: { - rtiPartner: 'UID2' - } - }] - }], - fpc: 'pubcid_1' - }); - - expect(request.data.source).to.deep.equal({ - ext: { - schain: { - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1 - } - ] - } - } - }) - }); - - it('should process premarket bids', function() { - let request; - sinon.stub(Date, 'now').returns(1000); - - bid.params.pre_market_bids = [{ - vast_url: 'prebid.js', - deal_id: '123abc', - price: 12, - currency: 'USD' - }]; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.ext.pre_market_bids).to.deep.equal([ - { - 'cur': 'USD', - 'ext': { - 'event_log': [ - {} - ] - }, - 'id': '123abc', - 'seatbid': [ - { - 'bid': [ - { - 'adm': 'prebid.js', - 'dealid': '123abc', - 'impid': 1000, - 'price': 12, - } - ] - } - ] - } - ]); - Date.now.restore(); - }); - - it('should pass GDPR params', function() { - let request; - - bidRequestObj.gdprConsent = { - consentString: 'consent123', - gdprApplies: true - }; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.regs.ext.gdpr).to.equal(1); - expect(request.data.user.ext.consent).to.equal('consent123'); - }); - - it('should pass CCPA us_privacy string', function() { - let request; - - bidRequestObj.uspConsent = '1YYY' - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('should pass both GDPR params and CCPA us_privacy', function() { - let request; - - bidRequestObj.gdprConsent = { - consentString: 'consent123', - gdprApplies: true - }; - bidRequestObj.uspConsent = '1YYY' - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.regs.ext.gdpr).to.equal(1); - expect(request.data.user.ext.consent).to.equal('consent123'); - expect(request.data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('should pass min and max duration params', function() { - let request; - - bid.params.min_duration = 3 - bid.params.max_duration = 15 - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.video.minduration).to.equal(3); - expect(request.data.imp.video.maxduration).to.equal(15); - }); - - it('should pass placement_type and position params', function() { - let request; - - bid.params.placement_type = 2 - bid.params.position = 5 - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.video.ext.placement).to.equal(2); - expect(request.data.imp.video.ext.pos).to.equal(5); - }); - - it('should pass page param and override refererInfo.referer', function() { - let request; - - bid.params.page = 'https://example.com'; - - let origGetConfig = config.getConfig; - sinon.stub(config, 'getConfig').callsFake(function (key) { - if (key === 'pageUrl') { - return 'https://www.spotx.tv'; - } - return origGetConfig.apply(config, arguments); - }); - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.site.page).to.equal('https://example.com'); - config.getConfig.restore(); - }); - - it('should use refererInfo.referer if no page is passed', function() { - let request; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.site.page).to.equal('prebid.js'); - }); - - it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { - let request; - - let origGetConfig = config.getConfig; - sinon.stub(config, 'getConfig').callsFake(function (key) { - if (key === 'cache') { - return { - url: 'prebidCacheLocation', - ignoreBidderCacheKey: true - }; - } - if (key === 'cache.url') { - return 'prebidCacheLocation'; - } - if (key === 'cache.ignoreBidderCacheKey') { - return true; - } - return origGetConfig.apply(config, arguments); - }); - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.ext.wrap_response).to.equal(0); - config.getConfig.restore(); - }); - - it('should pass price floor in USD from the floors module if available', function () { - let request; - - bid.getFloor = function () { - return { currency: 'USD', floor: 3 }; - } - - bid.params.price_floor = 2; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.bidfloor).to.equal(3); - }); - - it('should not pass price floor if price floors module gives a non-USD currency', function () { - let request; - - bid.getFloor = function () { - return { currency: 'EUR', floor: 3 }; - } - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.bidfloor).to.be.undefined; - }); - - it('if floors module is not available, should pass price floor from price_floor param if available', function () { - let request; - - bid.params.price_floor = 2; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.bidfloor).to.equal(2); - }); - }); - - describe('interpretResponse', function() { - let serverResponse, bidderRequestObj; - - beforeEach(function() { - bidderRequestObj = { - bidRequest: { - bids: [{ - mediaTypes: { - video: { - playerSize: [['400', '300']] - } - }, - bidId: 123, - params: { - ad_unit: 'outstream', - player_width: 400, - player_height: 300, - content_page_url: 'prebid.js', - ad_mute: 1, - outstream_options: {foo: 'bar'}, - outstream_function: 'function' - } - }, { - mediaTypes: { - video: { - playerSize: [['200', '100']] - } - }, - bidId: 124, - params: { - player_width: 200, - player_height: 100, - content_page_url: 'prebid.js', - ad_mute: 1, - outstream_options: {foo: 'bar'}, - outstream_function: 'function' - } - }] - } - }; - - serverResponse = { - body: { - id: 12345, - seatbid: [{ - bid: [{ - impid: 123, - cur: 'USD', - price: 12, - adomain: ['abc.com'], - crid: 321, - w: 400, - h: 300, - ext: { - cache_key: 'cache123', - slot: 'slot123' - } - }, { - impid: 124, - cur: 'USD', - price: 13, - adomain: ['def.com'], - w: 200, - h: 100, - ext: { - cache_key: 'cache124', - slot: 'slot124' - } - }] - }] - } - }; - }); - - it('should return an array of bid responses', function() { - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - expect(responses).to.be.an('array').with.length(2); - expect(responses[0].cache_key).to.equal('cache123'); - expect(responses[0].channel_id).to.equal(12345); - expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); - expect(responses[0].cpm).to.equal(12); - expect(responses[0].creativeId).to.equal(321); - expect(responses[0].currency).to.equal('USD'); - expect(responses[0].height).to.equal(300); - expect(responses[0].mediaType).to.equal('video'); - expect(responses[0].netRevenue).to.equal(true); - expect(responses[0].requestId).to.equal(123); - expect(responses[0].ttl).to.equal(360); - expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(responses[0].videoCacheKey).to.equal('cache123'); - expect(responses[0].width).to.equal(400); - expect(responses[1].cache_key).to.equal('cache124'); - expect(responses[1].channel_id).to.equal(12345); - expect(responses[1].cpm).to.equal(13); - expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); - expect(responses[1].creativeId).to.equal(''); - expect(responses[1].currency).to.equal('USD'); - expect(responses[1].height).to.equal(100); - expect(responses[1].mediaType).to.equal('video'); - expect(responses[1].netRevenue).to.equal(true); - expect(responses[1].requestId).to.equal(124); - expect(responses[1].ttl).to.equal(360); - expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); - expect(responses[1].videoCacheKey).to.equal('cache124'); - expect(responses[1].width).to.equal(200); - }); - - it('should set the renderer attached to the bid to render immediately', function () { - var renderer = spec.interpretResponse(serverResponse, bidderRequestObj)[0].renderer, - hasRun = false; - expect(renderer._render).to.be.a('function'); - renderer._render = () => { - hasRun = true; - } - renderer.render(); - expect(hasRun).to.equal(true); - }); - - it('should include the url property on the renderer for Prebid Core checks', function () { - var renderer = spec.interpretResponse(serverResponse, bidderRequestObj)[0].renderer; - expect(isRendererRequired(renderer)).to.be.true; - }); - }); - - describe('outstreamRender', function() { - let serverResponse, bidderRequestObj; - - beforeEach(function() { - sinon.stub(window.document, 'getElementById').returns({ - clientWidth: 200, - appendChild: sinon.stub().callsFake(function(script) {}) - }); - sinon.stub(window.document, 'createElement').returns({ - setAttribute: function () {} - }); - bidderRequestObj = { - bidRequest: { - bids: [{ - mediaTypes: { - video: { - playerSize: [['400', '300']] - } - }, - bidId: 123, - params: { - ad_unit: 'outstream', - player_width: 400, - player_height: 300, - content_page_url: 'prebid.js', - outstream_options: { - ad_mute: 1, - foo: 'bar', - slot: 'slot123', - playersize_auto_adapt: true, - custom_override: { - digitrust_opt_out: 1, - vast_url: 'bad_vast' - } - }, - } - }] - } - }; - - serverResponse = { - body: { - id: 12345, - seatbid: [{ - bid: [{ - impid: 123, - cur: 'USD', - price: 12, - crid: 321, - w: 400, - h: 300, - ext: { - cache_key: 'cache123', - slot: 'slot123' - } - }] - }] - } - }; - }); - afterEach(function () { - window.document.getElementById.restore(); - window.document.createElement.restore(); - }); - - it('should attempt to insert the EASI script', function() { - window.document.getElementById.restore(); - sinon.stub(window.document, 'getElementById').returns({ - appendChild: sinon.stub().callsFake(function(script) {}), - }); - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - let attrs; - - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.called).to.be.true; - attrs = valuesToString(loadExternalScript.args[0][4]); - - expect(attrs['data-spotx_channel_id']).to.equal('12345'); - expect(attrs['data-spotx_vast_url']).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(attrs['data-spotx_ad_unit']).to.equal('incontent'); - expect(attrs['data-spotx_collapse']).to.equal('0'); - expect(attrs['data-spotx_autoplay']).to.equal('1'); - expect(attrs['data-spotx_blocked_autoplay_override_mode']).to.equal('1'); - expect(attrs['data-spotx_video_slot_can_autoplay']).to.equal('1'); - expect(attrs['data-spotx_digitrust_opt_out']).to.equal('1'); - expect(attrs['data-spotx_content_width']).to.equal('400'); - expect(attrs['data-spotx_content_height']).to.equal('300'); - expect(attrs['data-spotx_ad_mute']).to.equal('1'); - }); - - it('should append into an iframe', function() { - bidderRequestObj.bidRequest.bids[0].params.outstream_options.in_iframe = 'iframeId'; - window.document.getElementById.restore(); - sinon.stub(window.document, 'getElementById').returns({ - nodeName: 'IFRAME', - clientWidth: 200, - appendChild: sinon.stub().callsFake(function(script) {}), - contentDocument: {nodeName: 'IFRAME'} - }); - - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.called).to.be.true; - expect(loadExternalScript.args[0][3].nodeName).to.equal('IFRAME'); - }); - - it('should adjust width and height to match slot clientWidth if playersize_auto_adapt is used', function() { - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.args[0][4]['data-spotx_content_width']).to.equal('200'); - expect(loadExternalScript.args[0][4]['data-spotx_content_height']).to.equal('150'); - }); - - it('should use a default 4/3 ratio if playersize_auto_adapt is used and response does not contain width or height', function() { - delete serverResponse.body.seatbid[0].bid[0].w; - delete serverResponse.body.seatbid[0].bid[0].h; - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.args[0][4]['data-spotx_content_width']).to.equal('200'); - expect(loadExternalScript.args[0][4]['data-spotx_content_height']).to.equal('150'); - }); - }); -}); - -function valuesToString(obj) { - let newObj = {}; - for (let prop in obj) { - newObj[prop] = '' + obj[prop]; - } - return newObj; -} diff --git a/test/spec/modules/staqAnalyticsAdapter_spec.js b/test/spec/modules/staqAnalyticsAdapter_spec.js deleted file mode 100644 index 3f28098e1d1..00000000000 --- a/test/spec/modules/staqAnalyticsAdapter_spec.js +++ /dev/null @@ -1,302 +0,0 @@ -import analyticsAdapter, { ExpiringQueue, getUmtSource, storage } from 'modules/staqAnalyticsAdapter.js'; -import { expect } from 'chai'; -import adapterManager from 'src/adapterManager.js'; -import { EVENTS } from 'src/constants.js'; - -const events = require('../../../src/events'); - -const DIRECT = { - source: '(direct)', - medium: '(direct)', - campaign: '(direct)' -}; -const REFERRER = { - source: 'lander.com', - medium: '(referral)', - campaign: '(referral)', - content: '/lander.html' -}; -const GOOGLE_ORGANIC = { - source: 'google', - medium: '(organic)', - campaign: '(organic)' -}; -const CAMPAIGN = { - source: 'adkernel', - medium: 'email', - campaign: 'new_campaign', - c1: '1', - c2: '2', - c3: '3', - c4: '4', - c5: '5' - -}; -describe('', function() { - let sandbox; - - before(function() { - sandbox = sinon.sandbox.create(); - }); - - after(function() { - sandbox.restore(); - analyticsAdapter.disableAnalytics(); - }); - - describe('UTM source parser', function() { - let stubSetItem; - let stubGetItem; - - before(function() { - stubSetItem = sandbox.stub(storage, 'setItem'); - stubGetItem = sandbox.stub(storage, 'getItem'); - }); - - afterEach(function() { - sandbox.reset(); - }); - - it('should parse first direct visit as (direct)', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://example.com'); - expect(source).to.be.eql(DIRECT); - }); - - it('should parse visit from google as organic', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://example.com', 'https://www.google.com/search?q=pikachu'); - expect(source).to.be.eql(GOOGLE_ORGANIC); - }); - - it('should parse referral visit', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://example.com', 'https://lander.com/lander.html'); - expect(source).to.be.eql(REFERRER); - }); - - it('should parse referral visit from same domain as direct', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://lander.com/news.html', 'https://lander.com/lander.html'); - expect(source).to.be.eql(DIRECT); - }); - - it('should parse campaign visit', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://lander.com/index.html?utm_campaign=new_campaign&utm_source=adkernel&utm_medium=email&utm_c1=1&utm_c2=2&utm_c3=3&utm_c4=4&utm_c5=5'); - expect(source).to.be.eql(CAMPAIGN); - }); - }); - - describe('ExpiringQueue', function() { - let timer; - before(function() { - timer = sandbox.useFakeTimers(0); - }); - after(function() { - timer.restore(); - }); - - it('should notify after timeout period', (done) => { - let queue = new ExpiringQueue(() => { - let elements = queue.popAll(); - expect(elements).to.be.eql([1, 2, 3, 4]); - elements = queue.popAll(); - expect(elements).to.have.lengthOf(0); - expect(Date.now()).to.be.equal(200); - done(); - }, 100); - - queue.push(1); - setTimeout(() => { - queue.push([2, 3]); - timer.tick(50); - }, 50); - setTimeout(() => { - queue.push([4]); - timer.tick(100); - }, 100); - timer.tick(50); - }); - }); - - const REQUEST = { - bidderCode: 'AppNexus', - bidderName: 'AppNexus', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: 'AppNexus', - params: {}, - adUnitCode: 'container-1', - transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', - sizes: [ - [300, 250] - ], - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }; - - const RESPONSE = { - bidderCode: 'AppNexus', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '208750227436c1', - mediaType: 'banner', - cpm: 0.015, - ad: '', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - responseTimestamp: 1509369418832, - requestTimestamp: 1509369418389, - bidder: 'AppNexus', - adUnitCode: 'container-1', - timeToRespond: 443, - size: '300x250' - }; - - const bidTimeoutArgsV1 = [{ - bidId: '2baa51527bd015', - bidderCode: 'AppNexus', - adUnitCode: 'container-1', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' - }, - { - bidId: '6fe3b4c2c23092', - bidderCode: 'AppNexus', - adUnitCode: 'container-2', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' - }]; - - describe('Analytics adapter', function() { - let ajaxStub; - let timer; - - before(function() { - ajaxStub = sandbox.stub(analyticsAdapter, 'ajaxCall'); - timer = sandbox.useFakeTimers(0); - }); - - beforeEach(function() { - sandbox.stub(events, 'getEvents').callsFake(() => { - return [] - }); - }); - - afterEach(function() { - events.getEvents.restore(); - }); - - it('should be configurable', function() { - adapterManager.registerAnalyticsAdapter({ - code: 'staq', - adapter: analyticsAdapter - }); - - adapterManager.enableAnalytics({ - provider: 'staq', - options: { - connId: 777, - queueTimeout: 1000, - url: 'https://localhost/prebid' - } - }); - - expect(analyticsAdapter.context).to.have.property('connectionId', 777); - }); - - it('should handle auction init event', function() { - events.emit(EVENTS.AUCTION_INIT, { config: {}, timeout: 3000 }); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(1); - expect(ev[0]).to.be.eql({ event: 'auctionInit', auctionId: undefined }); - }); - - it('should handle bid request event', function() { - events.emit(EVENTS.BID_REQUESTED, REQUEST); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(2); - expect(ev[1]).to.be.eql({ - adUnitCode: 'container-1', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - event: 'bidRequested', - adapter: 'AppNexus', - bidderName: 'AppNexus' - }); - }); - - it('should handle bid response event', function() { - events.emit(EVENTS.BID_RESPONSE, RESPONSE); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(3); - expect(ev[2]).to.be.eql({ - adId: '208750227436c1', - event: 'bidResponse', - adapter: 'AppNexus', - bidderName: 'AppNexus', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - adUnitCode: 'container-1', - cpm: 0.015, - timeToRespond: 0.443, - height: 250, - width: 300, - bidWon: false, - }); - }); - - it('should handle timeouts properly', function() { - events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); - - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(5); // remember, we added 2 timeout events - expect(ev[3]).to.be.eql({ - adapter: 'AppNexus', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f', - bidderName: 'AppNexus', - event: 'adapterTimedOut' - }) - }); - - it('should handle winning bid', function() { - events.emit(EVENTS.BID_WON, RESPONSE); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(6); - expect(ev[5]).to.be.eql({ - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - adId: '208750227436c1', - event: 'bidWon', - adapter: 'AppNexus', - bidderName: 'AppNexus', - adUnitCode: 'container-1', - cpm: 0.015, - height: 250, - width: 300, - bidWon: true, - }); - }); - - it('should handle auction end event', function() { - timer.tick(447); - events.emit(EVENTS.AUCTION_END, RESPONSE); - let ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(0); - expect(ajaxStub.calledOnce).to.be.equal(true); - let firstCallArgs0 = ajaxStub.firstCall.args[0]; - ev = JSON.parse(firstCallArgs0); - const ev6 = ev['events'][6]; - expect(ev['connId']).to.be.eql(777); - expect(ev6.auctionId).to.be.eql('5018eb39-f900-4370-b71e-3bb5b48d324f'); - expect(ev6.event).to.be.eql('auctionEnd'); - }); - }); -}); diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index a8295c197ef..66e2b575b8b 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -169,16 +169,17 @@ describe('stroeerCore bid adapter', function () { } function setupSingleWindow(sandBox, placementElements = [createElement('div-1', 17), createElement('div-2', 54)]) { - const win = createWindow('http://www.xyz.com/', { - parent: win, top: win, frameElement: createElement(undefined, 304), placementElements: placementElements + let singleWin = null + singleWin = createWindow('http://www.xyz.com/', { + parent: singleWin, top: singleWin, frameElement: createElement(undefined, 304), placementElements: placementElements }); - win.innerHeight = 200; + singleWin.innerHeight = 200; - sandBox.stub(utils, 'getWindowSelf').returns(win); - sandBox.stub(utils, 'getWindowTop').returns(win); + sandBox.stub(utils, 'getWindowSelf').returns(singleWin); + sandBox.stub(utils, 'getWindowTop').returns(singleWin); - return win; + return singleWin; } function setupNestedWindows(sandBox, placementElements = [createElement('div-1', 17), createElement('div-2', 54)]) { diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js index 3ef865ed2f1..099d8d33b02 100644 --- a/test/spec/modules/stvBidAdapter_spec.js +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -30,12 +30,12 @@ describe('stvAdapter', function() { }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 55d0731ec21..bcf388a67e2 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1149,7 +1149,7 @@ describe('Taboola Adapter', function () { }, } ], - 'fledgeAuctionConfigs': [ + 'paapi': [ { 'impId': request.bids[0].bidId, 'config': { @@ -1222,7 +1222,7 @@ describe('Taboola Adapter', function () { }, } ], - 'fledgeAuctionConfigs': [ + 'paapi': [ { 'impId': request.bids[0].bidId, 'config': { diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/tcfControl_spec.js similarity index 95% rename from test/spec/modules/gdprEnforcement_spec.js rename to test/spec/modules/tcfControl_spec.js index 4caf0276874..bdb14f6e44e 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/tcfControl_spec.js @@ -1,18 +1,19 @@ import { accessDeviceRule, + ACTIVE_RULES, enrichEidsRule, fetchBidsRule, - transmitEidsRule, - transmitPreciseGeoRule, getGvlid, getGvlidFromAnalyticsAdapter, - ACTIVE_RULES, reportAnalyticsRule, setEnforcementConfig, STRICT_STORAGE_ENFORCEMENT, - syncUserRule, ufpdRule, + syncUserRule, + transmitEidsRule, + transmitPreciseGeoRule, + ufpdRule, validateRules -} from 'modules/gdprEnforcement.js'; +} from 'modules/tcfControl.js'; import {config} from 'src/config.js'; import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; @@ -26,8 +27,7 @@ import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; -import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../../../src/consentHandler.js'; -import {validateStorageEnforcement} from '../../../src/storageManager.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js'; import {activityParams} from '../../../src/activities/activityParams.js'; describe('gdpr enforcement', function () { @@ -762,47 +762,52 @@ describe('gdpr enforcement', function () { }) }) - describe('when module does not need vendor consent', () => { + describe('first party modules', () => { Object.entries({ - 'storage': 1, - 'basicAds': 2, - 'measurement': 7, - 'personalizedAds': 4, - }).forEach(([purpose, purposeNo]) => { - describe(`for purpose ${purpose}`, () => { + 'storage': { + purposeNo: 1, + allowsLI: false + }, + 'basicAds': { + purposeNo: 2, + allowsLI: true + }, + 'measurement': { + purposeNo: 7, + allowsLI: true + }, + 'personalizedAds': { + purposeNo: 4, + allowsLI: false + }, + }).forEach(([purpose, {purposeNo, allowsLI}]) => { + describe(`purpose ${purpose}`, () => { + let consent; + beforeEach(() => { + consent = utils.deepClone(consentData); + }) const rule = createGdprRule(purpose); Object.entries({ 'allowed': true, 'not allowed': false }).forEach(([t, consentGiven]) => { - it(`should be ${t} when purpose is ${t}`, () => { - const consent = utils.deepClone(consentData); - consent.vendorData.purpose.consents[purposeNo] = consentGiven; - // take legitimate interest out of the picture for this test - consent.vendorData.purpose.legitimateInterests = {}; + it(`should be ${t} when publisher is ${t}`, () => { + consent.vendorData.publisher.consents[purposeNo] = consentGiven; + consent.vendorData.publisher.legitimateInterests[purposeNo] = false; const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID); expect(actual).to.equal(consentGiven); - }) + }); + }) + it(`should ${allowsLI ? '' : 'NOT '}be allowed when publisher consent is not given, but LI is`, () => { + consent.vendorData.publisher.consents[purposeNo] = false; + consent.vendorData.publisher.legitimateInterests[purposeNo] = true; + const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID); + expect(actual).to.equal(allowsLI); }) }) }) }) - it('if validateRules is passed FIRST_PARTY_GVLID, it will use publisher.consents', () => { - const rule = createGdprRule(); - const consentData = { - 'vendorData': { - 'publisher': { - 'consents': { - '1': true - } - }, - }, - }; - const result = validateRules(rule, consentData, 'cdep', FIRST_PARTY_GVLID); - expect(result).to.equal(true); - }); - describe('validateRules', function () { Object.entries({ '1 (which does not consider LI)': [1, 'storage', false], diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 40011367ac0..8ccfdd44649 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -44,47 +44,47 @@ describe('teadsBidAdapter', () => { }); it('should return false when pageId is not valid (letters)', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 1234, 'pageId': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when placementId is not valid (letters)', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 'FCP', 'pageId': 1234 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when placementId < 0 or pageId < 0', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': -1, 'pageId': -1 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; - bid.params = { + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index 4a79e7f77fd..6bc8eb1e7c3 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -360,9 +360,21 @@ describe('topics', () => { }]] ); storage.setDataInLocalStorage(topicStorageName, storedSegments); + config.setConfig({ + userSync: { + topics: { + maxTopicCaller: 4, + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' + }] + } + } + }) }); afterEach(() => { sandbox.restore(); + config.resetConfig(); }); it('should return segments for bidder if transmitUfpd is allowed', () => { diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index 505bc9d878f..48295e6fdff 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -4,7 +4,7 @@ import { generateUUID } from '../../../src/utils.js'; import { expect } from 'chai'; import * as utils from 'src/utils'; import * as sinon from 'sinon'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 11ff547cc78..9c564606186 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -9,9 +9,11 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/paapi.js'; + import {deepClone} from 'src/utils.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -193,9 +195,9 @@ describe('TrafficgateOpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); describe('and request config uses both host and platform', () => { @@ -250,10 +252,10 @@ describe('TrafficgateOpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); - delete videoBidWithMediaType.params; - videoBidWithMediaType.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithMediaType); + delete invalidVideoBidWithMediaTypes.params; + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); }); @@ -1017,7 +1019,9 @@ describe('TrafficgateOpenxRtbAdapter', function () { it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } }); expect(request[0].data.imp[0].ext.ae).to.equal(2); }); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 09e57e29a12..216142ab02e 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -874,7 +874,7 @@ describe('triplelift adapter', function () { expect(url).to.match(/(\?|&)us_privacy=1YYY/); }); it('should pass fledge signal when Triplelift is eligible for fledge', function() { - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const url = request.url; expect(url).to.match(/(\?|&)fledge=true/); @@ -1411,11 +1411,11 @@ describe('triplelift adapter', function () { let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); expect(result).to.have.property('bids'); - expect(result).to.have.property('fledgeAuctionConfigs'); - expect(result.fledgeAuctionConfigs.length).to.equal(2); - expect(result.fledgeAuctionConfigs[0].bidId).to.equal('30b31c1838de1e'); - expect(result.fledgeAuctionConfigs[1].bidId).to.equal('73edc0ba8de203'); - expect(result.fledgeAuctionConfigs[0].config).to.deep.equal( + expect(result).to.have.property('paapi'); + expect(result.paapi.length).to.equal(2); + expect(result.paapi[0].bidId).to.equal('30b31c1838de1e'); + expect(result.paapi[1].bidId).to.equal('73edc0ba8de203'); + expect(result.paapi[0].config).to.deep.equal( { 'seller': 'https://3lift.com', 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', @@ -1423,7 +1423,7 @@ describe('triplelift adapter', function () { 'perBuyerSignals': { 'https://some_buyer.com': { 'a': 1 } } } ); - expect(result.fledgeAuctionConfigs[1].config).to.deep.equal( + expect(result.paapi[1].config).to.deep.equal( { 'seller': 'https://3lift.com', 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', diff --git a/test/spec/modules/truereachBidAdapter_spec.js b/test/spec/modules/truereachBidAdapter_spec.js index cd7d0873569..78e6828147b 100644 --- a/test/spec/modules/truereachBidAdapter_spec.js +++ b/test/spec/modules/truereachBidAdapter_spec.js @@ -12,7 +12,6 @@ describe('truereachBidAdapterTests', function () { bidder: 'truereach', params: { site_id: '0142010a-8400-1b01-72cb-a553b9000009', - bidfloor: 0.1 } })).to.equal(true); }); @@ -27,8 +26,7 @@ describe('truereachBidAdapterTests', function () { }, bidder: 'truereach', params: { - site_id: '0142010a-8400-1b01-72cb-a553b9000009', - bidfloor: 0.1 + site_id: '0142010a-8400-1b01-72cb-a553b9000009' }, sizes: [[300, 250]] }]; @@ -40,7 +38,6 @@ describe('truereachBidAdapterTests', function () { expect(req_data.imp[0].id).to.equal('34ce3f3b15190a'); expect(req_data.imp[0].banner.w).to.equal(300); expect(req_data.imp[0].banner.h).to.equal(250); - expect(req_data.imp[0].bidfloor).to.equal(0); }); it('validate_response_params', function () { diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 7d263f6d4f0..170ff51c6fd 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -588,7 +588,7 @@ describe('TwistDigitalBidAdapter', function () { it('should set fledge correctly if enabled', function () { config.resetConfig(); const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); const requests = adapter.buildRequests([BID], bidderRequest); expect(requests[0].data.fledge).to.equal(1); diff --git a/test/spec/modules/uid2IdSystem_helpers.js b/test/spec/modules/uid2IdSystem_helpers.js index e0bef047acb..cf60b123c66 100644 --- a/test/spec/modules/uid2IdSystem_helpers.js +++ b/test/spec/modules/uid2IdSystem_helpers.js @@ -1,4 +1,4 @@ -import {setConsentConfig} from 'modules/consentManagement.js'; +import {setConsentConfig} from 'modules/consentManagementTcf.js'; import {server} from 'test/mocks/xhr.js'; import {coreStorage, requestBidsHook} from 'modules/userId/index.js'; diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 357dfdd9bea..434bca17416 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -3,12 +3,12 @@ import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import { uid2IdSubmodule } from 'modules/uid2IdSystem.js'; import 'src/prebid.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import { getGlobal } from 'src/prebidGlobal.js'; import { configureTimerInterceptors } from 'test/mocks/timers.js'; import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; -import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; import {server} from 'test/mocks/xhr'; import {createEidsArray} from '../../../modules/userId/eids.js'; @@ -93,7 +93,7 @@ describe(`UID2 module`, function () { before(function () { timerSpy = configureTimerInterceptors(debugOutput); hook.ready(); - uninstallGdprEnforcement(); + uninstallTcfControl(); attachIdSystem(uid2IdSubmodule); suiteSandbox = sinon.sandbox.create(); diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index abf1a54787d..662e5c0e03d 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -696,7 +696,9 @@ describe('UnrulyAdapter', function () { it('should return an array with 2 items and enabled protected audience', function () { mockBidRequests = { 'bidderCode': 'unruly', - 'fledgeEnabled': true, + 'paapi': { + enabled: true + }, 'bids': [ { 'bidder': 'unruly', @@ -782,7 +784,9 @@ describe('UnrulyAdapter', function () { it('should return an array with 2 items and enabled protected audience on only one unit', function () { mockBidRequests = { 'bidderCode': 'unruly', - 'fledgeEnabled': true, + 'paapi': { + enabled: true + }, 'bids': [ { 'bidder': 'unruly', @@ -1043,7 +1047,7 @@ describe('UnrulyAdapter', function () { mediaType: 'video' } ], - 'fledgeAuctionConfigs': [{ + 'paapi': [{ 'bidId': bidId, 'config': { 'seller': 'https://nexxen.tech', @@ -1107,7 +1111,7 @@ describe('UnrulyAdapter', function () { expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ 'bids': [], - 'fledgeAuctionConfigs': [{ + 'paapi': [{ 'bidId': bidId, 'config': { 'seller': 'https://nexxen.tech', diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index a5684fa5c8f..a43586605c9 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -21,7 +21,7 @@ import {getPrebidInternal} from 'src/utils.js'; import * as events from 'src/events.js'; import {EVENTS} from 'src/constants.js'; import {getGlobal} from 'src/prebidGlobal.js'; -import {resetConsentData} from 'modules/consentManagement.js'; +import {resetConsentData, } from 'modules/consentManagementTcf.js'; import {setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from 'modules/liveIntentIdSystem.js'; import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; @@ -30,7 +30,7 @@ import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; -import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; import {allConsent, GDPR_GVLIDS, gdprDataHandler} from '../../../src/consentHandler.js'; import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; import {ACTIVITY_ENRICH_EIDS} from '../../../src/activities/activities.js'; @@ -154,7 +154,7 @@ describe('User ID', function () { before(function () { hook.ready(); - uninstallGdprEnforcement(); + uninstallTcfControl(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); liveIntentIdSubmoduleDoNotFireEvent(); }); @@ -173,6 +173,7 @@ describe('User ID', function () { afterEach(() => { sandbox.restore(); + config.resetConfig(); }); describe('GVL IDs', () => { @@ -1310,7 +1311,6 @@ describe('User ID', function () { coreStorage.setCookie(PBJS_USER_ID_OPTOUT_NAME, '', EXPIRED_COOKIE_DATE); $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); - config.resetConfig(); }); it('does not fetch ids if opt out cookie exists', function () { @@ -1340,7 +1340,6 @@ describe('User ID', function () { afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); - config.resetConfig(); }); it('handles config with no usersync object', function () { @@ -1455,7 +1454,7 @@ describe('User ID', function () { expect(auctionDelay).to.equal(100); }); - it('config auctionDelay defaults to 0 if not a number', function () { + it('config auctionDelay defaults to 500 if not a number', function () { init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ @@ -1467,7 +1466,7 @@ describe('User ID', function () { }] } }); - expect(auctionDelay).to.equal(0); + expect(auctionDelay).to.equal(500); }); describe('auction and user sync delays', function () { @@ -1585,9 +1584,10 @@ describe('User ID', function () { }); }); - it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function () { + it('does not delay auction if set to 0, delays id fetch after auction ends with syncDelay', function () { config.setConfig({ userSync: { + auctionDelay: 0, syncDelay: 77, userIds: [{ name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} @@ -1595,10 +1595,6 @@ describe('User ID', function () { } }); - // check config has been set correctly - expect(auctionDelay).to.equal(0); - expect(syncDelay).to.equal(77); - return expectImmediateBidHook(auctionSpy, {adUnits}) .then(() => { // should not delay auction @@ -1624,6 +1620,7 @@ describe('User ID', function () { it('does not delay user id sync after auction ends if set to 0', function () { config.setConfig({ userSync: { + auctionDelay: 0, syncDelay: 0, userIds: [{ name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} @@ -1631,8 +1628,6 @@ describe('User ID', function () { } }); - expect(syncDelay).to.equal(0); - return expectImmediateBidHook(auctionSpy, {adUnits}) .then(() => { // auction should not be delayed @@ -2040,6 +2035,7 @@ describe('User ID', function () { done(); }, {adUnits}); }); + it('should add new id system ', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); @@ -2137,6 +2133,12 @@ describe('User ID', function () { describe('callbacks at the end of auction', function () { beforeEach(function () { + config.setConfig({ + // callbacks run after auction end only when auctionDelay is 0 + userSync: { + auctionDelay: 0, + } + }) sinon.stub(events, 'getEvents').returns([]); sinon.stub(utils, 'triggerPixel'); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -2156,17 +2158,13 @@ describe('User ID', function () { } it('pubcid callback with url', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); - config.setConfig(customCfg); - return runBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}).then(() => { + config.mergeConfig(customCfg); + return runBidsHook({}).then(() => { expect(utils.triggerPixel.called).to.be.false; return endAuction(); }).then(() => { diff --git a/test/spec/modules/utiqSystem_spec.js b/test/spec/modules/utiqIdSystem_spec.js similarity index 86% rename from test/spec/modules/utiqSystem_spec.js rename to test/spec/modules/utiqIdSystem_spec.js index afeeea7c3ea..62754d39fa3 100644 --- a/test/spec/modules/utiqSystem_spec.js +++ b/test/spec/modules/utiqIdSystem_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { utiqSubmodule } from 'modules/utiqSystem.js'; -import { storage } from 'modules/utiqSystem.js'; +import { utiqIdSubmodule } from 'modules/utiqIdSystem.js'; +import { storage } from 'modules/utiqIdSystem.js'; -describe('utiqSystem', () => { +describe('utiqIdSystem', () => { const utiqPassKey = 'utiqPass'; const getStorageData = (idGraph) => { @@ -17,7 +17,7 @@ describe('utiqSystem', () => { }; it('should have the correct module name declared', () => { - expect(utiqSubmodule.name).to.equal('utiq'); + expect(utiqIdSubmodule.name).to.equal('utiqId'); }); describe('utiq getId()', () => { @@ -26,13 +26,13 @@ describe('utiqSystem', () => { }); it('it should return object with key callback', () => { - expect(utiqSubmodule.getId()).to.have.property('callback'); + expect(utiqIdSubmodule.getId()).to.have.property('callback'); }); it('should return object with key callback with value type - function', () => { storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData())); - expect(utiqSubmodule.getId()).to.have.property('callback'); - expect(typeof utiqSubmodule.getId().callback).to.be.equal('function'); + expect(utiqIdSubmodule.getId()).to.have.property('callback'); + expect(typeof utiqIdSubmodule.getId().callback).to.be.equal('function'); }); it('tests if localstorage & JSON works properly ', () => { @@ -50,7 +50,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); @@ -61,7 +61,7 @@ describe('utiqSystem', () => { 'domain': 'test.domain', 'atid': 'atidValue', }; - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -82,7 +82,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -105,7 +105,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; - const response = utiqSubmodule.getId({params: {maxDelayTime: 200}}); + const response = utiqIdSubmodule.getId({params: {maxDelayTime: 200}}); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -138,7 +138,7 @@ describe('utiqSystem', () => { ]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the utiq for a payload with {utiq: value}', () => { - expect(utiqSubmodule.decode(responseData.payload)).to.deep.equal( + expect(utiqIdSubmodule.decode(responseData.payload)).to.deep.equal( {utiq: responseData.expected} ); }); @@ -146,7 +146,7 @@ describe('utiqSystem', () => { [{}, '', {foo: 'bar'}].forEach((response) => { it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { - expect(utiqSubmodule.decode(response)).to.be.null; + expect(utiqIdSubmodule.decode(response)).to.be.null; }); }); }); @@ -177,7 +177,7 @@ describe('utiqSystem', () => { window.dispatchEvent(new MessageEvent('message', eventData)); - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index 73fdb7f3dc8..271c944e6e9 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -259,6 +259,7 @@ describe('viantOrtbBidAdapter', function () { 'skip': 1, 'skipafter': 5, 'minduration': 10, + 'placement': 1, 'maxduration': 31 } }, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 5515002a054..ad5a298bb8e 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -21,7 +21,7 @@ import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; import {deepSetValue} from 'src/utils.js'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'openrtb'; @@ -600,7 +600,7 @@ describe('VidazooBidAdapter', function () { it('should set fledge correctly if enabled', function () { config.resetConfig(); const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); const requests = adapter.buildRequests([BID], bidderRequest); expect(requests[0].data.fledge).to.equal(1); @@ -750,8 +750,6 @@ describe('VidazooBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: diff --git a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js index a7379ccbab2..125f608f803 100644 --- a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js @@ -6,7 +6,7 @@ import { const {VideojsProvider, utils} = require('modules/videojsVideoProvider'); const { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLCMT, VPAID_MIME_TYPE, AD_POSITION } = require('libraries/video/constants/ortb.js'); const videojs = require('video.js').default; @@ -139,7 +139,7 @@ describe('videojsProvider', function () { expect(video.playbackmethod).to.include(PLAYBACK_METHODS.CLICK_TO_PLAY); expect(video.playbackend).to.equal(1); expect(video.api).to.deep.equal([2]); - expect(video.placement).to.be.equal(PLACEMENT.INSTREAM); + expect(video.plcmt).to.be.equal(PLCMT.ACCOMPANYING_CONTENT); }); it('should populate oRTB Content', function () { diff --git a/test/spec/modules/videoreachBidAdapter_spec.js b/test/spec/modules/videoreachBidAdapter_spec.js index 67ad89eac3d..dc81ec74ff8 100644 --- a/test/spec/modules/videoreachBidAdapter_spec.js +++ b/test/spec/modules/videoreachBidAdapter_spec.js @@ -21,12 +21,12 @@ describe('videoreachBidAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'TagId': '' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 38fa872e6b8..6fd779bdb0b 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -44,9 +44,9 @@ describe('vidoomyBidAdapter', function() { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when mediaType is video with INSTREAM context and lacks playerSize property', function () { diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index f70f614b2c8..0653d7a8ce7 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -32,21 +32,21 @@ describe('VisxAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'uid': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when uid can not be parsed as number', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'uid': 'sdvsdv' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('it should fail on invalid video bid', function () { diff --git a/test/spec/modules/winrBidAdapter_spec.js b/test/spec/modules/winrBidAdapter_spec.js index 95d1473d1cb..b0d8d72f0a1 100644 --- a/test/spec/modules/winrBidAdapter_spec.js +++ b/test/spec/modules/winrBidAdapter_spec.js @@ -93,22 +93,22 @@ describe('WinrAdapter', function () { }); it('should return false when mediaType is not banner', function () { - let bid = Object.assign({}, bid); - delete bid.mediaTypes; - bid.mediaTypes = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.mediaTypes; + invalidBid.mediaTypes = { 'video': {} }; - expect(getMediaTypeFromBid(bid)).to.not.equal('banner'); - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(getMediaTypeFromBid(invalidBid)).to.not.equal('banner'); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/wipesBidAdapter_spec.js b/test/spec/modules/wipesBidAdapter_spec.js index a458dcf69c8..a45e324f4fd 100644 --- a/test/spec/modules/wipesBidAdapter_spec.js +++ b/test/spec/modules/wipesBidAdapter_spec.js @@ -29,9 +29,9 @@ describe('wipesBidAdapter', function () { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahooAdsBidAdapter_spec.js similarity index 99% rename from test/spec/modules/yahoosspBidAdapter_spec.js rename to test/spec/modules/yahooAdsBidAdapter_spec.js index 40dc2b3c63b..b1d590cb806 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahooAdsBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { spec } from 'modules/yahoosspBidAdapter.js'; +import { spec } from 'modules/yahooAdsBidAdapter.js'; import {createEidsArray} from '../../../modules/userId/eids'; const DEFAULT_BID_ID = '84ab500420319d'; @@ -1169,7 +1169,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); @@ -1201,7 +1202,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); }); @@ -1298,7 +1300,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); }); @@ -1351,7 +1354,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); }); @@ -1381,7 +1385,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: 123456, playbackmethod: 1, rewarded: 1, - placement: 1 + placement: 1, + plcmt: 1 } } } @@ -1410,7 +1415,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { delivery: 1, pos: 123456, playbackmethod: 1, - placement: 1 + placement: 1, + plcmt: 1 } const validBidRequests = [bidRequest]; bidderRequest.bids = validBidRequests; @@ -1430,6 +1436,7 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: 123456, playbackmethod: 1, placement: 1, + plcmt: 1, rewarded: undefined }); }); diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index a10247411db..4664f77216d 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -34,9 +34,9 @@ describe('yieldoneBidAdapter', function() { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/ortbConverter/gdpr_spec.js b/test/spec/ortbConverter/gdpr_spec.js index 78fd1830438..65be8c5ebbe 100644 --- a/test/spec/ortbConverter/gdpr_spec.js +++ b/test/spec/ortbConverter/gdpr_spec.js @@ -1,4 +1,4 @@ -import {setOrtbAdditionalConsent} from '../../../modules/consentManagement.js'; +import {setOrtbAdditionalConsent} from '../../../modules/consentManagementTcf.js'; describe('pbjs -> ortb addtlConsent', () => { it('sets ConsentedProvidersSettings', () => { diff --git a/test/spec/ortbConverter/pbsExtensions/params_spec.js b/test/spec/ortbConverter/pbsExtensions/params_spec.js index 73b92a0755d..d1b36c18b49 100644 --- a/test/spec/ortbConverter/pbsExtensions/params_spec.js +++ b/test/spec/ortbConverter/pbsExtensions/params_spec.js @@ -34,63 +34,5 @@ describe('pbjs -> ortb bid params to imp[].ext.prebid.BIDDER', () => { it('has no effect if bidRequest has no params', () => { expect(setParams({bidder: 'mockBidder'})).to.eql({}); - }) - - describe('when adapter provides transformBidParams', () => { - let transform, bidderRequest; - beforeEach(() => { - bidderRequest = {bidderCode: 'mockBidder'}; - transform = sinon.stub().callsFake((p) => Object.assign({transformed: true}, p)); - bidderRegistry.mockBidder = { - getSpec() { - return { - transformBidParams: transform - } - } - } - }) - - it('runs params through transform', () => { - expect(setParams({bidder: 'mockBidder', params: {a: 'param'}}, {bidderRequest})).to.eql({ - ext: { - prebid: { - bidder: { - mockBidder: { - a: 'param', - transformed: true - } - } - } - } - }); - }); - - it('runs through transform even if bid has no params', () => { - expect(setParams({bidder: 'mockBidder'}, {bidderRequest})).to.eql({ - ext: { - prebid: { - bidder: { - mockBidder: { - transformed: true - } - } - } - } - }) - }) - - it('by default, passes adUnit from index, bidderRequest from context', () => { - const params = {a: 'param'}; - setParams({bidder: 'mockBidder', params}, {bidderRequest}); - sinon.assert.calledWith(transform, params, true, adUnit, [bidderRequest]) - }); - - it('uses provided adUnit, bidderRequests', () => { - const adUnit = {code: 'other-ad-unit'}; - const bidderRequests = [{bidderCode: 'one'}, {bidderCode: 'two'}]; - const params = {a: 'param'}; - setParams({bidder: 'mockBidder', params}, {}, {adUnit, bidderRequests}); - sinon.assert.calledWith(transform, params, true, adUnit, bidderRequests); - }) }); }); diff --git a/test/spec/ortbConverter/video_spec.js b/test/spec/ortbConverter/video_spec.js index 8ac6d8b4d08..ab4034bb60a 100644 --- a/test/spec/ortbConverter/video_spec.js +++ b/test/spec/ortbConverter/video_spec.js @@ -30,7 +30,6 @@ describe('pbjs -> ortb video conversion', () => { h: 2, mimes: ['video/mp4'], skip: 1, - placement: 1, }, }, }, diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index ef6d1de0b30..56668759db6 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1510,39 +1510,29 @@ describe('bidderFactory', () => { paapiStub = sinon.stub(); }); - const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapi']; - - it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => { - expect(() => { - runBidder(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) - }).to.throw(); - }) + describe(`when response has paapi`, () => { + it('should call paapi config hook with auction configs', function () { + runBidder({ + bids: bids, + paapi: [paapiConfig] + }); + expect(paapiStub.calledOnce).to.equal(true); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); + }); - PAAPI_PROPS.forEach(paapiProp => { - describe(`using ${paapiProp}`, () => { - it('should call paapi config hook with auction configs', function() { + Object.entries({ + 'missing': undefined, + 'an empty array': [] + }).forEach(([t, bids]) => { + it(`should call paapi config hook with PAAPI configs even when bids is ${t}`, function () { runBidder({ - bids: bids, - [paapiProp]: [paapiConfig] - }) - expect(paapiStub.calledOnce).to.equal(true); - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); - sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); - }) - - Object.entries({ - 'missing': undefined, - 'an empty array': [] - }).forEach(([t, bids]) => { - it(`should call paapi config hook with PAAPI configs even when bids is ${t}`, function() { - runBidder({ - bids, - [paapiProp]: [paapiConfig] - }) - expect(paapiStub.calledOnce).to.be.true; - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); - expect(addBidResponseStub.calledOnce).to.equal(false); + bids, + paapi: [paapiConfig] }); + expect(paapiStub.calledOnce).to.be.true; + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); + expect(addBidResponseStub.calledOnce).to.equal(false); }); }); }); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 54ea942e373..f6cfeededd3 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import { + getGPTSlotsForAdUnits, filters, getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm, @@ -1310,6 +1311,17 @@ describe('targeting tests', function () { describe('setTargetingForAst', function () { let sandbox, apnTagStub; + + before(() => { + if (window.apntag?.setKeywords == null) { + const orig = window.apntag; + window.apntag = {setKeywords: () => {}} + after(() => { + window.apntag = orig; + }) + } + }); + beforeEach(function() { sandbox = sinon.createSandbox(); sandbox.stub(targetingInstance, 'resetPresetTargetingAST'); @@ -1346,4 +1358,62 @@ describe('targeting tests', function () { expect(apnTagStub.getCall(1).args[1]).to.deep.equal({HB_BIDDER: 'appnexus'}); }); }); + + describe('getGPTSlotsForAdUnits', () => { + function mockSlot(path, elId) { + return { + getAdUnitPath() { + return path; + }, + getSlotElementId() { + return elId; + } + } + } + + let slots; + + beforeEach(() => { + slots = [ + mockSlot('slot/1', 'div-1'), + mockSlot('slot/2', 'div-2'), + ] + }); + + Object.entries({ + 'ad unit path': ['slot/1', 'slot/2'], + 'element id': ['div-1', 'div-2'] + }).forEach(([t, adUnitCodes]) => { + it(`can find slots by ${t}`, () => { + expect(getGPTSlotsForAdUnits(adUnitCodes, null, () => slots)).to.eql(Object.fromEntries(adUnitCodes.map((au, i) => [au, [slots[i]]]))); + }) + }); + + it('returns empty list on no match', () => { + expect(getGPTSlotsForAdUnits(['missing', 'slot/2'], null, () => slots)).to.eql({ + missing: [], + 'slot/2': [slots[1]] + }); + }); + + it('can use customSlotMatching', () => { + const csm = (slot) => { + if (slot.getAdUnitPath() === 'slot/1') { + return (au) => { + return au === 'custom' + } + } + } + expect(getGPTSlotsForAdUnits(['div-2', 'custom'], csm, () => slots)).to.eql({ + 'custom': [slots[0]], + 'div-2': [slots[1]] + }) + }); + + it('can handle repeated adUnitCodes', () => { + expect(getGPTSlotsForAdUnits(['div-1', 'div-1'], null, () => slots)).to.eql({ + 'div-1': [slots[0]] + }) + }) + }) }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 94643f34a05..962ba1b0178 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1,26 +1,25 @@ import { + createBidReceived, getAdServerTargeting, + getAdUnits, getBidRequests, getBidResponses, getBidResponsesFromAPI, getTargetingKeys, - getTargetingKeysBidLandscape, - getAdUnits, - createBidReceived + getTargetingKeysBidLandscape } from 'test/fixtures/fixtures.js'; -import { auctionManager, newAuctionManager } from 'src/auctionManager.js'; -import { targeting, newTargeting, filters } from 'src/targeting.js'; -import { config as configObj } from 'src/config.js'; +import {auctionManager, newAuctionManager} from 'src/auctionManager.js'; +import {filters, newTargeting, targeting} from 'src/targeting.js'; +import {config as configObj} from 'src/config.js'; import * as ajaxLib from 'src/ajax.js'; import * as auctionModule from 'src/auction.js'; -import { registerBidder } from 'src/adapters/bidderFactory.js'; -import {resizeRemoteCreative} from 'src/secureCreatives.js'; +import {resetAuctionState} from 'src/auction.js'; +import {registerBidder} from 'src/adapters/bidderFactory.js'; import {find} from 'src/polyfill.js'; import * as pbjsModule from 'src/prebid.js'; +import $$PREBID_GLOBAL$$ from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {reset as resetDebugging} from '../../../src/debugging.js'; -import $$PREBID_GLOBAL$$ from 'src/prebid.js'; -import {resetAuctionState} from 'src/auction.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {createBid} from '../../../src/bidfactory.js'; import {enrichFPD} from '../../../src/fpd/enrichment.js'; @@ -2503,6 +2502,52 @@ describe('Unit: Prebid Module', function () { } }); + if (FEATURES.NATIVE) { + Object.entries({ + missing: {}, + negative: {id: -1}, + 'not an integer': {id: 1.23}, + NaN: {id: 'garbage'} + }).forEach(([t, props]) => { + it(`should reject native ortb when asset ID is ${t}`, () => { + const adUnit = { + code: 'au', + mediaTypes: { + native: { + ortb: { + assets: [props] + } + } + }, + bids: [{bidder: 'appnexus'}] + }; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [adUnit] + }); + expect(auctionArgs.adUnits[0].bids.length).to.equal(0); + }); + }); + + ['sendTargetingKeys', 'types'].forEach(key => { + it(`should reject native that includes both ortb and ${key}`, () => { + const adUnit = { + code: 'au', + mediaTypes: { + native: { + ortb: {}, + [key]: {} + } + }, + bids: [{bidder: 'appnexus'}] + }; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [adUnit] + }); + expect(auctionArgs.adUnits[0].bids.length).to.equal(0); + }) + }); + } + it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', function () { const adUnits = [{ code: 'ad-unit-1', diff --git a/test/test_deps.js b/test/test_deps.js index c8a3bcc9426..3f0f766b457 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -19,3 +19,4 @@ require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); require('test/mocks/analyticsStub.js'); +require('test/mocks/ortbConverter.js') diff --git a/webpack.conf.js b/webpack.conf.js index 1035e985b22..5b0d864045e 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -126,7 +126,6 @@ module.exports = { }) ); const core = path.resolve('./src'); - const paapiMod = path.resolve('./modules/paapi.js'); return Object.assign(libraries, { core: { @@ -135,16 +134,6 @@ module.exports = { return module.resource && module.resource.startsWith(core); } }, - paapi: { - // fledgeForGpt imports paapi to keep backwards compat for NPM consumers - // this makes the paapi module its own chunk, pulled in by both paapi and fledgeForGpt entry points, - // to avoid duplication - // TODO: remove this in prebid 9 - name: 'chunk-paapi', - test: (module) => { - return module.resource === paapiMod; - } - } }, { default: false, defaultVendors: false From 7fdc9d54b60183d4674b18ca57823ea77ddae456 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 13 Jun 2024 23:00:19 +0000 Subject: [PATCH 0207/1097] Prebid 9.0.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24b7da35847..dd6072b2d54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.0.0-pre", + "version": "9.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.0.0-pre", + "version": "9.0.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 43edec4be1f..28fb8ead5f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.0.0-pre", + "version": "9.0.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 566c48fddfa8bdd759153be723a14453a0739721 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 13 Jun 2024 23:00:19 +0000 Subject: [PATCH 0208/1097] Increment version to 9.1.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd6072b2d54..de68505b648 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.0.0", + "version": "9.1.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.0.0", + "version": "9.1.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 28fb8ead5f2..a4d6e3a61dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.0.0", + "version": "9.1.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 5b0e6b8e31ca021db98a5d1ca337aeb592e724eb Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 14 Jun 2024 10:00:07 -0400 Subject: [PATCH 0209/1097] Mgid Bid Adapter : don't throw error without floors module (#11796) * Update mgidXBidAdapter.js * Update mgidXBidAdapter.js --- modules/mgidXBidAdapter.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index 3ef97cddb88..9ca16394087 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -1,7 +1,6 @@ import { deepAccess, logMessage, - logError, isPlainObject, isNumber, isArray, @@ -104,7 +103,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } From 560ad53179abfd65d751930497e02eb2e061f1c1 Mon Sep 17 00:00:00 2001 From: Ruslan S Date: Sat, 15 Jun 2024 20:01:15 +0800 Subject: [PATCH 0210/1097] Smaato: Add DOOH support (#11801) --- modules/smaatoBidAdapter.js | 23 +++++++++++++----- test/spec/modules/smaatoBidAdapter_spec.js | 27 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 5d3ab0414a4..f6dbcfe6071 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -212,15 +212,19 @@ const converter = ortbConverter({ return bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies; } + function setPublisherId(node) { + deepSetValue(node, 'publisher.id', bidRequest.params.publisherId); + } + const request = buildRequest(imps, bidderRequest, context); const bidRequest = context.bidRequests[0]; - let siteContent; + let content; const mediaType = context.mediaType; if (mediaType === VIDEO) { const videoParams = bidRequest.mediaTypes[VIDEO]; if (videoParams.context === ADPOD) { request.imp = createAdPodImp(request.imp[0], videoParams); - siteContent = addOptionalAdpodParameters(videoParams); + content = addOptionalAdpodParameters(videoParams); } } @@ -242,19 +246,26 @@ const converter = ortbConverter({ if (request.site) { request.site.id = window.location.hostname - if (siteContent) { - request.site.content = siteContent; + if (content) { + request.site.content = content; + } + setPublisherId(request.site); + } else if (request.dooh) { + request.dooh.id = window.location.hostname + if (content) { + request.dooh.content = content; } + setPublisherId(request.dooh); } else { request.site = { id: window.location.hostname, domain: bidderRequest.refererInfo.domain || window.location.hostname, page: bidderRequest.refererInfo.page || window.location.href, ref: bidderRequest.refererInfo.ref, - content: siteContent || null + content: content || null } + setPublisherId(request.site); } - deepSetValue(request.site, 'publisher.id', bidRequest.params.publisherId); if (request.regs) { if (isGdprApplicable()) { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index b7a9a43b6a0..9052fbbcdfd 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -296,6 +296,33 @@ describe('smaatoBidAdapterTest', () => { expect(req.site.page).to.equal(page); expect(req.site.ref).to.equal(ref); expect(req.site.publisher.id).to.equal('publisherId'); + expect(req.dooh).to.be.undefined; + }) + + it('sends correct dooh from ortb2', () => { + const name = 'name'; + const domain = 'domain'; + const keywords = 'keyword1,keyword2'; + const venuetypetax = 1; + const ortb2 = { + dooh: { + name: name, + domain: domain, + keywords: keywords, + venuetypetax: venuetypetax + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.dooh.id).to.exist.and.to.be.a('string'); + expect(req.dooh.name).to.equal(name); + expect(req.dooh.domain).to.equal(domain); + expect(req.dooh.keywords).to.equal(keywords); + expect(req.dooh.venuetypetax).to.equal(venuetypetax); + expect(req.dooh.publisher.id).to.equal('publisherId'); + expect(req.site).to.be.undefined; }) it('sends correct device from ortb2', () => { From 02e1581efb628824011062ba12f08d3161ef4a96 Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Sat, 15 Jun 2024 08:04:32 -0400 Subject: [PATCH 0211/1097] Real Time Data Module: Mobian Brand Safety (#11798) * add mobian brand safety variables * directly set site.ext.data in a getBidRequestData function --- modules/mobianRtdProvider.js | 52 ++++++++++ modules/mobianRtdProvider.md | 11 ++ test/spec/modules/mobianRtdProvider_spec.js | 107 ++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 modules/mobianRtdProvider.js create mode 100644 modules/mobianRtdProvider.md create mode 100644 test/spec/modules/mobianRtdProvider_spec.js diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js new file mode 100644 index 00000000000..f43dde20681 --- /dev/null +++ b/modules/mobianRtdProvider.js @@ -0,0 +1,52 @@ +import { submodule } from '../src/hook.js'; +import { ajaxBuilder } from '../src/ajax.js'; +export const MOBIAN_URL = 'http://impact-analytics-staging.themobian.com'; + +export const mobianBrandSafetySubmodule = { + name: 'mobianBrandSafety', + gvlid: null, + init: init, + getBidRequestData: getBidRequestData +}; + +function init() { + return true; +} +function getBidRequestData(bidReqConfig, callback, config) { + const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + const pageUrl = encodeURIComponent(getPageUrl()); + const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; + + const ajax = ajaxBuilder(); + + return new Promise((resolve) => { + ajax(requestUrl, { + success: function(response) { + const risks = ['garm_high_risk', 'garm_medium_risk', 'garm_low_risk', 'garm_no_risk']; + const riskLevels = ['high_risk', 'medium_risk', 'low_risk', 'no_risk']; + + let mobianGarmRisk = 'unknown'; + for (let i = 0; i < risks.length; i++) { + if (response[risks[i]]) { + mobianGarmRisk = riskLevels[i]; + break; + } + } + const risk = { + 'mobianGarmRisk': mobianGarmRisk + }; + resolve(risk); + ortb2Site.ext.data['mobian'] = risk + }, + error: function () { + resolve({}); + } + }); + }); +} + +function getPageUrl() { + return window.location.href; +} + +submodule('realTimeData', mobianBrandSafetySubmodule); diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md new file mode 100644 index 00000000000..e7475eb40fb --- /dev/null +++ b/modules/mobianRtdProvider.md @@ -0,0 +1,11 @@ +# Overview + +Module Name: Mobian Rtd Provider +Module Type: Rtd Provider +Maintainer: rich.rodriguez@themobian.com + +# Description + +RTD provider for themobian Brand Safety determinations. Publishers +should use this to get Mobian's GARM Risk evaluations for +a URL. diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js new file mode 100644 index 00000000000..0b09f4fd0cd --- /dev/null +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -0,0 +1,107 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { mobianBrandSafetySubmodule, MOBIAN_URL } from 'modules/mobianRtdProvider.js'; +import * as ajax from 'src/ajax.js'; + +describe('Mobian RTD Submodule', function () { + let ajaxStub; + let bidReqConfig; + + beforeEach(function () { + bidReqConfig = { + ortb2Fragments: { + global: { + site: { + ext: { + data: {} + } + } + } + } + }; + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should return no_risk when server responds with garm_no_risk', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success({ + garm_no_risk: true, + garm_low_risk: false, + garm_medium_risk: false, + garm_high_risk: false + }); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.have.property('mobianGarmRisk'); + expect(risk['mobianGarmRisk']).to.equal('no_risk'); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); + }); + }); + + it('should return low_risk when server responds with garm_no_risk', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success({ + garm_no_risk: false, + garm_low_risk: true, + garm_medium_risk: false, + garm_high_risk: false + }); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.have.property('mobianGarmRisk'); + expect(risk['mobianGarmRisk']).to.equal('low_risk'); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); + }); + }); + + it('should return medium_risk when server responds with garm_medium_risk', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success({ + garm_no_risk: false, + garm_low_risk: false, + garm_medium_risk: true, + garm_high_risk: false + }); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.have.property('mobianGarmRisk'); + expect(risk['mobianGarmRisk']).to.equal('medium_risk'); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); + }); + }); + + it('should return high_risk when server responds with garm_high_risk', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success({ + garm_no_risk: false, + garm_low_risk: false, + garm_medium_risk: false, + garm_high_risk: true + }); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.have.property('mobianGarmRisk'); + expect(risk['mobianGarmRisk']).to.equal('high_risk'); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); + }); + }); + + it('should return unknown when server response is not of the expected shape', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success('unexpected output not even of the right type'); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.have.property('mobianGarmRisk'); + expect(risk['mobianGarmRisk']).to.equal('unknown'); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); + }); + }); +}); From f43411f56a06e64ebddd400e4bd40df272dfc288 Mon Sep 17 00:00:00 2001 From: Muki Seiler Date: Sun, 16 Jun 2024 14:15:43 +0200 Subject: [PATCH 0212/1097] Update .nvmrc (#11808) Reflect latest node version in .nvrmrc --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 66df3b7ab2d..f203ab89b79 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12.16.1 +20.13.1 From eedcb055d0b592c738db0015782b11fc0b128fa4 Mon Sep 17 00:00:00 2001 From: ElgarsG <65354776+eldzis@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:42:01 +0300 Subject: [PATCH 0213/1097] Setupad adapter: param change and minor fixes (#11770) * Fix analytics params and set account_id param mandatory * Remove unused param * Remove unneeded function logic and refactor --- modules/setupadBidAdapter.js | 45 ++++++++++++--------- modules/setupadBidAdapter.md | 2 +- test/spec/modules/setupadBidAdapter_spec.js | 15 +++++++ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/modules/setupadBidAdapter.js b/modules/setupadBidAdapter.js index 55677d51c56..c6fb9097122 100644 --- a/modules/setupadBidAdapter.js +++ b/modules/setupadBidAdapter.js @@ -13,7 +13,7 @@ import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'setupad'; const ENDPOINT = 'https://prebid.setupad.io/openrtb2/auction'; const SYNC_ENDPOINT = 'https://cookie.stpd.cloud/sync?'; -const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics'; +const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics?'; const GVLID = 1241; const TIME_TO_LIVE = 360; const biddersCreativeIds = {}; @@ -28,7 +28,12 @@ export const spec = { gvlid: GVLID, isBidRequestValid: function (bid) { - return !!(bid.params.placement_id && isStr(bid.params.placement_id)); + return !!( + bid.params.placement_id && + isStr(bid.params.placement_id) && + bid.params.account_id && + isStr(bid.params.account_id) + ); }, buildRequests: function (validBidRequests, bidderRequest) { @@ -225,17 +230,23 @@ export const spec = { if (!placementIds) return; - let extraBidParams = ''; - // find the winning bidder by using creativeId as identification if (biddersCreativeIds.hasOwnProperty(bid.creativeId) && biddersCreativeIds[bid.creativeId]) { bidder = biddersCreativeIds[bid.creativeId]; } - // Add extra parameters - extraBidParams = `&cpm=${bid.originalCpm}¤cy=${bid.originalCurrency}`; + const queryParams = []; + queryParams.push(`event=bidWon`); + queryParams.push('bidder=' + bidder); + queryParams.push('placementIds=' + placementIds); + queryParams.push('auctionId=' + auctionId); + queryParams.push('cpm=' + bid.originalCpm); + queryParams.push('currency=' + bid.originalCurrency); + queryParams.push('timestamp=' + Date.now()); + + const strQueryParams = queryParams.join('&'); - const url = `${REPORT_ENDPOINT}?event=bidWon&bidder=${bidder}&placementIds=${placementIds}&auctionId=${auctionId}${extraBidParams}×tamp=${Date.now()}`; + const url = REPORT_ENDPOINT + strQueryParams; triggerPixel(url); }, }; @@ -251,21 +262,15 @@ function getBidders(serverResponse) { } function getAd(bid) { - let ad, adUrl; - - switch (deepAccess(bid, 'ext.prebid.type')) { - default: - if (bid.adm && bid.nurl) { - ad = bid.adm; - ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - ad = bid.adm; - } else if (bid.nurl) { - adUrl = bid.nurl; - } + const { adm, nurl } = bid; + let ad = adm; + + if (nurl) { + const trackingPixel = createTrackPixelHtml(decodeURIComponent(nurl)); + ad += trackingPixel; } - return { ad, adUrl }; + return { ad }; } registerBidder(spec); diff --git a/modules/setupadBidAdapter.md b/modules/setupadBidAdapter.md index 0d4f0ef392e..c11e8eb4bae 100644 --- a/modules/setupadBidAdapter.md +++ b/modules/setupadBidAdapter.md @@ -26,7 +26,7 @@ const adUnits = [ bidder: 'setupad', params: { placement_id: '123', //required - account_id: '123', //optional + account_id: '123', //required }, }, ], diff --git a/test/spec/modules/setupadBidAdapter_spec.js b/test/spec/modules/setupadBidAdapter_spec.js index d4ff73d005f..bc1527df603 100644 --- a/test/spec/modules/setupadBidAdapter_spec.js +++ b/test/spec/modules/setupadBidAdapter_spec.js @@ -92,11 +92,26 @@ describe('SetupadAdapter', function () { bidder: 'setupad', params: { placement_id: '123', + account_id: '123', }, }; + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + + it('should return false when placement_id is missing', function () { + const bidWithoutPlacementId = { ...bid }; + delete bidWithoutPlacementId.params.placement_id; + expect(spec.isBidRequestValid(bidWithoutPlacementId)).to.equal(false); + }); + + it('should return false when account_id is missing', function () { + const bidWithoutAccountId = { ...bid }; + delete bidWithoutAccountId.params.account_id; + expect(spec.isBidRequestValid(bidWithoutAccountId)).to.equal(false); + }); + it('should return false when required params are not passed', function () { delete bid.params.placement_id; expect(spec.isBidRequestValid(bid)).to.equal(false); From 8e1d5a72c575f9674302b671924c4eecefc2be70 Mon Sep 17 00:00:00 2001 From: orazumov-zeta <99964372+orazumov-zeta@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:29:57 +0300 Subject: [PATCH 0214/1097] ZetaGlobalSsp Analytics Adapter : provide adUnitCode (#11812) --- modules/zeta_global_sspAnalyticsAdapter.js | 12 ++++++++---- .../modules/zeta_global_sspAnalyticsAdapter_spec.js | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 31bcffcd7b1..02cfb8739b7 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -41,7 +41,8 @@ function adRenderSucceededHandler(args) { size: args.bid?.size, adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, - cpm: args.bid?.cpm + cpm: args.bid?.cpm, + adUnitCode: args.bid?.adUnitCode }, device: { ua: navigator.userAgent @@ -63,7 +64,8 @@ function auctionEndHandler(args) { bidder: b?.bidder, mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), - device: b?.ortb2?.device + device: b?.ortb2?.device, + adUnitCode: b?.adUnitCode })) })), bidsReceived: args.bidsReceived?.map(br => ({ @@ -75,7 +77,8 @@ function auctionEndHandler(args) { size: br?.size, adomain: br?.adserverTargeting?.hb_adomain, timeToRespond: br?.timeToRespond, - cpm: br?.cpm + cpm: br?.cpm, + adUnitCode: br?.adUnitCode })) } sendEvent(EVENTS.AUCTION_END, event); @@ -93,7 +96,8 @@ function bidTimeoutHandler(args) { mediaType: t?.mediaTypes?.video ? 'VIDEO' : (t?.mediaTypes?.banner ? 'BANNER' : undefined), size: t?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), timeout: t?.timeout, - device: t?.ortb2?.device + device: t?.ortb2?.device, + adUnitCode: t?.adUnitCode })) } sendEvent(EVENTS.BID_TIMEOUT, event); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 4331060b1cd..7abfc7dcc10 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -412,6 +412,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { domain: 'test-zeta-ssp.net:63342', page: 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', bids: [{ + adUnitCode: '/19968336/header-bid-tag-0', bidId: '206be9a13236af', auctionId: '75e394d9', bidder: 'zeta_global_ssp', @@ -426,6 +427,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { domain: 'test-zeta-ssp.net:63342', page: 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', bids: [{ + adUnitCode: '/19968336/header-bid-tag-0', bidId: '41badc0e164c758', auctionId: '75e394d9', bidder: 'appnexus', @@ -437,6 +439,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { }] }], bidsReceived: [{ + adUnitCode: '/19968336/header-bid-tag-0', adId: '5759bb3ef7be1e8', requestId: '206be9a13236af', creativeId: '456456456', @@ -459,6 +462,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { expect(auctionSucceeded.domain).to.eql('test-zeta-ssp.net'); expect(auctionSucceeded.page).to.eql('test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html'); expect(auctionSucceeded.bid).to.be.deep.equal({ + adUnitCode: '/19968336/header-bid-tag-0', adId: '5759bb3ef7be1e8', requestId: '206be9a13236af', auctionId: '75e394d9', From 3ecb1f4294ef7186a1a18d5fee88221971fa9f43 Mon Sep 17 00:00:00 2001 From: Andrey Senaev Date: Mon, 17 Jun 2024 20:22:50 +0300 Subject: [PATCH 0215/1097] Update novatiqIdSystem.md (just resolved problem with syntax) (#11816) --- modules/novatiqIdSystem.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md index f33fc700311..a78363e8fe3 100644 --- a/modules/novatiqIdSystem.md +++ b/modules/novatiqIdSystem.md @@ -19,12 +19,11 @@ pbjs.setConfig({ name: 'novatiq', params: { // change to the Partner Number you received from Novatiq - sourceid '1a3' - } + sourceid: '1a3' } }], // 50ms maximum auction delay, applies to all userId modules - auctionDelay: 50 + auctionDelay: 50 } }); ``` From 03d41628491a09c0c143c866c243012070538f6a Mon Sep 17 00:00:00 2001 From: Kevin Siow Date: Tue, 18 Jun 2024 12:20:05 +0200 Subject: [PATCH 0216/1097] Dailymotion Bid Adapter: fix content cat type (#11818) Co-authored-by: Kevin Siow --- modules/dailymotionBidAdapter.js | 2 +- test/spec/modules/dailymotionBidAdapter_spec.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index bf8eaaebb55..b268d497723 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -67,7 +67,7 @@ function getVideoMetadata(bidRequest, bidderRequest) { ? videoParams.isCreatedForKids : null, context: { - siteOrAppCat: deepAccess(contentObj, 'cat', ''), + siteOrAppCat: deepAccess(contentObj, 'cat', []), videoViewsInSession: ( typeof videoParams.videoViewsInSession === 'number' && videoParams.videoViewsInSession >= 0 diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 0fa199ed034..4f8862982ad 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -196,7 +196,7 @@ describe('dailymotionBidAdapterTests', () => { livestream: !!bidRequestData[0].params.video.livestream, isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, context: { - siteOrAppCat: '', + siteOrAppCat: [], videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, autoplay: bidRequestData[0].params.video.autoplay, playerVolume: bidRequestData[0].params.video.playerVolume, @@ -342,7 +342,7 @@ describe('dailymotionBidAdapterTests', () => { livestream: !!bidRequestData[0].params.video.livestream, isCreatedForKids: null, context: { - siteOrAppCat: '', + siteOrAppCat: [], videoViewsInSession: null, autoplay: null, playerVolume: null, @@ -588,7 +588,7 @@ describe('dailymotionBidAdapterTests', () => { livestream: false, isCreatedForKids: null, context: { - siteOrAppCat: '', + siteOrAppCat: [], videoViewsInSession: null, autoplay: null, playerVolume: null, From 32681567be5471257baf2a66864aeafdbbb22be5 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Tue, 18 Jun 2024 18:51:10 +0200 Subject: [PATCH 0217/1097] Update richaudienceBidAdapter.md (#11826) Update maintainer e-mail to integrations@richaudience.com --- modules/richaudienceBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index f888117b166..35298b8421d 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Rich Audience Bidder Adapter Module Type: Bidder Adapter -Maintainer: cert@richaudience.com +Maintainer: integrations@richaudience.com ``` # Description From 7bad57e7a0fe772141853fffc4800ae53e9dbe20 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Tue, 18 Jun 2024 19:32:50 +0200 Subject: [PATCH 0218/1097] Richaudience Bid Adapter: add adapter JS file (#11827) * Update richaudienceBidAdapter.md Update maintainer e-mail to integrations@richaudience.com * Add richaudienceBidAdapter.js file * Add richaudienceBidAdapter_spec.js * Update richaudienceBidAdapter.js --------- Co-authored-by: Patrick McCann --- modules/richaudienceBidAdapter.js | 375 +++++ .../modules/richaudienceBidAdapter_spec.js | 1304 +++++++++++++++++ 2 files changed, 1679 insertions(+) create mode 100644 modules/richaudienceBidAdapter.js create mode 100644 test/spec/modules/richaudienceBidAdapter_spec.js diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js new file mode 100644 index 00000000000..98d628df181 --- /dev/null +++ b/modules/richaudienceBidAdapter.js @@ -0,0 +1,375 @@ +import {deepAccess, isStr, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; + +const BIDDER_CODE = 'richaudience'; +let REFERER = ''; + +export const spec = { + code: BIDDER_CODE, + gvlid: 108, + aliases: ['ra'], + supportedMediaTypes: [BANNER, VIDEO], + + /*** + * Determines whether or not the given bid request is valid + * + * @param {bidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise + */ + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.pid && bid.params.supplyType); + }, + /*** + * Build a server request from the list of valid BidRequests + * @param {validBidRequests} is an array of the valid bids + * @param {bidderRequest} bidder request object + * @returns {ServerRequest} Info describing the request to the server + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var payload = { + bidfloor: raiGetFloor(bid, config), + ifa: bid.params.ifa, + pid: bid.params.pid, + supplyType: bid.params.supplyType, + currencyCode: config.getConfig('currency.adServerCurrency'), + auctionId: bid.auctionId, + bidId: bid.bidId, + BidRequestsCount: bid.bidRequestsCount, + bidder: bid.bidder, + bidderRequestId: bid.bidderRequestId, + tagId: bid.adUnitCode, + sizes: raiGetSizes(bid), + referer: (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null), + numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), + transactionId: bid.ortb2Imp?.ext?.tid, + timeout: bidderRequest.timeout || 600, + user: raiSetEids(bid), + demand: raiGetDemandType(bid), + videoData: raiGetVideoInfo(bid), + scr_rsl: raiGetResolution(), + cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), + kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','), + schain: bid.schain, + gpid: raiSetPbAdSlot(bid) + }; + + REFERER = (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null) + + payload.gdpr_consent = ''; + payload.gdpr = false; + + if (bidderRequest && bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies != 'undefined') { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + } + if (typeof bidderRequest.gdprConsent.consentString != 'undefined') { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + } + + if (bidderRequest?.gppConsent) { + payload.privacy = { + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest?.ortb2?.regs?.gpp) { + payload.privacy = { + gpp: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + } + } + + var payloadString = JSON.stringify(payload); + + var endpoint = 'https://shb.richaudience.com/hb/'; + + return { + method: 'POST', + url: endpoint, + data: payloadString, + }; + }); + }, + /*** + * Read the response from the server and build a list of bids + * @param {serverResponse} Response from the server. + * @param {bidRequest} Bid request object + * @returns {bidResponses} Array of bids which were nested inside the server + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + // try catch + var response = serverResponse.body; + if (response) { + var bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creative_id, + mediaType: response.media_type, + netRevenue: response.netRevenue, + currency: response.currency, + ttl: response.ttl, + meta: response.adomain, + dealId: response.dealId + }; + + if (response.media_type === 'video') { + bidResponse.vastXml = response.vastXML; + try { + if (bidResponse.vastXml != null) { + if (JSON.parse(bidRequest.data).videoData.format == 'outstream' || JSON.parse(bidRequest.data).videoData.format == 'banner') { + bidResponse.renderer = Renderer.install({ + id: bidRequest.bidId, + adunitcode: bidRequest.tagId, + loaded: false, + config: response.media_type, + url: 'https://cdn3.richaudience.com/prebidVideo/player.js' + }); + } + bidResponse.renderer.setRender(renderer); + } + } catch (e) { + bidResponse.ad = response.adm; + } + } else { + bidResponse.ad = response.adm; + } + + bidResponses.push(bidResponse); + } + return bidResponses; + }, + /*** + * User Syncs + * + * @param {syncOptions} Publisher prebid configuration + * @param {serverResponses} Response from the server + * @param {gdprConsent} GPDR consent object + * @returns {Array} + */ + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + + var rand = Math.floor(Math.random() * 9999999999); + var syncUrl = ''; + var consent = ''; + var consentGPP = ''; + + var raiSync = {}; + + raiSync = raiGetSyncInclude(config); + + if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') { + consent = `consentString=${gdprConsent.consentString}` + } + + // GPP Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + consentGPP = 'gpp=' + encodeURIComponent(gppConsent.gppString); + consentGPP += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); + } + + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { + syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand + if (consent != '') { + syncUrl += `&${consent}` + } + if (consentGPP != '') { + syncUrl += `&${consentGPP}` + } + syncs.push({ + type: 'iframe', + url: syncUrl + }); + } + + if (syncOptions.pixelEnabled && REFERER != null && syncs.length == 0 && raiSync.raiImage != 'exclude') { + syncUrl = `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=${REFERER}`; + if (consent != '') { + syncUrl += `&${consent}` + } + if (consentGPP != '') { + syncUrl += `&${consentGPP}` + } + syncs.push({ + type: 'image', + url: syncUrl + }); + } + return syncs + }, + + onTimeout: function (data) { + let url = raiGetTimeoutURL(data); + if (url) { + triggerPixel(url); + } + } +}; + +registerBidder(spec); + +function raiGetSizes(bid) { + let raiNewSizes; + if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { + raiNewSizes = bid.mediaTypes.banner.sizes + } + if (raiNewSizes != null) { + return raiNewSizes.map(size => ({ + w: size[0], + h: size[1] + })); + } +} + +function raiGetDemandType(bid) { + let raiFormat = 'display'; + if (typeof bid.sizes != 'undefined') { + bid.sizes.forEach(function (sz) { + if ((sz[0] == '1800' && sz[1] == '1000') || (sz[0] == '1' && sz[1] == '1')) { + raiFormat = 'skin' + } + }) + } + if (bid.mediaTypes != undefined) { + if (bid.mediaTypes.video != undefined) { + raiFormat = 'video'; + } + } + return raiFormat; +} + +function raiGetVideoInfo(bid) { + let videoData; + if (raiGetDemandType(bid) == 'video') { + videoData = { + format: bid.mediaTypes.video.context, + playerSize: bid.mediaTypes.video.playerSize, + mimes: bid.mediaTypes.video.mimes + }; + } else { + videoData = { + format: 'banner' + } + } + return videoData; +} + +function raiSetEids(bid) { + let eids = []; + + if (bid && bid.userId) { + raiSetUserId(bid, eids, 'id5-sync.com', deepAccess(bid, `userId.id5id.uid`)); + raiSetUserId(bid, eids, 'pubcommon', deepAccess(bid, `userId.pubcid`)); + raiSetUserId(bid, eids, 'criteo.com', deepAccess(bid, `userId.criteoId`)); + raiSetUserId(bid, eids, 'liveramp.com', deepAccess(bid, `userId.idl_env`)); + raiSetUserId(bid, eids, 'liveintent.com', deepAccess(bid, `userId.lipb.lipbid`)); + raiSetUserId(bid, eids, 'adserver.org', deepAccess(bid, `userId.tdid`)); + } + + return eids; +} + +function raiSetUserId(bid, eids, source, value) { + if (isStr(value)) { + eids.push({ + userId: value, + source: source + }); + } +} + +function renderer(bid) { + bid.renderer.push(() => { + renderAd(bid) + }); +} + +function renderAd(bid) { + let raOutstreamHBPassback = `${bid.vastXml}`; + let raPlayerHB = { + config: bid.params[0].player != undefined ? { + end: bid.params[0].player.end != null ? bid.params[0].player.end : 'close', + init: bid.params[0].player.init != null ? bid.params[0].player.init : 'close', + skin: bid.params[0].player.skin != null ? bid.params[0].player.skin : 'light', + } : {end: 'close', init: 'close', skin: 'light'}, + pid: bid.params[0].pid, + adUnit: bid.adUnitCode + }; + + window.raParams(raPlayerHB, raOutstreamHBPassback, true); +} + +function raiGetResolution() { + let resolution = ''; + if (typeof window.screen != 'undefined') { + resolution = window.screen.width + 'x' + window.screen.height; + } + return resolution; +} + +function raiSetPbAdSlot(bid) { + let pbAdSlot = ''; + if (deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') != null) { + pbAdSlot = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') + } + return pbAdSlot +} + +function raiGetSyncInclude(config) { + try { + let raConfig = null; + let raiSync = {}; + if (config.getConfig('userSync').filterSettings != null && typeof config.getConfig('userSync').filterSettings != 'undefined') { + raConfig = config.getConfig('userSync').filterSettings + if (raConfig.iframe != null && typeof raConfig.iframe != 'undefined') { + raiSync.raiIframe = raConfig.iframe.bidders == 'richaudience' || raConfig.iframe.bidders == '*' ? raConfig.iframe.filter : 'exclude'; + } + if (raConfig.image != null && typeof raConfig.image != 'undefined') { + raiSync.raiImage = raConfig.image.bidders == 'richaudience' || raConfig.image.bidders == '*' ? raConfig.image.filter : 'exclude'; + } + } + return raiSync; + } catch (e) { + return null; + } +} + +function raiGetFloor(bid, config) { + try { + let raiFloor; + if (bid.params.bidfloor != null) { + raiFloor = bid.params.bidfloor; + } else if (typeof bid.getFloor == 'function') { + let floorSpec = bid.getFloor({ + currency: config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD', + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', + size: '*' + }) + + raiFloor = floorSpec.floor; + } + return raiFloor + } catch (e) { + return 0 + } +} + +function raiGetTimeoutURL(data) { + let {params, timeout} = data[0] + let url = 'https://s.richaudience.com/err/?ec=6&ev=[timeout_publisher]&pla=[placement_hash]&int=PREBID&pltfm=&node=&dm=[domain]'; + + url = url.replace('[timeout_publisher]', timeout) + url = url.replace('[placement_hash]', params[0].pid) + if (document.location.host != null) { + url = url.replace('[domain]', document.location.host) + } + return url +} diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js new file mode 100644 index 00000000000..d2b173f53df --- /dev/null +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -0,0 +1,1304 @@ +// import or require modules necessary for the test, e.g.: +import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' +import { + spec +} from 'modules/richaudienceBidAdapter.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import sinon from 'sinon'; + +describe('Richaudience adapter tests', function () { + var DEFAULT_PARAMS_NEW_SIZES = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + ortb2Imp: { + ext: { + tid: '29df2112-348b-4961-8863-1b33684d95e6', + } + }, + user: {} + }]; + + var DEFAULT_PARAMS_NEW_SIZES_GPID = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1#example-2', + data: { + pbadslot: '/19968336/header-bid-tag-1#example-2' + } + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO_TIMEOUT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: [{ + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }], + timeout: 3000, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }] + + var DEFAULT_PARAMS_VIDEO_IN = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_VIDEO_OUT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [[300, 250], [600, 300]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_APP = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + bidfloor: 0.5, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + pid: 'ADb1f40rmi', + supplyType: 'app', + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + }, + auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var BID_RESPONSE = { + body: { + cpm: 1.50, + adm: '', + media_type: 'js', + width: 300, + height: 250, + creative_id: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + dealId: 'dealId', + adomain: 'richaudience.com' + } + }; + + var BID_RESPONSE_VIDEO = { + body: { + cpm: 1.50, + media_type: 'video', + width: 1, + height: 1, + creative_id: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + vastXML: '', + dealId: 'dealId', + adomain: 'richaudience.com' + } + }; + + var DEFAULT_PARAMS_GDPR = { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'http://domain.com', + numIframes: 0 + } + } + + it('Referer undefined', function() { + config.setConfig({ + 'currency': {'adServerCurrency': 'USD'} + }) + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('referer').and.to.equal(null); + expect(requestContent).to.have.property('referer').and.to.equal(null); + }) + + it('Verify build request to prebid 3.0 display test', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.5); + expect(requestContent).to.have.property('pid').and.to.equal('ADb1f40rmi'); + expect(requestContent).to.have.property('supplyType').and.to.equal('site'); + expect(requestContent).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); + expect(requestContent).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); + expect(requestContent).to.have.property('BidRequestsCount').and.to.equal(1); + expect(requestContent).to.have.property('bidder').and.to.equal('richaudience'); + expect(requestContent).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); + expect(requestContent).to.have.property('tagId').and.to.equal('test-div'); + expect(requestContent).to.have.property('referer').and.to.equal('https%3A%2F%2Fdomain.com'); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); + expect(requestContent.sizes[2]).to.have.property('w').and.to.equal(728); + expect(requestContent.sizes[2]).to.have.property('h').and.to.equal(90); + expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); + expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); + expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); + expect(requestContent).to.have.property('timeout').and.to.equal(600); + expect(requestContent).to.have.property('numIframes').and.to.equal(0); + expect(typeof requestContent.scr_rsl === 'string') + expect(typeof requestContent.cpuc === 'number') + expect(typeof requestContent.gpid === 'string') + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); + }) + + it('Verify build request to prebid video inestream', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('demand').and.to.equal('video'); + expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); + }) + + it('Verify build request to prebid video outstream', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('demand').and.to.equal('video'); + expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); + }) + + describe('gdpr test', function () { + it('Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'USD' + }, + consentManagement: { + cmpApi: 'iab', + timeout: 8000, + allowAuctionWithoutConsent: true + } + }); + + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); + }); + + it('Verify adding ifa when supplyType equal to app', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_APP, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + }); + + it('Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 8000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA' + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); + }); + }); + + describe('UID test', function () { + config.setConfig({ + consentManagement: { + cmpApi: 'iab', + timeout: 5000, + allowAuctionWithoutConsent: true + }, + userSync: { + userIds: [{ + name: 'id5Id', + params: { + partner: 173, // change to the Partner Number you received from ID5 + pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this + }, + storage: { + type: 'html5', // "html5" is the required storage type + name: 'id5id', // "id5id" is the required storage name + expires: 90, // storage lasts for 90 days + refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh + } + }], + auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules + } + }); + it('Verify build id5', function () { + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build pubCommonId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 'pub_common_user_id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'pub_common_user_id', + 'source': 'pubcommon' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build criteoId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 'criteo-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'criteo-user-id', + 'source': 'criteo.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build identityLink', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'identity-link-user-id', + 'source': 'liveramp.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + it('Verify build liveIntentId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data) + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'identity-link-user-id', + 'source': 'liveramp.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + it('Verify build TradeDesk', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.tdid = 'tdid-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data) + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'tdid-user-id', + 'source': 'adserver.org' + }]); + + request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + }); + + it('Verify interprete response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.ad).to.equal(''); + expect(bid.mediaType).to.equal('js'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + expect(bid.meta).to.equal('richaudience.com'); + }); + + it('no banner media response inestream', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + expect(bid.meta).to.equal('richaudience.com'); + }); + + it('no banner media response outstream', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + page: 'https://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + const bid = bids[0]; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + }); + + it('Verifies bidder_code', function () { + expect(spec.code).to.equal('richaudience'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('ra'); + }); + + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + + it('Verifies if bid request is valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'app' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'app', + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); + }); + + it('should pass schain', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + DEFAULT_PARAMS_NEW_SIZES[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('schain').to.deep.equal(schain); + }) + + it('should pass gpid', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); + }) + + describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('onTimeout exist as a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should send timeouts', function () { + spec.onTimeout(DEFAULT_PARAMS_VIDEO_TIMEOUT); + expect(utils.triggerPixel.called).to.equal(true); + expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=localhost:9876'); + }); + }); + + describe('userSync', function () { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); + it('Verifies user syncs iframe include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user syncs iframe exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: false + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('Verifies user syncs image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [], { + consentString: null, + referer: 'http://domain.com', + gdprApplies: false + }) + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe exclude / image include', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe include / image exclude', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + }) + + var syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true, + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + }); + + it('Verifies user syncs iframe/image include with GPP', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE], { + gppString: 'DBABL~BVVqAAEABgA.QA', + applicableSections: [7]}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({pixelEnabled: true}, [BID_RESPONSE], { + gppString: 'DBABL~BVVqAAEABgA.QA', + applicableSections: [7, 5]}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('Verifies user syncs URL image include with GPP', function () { + const gppConsent = { gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', applicableSections: [0] }; + const result = spec.getUserSyncs({pixelEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'image', url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` + }]); + }); + }) +}); From c2a97859e771aca8b0352b4c214f40e0f12e08bb Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 18 Jun 2024 22:21:19 +0000 Subject: [PATCH 0219/1097] Prebid 9.1.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index de68505b648..d2a57d49dea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.1.0-pre", + "version": "9.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.1.0-pre", + "version": "9.1.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index a4d6e3a61dd..921e3dd9250 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.1.0-pre", + "version": "9.1.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From b03ca3d918b6431be980748f9ed9ca98d71235dd Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 18 Jun 2024 22:21:19 +0000 Subject: [PATCH 0220/1097] Increment version to 9.2.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2a57d49dea..04572fd3c8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.1.0", + "version": "9.2.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.1.0", + "version": "9.2.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 921e3dd9250..ab91c6e006a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.1.0", + "version": "9.2.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From a6b235a000323ebe33c56d65c6fc9888ac163676 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 18 Jun 2024 22:43:55 -0700 Subject: [PATCH 0221/1097] Core: fix video cache silent failure (#11781) * move some video cache logic to videoCache.js * move more video cache logic to videoCache.js * Fix silent failure on video cache (https://github.com/prebid/Prebid.js/issues/11778) --- modules/ceeIdSystem.js | 2 +- src/auction.js | 68 ++----------------- src/videoCache.js | 72 ++++++++++++++++++++ test/mocks/videoCacheStub.js | 2 +- test/spec/auctionmanager_spec.js | 2 +- test/spec/videoCache_spec.js | 109 ++++++++++++++++--------------- 6 files changed, 136 insertions(+), 119 deletions(-) diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js index d1534ddada2..30240e3f75f 100644 --- a/modules/ceeIdSystem.js +++ b/modules/ceeIdSystem.js @@ -43,7 +43,7 @@ export const ceeIdSubmodule = { * performs action to obtain id and return a value * @function * @returns {(IdResponse|undefined)} - */ + */ getId(config) { const { params = {} } = config; const { tokenName, value } = params diff --git a/src/auction.js b/src/auction.js index 881dee9f2de..b422ffa7333 100644 --- a/src/auction.js +++ b/src/auction.js @@ -82,7 +82,7 @@ import { } from './utils.js'; import {getPriceBucketString} from './cpmBucketManager.js'; import {getNativeTargeting, isNativeResponse, setNativeResponseProperties} from './native.js'; -import {getCacheUrl, store} from './videoCache.js'; +import {batchAndStore} from './videoCache.js'; import {Renderer} from './Renderer.js'; import {config} from './config.js'; import {userSync} from './userSync.js'; @@ -94,7 +94,7 @@ import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; -import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS } from './constants.js'; +import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; @@ -580,68 +580,10 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au } } -const _storeInCache = (batch) => { - store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { - cacheIds.forEach((cacheId, i) => { - const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; - if (error) { - logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - } else { - if (cacheId.uuid === '') { - logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); - } else { - bidResponse.videoCacheKey = cacheId.uuid; - if (!bidResponse.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); - } - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); - } - } - }); - }); -}; - -const storeInCache = FEATURES.VIDEO ? _storeInCache : () => {}; - -let batchSize, batchTimeout; -config.getConfig('cache', (cacheConfig) => { - batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 - ? cacheConfig.cache.batchSize - : 1; - batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 - ? cacheConfig.cache.batchTimeout - : 0; -}); - -export const batchingCache = (timeout = setTimeout, cache = storeInCache) => { - let batches = [[]]; - let debouncing = false; - const noTimeout = cb => cb(); - - return function(auctionInstance, bidResponse, afterBidAdded) { - const batchFunc = batchTimeout > 0 ? timeout : noTimeout; - if (batches[batches.length - 1].length >= batchSize) { - batches.push([]); - } - - batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); - - if (!debouncing) { - debouncing = true; - batchFunc(() => { - batches.forEach(cache); - batches = [[]]; - debouncing = false; - }, batchTimeout); - } - } -}; - -const batchAndStore = batchingCache(); - export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, videoMediaType) { - batchAndStore(auctionInstance, bidResponse, afterBidAdded); + if (FEATURES.VIDEO) { + batchAndStore(auctionInstance, bidResponse, afterBidAdded); + } }, 'callPrebidCache'); /** diff --git a/src/videoCache.js b/src/videoCache.js index 6cba77de308..cf39c1c9452 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -12,6 +12,8 @@ import {ajaxBuilder} from './ajax.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; +import {logError, logWarn} from './utils.js'; +import {addBidToAuction} from './auction.js'; /** * Might be useful to be configurable in the future @@ -159,3 +161,73 @@ export function store(bids, done, getAjax = ajaxBuilder) { export function getCacheUrl(id) { return `${config.getConfig('cache.url')}?uuid=${id}`; } + +export const _internal = { + store +} + +export function storeBatch(batch) { + const bids = batch.map(entry => entry.bidResponse) + function err(msg) { + logError(`Failed to save to the video cache: ${msg}. Video bids will be discarded:`, bids) + } + _internal.store(bids, function (error, cacheIds) { + if (error) { + err(error) + } else if (batch.length !== cacheIds.length) { + logError(`expected ${batch.length} cache IDs, got ${cacheIds.length} instead`) + } else { + cacheIds.forEach((cacheId, i) => { + const {auctionInstance, bidResponse, afterBidAdded} = batch[i]; + if (cacheId.uuid === '') { + logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); + } else { + bidResponse.videoCacheKey = cacheId.uuid; + if (!bidResponse.vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); + } + }); + } + }); +}; + +let batchSize, batchTimeout; +if (FEATURES.VIDEO) { + config.getConfig('cache', (cacheConfig) => { + batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 + ? cacheConfig.cache.batchSize + : 1; + batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 + ? cacheConfig.cache.batchTimeout + : 0; + }); +} + +export const batchingCache = (timeout = setTimeout, cache = storeBatch) => { + let batches = [[]]; + let debouncing = false; + const noTimeout = cb => cb(); + + return function (auctionInstance, bidResponse, afterBidAdded) { + const batchFunc = batchTimeout > 0 ? timeout : noTimeout; + if (batches[batches.length - 1].length >= batchSize) { + batches.push([]); + } + + batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); + + if (!debouncing) { + debouncing = true; + batchFunc(() => { + batches.forEach(cache); + batches = [[]]; + debouncing = false; + }, batchTimeout); + } + }; +}; + +export const batchAndStore = batchingCache(); diff --git a/test/mocks/videoCacheStub.js b/test/mocks/videoCacheStub.js index 7ce899cae35..acae5cd6a32 100644 --- a/test/mocks/videoCacheStub.js +++ b/test/mocks/videoCacheStub.js @@ -1,4 +1,4 @@ -import * as videoCache from 'src/videoCache.js'; +import {_internal as videoCache} from 'src/videoCache.js'; /** * Function which can be called from unit tests to stub out the video cache. diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e5cdb66e75f..26f641a10e7 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -12,7 +12,7 @@ import * as auctionModule from 'src/auction.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { createBid } from 'src/bidfactory.js'; import { config } from 'src/config.js'; -import * as store from 'src/videoCache.js'; +import {_internal as store} from 'src/videoCache.js'; import * as ajaxLib from 'src/ajax.js'; import {find} from 'src/polyfill.js'; import { server } from 'test/mocks/xhr.js'; diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index fc6e71779cb..7d07da9de90 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,39 +1,13 @@ import chai from 'chai'; -import {getCacheUrl, store} from 'src/videoCache.js'; +import {batchingCache, getCacheUrl, store, _internal, storeBatch} from 'src/videoCache.js'; import {config} from 'src/config.js'; import {server} from 'test/mocks/xhr.js'; import {auctionManager} from '../../src/auctionManager.js'; import {AuctionIndex} from '../../src/auctionIndex.js'; -import {batchingCache} from '../../src/auction.js'; +import * as utils from 'src/utils.js'; const should = chai.should(); -function getMockBid(bidder, auctionId, bidderRequestId) { - return { - 'bidder': bidder, - 'params': { - 'placementId': '10433394', - 'member': 123, - 'keywords': { - 'foo': ['bar', 'baz'], - 'fizz': ['buzz'] - } - }, - 'bid_id': '12345abc', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', - 'sizes': [300, 250], - 'bidId': '123', - 'bidderRequestId': bidderRequestId, - 'auctionId': auctionId - }; -} - describe('The video cache', function () { function assertError(callbackSpy) { callbackSpy.calledOnce.should.equal(true); @@ -126,9 +100,7 @@ describe('The video cache', function () { prebid.org wrapper - - - + \n \n `; @@ -335,34 +307,36 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); - it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { - const mockAfterBidAdded = function() {}; - let callback = null; - let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); + if (FEATURES.VIDEO) { + it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { + const mockAfterBidAdded = function() {}; + let callback = null; + let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache', - batchSize: 3, - batchTimeout: 20 - } - }); + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + batchSize: 3, + batchTimeout: 20 + } + }); - let stubCache = sinon.stub(); - const batchAndStore = batchingCache(mockTimeout, stubCache); - for (let i = 0; i < 3; i++) { - batchAndStore({}, {}, mockAfterBidAdded); - } + let stubCache = sinon.stub(); + const batchAndStore = batchingCache(mockTimeout, stubCache); + for (let i = 0; i < 3; i++) { + batchAndStore({}, {}, mockAfterBidAdded); + } - sinon.assert.calledOnce(mockTimeout); - sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); + sinon.assert.calledOnce(mockTimeout); + sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); - const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; + const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; - callback(); + callback(); - sinon.assert.calledWith(stubCache, expectedBatch); - }); + sinon.assert.calledWith(stubCache, expectedBatch); + }); + } function assertRequestMade(bid, expectedValue) { store([bid], function () { }); @@ -393,6 +367,35 @@ describe('The video cache', function () { return callback; } }); + + describe('storeBatch', () => { + let sandbox; + let err, cacheIds + beforeEach(() => { + err = null; + cacheIds = []; + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'logError'); + sandbox.stub(_internal, 'store').callsFake((_, cb) => cb(err, cacheIds)); + }); + afterEach(() => { + sandbox.restore(); + }) + it('should log an error when store replies with an error', () => { + err = new Error('err'); + storeBatch([]); + sinon.assert.called(utils.logError); + }); + it('should not process returned uuids if they do not match the batch size', () => { + const el = {auctionInstance: {}, bidResponse: {}, afterBidAdded: sinon.stub()} + const batch = [el, el]; + cacheIds = [{uuid: 'mock-id'}] + storeBatch(batch); + expect(el.bidResponse.videoCacheKey).to.not.exist; + sinon.assert.notCalled(batch[0].afterBidAdded); + sinon.assert.called(utils.logError); + }) + }) }); describe('The getCache function', function () { From b350eabc9714b6fc6c7c8605aa727935f71376fb Mon Sep 17 00:00:00 2001 From: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:10:56 +0200 Subject: [PATCH 0222/1097] ID5 UserId module - integrate with TrueLink Id (#11802) * ID5 User Id module - integrate with TrueLinkId * ID5 User Id module - eids documentation * ID5 User Id module - changed the documentation, so it doesn't use multiplication Some partners are setting refreshInSeconds as string with value '8*3600', taken from the configuration. By using just a number we can still parse it (but we don't want to parse mathematical operations) --- modules/id5IdSystem.js | 33 ++++++++++- modules/id5IdSystem.md | 69 +++++++++++++++++++++- test/spec/modules/id5IdSystem_spec.js | 82 ++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index e7a7ec7f037..1f369f5a7a1 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -41,6 +41,7 @@ const LOCAL_STORAGE = 'html5'; const LOG_PREFIX = 'User ID - ID5 submodule: '; const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; const ID5_DOMAIN = 'id5-sync.com'; +const TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use @@ -134,12 +135,13 @@ export const id5IdSubmodule = { * @returns {(Object|undefined)} */ decode(value, config) { - let universalUid; + let universalUid, publisherTrueLinkId; let ext = {}; if (value && typeof value.universal_uid === 'string') { universalUid = value.universal_uid; ext = value.ext || ext; + publisherTrueLinkId = value.publisherTrueLinkId; } else { return undefined; } @@ -159,6 +161,12 @@ export const id5IdSubmodule = { }; } + if (publisherTrueLinkId) { + responseObj.trueLinkId = { + uid: publisherTrueLinkId, + }; + } + const abTestingResult = deepAccess(value, 'ab_testing.result'); switch (abTestingResult) { case 'control': @@ -263,7 +271,22 @@ export const id5IdSubmodule = { return data.ext; } } + }, + 'trueLinkId': { + getValue: function (data) { + return data.uid; + }, + getSource: function (data) { + return TRUE_LINK_SOURCE; + }, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } } + } }; @@ -380,6 +403,8 @@ export class IdFetchFlow { const referer = getRefererInfo(); const signature = (this.cacheIdObj && this.cacheIdObj.signature) ? this.cacheIdObj.signature : getLegacyCookieSignature(); const nbPage = incrementAndResetNb(params.partner); + const trueLinkInfo = window.id5Bootstrap ? window.id5Bootstrap.getTrueLinkInfo() : {booted: false}; + const data = { 'partner': params.partner, 'gdpr': hasGdpr, @@ -392,7 +417,8 @@ export class IdFetchFlow { 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', 'storage': this.submoduleConfig.storage, - 'localStorage': storage.localStorageIsEnabled() ? 1 : 0 + 'localStorage': storage.localStorageIsEnabled() ? 1 : 0, + 'true_link': trueLinkInfo }; // pass in optional data, but only if populated @@ -431,6 +457,9 @@ export class IdFetchFlow { try { if (fetchCallResponse.privacy) { storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); + if (window.id5Bootstrap && window.id5Bootstrap.setPrivacy) { + window.id5Bootstrap.setPrivacy(fetchCallResponse.privacy); + } } } catch (error) { logError(LOG_PREFIX + 'Error while writing privacy info into local storage.', error); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 592c69056fa..a2782b70559 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -37,7 +37,7 @@ pbjs.setConfig({ type: 'html5', // "html5" is the required storage type name: 'id5id', // "id5id" is the required storage name expires: 90, // storage lasts for 90 days - refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh + refreshInSeconds: 7200 // refresh ID every 2 hours to ensure it's fresh } }], auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules @@ -61,7 +61,7 @@ pbjs.setConfig({ | storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | | storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | -| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). @@ -73,3 +73,68 @@ To turn on A/B Testing, simply edit the configuration (see above table) to enabl ### A Note on Using Multiple Wrappers If you or your monetization partners are deploying multiple Prebid wrappers on your websites, you should make sure you add the ID5 ID User ID module to *every* wrapper. Only the bidders configured in the Prebid wrapper where the ID5 ID User ID module is installed and configured will be able to pick up the ID5 ID. Bidders from other Prebid instances will not be able to pick up the ID5 ID. + +### Provided eids +The module provides following eids: + +``` +[ + { + source: 'id5-sync.com', + uids: [ + { + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 2, + abTestingControlGroup: false + } + } + ] + }, + { + source: 'true-link-id5-sync.com', + uids: [ + { + id: 'some-publisher-true-link-id', + atype: 1 + } + ] + }, + { + source: 'uidapi.com', + uids: [ + { + id: 'some-uid2', + atype: 3, + ext: { + provider: 'id5-sync.com' + } + } + ] + } +] +``` + +The id from `id5-sync.com` should be always present (though the id provided will be '0' in case of no consent or optout) + +The id from `true-link-id5-sync.com` will be available if the page is integrated with TrueLink (if you are an ID5 partner you can learn more at https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/true-link-integration) + +The id from `uidapi.com` will be available if the partner that is used in ID5 user module has the EUID2 integration enabled (it has to be enabled on the ID5 side) + + +### Providing TrueLinkId as a Google PPID + +TrueLinkId can be provided as a PPID - to use, it the `true-link-id5-sync.com` needs to be provided as a ppid source in prebid userSync configuration: + +```javascript +pbjs.setConfig({ + userSync: { + ppid: 'true-link-id5-sync.com', + userIds: [], //userIds modules should be configured here + } +}); +``` + + + diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 7a2756dff9e..eae5fd21310 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -26,6 +26,7 @@ describe('ID5 ID System', function () { const ID5_MODULE_NAME = 'id5Id'; const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); const ID5_SOURCE = 'id5-sync.com'; + const TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; const ID5_TEST_PARTNER_ID = 173; const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_TEST_PARTNER_ID}.json`; const ID5_API_CONFIG_URL = `https://id5-sync.com/api/config/prebid`; @@ -48,10 +49,8 @@ describe('ID5 ID System', function () { const EUID_STORED_ID = 'EUID_1'; const EUID_SOURCE = 'uidapi.com'; const ID5_STORED_OBJ_WITH_EUID = { - 'universal_uid': ID5_STORED_ID, - 'signature': ID5_STORED_SIGNATURE, + ...ID5_STORED_OBJ, 'ext': { - 'linkType': ID5_STORED_LINK_TYPE, 'euid': { 'source': EUID_SOURCE, 'uids': [{ @@ -61,6 +60,11 @@ describe('ID5 ID System', function () { } } }; + const TRUE_LINK_STORED_ID = 'TRUE_LINK_1'; + const ID5_STORED_OBJ_WITH_TRUE_LINK = { + ...ID5_STORED_OBJ, + publisherTrueLinkId: TRUE_LINK_STORED_ID + }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; const ID5_RESPONSE_LINK_TYPE = 2; @@ -148,6 +152,16 @@ describe('ID5 ID System', function () { }); } + function wrapAsyncExpects(done, expectsFn) { + return function () { + try { + expectsFn(); + } catch (err) { + done(err); + } + } + } + class XhrServerMock { currentRequestIdx = 0; server; @@ -837,6 +851,38 @@ describe('ID5 ID System', function () { id5System.id5IdSubmodule.getId(getId5FetchConfig()); }); }); + + it('should pass true link info to ID5 server even when true link is not booted', function () { + let xhrServerMock = new XhrServerMock(server); + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.true_link).is.deep.equal({booted: false}); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); + }); + + it('should pass full true link info to ID5 server when true link is booted', function () { + let xhrServerMock = new XhrServerMock(server); + let trueLinkResponse = {booted: true, redirected: true, id: 'TRUE_LINK_ID'}; + window.id5Bootstrap = { + getTrueLinkInfo: function () { + return trueLinkResponse; + } + }; + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.true_link).is.deep.equal(trueLinkResponse); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); + }); }); describe('Local storage', () => { @@ -950,6 +996,31 @@ describe('ID5 ID System', function () { }, {adUnits}); }); + it('should add stored TRUE_LINK_ID from cache to bids', function (done) { + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + requestBidsHook(wrapAsyncExpects(done, function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); + expect(bid.userId.trueLinkId.uid).is.equal(TRUE_LINK_STORED_ID); + expect(bid.userIdAsEids[1]).is.deep.equal({ + source: TRUE_LINK_SOURCE, + uids: [{ + id: TRUE_LINK_STORED_ID, + atype: 1, + }] + }); + }); + }); + done(); + }), {adUnits}); + }); + it('should add config value ID to bids', function (done) { init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); @@ -1056,6 +1127,11 @@ describe('ID5 ID System', function () { 'ext': {'provider': ID5_SOURCE} }); }); + it('should decode trueLinkId from a stored object with trueLinkId', function () { + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_TRUE_LINK, getId5FetchConfig()).trueLinkId).is.deep.equal({ + 'uid': TRUE_LINK_STORED_ID + }); + }); }); describe('A/B Testing', function () { From 4fb10cd3b5a0c31221b5cdb6b789031ae54c0676 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 19 Jun 2024 19:48:46 +0200 Subject: [PATCH 0223/1097] Utils: add `getDomLoadingDuration()` to remove dupe code (#11831) --- modules/adagioRtdProvider.js | 21 +++---------- modules/bliinkBidAdapter.js | 34 ++-------------------- src/utils.js | 27 +++++++++++++++++ test/spec/modules/bliinkBidAdapter_spec.js | 10 +++++-- 4 files changed, 42 insertions(+), 50 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index a901c2c489d..7c5ff1352df 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -15,6 +15,7 @@ import { deepAccess, deepSetValue, generateUUID, + getDomLoadingDuration, getUniqueIdentifierStr, getWindowSelf, getWindowTop, @@ -131,12 +132,14 @@ const _FEATURES = (function() { features.data = {}; }, get: function() { + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + if (!features.initialized) { features.data = { page_dimensions: getPageDimensions().toString(), viewport_dimensions: getViewPortDimensions().toString(), user_timestamp: getTimestampUTC().toString(), - dom_loading: getDomLoadingDuration().toString(), + dom_loading: getDomLoadingDuration(w).toString(), }; features.initialized = true; } @@ -538,22 +541,6 @@ function getTimestampUTC() { return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; } -function getDomLoadingDuration() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - const performance = w.performance; - - let domLoadingDuration = -1; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - /** * registerEventsForAdServers bind adagio listeners to ad-server events. * Theses events are used to track the viewability and attention. diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 0fb56949539..b66923fd476 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' -import { _each, deepAccess, deepSetValue, getWindowSelf, getWindowTop } from '../src/utils.js' +import { _each, canAccessWindowTop, deepAccess, deepSetValue, getDomLoadingDuration, getWindowSelf, getWindowTop } from '../src/utils.js' export const BIDDER_CODE = 'bliink' export const GVL_ID = 658 export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/prebid' @@ -121,35 +121,6 @@ export function getKeywords() { return []; } -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -} - -/** - * domLoading feature is computed on window.top if reachable. - */ -export function getDomLoadingDuration() { - let domLoadingDuration = -1; - let performance; - - performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - /** * @param bidResponse * @return {({cpm, netRevenue: boolean, requestId, width: number, currency, ttl: number, creativeId, height: number}&{mediaType: string, vastXml})|null} @@ -208,7 +179,8 @@ export const isBidRequestValid = (bid) => { */ export const buildRequests = (validBidRequests, bidderRequest) => { if (!validBidRequests || !bidderRequest || !bidderRequest.bids) return null - const domLoadingDuration = getDomLoadingDuration().toString(); + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + const domLoadingDuration = getDomLoadingDuration(w).toString(); const tags = bidderRequest.bids.map((bid) => { let bidFloor; const sizes = bid.sizes.map((size) => ({ w: size[0], h: size[1] })); diff --git a/src/utils.js b/src/utils.js index 1f05b7b37c1..13613dc7a7f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -693,6 +693,33 @@ export function getPerformanceNow() { return (window.performance && window.performance.now && window.performance.now()) || 0; } +/** + * Retuns the difference between `timing.domLoading` and `timing.navigationStart`. + * This function uses the deprecated `Performance.timing` API and should be removed in future. + * It has not been updated yet because it is still used in some modules. + * @deprecated + * @param {Window} w The window object used to perform the api call. default to window.self + * @returns {number} + */ +export function getDomLoadingDuration(w) { + let domLoadingDuration = -1; + + w = w || getWindowSelf(); + + const performance = w.performance; + + if (w.performance?.timing) { + if (w.performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) { + domLoadingDuration = val; + } + } + } + + return domLoadingDuration; +} + /** * When the deviceAccess flag config option is false, no cookies should be read or set * @returns {boolean} diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index 3db97a17d88..ff48d8579a7 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -7,9 +7,14 @@ import { BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME, getEffectiveConnectionType, getUserIds, - getDomLoadingDuration, GVL_ID, } from 'modules/bliinkBidAdapter.js'; +import { + canAccessWindowTop, + getDomLoadingDuration, + getWindowSelf, + getWindowTop +} from 'src/utils.js'; import { config } from 'src/config.js'; /** @@ -32,8 +37,9 @@ import { config } from 'src/config.js'; * ortb2Imp: {ext: {data: {pbadslot: string}}}}} */ +const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); const connectionType = getEffectiveConnectionType(); -const domLoadingDuration = getDomLoadingDuration().toString(); +const domLoadingDuration = getDomLoadingDuration(w).toString(); const getConfigBid = (placement) => { return { adUnitCode: '/19968336/test', From ede12d28857eba90998f7d4fa1527438d500e9a2 Mon Sep 17 00:00:00 2001 From: sangarbe Date: Wed, 19 Jun 2024 19:53:35 +0200 Subject: [PATCH 0224/1097] sends gpid into seedtag adapter payload (#11832) --- modules/seedtagBidAdapter.js | 1 + test/spec/modules/seedtagBidAdapter_spec.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 284e62e70fe..7170f8b04b7 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -119,6 +119,7 @@ function buildBidRequest(validBidRequest) { const bidRequest = { id: validBidRequest.bidId, transactionId: validBidRequest.ortb2Imp?.ext?.tid, + gpid: validBidRequest.ortb2Imp?.ext?.gpid, sizes: validBidRequest.sizes, supplyTypes: mediaTypes, adUnitId: params.adUnitId, diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index ed3e2c9be0b..1e0141b5107 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -37,6 +37,7 @@ function getSlotConfigs(mediaTypes, params) { ortb2Imp: { ext: { tid: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + gpid: 'some-gpid' } }, adUnitCode: adUnitCode, @@ -299,6 +300,7 @@ describe('Seedtag Adapter', function () { expect(data.ttfb).to.be.greaterThanOrEqual(0); expect(data.bidRequests[0].adUnitCode).to.equal(adUnitCode); + expect(data.bidRequests[0].gpid).to.equal('some-gpid'); }); describe('GDPR params', function () { From 4bf11bd17cbeb7a4f6a276311cded55f6c1ec666 Mon Sep 17 00:00:00 2001 From: nalexand <35492736+nalexand@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:55:19 +0200 Subject: [PATCH 0225/1097] SmartytechBidAdapter: Add meta (#11815) * Add mediaimpact bid adapter * Add mediaimpact bid adapter tests * Add custom sizes * Refactor response meta * mediaimpact adapter fix tests * SmartytechBidAdapter: Add meta * MediaimpactBidAdapter: Fix tests * MediaimpactBidAdapter: Fix tests * Add meta to tests * rerun tests * rerun tests --------- Co-authored-by: koshe --- modules/smartytechBidAdapter.js | 9 +++++++-- test/spec/modules/smartytechBidAdapter_spec.js | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index af1442bd301..c081b49c2e6 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -127,14 +127,19 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, - mediaType: BANNER - } + mediaType: BANNER, + meta: {} + }; if (response.mediaType === VIDEO) { bidObject.vastXml = response.ad; bidObject.mediaType = VIDEO; } + if (response.meta) { + bidObject.meta = response.meta; + } + return bidObject; }, diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 6b3147859bf..3b6d5d0c5fc 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -204,7 +204,11 @@ function mockResponseData(requestData) { creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), currency: `UAH-${rndIndex}`, - mediaType: mediaType + mediaType: mediaType, + meta: { + primaryCatId: 'IAB2-2', + secondaryCatIds: ['IAB2-14', 'IAB2-6'] + } }; }); return { From eacd24efb485fa5c457d7b56d1bce20898905e46 Mon Sep 17 00:00:00 2001 From: Rupesh Lakhani <35333377+AskRupert-DM@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:28:32 +0100 Subject: [PATCH 0226/1097] Ozone Bid Adapter: add support for GPP Module & updates (#11648) * Update ozoneBidAdapter.js * Update ozoneBidAdapter_spec.js * Update ozoneBidAdapter_spec.js * Update ozoneBidAdapter.js fix to address plcmt feedback --- modules/ozoneBidAdapter.js | 175 ++++++++++++------ test/spec/modules/ozoneBidAdapter_spec.js | 214 +++++++++++++++++++--- 2 files changed, 309 insertions(+), 80 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 1e5b2ae8ca5..9968072eff4 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,5 +1,4 @@ import { - deepClone, logInfo, logError, deepAccess, @@ -9,7 +8,7 @@ import { contains, mergeDeep, parseUrl, - generateUUID + generateUUID, isInteger, deepClone } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -23,7 +22,7 @@ const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.1'; +const OZONEVERSION = '2.9.2'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], @@ -39,11 +38,11 @@ export const spec = { 'auctionUrl': ORIGIN + AUCTIONURI, 'cookieSyncUrl': ORIGIN + OZONECOOKIESYNC, 'rendererUrl': OZONE_RENDERER_URL, - 'batchRequests': false /* you can change this to true OR override it in the config: config.ozone.batchRequests */ + 'batchRequests': false /* you can change this to true OR numeric OR override it in the config: config.ozone.batchRequests = true/false/number */ }, loadWhitelabelData(bid) { if (this.propertyBag.whitelabel) { return; } - this.propertyBag.whitelabel = deepClone(this.whitelabel_defaults); + this.propertyBag.whitelabel = JSON.parse(JSON.stringify(this.whitelabel_defaults)); let bidder = bid.bidder || 'ozone'; // eg. ozone this.propertyBag.whitelabel.logId = bidder.toUpperCase(); this.propertyBag.whitelabel.bidder = bidder; @@ -77,10 +76,22 @@ export const spec = { } } if (bidderConfig.hasOwnProperty('batchRequests')) { - this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; + if (this.batchValueIsValid(bidderConfig.batchRequests)) { + this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; + } else { + logError('bidderConfig.batchRequests must be boolean or a number. Found & ignored data type: ' + typeof bidderConfig.batchRequests); + } + } + if (bidderConfig.hasOwnProperty('videoParams')) { + this.propertyBag.whitelabel.videoParams = bidderConfig.videoParams; } if (arr.hasOwnProperty('batchRequests')) { - this.propertyBag.whitelabel.batchRequests = true; + let getBatch = parseInt(arr.batchRequests); + if (this.batchValueIsValid(getBatch)) { + this.propertyBag.whitelabel.batchRequests = getBatch; + } else { + logError('Ignoring query param: batchRequests - this must be a positive number'); + } } try { if (arr.hasOwnProperty('auction') && arr.auction === 'dev') { @@ -94,6 +105,9 @@ export const spec = { } catch (e) {} logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); }, + batchValueIsValid(batch) { + return typeof batch === 'boolean' || (typeof batch === 'number' && batch > 0); + }, getAuctionUrl() { return this.propertyBag.whitelabel.auctionUrl; }, @@ -103,9 +117,17 @@ export const spec = { getRendererUrl() { return this.propertyBag.whitelabel.rendererUrl; }, - isBatchRequests() { - logInfo('isBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); - return this.propertyBag.whitelabel.batchRequests; + getVideoPlacementValue: function(context) { + if (['instream', 'outstream'].indexOf(context) < 0) return null; + return deepAccess(this.propertyBag, `whitelabel.videoParams.${context}`, null); + }, + getBatchRequests() { + logInfo('getBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); + if (this.propertyBag.whitelabel.batchRequests === true) { return 10; } + if (typeof this.propertyBag.whitelabel.batchRequests === 'number' && this.propertyBag.whitelabel.batchRequests > 0) { + return this.propertyBag.whitelabel.batchRequests; + } + return false; }, isBidRequestValid(bid) { this.loadWhitelabelData(bid); @@ -182,9 +204,10 @@ export const spec = { if (this.blockTheRequest()) { return []; } + let fledgeEnabled = !!bidderRequest.fledgeEnabled; // IF true then this is added as each bid[].ext.ae=1 let htmlParams = {'publisherId': '', 'siteId': ''}; if (validBidRequests.length > 0) { - this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); + this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIdsFromEids(validBidRequests[0])); this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; @@ -306,6 +329,14 @@ export const spec = { if (gpid) { deepSetValue(obj, 'ext.gpid', gpid); } + if (fledgeEnabled) { // fledge is enabled at some config level - pbjs.setBidderConfig or pbjs.setConfig + const auctionEnvironment = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.ae'); // this will be set for one of 3 reasons; adunit, setBidderConfig, setConfig + if (isInteger(auctionEnvironment)) { + deepSetValue(obj, 'ext.ae', auctionEnvironment); + } else { + logError('ortb2Imp.ext.ae is not an integer - ignoring it for obj.id=' + obj.id); + } + } return obj; }); let extObj = {}; @@ -314,8 +345,8 @@ export const spec = { extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - if (userIds.hasOwnProperty('pubcid')) { - extObj[whitelabelBidder].pubcid = userIds.pubcid; + if (userIds.hasOwnProperty('pubcid.org')) { + extObj[whitelabelBidder].pubcid = userIds['pubcid.org']; } } extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called @@ -363,6 +394,10 @@ export const spec = { } else { logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); } + if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(ozoneRequest, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(ozoneRequest, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } if (schain) { // we set this while iterating over the bids logInfo('schain found'); deepSetValue(ozoneRequest, 'source.ext.schain', schain); @@ -370,15 +405,26 @@ export const spec = { if (config.getConfig('coppa') === true) { deepSetValue(ozoneRequest, 'regs.coppa', 1); } + extObj[whitelabelBidder].cookieDeprecationLabel = deepAccess(bidderRequest, 'ortb2.device.ext.cdep', 'none'); + logInfo('cookieDeprecationLabel from bidderRequest object = ' + extObj[whitelabelBidder].cookieDeprecationLabel); let ozUuid = generateUUID(); - if (this.isBatchRequests()) { + let batchRequestsVal = this.getBatchRequests(); // false|numeric + if (typeof batchRequestsVal === 'number') { logInfo('going to batch the requests'); let arrRet = []; // return an array of objects containing data describing max 10 bids - for (let i = 0; i < tosendtags.length; i += 10) { - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) - ozoneRequest.imp = tosendtags.slice(i, i + 10); - ozoneRequest.ext = extObj; + for (let i = 0; i < tosendtags.length; i += batchRequestsVal) { + if (bidderRequest.auctionId) { + logInfo('Found bidderRequest.auctionId - will pass these values through & not generate our own id'); + ozoneRequest.id = bidderRequest.auctionId; + ozoneRequest.auctionId = bidderRequest.auctionId; + deepSetValue(ozoneRequest, 'source.tid', deepAccess(bidderRequest, 'ortb2.source.tid')); + } else { + logInfo('Did not find bidderRequest.auctionId - will generate our own id'); + ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + } deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + ozoneRequest.imp = tosendtags.slice(i, i + batchRequestsVal); + ozoneRequest.ext = extObj; if (ozoneRequest.imp.length > 0) { arrRet.push({ method: 'POST', @@ -394,7 +440,15 @@ export const spec = { logInfo('requests will not be batched.'); if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + if (bidderRequest.auctionId) { + logInfo('Found bidderRequest.auctionId - will pass these values through & not generate our own id'); + ozoneRequest.id = bidderRequest.auctionId; + ozoneRequest.auctionId = bidderRequest.auctionId; + deepSetValue(ozoneRequest, 'source.tid', deepAccess(bidderRequest, 'ortb2.source.tid')); + } else { + logInfo('Did not find bidderRequest.auctionId - will generate our own id'); + ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + } ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); @@ -522,6 +576,7 @@ export const spec = { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_size'] = String(allBidsForThisBidid[bidderName].width) + 'x' + String(allBidsForThisBidid[bidderName].height); if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); } @@ -572,10 +627,28 @@ export const spec = { arrAllBids.push(thisBid); } } + let ret = arrAllBids; + let fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.igi') || []; // 20240606 standardising + if (Array.isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { + fledgeAuctionConfigs = fledgeAuctionConfigs.filter(config => { + if (!this.isValidAuctionConfig(config)) { + logWarn('Malformed auction config detected:', config); + return false; + } + return true; + }); + ret = { + bids: arrAllBids, + fledgeAuctionConfigs, + }; + } let endTime = new Date().getTime(); logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); - logInfo('interpretResponse arrAllBids (serialised): ', deepClone(arrAllBids)); // this is ok to log because the renderer has not been attached yet - return arrAllBids; + logInfo('interpretResponse arrAllBids (serialised): ', deepClone(ret)); // this is ok to log because the renderer has not been attached yet + return ret; + }, + isValidAuctionConfig(config) { + return typeof config === 'object' && config !== null; }, setBidMediaTypeIfNotExist(thisBid, mediaType) { if (!thisBid.hasOwnProperty('mediaType')) { @@ -614,11 +687,12 @@ export const spec = { } return ret; }, - getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy, gppConsent = {}) { logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } + let { gppString = '', applicableSections = [] } = gppConsent; if (optionsType.iframeEnabled) { var arrQueryString = []; if (config.getConfig('debug')) { @@ -627,6 +701,10 @@ export const spec = { arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); arrQueryString.push('usp_consent=' + (usPrivacy || '')); + arrQueryString.push('gpp=' + gppString); + if (Array.isArray(applicableSections)) { + arrQueryString.push(`gpp_sid=${applicableSections.join()}`); + } for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } @@ -660,43 +738,26 @@ export const spec = { } return null; }, - findAllUserIds(bidRequest) { - var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; - if (bidRequest.hasOwnProperty('userId')) { - for (let arrayId in searchKeysSingle) { - let key = searchKeysSingle[arrayId]; - if (bidRequest.userId.hasOwnProperty(key)) { - if (typeof (bidRequest.userId[key]) == 'string') { - ret[key] = bidRequest.userId[key]; - } else if (typeof (bidRequest.userId[key]) == 'object') { - logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); - ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values - } else { - logError(`failed to get string key value for userId : ${key}`); - } - } - } - let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); - if (lipbid) { - ret['lipb'] = {'lipbid': lipbid}; - } - let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); - if (id5id) { - ret['id5id'] = id5id; - } - let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); - if (sharedid) { - ret['sharedid'] = sharedid; - } + findAllUserIdsFromEids(bidRequest) { + let ret = {}; + if (!bidRequest.hasOwnProperty('userIdAsEids')) { + logInfo('findAllUserIdsFromEids - no bidRequest.userIdAsEids object - will quit'); + this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy + return ret; + } + for (let obj of bidRequest.userIdAsEids) { + ret[obj.source] = deepAccess(obj, 'uids.0.id'); } + this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy + return ret; + }, + tryGetPubCidFromOldLocation(ret, bidRequest) { if (!ret.hasOwnProperty('pubcid')) { let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); if (pubcid) { - ret['pubcid'] = pubcid; // if built with old pubCommonId module + ret['pubcid.org'] = pubcid; // if built with old pubCommonId module (use the new eid key) } } - return ret; }, getPlacementId(bidRequest) { return (bidRequest.params.placementId).toString(); @@ -792,11 +853,9 @@ export const spec = { return objRet; }, _addVideoDefaults(objRet, objConfig, addIfMissing) { - let context = deepAccess(objConfig, 'context'); - if (context === 'outstream') { - objRet.placement = 3; - } else if (context === 'instream') { - objRet.placement = 1; + let placementValue = this.getVideoPlacementValue(deepAccess(objConfig, 'context')); + if (placementValue) { + objRet.placement = placementValue; } let skippable = deepAccess(objConfig, 'skippable', null); if (skippable == null) { diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 73df2fba8fd..b48943da266 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import {Renderer} from '../../../src/Renderer.js'; import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozoneBidAdapter.js'; import * as utils from '../../../src/utils.js'; +import {deepSetValue} from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; var validBidRequests = [ @@ -401,6 +402,66 @@ var validBidderRequest = { start: 1536838908987, timeout: 3000 }; +var validBidderRequestWithCookieDeprecation = { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000, + ortb2: { + 'device': { + 'w': 1617, + 'h': 317, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '125' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '125' + ] + }, + { + 'brand': 'Not.A/Brand', + 'version': [ + '24' + ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'fake_control_2' + } + } + } +}; var bidderRequestWithFullGdpr = { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', auctionStart: 1536838908986, @@ -1814,7 +1875,7 @@ describe('ozone Adapter', function () { }); it('should add gdpr consent information to the request when ozone is true', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, @@ -1832,7 +1893,7 @@ describe('ozone Adapter', function () { }); it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, @@ -1848,7 +1909,7 @@ describe('ozone Adapter', function () { }); it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, @@ -1863,9 +1924,24 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.regs.ext.gdpr).to.equal(0); }); + it('should set gpp and gpp_sid when available', function() { + let gppString = 'gppConsentString'; + let gppSections = [7, 8, 9]; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = {regs: {gpp: gppString, gpp_sid: gppSections}}; + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.gpp).to.equal(gppString); + expect(payload.regs.gpp_sid).to.have.same.members(gppSections); + }); + it('should not set gpp and gpp_sid keys when not available', function() { + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload).to.not.contain.keys(['gpp', 'gpp_sid', 'ext', 'regs']); + }); it('should not have imp[N].ext.ozone.userId', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, @@ -1876,7 +1952,7 @@ describe('ozone Adapter', function () { purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} } }; - let bidRequests = validBidRequests; + let bidRequests = JSON.parse(JSON.stringify(validBidRequests)); bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, @@ -1891,23 +1967,12 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); let firstBid = payload.imp[0].ext.ozone; expect(firstBid).to.not.have.property('userId'); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, validBidderRequest); const payload = JSON.parse(request.data); expect(payload.ext.ozone.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest); @@ -2056,7 +2121,7 @@ describe('ozone Adapter', function () { const data = JSON.parse(request.data); expect(data.imp[0].ext.gpid).to.equal('/22037345/projectozone'); }); - it('should batch into 10s if config is set', function () { + it('should batch into 10s if config is set to true', function () { config.setConfig({ozone: {'batchRequests': true}}); var specMock = utils.deepClone(spec); let arrReq = []; @@ -2069,7 +2134,20 @@ describe('ozone Adapter', function () { expect(request.length).to.equal(3); config.resetConfig(); }); - it('should not batch into 10s if config is set to false and singleRequest is true', function () { + it('should batch into 7 if config is set to 7', function () { + config.setConfig({ozone: {'batchRequests': 7}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 25; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(4); + config.resetConfig(); + }); + it('should not batch if config is set to false and singleRequest is true', function () { config.setConfig({ozone: {'batchRequests': false, 'singleRequest': true}}); var specMock = utils.deepClone(spec); let arrReq = []; @@ -2082,6 +2160,57 @@ describe('ozone Adapter', function () { expect(request.method).to.equal('POST'); config.resetConfig(); }); + it('should not batch if config is set to invalid value -10 and singleRequest is true', function () { + config.setConfig({ozone: {'batchRequests': -10, 'singleRequest': true}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 15; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); + config.resetConfig(); + }); + it('should use GET values for batchRequests if found', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': '5'}; + }; + let arrReq = []; + for (let i = 0; i < 25; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + let request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(5); // 5 x 5 = 25 + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': '10'}; // the built in function will return '10' (string) + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(3); // 10, 10, 5 + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': true}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching - GET param must be numeric + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': 'true'}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching - GET param must be numeric + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': -5}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching + }); it('should use GET values auction=dev & cookiesync=dev if set', function() { var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { @@ -2295,6 +2424,27 @@ describe('ozone Adapter', function () { expect(data.source.ext).to.haveOwnProperty('schain'); expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` }); + it('should find ortb2 cookieDeprecation values', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequestWithCookieDeprecation)); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('fake_control_2'); + }); + it('should set ortb2 cookieDeprecation to "none" if there is none', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('none'); + }); + it('should handle fledge requests', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + let bidRequests = JSON.parse(JSON.stringify(validBidRequests)); + deepSetValue(bidRequests[0], 'ortb2Imp.ext.ae', 1); + bidderRequest.fledgeEnabled = true; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.ae).to.equal(1); + }); }); describe('interpretResponse', function () { beforeEach(function () { @@ -2509,6 +2659,20 @@ describe('ozone Adapter', function () { const bid = result[0]; expect(bid.mediaType).to.equal('video'); }); + it('should handle fledge response', function () { + const req = spec.buildRequests(validBidRequests, validBidderRequest); + let objResp = JSON.parse(JSON.stringify(validResponse)); + objResp.body.ext = {igi: [{ + 'impid': '1', + 'igb': [{ + 'origin': 'https://paapi.dsp.com', + 'pbs': '{"key": "value"}' + }] + }]}; + const result = spec.interpretResponse(objResp, req); + expect(result).to.be.an('object'); + expect(result.fledgeAuctionConfigs[0]['impid']).to.equal('1'); + }); }); describe('userSyncs', function () { it('should fail gracefully if no server response', function () { @@ -2540,6 +2704,10 @@ describe('ozone Adapter', function () { expect(result).to.be.an('array'); expect(result[0].url).to.include('usp_consent=&'); }); + it('should add gpp if its present', function () { + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1, '1---', { gppString: 'gppStringHere', applicableSections: [7, 8, 9] }); + expect(result[0].url).to.include('gpp=gppStringHere&gpp_sid=7,8,9'); + }); }); describe('video object utils', function () { it('should find width & height from video object', function () { @@ -2698,7 +2866,7 @@ describe('ozone Adapter', function () { }); }); describe('addVideoDefaults', function() { - it('should correctly add video defaults', function () { + it('should not add video defaults if there is no videoParams config', function () { let mediaTypes = { playerSize: [640, 480], mimes: ['video/mp4'], @@ -2715,12 +2883,14 @@ describe('ozone Adapter', function () { testKey: 'child value' }; let result = spec.addVideoDefaults({}, mediaTypes, mediaTypes); - expect(result.placement).to.equal(3); + expect(result.placement).to.be.undefined; expect(result.skip).to.equal(0); result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); expect(result.skip).to.equal(1); }); - it('should correctly add video defaults including skippable in parent', function () { + it('should correctly add video defaults if page config videoParams is defined, also check skip in the parent', function () { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel.videoParams = {outstream: 3, instream: 1}; let mediaTypes = { playerSize: [640, 480], mimes: ['video/mp4'], @@ -2736,7 +2906,7 @@ describe('ozone Adapter', function () { skipafter: 5, testKey: 'child value' }; - let result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); + let result = specMock.addVideoDefaults({}, mediaTypes, bid_params_video); expect(result.placement).to.equal(3); expect(result.skip).to.equal(1); }); From 06685440c4e1a06b2def8ae3e744bd8bfd6158c1 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 19 Jun 2024 20:48:50 +0200 Subject: [PATCH 0227/1097] Adagio Rtd Provider: add missing signal at ortb2.site.ext.data.adg_rtd level (#11772) * AdagioRtdProvider: add missing signal at ortb2 level * AdagioRtdProvider: move safeFrame related code inside Core utils.js --- modules/adagioRtdProvider.js | 22 ++++++++++------------ src/utils.js | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index 7c5ff1352df..d76a27b17d7 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -16,6 +16,7 @@ import { deepSetValue, generateUUID, getDomLoadingDuration, + getSafeframeGeometry, getUniqueIdentifierStr, getWindowSelf, getWindowTop, @@ -278,6 +279,7 @@ function onGetBidRequestData(bidReqConfig, callback, config) { const features = _internal.getFeatures().get(); const ext = { uid: generateUUID(), + pageviewId: _ADAGIO.pageviewId, features: { ...features }, session: { ..._SESSION.get() } }; @@ -431,16 +433,14 @@ function getSlotPosition(adUnit) { const position = { x: 0, y: 0 }; if (isSafeFrameWindow()) { - const ws = getWindowSelf(); + const { self } = getSafeframeGeometry() || {}; - const sfGeom = (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : null; - - if (!sfGeom || !sfGeom.self) { + if (!self) { return ''; } - position.x = Math.round(sfGeom.self.t); - position.y = Math.round(sfGeom.self.l); + position.x = Math.round(self.t); + position.y = Math.round(self.l); } else { try { // window.top based computing @@ -516,16 +516,14 @@ function getViewPortDimensions() { const viewportDims = { w: 0, h: 0 }; if (isSafeFrameWindow()) { - const ws = getWindowSelf(); - - const sfGeom = (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : null; + const { win } = getSafeframeGeometry() || {}; - if (!sfGeom || !sfGeom.win) { + if (!win) { return ''; } - viewportDims.w = Math.round(sfGeom.win.w); - viewportDims.h = Math.round(sfGeom.win.h); + viewportDims.w = Math.round(win.w); + viewportDims.h = Math.round(win.h); } else { // window.top based computing const wt = getWindowTop(); diff --git a/src/utils.js b/src/utils.js index 13613dc7a7f..35596cb6442 100644 --- a/src/utils.js +++ b/src/utils.js @@ -661,6 +661,21 @@ export function isSafeFrameWindow() { return !!(ws.$sf && ws.$sf.ext); } +/** + * Returns the result of calling the function $sf.ext.geom() if it exists + * @see https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf — 5.4 Function $sf.ext.geom + * @returns {Object | undefined} geometric information about the container + */ +export function getSafeframeGeometry() { + try { + const ws = getWindowSelf(); + return (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : undefined; + } catch (e) { + logError('Error getting SafeFrame geometry', e); + return undefined; + } +} + export function isSafariBrowser() { return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); } From cdda91bec532c3eb89e3fea118b26ba0f9c5efb7 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 Jun 2024 05:06:32 -0400 Subject: [PATCH 0228/1097] Update pubxaiAnalyticsAdapter.js (#11830) --- modules/pubxaiAnalyticsAdapter.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index d4a7ec70a70..3b9aee3ecb7 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -24,11 +24,7 @@ let events = { }; function getStorage() { - try { - return window.top['sessionStorage']; - } catch (e) { - return null; - } + return null; } var pubxaiAnalyticsAdapter = Object.assign(adapter( From e13245039a5b4c877d68b3b1c738e33664dc8fed Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 Jun 2024 06:34:45 -0400 Subject: [PATCH 0229/1097] nobid Analytics Adapter : import logMessage (#11833) * Update nobidAnalyticsAdapter.js Fixes #11668 * Update uid2IdSystem_shared.js * Update uid2IdSystem.js * Update nobidAnalyticsAdapter.js --- modules/nobidAnalyticsAdapter.js | 5 ++--- modules/uid2IdSystem.js | 1 - modules/uid2IdSystem_shared.js | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index b5de3df4626..afa980b05c9 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {deepClone, logError, getParameterByName} from '../src/utils.js'; +import {deepClone, logError, getParameterByName, logMessage} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -26,8 +26,7 @@ const { AD_RENDER_SUCCEEDED } = EVENTS; function log (msg) { - // eslint-disable-next-line no-console - console.log(`%cNoBid Analytics ${VERSION}`, 'padding: 2px 8px 2px 8px; background-color:#f50057; color: white', msg); + logMessage(`%cNoBid Analytics ${VERSION}: ${msg}`); } function isJson (str) { return str && str.startsWith('{') && str.endsWith('}'); diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 1ce9b0f5a09..afdde5f0a7f 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ /** * This module adds uid2 ID support to the User ID module * The {@link module:modules/userId} module is required. diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 917e305f3fb..36e5d414941 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { ajax } from '../src/ajax.js'; import { cyrb53Hash } from '../src/utils.js'; From 1a8b99330e53b4ae50f367d6a764a2f7dddb4f68 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 20 Jun 2024 07:10:17 -0400 Subject: [PATCH 0230/1097] Loyal Bid Adapter : no error without floors module (#11834) * LoyalBidAdapter.js: no error without floors module * Update loyalBidAdapter.js --- modules/loyalBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js index ffa88529b2f..80524f1e8ed 100644 --- a/modules/loyalBidAdapter.js +++ b/modules/loyalBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, logError } from '../src/utils.js'; +import { logMessage } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -84,7 +84,6 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (err) { - logError(err); return 0; } } From e647c3905cc63af88b3ef3a8fac7f691d970443e Mon Sep 17 00:00:00 2001 From: aleksandar-rayn <155530509+aleksandar-rayn@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:27:20 +0200 Subject: [PATCH 0231/1097] update rayn rtd provider module with rayn persona taxonomy (#11841) --- .../gpt/raynRtdProvider_example.html | 1 + modules/raynRtdProvider.js | 39 +++++++++++++++++-- test/spec/modules/raynRtdProvider_spec.js | 25 ++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/integrationExamples/gpt/raynRtdProvider_example.html b/integrationExamples/gpt/raynRtdProvider_example.html index 2d43c37513a..7965daa6e85 100644 --- a/integrationExamples/gpt/raynRtdProvider_example.html +++ b/integrationExamples/gpt/raynRtdProvider_example.html @@ -6,6 +6,7 @@ "3": ["264", "267", "261"], "4": ["438"] }, + "103015": ['agdv23', 'avscg3'], "903555595": { "7": { "2": ["51", "246"] diff --git a/modules/raynRtdProvider.js b/modules/raynRtdProvider.js index d558c360c4a..ee3d18be381 100644 --- a/modules/raynRtdProvider.js +++ b/modules/raynRtdProvider.js @@ -14,6 +14,7 @@ import { deepAccess, deepSetValue, logError, logMessage, mergeDeep } from '../sr const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'rayn'; const RAYN_TCF_ID = 1220; +const RAYN_PERSONA_TAXONOMY_ID = 103015; const LOG_PREFIX = 'RaynJS: '; export const SEGMENTS_RESOLVER = 'rayn.io'; export const RAYN_LOCAL_STORAGE_KEY = 'rayn-segtax'; @@ -77,6 +78,32 @@ export function generateOrtbDataObject(segtax, segment, maxTier) { }; } +/** + * Create and return ORTB2 object with segtax and personaIds + * @param {number} segtax + * @param {Array} personaIds + * @return {Array} + */ +export function generatePersonaOrtbDataObject(segtax, personaIds) { + const segmentIds = []; + + try { + segmentIds.push(...personaIds.map((id) => { + return { id }; + })) + } catch (error) { + logError(LOG_PREFIX, error); + } + + return { + name: SEGMENTS_RESOLVER, + ext: { + segtax, + }, + segment: segmentIds, + }; +} + /** * Generates checksum * @param {string} url @@ -127,8 +154,14 @@ export function setSegmentsAsBidderOrtb2(bidConfig, bidders, integrationConfig, deepSetValue(raynOrtb2, 'site.content.data', raynContentData); } + const raynUserData = []; if (integrationConfig.iabAudienceCategories.v1_1.enabled && segments[4]) { - const raynUserData = [generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)]; + raynUserData.push(generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)); + } + if (segments[RAYN_PERSONA_TAXONOMY_ID]) { + raynUserData.push(generatePersonaOrtbDataObject(RAYN_PERSONA_TAXONOMY_ID, segments[RAYN_PERSONA_TAXONOMY_ID])); + } + if (raynUserData.length > 0) { deepSetValue(raynOrtb2, 'user.data', raynUserData); } @@ -163,8 +196,8 @@ function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { segments[checksum] || (segments[4] && integrationConfig.iabAudienceCategories.v1_1.enabled && !integrationConfig.iabContentCategories.v2_2.enabled && - !integrationConfig.iabContentCategories.v3_0.enabled - ) + !integrationConfig.iabContentCategories.v3_0.enabled) || + segments[RAYN_PERSONA_TAXONOMY_ID] )) { logMessage(LOG_PREFIX, `Segtax data from localStorage: ${JSON.stringify(segments)}`); setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segments, checksum); diff --git a/test/spec/modules/raynRtdProvider_spec.js b/test/spec/modules/raynRtdProvider_spec.js index 69ea316e8b5..3920d090550 100644 --- a/test/spec/modules/raynRtdProvider_spec.js +++ b/test/spec/modules/raynRtdProvider_spec.js @@ -152,6 +152,7 @@ describe('rayn RTD Submodule', function () { 2: ['71', '313'], 4: ['33', '145', '712'] }; + TEST_SEGMENTS['103015'] = ['agdv23', 'avscg3']; const bidderOrtb2 = {}; const bidders = RTD_CONFIG.dataProviders[0].params.bidders; @@ -174,6 +175,9 @@ describe('rayn RTD Submodule', function () { TEST_SEGMENTS['4']['3'].forEach((id) => { expect(ortb2.user.data[0].segment.find(segment => segment.id === id)).to.exist; }); + TEST_SEGMENTS['103015'].forEach((id) => { + expect(ortb2.user.data[1].segment.find(segment => segment.id === id)).to.exist; + }); }); }); }); @@ -229,6 +233,27 @@ describe('rayn RTD Submodule', function () { logMessageSpy.restore(); }); + it('should update reqBidsConfigObj and execute callback using persona segment from localStorage', function () { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const testSegments = { + 103015: ['agdv23', 'avscg3'] + }; + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(JSON.stringify(testSegments)); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`Segtax data from localStorage: ${JSON.stringify(testSegments)}`); + + logMessageSpy.restore(); + }); + it('should update reqBidsConfigObj and execute callback using segments from raynJS', function () { const callbackSpy = sinon.spy(); const logMessageSpy = sinon.spy(utils, 'logMessage'); From 2efc834cc291b3c2ff9abc70e95d5b3d3eb01b63 Mon Sep 17 00:00:00 2001 From: ElgarsG <65354776+eldzis@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:29:51 +0300 Subject: [PATCH 0232/1097] Setupad adapter: Use ortbConverter (#11842) * Create a single request for all placements * Switch from manual ORTB conversion to ortbConverter library * Turn off test request * Add custom setupad param to ortb request and write test --- modules/setupadBidAdapter.js | 198 +++++------------- test/spec/modules/setupadBidAdapter_spec.js | 221 +++++++++++--------- 2 files changed, 182 insertions(+), 237 deletions(-) diff --git a/modules/setupadBidAdapter.js b/modules/setupadBidAdapter.js index c6fb9097122..4ee6dd7c085 100644 --- a/modules/setupadBidAdapter.js +++ b/modules/setupadBidAdapter.js @@ -1,14 +1,14 @@ import { _each, - createTrackPixelHtml, - deepAccess, isStr, getBidIdParameter, triggerPixel, logWarn, + deepSetValue, } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'setupad'; const ENDPOINT = 'https://prebid.setupad.io/openrtb2/auction'; @@ -16,11 +16,39 @@ const SYNC_ENDPOINT = 'https://cookie.stpd.cloud/sync?'; const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics?'; const GVLID = 1241; const TIME_TO_LIVE = 360; -const biddersCreativeIds = {}; - -function getEids(bidRequest) { - if (deepAccess(bidRequest, 'userIdAsEids')) return bidRequest.userIdAsEids; -} +export const biddersCreativeIds = {}; // export only for tests +const NET_REVENUE = true; +const TEST_REQUEST = 0; // used only for testing + +const converter = ortbConverter({ + context: { + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue( + imp, + 'ext.prebid.storedrequest.id', + getBidIdParameter('placement_id', bidRequest.params) + ); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'test', TEST_REQUEST); + deepSetValue( + request, + 'ext.prebid.storedrequest.id', + getBidIdParameter( + 'account_id', + bidderRequest.bids.find((bid) => bid.hasOwnProperty('params')).params + ) + ); + deepSetValue(request, 'setupad', 'adapter'); + return request; + }, +}); export const spec = { code: BIDDER_CODE, @@ -37,97 +65,17 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - const requests = []; - - _each(validBidRequests, function (bid) { - const id = getBidIdParameter('placement_id', bid.params); - const accountId = getBidIdParameter('account_id', bid.params); - const auctionId = bid.auctionId; - const bidId = bid.bidId; - const eids = getEids(bid) || undefined; - let sizes = bid.sizes; - if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; - - const site = { - page: bidderRequest?.refererInfo?.page, - ref: bidderRequest?.refererInfo?.ref, - domain: bidderRequest?.refererInfo?.domain, - }; - const device = { - w: bidderRequest?.ortb2?.device?.w, - h: bidderRequest?.ortb2?.device?.h, - }; - - const payload = { - id: bid?.bidderRequestId, - ext: { - prebid: { - storedrequest: { - id: accountId || 'default', - }, - }, - }, - user: { ext: { eids } }, - device, - site, - imp: [], - }; - - const imp = { - id: bid.adUnitCode, - ext: { - prebid: { - storedrequest: { id }, - }, - }, - }; - - if (deepAccess(bid, 'mediaTypes.banner')) { - imp.banner = { - format: (sizes || []).map((s) => { - return { w: s[0], h: s[1] }; - }), - }; - } - - payload.imp.push(imp); - - const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const uspConsent = bidderRequest && bidderRequest.uspConsent; - - if (gdprConsent || uspConsent) { - payload.regs = { ext: {} }; - - if (uspConsent) payload.regs.ext.us_privacy = uspConsent; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies !== 'undefined') { - payload.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; - } - - if (typeof gdprConsent.consentString !== 'undefined') { - payload.user.ext.consent = gdprConsent.consentString; - } - } - } - const params = bid.params; - - requests.push({ - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(payload), - options: { - contentType: 'text/plain', - withCredentials: true, - }, - - bidId, - params, - auctionId, - }); - }); - - return requests; + const data = converter.toORTB({ validBidRequests, bidderRequest }); + + return { + method: 'POST', + url: ENDPOINT, + data, + options: { + contentType: 'text/plain', + withCredentials: true, + }, + }; }, interpretResponse: function (serverResponse, bidRequest) { @@ -141,40 +89,22 @@ export const spec = { return []; } - const serverBody = serverResponse.body; - const bidResponses = []; - - _each(serverBody.seatbid, (res) => { + // set a seat for creativeId for triggerPixel url + _each(serverResponse.body.seatbid, (res) => { _each(res.bid, (bid) => { - const requestId = bidRequest.bidId; - const params = bidRequest.params; - const { ad, adUrl } = getAd(bid); - - const bidResponse = { - requestId, - params, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.id, - currency: serverBody.cur, - netRevenue: true, - ttl: TIME_TO_LIVE, - meta: { - advertiserDomains: bid.adomain || [], - }, - }; - - // set a seat for creativeId for triggerPixel url - biddersCreativeIds[bidResponse.creativeId] = res.seat; - - bidResponse.ad = ad; - bidResponse.adUrl = adUrl; - bidResponses.push(bidResponse); + biddersCreativeIds[bid.crid] = res.seat; }); }); - return bidResponses; + // used for a test case "should update biddersCreativeIds correctly" to return early and not throw ORTB error + if (serverResponse.testCase === 1) return; + + const bids = converter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + return bids; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { @@ -261,16 +191,4 @@ function getBidders(serverResponse) { } } -function getAd(bid) { - const { adm, nurl } = bid; - let ad = adm; - - if (nurl) { - const trackingPixel = createTrackPixelHtml(decodeURIComponent(nurl)); - ad += trackingPixel; - } - - return { ad }; -} - registerBidder(spec); diff --git a/test/spec/modules/setupadBidAdapter_spec.js b/test/spec/modules/setupadBidAdapter_spec.js index bc1527df603..3a184c50922 100644 --- a/test/spec/modules/setupadBidAdapter_spec.js +++ b/test/spec/modules/setupadBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { spec } from 'modules/setupadBidAdapter.js'; +import { spec, biddersCreativeIds } from 'modules/setupadBidAdapter.js'; describe('SetupadAdapter', function () { const userIdAsEids = [ @@ -42,9 +42,104 @@ describe('SetupadAdapter', function () { }, userIdAsEids, }, + { + adUnitCode: 'test-div-2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, ]; const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: 1579746300522, + bidderCode: 'setupad', + bidderRequestId: '15246a574e859f', + bids: [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + { + adUnitCode: 'test-div-2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + ], + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + vendorData: {}, + gdprApplies: true, + }, ortb2: { device: { w: 1500, @@ -52,39 +147,27 @@ describe('SetupadAdapter', function () { }, }, refererInfo: { + canonicalUrl: null, domain: 'test.com', page: 'http://test.com', - ref: '', + referer: null, }, }; const serverResponse = { body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', seatbid: [ { - bid: [ - { - id: 'test-bid-id', - price: 0.8, - adm: 'this is an ad', - adid: 'test-ad-id', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - }, - ], - seat: 'testBidder', + bid: [{ crid: 123 }, { crid: 1234 }], + seat: 'pubmatic', }, - ], - cur: 'USD', - ext: { - sync: { - image: ['urlA?gdpr={{.GDPR}}'], - iframe: ['urlB'], + { + bid: [{ crid: 12345 }], + seat: 'setupad', }, - }, + ], }, + testCase: 1, }; describe('isBidRequestValid', function () { @@ -119,77 +202,25 @@ describe('SetupadAdapter', function () { }); describe('buildRequests', function () { - it('check request params with GDPR and USP', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(JSON.parse(request[0].data).user.ext.consent).to.equal( - 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA' - ); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); - }); - - it('check request params without GDPR', function () { - let bidRequestsWithoutGDPR = Object.assign({}, bidRequests[0]); - delete bidRequestsWithoutGDPR.gdprConsent; - const request = spec.buildRequests([bidRequestsWithoutGDPR], bidRequestsWithoutGDPR); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); + it('should return correct storedrequest id for bids if placement_id is provided', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.prebid.storedrequest.id).to.equal('123'); }); it('should return correct storedrequest id if account_id is provided', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('test-account-id'); - }); - - it('should return correct storedrequest id if account_id is not provided', function () { - let bidRequestsWithoutAccountId = Object.assign({}, bidRequests[0]); - delete bidRequestsWithoutAccountId.params.account_id; - const request = spec.buildRequests( - [bidRequestsWithoutAccountId], - bidRequestsWithoutAccountId - ); - expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('default'); - }); - - it('validate generated params', function () { - const request = spec.buildRequests(bidRequests); - expect(request[0].bidId).to.equal('22c4871113f461'); - expect(JSON.parse(request[0].data).id).to.equal('15246a574e859f'); - }); - - it('check if correct site object was added', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - const siteObj = JSON.parse(request[0].data).site; - - expect(siteObj.domain).to.equal('test.com'); - expect(siteObj.page).to.equal('http://test.com'); - expect(siteObj.ref).to.equal(''); + expect(request.data.ext.prebid.storedrequest.id).to.equal('test-account-id'); }); - it('check if correct device object was added', function () { + it('should return setupad custom adapter param', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - const deviceObj = JSON.parse(request[0].data).device; - - expect(deviceObj.w).to.equal(1500); - expect(deviceObj.h).to.equal(1000); - }); - - it('check if imp object was added', function () { - const request = spec.buildRequests(bidRequests); - expect(JSON.parse(request[0].data).imp).to.be.an('array'); - }); - - it('should send "user.ext.eids" in the request for Prebid.js supported modules only', function () { - const request = spec.buildRequests(bidRequests); - expect(JSON.parse(request[0].data).user.ext.eids).to.deep.equal(userIdAsEids); + expect(request.data.setupad).to.equal('adapter'); }); - it('should send an undefined "user.ext.eids" in the request if userId module is unsupported', function () { - let bidRequestsUnsupportedUserIdModule = Object.assign({}, bidRequests[0]); - delete bidRequestsUnsupportedUserIdModule.userIdAsEids; - const request = spec.buildRequests(bidRequestsUnsupportedUserIdModule); - - expect(JSON.parse(request[0].data).user.ext.eids).to.be.undefined; + // Change this to 1 whenever TEST_REQUEST = 1. This is allowed only for testing requests locally + it('should return correct test attribute value from global value', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.test).to.equal(0); }); }); @@ -256,25 +287,21 @@ describe('SetupadAdapter', function () { describe('interpretResponse', function () { it('should return empty array if error during parsing', () => { const wrongServerResponse = 'wrong data'; - let request = spec.buildRequests(bidRequests, bidRequests[0]); + let request = spec.buildRequests(bidRequests, bidderRequest); let result = spec.interpretResponse(wrongServerResponse, request); expect(result).to.be.instanceof(Array); expect(result.length).to.equal(0); }); - it('should get correct bid response', function () { - const result = spec.interpretResponse(serverResponse, bidRequests[0]); - expect(result).to.be.an('array').with.lengthOf(1); - expect(result[0].requestId).to.equal('22c4871113f461'); - expect(result[0].cpm).to.equal(0.8); - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal('test-bid-id'); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(360); - expect(result[0].ad).to.equal('this is an ad'); + it('should update biddersCreativeIds correctly', function () { + spec.interpretResponse(serverResponse, bidderRequest); + + expect(biddersCreativeIds).to.deep.equal({ + 123: 'pubmatic', + 1234: 'pubmatic', + 12345: 'setupad', + }); }); }); From 77303f41b87a992d840bd98fb363eebdfdf68676 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 20 Jun 2024 17:08:22 +0200 Subject: [PATCH 0233/1097] AdagioRtdProvider: add to .submodules.json (#11846) --- modules/.submodules.json | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/.submodules.json b/modules/.submodules.json index 39f3969c4fd..38cf5f71cc6 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -60,6 +60,7 @@ "1plusXRtdProvider", "a1MediaRtdProvider", "aaxBlockmeterRtdProvider", + "adagioRtdProvider", "adlooxRtdProvider", "adnuntiusRtdProvider", "airgridRtdProvider", From 27a2d3b19d0fbc01b37a784bcf316831e97e1f12 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Jun 2024 08:09:11 -0700 Subject: [PATCH 0234/1097] PAAPI: fix perBuyerSignals + topicsFpdModule: fix exception (#11845) * fix PAAPI perBuyerSignals * fix topics exception * improvement --- modules/paapi.js | 3 +- modules/topicsFpdModule.js | 2 +- test/spec/modules/paapi_spec.js | 10 +-- test/spec/modules/topicsFpdModule_spec.js | 102 +++++++++------------- 4 files changed, 51 insertions(+), 66 deletions(-) diff --git a/modules/paapi.js b/modules/paapi.js index 310974b31fe..8ddd1912c29 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -158,8 +158,9 @@ export function addPaapiConfigHook(next, request, paapiConfig) { const {config, igb} = paapiConfig; if (config) { config.auctionSignals = setFPD(config.auctionSignals || {}, request); + const pbs = config.perBuyerSignals = config.perBuyerSignals ?? {}; (config.interestGroupBuyers || []).forEach(buyer => { - deepSetValue(config, `perBuyerSignals.${buyer}`, setFPD(config.perBuyerSignals?.[buyer] || {}, request)); + pbs[buyer] = setFPD(pbs[buyer] ?? {}, request); }) storePendingData(pendingConfigsForAuction, config); } diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index d99696152ba..8df01fcd599 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -130,7 +130,7 @@ export function processFpd(config, {global}, {data = topicsData} = {}) { export function getCachedTopics() { let cachedTopicData = []; const topics = config.getConfig('userSync.topics'); - const bidderList = topics.bidders || []; + const bidderList = topics?.bidders || []; let storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); storedSegments && storedSegments.forEach((value, cachedBidder) => { // Check bidder exist in config for cached bidder data and then only retrieve the cached data diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 7814c09ea61..77c8a6d7dda 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -91,10 +91,10 @@ describe('paapi module', () => { let igb1, igb2, buyerAuctionConfig; beforeEach(() => { igb1 = { - origin: 'buyer1' + origin: 'buyer.1' }; igb2 = { - origin: 'buyer2' + origin: 'buyer.2' }; buyerAuctionConfig = { seller: 'seller', @@ -263,11 +263,11 @@ describe('paapi module', () => { }); it('should be added to perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer1', 'buyer2']; + auctionConfig.interestGroupBuyers = ['buyer.1', 'buyer.2']; const pbs = getComponentAuctionConfig().perBuyerSignals; sinon.assert.match(pbs, { - buyer1: {prebid: {ortb2, ortb2Imp}}, - buyer2: {prebid: {ortb2, ortb2Imp}} + 'buyer.1': {prebid: {ortb2, ortb2Imp}}, + 'buyer.2': {prebid: {ortb2, ortb2Imp}} }); }); diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index 6bc8eb1e7c3..0f8e379e95f 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -308,33 +308,6 @@ describe('topics', () => { }], name: 'ads.pubmatic.com' }]; - const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; - const consentConfig = { - consentString: consentString, - gdprApplies: true, - vendorData: { - metadata: consentString, - gdprApplies: true, - purpose: { - consents: { - 1: true, - 2: true, - 3: true, - 4: true - } - } - } - }; - const mockData = [ - { - name: 'domain', - segment: [{id: 123}] - }, - { - name: 'domain', - segment: [{id: 321}], - } - ]; const evt = { data: '{"segment":{"domain":"ads.pubmatic.com","topics":[{"configVersion":"chrome.1","modelVersion":"2206021246","taxonomyVersion":"1","topic":165,"version":"chrome.1:1:2206021246"}],"bidder":"pubmatic"},"date":1669743901858}', @@ -345,49 +318,60 @@ describe('topics', () => { storage.removeDataFromLocalStorage(topicStorageName); }); - describe('when cached data is available and not expired', () => { + describe('caching', () => { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - const storedSegments = JSON.stringify( - [['pubmatic', { - '2206021246': { - 'ext': {'segtax': 600, 'segclass': '2206021246'}, - 'segment': [{'id': '243'}, {'id': '265'}], - 'name': 'ads.pubmatic.com' - }, - 'lastUpdated': new Date().getTime() - }]] - ); - storage.setDataInLocalStorage(topicStorageName, storedSegments); - config.setConfig({ - userSync: { - topics: { - maxTopicCaller: 4, - bidders: [{ - bidder: 'pubmatic', - iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' - }] - } - } - }) - }); + }) + afterEach(() => { sandbox.restore(); config.resetConfig(); }); - it('should return segments for bidder if transmitUfpd is allowed', () => { - assert.deepEqual(getCachedTopics(), expected); - }); + it('should return no segments when not configured', () => { + config.setConfig({userSync: {}}); + expect(getCachedTopics()).to.eql([]); + }) + + describe('when cached data is available and not expired', () => { + beforeEach(() => { + const storedSegments = JSON.stringify( + [['pubmatic', { + '2206021246': { + 'ext': {'segtax': 600, 'segclass': '2206021246'}, + 'segment': [{'id': '243'}, {'id': '265'}], + 'name': 'ads.pubmatic.com' + }, + 'lastUpdated': new Date().getTime() + }]] + ); + storage.setDataInLocalStorage(topicStorageName, storedSegments); + config.setConfig({ + userSync: { + topics: { + maxTopicCaller: 4, + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' + }] + } + } + }) + }); - it('should NOT return segments for bidder if enrichUfpd is NOT allowed', () => { - sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { - return !(activity === ACTIVITY_ENRICH_UFPD && params.component === 'bidder.pubmatic'); + it('should return segments for bidder if transmitUfpd is allowed', () => { + assert.deepEqual(getCachedTopics(), expected); + }); + + it('should NOT return segments for bidder if enrichUfpd is NOT allowed', () => { + sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { + return !(activity === ACTIVITY_ENRICH_UFPD && params.component === 'bidder.pubmatic'); + }); + expect(getCachedTopics()).to.eql([]); }); - expect(getCachedTopics()).to.eql([]); }); - }) + }); it('should return empty segments for bidder if there is cached segments stored which is expired', () => { let storedSegments = '[["pubmatic",{"2206021246":{"ext":{"segtax":600,"segclass":"2206021246"},"segment":[{"id":"243"},{"id":"265"}],"name":"ads.pubmatic.com"},"lastUpdated":10}]]'; From 0ee1ba7ea83e82e5ab76484da47c7b0186d4d2a9 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 20 Jun 2024 21:03:27 +0200 Subject: [PATCH 0235/1097] Adagio*: add adagioUtils.js, remove duplicated code (#11849) --- libraries/adagioUtils/adagioUtils.js | 35 ++++++++++++++++++++++++++++ modules/adagioBidAdapter.js | 18 +------------- modules/adagioRtdProvider.js | 20 ++-------------- 3 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 libraries/adagioUtils/adagioUtils.js diff --git a/libraries/adagioUtils/adagioUtils.js b/libraries/adagioUtils/adagioUtils.js new file mode 100644 index 00000000000..c2614c45d0c --- /dev/null +++ b/libraries/adagioUtils/adagioUtils.js @@ -0,0 +1,35 @@ +import { + canAccessWindowTop, + generateUUID, + getWindowSelf, + getWindowTop, + isSafeFrameWindow +} from '../../src/utils.js'; + +/** + * Returns the best Window object to use with ADAGIO. + * @returns {Window} window.top or window.self object + */ +export function getBestWindowForAdagio() { + return (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); +} + +/** + * Returns the window.ADAGIO global object used to store Adagio data. + * This object is created in window.top if possible, otherwise in window.self. + */ +export const _ADAGIO = (function() { + const w = getBestWindowForAdagio(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.versions = w.ADAGIO.versions || {}; + w.ADAGIO.versions.pbjs = '$prebid.version$'; + w.ADAGIO.windows = w.ADAGIO.windows || []; + w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); + + return w.ADAGIO; +})(); diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3cc31336827..8d945beafa1 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -7,13 +7,11 @@ import { generateUUID, getDNT, getWindowSelf, - getWindowTop, isArray, isArrayOfNums, isFn, isInteger, isNumber, - isSafeFrameWindow, isStr, logError, logInfo, @@ -26,6 +24,7 @@ import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { find } from '../src/polyfill.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { _ADAGIO } from '../libraries/adagioUtils/adagioUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { userSync } from '../src/userSync.js'; @@ -78,21 +77,6 @@ export const ORTB_VIDEO_PARAMS = { * Returns the window.ADAGIO global object used to store Adagio data. * This object is created in window.top if possible, otherwise in window.self. */ -const _ADAGIO = (function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); - w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.versions = w.ADAGIO.versions || {}; - w.ADAGIO.versions.pbjs = '$prebid.version$'; - w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); - - return w.ADAGIO; -})(); - function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index d76a27b17d7..75562580cd6 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -26,6 +26,7 @@ import { isStr, prefixLog } from '../src/utils.js'; +import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; /** @@ -44,23 +45,6 @@ const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); // Guard to avoid storing the same bid data several times. const guard = new Set(); -/** - * Returns the window.ADAGIO global object used to store Adagio data. - * This object is created in window.top if possible, otherwise in window.self. - */ -const _ADAGIO = (function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); - w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.windows = w.ADAGIO.windows || []; - - return w.ADAGIO; -})(); - /** * Store the sampling data. * This data is used to determine if beacons should be sent to adagio. @@ -133,7 +117,7 @@ const _FEATURES = (function() { features.data = {}; }, get: function() { - const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + const w = getBestWindowForAdagio(); if (!features.initialized) { features.data = { From 9dc08c57b2d945501b41eb21fbbf5fd398acbe6d Mon Sep 17 00:00:00 2001 From: Sanved Tapkeer Date: Thu, 20 Jun 2024 17:37:36 -0400 Subject: [PATCH 0236/1097] Prebid Server Bid Adapter : Support for custom headers for XHR call (#11780) * merge fix * Adding support for custom headers for prebid server --- modules/prebidServerBidAdapter/index.js | 4 +++- test/spec/modules/prebidServerBidAdapter_spec.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 168758763e8..0ecb6365430 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -550,6 +550,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); + const customHeaders = deepAccess(s2sBidRequest, 's2sConfig.customHeaders', {}); if (request && requestJson && endpointUrl) { const networkDone = s2sBidRequest.metrics.startTiming('net'); ajax( @@ -584,7 +585,8 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques { contentType: 'text/plain', withCredentials: true, - browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)) + browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)), + customHeaders } ); } else { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 2b4cd025515..d44a67d2acc 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -825,6 +825,20 @@ describe('S2S Adapter', function () { }) }) }) + + it('should set customHeaders correctly when publisher has provided it', () => { + let configWithCustomHeaders = utils.deepClone(CONFIG); + configWithCustomHeaders.customHeaders = { customHeader1: 'customHeader1Value' }; + config.setConfig({ s2sConfig: configWithCustomHeaders }); + + let reqWithNewConfig = utils.deepClone(REQUEST); + reqWithNewConfig.s2sConfig = configWithCustomHeaders; + + adapter.callBids(reqWithNewConfig, BID_REQUESTS, addBidResponse, done, ajax); + const reqHeaders = server.requests[0].requestHeaders + expect(reqHeaders.customHeader1).to.exist; + expect(reqHeaders.customHeader1).to.equal('customHeader1Value'); + }); it('should block request if config did not define p1Consent URL in endpoint object config', function () { let badConfig = utils.deepClone(CONFIG); From d4cae0a3c9450a28ac757e84e353a5697111028b Mon Sep 17 00:00:00 2001 From: Matthieu Wipliez <89922776+github-matthieu-wipliez@users.noreply.github.com> Date: Fri, 21 Jun 2024 03:16:00 +0200 Subject: [PATCH 0237/1097] Autoplay detection: fix fullscreen issue on iOS (#11822) * Improve autoplay detection to fix fullscreen issue on iOS * Skip autodetection on WebView on iOS < 17 --- libraries/autoplayDetection/autoplay.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/autoplayDetection/autoplay.js b/libraries/autoplayDetection/autoplay.js index 99922da7b49..3ca4c4a8d11 100644 --- a/libraries/autoplayDetection/autoplay.js +++ b/libraries/autoplayDetection/autoplay.js @@ -22,6 +22,12 @@ const autoplayVideoUrl = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAADxtZGF0AAAAMGWIhAAV//73ye/Apuvb3rW/k89I/Cy3PsIqP39atohOSV14BYa1heKCYgALQC5K4QAAAwZtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAD6AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACMHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAD6AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAoAAAAFoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAA+gAAAAAAAEAAAAAAahtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAEAAAABAAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFTbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABE3N0YmwAAACvc3RzZAAAAAAAAAABAAAAn2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAoABaAEgAAABIAAAAAAAAAAEVTGF2YzYwLjMxLjEwMiBsaWJ4MjY0AAAAAAAAAAAAAAAY//8AAAA1YXZjQwFkAAr/4QAYZ2QACqzZQo35IQAAAwABAAADAAIPEiWWAQAGaOvjyyLA/fj4AAAAABRidHJ0AAAAAAAAAaAAAAGgAAAAGHN0dHMAAAAAAAAAAQAAAAEAAEAAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAADQAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjYwLjE2LjEwMA=='; function startDetection() { + const version = navigator.userAgent.match(/iPhone OS (\d+)_(\d+)/) + if (version !== null && parseInt(version[1]) < 17 && !navigator.userAgent.includes('Safari')) { + // skip autodetection on iOS 16 WebView + return + } + // we create an HTMLVideoElement muted and not displayed in which we try to play a one frame video const videoElement = document.createElement('video'); videoElement.src = autoplayVideoUrl; @@ -32,7 +38,8 @@ function startDetection() { .play() .then(() => { autoplayEnabled = true; - videoElement.pause(); + // if the video is played on a WebView with playsinline = false, this stops the video, to prevent it from being displayed fullscreen + videoElement.src = ''; }) .catch(() => { autoplayEnabled = false; From 447859171315478be82acbea2f0f19072c320a6b Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Fri, 21 Jun 2024 05:46:02 -0400 Subject: [PATCH 0238/1097] fix API endpoint in Mobian RTD module (#11850) --- modules/mobianRtdProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index f43dde20681..09a338c9bb0 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -1,6 +1,6 @@ import { submodule } from '../src/hook.js'; import { ajaxBuilder } from '../src/ajax.js'; -export const MOBIAN_URL = 'http://impact-analytics-staging.themobian.com'; +export const MOBIAN_URL = 'https://impact-api-prod.themobian.com/brand_safety'; export const mobianBrandSafetySubmodule = { name: 'mobianBrandSafety', @@ -15,7 +15,7 @@ function init() { function getBidRequestData(bidReqConfig, callback, config) { const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const pageUrl = encodeURIComponent(getPageUrl()); - const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; + const requestUrl = `${MOBIAN_URL}/by_url?url=${pageUrl}`; const ajax = ajaxBuilder(); From 1f8af9e80b2d9a620c15582daf70d396f22e9180 Mon Sep 17 00:00:00 2001 From: CPG Date: Fri, 21 Jun 2024 12:10:19 +0200 Subject: [PATCH 0239/1097] Add new appnexus alias stailamedia (#11852) --- libraries/appnexusUtils/anUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/appnexusUtils/anUtils.js b/libraries/appnexusUtils/anUtils.js index 7897cfc0e0e..1d04711bd0f 100644 --- a/libraries/appnexusUtils/anUtils.js +++ b/libraries/appnexusUtils/anUtils.js @@ -22,6 +22,7 @@ export const appnexusAliases = [ { code: 'adasta', gvlid: 32 }, { code: 'beintoo', gvlid: 618 }, { code: 'projectagora', gvlid: 1032 }, + { code: 'stailamedia', gvlid: 32 }, { code: 'uol', gvlid: 32 }, { code: 'adzymic', gvlid: 723 }, ]; From a4a6de8309ad25efd5575744c2df6b0a6210f673 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:50:54 +0530 Subject: [PATCH 0240/1097] PubMatic Analytics Adapter : Logging MultiBids from bidders (#11844) * Using orignalRequestId to find the bid in case of MultiBid response * Using orignalRequestId to find the winning bid in case of MultiBid response * Handled bidderCode while logging * replaced Prebid version method by string --------- Co-authored-by: pm-azhar-mulla --- modules/pubmaticAnalyticsAdapter.js | 29 +++++++--- .../modules/pubmaticAnalyticsAdapter_spec.js | 57 +++++++++++++++++-- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 9e1fa49fef2..e54db34d401 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -281,7 +281,7 @@ function isOWPubmaticBid(adapterName) { }) } -function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { +function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { adUnit.bids[bidId].forEach(function(bid) { @@ -290,6 +290,16 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { return; } const pg = window.parseFloat(Number(bid.bidResponse?.adserverTargeting?.hb_pb || bid.bidResponse?.adserverTargeting?.pwtpb).toFixed(BID_PRECISION)); + + const prebidBidsReceived = e?.bidsReceived; + if (isArray(prebidBidsReceived) && prebidBidsReceived.length > 0) { + prebidBidsReceived.forEach(function(iBid) { + if (iBid.adId === bid.adId) { + bid.bidderCode = iBid.bidderCode; + } + }); + } + partnerBids.push({ 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, @@ -391,7 +401,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['pdvid'] = '' + profileVersionId; outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = getTgId(); - outputObj['pbv'] = getGlobal()?.version || '-1'; + outputObj['pbv'] = '$prebid.version$' || '-1'; if (floorData && floorFetchStatus) { outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; @@ -407,7 +417,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'au': origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), 'sz': getSizesForAdUnit(adUnit, adUnitId), - 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), + 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId), e), 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, 'sid': generateUUID() }; @@ -559,7 +569,8 @@ function bidResponseHandler(args) { logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler'); return; } - let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; + let requestId = args.originalRequestId || args.requestId; + let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); return; @@ -567,7 +578,9 @@ function bidResponseHandler(args) { if ((bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) || (bid.bidder === args.bidderCode && bid.status === SUCCESS)) { bid = copyRequiredBidDetails(args); - cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid); + cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId].push(bid); + } else if (args.originalRequestId) { + bid.bidId = args.requestId; } if (args.floorData) { @@ -598,7 +611,7 @@ function bidRejectedHandler(args) { function bidderDoneHandler(args) { cache.auctions[args.auctionId].bidderDonePendingCount--; args.bids.forEach(bid => { - let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.requestId]; + let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.originalRequestId || bid.requestId]; if (typeof bid.serverResponseTimeMs !== 'undefined') { cachedBid.serverLatencyTimeMs = bid.serverResponseTimeMs; } @@ -613,7 +626,7 @@ function bidderDoneHandler(args) { function bidWonHandler(args) { let auctionCache = cache.auctions[args.auctionId]; - auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.requestId; + auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.originalRequestId || args.requestId; auctionCache.adUnitCodes[args.adUnitCode].bidWonAdId = args.adId; executeBidWonLoggerCall(args.auctionId, args.adUnitCode); } @@ -631,7 +644,7 @@ function bidTimeoutHandler(args) { // db = 0 and t = 1 means bidder did respond with a bid but post timeout args.forEach(badBid => { let auctionCache = cache.auctions[badBid.auctionId]; - let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.requestId ][0]; + let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.originalRequestId || badBid.requestId ][0]; if (bid) { bid.status = ERROR; bid.error = { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 002b7fb3063..f77b167a3e9 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -568,7 +568,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -786,7 +786,7 @@ describe('pubmatic analytics adapter', function () { expect(data.pid).to.equal('1111'); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.tgid).to.equal(0); @@ -866,7 +866,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(0);// test group id should be between 0-15 else set to 0 expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -1434,7 +1434,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1566,7 +1566,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1657,8 +1657,55 @@ describe('pubmatic analytics adapter', function () { expect(data.origbidid).to.equal('partnerImpressionID-1'); }); + it('Logger: should use originalRequestId to find the bid', function() { + MOCK.BID_RESPONSE[1]['originalRequestId'] = '3bd4ebb1c900e2'; + MOCK.BID_RESPONSE[1]['requestId'] = '54d4ebb1c9003e'; + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + + // slot 1 + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); + + // slot 2 + expect(data.s[1].ps[0].bidid).to.equal('54d4ebb1c9003e'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); + + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.bidid).to.equal('2ecff0db240757'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); + }); + it('Logger: best case + win tracker. Log bidId when partnerimpressionid is missing', function() { delete MOCK.BID_RESPONSE[1]['partnerImpId']; + MOCK.BID_RESPONSE[1]['requestId'] = '3bd4ebb1c900e2'; MOCK.BID_RESPONSE[1]['prebidBidId'] = 'Prebid-bid-id-1'; sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] From b92bff33799b8edf5b95388650c2448c1ba9a3cf Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 21 Jun 2024 14:11:52 +0200 Subject: [PATCH 0241/1097] AdagioRtdProvider: add placementSource param (#11779) --- modules/adagioRtdProvider.js | 31 ++++++++-- modules/adagioRtdProvider.md | 1 + test/spec/modules/adagioRtdProvider_spec.js | 63 ++++++++++++++++++++- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index 75562580cd6..16bd8fc2ae2 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -38,6 +38,11 @@ const ADAGIO_BIDDER_CODE = 'adagio'; const GVLID = 617; const SCRIPT_URL = 'https://script.4dex.io/a/latest/adagio.js'; const SESS_DURATION = 30 * 60 * 1000; +export const PLACEMENT_SOURCES = { + ORTB: 'ortb', // implicit default, not used atm. + ADUNITCODE: 'code', + GPID: 'gpid' +}; export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); @@ -259,6 +264,7 @@ function onBidRequest(bidderRequest, config, _userConsent) { * @param {*} config */ function onGetBidRequestData(bidReqConfig, callback, config) { + const configParams = deepAccess(config, 'params', {}); const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const features = _internal.getFeatures().get(); const ext = { @@ -283,8 +289,26 @@ function onGetBidRequestData(bidReqConfig, callback, config) { const slotPosition = getSlotPosition(adUnit); deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition); - // We expect `pagetype` `category` are defined in FPD `ortb2.site.ext.data` object. - // `placement` is expected in FPD `adUnits[].ortb2Imp.ext.data` object. (Please note that this `placement` is not related to the oRTB video property.) + // It is expected that the publisher set a `adUnits[].ortb2Imp.ext.data.placement` value. + // Btw, We allow fallback sources to programmatically set this value. + // The source is defined in the `config.params.placementSource` and the possible values are `code` or `gpid`. + // (Please note that this `placement` is not related to the oRTB video property.) + if (!deepAccess(ortb2Imp, 'ext.data.placement')) { + const { placementSource = '' } = configParams; + + switch (placementSource.toLowerCase()) { + case PLACEMENT_SOURCES.ADUNITCODE: + deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); + break; + case PLACEMENT_SOURCES.GPID: + deepSetValue(ortb2Imp, 'ext.data.placement', deepAccess(ortb2Imp, 'ext.gpid')); + break; + default: + logWarn('`ortb2Imp.ext.data.placement` is missing and `params.definePlacement` is not set in the config.'); + } + } + + // We expect that `pagetype`, `category`, `placement` are defined in FPD `ortb2.site.ext.data` and `adUnits[].ortb2Imp.ext.data` objects. // Btw, we have to ensure compatibility with publishers that use the "legacy" adagio params at the adUnit.params level. const adagioBid = adUnit.bids.find(bid => _internal.isAdagioBidder(bid.bidder)); if (adagioBid) { @@ -305,9 +329,6 @@ function onGetBidRequestData(bidReqConfig, callback, config) { if (adagioBid.params.placement) { deepSetValue(ortb2Imp, 'ext.data.placement', adagioBid.params.placement); mustWarnOrtb2Imp = true; - } else { - // If the placement is not defined, we fallback to the adUnit code. - deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); } } diff --git a/modules/adagioRtdProvider.md b/modules/adagioRtdProvider.md index f05521ec54a..a51137d571f 100644 --- a/modules/adagioRtdProvider.md +++ b/modules/adagioRtdProvider.md @@ -30,6 +30,7 @@ pbjs.setConfig({ params: { organizationId: '1000' // Required. Provided by Adagio site: 'my-site' // Required. Provided by Adagio + placementSource: 'ortb' // Optional. Where to find the "placement" value. Possible values: 'ortb' | 'code' | 'gpid' } }] } diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index 2c1612f2e83..ad469d29b37 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -1,4 +1,9 @@ -import { adagioRtdSubmodule, _internal, storage } from 'modules/adagioRtdProvider.js'; +import { + PLACEMENT_SOURCES, + _internal, + adagioRtdSubmodule, + storage, +} from 'modules/adagioRtdProvider.js'; import * as utils from 'src/utils.js'; import { loadExternalScript } from '../../../src/adloader.js'; import { expect } from 'chai'; @@ -376,6 +381,62 @@ describe('Adagio Rtd Provider', function () { const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; expect(ortb2ImpExt.adunit_position).equal(''); }); + + describe('update the ortb2Imp.ext.data.placement if not present', function() { + const config = { + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 'mysite' + } + }; + + it('update the placement value with the adUnit.code value', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; + + const bidRequest = utils.deepClone(bidReqConfig); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + }); + + it('update the placement value with the gpid value', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.GPID; + + const bidRequest = utils.deepClone(bidReqConfig); + const gpid = '/19968336/header-bid-tag-0' + utils.deepSetValue(bidRequest.adUnits[0], 'ortb2Imp.ext.gpid', gpid) + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(gpid); + }); + + it('update the placement value the legacy adUnit[].bids adagio.params.placement value', function() { + const placement = 'placement-value'; + + const configCopy = utils.deepClone(config); + + const bidRequest = utils.deepClone(bidReqConfig); + bidRequest.adUnits[0].bids[0].params.placement = placement; + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(placement); + }); + + it('it does not populate `ortb2Imp.ext.data.placement` if no fallback', function() { + const configCopy = utils.deepClone(config); + const bidRequest = utils.deepClone(bidReqConfig); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.not.exist; + }); + }); }); describe('submodule `onBidRequestEvent`', function() { From 8000f238ba635dcadb43894187250195e84df993 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 21 Jun 2024 18:23:46 -0700 Subject: [PATCH 0242/1097] PBS adapter: fix bug where incorrect bidderCode is used on certain browsers (#11848) * PBS adapter: fix bug where incorrect bidderCode is used on certain browsers * Do not mention OS version in safari_latest --- browsers.json | 6 +++--- libraries/ortbConverter/converter.js | 2 +- test/spec/ortbConverter/converter_spec.js | 10 +++++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/browsers.json b/browsers.json index 1bb8108d456..0649a13e873 100644 --- a/browsers.json +++ b/browsers.json @@ -31,9 +31,9 @@ "device": null, "os": "Windows" }, - "bs_safari_latest_mac_ventura": { + "bs_safari_latest_mac": { "base": "BrowserStack", - "os_version": "Ventura", + "os_version": "Sonoma", "browser": "safari", "browser_version": "latest", "device": null, @@ -47,5 +47,5 @@ "device": null, "os": "OS X" } - + } diff --git a/libraries/ortbConverter/converter.js b/libraries/ortbConverter/converter.js index c367aec268a..9cef8898c39 100644 --- a/libraries/ortbConverter/converter.js +++ b/libraries/ortbConverter/converter.js @@ -117,7 +117,7 @@ export function ortbConverter({ throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') } function augmentContext(ctx, extraParams = {}) { - return Object.assign(ctx, {ortbRequest: request}, extraParams, ctx); + return Object.assign(ctx, {ortbRequest: request}, extraParams); } const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); const bidResponses = (response.seatbid || []).flatMap(seatbid => diff --git a/test/spec/ortbConverter/converter_spec.js b/test/spec/ortbConverter/converter_spec.js index e00b46e66da..fd2dec6d6bb 100644 --- a/test/spec/ortbConverter/converter_spec.js +++ b/test/spec/ortbConverter/converter_spec.js @@ -80,6 +80,7 @@ describe('pbjs-ortb converter', () => { if (context.reqContext?.ctx) { bidResponse.reqCtx = context.reqContext?.ctx; } + bidResponse.seatbid = context.seatbid; } } }, @@ -116,13 +117,16 @@ describe('pbjs-ortb converter', () => { const response = cvt.fromORTB({request, response: MOCK_ORTB_RESPONSE}); expect(response.bids).to.eql([{ impid: 'imp0', - bidId: 111 + bidId: 111, + seatbid: MOCK_ORTB_RESPONSE.seatbid[0] }, { impid: 'imp1', - bidId: 112 + bidId: 112, + seatbid: MOCK_ORTB_RESPONSE.seatbid[0] }, { impid: 'imp1', - bidId: 112 + bidId: 112, + seatbid: MOCK_ORTB_RESPONSE.seatbid[1] }]); expect(response.marker).to.be.true; }); From 850e0e2af883c66d8b2f802329a497d28a25850c Mon Sep 17 00:00:00 2001 From: MaksymTeqBlaze Date: Sat, 22 Jun 2024 22:56:49 +0300 Subject: [PATCH 0243/1097] Libraries: New utility file to Remove code duplication in several adapters (#11854) * Added library * updated bidderUtils and used it in some adapters * added library functions to several new adapters * added library functions to several new adapters * added library functions to several new adapters --- libraries/teqblazeUtils/bidderUtils.js | 253 +++++++ modules/acuityadsBidAdapter.js | 208 +----- modules/admanBidAdapter.js | 219 +------ modules/adprimeBidAdapter.js | 193 +----- modules/axisBidAdapter.js | 198 +----- modules/beyondmediaBidAdapter.js | 194 +----- modules/boldwinBidAdapter.js | 180 +---- modules/compassBidAdapter.js | 203 +----- modules/contentexchangeBidAdapter.js | 205 +----- modules/e_volutionBidAdapter.js | 190 +----- modules/emtvBidAdapter.js | 203 +----- modules/globalsunBidAdapter.js | 204 +----- modules/iqzoneBidAdapter.js | 229 +------ modules/kiviadsBidAdapter.js | 204 +----- modules/krushmediaBidAdapter.js | 168 +---- modules/loyalBidAdapter.js | 181 +---- modules/lunamediahbBidAdapter.js | 123 +--- modules/mathildeadsBidAdapter.js | 203 +----- modules/mgidXBidAdapter.js | 221 +------ modules/mobfoxpbBidAdapter.js | 139 +--- modules/pgamsspBidAdapter.js | 245 +------ modules/playdigoBidAdapter.js | 190 +----- modules/pubCircleBidAdapter.js | 223 +------ modules/qtBidAdapter.js | 229 +------ modules/visiblemeasuresBidAdapter.js | 204 +----- .../teqblazeUtils/bidderUtils_spec.js | 519 +++++++++++++++ test/spec/modules/acuityadsBidAdapter_spec.js | 122 ++-- test/spec/modules/admanBidAdapter_spec.js | 617 +++++++++++------- test/spec/modules/adprimeBidAdapter_spec.js | 303 ++++++--- test/spec/modules/axisBidAdapter_spec.js | 92 ++- .../modules/beyondmediaBidAdapter_spec.js | 98 ++- test/spec/modules/boldwinBidAdapter_spec.js | 296 ++++++--- test/spec/modules/compassBidAdapter_spec.js | 146 ++++- .../modules/contentexchangeBidAdapter_spec.js | 155 ++++- .../spec/modules/e_volutionBidAdapter_spec.js | 68 +- test/spec/modules/emtvBidAdapter_spec.js | 147 ++++- test/spec/modules/globalsunBidAdapter_spec.js | 146 ++++- test/spec/modules/iqzoneBidAdapter_spec.js | 78 ++- test/spec/modules/kiviadsBidAdapter_spec.js | 151 ++++- .../spec/modules/krushmediaBidAdapter_spec.js | 362 +++++++--- test/spec/modules/loyalBidAdapter_spec.js | 128 +++- .../modules/lunamediahbBidAdapter_spec.js | 308 ++++++--- .../modules/mathildeadsBidAdapter_spec.js | 105 ++- test/spec/modules/mgidXBidAdapter_spec.js | 14 +- test/spec/modules/mobfoxpbBidAdapter_spec.js | 320 ++++++--- test/spec/modules/pgamsspBidAdapter_spec.js | 99 ++- test/spec/modules/playdigoBidAdapter_spec.js | 47 +- test/spec/modules/pubCircleBidAdapter_spec.js | 99 ++- test/spec/modules/qtBidAdapter_spec.js | 18 +- .../modules/visiblemeasuresBidAdapter_spec.js | 146 ++++- 50 files changed, 4132 insertions(+), 5461 deletions(-) create mode 100644 libraries/teqblazeUtils/bidderUtils.js create mode 100644 test/spec/libraries/teqblazeUtils/bidderUtils_spec.js diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js new file mode 100644 index 00000000000..42db3194308 --- /dev/null +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -0,0 +1,253 @@ +import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; +import { deepAccess } from '../../src/utils.js'; +import { config } from '../../src/config.js'; + +const PROTOCOL_PATTERN = /^[a-z0-9.+-]+:/i; + +const isBidResponseValid = (bid) => { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +}; + +const getBidFloor = (bid) => { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + + return bidFloor.floor; + } catch (err) { + return 0; + } +}; + +const createBasePlacement = (bid) => { + const { bidId, mediaTypes, transactionId, userIdAsEids } = bid; + const schain = bid.schain || {}; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; + } + + return placement; +}; + +const defaultPlacementType = (bid, bidderRequest, placement) => { + const { placementId, endpointId } = bid.params; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } +}; + +const checkIfObjectHasKey = (keys, obj, mode = 'some') => { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = obj[key]; + + if (mode === 'some' && val) return true; + if (!val) return false; + } + + return mode === 'every'; +} + +export const isBidRequestValid = (keys = ['placementId', 'endpointId'], mode) => (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && checkIfObjectHasKey(keys, params, mode)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + + return valid; +}; + +/** + * @param {{ adUrl, validBidRequests, bidderRequest, placementProcessingFunction }} config + * @returns {function} + */ +export const buildRequestsBase = (config) => { + const { adUrl, validBidRequests, bidderRequest } = config; + const placementProcessingFunction = config.placementProcessingFunction || buildPlacementProcessingFunction(); + const device = deepAccess(bidderRequest, 'ortb2.device'); + const page = deepAccess(bidderRequest, 'refererInfo.page', ''); + + const proto = PROTOCOL_PATTERN.exec(page); + const protocol = proto?.[0]; + + const placements = []; + const request = { + deviceWidth: device?.w || 0, + deviceHeight: device?.h || 0, + language: device?.language?.split('-')[0] || '', + secure: protocol === 'https:' ? 1 : 0, + host: deepAccess(bidderRequest, 'refererInfo.domain', ''), + page, + placements, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, + tmax: bidderRequest.timeout + }; + + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(placementProcessingFunction(bid, bidderRequest)); + } + + return { + method: 'POST', + url: adUrl, + data: request + }; +}; + +export const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => { + const placementProcessingFunction = buildPlacementProcessingFunction(); + + return buildRequestsBase({ adUrl, validBidRequests, bidderRequest, placementProcessingFunction }); +}; + +export const interpretResponse = (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + + return response; +}; + +export const getUserSyncs = (syncUrl) => (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + const type = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let url = syncUrl + `/${type}?pbjs=1`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (uspConsent && uspConsent.consentString) { + url += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + url += '&gpp=' + gppConsent.gppString; + url += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + url += `&coppa=${coppa}`; + + return [{ + type, + url + }]; +}; + +/** + * + * @param {{ addPlacementType?: function, addCustomFieldsToPlacement?: function }} [config] + * @returns {function(object, object): object} + */ +export const buildPlacementProcessingFunction = (config) => (bid, bidderRequest) => { + const addPlacementType = config?.addPlacementType ?? defaultPlacementType; + + const placement = createBasePlacement(bid); + + addPlacementType(bid, bidderRequest, placement); + + if (config?.addCustomFieldsToPlacement) { + config.addCustomFieldsToPlacement(bid, bidderRequest, placement); + } + + return placement; +}; diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index 4000230b1e0..bd85d2b3cc0 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -1,217 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'acuityads'; const AD_URL = 'https://prebid.admanmedia.com/pbjs'; const SYNC_URL = 'https://cs.admanmedia.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - placement.placementId = placementId; - placement.type = 'publisher'; - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - // Add GPP consent - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index fd59ba74944..6778e536a1b 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -1,208 +1,51 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction +} from '../libraries/teqblazeUtils/bidderUtils.js'; const GVLID = 149; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; -const URL_SYNC = 'https://sync.admanmedia.com'; +const SYNC_URL = 'https://sync.admanmedia.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid['mediaType']) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + placement.traffic = placement.adFormat; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } +}; - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); + const content = deepAccess(bidderRequest, 'ortb2.site.content', config.getAnyConfig('ortb2.site.content')); -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); + if (content) { + request.data.content = content; } -} + + return request; +}; export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId)); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const content = deepAccess(bidderRequest, 'ortb2.site.content', config.getAnyConfig('ortb2.site.content')); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - if (content) { - request.content = content; - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const { params, bidId, mediaTypes } = bid; - - const placement = { - placementId: params.placementId, - bidId, - eids: [], - bidFloor: getBidFloor(bid) - } - - if (bid.transactionId) { - placement.ext = placement.ext || {}; - placement.ext.tid = bid.transactionId; - } - - if (bid.schain) { - placement.schain = bid.schain; - } - - if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); - getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); - getUserId(placement.eids, bid.userId.idx, 'idx.lat'); - } - - if (mediaTypes?.[BANNER]) { - placement.traffic = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes?.[VIDEO]) { - placement.traffic = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } - - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - serverResponse = serverResponse.body; - for (let i = 0; i < serverResponse.length; i++) { - let resItem = serverResponse[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = URL_SYNC + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } - + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index 55ee1f0900c..e40d20356af 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -1,183 +1,46 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'adprime'; const AD_URL = 'https://delta.adprime.com/pbjs'; const SYNC_URL = 'https://sync.adprime.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; + if (bid.userId && bid.userId.idl_env) { + placement.identeties = {}; + placement.identeties.identityLink = bid.userId.idl_env; } -} -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } + placement.keywords = getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords); + placement.audiences = bid.params.audiences || []; +}; - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let placements = []; - let request = { - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - secure: 1, - host: location.host, - page: location.pathname, - placements: placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - const { mediaTypes } = bid; - const placement = {}; - let sizes - let identeties = {} - if (mediaTypes) { - if (mediaTypes[BANNER] && mediaTypes[BANNER].sizes) { - placement.adFormat = BANNER; - sizes = mediaTypes[BANNER].sizes - } else if (mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize) { - placement.adFormat = VIDEO; - sizes = mediaTypes[VIDEO].playerSize - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else { - placement.adFormat = NATIVE; - placement.native = mediaTypes[NATIVE]; - } - } - if (bid.userId && bid.userId.idl_env) { - identeties.identityLink = bid.userId.idl_env - } - - placements.push({ - ...placement, - placementId: bid.params.placementId, - bidId: bid.bidId, - sizes: sizes || [], - wPlayer: sizes ? sizes[0] : 0, - hPlayer: sizes ? sizes[1] : 0, - schain: bid.schain || {}, - keywords: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords), - audiences: bid.params.audiences || [], - identeties, - bidFloor: getBidFloor(bid) - }); - } - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/axisBidAdapter.js b/modules/axisBidAdapter.js index da167fae062..c2ad40b2b94 100644 --- a/modules/axisBidAdapter.js +++ b/modules/axisBidAdapter.js @@ -1,190 +1,53 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - +import { deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'axis'; const AD_URL = 'https://prebid.axis-marketplace.com/pbjs'; const SYNC_URL = 'https://cs.axis-marketplace.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { integration, token } = params; - const bidfloor = getBidFloor(bid); +const addPlacementType = (bid, bidderRequest, placement) => { + placement.integration = bid.params.integration; + placement.token = bid.params.token; +}; - const placement = { - integration, - token, - bidId, - schain, - bidfloor - }; +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + const { mediaTypes } = bid; - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; + if (placement.adFormat === BANNER) { placement.pos = mediaTypes[BANNER].pos; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; + } else if (placement.adFormat === VIDEO) { placement.pos = mediaTypes[VIDEO].pos; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; placement.context = mediaTypes[VIDEO].context; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; } +}; - return placement; -} +const placementProcessingFunction = buildPlacementProcessingFunction({ addPlacementType, addCustomFieldsToPlacement }); -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (e) { - logError(e); - return 0; - } -} + request.data.iabCat = deepAccess(bidderRequest, 'ortb2.site.cat'); + + return request; +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.integration && params.token); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + isBidRequestValid: isBidRequestValid(['integration', 'token'], 'every'), + buildRequests, + interpretResponse, - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - iabCat: deepAccess(bidderRequest, 'ortb2.site.cat'), - coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout || 3000, - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; if (gdprConsent && gdprConsent.consentString) { @@ -198,6 +61,11 @@ export const spec = { syncUrl += `&ccpa=${uspConsent.consentString}`; } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl += '&gpp=' + gppConsent.gppString; + syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + const coppa = config.getConfig('coppa') ? 1 : 0; syncUrl += `&coppa=${coppa}`; @@ -206,6 +74,6 @@ export const spec = { url: syncUrl }]; } -}; +} registerBidder(spec); diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index d3c7d185058..077717c36f4 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -1,203 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'beyondmedia'; const AD_URL = 'https://backend.andbeyond.media/pbjs'; const SYNC_URL = 'https://cookies.andbeyond.media'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - placement.placementId = placementId; - placement.type = 'publisher'; - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } - - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index 6d49c814486..1cf3bf889b7 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -1,176 +1,38 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'boldwin'; const AD_URL = 'https://ssp.videowalldirect.com/pbjs'; const SYNC_URL = 'https://sync.videowalldirect.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} +}; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && (bid.params.placementId || bid.params.endpointId)); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - - // Add GPP consent - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - const { mediaTypes, params } = bid; - const placement = {}; - let sizes; - if (mediaTypes) { - if (mediaTypes[BANNER] && mediaTypes[BANNER].sizes) { - placement.adFormat = BANNER; - sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize) { - placement.adFormat = VIDEO; - sizes = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else { - placement.adFormat = NATIVE; - placement.native = mediaTypes[NATIVE]; - } - } - - const { placementId, endpointId } = params; - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - placements.push({ - ...placement, - bidId: bid.bidId, - sizes: sizes || [], - wPlayer: sizes ? sizes[0] : 0, - hPlayer: sizes ? sizes[1] : 0, - schain: bid.schain || {}, - bidFloor: getBidFloor(bid), - }); - } - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: () => { - return [{ - type: 'image', - url: SYNC_URL - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index addcdfebb27..f5d98312a63 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'compass'; const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; const SYNC_URL = 'https://sa-cs.deliverimp.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index a6aa9262061..96a55d657d2 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -1,216 +1,21 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'contentexchange'; const AD_URL = 'https://eu2.adnetwork.agency/pbjs'; const SYNC_URL = 'https://sync2.adnetwork.agency'; const GVLID = 864; -function isBidResponseValid (bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData (bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, adFormat } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - placementId, - bidId, - adFormat, - schain, - bidfloor - }; - - switch (adFormat) { - case BANNER: - placement.sizes = mediaTypes[BANNER].sizes; - break; - case VIDEO: - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - break; - case NATIVE: - placement.native = mediaTypes[NATIVE]; - break; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && - params && - params.placementId && - params.adFormat - ); - switch (params.adFormat) { - case BANNER: - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - break; - case VIDEO: - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - break; - case NATIVE: - valid = valid && Boolean(mediaTypes[NATIVE]); - break; - default: - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - // TODO: does the fallback to 'window.location' make sense? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 5c4627cfe1b..e87e39599a0 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -1,201 +1,19 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { deepAccess, logMessage } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'e_volution'; const GVLID = 957; const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; - const schain = bid.schain || {}; - const { placementId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - placementId, - bidId, - schain, - bidfloor - }; - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - if (transactionId) { - placement.ext = placement.ext || {}; - placement.ext.tid = transactionId; - } - - if (userIdAsEids && userIdAsEids.length) { - placement.eids = userIdAsEids; - } - - return placement; -} - -function getBidFloor(bid) { - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - return 0; - } -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js index 30a63ea5942..822fa683d9e 100644 --- a/modules/emtvBidAdapter.js +++ b/modules/emtvBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'emtv'; const AD_URL = 'https://us-east-ep.engagemedia.tv/pbjs'; const SYNC_URL = 'https://cs.engagemedia.tv'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index eeecf152869..1684509b7b9 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -1,213 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'globalsun'; const AD_URL = 'https://endpoint.globalsun.io/pbjs'; const SYNC_URL = 'https://cs.globalsun.io'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 0af26ccf6e7..c03d579d2a5 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -1,238 +1,19 @@ -import { logMessage, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'iqzone'; const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; const SYNC_URL = 'https://cs.smartssp.iqzone.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - if (transactionId) { - placement.ext = placement.ext || {}; - placement.ext.tid = transactionId; - } - - if (userIdAsEids && userIdAsEids.length) { - placement.eids = userIdAsEids; - } - - return placement; -} - -function getBidFloor(bid) { - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { - syncUrl += '&gpp=' + gppConsent.gppString; - syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/kiviadsBidAdapter.js b/modules/kiviadsBidAdapter.js index cc1e319c348..161ddad470f 100644 --- a/modules/kiviadsBidAdapter.js +++ b/modules/kiviadsBidAdapter.js @@ -1,213 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'kiviads'; const AD_URL = 'https://lb.kiviads.com/pbjs'; const SYNC_URL = 'https://sync.kiviads.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: bidderRequest.coppa === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - gpp: bidderRequest.gppConsent || undefined, - tmax: bidderRequest.bidderTimeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index b2c1548a02d..255e3670254 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -1,158 +1,40 @@ -import { isFn, deepAccess } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'krushmedia'; const AD_URL = 'https://ads4.krushmedia.com/?c=rtb&m=hb'; -const SYNC_URL = 'https://cs.krushmedia.com/html?src=pbjs' - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; +const SYNC_URL = 'https://cs.krushmedia.com'; + +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + placement.key = bid.params.key; + placement.traffic = placement.adFormat; + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } -} +}; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.key))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - location = bidderRequest?.refererInfo ?? null; - const placements = []; - const request = { - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - secure: 1, - host: location?.domain ?? '', - page: location?.page ?? '', - placements: placements - }; - - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const placement = { - key: bid.params.key, - bidId: bid.bidId, - schain: bid.schain || {}, - bidFloor: getBidFloor(bid) - }; - - if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - placement.traffic = BANNER; - placement.sizes = bid.mediaTypes[BANNER].sizes; - } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - placement.traffic = VIDEO; - placement.wPlayer = bid.mediaTypes[VIDEO].playerSize[0]; - placement.hPlayer = bid.mediaTypes[VIDEO].playerSize[1]; - placement.minduration = bid.mediaTypes[VIDEO].minduration; - placement.maxduration = bid.mediaTypes[VIDEO].maxduration; - placement.mimes = bid.mediaTypes[VIDEO].mimes; - placement.protocols = bid.mediaTypes[VIDEO].protocols; - placement.startdelay = bid.mediaTypes[VIDEO].startdelay; - placement.placement = bid.mediaTypes[VIDEO].placement; - placement.plcmt = bid.mediaTypes[VIDEO].plcmt; - placement.skip = bid.mediaTypes[VIDEO].skip; - placement.skipafter = bid.mediaTypes[VIDEO].skipafter; - placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate; - placement.delivery = bid.mediaTypes[VIDEO].delivery; - placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod; - placement.api = bid.mediaTypes[VIDEO].api; - placement.linearity = bid.mediaTypes[VIDEO].linearity; - } else if (bid.mediaTypes && bid.mediaTypes[NATIVE]) { - placement.traffic = NATIVE; - placement.native = bid.mediaTypes[NATIVE]; - } - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncUrl = SYNC_URL - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - return [{ - type: 'iframe', - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(['key']), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js index 80524f1e8ed..e34ec89cf35 100644 --- a/modules/loyalBidAdapter.js +++ b/modules/loyalBidAdapter.js @@ -1,190 +1,17 @@ -import { logMessage } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'loyal'; const AD_URL = 'https://us-east-1.loyal.app/pbjs'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor, - eids: [] - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.gdprConsent?.consentString) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index 470a11510c5..6ad42a4f3ca 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,132 +1,19 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'lunamediahb'; const AD_URL = 'https://balancer.lmgssp.com/?c=o&m=multi'; const SYNC_URL = 'https://cookie.lmgssp.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl) || Boolean(bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - location = bidderRequest?.refererInfo ?? null; - - const placements = []; - const request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location?.domain ?? '', - 'page': location?.page ?? '', - 'placements': placements - }; - - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - schain: bid.schain || {}, - }; - const mediaType = bid.mediaTypes - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.traffic = BANNER; - } else if (mediaType && mediaType[VIDEO]) { - if (mediaType[VIDEO].playerSize) { - placement.wPlayer = mediaType[VIDEO].playerSize[0]; - placement.hPlayer = mediaType[VIDEO].playerSize[1]; - } - placement.traffic = VIDEO; - placement.videoContext = mediaType[VIDEO].context || 'instream' - } else if (mediaType && mediaType[NATIVE]) { - placement.native = mediaType[NATIVE]; - placement.traffic = NATIVE; - } - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/mathildeadsBidAdapter.js b/modules/mathildeadsBidAdapter.js index 929cee8f3c0..0ecfe63765b 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'mathildeads'; const AD_URL = 'https://endpoint2.mathilde-ads.com/pbjs'; const SYNC_URL = 'https://cs2.mathilde-ads.com'; -function isBidResponseValid (bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData (bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - placementId, - bidId, - schain, - bidfloor - }; - - if (mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } - - if (mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } - - if (mediaTypes[NATIVE]) { - placement.adFormat = NATIVE; - placement.native = mediaTypes[NATIVE]; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - if (mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } - - if (mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } - - if (mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } - - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest?.refererInfo?.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index 9ca16394087..471e8fb2754 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -1,17 +1,9 @@ -import { - deepAccess, - logMessage, - isPlainObject, - isNumber, - isArray, - isStr -} from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - +import { isPlainObject, isNumber, isArray, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { USERSYNC_DEFAULT_CONFIG } from '../src/userSync.js'; +import { isBidRequestValid, buildRequestsBase, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'mgidX'; const GVLID = 358; @@ -19,212 +11,27 @@ const AD_URL = 'https://#{REGION}#.mgid.com/pbjs'; const PIXEL_SYNC_URL = 'https://cm.mgid.com/i.gif'; const IFRAME_SYNC_URL = 'https://cm.mgid.com/i.html'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - if (transactionId) { - placement.ext = placement.ext || {}; - placement.ext.tid = transactionId; - } +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest }); + const region = validBidRequests[0].params?.region; - if (userIdAsEids && userIdAsEids.length) { - placement.eids = userIdAsEids; + if (region === 'eu') { + request.url = AD_URL.replace('#{REGION}#', 'eu'); + } else { + request.url = AD_URL.replace('#{REGION}#', 'us-east-x'); } - return placement; -} - -function getBidFloor(bid) { - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - return 0; - } -} + return request; +}; export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - const region = validBidRequests[0].params?.region; - - let url; - if (region === 'eu') { - url = AD_URL.replace('#{REGION}#', 'eu'); - } else { - url = AD_URL.replace('#{REGION}#', 'us-east-x'); - } - - return { - method: 'POST', - url: url, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, + isBidRequestValid: isBidRequestValid(), + buildRequests, + interpretResponse, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { const spb = isPlainObject(config.getConfig('userSync')) && diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js index 7fb585ada06..dcc6e9594c4 100644 --- a/modules/mobfoxpbBidAdapter.js +++ b/modules/mobfoxpbBidAdapter.js @@ -1,148 +1,17 @@ -import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'mobfoxpb'; const AD_URL = 'https://bes.mobfox.com/pbjs'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const winTop = getWindowTop(); - const location = winTop.location; - const placements = []; - const request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - - // Add GPP consent - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - schain: bid.schain || {}, - bidfloor: getBidFloor(bid) - }; - const mediaType = bid.mediaTypes; - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.traffic = BANNER; - placement.sizes = mediaType[BANNER].sizes; - } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { - placement.traffic = VIDEO; - placement.wPlayer = mediaType[VIDEO].playerSize[0]; - placement.hPlayer = mediaType[VIDEO].playerSize[1]; - placement.playerSize = mediaType[VIDEO].playerSize; - placement.minduration = mediaType[VIDEO].minduration; - placement.maxduration = mediaType[VIDEO].maxduration; - placement.mimes = mediaType[VIDEO].mimes; - placement.protocols = mediaType[VIDEO].protocols; - placement.startdelay = mediaType[VIDEO].startdelay; - placement.placement = mediaType[VIDEO].placement; - placement.plcmt = mediaType[VIDEO].plcmt; - placement.skip = mediaType[VIDEO].skip; - placement.skipafter = mediaType[VIDEO].skipafter; - placement.minbitrate = mediaType[VIDEO].minbitrate; - placement.maxbitrate = mediaType[VIDEO].maxbitrate; - placement.delivery = mediaType[VIDEO].delivery; - placement.playbackmethod = mediaType[VIDEO].playbackmethod; - placement.api = mediaType[VIDEO].api; - placement.linearity = mediaType[VIDEO].linearity; - } else if (mediaType && mediaType[NATIVE]) { - placement.traffic = NATIVE; - placement.native = mediaType[NATIVE]; - } - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - resItem.meta = resItem.meta || {}; - resItem.meta.advertiserDomains = resItem.adomain || []; - - response.push(resItem); - } - } - return response; - }, + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index 0304c325c33..859bfc9de7e 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -1,254 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'pgamssp'; const AD_URL = 'https://us-east.pgammedia.com/pbjs'; const SYNC_URL = 'https://cs.pgammedia.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor, - eids: [] - }; - - if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2?.id, 'uidapi.com'); - getUserId(placement.eids, bid.userId.id5id?.uid, 'id5-sync.com'); - } - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { - syncUrl += '&gpp=' + gppConsent.gppString; - syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/playdigoBidAdapter.js b/modules/playdigoBidAdapter.js index 6c4ea6492d9..4fc7addc0a0 100644 --- a/modules/playdigoBidAdapter.js +++ b/modules/playdigoBidAdapter.js @@ -1,199 +1,17 @@ -import { logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'playdigo'; const AD_URL = 'https://server.playdigo.com/pbjs'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!bid.getFloor || typeof bid.getFloor !== 'function') { - return 0; - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - tmax: config.getConfig('bidderTimeout') - }; - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/pubCircleBidAdapter.js b/modules/pubCircleBidAdapter.js index db435d5fa4f..c63b80b819c 100644 --- a/modules/pubCircleBidAdapter.js +++ b/modules/pubCircleBidAdapter.js @@ -1,232 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'pubcircle'; const AD_URL = 'https://ml.pubcircle.ai/pbjs'; const SYNC_URL = 'https://cs.pubcircle.ai'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - placement.placementId = placementId; - placement.type = 'publisher'; - - if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); - getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); - getUserId(placement.eids, bid.userId.idx, 'idx.lat'); - getUserId(placement.eids, bid.userId.idl_env, 'liveramp.com'); - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - }, - - onBidViewable: function (bid) { - // to do : we need to implement js tag to fire pixel with viewability counter - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/qtBidAdapter.js b/modules/qtBidAdapter.js index 7616b990ff8..f9f8b9b9efe 100644 --- a/modules/qtBidAdapter.js +++ b/modules/qtBidAdapter.js @@ -1,238 +1,19 @@ -import { logMessage, deepAccess } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'qt'; const AD_URL = 'https://endpoint1.qt.io/pbjs'; const SYNC_URL = 'https://cs.qt.io'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes, transactionId, userIdAsEids } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - if (transactionId) { - placement.ext = placement.ext || {}; - placement.ext.tid = transactionId; - } - - if (userIdAsEids && userIdAsEids.length) { - placement.eids = userIdAsEids; - } - - return placement; -} - -function getBidFloor(bid) { - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { - syncUrl += '&gpp=' + gppConsent.gppString; - syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js index fa54f27e4c0..1c51944c538 100644 --- a/modules/visiblemeasuresBidAdapter.js +++ b/modules/visiblemeasuresBidAdapter.js @@ -1,213 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'visiblemeasures'; const AD_URL = 'https://us-e.visiblemeasures.com/pbjs'; const SYNC_URL = 'https://cs.visiblemeasures.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js new file mode 100644 index 00000000000..650ff668070 --- /dev/null +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -0,0 +1,519 @@ +import { expect } from 'chai'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../../../../libraries/teqblazeUtils/bidderUtils.js'; +import { BANNER, VIDEO, NATIVE } from '../../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../../src/utils.js'; + +const bidder = 'bidder'; +const DOMAIN = 'test.org'; +const AD_URL = `https://${DOMAIN}/pbjs`; +const SYNC_URL = `https://${DOMAIN}`; + +describe('TeqBlazeBidderUtils', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + const spec = { + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`https://${DOMAIN}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`https://${DOMAIN}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`https://${DOMAIN}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`) + }); + }); +}); diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index 31ef9dd6466..526bbf6fd54 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -1,11 +1,21 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/acuityadsBidAdapter'; +import { spec } from '../../../modules/acuityadsBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'acuityads' +const bidder = 'acuityads'; describe('AcuityAdsBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('AcuityAdsBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('AcuityAdsBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('AcuityAdsBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,12 +86,22 @@ describe('AcuityAdsBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, - timeout: 500, - ortb2: {} + timeout: 500 }; describe('isBidRequestValid', function () { @@ -130,7 +153,7 @@ describe('AcuityAdsBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -146,6 +169,7 @@ describe('AcuityAdsBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -171,8 +195,10 @@ describe('AcuityAdsBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -187,40 +213,38 @@ describe('AcuityAdsBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - describe('Returns data with gppConsent', function () { - it('bidderRequest.gppConsent', () => { - bidderRequest.gppConsent = { - gppString: 'abc123', - applicableSections: [8] - }; + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - serverRequest = spec.buildRequests(bids, bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.property('gpp'); - expect(data).to.have.property('gpp_sid'); - delete bidderRequest.gppConsent; - }) + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); - it('bidderRequest.ortb2.regs.gpp', () => { - bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; - bidderRequest.ortb2.regs.gpp = 'abc123'; - bidderRequest.ortb2.regs.gpp_sid = [8]; + delete bidderRequest.gppConsent; + }) - serverRequest = spec.buildRequests(bids, bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.property('gpp'); - expect(data).to.have.property('gpp_sid'); - }) - }); + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -424,5 +448,17 @@ describe('AcuityAdsBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index 3cff5816356..cca5d57db0f 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -1,343 +1,466 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/admanBidAdapter.js'; -import {deepClone} from '../../../src/utils' +import { expect } from 'chai'; +import { spec } from '../../../modules/admanBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; -describe('AdmanAdapter', function () { - let bidBanner = { - bidId: '2dd581a2b6281d', - bidder: 'adman', - bidderRequestId: '145e1d6a7837c9', - params: { - placementId: 0 - }, - placementCode: 'placementid_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - mediaTypes: { - banner: { - sizes: [[300, 250]] +const bidder = 'adman'; + +describe('AdmanBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids }, - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '0', - hp: 1, - rid: 'bidrequestid', - // name: 'alladsallthetime', - domain: 'example.com' + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 } - ] + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids } - }; + ]; - let bidVideo = deepClone({ - ...bidBanner, - params: { - placementId: 0, - traffic: 'video' - }, + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - video: { - playerSize: [300, 250] + [BANNER]: { + sizes: [[300, 250]] } + }, + params: { + } - }); + } - let bidderRequest = { - bidderCode: 'adman', - auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', - bidderRequestId: 'ffffffffffffff', - start: 1472239426002, - auctionStart: 1472239426000, - timeout: 5000, - uspConsent: '1YN-', - gdprConsent: 'gdprConsent', + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'http://www.example.com', - reachedTop: true, + referer: 'https://test.com', + page: 'https://test.com' }, - bids: [bidBanner, bidVideo] - } + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; describe('isBidRequestValid', function () { - it('Should return true when placementId can be cast to a number', function () { - expect(spec.isBidRequestValid(bidBanner)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); - it('Should return false when placementId is not a number', function () { - bidBanner.params.placementId = 'aaa'; - expect(spec.isBidRequestValid(bidBanner)).to.be.false; - bidBanner.params.placementId = 0; + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bidBanner], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://pub.admanmedia.com/?c=o&m=multi'); }); - it('Should contain ccpa', function() { - expect(serverRequest.data.ccpa).to.be.an('string') - }) - it('Returns valid BANNER data if array of bids is valid', function () { - serverRequest = spec.buildRequests([bidBanner], bidderRequest); + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - let placements = data['placements']; - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor', 'ext'); - expect(placement.schain).to.be.an('object') - expect(placement.ext).to.be.an('object') - expect(placement.ext).to.have.key('tid') - expect(placement.ext.tid).to.equal(bidBanner.transactionId); - expect(placement.placementId).to.be.a('number'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.traffic).to.be.oneOf([BANNER, VIDEO, NATIVE]); expect(placement.bidId).to.be.a('string'); - expect(placement.traffic).to.be.a('string'); - expect(placement.sizes).to.be.an('array'); - expect(placement.bidFloor).to.be.an('number'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.traffic === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } } }); - it('Returns valid VIDEO data if array of bids is valid', function () { - serverRequest = spec.buildRequests([bidVideo], bidderRequest); + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - let placements = data['placements']; - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'schain', 'bidFloor', - 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', - 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'ext'); - expect(placement.ext).to.be.an('object') - expect(placement.ext).to.have.key('tid') - expect(placement.ext.tid).to.equal(bidBanner.transactionId); - expect(placement.schain).to.be.an('object') - expect(placement.placementId).to.be.a('number'); - expect(placement.bidId).to.be.a('string'); - expect(placement.traffic).to.be.a('string'); - expect(placement.bidFloor).to.be.an('number'); - } + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; }); }); - describe('buildRequests with user ids', function () { - bidBanner.userId = {} - bidBanner.userId.uid2 = { id: 'uid2id123' }; - let serverRequest = spec.buildRequests([bidBanner], bidderRequest); - it('Returns valid data if array of bids is valid', function () { + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - let placements = data['placements']; expect(data).to.be.an('object'); - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.property('eids') - expect(placement.eids).to.be.an('array') - expect(placement.eids.length).to.be.equal(1) - for (let index in placement.eids) { - let v = placement.eids[index]; - expect(v).to.have.all.keys('source', 'uids') - expect(v.source).to.be.oneOf(['uidapi.com']) - expect(v.uids).to.be.an('array'); - expect(v.uids.length).to.be.equal(1) - expect(v.uids[0]).to.have.property('id') - } - } - }); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { - it('(BANNER) Returns an array of valid server responses if response object is valid', function () { - const resBannerObject = { - body: [ { - requestId: '123', + it('Should interpret banner response', function () { + const banner = { + body: [{ mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', netRevenue: true, currency: 'USD', - adomain: ['example.com'], + dealId: '1', meta: { advertiserDomains: ['google.com'], advertiserId: 1234 } - } ] + }] }; - - const serverResponses = spec.interpretResponse(resBannerObject); - - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.ad).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); - } + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); - - it('(VIDEO) Returns an array of valid server responses if response object is valid', function () { - const resVideoObject = { - body: [ { - requestId: '123', + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', mediaType: 'video', - cpm: 0.3, - width: 320, - height: 50, - vastUrl: 'https://', - ttl: 1000, - creativeId: '123asd', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', netRevenue: true, currency: 'USD', - adomain: ['example.com'], + dealId: '1', meta: { advertiserDomains: ['google.com'], advertiserId: 1234 } - } ] + }] }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; - const serverResponses = spec.interpretResponse(resVideoObject); - - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.vastUrl).to.be.a('string'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); - } + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); - - it('(NATIVE) Returns an array of valid server responses if response object is valid', function () { - const resNativeObject = { - body: [ { - requestId: '123', + it('Should interpret native response', function () { + const native = { + body: [{ mediaType: 'native', - cpm: 0.3, - width: 320, - height: 50, native: { - title: 'title', - image: 'image', - impressionTrackers: [ 'https://' ] + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], }, - ttl: 1000, - creativeId: '123asd', + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', netRevenue: true, currency: 'USD', - adomain: ['example.com'], meta: { advertiserDomains: ['google.com'], advertiserId: 1234 } - } ] + }] }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; - const serverResponses = spec.interpretResponse(resNativeObject); - - expect(serverResponses).to.be.an('array').that.is.not.empty; - for (let i = 0; i < serverResponses.length; i++) { - let dataItem = serverResponses[i]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); - expect(dataItem.requestId).to.be.a('string'); - expect(dataItem.cpm).to.be.a('number'); - expect(dataItem.width).to.be.a('number'); - expect(dataItem.height).to.be.a('number'); - expect(dataItem.native).to.be.an('object'); - expect(dataItem.ttl).to.be.a('number'); - expect(dataItem.creativeId).to.be.a('string'); - expect(dataItem.netRevenue).to.be.a('boolean'); - expect(dataItem.currency).to.be.a('string'); - expect(dataItem.mediaType).to.be.a('string'); - expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); - } + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); - - it('Invalid mediaType in response', function () { - const resBadObject = { - body: [ { - mediaType: 'other', - requestId: '123', - cpm: 0.3, - ttl: 1000, - creativeId: '123asd', - currency: 'USD' - } ] + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] }; - const serverResponses = spec.interpretResponse(resBadObject); - + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); expect(serverResponses).to.be.an('array').that.is.empty; }); }); - describe('getUserSyncs', function () { - const gdprConsent = { consentString: 'consentString', gdprApplies: 1 }; - const consentString = { consentString: 'consentString' } - let userSync = spec.getUserSyncs({}, {}, gdprConsent, consentString); - it('Returns valid URL and type', function () { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://sync.admanmedia.com/image?pbjs=1&gdpr=0&gdpr_consent=consentString&ccpa_consent=consentString&coppa=0'); + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.admanmedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.admanmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.admanmedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 5efed4ec5ab..71aeccb2975 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -1,131 +1,256 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/adprimeBidAdapter.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/adprimeBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; -describe('AdprimebBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'adprime', +const bidder = 'adprime'; + +describe('AdprimeBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - banner: { - sizes: [[300, 250]], + [BANNER]: { + sizes: [[300, 250]] } }, params: { - placementId: 'testBanner' + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://delta.adprime.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'identeties', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords', 'audiences', 'bidFloor'); - expect(placement.placementId).to.equal('testBanner'); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.adFormat).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement.adFormat).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.keywords).to.be.an('array'); + expect(placement.audiences).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); - }); - describe('buildRequests with user ids', function () { - bid.userId = {} - bid.userId.idl_env = 'idl_env123'; - let serverRequest = spec.buildRequests([bid], bidderRequest); - it('Return bids with user identeties', function () { + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - let placements = data['placements']; expect(data).to.be.an('object'); - for (let i = 0; i < placements.length; i++) { - let placement = placements[i]; - expect(placement).to.have.property('identeties') - expect(placement.identeties).to.be.an('object') - expect(placement.identeties).to.have.property('identityLink') - expect(placement.identeties.identityLink).to.be.equal('idl_env123') - } - }); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -141,7 +266,10 @@ describe('AdprimebBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -149,15 +277,15 @@ describe('AdprimebBidAdapter', function () { let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { @@ -172,7 +300,10 @@ describe('AdprimebBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -206,7 +337,10 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -293,6 +427,7 @@ describe('AdprimebBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -317,5 +452,17 @@ describe('AdprimebBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://sync.adprime.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.adprime.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/axisBidAdapter_spec.js b/test/spec/modules/axisBidAdapter_spec.js index 083f05f5c0a..c1e6adef0c6 100644 --- a/test/spec/modules/axisBidAdapter_spec.js +++ b/test/spec/modules/axisBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/axisBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'axis' +const bidder = 'axis'; describe('AxisBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -19,7 +29,8 @@ describe('AxisBidAdapter', function () { params: { integration: '000000', token: '000000' - } + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -35,7 +46,8 @@ describe('AxisBidAdapter', function () { params: { integration: '000000', token: '000000' - } + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -59,7 +71,8 @@ describe('AxisBidAdapter', function () { params: { integration: '000000', token: '000000' - } + }, + userIdAsEids } ]; @@ -78,15 +91,25 @@ describe('AxisBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' }, ortb2: { site: { cat: ['IAB24'] + }, + device: { + w: 1512, + h: 982, + language: 'en-UK' } - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -139,7 +162,7 @@ describe('AxisBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.iabCat).to.have.lengthOf(1); @@ -156,6 +179,7 @@ describe('AxisBidAdapter', function () { expect(placement.token).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -183,8 +207,10 @@ describe('AxisBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -199,12 +225,38 @@ describe('AxisBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -410,5 +462,17 @@ describe('AxisBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.axis-marketplace.com/image?pbjs=1&ccpa=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.axis-marketplace.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 751b3ae1098..6bc071eb780 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/beyondmediaBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'beyondmedia' +const bidder = 'beyondmedia'; describe('AndBeyondMediaBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('AndBeyondMediaBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('AndBeyondMediaBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('AndBeyondMediaBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('AndBeyondMediaBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -83,8 +107,6 @@ describe('AndBeyondMediaBidAdapter', function () { describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { expect(spec.isBidRequestValid(bids[0])).to.be.true; - expect(spec.isBidRequestValid(bids[1])).to.be.true; - expect(spec.isBidRequestValid(bids[2])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; @@ -131,7 +153,7 @@ describe('AndBeyondMediaBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -172,8 +194,10 @@ describe('AndBeyondMediaBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +212,38 @@ describe('AndBeyondMediaBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -397,5 +447,17 @@ describe('AndBeyondMediaBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 9a7b16c0914..2a2cd070d4c 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -1,115 +1,220 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/boldwinBidAdapter.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/boldwinBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'boldwin'; describe('BoldwinBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'boldwin', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - banner: { - sizes: [ [300, 250], [320, 50] ], + [BANNER]: { + sizes: [[300, 250]] } }, params: { - placementId: 'testBanner', + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - ortb2: {} + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'bidFloor', 'type'); - expect(placement.placementId).to.equal('testBanner'); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.adFormat).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.type).to.exist.and.to.equal('publisher'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement.adFormat).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); - - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('gpp consent', function () { @@ -119,7 +224,7 @@ describe('BoldwinBidAdapter', function () { applicableSections: [8] }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); @@ -129,15 +234,18 @@ describe('BoldwinBidAdapter', function () { }) it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; bidderRequest.ortb2.regs.gpp = 'abc123'; bidderRequest.ortb2.regs.gpp_sid = [8]; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; }) }); @@ -155,7 +263,11 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -163,18 +275,17 @@ describe('BoldwinBidAdapter', function () { let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); - it('Should interpret video response', function () { const video = { body: [{ @@ -186,7 +297,11 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -220,6 +335,10 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -307,14 +426,41 @@ describe('BoldwinBidAdapter', function () { }); }); - describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); - it('Returns valid URL and type', function () { - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://sync.videowalldirect.com'); + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.videowalldirect.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.videowalldirect.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.videowalldirect.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 6a761e63ea1..98c79fb22e2 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/compassBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'compass' +const bidder = 'compass'; describe('CompassBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('CompassBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('CompassBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('CompassBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('CompassBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -129,7 +153,7 @@ describe('CompassBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +169,56 @@ describe('CompassBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +244,10 @@ describe('CompassBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +262,38 @@ describe('CompassBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -395,5 +497,17 @@ describe('CompassBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 1b3dc4f19c9..7c7d3d4ef2a 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/contentexchangeBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'contentexchange' +const bidder = 'contentexchange'; describe('ContentexchangeBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,9 +26,9 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - placementId: 'test', - adFormat: BANNER - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -31,9 +41,9 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - placementId: 'test', - adFormat: VIDEO - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,9 +65,9 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - placementId: 'test', - adFormat: NATIVE - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -70,15 +80,26 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - adFormat: BANNER + } } const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -132,7 +153,7 @@ describe('ContentexchangeBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -142,11 +163,62 @@ describe('ContentexchangeBidAdapter', function () { const { placements } = serverRequest.data; for (let i = 0, len = placements.length; i < len; i++) { const placement = placements[i]; - expect(placement.placementId).to.be.equal('test'); + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); expect(placement.bidId).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -172,8 +244,10 @@ describe('ContentexchangeBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +262,38 @@ describe('ContentexchangeBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -372,6 +472,7 @@ describe('ContentexchangeBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -396,5 +497,17 @@ describe('ContentexchangeBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://sync2.adnetwork.agency/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync2.adnetwork.agency/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index f257434fd70..2eee3b4356e 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -26,7 +26,7 @@ describe('EvolutionTechBidAdapter', function () { } }, params: { - placementId: 'testBanner', + placementId: 'testBanner' }, userIdAsEids }, @@ -41,7 +41,7 @@ describe('EvolutionTechBidAdapter', function () { } }, params: { - placementId: 'testVideo', + placementId: 'testVideo' }, userIdAsEids }, @@ -91,7 +91,15 @@ describe('EvolutionTechBidAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -119,6 +127,10 @@ describe('EvolutionTechBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://service.e-volution.ai/?c=o&m=multi'); + }); + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); @@ -156,6 +168,56 @@ describe('EvolutionTechBidAdapter', function () { expect(placement.bidId).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js index 4f95a0cc094..4468cb64f0c 100644 --- a/test/spec/modules/emtvBidAdapter_spec.js +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -8,6 +8,16 @@ const adUrl = 'https://us-east-ep.engagemedia.tv/pbjs'; const syncUrl = 'https://cs.engagemedia.tv'; describe('EMTVBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -18,8 +28,9 @@ describe('EMTVBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -32,8 +43,9 @@ describe('EMTVBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,8 +67,9 @@ describe('EMTVBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -75,10 +88,22 @@ describe('EMTVBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -130,7 +155,7 @@ describe('EMTVBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -146,6 +171,56 @@ describe('EMTVBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -171,8 +246,10 @@ describe('EMTVBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -187,12 +264,38 @@ describe('EMTVBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -396,5 +499,17 @@ describe('EMTVBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`) + }); }); }); diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 0d17c25363d..0920ca5bd78 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/globalsunBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'globalsun' +const bidder = 'globalsun'; describe('GlobalsunBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('GlobalsunBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('GlobalsunBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('GlobalsunBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('GlobalsunBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -129,7 +153,7 @@ describe('GlobalsunBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +169,56 @@ describe('GlobalsunBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +244,10 @@ describe('GlobalsunBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +262,38 @@ describe('GlobalsunBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -395,5 +497,17 @@ describe('GlobalsunBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.globalsun.io/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.globalsun.io/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index 9d012e526e2..8f622cc39ed 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from '../../../modules/iqzoneBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'iqzone' +const bidder = 'iqzone'; describe('IQZoneBidAdapter', function () { const userIdAsEids = [{ @@ -26,7 +26,7 @@ describe('IQZoneBidAdapter', function () { } }, params: { - placementId: 'testBanner', + placementId: 'testBanner' }, userIdAsEids }, @@ -41,7 +41,7 @@ describe('IQZoneBidAdapter', function () { } }, params: { - placementId: 'testVideo', + placementId: 'testVideo' }, userIdAsEids }, @@ -65,7 +65,7 @@ describe('IQZoneBidAdapter', function () { } }, params: { - placementId: 'testNative', + placementId: 'testNative' }, userIdAsEids } @@ -87,10 +87,19 @@ describe('IQZoneBidAdapter', function () { const bidderRequest = { uspConsent: '1---', gdprConsent: { - consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -181,12 +190,63 @@ describe('IQZoneBidAdapter', function () { } }); + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + it('Returns data with gdprConsent and without uspConsent', function () { delete bidderRequest.uspConsent; serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; @@ -202,12 +262,6 @@ describe('IQZoneBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); - - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('gpp consent', function () { diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index 03d58cbc265..618648a0c07 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -3,11 +3,21 @@ import { spec } from '../../../modules/kiviadsBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'kiviads' +const bidder = 'kiviads'; const adUrl = 'https://lb.kiviads.com/pbjs'; const syncUrl = 'https://sync.kiviads.com'; describe('KiviAdsBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -18,8 +28,9 @@ describe('KiviAdsBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -32,8 +43,9 @@ describe('KiviAdsBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,8 +67,9 @@ describe('KiviAdsBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -75,11 +88,22 @@ describe('KiviAdsBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - bidderTimeout: 300 + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -122,7 +146,6 @@ describe('KiviAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'gpp', 'tmax' ); expect(data.deviceWidth).to.be.a('number'); @@ -130,11 +153,9 @@ describe('KiviAdsBidAdapter', function () { expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); - expect(data.host).to.contain('localhost'); expect(data.page).to.be.a('string'); - expect(data.page).to.equal('/'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -150,6 +171,56 @@ describe('KiviAdsBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -175,8 +246,10 @@ describe('KiviAdsBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -191,12 +264,38 @@ describe('KiviAdsBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -400,5 +499,17 @@ describe('KiviAdsBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`) + }); }); }); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 16f87394df9..452eb517eb8 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -1,146 +1,303 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/krushmediaBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/krushmediaBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'krushmedia'; describe('KrushmediabBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'krushmedia', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + key: 783 + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + key: 783 + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + key: 783 + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - key: 783 + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.key; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://ads4.krushmedia.com/?c=rtb&m=hb'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('key', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor'); - expect(placement.key).to.equal(783); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('key', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'bidFloor', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', - 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); - expect(placement.traffic).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.key).to.be.equal(783); + expect(placement.traffic).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.traffic === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); - it('Returns valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids } - }; + ]; - bid.mediaTypes = {}; - bid.params.traffic = NATIVE; - bid.mediaTypes[NATIVE] = native; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('key', 'bidId', 'traffic', 'native', 'schain', 'bidFloor'); - expect(placement.traffic).to.equal(NATIVE); - expect(placement.native).to.equal(native); + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.traffic).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.traffic === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -155,7 +312,11 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -163,15 +324,15 @@ describe('KrushmediabBidAdapter', function () { let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { @@ -185,7 +346,11 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -219,6 +384,10 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -305,6 +474,7 @@ describe('KrushmediabBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -314,20 +484,32 @@ describe('KrushmediabBidAdapter', function () { expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') - expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].type).to.equal('image') expect(syncData[0].url).to.be.a('string') - expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&gdpr=1&gdpr_consent=ALL') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { const syncData = spec.getUserSyncs({}, {}, {}, { - consentString: '1NNN' + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] }); expect(syncData).to.be.an('array').which.is.not.empty; expect(syncData[0]).to.be.an('object') expect(syncData[0].type).to.be.a('string') - expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].type).to.equal('image') expect(syncData[0].url).to.be.a('string') - expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&ccpa_consent=1NNN') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/loyalBidAdapter_spec.js b/test/spec/modules/loyalBidAdapter_spec.js index 28e87fc7047..f125eef4c5c 100644 --- a/test/spec/modules/loyalBidAdapter_spec.js +++ b/test/spec/modules/loyalBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/loyalBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'loyal' +const bidder = 'loyal'; describe('LoyalBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('LoyalBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('LoyalBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('LoyalBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -74,10 +87,19 @@ describe('LoyalBidAdapter', function () { const bidderRequest = { uspConsent: '1---', gdprConsent: { - consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -147,7 +169,56 @@ describe('LoyalBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); - expect(placement.eids).to.exist.and.to.be.an('array'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -174,6 +245,9 @@ describe('LoyalBidAdapter', function () { let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +262,38 @@ describe('LoyalBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index 181b6e75fe7..f2653269c7b 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -1,145 +1,252 @@ import {expect} from 'chai'; import {spec} from '../../../modules/lunamediahbBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'lunamediahb'; describe('LunamediaHBBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'lunamediahb', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 783, - traffic: BANNER + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://balancer.lmgssp.com/?c=o&m=multi'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); - expect(placement.placementId).to.equal(783); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'videoContext'); - expect(placement.traffic).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); - }); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - it('Returns valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); } - }; - - bid.mediaTypes = {}; - bid.params.traffic = NATIVE; - bid.mediaTypes[NATIVE] = native; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); - expect(placement.traffic).to.equal(NATIVE); - expect(placement.native).to.equal(native); + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -154,23 +261,28 @@ describe('LunamediaHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -183,7 +295,11 @@ describe('LunamediaHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -191,7 +307,7 @@ describe('LunamediaHBBidAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -199,6 +315,7 @@ describe('LunamediaHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -216,13 +333,17 @@ describe('LunamediaHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -235,6 +356,7 @@ describe('LunamediaHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { @@ -326,5 +448,17 @@ describe('LunamediaHBBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cookie.lmgssp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cookie.lmgssp.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 107906ec83d..e55e7175f4b 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -3,21 +3,32 @@ import { spec } from '../../../modules/mathildeadsBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'mathildeads' +const bidder = 'mathildeads'; describe('MathildeAdsBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), - bidder: bidder, + bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('MathildeAdsBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('MathildeAdsBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -66,14 +79,27 @@ describe('MathildeAdsBidAdapter', function () { sizes: [[300, 250]] } }, - params: {} + params: { + + } } const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -127,7 +153,7 @@ describe('MathildeAdsBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -142,6 +168,8 @@ describe('MathildeAdsBidAdapter', function () { expect(placement.bidId).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -167,8 +195,10 @@ describe('MathildeAdsBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -183,12 +213,38 @@ describe('MathildeAdsBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -367,6 +423,7 @@ describe('MathildeAdsBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -391,5 +448,17 @@ describe('MathildeAdsBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs2.mathilde-ads.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs2.mathilde-ads.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 0660676996c..ae1ca17c70a 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -5,7 +5,7 @@ import { getUniqueIdentifierStr } from '../../../src/utils.js'; import { config } from '../../../src/config'; import { USERSYNC_DEFAULT_CONFIG } from '../../../src/userSync'; -const bidder = 'mgidX' +const bidder = 'mgidX'; describe('MGIDXBidAdapter', function () { const userIdAsEids = [{ @@ -95,9 +95,17 @@ describe('MGIDXBidAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - timeout: 1000 + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index ad88f18eb4c..de289f8a5e5 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -1,148 +1,268 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/mobfoxpbBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/mobfoxpbBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'mobfoxpb'; describe('MobfoxHBBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'mobfoxpb', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 783, - traffic: BANNER + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - ortb2: {} + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://bes.mobfox.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain', 'bidfloor'); - expect(placement.placementId).to.equal(783); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); - expect(placement.bidfloor).to.equal(0); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'playerSize', 'wPlayer', 'hPlayer', 'schain', 'bidfloor', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', - 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); - expect(placement.traffic).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); - it('Returns valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids } - }; + ]; - bid.mediaTypes = {}; - bid.params.traffic = NATIVE; - bid.mediaTypes[NATIVE] = native; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain', 'bidfloor'); - expect(placement.traffic).to.equal(NATIVE); - expect(placement.native).to.equal(native); + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.ccpa).to.exist; expect(data.ccpa).to.be.a('string'); expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); - - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('gpp consent', function () { @@ -152,7 +272,7 @@ describe('MobfoxHBBidAdapter', function () { applicableSections: [8] }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); @@ -162,15 +282,18 @@ describe('MobfoxHBBidAdapter', function () { }) it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; bidderRequest.ortb2.regs.gpp = 'abc123'; bidderRequest.ortb2.regs.gpp_sid = [8]; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; }) }); @@ -188,7 +311,11 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -196,15 +323,16 @@ describe('MobfoxHBBidAdapter', function () { let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -217,7 +345,11 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -233,6 +365,7 @@ describe('MobfoxHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -250,6 +383,10 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -269,6 +406,7 @@ describe('MobfoxHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index 281a012fb7a..a90eadba8b0 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/pgamsspBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'pgamssp' +const bidder = 'pgamssp'; describe('PGAMBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('PGAMBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('PGAMBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('PGAMBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -74,10 +87,19 @@ describe('PGAMBidAdapter', function () { const bidderRequest = { uspConsent: '1---', gdprConsent: { - consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -147,7 +169,56 @@ describe('PGAMBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); - expect(placement.eids).to.exist.and.to.be.an('array'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -174,6 +245,8 @@ describe('PGAMBidAdapter', function () { let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; @@ -189,12 +262,6 @@ describe('PGAMBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); - - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('gpp consent', function () { diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js index 80fc3c96e81..62ae2796596 100644 --- a/test/spec/modules/playdigoBidAdapter_spec.js +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -6,6 +6,16 @@ import { getUniqueIdentifierStr } from '../../../src/utils.js'; const bidder = 'playdigo' describe('PlaydigoBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('PlaydigoBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('PlaydigoBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -54,7 +66,8 @@ describe('PlaydigoBidAdapter', function () { }, params: { placementId: 'testNative' - } + }, + userIdAsEids } ]; @@ -78,8 +91,17 @@ describe('PlaydigoBidAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -105,6 +127,10 @@ describe('PlaydigoBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://server.playdigo.com/pbjs'); + }); + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); @@ -143,6 +169,7 @@ describe('PlaydigoBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -175,9 +202,10 @@ describe('PlaydigoBidAdapter', function () { }, params: { endpointId: 'testBanner', - } + }, + userIdAsEids } - ] + ]; let serverRequest = spec.buildRequests(bids, bidderRequest); @@ -190,6 +218,7 @@ describe('PlaydigoBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index 8aaa023ee1c..aa880c206fa 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -1,11 +1,21 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/pubCircleBidAdapter'; +import { spec } from '../../../modules/pubCircleBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'pubcircle' +const bidder = 'pubcircle'; describe('PubCircleBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('PubCircleBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('PubCircleBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('PubCircleBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('PubCircleBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -129,7 +153,7 @@ describe('PubCircleBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +169,7 @@ describe('PubCircleBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +195,10 @@ describe('PubCircleBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +213,38 @@ describe('PubCircleBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -395,5 +448,17 @@ describe('PubCircleBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.pubcircle.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.pubcircle.ai/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js index e573a6ae0e9..8bd8730c7a9 100644 --- a/test/spec/modules/qtBidAdapter_spec.js +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -26,7 +26,7 @@ describe('QTBidAdapter', function () { } }, params: { - placementId: 'testBanner', + placementId: 'testBanner' }, userIdAsEids }, @@ -41,7 +41,7 @@ describe('QTBidAdapter', function () { } }, params: { - placementId: 'testVideo', + placementId: 'testVideo' }, userIdAsEids }, @@ -91,7 +91,15 @@ describe('QTBidAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -119,6 +127,10 @@ describe('QTBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://endpoint1.qt.io/pbjs'); + }); + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index ad75e17699f..1e0fd6f64d2 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -3,11 +3,21 @@ import { spec } from '../../../modules/visiblemeasuresBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'visiblemeasures' +const bidder = 'visiblemeasures'; const adUrl = 'https://us-e.visiblemeasures.com/pbjs'; const syncUrl = 'https://cs.visiblemeasures.com'; describe('VisibleMeasuresBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -18,8 +28,9 @@ describe('VisibleMeasuresBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -32,8 +43,9 @@ describe('VisibleMeasuresBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,8 +67,9 @@ describe('VisibleMeasuresBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -75,9 +88,20 @@ describe('VisibleMeasuresBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -131,7 +155,7 @@ describe('VisibleMeasuresBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -147,6 +171,56 @@ describe('VisibleMeasuresBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -172,8 +246,10 @@ describe('VisibleMeasuresBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +264,38 @@ describe('VisibleMeasuresBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -397,5 +499,17 @@ describe('VisibleMeasuresBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`); + }); }); }); From 124fa0a2889e369fd5bb07a82bd30e51b24c6ce7 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Sun, 23 Jun 2024 05:28:06 +0200 Subject: [PATCH 0244/1097] Prebid Core: TTL counts only when page is active (#11803) * 11268 TTL counts only when page is active * refactor * refactor due to performance optimization * get rid of false timer id --------- Co-authored-by: Marcin Komorski --- src/utils/focusTimeout.js | 41 ++++++++++++++++ src/utils/ttlCollection.js | 3 +- test/spec/auctionmanager_spec.js | 15 ++++++ test/spec/unit/utils/focusTimeout_spec.js | 60 +++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/utils/focusTimeout.js create mode 100644 test/spec/unit/utils/focusTimeout_spec.js diff --git a/src/utils/focusTimeout.js b/src/utils/focusTimeout.js new file mode 100644 index 00000000000..0ba66cc4efc --- /dev/null +++ b/src/utils/focusTimeout.js @@ -0,0 +1,41 @@ +let outOfFocusStart; +let timeOutOfFocus = 0; +let suspendedTimeouts = []; + +document.addEventListener('visibilitychange', () => { + if (document.hidden) { + outOfFocusStart = Date.now() + } else { + timeOutOfFocus += Date.now() - outOfFocusStart + suspendedTimeouts.forEach(({ callback, startTime, setTimerId }) => setTimerId(setFocusTimeout(callback, timeOutOfFocus - startTime)())) + outOfFocusStart = null; + } +}); + +/** + * Wraps native setTimeout function in order to count time only when page is focused + * + * @param {function(*): ()} [callback] - A function that will be invoked after passed time + * @param {number} [milliseconds] - Minimum duration (in milliseconds) that the callback will be executed after + * @returns {function(*): (number)} - Getter function for current timer id + */ +export default function setFocusTimeout(callback, milliseconds) { + const startTime = timeOutOfFocus; + let timerId = setTimeout(() => { + if (timeOutOfFocus === startTime && outOfFocusStart == null) { + callback(); + } else if (outOfFocusStart != null) { + // case when timeout ended during page is out of focus + suspendedTimeouts.push({ + callback, + startTime, + setTimerId(newId) { + timerId = newId; + } + }) + } else { + timerId = setFocusTimeout(callback, timeOutOfFocus - startTime)(); + } + }, milliseconds); + return () => timerId; +} diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index 2294c072108..b6e0a5198df 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -1,5 +1,6 @@ import {GreedyPromise} from './promise.js'; import {binarySearch, logError, timestamp} from '../utils.js'; +import setFocusTimeout from './focusTimeout.js'; /** * Create a set-like collection that automatically forgets items after a certain time. @@ -46,7 +47,7 @@ export function ttlCollection( if (pendingPurge.length > 0) { const now = timestamp(); nextPurge = Math.max(now, pendingPurge[0].expiry + slack); - task = setTimeout(() => { + task = setFocusTimeout(() => { const now = timestamp(); let cnt = 0; for (const entry of pendingPurge) { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 26f641a10e7..ab00ac86d98 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -27,6 +27,7 @@ import {PrebidServer} from '../../modules/prebidServerBidAdapter/index.js'; import '../../modules/currency.js' import { setConfig as setCurrencyConfig } from '../../modules/currency.js'; import { REJECTION_REASON } from '../../src/constants.js'; +import { setDocumentHidden } from './unit/utils/focusTimeout_spec.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -888,6 +889,20 @@ describe('auctionmanager.js', function () { }) }); + it('are not dropped after `minBidCacheTTL` seconds if the page was hidden', () => { + auction.callBids(); + config.setConfig({ + minBidCacheTTL: 10 + }); + return auction.end.then(() => { + expect(auctionManager.getNoBids().length).to.eql(1); + setDocumentHidden(true); + clock.tick(10 * 10000); + setDocumentHidden(false); + expect(auctionManager.getNoBids().length).to.eql(1); + }) + }); + Object.entries({ 'bids': { bd: [{ diff --git a/test/spec/unit/utils/focusTimeout_spec.js b/test/spec/unit/utils/focusTimeout_spec.js new file mode 100644 index 00000000000..ed7b1c0c2f3 --- /dev/null +++ b/test/spec/unit/utils/focusTimeout_spec.js @@ -0,0 +1,60 @@ +import setFocusTimeout from '../../../../src/utils/focusTimeout'; + +export const setDocumentHidden = (hidden) => { + Object.defineProperty(document, 'hidden', { + configurable: true, + get: () => hidden, + }); + document.dispatchEvent(new Event('visibilitychange')); +}; + +describe('focusTimeout', () => { + let clock; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + }) + + it('should invoke callback when page is visible', () => { + let callback = sinon.stub(); + setFocusTimeout(callback, 2000); + clock.tick(2000); + expect(callback.called).to.be.true; + }); + + it('should not invoke callback if page was hidden', () => { + let callback = sinon.stub(); + setFocusTimeout(callback, 2000); + setDocumentHidden(true); + clock.tick(3000); + expect(callback.called).to.be.false; + }); + + it('should defer callback execution when page is hidden', () => { + let callback = sinon.stub(); + setFocusTimeout(callback, 4000); + clock.tick(2000); + setDocumentHidden(true); + clock.tick(2000); + setDocumentHidden(false); + expect(callback.called).to.be.false; + clock.tick(2000); + expect(callback.called).to.be.true; + }); + + it('should return updated timerId after page was showed again', () => { + let callback = sinon.stub(); + const getCurrentTimerId = setFocusTimeout(callback, 4000); + const oldTimerId = getCurrentTimerId(); + clock.tick(2000); + setDocumentHidden(true); + clock.tick(2000); + setDocumentHidden(false); + const newTimerId = getCurrentTimerId(); + expect(oldTimerId).to.not.equal(newTimerId); + }); +}); From 98801df2c710b7dffac35fec82dd7c0e82a542b9 Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:50:59 -0700 Subject: [PATCH 0245/1097] Conversant Bid Adapter: send request in USD (#11857) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 1 + test/spec/modules/conversantBidAdapter_spec.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 6ef289af411..ea8488cf361 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -60,6 +60,7 @@ const converter = ortbConverter({ request: function (buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); request.at = 1; + request.cur = ['USD']; if (context.bidRequests) { const bidRequest = context.bidRequests[0]; setSiteId(bidRequest, request); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index c0560e08431..d68383b9118 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -441,6 +441,13 @@ describe('Conversant adapter tests', function() { expect(payload.site.content).to.have.property('title'); }); + it('Verify currency', () => { + const bidderRequest = { timeout: 9999, ortb2: {cur: ['EUR']} }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload.cur).deep.equal(['USD']); + }) + it('Verify supply chain data', () => { const bidderRequest = {refererInfo: {page: 'http://test.com?a=b&c=123'}}; const schain = {complete: 1, ver: '1.0', nodes: [{asi: 'bidderA.com', sid: '00001', hp: 1}]}; From 2cbeb80ac39d9eeeee239516ad66b15b42768585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=BEidar=20Bare=C5=A1i=C4=87?= <153560835+bbaresic@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:03:41 -0500 Subject: [PATCH 0246/1097] improve-server-bid-adapter-error-message (#11858) --- modules/prebidServerBidAdapter/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 0ecb6365430..a6c840193d9 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -488,7 +488,12 @@ export function PrebidServer() { doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, onError(msg, error) { - logError(`Prebid server call failed: '${msg}'`, error); + const {p1Consent = '', noP1Consent = ''} = s2sBidRequest?.s2sConfig?.endpoint || {}; + if (p1Consent === noP1Consent) { + logError(`Prebid server call failed: '${msg}'. Endpoint: "${p1Consent}"}`, error); + } else { + logError(`Prebid server call failed: '${msg}'. Endpoints: p1Consent "${p1Consent}", noP1Consent "${noP1Consent}"}`, error); + } bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_ERROR, { error, bidderRequest })); done(error.timedOut); }, From ea364657ac25caefb8fd67dbe1b84ff50e16f175 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 24 Jun 2024 21:24:17 +0000 Subject: [PATCH 0247/1097] Prebid 9.2.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04572fd3c8a..851fc0ee6ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.2.0-pre", + "version": "9.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.2.0-pre", + "version": "9.2.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index ab91c6e006a..8079e93d68d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.2.0-pre", + "version": "9.2.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 64729a104c419504e06b7f03e2857340f05ef3d4 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 24 Jun 2024 21:24:17 +0000 Subject: [PATCH 0248/1097] Increment version to 9.3.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 851fc0ee6ef..071e836e503 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.2.0", + "version": "9.3.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.2.0", + "version": "9.3.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 8079e93d68d..2860f0dc61c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.2.0", + "version": "9.3.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 743e749dcbfb4954533ce040f16059ac1ac9c934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Baudisch?= Date: Tue, 25 Jun 2024 05:01:06 +0200 Subject: [PATCH 0249/1097] orbidderBidAdapter.js: remove mediaType video to avoid broken requests (#11851) * orbidderBidAdapter.js: remove mediaType video to avoid broken requests * orbidderBidAdapter.js: fix reference --- modules/orbidderBidAdapter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 0f912384db7..9d96c329d4a 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -93,6 +93,9 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { referer = bidderRequest.refererInfo.page || ''; } + if (bidRequest?.mediaTypes?.video) { + delete bidRequest.mediaTypes.video; + } bidRequest.params.bidfloor = getBidFloor(bidRequest); From d8937d76ca2ebb2e4c41fa81c3c8808b770f7208 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 24 Jun 2024 20:36:14 -0700 Subject: [PATCH 0250/1097] consentManagementTcf: add flag to set dsarequired (#11824) * consentManagementTcf: add flag to set dsarequired * Fix duplication --- libraries/consentManagement/cmUtils.js | 38 ++++++++++++++++++++++ modules/consentManagementGpp.js | 40 ++--------------------- modules/consentManagementTcf.js | 45 ++++---------------------- test/spec/fpd/gdpr_spec.js | 26 +++++++++++++-- 4 files changed, 71 insertions(+), 78 deletions(-) create mode 100644 libraries/consentManagement/cmUtils.js diff --git a/libraries/consentManagement/cmUtils.js b/libraries/consentManagement/cmUtils.js new file mode 100644 index 00000000000..f4dbd81f493 --- /dev/null +++ b/libraries/consentManagement/cmUtils.js @@ -0,0 +1,38 @@ +import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; +import {logError, logInfo, logWarn} from '../../src/utils.js'; + +export function consentManagementHook(name, getConsent, loadConsentData) { + function loadIfMissing(cb) { + if (getConsent()) { + logInfo('User consent information already known. Pulling internally stored information...'); + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } else { + loadConsentData(cb); + } + } + + return timedAuctionHook(name, function requestBidsHook(fn, reqBidsConfigObj) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); + } + + if (shouldCancelAuction) { + fn.stopTiming(); + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } + } else { + fn.call(this, reqBidsConfigObj); + } + }); + }); +} diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index bd40713924e..013650de20a 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -7,12 +7,12 @@ import {deepSetValue, isEmpty, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import {gppDataHandler} from '../src/adapterManager.js'; -import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; import {GreedyPromise} from '../src/utils/promise.js'; import {buildActivityParams} from '../src/activities/params.js'; +import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -228,20 +228,6 @@ function loadConsentData(cb) { } } -/** - * Like `loadConsentData`, but cache and re-use previously loaded data. - * @param cb - */ -function loadIfMissing(cb) { - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - // eslint-disable-next-line standard/no-callback-literal - cb(false); - } else { - loadConsentData(cb); - } -} - /** * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the * user's encoded consent string from the supported CMP. Once obtained, the module will store this @@ -250,29 +236,7 @@ function loadIfMissing(cb) { * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook(fn, reqBidsConfigObj) { - loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { - if (errMsg) { - let log = logWarn; - if (shouldCancelAuction) { - log = logError; - errMsg = `${errMsg} Canceling auction as per consentManagement config.`; - } - log(errMsg, ...extraArgs); - } - - if (shouldCancelAuction) { - fn.stopTiming(); - if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { - reqBidsConfigObj.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } - } else { - fn.call(this, reqBidsConfigObj); - } - }); -}); +export const requestBidsHook = consentManagementHook('gpp', () => consentData, loadConsentData); function processCmpData(consentData) { if ( diff --git a/modules/consentManagementTcf.js b/modules/consentManagementTcf.js index c7c618635ba..7de273c1380 100644 --- a/modules/consentManagementTcf.js +++ b/modules/consentManagementTcf.js @@ -8,11 +8,11 @@ import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn import {config} from '../src/config.js'; import {gdprDataHandler} from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; -import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {cmpClient} from '../libraries/cmp/cmpClient.js'; +import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -22,6 +22,7 @@ export let userCMP; export let consentTimeout; export let gdprScope; export let staticConsentData; +let dsaPlatform = false; let actionTimeout; let consentData; @@ -156,20 +157,6 @@ function loadConsentData(cb) { } } -/** - * Like `loadConsentData`, but cache and re-use previously loaded data. - * @param cb - */ -function loadIfMissing(cb) { - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - // eslint-disable-next-line standard/no-callback-literal - cb(false); - } else { - loadConsentData(cb); - } -} - /** * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the * user's encoded consent string from the supported CMP. Once obtained, the module will store this @@ -178,29 +165,7 @@ function loadIfMissing(cb) { * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const requestBidsHook = timedAuctionHook('gdpr', function requestBidsHook(fn, reqBidsConfigObj) { - loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { - if (errMsg) { - let log = logWarn; - if (shouldCancelAuction) { - log = logError; - errMsg = `${errMsg} Canceling auction as per consentManagement config.`; - } - log(errMsg, ...extraArgs); - } - - if (shouldCancelAuction) { - fn.stopTiming(); - if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { - reqBidsConfigObj.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } - } else { - fn.call(this, reqBidsConfigObj); - } - }); -}); +export const requestBidsHook = consentManagementHook('gdpr', () => consentData, loadConsentData); /** * This function checks the consent data provided by CMP to ensure it's in an expected state. @@ -282,6 +247,7 @@ export function setConsentConfig(config) { // if true, then gdprApplies should be set to true gdprScope = config.defaultGdprScope === true; + dsaPlatform = !!config.dsaPlatform; logInfo('consentManagement module has been activated...'); @@ -315,6 +281,9 @@ export function enrichFPDHook(next, fpd) { } deepSetValue(ortb2, 'user.ext.consent', consent.consentString); } + if (dsaPlatform) { + deepSetValue(ortb2, 'regs.ext.dsa.dsarequired', 3); + } return ortb2; })); } diff --git a/test/spec/fpd/gdpr_spec.js b/test/spec/fpd/gdpr_spec.js index 68303657939..acd3531c2a2 100644 --- a/test/spec/fpd/gdpr_spec.js +++ b/test/spec/fpd/gdpr_spec.js @@ -1,5 +1,7 @@ import {gdprDataHandler} from '../../../src/adapterManager.js'; import {enrichFPDHook} from '../../../modules/consentManagementTcf.js'; +import {config} from 'src/config.js'; +import 'src/prebid.js'; describe('GDPR FPD enrichment', () => { let sandbox, consent; @@ -12,9 +14,9 @@ describe('GDPR FPD enrichment', () => { sandbox.restore(); }) - function callHook() { + function callHook(ortb2 = {}) { let result; - enrichFPDHook((res) => { result = res }, Promise.resolve({})); + enrichFPDHook((res) => { result = res }, Promise.resolve(ortb2)); return result; } @@ -44,4 +46,24 @@ describe('GDPR FPD enrichment', () => { }) }) }); + + describe('dsa', () => { + describe('when dsaPlaform is set', () => { + beforeEach(() => { + config.setConfig({ + consentManagement: { + gdpr: { + dsaPlatform: true + } + } + }); + }); + + it('sets dsarequired', () => { + return callHook().then(ortb2 => { + expect(ortb2.regs.ext.dsa.dsarequired).to.equal(3); + }); + }); + }); + }); }); From 980a11ef264e7e360499f44c377c2dd3cd0c75a4 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 25 Jun 2024 09:32:31 -0400 Subject: [PATCH 0251/1097] Various modules: don't import global to get version (#11836) * Update mabidderBidAdapter.js * Update addefendBidAdapter.js * Update orbidderBidAdapter.js * Update adxcgAnalyticsAdapter.js * Update medianetAnalyticsAdapter.js * Update orbidderBidAdapter.js * Update medianetAnalyticsAdapter.js * Update medianetAnalyticsAdapter.js * Update addefendBidAdapter.js * Update mabidderBidAdapter.js * Update orbidderBidAdapter.js * Update pixfutureBidAdapter.js * Update conversantAnalyticsAdapter.js * Update yuktamediaAnalyticsAdapter.js * Update medianetBidAdapter.js * Update id5AnalyticsAdapter.js * Update tappxBidAdapter.js * Update adlooxRtdProvider.js * Update snigelBidAdapter.js * Update terceptAnalyticsAdapter.js * Update conversantAnalyticsAdapter.js * Update pixfutureBidAdapter.js * Update tappxBidAdapter.js * Update conversantAnalyticsAdapter_spec.js * Update conversantAnalyticsAdapter.js * Update conversantAnalyticsAdapter_spec.js --- modules/addefendBidAdapter.js | 3 +-- modules/adlooxRtdProvider.js | 2 +- modules/adxcgAnalyticsAdapter.js | 3 +-- modules/conversantAnalyticsAdapter.js | 3 +-- modules/id5AnalyticsAdapter.js | 3 +-- modules/mabidderBidAdapter.js | 3 +-- modules/medianetAnalyticsAdapter.js | 3 +-- modules/medianetBidAdapter.js | 2 +- modules/orbidderBidAdapter.js | 3 +-- modules/pixfutureBidAdapter.js | 3 +-- modules/snigelBidAdapter.js | 3 +-- modules/tappxBidAdapter.js | 3 +-- modules/terceptAnalyticsAdapter.js | 3 +-- modules/yuktamediaAnalyticsAdapter.js | 3 +-- test/spec/modules/conversantAnalyticsAdapter_spec.js | 2 +- 15 files changed, 15 insertions(+), 27 deletions(-) diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index dbb186fdc86..a646400b083 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -1,5 +1,4 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'addefend'; @@ -17,7 +16,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let bid = { - v: getGlobal().version, + v: 'v' + '$prebid.version$', auctionId: false, pageId: false, gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index 727dc84e399..1545588676d 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -106,7 +106,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { // buildUrl creates PHP style multi-parameters and includes undefined... (╯°□°)╯ ┻━┻ const url = buildUrl(mergeDeep(parseUrl(`${API_ORIGIN}/q`), { search: { - 'v': `pbjs-${getGlobal().version}`, + 'v': 'pbjs-v' + '$prebid.version$', 'c': config.params.clientid, 'p': config.params.platformid, 't': config.params.tagid, diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 7ad95121209..7538e2962cc 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -3,7 +3,6 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; /** * Analytics adapter from adxcg.com @@ -123,7 +122,7 @@ function send (data) { ats: adxcgAnalyticsAdapter.context.auctionTimestamp, aav: adxcgAnalyticsVersion, iob: intersectionObserverAvailable(window) ? '1' : '0', - pbv: getGlobal().version, + pbv: '$prebid.version$', sz: window.screen.width + 'x' + window.screen.height } }); diff --git a/modules/conversantAnalyticsAdapter.js b/modules/conversantAnalyticsAdapter.js index 44e712d54f7..520735d4e73 100644 --- a/modules/conversantAnalyticsAdapter.js +++ b/modules/conversantAnalyticsAdapter.js @@ -1,7 +1,6 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import adapterManager from '../src/adapterManager.js'; import {logInfo, logWarn, logError, logMessage, deepAccess, isInteger} from '../src/utils.js'; import {getRefererInfo} from '../src/refererDetection.js'; @@ -515,7 +514,7 @@ cnvrHelper.createPayload = function(payloadType, auctionId, timestamp) { cnvrSampleRate: initOptions.cnvr_sample_rate, auction: { auctionId: auctionId, - preBidVersion: getGlobal().version, + preBidVersion: '$prebid.version$', sid: initOptions.site_id, auctionTimestamp: timestamp }, diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index 70da467ff7c..10fe8d82ef4 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -4,7 +4,6 @@ import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { logInfo, logError } from '../src/utils.js'; import * as events from '../src/events.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const { AUCTION_END, @@ -33,7 +32,7 @@ const FLUSH_EVENTS = [ const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics' const TZ = new Date().getTimezoneOffset(); -const PBJS_VERSION = getGlobal().version; +const PBJS_VERSION = 'v' + '$prebid.version$'; const ID5_REDACTED = '__ID5_REDACTED__'; const isArray = Array.isArray; diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 8d97f48f604..6df68bda269 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,6 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { getGlobal } from '../src/prebidGlobal.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'mabidder'; @@ -39,7 +38,7 @@ export const spec = { url: baseUrl, method: 'POST', data: { - v: getGlobal().version, + v: 'v' + '$prebid.version$', bids: bids, url: bidderRequest.refererInfo.page || '', referer: bidderRequest.refererInfo.ref || '', diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 68927cc6b13..e8d73e77d5f 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -36,8 +36,7 @@ const PRICE_GRANULARITY = { }; const MEDIANET_BIDDER_CODE = 'medianet'; -// eslint-disable-next-line no-undef -const PREBID_VERSION = getGlobal().version; +const PREBID_VERSION = '$prebid.version$' const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 5949e198fd1..73e25379144 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -186,7 +186,7 @@ function extParams(bidRequest, bidderRequests) { const coppaApplies = !!(config.getConfig('coppa')); return Object.assign({}, { customer_id: params.cid }, - { prebid_version: getGlobal().version }, + { prebid_version: 'v' + '$prebid.version$' }, { gdpr_applies: gdprApplies }, (gdprApplies) && { gdpr_consent_string: gdpr.consentString || '' }, { usp_applies: uspApplies }, diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 9d96c329d4a..dc8293e74f2 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,7 +3,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { getGlobal } from '../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -104,7 +103,7 @@ export const spec = { method: 'POST', options: { withCredentials: true }, data: { - v: getGlobal().version, + v: 'v' + '$prebid.version$', pageUrl: referer, ...bidRequest // get all data provided by bid request } diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 60a10621d97..c7ed1ec989d 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -5,7 +5,6 @@ import {config} from '../src/config.js'; import {find, includes} from '../src/polyfill.js'; import {deepAccess, isArray, isFn, isNumber, isPlainObject} from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; @@ -126,7 +125,7 @@ export const spec = { method: 'POST', options: {withCredentials: true}, data: { - v: getGlobal().version, + v: 'v' + '$prebid.version$', pageUrl: referer, bidId: bidRequest.bidId, // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 5a327b05cd0..e8a8b4005c5 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -3,7 +3,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject, inIframe, getDNT, generateUUID} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'snigel'; @@ -46,7 +45,7 @@ export const spec = { gdprConsent: gdprApplies === true ? hasFullGdprConsent(deepAccess(bidderRequest, 'gdprConsent')) : false, cur: getCurrencies(), test: getTestFlag(), - version: getGlobal().version, + version: 'v' + '$prebid.version$', gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || deepAccess(bidderRequest, 'ortb2.regs.gpp'), gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || deepAccess(bidderRequest, 'ortb2.regs.gpp_sid'), diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index b2939bffedf..0b618b3c124 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -6,7 +6,6 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { parseDomain } from '../src/refererDetection.js'; -import { getGlobal } from '../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -494,7 +493,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { payload.regs = regs; // < Payload - let pbjsv = (getGlobal().version !== null) ? encodeURIComponent(getGlobal().version) : -1; + let pbjsv = 'v' + '$prebid.version$'; return { method: 'POST', diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index 089f8d917d6..289a19beb53 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -3,7 +3,6 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -124,7 +123,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, terceptAnalyticsVersion: terceptAnalyticsVersion, - prebidVersion: getGlobal().version + prebidVersion: 'v' + '$prebid.version$' } }); diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 1b9ab08cfc1..19825041589 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -6,7 +6,6 @@ import { EVENTS, STATUS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_CODE = 'yuktamedia'; @@ -37,7 +36,7 @@ const _pageInfo = { referer: referer, refererDomain: parseUrl(referer).host, yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, - prebidVersion: getGlobal().version + prebidVersion: 'v' + 'prebid.version$' }; function getParameterByName(param) { diff --git a/test/spec/modules/conversantAnalyticsAdapter_spec.js b/test/spec/modules/conversantAnalyticsAdapter_spec.js index 75cb63b02f6..f46de31b19c 100644 --- a/test/spec/modules/conversantAnalyticsAdapter_spec.js +++ b/test/spec/modules/conversantAnalyticsAdapter_spec.js @@ -14,7 +14,7 @@ describe('Conversant analytics adapter tests', function() { let clock; // clock stub from sinon to mock our cache cleanup interval let logInfoStub; - const PREBID_VERSION = '1.2'; + const PREBID_VERSION = '$prebid.version$'; const SITE_ID = 108060; let requests; From ac3f0e82ba0cd439793f5a73254fff22508d6c8f Mon Sep 17 00:00:00 2001 From: Yohan Boutin Date: Wed, 26 Jun 2024 01:57:17 +0200 Subject: [PATCH 0252/1097] Seedtag - add device.sua parameter to the bidRequest (#11856) * add device.sua parameter to the payload * lint * fix test --- modules/seedtagBidAdapter.js | 4 +++ test/spec/modules/seedtagBidAdapter_spec.js | 27 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 7170f8b04b7..f3bcd70c714 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -333,6 +333,10 @@ export const spec = { payload.badv = bidderRequest.ortb2?.badv } + if (bidderRequest.ortb2?.device?.sua) { + payload.sua = bidderRequest.ortb2.device.sua + } + const payloadString = JSON.stringify(payload); return { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 1e0141b5107..3cd21c122eb 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -628,6 +628,33 @@ describe('Seedtag Adapter', function () { expect(data.badv).to.be.undefined; }); }); + + describe('device.sua param', function () { + it('should add device.sua param to payload when bidderRequest has ortb2 device.sua info', function () { + const sua = 1 + var ortb2 = { + device: { + sua: sua + } + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.sua).to.equal(sua); + }); + + it('should not add device.sua param to payload when bidderRequest does not have ortb2 device.sua info', function () { + var ortb2 = { + device: {} + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.sua).to.be.undefined; + }); + }); }) describe('interpret response method', function () { it('should return a void array, when the server response are not correct.', function () { From 8e27b0902205fbe7743404b2bfe055cd583b2c6c Mon Sep 17 00:00:00 2001 From: Copper6SSP Date: Wed, 26 Jun 2024 04:53:46 +0300 Subject: [PATCH 0253/1097] Copper6SSP: new adapter (#11809) * release adapter Copper6SSP * removed code duplication --- modules/copper6sspBidAdapter.js | 19 + modules/copper6sspBidAdapter.md | 79 +++ .../spec/modules/copper6sspBidAdapter_spec.js | 514 ++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 modules/copper6sspBidAdapter.js create mode 100755 modules/copper6sspBidAdapter.md create mode 100644 test/spec/modules/copper6sspBidAdapter_spec.js diff --git a/modules/copper6sspBidAdapter.js b/modules/copper6sspBidAdapter.js new file mode 100644 index 00000000000..335b3b3d144 --- /dev/null +++ b/modules/copper6sspBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'copper6ssp'; +const AD_URL = 'https://endpoint.copper6.com/pbjs'; +const SYNC_URL = 'https://сsync.copper6.com'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/copper6sspBidAdapter.md b/modules/copper6sspBidAdapter.md new file mode 100755 index 00000000000..a414187022d --- /dev/null +++ b/modules/copper6sspBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Copper6SSP Bidder Adapter +Module Type: Copper6SSP Bidder Adapter +Maintainer: info@copper6.com +``` + +# Description + +Connects to Copper6SSP exchange for bids. +Copper6SSP bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'copper6ssp', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'copper6ssp', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'copper6ssp', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js new file mode 100644 index 00000000000..cc4cb832450 --- /dev/null +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -0,0 +1,514 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/copper6sspBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'copper6ssp'; + +describe('Copper6SSPBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://endpoint.copper6.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); From c533b08e5b8c6a73506bcf3b7f8961a58b98e322 Mon Sep 17 00:00:00 2001 From: Ben Oraki <46795400+BenOraki@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:56:44 +0300 Subject: [PATCH 0254/1097] New Adapter: Oraki (#11727) * New Adapter: Oraki * added some fixes * removed logError * used lib functions to remove code duplication --- modules/orakiBidAdapter.js | 19 + modules/orakiBidAdapter.md | 79 ++++ test/spec/modules/orakiBidAdapter_spec.js | 510 ++++++++++++++++++++++ 3 files changed, 608 insertions(+) create mode 100644 modules/orakiBidAdapter.js create mode 100644 modules/orakiBidAdapter.md create mode 100644 test/spec/modules/orakiBidAdapter_spec.js diff --git a/modules/orakiBidAdapter.js b/modules/orakiBidAdapter.js new file mode 100644 index 00000000000..d3ef143249b --- /dev/null +++ b/modules/orakiBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'oraki'; +const AD_URL = 'https://eu1.oraki.io/pbjs'; +const SYNC_URL = 'https://sync.oraki.io'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/orakiBidAdapter.md b/modules/orakiBidAdapter.md new file mode 100644 index 00000000000..ce35021a937 --- /dev/null +++ b/modules/orakiBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Oraki Bidder Adapter +Module Type: Oraki Bidder Adapter +Maintainer: ben@oraki.io +``` + +# Description + +Connects to Oraki exchange for bids. +Oraki bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'oraki', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'oraki', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'oraki', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js new file mode 100644 index 00000000000..47a7bf8779d --- /dev/null +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -0,0 +1,510 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/orakiBidAdapter'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes'; +import { getUniqueIdentifierStr } from '../../../src/utils'; + +const bidder = 'oraki'; + +describe('OrakiBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK', + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.oraki.io/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.oraki.io/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.oraki.io/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); From 92c77f5dbf6e0ca44a3e147af5f5b2d433ff9c7b Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 25 Jun 2024 21:57:53 -0400 Subject: [PATCH 0255/1097] criteoBidAdapter.js: allow plcmt instead of placement (#11819) --- modules/criteoBidAdapter.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 55879089530..229acaac01a 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -540,9 +540,16 @@ function hasValidVideoMediaType(bidRequest) { var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; requiredMediaTypesParams.forEach(function (param) { - if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { - isValid = false; - logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); + if (param === 'placement') { + if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined && deepAccess(bidRequest, 'mediaTypes.video.plcmt') === undefined && deepAccess(bidRequest, 'params.video.plcmt') === undefined) { + isValid = false; + logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' or mediaTypes.video.plcmt is required'); + } + } else { + if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { + isValid = false; + logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); + } } }); From 0ea2add0bf9225fadb47ceff30da20603f2ef862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Wed, 26 Jun 2024 05:38:18 +0300 Subject: [PATCH 0256/1097] Vidazoo Adapter: save and send first request time (#11821) * Vidazoo Adapter: save and send load time * fix: move shared code into new library * fix: more utils functions * fix: moved vidazoo buildRequestData into lib * fix: moved vidazoo interpretResponse to lib * fix: moved vidazoo buildRequests to lib * twistdigital lib fns --- libraries/vidazooUtils/bidderUtils.js | 481 +++++++++++++++++ libraries/vidazooUtils/constants.js | 7 + modules/twistDigitalBidAdapter.js | 13 +- modules/vidazooBidAdapter.js | 494 +----------------- .../modules/twistDigitalBidAdapter_spec.js | 8 +- test/spec/modules/vidazooBidAdapter_spec.js | 44 +- 6 files changed, 541 insertions(+), 506 deletions(-) create mode 100644 libraries/vidazooUtils/bidderUtils.js create mode 100644 libraries/vidazooUtils/constants.js diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js new file mode 100644 index 00000000000..99579fbf7ff --- /dev/null +++ b/libraries/vidazooUtils/bidderUtils.js @@ -0,0 +1,481 @@ +import { + _each, + deepAccess, + formatQS, + isArray, + isFn, + parseSizesInput, + parseUrl, + triggerPixel, + uniques +} from '../../src/utils.js'; +import {chunk} from '../chunk/chunk.js'; +import {CURRENCY, DEAL_ID_EXPIRY, SESSION_ID_KEY, TTL_SECONDS, UNIQUE_DEAL_ID_EXPIRY} from './constants.js'; +import {bidderSettings} from '../../src/bidderSettings.js'; +import {config} from '../../src/config.js'; +import {BANNER, VIDEO} from '../../src/mediaTypes.js'; + +export function createSessionId() { + return 'wsid_' + parseInt(Date.now() * Math.random()); +} + +export function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +export function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export function setStorageItem(storage, key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function getStorageItem(storage, key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key, null)); + } catch (e) { + } + + return null; +} + +export function getCacheOpt(storage, useKey) { + let data = storage.getDataFromLocalStorage(useKey, null); + if (!data) { + data = String(Date.now()); + storage.setDataInLocalStorage(useKey, data, null); + } + + return data; +} + +export function getUniqueDealId(storage, key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storage, storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storage, storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getNextDealId(storage, key, expiry = DEAL_ID_EXPIRY) { + try { + const data = getStorageItem(storage, key); + let currentValue = 0; + let timestamp; + + if (data && data.value && Date.now() - data.created < expiry) { + currentValue = data.value; + timestamp = data.created; + } + + const nextValue = currentValue + 1; + setStorageItem(storage, key, nextValue, timestamp); + return nextValue; + } catch (e) { + return 0; + } +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function onBidWon(bid) { + if (!bid.nurl) { + return; + } + const wonBid = { + adId: bid.adId, + creativeId: bid.creativeId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + netRevenue: bid.netRevenue, + mediaType: bid.mediaType, + timeToRespond: bid.timeToRespond, + status: bid.status, + }; + const qs = formatQS(wonBid); + const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; + triggerPixel(url); +} + +/** + * Create the spec function for getting user syncs + * + * The options object accepts the following fields: + * + * - iframeSyncUrl + * - imageSyncUrl + * + * @param options + */ +export function createUserSyncGetter(options = { + iframeSyncUrl: '', + imageSyncUrl: '' +}) { + return function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { + const syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + const {gppString, applicableSections} = gppConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; + + if (gppString && applicableSections?.length) { + params += '&gpp=' + encodeURIComponent(gppString); + params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); + } + + if (iframeEnabled && options.iframeSyncUrl) { + syncs.push({ + type: 'iframe', + url: `${options.iframeSyncUrl}/${params}` + }); + } + if (pixelEnabled && options.imageSyncUrl) { + syncs.push({ + type: 'image', + url: `${options.imageSyncUrl}/${params}` + }); + } + return syncs; + } +} + +export function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + switch (idSystemProviderName) { + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +export function getVidazooSessionId(storage) { + return getStorageItem(storage, SESSION_ID_KEY) || ''; +} + +export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, webSessionId, storage, bidderVersion, bidderCode, getUniqueRequestData) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueRequestData = isFn(getUniqueRequestData) ? getUniqueRequestData(hashUrl) : {}; + const uniqueDealId = getUniqueDealId(storage, hashUrl); + const pId = extractPID(params); + const isStorageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); + const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); + const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); + const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); + const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: bidderVersion, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + isStorageAllowed: isStorageAllowed, + gpid: gpid, + cat: cat, + contentData, + userData: userData, + pagecat: pagecat, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout, + webSessionId: webSessionId, + ...uniqueRequestData + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (bidderRequest.paapi?.enabled) { + const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); + if (fledge) { + data.fledge = fledge; + } + } + + _each(ext, (value, key) => { + data['ext.' + key] = value; + }); + + return data; +} + +export function createInterpretResponseFn(bidderCode) { + return function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + + const singleRequestMode = config.getConfig(`${bidderCode}.singleRequest`); + const reqBidId = deepAccess(request, 'data.bidId'); + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach((result, i) => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + bidId, + nurl, + advertiserDomains, + metaData, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: (singleRequestMode && bidId) ? bidId : reqBidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (nurl) { + response.nurl = nurl; + } + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + + return output; + } catch (e) { + return []; + } + } +} + +export function createBuildRequestsFn(createRequestDomain, createUniqueRequestData, webSessionId, storage, bidderCode, bidderVersion) { + function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const {params} = bid; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, webSessionId, storage, bidderVersion, bidderCode, createUniqueRequestData); + const dto = { + method: 'POST', url: `${createRequestDomain(subDomain)}/prebid/multi/${cId}`, data: data + }; + return dto; + } + + function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { + const {params} = bidRequests[0]; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = bidRequests.map(bid => { + const sizes = parseSizesInput(bid.sizes); + return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, webSessionId, storage, bidderVersion, bidderCode, createUniqueRequestData) + }); + const chunkSize = Math.min(20, config.getConfig(`${bidderCode}.chunkSize`) || 10); + + const chunkedData = chunk(data, chunkSize); + return chunkedData.map(chunk => { + return { + method: 'POST', + url: `${createRequestDomain(subDomain)}/prebid/multi/${cId}`, + data: { + bids: chunk + } + }; + }); + } + + return function buildRequests(validBidRequests, bidderRequest) { + // TODO: does the fallback make sense here? + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + + const singleRequestMode = config.getConfig('vidazoo.singleRequest'); + + const requests = []; + + if (singleRequestMode) { + // banner bids are sent as a single request + const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); + if (bannerBidRequests.length > 0) { + const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); + requests.push(...singleRequests); + } + + // video bids are sent as a single request for each bid + + const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); + videoBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } else { + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } + return requests; + } +} diff --git a/libraries/vidazooUtils/constants.js b/libraries/vidazooUtils/constants.js new file mode 100644 index 00000000000..b1056c15899 --- /dev/null +++ b/libraries/vidazooUtils/constants.js @@ -0,0 +1,7 @@ +export const CURRENCY = 'USD'; +export const TTL_SECONDS = 60 * 5; +export const DEAL_ID_EXPIRY = 1000 * 60 * 15; +export const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; +export const SESSION_ID_KEY = 'vidSid'; +export const OPT_CACHE_KEY = 'vdzwopt'; +export const OPT_TIME_KEY = 'vdzHum'; diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js index bee32a19870..8a5f3c0ed56 100644 --- a/modules/twistDigitalBidAdapter.js +++ b/modules/twistDigitalBidAdapter.js @@ -15,6 +15,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; import {config} from '../src/config.js'; import {chunk} from '../libraries/chunk/chunk.js'; +import {extractCID, extractPID, extractSubDomain} from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1292; const DEFAULT_SUB_DOMAIN = 'exchange'; @@ -40,18 +41,6 @@ export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.twist.win`; } -export function extractCID(params) { - return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; -} - -export function extractPID(params) { - return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; -} - -export function extractSubDomain(params) { - return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; -} - function isBidRequestValid(bid) { const params = bid.params || {}; return !!(extractCID(params) && extractPID(params)); diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index fd53b684ec0..21ba444c69c 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,488 +1,46 @@ -import { - _each, - deepAccess, - isFn, - parseSizesInput, - parseUrl, - uniques, - isArray, - formatQS, - triggerPixel -} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {bidderSettings} from '../src/bidderSettings.js'; -import {config} from '../src/config.js'; -import {chunk} from '../libraries/chunk/chunk.js'; +import { + createSessionId, + isBidRequestValid, + getCacheOpt, + getNextDealId, + onBidWon, + createUserSyncGetter, + getVidazooSessionId, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; +import {OPT_CACHE_KEY, OPT_TIME_KEY} from '../libraries/vidazooUtils/constants.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; const BIDDER_CODE = 'vidazoo'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const DEAL_ID_EXPIRY = 1000 * 60 * 15; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; -const SESSION_ID_KEY = 'vidSid'; -const OPT_CACHE_KEY = 'vdzwopt'; -export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const webSessionId = createSessionId(); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.cootlogix.com`; } -export function extractCID(params) { - return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; -} - -export function extractPID(params) { - return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; -} - -export function extractSubDomain(params) { - return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - const {ext} = params; - let {bidFloor} = params; - const hashUrl = hashCode(topWindowUrl); - const dealId = getNextDealId(hashUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const sId = getVidazooSessionId(); - const pId = extractPID(params); - const ptrace = getCacheOpt(); - const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); - const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); - const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); - const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); - const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sessionId: sId, - sizes: sizes, - dealId: dealId, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - ptrace: ptrace, - isStorageAllowed: isStorageAllowed, - gpid: gpid, - cat: cat, - contentData, - userData: userData, - pagecat: pagecat, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout, - webSessionId: webSessionId - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (bidderRequest.paapi?.enabled) { - const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); - if (fledge) { - data.fledge = fledge; - } - } - - _each(ext, (value, key) => { - data['ext.' + key] = value; - }); - - return data; -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const {params} = bid; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout); - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - return dto; -} - -function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { - const {params} = bidRequests[0]; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = bidRequests.map(bid => { - const sizes = parseSizesInput(bid.sizes); - return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) - }); - const chunkSize = Math.min(20, config.getConfig('vidazoo.chunkSize') || 10); - - const chunkedData = chunk(data, chunkSize); - return chunkedData.map(chunk => { - return { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: { - bids: chunk - } - }; - }); -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - switch (idSystemProviderName) { - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - // TODO: does the fallback make sense here? - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - - const singleRequestMode = config.getConfig('vidazoo.singleRequest'); - - const requests = []; - - if (singleRequestMode) { - // banner bids are sent as a single request - const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); - if (bannerBidRequests.length > 0) { - const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); - requests.push(...singleRequests); - } - - // video bids are sent as a single request for each bid - - const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); - videoBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } else { - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - - const singleRequestMode = config.getConfig('vidazoo.singleRequest'); - const reqBidId = deepAccess(request, 'data.bidId'); - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach((result, i) => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - bidId, - nurl, - advertiserDomains, - metaData, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: (singleRequestMode && bidId) ? bidId : reqBidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (nurl) { - response.nurl = nurl; - } - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; +function createUniqueRequestData(hashUrl) { + const dealId = getNextDealId(storage, hashUrl); + const sessionId = getVidazooSessionId(storage); + const ptrace = getCacheOpt(storage, OPT_CACHE_KEY); + const vdzhum = getCacheOpt(storage, OPT_TIME_KEY); - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.cootlogix.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.cootlogix.com/api/sync/image/${params}` - }); - } - return syncs; -} - -/** - * @param {Bid} bid - */ -function onBidWon(bid) { - if (!bid.nurl) { - return; - } - const wonBid = { - adId: bid.adId, - creativeId: bid.creativeId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - adUnitCode: bid.adUnitCode, - cpm: bid.cpm, - currency: bid.currency, - originalCpm: bid.originalCpm, - originalCurrency: bid.originalCurrency, - netRevenue: bid.netRevenue, - mediaType: bid.mediaType, - timeToRespond: bid.timeToRespond, - status: bid.status, + return { + dealId: dealId, sessionId: sessionId, ptrace: ptrace, vdzhum: vdzhum }; - const qs = formatQS(wonBid); - const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; - triggerPixel(url); -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; } -export function getNextDealId(key, expiry = DEAL_ID_EXPIRY) { - try { - const data = getStorageItem(key); - let currentValue = 0; - let timestamp; - - if (data && data.value && Date.now() - data.created < expiry) { - currentValue = data.value; - timestamp = data.created; - } - - const nextValue = currentValue + 1; - setStorageItem(key, nextValue, timestamp); - return nextValue; - } catch (e) { - return 0; - } -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getVidazooSessionId() { - return getStorageItem(SESSION_ID_KEY) || ''; -} - -export function getCacheOpt() { - let data = storage.getDataFromLocalStorage(OPT_CACHE_KEY); - if (!data) { - data = String(Date.now()); - storage.setDataInLocalStorage(OPT_CACHE_KEY, data); - } - - return data; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, webSessionId, storage, BIDDER_CODE, BIDDER_VERSION); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.cootlogix.com/api/sync/iframe', imageSyncUrl: 'https://sync.cootlogix.com/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 170ff51c6fd..1a73d2f6897 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -3,9 +3,6 @@ import { spec as adapter, createDomain, hashCode, - extractPID, - extractCID, - extractSubDomain, getStorageItem, setStorageItem, tryParseJSON, @@ -18,6 +15,11 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; import {deepSetValue} from 'src/utils.js'; +import { + extractPID, + extractCID, + extractSubDomain +} from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index ad5a298bb8e..c5da6eebdd9 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,7 +1,11 @@ import {expect} from 'chai'; import { spec as adapter, + storage, createDomain, + webSessionId +} from 'modules/vidazooBidAdapter.js'; +import { hashCode, extractPID, extractCID, @@ -11,9 +15,9 @@ import { tryParseJSON, getUniqueDealId, getNextDealId, - getVidazooSessionId, - webSessionId -} from 'modules/vidazooBidAdapter.js'; + getTopWindowQueryParams, + getVidazooSessionId +} from 'libraries/vidazooUtils/bidderUtils.js' import * as utils from 'src/utils.js'; import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; @@ -205,15 +209,6 @@ const REQUEST = { } }; -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - describe('VidazooBidAdapter', function () { describe('validtae spec', function () { it('exists and is a function', function () { @@ -317,6 +312,7 @@ describe('VidazooBidAdapter', function () { gpid: '', prebidVersion: version, ptrace: '1000', + vdzhum: '1000', publisherId: '59ac17c192832d0011283fe3', url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -436,6 +432,7 @@ describe('VidazooBidAdapter', function () { prebidVersion: version, schain: BID.schain, ptrace: '1000', + vdzhum: '1000', res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), @@ -526,6 +523,7 @@ describe('VidazooBidAdapter', function () { prebidVersion: version, schain: BID.schain, ptrace: '1000', + vdzhum: '1000', res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), @@ -800,14 +798,14 @@ describe('VidazooBidAdapter', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; }); it('should get undefined vidazoo session id', function () { - const sessionId = getVidazooSessionId(); + const sessionId = getVidazooSessionId(storage); expect(sessionId).to.be.empty; }); it('should get vidazoo session id from storage', function () { const vidSid = '1234-5678'; window.localStorage.setItem('vidSid', vidSid); - const sessionId = getVidazooSessionId(); + const sessionId = getVidazooSessionId(storage); expect(sessionId).to.be.equal(vidSid); }); }); @@ -826,15 +824,15 @@ describe('VidazooBidAdapter', function () { const key = 'myDealKey'; it('should get the next deal id', function () { - const dealId = getNextDealId(key); - const nextDealId = getNextDealId(key); + const dealId = getNextDealId(storage, key); + const nextDealId = getNextDealId(storage, key); expect(dealId).to.be.equal(1); expect(nextDealId).to.be.equal(2); }); it('should get the first deal id on expiration', function (done) { setTimeout(function () { - const dealId = getNextDealId(key, 100); + const dealId = getNextDealId(storage, key, 100); expect(dealId).to.be.equal(1); done(); }, 200); @@ -855,13 +853,13 @@ describe('VidazooBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -869,7 +867,7 @@ describe('VidazooBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -893,8 +891,8 @@ describe('VidazooBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -905,7 +903,7 @@ describe('VidazooBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); From 1131a90ea496d5de75af3027cf6c134301bec049 Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:15:18 +0300 Subject: [PATCH 0257/1097] SmartyadsBidAdapter remove usprivacy (#11829) * SmartyadsBidAdapter remove usprivacy * refactor * refactor * remove unusable code * try to fix duplicated code * try to fix duplicated code * smallfixes * smallfixes * smallfix * tests fix * revert fixes * remove language * move getAdUrlByRegion to libs * refactor * undo creative functions --------- Co-authored-by: vrishko --- libraries/smartyadsUtils/getAdUrlByRegion.js | 32 +++++ modules/smartyadsBidAdapter.js | 116 +----------------- test/spec/modules/smartyadsBidAdapter_spec.js | 89 +------------- 3 files changed, 44 insertions(+), 193 deletions(-) create mode 100644 libraries/smartyadsUtils/getAdUrlByRegion.js diff --git a/libraries/smartyadsUtils/getAdUrlByRegion.js b/libraries/smartyadsUtils/getAdUrlByRegion.js new file mode 100644 index 00000000000..cad9055f671 --- /dev/null +++ b/libraries/smartyadsUtils/getAdUrlByRegion.js @@ -0,0 +1,32 @@ +const adUrls = { + US_EAST: 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + EU: 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + SGP: 'https://n6.smartyads.com/?c=o&m=prebid&secret_key=prebid_js' +}; + +export function getAdUrlByRegion(bid) { + let adUrl; + + if (bid.params.region && adUrls[bid.params.region]) { + adUrl = adUrls[bid.params.region]; + } else { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + + switch (region) { + case 'Europe': + adUrl = adUrls['EU']; + break; + case 'Asia': + adUrl = adUrls['SGP']; + break; + default: adUrl = adUrls['US_EAST']; + } + } catch (err) { + adUrl = adUrls['US_EAST']; + } + } + + return adUrl; +}; diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 9098bb8f862..de7c61b7163 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,63 +1,15 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { ajax } from '../src/ajax.js'; +import { getAdUrlByRegion } from '../libraries/smartyadsUtils/getAdUrlByRegion.js'; +import { interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'smartyads'; const GVLID = 534; -const adUrls = { - US_EAST: 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', - EU: 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', - SGP: 'https://n6.smartyads.com/?c=o&m=prebid&secret_key=prebid_js' -} const URL_SYNC = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid['mediaType']) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl) || Boolean(bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} - -function getAdUrlByRegion(bid) { - let adUrl; - - if (bid.params.region && adUrls[bid.params.region]) { - adUrl = adUrls[bid.params.region]; - } else { - try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; - - switch (region) { - case 'Europe': - adUrl = adUrls['EU']; - break; - case 'Asia': - adUrl = adUrls['SGP']; - break; - default: adUrl = adUrls['US_EAST']; - } - } catch (err) { - adUrl = adUrls['US_EAST']; - } - } - - return adUrl; -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -78,8 +30,6 @@ export const spec = { let request = { 'deviceWidth': winTop.screen.width, 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': 1, 'host': location?.domain ?? '', 'page': location?.page ?? '', 'coppa': config.getConfig('coppa') === true ? 1 : 0, @@ -87,11 +37,8 @@ export const spec = { 'eeid': validBidRequests[0]?.userIdAsEids, 'ifa': bidderRequest?.ortb2?.device?.ifa, }; - request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) + if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } if (bidderRequest.gdprConsent) { request.gdpr = bidderRequest.gdprConsent } @@ -128,59 +75,8 @@ export const spec = { } }, - interpretResponse: (serverResponse) => { - let response = []; - serverResponse = serverResponse.body; - for (let i = 0; i < serverResponse.length; i++) { - let resItem = serverResponse[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '', gppConsent = '') => { - let syncs = []; - let { gdprApplies, consentString = '' } = gdprConsent; - - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&type=iframe&us_privacy=${uspConsent}&gpp=${gppConsent}` - }); - } else { - syncs.push({ - type: 'image', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&type=image&us_privacy=${uspConsent}&gpp=${gppConsent}` - }); - } - - return syncs - }, - - onBidWon: function(bid) { - if (bid.winUrl) { - ajax(bid.winUrl, () => {}, JSON.stringify(bid)); - } else { - if (bid?.postData && bid?.postData[0] && bid?.postData[0].params && bid?.postData[0].params[0].host == 'prebid') { - ajax('https://et-nd43.itdsmr.com/?c=o&m=prebid&secret_key=prebid_js&winTest=1', () => {}, JSON.stringify(bid)); - } - } - }, - - onTimeout: function(bid) { - if (bid?.postData && bid?.postData[0] && bid?.postData[0].params && bid?.postData[0].params[0].host == 'prebid') { - ajax('https://et-nd43.itdsmr.com/?c=o&m=prebid&secret_key=prebid_js&bidTimeout=1', () => {}, JSON.stringify(bid)); - } - }, - - onBidderError: function(bid) { - if (bid?.postData && bid?.postData[0] && bid?.postData[0].params && bid?.postData[0].params[0].host == 'prebid') { - ajax('https://et-nd43.itdsmr.com/?c=o&m=prebid&secret_key=prebid_js&bidderError=1', () => {}, JSON.stringify(bid)); - } - }, - + interpretResponse, + getUserSyncs: getUserSyncs(URL_SYNC), }; registerBidder(spec); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 1b592e142c3..65480ee11e6 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -61,12 +61,10 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa', 'eeid', 'ifa'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'host', 'page', 'placements', 'coppa', 'eeid', 'ifa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.coppa).to.be.a('number'); - expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); let placement = data['placements'][0]; @@ -126,8 +124,8 @@ describe('SmartyadsAdapter', function () { expect(dataItem.width).to.equal(300); expect(dataItem.height).to.equal(250); expect(dataItem.ad).to.equal('Test'); - expect(dataItem.meta).to.have.property('advertiserDomains') - expect(dataItem.meta.advertiserDomains).to.deep.equal(['example.com']); + expect(dataItem.meta).to.have.property('advertiserDomains'); + expect(dataItem.meta.advertiserDomains).to.be.an('array'); expect(dataItem.ttl).to.equal(120); expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; @@ -152,7 +150,7 @@ describe('SmartyadsAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -183,7 +181,7 @@ describe('SmartyadsAdapter', function () { expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -263,7 +261,7 @@ describe('SmartyadsAdapter', function () { }); }); describe('getUserSyncs', function () { - const syncUrl = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a&gdpr=0&gdpr_consent=&type=iframe&us_privacy=&gpp='; + const syncUrl = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a/iframe?pbjs=1&coppa=0'; const syncOptions = { iframeEnabled: true }; @@ -277,79 +275,4 @@ describe('SmartyadsAdapter', function () { ]); }); }); - - describe('onBidWon', function () { - it('should exists', function () { - expect(spec.onBidWon).to.exist.and.to.be.a('function'); - }); - - it('should send a valid bid won notice', function () { - const bid = { - 'c': 'o', - 'm': 'prebid', - 'secret_key': 'prebid_js', - 'winTest': '1', - 'postData': [{ - 'bidder': 'smartyads', - 'params': [ - {'host': 'prebid', - 'accountid': '123', - 'sourceid': '12345' - }] - }] - }; - spec.onBidWon(bid); - expect(server.requests.length).to.equal(1); - }); - }); - - describe('onTimeout', function () { - it('should exists', function () { - expect(spec.onTimeout).to.exist.and.to.be.a('function'); - }); - - it('should send a valid bid timeout notice', function () { - const bid = { - 'c': 'o', - 'm': 'prebid', - 'secret_key': 'prebid_js', - 'bidTimeout': '1', - 'postData': [{ - 'bidder': 'smartyads', - 'params': [ - {'host': 'prebid', - 'accountid': '123', - 'sourceid': '12345' - }] - }] - }; - spec.onTimeout(bid); - expect(server.requests.length).to.equal(1); - }); - }); - - describe('onBidderError', function () { - it('should exists', function () { - expect(spec.onBidderError).to.exist.and.to.be.a('function'); - }); - - it('should send a valid bidder error notice', function () { - const bid = { - 'c': 'o', - 'm': 'prebid', - 'secret_key': 'prebid_js', - 'bidderError': '1', - 'postData': [{ - 'bidder': 'smartyads', - 'params': [ - {'host': 'prebid', - 'accountid': '123', - 'sourceid': '12345' - }] - }] - }; - spec.onBidderError(bid); - expect(server.requests.length).to.equal(1); - }); - }); }); From a3455e5863944c6e9d0795acdd0b11a8cb7f044b Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Thu, 27 Jun 2024 01:38:00 +1000 Subject: [PATCH 0258/1097] Adds advertiserTransparency as an option. (#11866) --- modules/adnuntiusBidAdapter.js | 1 + test/spec/modules/adnuntiusBidAdapter_spec.js | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 52c5499b0b8..c9275e6bd0b 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -252,6 +252,7 @@ export const spec = { const bidderConfig = config.getConfig(); if (bidderConfig.useCookie === false) queryParamsAndValues.push('noCookies=true'); + if (bidderConfig.advertiserTransparency === true) queryParamsAndValues.push('advertiserTransparency=true'); if (bidderConfig.maxDeals > 0) queryParamsAndValues.push('ds=' + Math.min(bidderConfig.maxDeals, MAXIMUM_DEALS_LIMIT)); const bidRequests = {}; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index d0d16d0f399..cef63486420 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -876,14 +876,15 @@ describe('adnuntiusBidAdapter', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { - useCookie: false + useCookie: false, + advertiserTransparency: true } }); const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE); + expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE + '&advertiserTransparency=true'); }); }); @@ -981,7 +982,9 @@ describe('adnuntiusBidAdapter', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { - maxDeals: 2 + maxDeals: 2, + useCookie: 'ignore-this', + advertiserTransparency: 'ignore-this-as-well' } }); From 281b99fcb9715e8bc8f269eaf1af61f1b7645080 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 26 Jun 2024 12:47:47 -0400 Subject: [PATCH 0259/1097] Update liveIntentIdSystem.js (#11873) --- modules/liveIntentIdSystem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 6925f5fd4a0..a9708910ca7 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -20,7 +20,7 @@ import { getRefererInfo } from '../src/refererDetection.js'; * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ - +const GVLID = 148; const DEFAULT_AJAX_TIMEOUT = 5000; const EVENTS_TOPIC = 'pre_lips'; const MODULE_NAME = 'liveIntentId'; @@ -185,7 +185,7 @@ export const liveIntentIdSubmodule = { * @type {string} */ name: MODULE_NAME, - + gvlid: GVLID, setModuleMode(mode) { this.moduleMode = mode; }, From ff488d93fbd25a0ac832192772db0670f0ea9937 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 26 Jun 2024 13:05:43 -0400 Subject: [PATCH 0260/1097] yahooAdsBidAdapter.js: add gvl to alias (#11871) * Update yahooAdsBidAdapter.js: add gvl to alias add gvlid for aliass * Update yahooAdsBidAdapter.js * Update yahooAdsBidAdapter.js * Update yahooAdsBidAdapter.js * Update yahooAdsBidAdapter_spec.js * Update yahooAdsBidAdapter_spec.js --- modules/yahooAdsBidAdapter.js | 5 ++++- test/spec/modules/yahooAdsBidAdapter_spec.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/yahooAdsBidAdapter.js b/modules/yahooAdsBidAdapter.js index c25fb677cda..f50aa9cf002 100644 --- a/modules/yahooAdsBidAdapter.js +++ b/modules/yahooAdsBidAdapter.js @@ -7,8 +7,11 @@ import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const INTEGRATION_METHOD = 'prebid.js'; const BIDDER_CODE = 'yahooAds'; -const BIDDER_ALIASES = ['yahoossp', 'yahooAdvertising'] const GVLID = 25; +const BIDDER_ALIASES = [ + { code: 'yahoossp', gvlid: GVLID }, + { code: 'yahooAdvertising', gvlid: GVLID } +]; const ADAPTER_VERSION = '1.1.0'; const PREBID_VERSION = '$prebid.version$'; const DEFAULT_BID_TTL = 300; diff --git a/test/spec/modules/yahooAdsBidAdapter_spec.js b/test/spec/modules/yahooAdsBidAdapter_spec.js index b1d590cb806..aaa7e2da8ee 100644 --- a/test/spec/modules/yahooAdsBidAdapter_spec.js +++ b/test/spec/modules/yahooAdsBidAdapter_spec.js @@ -193,7 +193,7 @@ describe('Yahoo Advertising Bid Adapter:', () => { }); it('should define the correct bidder aliases', () => { - expect(spec.aliases).to.deep.equal(['yahoossp', 'yahooAdvertising']); + expect(spec.aliases).to.deep.equal([{ 'code': 'yahoossp', 'gvlid': 25 }, { 'code': 'yahooAdvertising', 'gvlid': 25 }]); }); it('should define the correct vendor ID', () => { From ad2af8f06a144efc483300fb4a2b2e448357c586 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 26 Jun 2024 10:16:10 -0700 Subject: [PATCH 0261/1097] userID: better config validation (#11872) --- modules/userId/index.js | 52 +++++++----- test/spec/modules/userId_spec.js | 133 ++++++++++++++++++++----------- 2 files changed, 117 insertions(+), 68 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 2970bad296d..b8d013df1c6 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -131,7 +131,7 @@ import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; -import { EVENTS } from '../../src/constants.js'; +import {EVENTS} from '../../src/constants.js'; import {module, ready as hooksReady} from '../../src/hook.js'; import {buildEidPermissions, createEidsArray, EID_CONFIG} from './eids.js'; import { @@ -147,7 +147,6 @@ import { getPrebidInternal, isArray, isEmpty, - isEmptyStr, isFn, isGptPubadsDefined, isNumber, @@ -951,29 +950,40 @@ function hasValidStorageTypes(config) { * @param {SubmoduleConfig[]} configRegistry * @returns {SubmoduleConfig[]} */ -function getValidSubmoduleConfigs(configRegistry) { +export function getValidSubmoduleConfigs(configRegistry) { + function err(msg, ...args) { + logWarn(`Invalid userSync.userId config: ${msg}`, ...args) + } if (!Array.isArray(configRegistry)) { + if (configRegistry != null) { + err('must be an array', configRegistry); + } return []; } - return configRegistry.reduce((carry, config) => { - // every submodule config obj must contain a valid 'name' - if (!config || isEmptyStr(config.name)) { - return carry; - } - // Validate storage config contains 'type' and 'name' properties with non-empty string values - // 'type' must be one of html5, cookies - if (config.storage && - !isEmptyStr(config.storage.type) && - !isEmptyStr(config.storage.name) && - hasValidStorageTypes(config)) { - carry.push(config); - } else if (isPlainObject(config.value)) { - carry.push(config); - } else if (!config.storage && !config.value) { - carry.push(config); + return configRegistry.filter(config => { + if (!config?.name) { + return err('must specify "name"', config); + } else if (config.storage) { + if (!config.storage.name || !config.storage.type) { + return err('must specify "storage.name" and "storage.type"', config); + } else if (!hasValidStorageTypes(config)) { + return err('invalid "storage.type"', config) + } + ['expires', 'refreshInSeconds'].forEach(param => { + let value = config.storage[param]; + if (value != null && typeof value !== 'number') { + value = Number(value) + if (isNaN(value)) { + err(`storage.${param} must be a number and will be ignored`, config); + delete config.storage[param]; + } else { + config.storage[param] = value; + } + } + }); } - return carry; - }, []); + return true; + }) } const ALL_STORAGE_TYPES = new Set([LOCAL_STORAGE, COOKIE]); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index a43586605c9..2f28df47667 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -4,7 +4,7 @@ import { coreStorage, dep, findRootDomain, - getConsentHash, + getConsentHash, getValidSubmoduleConfigs, init, PBJS_USER_ID_OPTOUT_NAME, requestBidsHook, @@ -190,6 +190,65 @@ describe('User ID', function () { }) }) + describe('userId config validation', () => { + beforeEach(() => { + sandbox.stub(utils, 'logWarn'); + }); + + function mockConfig(storageConfig = {}) { + return { + name: 'mockModule', + storage: { + name: 'mockStorage', + type: 'cookie', + ...storageConfig + } + } + } + + Object.entries({ + 'not an object': 'garbage', + 'missing name': {}, + 'empty name': {name: ''}, + 'empty storage config': {name: 'mockId', storage: {}}, + 'storage type, but no storage name': mockConfig({name: ''}), + 'storage name, but no storage type': mockConfig({type: undefined}), + }).forEach(([t, config]) => { + it(`should log a warning and reject configuration with ${t}`, () => { + expect(getValidSubmoduleConfigs([config]).length).to.equal(0); + sinon.assert.called(utils.logWarn); + }); + }); + + it('should reject non-array userId configuration', () => { + expect(getValidSubmoduleConfigs({})).to.eql([]); + sinon.assert.called(utils.logWarn); + }); + + it('should accept null configuration', () => { + expect(getValidSubmoduleConfigs()).to.eql([]); + sinon.assert.notCalled(utils.logWarn); + }); + + ['refreshInSeconds', 'expires'].forEach(param => { + describe(`${param} parameter`, () => { + it('should be made a number, when possible', () => { + expect(getValidSubmoduleConfigs([mockConfig({[param]: '123'})])[0].storage[param]).to.equal(123); + }); + + it('should log a warning when not a number', () => { + expect(getValidSubmoduleConfigs([mockConfig({[param]: 'garbage'})])[0].storage[param]).to.not.exist; + sinon.assert.called(utils.logWarn) + }); + + it('should be left untouched when not specified', () => { + expect(getValidSubmoduleConfigs([mockConfig()])[0].storage[param]).to.not.exist; + sinon.assert.notCalled(utils.logWarn); + }); + }) + }) + }) + describe('Decorate Ad Units', function () { beforeEach(function () { // reset mockGpt so nothing else interferes @@ -2173,60 +2232,40 @@ describe('User ID', function () { }); }); - describe('Set cookie behavior', function () { - let cookie, cookieStub; - - beforeEach(function () { - setSubmoduleRegistry([sharedIdSystemSubmodule]); - init(config); - cookie = document.cookie; - cookieStub = sinon.stub(document, 'cookie'); - cookieStub.get(() => cookie); - cookieStub.set((val) => cookie = val); - }); - - afterEach(function () { - cookieStub.restore(); - }); - - it('should allow submodules to override the domain', function () { - const submodule = { - submodule: { - domainOverride: function () { - return 'foo.com' - } - }, + describe('Submodule ID storage', () => { + let submodule; + beforeEach(() => { + submodule = { + submodule: {}, config: { name: 'mockId', - storage: { - type: 'cookie' - } }, storageMgr: { - setCookie: sinon.stub() + setCookie: sinon.stub(), + setDataInLocalStorage: sinon.stub() }, - enabledStorageTypes: [ 'cookie' ] + enabledStorageTypes: ['cookie', 'html5'] } - setStoredValue(submodule, 'bar'); - expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal('foo.com'); }); - it('should pass no domain if submodule does not override the domain', function () { - const submodule = { - submodule: {}, - config: { - name: 'mockId', - storage: { - type: 'cookie' - } - }, - storageMgr: { - setCookie: sinon.stub() - }, - enabledStorageTypes: [ 'cookie' ] - } - setStoredValue(submodule, 'bar'); - expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal(null); + describe('Set cookie behavior', function () { + beforeEach(() => { + submodule.config.storage = { + type: 'cookie' + } + }); + it('should allow submodules to override the domain', function () { + submodule.submodule.domainOverride = function() { + return 'foo.com' + } + setStoredValue(submodule, 'bar'); + expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal('foo.com'); + }); + + it('should pass no domain if submodule does not override the domain', function () { + setStoredValue(submodule, 'bar'); + expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal(null); + }); }); }); From 2c8811a3834ee9ca2cb521d5e1418bcc4f87b97f Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Thu, 27 Jun 2024 04:43:20 +0300 Subject: [PATCH 0262/1097] Intent iq id submodule: various enhancements (#11765) * updated flow * remove 'this' * rename privacy parameter * add callback timeout * Extract only used parameters from CryptoJS * add new unit tests * change callback timeout order * added tests and small fixes * change saving logic * support "html5" and "cookie" storage types * support storage type, update flow * add documentation * small updates * more docs * fix unit test * add browserBlackList to documentation --------- Co-authored-by: Eyvaz Ahmadzada --- modules/intentIqIdSystem.js | 458 ++++++++++++++++++--- modules/intentIqIdSystem.md | 92 +++++ test/spec/modules/intentIqIdSystem_spec.js | 300 ++++++++++++-- 3 files changed, 764 insertions(+), 86 deletions(-) create mode 100755 modules/intentIqIdSystem.md diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 109ea5c39c6..434caae7ffb 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -10,6 +10,9 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; +import AES from 'crypto-js/aes.js'; +import Utf8 from 'crypto-js/enc-utf8.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -21,11 +24,31 @@ const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; -export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; - -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +export let FIRST_PARTY_DATA_KEY = '_iiq_fdata'; +export const WITH_IIQ = 'A'; +export const WITHOUT_IIQ = 'B'; +export const NOT_YET_DEFINED = 'U'; +export const OPT_OUT = 'O'; +export const BLACK_LIST = 'L'; +export const CLIENT_HINTS_KEY = '_iiq_ch'; +export const EMPTY = 'EMPTY' +export const VERSION = 0.1 +const encoderCH = { + brands: 0, + mobile: 1, + platform: 2, + architecture: 3, + bitness: 4, + model: 5, + platformVersion: 6, + wow64: 7, + fullVersionList: 8 +}; const INVALID_ID = 'INVALID_ID'; +const SUPPORTED_TYPES = ['html5', 'cookie'] + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); /** * Generate standard UUID string @@ -42,16 +65,35 @@ function generateGUID() { } /** - * Read Intent IQ data from cookie or local storage + * Encrypts plaintext. + * @param {string} plainText The plaintext to encrypt. + * @returns {string} The encrypted text as a base64 string. + */ +export function encryptData(plainText) { + return AES.encrypt(plainText, MODULE_NAME).toString(); +} + +/** + * Decrypts ciphertext. + * @param {string} encryptedText The encrypted text as a base64 string. + * @returns {string} The decrypted plaintext. + */ +export function decryptData(encryptedText) { + const bytes = AES.decrypt(encryptedText, MODULE_NAME); + return bytes.toString(Utf8); +} + +/** + * Read Intent IQ data from local storage or cookie * @param key * @return {string} */ -export function readData(key) { +export function readData(key, allowedStorage) { try { - if (storage.hasLocalStorage()) { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { return storage.getDataFromLocalStorage(key); } - if (storage.cookiesAreEnabled()) { + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { return storage.getCookie(key); } } catch (error) { @@ -60,21 +102,20 @@ export function readData(key) { } /** - * Store Intent IQ data in either cookie or local storage + * Store Intent IQ data in cookie, local storage or both of them * expiration date: 365 days * @param key * @param {string} value IntentIQ ID value to sintentIqIdSystem_spec.jstore */ -function storeData(key, value, cookieStorageEnabled = false) { +export function storeData(key, value, allowedStorage) { try { logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); - if (value) { - if (storage.hasLocalStorage()) { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { storage.setDataInLocalStorage(key, value); } - const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - if (storage.cookiesAreEnabled() && cookieStorageEnabled) { + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); storage.setCookie(key, value, expiresStr, 'LAX'); } } @@ -83,10 +124,28 @@ function storeData(key, value, cookieStorageEnabled = false) { } } +/** + * Remove Intent IQ data from cookie or local storage + * @param key + */ + +export function removeDataByKey(key, allowedStorage) { + try { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { + storage.removeDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(key, '', expiredDate, 'LAX'); + } + } catch (error) { + logError(error); + } +} + /** * Parse json if possible, else return null * @param data - * @param {object|null} */ function tryParse(data) { try { @@ -97,6 +156,125 @@ function tryParse(data) { } } +/** + * Convert GPP data to an object + * @param {Object} data + * @return {string} The JSON string representation of the input data. + */ +export function handleGPPData(data = {}) { + if (Array.isArray(data)) { + let obj = {}; + for (const element of data) { + obj = Object.assign(obj, element); + } + return JSON.stringify(obj); + } + return JSON.stringify(data); +} + +/** + * Detects the browser using either userAgent or userAgentData + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +function detectBrowser() { + try { + if (navigator.userAgent) { + return detectBrowserFromUserAgent(navigator.userAgent); + } else if (navigator.userAgentData) { + return detectBrowserFromUserAgentData(navigator.userAgentData); + } + } catch (error) { + logError('Error detecting browser:', error); + } + return 'unknown'; +} + +/** + * Detects the browser from the user agent string + * @param {string} userAgent - The user agent string from the browser + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowserFromUserAgent(userAgent) { + const browserRegexPatterns = { + opera: /Opera|OPR/, + edge: /Edg/, + chrome: /Chrome|CriOS/, + safari: /Safari/, + firefox: /Firefox/, + ie: /MSIE|Trident/, + }; + + // Check for Chrome first to avoid confusion with Safari + if (browserRegexPatterns.chrome.test(userAgent)) { + return 'chrome'; + } + + // Now we can safely check for Safari + if (browserRegexPatterns.safari.test(userAgent) && !browserRegexPatterns.chrome.test(userAgent)) { + return 'safari'; + } + + // Check other browsers + for (const browser in browserRegexPatterns) { + if (browserRegexPatterns[browser].test(userAgent)) { + return browser; + } + } + + return 'unknown'; +} + +/** + * Detects the browser from the NavigatorUAData object + * @param {NavigatorUAData} userAgentData - The user agent data object from the browser + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowserFromUserAgentData(userAgentData) { + const brandNames = userAgentData.brands.map(brand => brand.brand); + + if (brandNames.includes('Microsoft Edge')) { + return 'edge'; + } else if (brandNames.includes('Opera')) { + return 'opera'; + } else if (brandNames.some(brand => brand === 'Chromium' || brand === 'Google Chrome')) { + return 'chrome'; + } + + return 'unknown'; +} + +/** + * Processes raw client hints data into a structured format. + * @param {object} clientHints - Raw client hints data + * @return {string} A JSON string of processed client hints or an empty string if no hints + */ +export function handleClientHints(clientHints) { + const chParams = {}; + for (const key in clientHints) { + if (clientHints.hasOwnProperty(key) && clientHints[key] !== '') { + if (['brands', 'fullVersionList'].includes(key)) { + let handledParam = ''; + clientHints[key].forEach((element, index) => { + const isNotLast = index < clientHints[key].length - 1; + handledParam += `"${element.brand}";v="${element.version}"${isNotLast ? ', ' : ''}`; + }); + chParams[encoderCH[key]] = handledParam; + } else if (typeof clientHints[key] === 'boolean') { + chParams[encoderCH[key]] = `?${clientHints[key] ? 1 : 0}`; + } else { + chParams[encoderCH[key]] = `"${clientHints[key]}"`; + } + } + } + return Object.keys(chParams).length ? JSON.stringify(chParams) : ''; +} + +function defineStorageType(params) { + if (!params || !Array.isArray(params)) return ['html5']; // use locale storage be default + const filteredArr = params.filter(item => SUPPORTED_TYPES.includes(item)); + return filteredArr.length ? filteredArr : ['html5']; +} + /** @type {Submodule} */ export const intentIqIdSubmodule = { /** @@ -120,25 +298,136 @@ export const intentIqIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config) { - const configParams = (config && config.params) || {}; - if (!configParams || typeof configParams.partner !== 'number') { + const configParams = (config?.params) || {}; + let decryptedData, callbackTimeoutID; + let callbackFired = false + let runtimeEids = {} + + const firePartnerCallback = () => { + if (configParams.callback && !callbackFired) { + callbackFired = true; + if (callbackTimeoutID) clearTimeout(callbackTimeoutID); + configParams.callback(runtimeEids, firstPartyData?.group || NOT_YET_DEFINED); + } + } + + callbackTimeoutID = setTimeout(() => { + firePartnerCallback(); + }, configParams.timeoutInMillis || 500 + ); + + if (typeof configParams.partner !== 'number') { logError('User ID - intentIqId submodule requires a valid partner to be defined'); + firePartnerCallback() return; } - const cookieStorageEnabled = typeof configParams.enableCookieStorage === 'boolean' ? configParams.enableCookieStorage : false - if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner; } let rrttStrtTime = 0; + let partnerData = {}; + let shouldCallServer = false + + const currentBrowserLowerCase = detectBrowser(); + const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; + const allowedStorage = defineStorageType(config.enabledStorageTypes); + + // Check if current browser is in blacklist + if (browserBlackList?.includes(currentBrowserLowerCase)) { + logError('User ID - intentIqId submodule: browser is in blacklist!'); + if (configParams.callback) configParams.callback('', BLACK_LIST); + return; + } + + // Get consent information + const cmpData = {}; + const uspData = uspDataHandler.getConsentData(); + const gppData = gppDataHandler.getConsentData(); + + if (uspData) { + cmpData.us_privacy = uspData; + } + + if (gppData) { + cmpData.gpp = ''; + cmpData.gpi = 1; + + if (gppData.parsedSections && 'usnat' in gppData.parsedSections) { + cmpData.gpp = handleGPPData(gppData.parsedSections['usnat']); + cmpData.gpi = 0 + } + cmpData.gpp_sid = gppData.applicableSections; + } + + // Read client hints from storage + let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); + + // Get client hints and save to storage + if (navigator.userAgentData) { + navigator.userAgentData + .getHighEntropyValues([ + 'brands', + 'mobile', + 'bitness', + 'wow64', + 'architecture', + 'model', + 'platform', + 'platformVersion', + 'fullVersionList' + ]) + .then(ch => { + clientHints = handleClientHints(ch); + storeData(CLIENT_HINTS_KEY, clientHints, allowedStorage) + }); + } + + if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { + FIRST_PARTY_DATA_KEY += '_' + configParams.partner; + } // Read Intent IQ 1st party id or generate it if none exists - let firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); - if (!firstPartyData || !firstPartyData.pcid || firstPartyData.pcidDate) { + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); + + if (!firstPartyData?.pcid) { const firstPartyId = generateGUID(); - firstPartyData = { 'pcid': firstPartyId, 'pcidDate': Date.now() }; - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), cookieStorageEnabled); + firstPartyData = { pcid: firstPartyId, pcidDate: Date.now(), group: NOT_YET_DEFINED, cttl: 0, uspapi_value: EMPTY, gpp_value: EMPTY, date: Date.now() }; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + } else if (!firstPartyData.pcidDate) { + firstPartyData.pcidDate = Date.now(); + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + } + + const savedData = tryParse(readData(FIRST_PARTY_DATA_KEY, allowedStorage)) + if (savedData) { + partnerData = savedData; + } + + if (partnerData.data) { + if (partnerData.data.length) { // encrypted data + decryptedData = tryParse(decryptData(partnerData.data)); + runtimeEids = decryptedData; + } } - let partnerData = tryParse(readData(FIRST_PARTY_DATA_KEY)); - if (!partnerData) partnerData = {}; + if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_value !== cmpData.gpp) { + firstPartyData.uspapi_value = cmpData.us_privacy; + firstPartyData.gpp_value = cmpData.gpp; + firstPartyData.isOptedOut = false + firstPartyData.cttl = 0 + shouldCallServer = true; + partnerData.data = {} + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + } else if (firstPartyData.isOptedOut) { + firePartnerCallback() + } + + if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids && Object.keys(runtimeEids).length)) { + firePartnerCallback() + } + + if (!shouldCallServer) { + firePartnerCallback(); + return { id: runtimeEids }; + } // use protocol relative urls for http or https let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; @@ -149,42 +438,97 @@ export const intentIqIdSubmodule = { url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : ''; url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : ''; url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : ''; + url += cmpData.us_privacy ? '&pa=' + encodeURIComponent(cmpData.us_privacy) : ''; + url += cmpData.gpp ? '&gpv=' + encodeURIComponent(cmpData.gpp) : ''; + url += cmpData.gpi ? '&gpi=' + cmpData.gpi : ''; + url += clientHints ? '&uh=' + encodeURIComponent(clientHints) : ''; + url += VERSION ? '&jsver=' + VERSION : ''; + url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : ''; + + const storeFirstPartyData = () => { + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + } const resp = function (callback) { const callbacks = { success: response => { let respJson = tryParse(response); // If response is a valid json and should save is true - if (respJson && respJson.ls) { - // Store pid field if found in response json - let shouldUpdateLs = false; - if ('pid' in respJson) { - firstPartyData.pid = respJson.pid; - shouldUpdateLs = true; + if (respJson) { + partnerData.date = Date.now(); + firstPartyData.date = Date.now(); + const defineEmptyDataAndFireCallback = () => { + respJson.data = partnerData.data = runtimeEids = {}; + removeDataByKey(FIRST_PARTY_DATA_KEY, allowedStorage) + firePartnerCallback() + callback() } + if (callbackTimeoutID) clearTimeout(callbackTimeoutID) if ('cttl' in respJson) { - partnerData.cttl = respJson.cttl; - shouldUpdateLs = true; + firstPartyData.cttl = respJson.cttl; + } else firstPartyData.cttl = 86400000; + + if ('tc' in respJson) { + partnerData.terminationCause = respJson.tc; + if (respJson.tc == 41) { + firstPartyData.group = WITHOUT_IIQ; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + defineEmptyDataAndFireCallback(); + return; + } else { + firstPartyData.group = WITH_IIQ; + } } - // If should save and data is empty, means we should save as INVALID_ID - if (respJson.data == '') { - respJson.data = INVALID_ID; - } else { + if ('isOptedOut' in respJson) { + if (respJson.isOptedOut !== firstPartyData.isOptedOut) { + firstPartyData.isOptedOut = respJson.isOptedOut; + } + if (respJson.isOptedOut === true) { + firstPartyData.group = OPT_OUT; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + defineEmptyDataAndFireCallback() + return; + } + } + if ('pid' in respJson) { + firstPartyData.pid = respJson.pid; + } + if ('ls' in respJson) { + if (respJson.ls === false) { + defineEmptyDataAndFireCallback(); + return + } + // If data is empty, means we should save as INVALID_ID + if (respJson.data == '') { + respJson.data = INVALID_ID; + } else { + // If data is a single string, assume it is an id with source intentiq.com + if (respJson.data && typeof respJson.data === 'string') { + respJson.data = { eids: [respJson.data] } + } + } partnerData.data = respJson.data; - shouldUpdateLs = true; } + if (rrttStrtTime && rrttStrtTime > 0) { partnerData.rrtt = Date.now() - rrttStrtTime; - shouldUpdateLs = true; } - if (shouldUpdateLs === true) { - partnerData.date = Date.now(); - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), cookieStorageEnabled); - storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), cookieStorageEnabled); + + if (respJson.data?.eids) { + runtimeEids = respJson.data.eids + callback(respJson.data.eids); + firePartnerCallback() + const encryptedData = encryptData(JSON.stringify(respJson.data.eids)) + partnerData.data = encryptedData; + } else { + callback(); + firePartnerCallback() } - callback(respJson.data); + storeFirstPartyData(); } else { callback(); + firePartnerCallback() } }, error: error => { @@ -192,18 +536,32 @@ export const intentIqIdSubmodule = { callback(); } }; - if (partnerData.date && partnerData.cttl && partnerData.data && - Date.now() - partnerData.date < partnerData.cttl) { callback(partnerData.data); } else { - rrttStrtTime = Date.now(); - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); - } + + ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); }; - return { callback: resp }; + const respObj = { callback: resp }; + if (runtimeEids?.length) respObj.id = runtimeEids; + return respObj }, eids: { 'intentIqId': { source: 'intentiq.com', - atype: 1 + atype: 1, + getSource: function (data) { + return data.source; + }, + getValue: function (data) { + if (data?.uids?.length) { + return data.uids[0].id + } + return null + }, + getUidExt: function (data) { + if (data?.uids?.length) { + return data.uids[0].ext; + } + return null + } }, } }; diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md new file mode 100755 index 00000000000..74208169eaa --- /dev/null +++ b/modules/intentIqIdSystem.md @@ -0,0 +1,92 @@ +``` +Module Name: IntentIQ Id System +Module Type: Id System +Maintainer: julian@intentiq.com, dmytro.piskun@intentiq.com +usp_supported: true +gpp_sids: usnat +``` + +# Intent IQ Universal ID module + +By leveraging the Intent IQ identity graph, our module helps publishers, SSPs, and DSPs overcome the challenges of monetizing cookie-less inventory and preparing for a future without 3rd-party cookies. Our solution implements 1st-party data clustering and provides Intent IQ person IDs with over 90% coverage and unmatched accuracy in supported countries while remaining privacy-friendly and CCPA compliant. This results in increased CPMs, higher fill rates, and, ultimately, lifting overall revenue + +# All you need is a few basic steps to start using our solution. + +## Registration + +Navigate to [our portal ](https://www.intentiq.com/) and contact our team for partner ID. +check our [documentation](https://pbmodule.documents.intentiq.com/) to get more information about our solution and how utilze it's full potential + +## Integration + +``` +gulp build –modules=intentIqIdSystem +``` + +We recommend including the Intent IQ Analytics adapter module for improved visibility + +## Configuration + +### Parameters + +Please find below list of paramters that could be used in configuring Intent IQ Universal ID module + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| ------------------------------ | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | +| params | Required | Object | Details for IntentIqId initialization. | | +| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | +| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | +| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | +| params.callback | Required | Function | This is a callback which is trigered with data and AB group | `(data, group) => console.log({ data, group })` | +| params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | +| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | + +### Configuration example + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "intentIqId", + params: { + partner: 123456, // valid partner id + callback: (data, group) => window.pbjs.requestBids(), + }, + storage: { + type: "html5", + name: "intentIqId", // set localstorage with this name + expires: 60, + refreshInSeconds: 4 * 3600, // refresh ID every 4 hours to ensure it's fresh + }, + }, + ], + syncDelay: 3000, + }, +}); +``` + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "intentIqId", + params: { + partner: 123456 // valid partner id + pcid: PCID_VARIABLE, // string value, dynamically loaded into a variable before setting the configuration + pai: PAI_VARIABLE , // string value, dynamically loaded into a variable before setting the configuration + timeoutInMillis: 500, + browserBlackList: "chrome", + callback: (data, group) => window.pbjs.requestBids() + }, + storage: { + type: "html5", + name: "intentIqId", // set localstorage with this name + expires: 60 + } + }], + syncDelay: 3000 + } +}); +``` diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 4d013ba0a8e..c5dabfbeed3 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,31 +2,61 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import {clearAllCookies} from '../../helpers/cookies.js'; +import { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, decryptData, detectBrowserFromUserAgent, detectBrowserFromUserAgentData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; +import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; +import { clearAllCookies } from '../../helpers/cookies'; const partner = 10; const pai = '11'; const pcid = '12'; -const enableCookieStorage = true; const defaultConfigParams = { params: { partner: partner } }; const paiConfigParams = { params: { partner: partner, pai: pai } }; const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; -const enableCookieConfigParams = { params: { partner: partner, enableCookieStorage: enableCookieStorage } }; -const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid, enableCookieStorage: enableCookieStorage } }; +const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; const responseHeader = { 'Content-Type': 'application/json' } +export const testClientHints = { + architecture: 'x86', + bitness: '64', + brands: [ + { + brand: 'Not(A:Brand', + version: '24' + }, + { + brand: 'Chromium', + version: '122' + } + ], + fullVersionList: [ + { + brand: 'Not(A:Brand', + version: '24.0.0.0' + }, + { + brand: 'Chromium', + version: '122.0.6261.128' + } + ], + mobile: false, + model: '', + platform: 'Linux', + platformVersion: '6.5.0', + wow64: false +}; + describe('IntentIQ tests', function () { let logErrorStub; let testLSValue = { - 'date': 1651945280759, + 'date': Date.now(), 'cttl': 2000, 'rrtt': 123 } let testLSValueWithData = { - 'date': 1651945280759, + 'date': Date.now(), 'cttl': 9999999999999, 'rrtt': 123, - 'data': 'previousTestData' + 'data': 'U2FsdGVkX18AKRyhEPdiL9kuxSigBrlqLaDJWvwSird2O5TdBW67gX+xbL4nYxHDjdS5G5FpdhtpouZiBFw2FBjyUyobZheM857G5q4BapdiA8z3K6j0W+r0im30Ak2SSn2NBfFwxcCgP/UAF5/ddxIIaeWl1yBMZBO+Gic6us2JUg86paAtp3Sk4unCvg1G+4myYYSKgGi/Vrw51ye/jAGn4AdAbFOCojENhV+Ts/XyVK0AQGdC3wqnQUId9MZpB2VoTA9wgXeYEzjpDDJmcKQ18V3WTKnK/H1FBVZa1vovOj13ZUeuMUZbZL83NFE/PkCrzJjRy14orcdnGbDUaxXUBBulDCr21gNnc0mLbYj7b18OQQ75/GhX80HroxbMEyc5tiECBrE/JsW+2sQ4MwoqePPPj/f5Bf4wJ4z3UphjK6maypoWaXsZCZTp2mJYmsf0XsNHLpt1MUrBeAmy6Bewkb+WEAeVe6/b53DQDlo2LQXpSzDPVucMn3CQOWFv1Bvz5cLIZRD8/NtDjgYzWNXHRRAGhhN19yew0ZyeS09x3UBiwER6A6ppv2qQVGs8QNsif3z7pkhkNoETcXQKyv1xa5X87tLvXkO4FYDQQvXEGInyPuNmkFtsZS5FJl+TYrdyaEiCOwRgkshwCU4s93WrfRUtPHtd4zNiA1LWAKGKeBEK6woeFn1YU1YIqsvx9wXfkCbqNkHTi2mD1Eb85a2niSK9BzDdbxwv6EzZ+f9j6esEVdBUIiYmsUuOfTp/ftOHKjBKi1lbeC5imAzZfV/AKvqS5opAVGp7Y9pq976sYblCrPBQ0PYI+Cm2ZNhG1vKc2Pa0rjwJwvusZp2Wvw9zSbnoZUeBi1O+XGYqGhkqYVvH3rXvrFiSmA7pk5Buz6vPd6YV1d55PVahv/4u3jksEI/ZN8QNshrM0foJ4tE/q4x8EKx22txb6433QQybwFfExdmA/XaPqM0rwqTm4qyK0mbX984A8niQka5T5pPkEfL4ALqlIgJ2Fo7X/s6FRU/sZq72JWKcVET4edebD0w5mjeotsjUz5EGT0jRSWRba0yxe4myNaAyY7Y0NTNY9J9Q0JLDFh9Hb05Ejt0Jeoq4Olv8/zFWObBoQtkQyeeRB8L7XIari/xgl191J6euhe5+8vu3ta3tX+XGk+gqdfip1R11tEYpW/XPsV+6DBEfS/8icDHiwK7sPpAgTx7GuJGL1U3Hbg7P/2zUU6xMSR5In/Oa5i1B9FtayGd+utiqrGJsqg8IyFlAt1B9B11k/wJFnWWevMly+y+Ko75ShF7UzfcNR2s41doov+2DEz/YiKH1qHjVOXjslBTYjceB3xqa8sSPDt/vQDDUIX5CPLyVBZj7AeeB/IKDFjZVovBDH92Xl8JTNILRuDHsWmSwNI1DUzgus6ox4u9Mi439caK6KnpNYso+ksLXNEQCm0m15WV2NC+fjkEwLV6hGNbz' } let testResponseWithValues = { 'abPercentage': 90, @@ -40,6 +70,10 @@ describe('IntentIQ tests', function () { } beforeEach(function () { + localStorage.clear(); + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(FIRST_PARTY_KEY, '', expiredDate, 'Lax'); + storage.setCookie(FIRST_PARTY_KEY + '_' + partner, '', expiredDate, 'Lax'); logErrorStub = sinon.stub(utils, 'logError'); }); @@ -67,7 +101,7 @@ describe('IntentIQ tests', function () { expect(submodule).to.be.undefined; }); - it('should not save data in cookie if enableCookieStorage configParam not set', function () { + it('should not save data in cookie if relevant type not set', function () { let callBackSpy = sinon.spy(); let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -82,9 +116,9 @@ describe('IntentIQ tests', function () { expect(storage.getCookie('_iiq_fdata_' + partner)).to.equal(null); }); - it('should save data in cookie if enableCookieStorage configParam set to true', function () { + it('should save data in cookie if storage type is "cookie"', function () { let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + let submoduleCallback = intentIqIdSubmodule.getId({ ...allConfigParams, enabledStorageTypes: ['cookie'] }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); @@ -94,9 +128,10 @@ describe('IntentIQ tests', function () { JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) ); expect(callBackSpy.calledOnce).to.be.true; - const cookieValue = storage.getCookie('_iiq_fdata_' + partner) - expect(cookieValue).to.not.equal(null) - expect(JSON.parse(cookieValue).data).to.be.equal('test_personid'); + const cookieValue = storage.getCookie('_iiq_fdata_' + partner); + expect(cookieValue).to.not.equal(null); + const decryptedData = JSON.parse(decryptData(JSON.parse(cookieValue).data)); + expect(decryptedData).to.deep.equal(['test_personid']); }); it('should call the IntentIQ endpoint with only partner', function () { @@ -114,8 +149,6 @@ describe('IntentIQ tests', function () { }); it('should ignore INVALID_ID and invalid responses in decode', function () { - // let resp = JSON.stringify({'RESULT': 'NA'}); - // expect(intentIqIdSubmodule.decode(resp)).to.equal(undefined); expect(intentIqIdSubmodule.decode('INVALID_ID')).to.equal(undefined); expect(intentIqIdSubmodule.decode('')).to.equal(undefined); expect(intentIqIdSubmodule.decode(undefined)).to.equal(undefined); @@ -202,7 +235,7 @@ describe('IntentIQ tests', function () { JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) ); expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.eq('test_personid'); + expect(callBackSpy.args[0][0]).to.deep.equal(['test_personid']); }); it('dont save result if ls=false', function () { @@ -220,21 +253,6 @@ describe('IntentIQ tests', function () { expect(callBackSpy.args[0][0]).to.be.undefined; }); - it('save result as INVALID_ID on empty data and ls=true ', function () { - let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); - request.respond( - 200, - responseHeader, - JSON.stringify({ pid: 'test_pid', data: '', ls: true }) - ); - expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.eq('INVALID_ID'); - }); - it('send addition parameters if were found in localstorage', function () { localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValue)) let callBackSpy = sinon.spy(); @@ -251,16 +269,226 @@ describe('IntentIQ tests', function () { JSON.stringify(testResponseWithValues) ); expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.eq(testResponseWithValues.data); + expect(callBackSpy.args[0][0]).to.deep.equal([testResponseWithValues.data]); }); it('return data stored in local storage ', function () { - localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValueWithData)) + localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValueWithData)); + let returnedValue = intentIqIdSubmodule.getId(allConfigParams); + expect(JSON.stringify(returnedValue.id)).to.equal(decryptData(testLSValueWithData.data)); + }); + + it('should handle browser blacklisting', function () { + let configParamsWithBlacklist = { + params: { partner: partner, browserBlackList: 'chrome' } + }; + sinon.stub(navigator, 'userAgent').value('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'); + let submoduleCallback = intentIqIdSubmodule.getId(configParamsWithBlacklist); + expect(logErrorStub.calledOnce).to.be.true; + expect(submoduleCallback).to.be.undefined; + }); + + it('should handle invalid JSON in readData', function () { + localStorage.setItem('_iiq_fdata_' + partner, 'invalid_json'); let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - expect(server.requests.length).to.be.equal(0); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.equal(testLSValueWithData.data); + expect(logErrorStub.called).to.be.true; + }); + + describe('handleGPPData', function () { + it('should convert array of objects to a single JSON string', function () { + const input = [ + { key1: 'value1' }, + { key2: 'value2' } + ]; + const expectedOutput = JSON.stringify({ key1: 'value1', key2: 'value2' }); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + + it('should convert a single object to a JSON string', function () { + const input = { key1: 'value1', key2: 'value2' }; + const expectedOutput = JSON.stringify(input); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + + it('should handle empty object', function () { + const input = {}; + const expectedOutput = JSON.stringify(input); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + + it('should handle empty array', function () { + const input = []; + const expectedOutput = JSON.stringify({}); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + }); + + describe('detectBrowserFromUserAgent', function () { + it('should detect Chrome browser', function () { + const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'; + const result = detectBrowserFromUserAgent(userAgent); + expect(result).to.equal('chrome'); + }); + + it('should detect Safari browser', function () { + const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15'; + const result = detectBrowserFromUserAgent(userAgent); + expect(result).to.equal('safari'); + }); + + it('should detect Firefox browser', function () { + const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'; + const result = detectBrowserFromUserAgent(userAgent); + expect(result).to.equal('firefox'); + }); + }); + + describe('detectBrowserFromUserAgentData', function () { + it('should detect Microsoft Edge browser', function () { + const userAgentData = { + brands: [ + { brand: 'Microsoft Edge', version: '91' }, + { brand: 'Chromium', version: '91' } + ] + }; + const result = detectBrowserFromUserAgentData(userAgentData); + expect(result).to.equal('edge'); + }); + + it('should detect Chrome browser', function () { + const userAgentData = { + brands: [ + { brand: 'Google Chrome', version: '91' }, + { brand: 'Chromium', version: '91' } + ] + }; + const result = detectBrowserFromUserAgentData(userAgentData); + expect(result).to.equal('chrome'); + }); + + it('should return unknown for unrecognized user agent data', function () { + const userAgentData = { + brands: [ + { brand: 'Unknown Browser', version: '1.0' } + ] + }; + const result = detectBrowserFromUserAgentData(userAgentData); + expect(result).to.equal('unknown'); + }); + }); + + describe('IntentIQ consent management within getId', function () { + let uspDataHandlerStub; + let gppDataHandlerStub; + + beforeEach(function () { + localStorage.clear(); + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(FIRST_PARTY_KEY, '', expiredDate, 'Lax'); + storage.setCookie(FIRST_PARTY_KEY + '_' + partner, '', expiredDate, 'Lax'); + uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppDataHandlerStub = sinon.stub(gppDataHandler, 'getConsentData'); + }); + + afterEach(function () { + uspDataHandlerStub.restore(); + gppDataHandlerStub.restore(); + }); + + it('should set cmpData.us_privacy if uspData exists', function () { + const uspData = '1NYN'; + uspDataHandlerStub.returns(uspData); + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: false }) + ); + expect(callBackSpy.calledOnce).to.be.true; + + // Check the local storage directly to see if cmpData.us_privacy was set correctly + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.uspapi_value).to.equal(uspData); + }); + + it('should set cmpData.gpp and cmpData.gpp_sid if gppData exists and has parsedSections with usnat', function () { + const gppData = { + parsedSections: { + usnat: { key1: 'value1', key2: 'value2' } + }, + applicableSections: ['usnat'] + }; + gppDataHandlerStub.returns(gppData); + + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: false }) + ); + expect(callBackSpy.calledOnce).to.be.true; + + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.gpp_value).to.equal(JSON.stringify({ key1: 'value1', key2: 'value2' })); + }); + + it('should handle gppData without usnat in parsedSections', function () { + const gppData = { + parsedSections: { + euconsent: { key1: 'value1' } + }, + applicableSections: ['euconsent'] + }; + gppDataHandlerStub.returns(gppData); + + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: true }) + ); + expect(callBackSpy.calledOnce).to.be.true; + + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.gpp_value).to.equal(''); + }); + }); + + it('should get and save client hints to storge', async () => { + // Client hints are async function, thats why async/await is using + localStorage.clear(); + Object.defineProperty(navigator, 'userAgentData', { + value: { getHighEntropyValues: async () => testClientHints }, + configurable: true + }); + await intentIqIdSubmodule.getId(defaultConfigParams); + const savedClientHints = readData(CLIENT_HINTS_KEY, ['html5']); + expect(savedClientHints).to.equal(handleClientHints(testClientHints)); }); }); From f6e1008ad9216c86801d706ca875f13e3976bbf4 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 27 Jun 2024 09:52:22 -0400 Subject: [PATCH 0263/1097] Update pairIdSystem.js: add defensive code before json parse (#11870) * Update pairIdSystem.js: add defensive code before json parse * Update pairIdSystem.js * Update pairIdSystem.js * Update pairIdSystem.js fix indents * Update pairIdSystem.js --- modules/pairIdSystem.js | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js index dbff4c6a402..778857bae1c 100644 --- a/modules/pairIdSystem.js +++ b/modules/pairIdSystem.js @@ -50,9 +50,11 @@ export const pairIdSubmodule = { return value && Array.isArray(value) ? {'pairId': value} : undefined }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @returns {id: string | undefined } + * Performs action to obtain ID and return a value in the callback's response argument. + * @function getId + * @param {Object} config - The configuration object. + * @param {Object} config.params - The parameters from the configuration. + * @returns {{id: string[] | undefined}} The obtained IDs or undefined if no IDs are found. */ getId(config) { const pairIdsString = pairIdFromLocalStorage(PAIR_ID_KEY) || pairIdFromCookie(PAIR_ID_KEY) @@ -67,13 +69,28 @@ export const pairIdSubmodule = { const configParams = (config && config.params) || {}; if (configParams && configParams.liveramp) { - let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY - const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation) - try { - const obj = JSON.parse(atob(liverampValue)); - ids = ids.concat(obj.envelope); - } catch (error) { - logInfo(error) + let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY; + const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation); + + if (liverampValue) { + try { + const parsedValue = atob(liverampValue); + if (parsedValue) { + const obj = JSON.parse(parsedValue); + + if (obj && typeof obj === 'object' && obj.envelope) { + ids = ids.concat(obj.envelope); + } else { + logInfo('Pairid: Parsed object is not valid or does not contain envelope'); + } + } else { + logInfo('Pairid: Decoded value is empty'); + } + } catch (error) { + logInfo('Pairid: Error parsing JSON: ', error); + } + } else { + logInfo('Pairid: liverampValue for pairId from storage is empty or null'); } } From c1c1a763d33020f6247e882065589a7ac219c8d2 Mon Sep 17 00:00:00 2001 From: Meng <5110935+edmonl@users.noreply.github.com> Date: Thu, 27 Jun 2024 12:16:09 -0400 Subject: [PATCH 0264/1097] s2s config formatted correctly (#11878) --- modules/prebidServerBidAdapter/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index a6c840193d9..2256e788e7e 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -179,7 +179,7 @@ function setS2sConfig(options) { const activeBidders = []; const optionsValid = normalizedOptions.every((option, i, array) => { - formatUrlParams(options); + formatUrlParams(option); const updateSuccess = updateConfigDefaultVendor(option); if (updateSuccess !== false) { const valid = validateConfigRequiredProps(option); From 1771824ba1b4574ca1a539307292b23f7b414d85 Mon Sep 17 00:00:00 2001 From: janzych-smart <103245435+janzych-smart@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:12:46 +0200 Subject: [PATCH 0265/1097] Smartadserver Bid Adapter: disable Sec-Browsing-Topics for calls to Smartadserver (#11876) * disable Sec-Browsing-Topics for smartadserver calls * u.t. for disabling browsingTopics --- modules/smartadserverBidAdapter.js | 3 +++ test/spec/modules/smartadserverBidAdapter_spec.js | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 415061d0eda..a90f99da2f7 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -156,6 +156,9 @@ export const spec = { method: 'POST', url: (domain !== undefined ? domain : 'https://prg.smartadserver.com') + '/prebid/v1', data: JSON.stringify(payload), + options: { + browsingTopics: false + } }; }, diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index b01d95e2a4c..2f06331f616 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -458,6 +458,13 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); + it('should set browsingTopics=false in request.options', () => { + const requests = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL); + expect(requests[0]).to.have.property('options').and.to.deep.equal({ + browsingTopics: false + }); + }); + describe('gdpr tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); From 8ef5719c2f2a547d72981b321175f97f0b13a8b1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 27 Jun 2024 21:37:41 +0000 Subject: [PATCH 0266/1097] Prebid 9.3.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 071e836e503..5609a6e6a9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.3.0-pre", + "version": "9.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.3.0-pre", + "version": "9.3.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 2860f0dc61c..5cddd94306c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.3.0-pre", + "version": "9.3.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 2f72b577748dbb7d64cd29f684326ef2ef1c1d3b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 27 Jun 2024 21:37:42 +0000 Subject: [PATCH 0267/1097] Increment version to 9.4.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5609a6e6a9d..343bc1f8867 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.3.0", + "version": "9.4.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.3.0", + "version": "9.4.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 5cddd94306c..c0bbb1674c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.3.0", + "version": "9.4.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 75e337536b135d03f6b55311b5a6404571302feb Mon Sep 17 00:00:00 2001 From: skapoorViant <153030693+skapoorViant@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:31:14 -0700 Subject: [PATCH 0268/1097] viantOrtbBidAdapter': deals support (#11864) * Added deals support in viantOrtbBidAdapter * refactoring * refactor * refactor * refactor * refactor * added test case * refactor * refactor test file * added test case and addressed suggestions --- modules/viantOrtbBidAdapter.js | 20 ++++ test/spec/modules/viantOrtbBidAdapter_spec.js | 93 +++++++++++++++++-- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/modules/viantOrtbBidAdapter.js b/modules/viantOrtbBidAdapter.js index d056dfeb2eb..b4448715f7a 100644 --- a/modules/viantOrtbBidAdapter.js +++ b/modules/viantOrtbBidAdapter.js @@ -6,6 +6,7 @@ import {deepAccess, getBidIdParameter, logError} from '../src/utils.js'; const BIDDER_CODE = 'viant'; const ENDPOINT = 'https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder' +const ADAPTER_VERSION = '2.0.0'; const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; @@ -85,6 +86,25 @@ function createRequest(bidRequests, bidderRequest, mediaType) { if (!data.regs.ext) data.regs.ext = {}; data.regs.ext.us_privacy = bidderRequest.uspConsent; } + let imp = data.imp || []; + let dealsMap = new Map(); + if (bidderRequest.bids) { + bidderRequest.bids.forEach(bid => { + if (bid.ortb2Imp && bid.ortb2Imp.pmp) { + dealsMap.set(bid.bidId, bid.ortb2Imp.pmp); + } + }); + } + imp.forEach((element) => { + let deals = dealsMap.get(element.id); + if (deals) { + element.pmp = deals; + } + }); + data.ext = data.ext || {}; + data.ext.viant = { + adapterVersion: ADAPTER_VERSION + }; return { method: 'POST', url: ENDPOINT, diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index 271c944e6e9..a289faf3573 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -1,8 +1,9 @@ -import { spec, converter } from 'modules/viantOrtbBidAdapter.js'; +import {spec, converter} from 'modules/viantOrtbBidAdapter.js'; import {assert, expect} from 'chai'; -import { deepClone } from '../../../src/utils'; +import {deepClone} from '../../../src/utils'; import {buildWindowTree} from '../../helpers/refererDetectionHelper'; import {detectReferer} from '../../../src/refererDetection'; + describe('viantOrtbBidAdapter', function () { function testBuildRequests(bidRequests, bidderRequestBase) { let clonedBidderRequest = deepClone(bidderRequestBase); @@ -10,7 +11,8 @@ describe('viantOrtbBidAdapter', function () { let requests = spec.buildRequests(bidRequests, clonedBidderRequest); return requests } - describe('isBidRequestValid', function() { + + describe('isBidRequestValid', function () { function makeBid() { return { 'bidder': 'viant', @@ -46,9 +48,7 @@ describe('viantOrtbBidAdapter', function () { it('should return true if placementId is not passed ', function () { let bid = makeBid(); delete bid.params.placementId; - bid.ortb2Imp = { - - } + bid.ortb2Imp = {} expect(spec.isBidRequestValid(bid)).to.equal(true); }); @@ -98,6 +98,7 @@ describe('viantOrtbBidAdapter', function () { 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' } } + it('should return true when required params found', function () { expect(spec.isBidRequestValid(makeBid())).to.equal(true); }); @@ -141,6 +142,7 @@ describe('viantOrtbBidAdapter', function () { 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' } } + it('should return true when required params found', function () { expect(spec.isBidRequestValid(makeBid())).to.equal(true); }); @@ -180,6 +182,57 @@ describe('viantOrtbBidAdapter', function () { 'src': 'client', 'bidRequestsCount': 1 }]; + const basePMPDealsBidRequests = [{ + 'bidder': 'viant', + 'params': { + 'publisherId': '464', + 'placementId': '1' + }, + 'ortb2Imp': { + 'pmp': { + 'private_auction': 0, + 'deals': [ + { + 'id': '1234567', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 1, + 'private_auction': 1 + } + }, + { + 'id': '1234568', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 0 + } + } + ] + }, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'gdprConsent': { + 'consentString': 'consentString', + 'gdprApplies': true, + }, + 'uspConsent': '1YYY', + 'sizes': [[728, 90]], + 'transactionId': '1111474f-58b1-4368-b812-84f8c937a099', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; const testWindow = buildWindowTree(['https://www.example.com/test', 'https://www.example.com/other/page', 'https://www.example.com/third/page'], 'https://othersite.com/', 'https://example.com/canonical/page'); const baseBidderRequestReferer = detectReferer(testWindow)(); @@ -236,6 +289,34 @@ describe('viantOrtbBidAdapter', function () { const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest)[0].data; expect(requestBody.imp[0].banner.pos).to.equal(1); }); + it('includes the deals in the bid request', function () { + const requestBody = testBuildRequests(basePMPDealsBidRequests, baseBidderRequest)[0].data; + expect(requestBody.imp[0].pmp).to.be.not.null; + expect(requestBody.imp[0].pmp).to.deep.equal({ + 'private_auction': 0, + 'deals': [ + { + 'id': '1234567', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 1, + 'private_auction': 1 + } + }, + { + 'id': '1234568', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 0 + } + } + ] + }); + }); }); if (FEATURES.VIDEO) { From c1fbae10aeb1394b7ff832a78d88f2ae537cdaba Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Fri, 28 Jun 2024 04:41:25 +0200 Subject: [PATCH 0269/1097] ZetaGlobalSsp adapter: merge ortb2.site and params.site (#11773) * ZetaGlobalSsp adapter: merge params.site and ortb2.site * fix unit test * extract buildVideo into utils * Revert "extract buildVideo into utils" This reverts commit a0755830c3e54bb97c88b7bb283d9b9ee5f4ef82. --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 4 ++-- test/spec/modules/zeta_global_sspBidAdapter_spec.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index a273927e1ec..2ea6d745096 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -128,8 +128,8 @@ export const spec = { id: bidderRequest.bidderRequestId, cur: [DEFAULT_CUR], imp: imps, - site: params.site ? params.site : {}, - device: {...(bidderRequest.ortb2?.device || {}), ...params.device}, + site: {...bidderRequest?.ortb2?.site, ...params?.site}, + device: {...bidderRequest?.ortb2?.device, ...params?.device}, user: params.user ? params.user : {}, app: params.app ? params.app : {}, ext: { diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index dfa7c9e6984..a903c91ec18 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -132,6 +132,9 @@ describe('Zeta Ssp Bid Adapter', function () { userIdAsEids: eids, timeout: 500, ortb2: { + site: { + inventorypartnerdomain: 'disqus.com' + }, device: { sua: { mobile: 1, @@ -709,4 +712,12 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.ext.tags.nullTag).to.be.undefined; expect(payload.ext.tags.complexEmptyTag).to.be.undefined; }); + + it('Test that site payload param are merged from ortb2 and params', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.site.page).to.eql('zetaglobal.com/page'); + expect(payload.site.inventorypartnerdomain).to.eql('disqus.com'); + }); }); From 66605617791f289988ca166428d348885a84f839 Mon Sep 17 00:00:00 2001 From: CPMStar Date: Fri, 28 Jun 2024 03:34:12 -0700 Subject: [PATCH 0270/1097] cpmstarBidAdapter: added gvlid, additional request data (#11881) * cpmstarBidAdapter - added gvlid, added ortb and banner request data, various cleanups * cpmstarBidAdapter - fixed schain test for different url encoding * cpmstarBidAdapter: removed extra blank line --- modules/cpmstarBidAdapter.js | 88 ++++++++++++--------- modules/cpmstarBidAdapter.md | 5 +- test/spec/modules/cpmstarBidAdapter_spec.js | 2 +- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js index e076fb4b0bb..772cb8c537c 100755 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -1,8 +1,8 @@ import * as utils from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {getBidIdParameter} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'cpmstar'; @@ -12,15 +12,18 @@ const ENDPOINT_PRODUCTION = 'https://server.cpmstar.com/view.aspx'; const DEFAULT_TTL = 300; const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; -function fixedEncodeURIComponent(str) { - return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { - return '%' + c.charCodeAt(0).toString(16); - }); -} +export const converter = ortbConverter({ + context: { + ttl: DEFAULT_TTL, + netRevenue: DEFAULT_NET_REVENUE + } +}); export const spec = { code: BIDDER_CODE, + gvlid: 1317, supportedMediaTypes: [BANNER, VIDEO], pageID: Math.floor(Math.random() * 10e6), @@ -43,22 +46,30 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { var requests = []; - // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch. for (var i = 0; i < validBidRequests.length; i++) { var bidRequest = validBidRequests[i]; - var referer = bidderRequest.refererInfo.page ? bidderRequest.refererInfo.page : bidderRequest.refererInfo.domain; - referer = encodeURIComponent(referer); - var e = getBidIdParameter('endpoint', bidRequest.params); - var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; - var mediaType = spec.getMediaType(bidRequest); - var playerSize = spec.getPlayerSize(bidRequest); - var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); - var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + - '&json=c_b&mv=1&poolid=' + getBidIdParameter('placementId', bidRequest.params) + - '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + - '&requestid=' + bidRequest.bidId + - '&referer=' + encodeURIComponent(referer); + const referer = bidderRequest.refererInfo.page ? bidderRequest.refererInfo.page : bidderRequest.refererInfo.domain; + const e = utils.getBidIdParameter('endpoint', bidRequest.params); + const ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; + const url = new URL(ENDPOINT); + const body = {}; + const mediaType = spec.getMediaType(bidRequest); + const playerSize = spec.getPlayerSize(bidRequest); + url.searchParams.set('media', mediaType); + if (mediaType == VIDEO) { + url.searchParams.set('fv', 0); + if (playerSize) { + url.searchParams.set('w', playerSize?.[0]); + url.searchParams.set('h', playerSize?.[1]); + } + } + url.searchParams.set('json', 'c_b'); + url.searchParams.set('mv', 1); + url.searchParams.set('poolid', utils.getBidIdParameter('placementId', bidRequest.params)); + url.searchParams.set('reachedTop', bidderRequest.refererInfo.reachedTop); + url.searchParams.set('requestid', bidRequest.bidId); + url.searchParams.set('referer', referer); if (bidRequest.schain && bidRequest.schain.nodes) { var schain = bidRequest.schain; @@ -67,45 +78,49 @@ export const spec = { for (var i2 = 0; i2 < schain.nodes.length; i2++) { var node = schain.nodes[i2]; schainString += '!' + - fixedEncodeURIComponent(node.asi || '') + ',' + - fixedEncodeURIComponent(node.sid || '') + ',' + - fixedEncodeURIComponent(node.hp || '') + ',' + - fixedEncodeURIComponent(node.rid || '') + ',' + - fixedEncodeURIComponent(node.name || '') + ',' + - fixedEncodeURIComponent(node.domain || ''); + (node.asi || '') + ',' + + (node.sid || '') + ',' + + (node.hp || '') + ',' + + (node.rid || '') + ',' + + (node.name || '') + ',' + + (node.domain || ''); } - url += '&schain=' + schainString; + url.searchParams.set('schain', schainString); } if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString != null) { - url += '&gdpr_consent=' + bidderRequest.gdprConsent.consentString; + url.searchParams.set('gdpr_consent', bidderRequest.gdprConsent.consentString); } if (bidderRequest.gdprConsent.gdprApplies != null) { - url += '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + url.searchParams.set('gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } } if (bidderRequest.uspConsent != null) { - url += '&us_privacy=' + bidderRequest.uspConsent; + url.searchParams.set('us_privacy', bidderRequest.uspConsent); } if (config.getConfig('coppa')) { - url += '&tfcd=' + (config.getConfig('coppa') ? 1 : 0); + url.searchParams.set('tfcd', (config.getConfig('coppa') ? 1 : 0)); } - let body = {}; let adUnitCode = bidRequest.adUnitCode; if (adUnitCode) { body.adUnitCode = adUnitCode; } if (mediaType == VIDEO) { body.video = utils.deepAccess(bidRequest, 'mediaTypes.video'); + } else if (mediaType == BANNER) { + body.banner = utils.deepAccess(bidRequest, 'mediaTypes.banner'); } + const ortb = converter.toORTB({ bidderRequest, bidRequests: [bidRequest] }); + Object.assign(body, ortb); + requests.push({ method: 'POST', - url: url, + url: url.toString(), bidRequest: bidRequest, data: body }); @@ -144,7 +159,7 @@ export const spec = { width: rawBid.width || 0, height: rawBid.height || 0, currency: rawBid.currency ? rawBid.currency : DEFAULT_CURRENCY, - netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, + netRevenue: rawBid.netRevenue ? rawBid.netRevenue : DEFAULT_NET_REVENUE, ttl: rawBid.ttl ? rawBid.ttl : DEFAULT_TTL, creativeId: rawBid.creativeid || 0, meta: { @@ -191,4 +206,5 @@ export const spec = { } }; + registerBidder(spec); diff --git a/modules/cpmstarBidAdapter.md b/modules/cpmstarBidAdapter.md index c227f19bfaf..66f13479c05 100755 --- a/modules/cpmstarBidAdapter.md +++ b/modules/cpmstarBidAdapter.md @@ -3,10 +3,7 @@ ``` Module Name: Cpmstar Bidder Adapter Module Type: Bidder Adapter -Maintainer: josh@cpmstar.com -gdpr_supported: true -usp_supported: true -coppa_supported: true +Maintainer: prebid@cpmstar.com ``` # Description diff --git a/test/spec/modules/cpmstarBidAdapter_spec.js b/test/spec/modules/cpmstarBidAdapter_spec.js index 285fca9690a..f9b1ba59cde 100755 --- a/test/spec/modules/cpmstarBidAdapter_spec.js +++ b/test/spec/modules/cpmstarBidAdapter_spec.js @@ -149,7 +149,7 @@ describe('Cpmstar Bid Adapter', function () { }; var requests = spec.buildRequests(reqs, bidderRequest); expect(requests[0]).to.have.property('url'); - expect(requests[0].url).to.include('&schain=1.0,1!exchange1.com,1234,1,,,!exchange2.com,abcd,1,,,'); + expect(requests[0].url).to.include('&schain=1.0%2C1%21exchange1.com%2C1234%2C1%2C%2C%2C%21exchange2.com%2Cabcd%2C1%2C%2C%2C'); }); describe('interpretResponse', function () { From ca24c395555167e5aba7007a83332c1d49441921 Mon Sep 17 00:00:00 2001 From: Gonca Karadeniz <49647923+Goncakkd@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:56:54 +0300 Subject: [PATCH 0271/1097] Visx bid adapter: import utilities and retrieve data from user on ortb2 (#11860) * AF-3740 retrieved topics data from user on ortb2 * AF-3740 kept old user data * AF-3740 added some checks for user data * AF-3740 used mergeDeep for merging user objects * AF-3740 provided unit tests for ortb2 data * AF-3740 moved some functionality to utils and used for avoiding duplication * AF-3740 provided unit tests --- libraries/processResponse/index.js | 12 ++ modules/carodaBidAdapter.js | 11 +- modules/gridBidAdapter.js | 14 +- modules/luponmediaBidAdapter.js | 14 +- modules/visxBidAdapter.js | 56 +++--- test/spec/libraries/processResponse_spec.js | 60 +++++++ test/spec/modules/visxBidAdapter_spec.js | 180 ++++++++++++++++++++ 7 files changed, 288 insertions(+), 59 deletions(-) create mode 100644 libraries/processResponse/index.js create mode 100644 test/spec/libraries/processResponse_spec.js diff --git a/libraries/processResponse/index.js b/libraries/processResponse/index.js new file mode 100644 index 00000000000..5ca2e9329f3 --- /dev/null +++ b/libraries/processResponse/index.js @@ -0,0 +1,12 @@ +import { logError } from '../../src/utils.js'; + +export function getBidFromResponse(respItem, LOG_ERROR_MESS) { + if (!respItem) { + logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js index cb7b5fbe7c5..8ec260213b6 100644 --- a/modules/carodaBidAdapter.js +++ b/modules/carodaBidAdapter.js @@ -8,7 +8,8 @@ import { deepSetValue, logError, mergeDeep, - parseSizesInput + sizeTupleToRtbSize, + sizesToSizeTuples } from '../src/utils.js'; import { config } from '../src/config.js'; @@ -195,13 +196,7 @@ function getImps (validBidRequests, common) { }; const bannerParams = deepAccess(bid, 'mediaTypes.banner'); if (bannerParams && bannerParams.sizes) { - const sizes = parseSizesInput(bannerParams.sizes); - const format = sizes.map(size => { - const [width, height] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); + const format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); imp.banner = { format }; diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index f7db6d878f1..4c1876dfb6f 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -15,6 +15,7 @@ import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import { getBidFromResponse } from '../libraries/processResponse/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -441,7 +442,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); + _addBidResponse(getBidFromResponse(respItem, LOG_ERROR_MESS), bidRequest, bidResponses, RendererConst, bidderCode); }); } if (errorMessage) logError(errorMessage); @@ -512,17 +513,6 @@ function _getFloor (mediaTypes, bid) { return floor; } -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bidderCode) { if (!serverBid) return; let errorMessage; diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 2c08ca3a435..447257f97da 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -9,7 +9,9 @@ import { logError, logMessage, logWarn, - parseSizesInput + parseSizesInput, + sizeTupleToRtbSize, + sizesToSizeTuples } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; @@ -271,16 +273,8 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { let bannerSizes = []; if (bannerParams && bannerParams.sizes) { - const sizes = parseSizesInput(bannerParams.sizes); - // get banner sizes in form [{ w: , h: }, ...] - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); - + const format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); bannerSizes = format; } diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index c7f415e4dac..a5b5358496d 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,10 +1,11 @@ -import {deepAccess, logError, parseSizesInput, triggerPixel} from '../src/utils.js'; +import {deepAccess, logError, mergeDeep, parseSizesInput, sizeTupleToRtbSize, sizesToSizeTuples, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM as VIDEO_INSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { getBidFromResponse } from '../libraries/processResponse/index.js'; const BIDDER_CODE = 'visx'; const GVLID = 154; @@ -69,6 +70,7 @@ export const spec = { let payloadSite; let payloadRegs; let payloadContent; + let payloadUser; if (currencyWhiteList.indexOf(currency) === -1) { logError(LOG_ERROR_MESS.notAllowedCurrency + currency); @@ -116,6 +118,24 @@ export const spec = { const { ortb2 } = bidderRequest; const { device, site, regs, content } = ortb2; + const userOrtb2 = ortb2.user; + let user; + let userReq; + const vads = _getUserId(); + if (payloadUserEids || payload.gdpr_consent || vads) { + user = { + ext: { + ...(payloadUserEids && { eids: payloadUserEids }), + ...(payload.gdpr_consent && { consent: payload.gdpr_consent }), + ...(vads && { vads }) + } + }; + } + if (user) { + userReq = mergeDeep(user, userOrtb2); + } else { + userReq = userOrtb2; + } if (device) { payloadDevice = device; } @@ -128,6 +148,9 @@ export const spec = { if (content) { payloadContent = content; } + if (userReq) { + payloadUser = userReq; + } } const tmax = timeout; @@ -139,14 +162,6 @@ export const spec = { } }; - const vads = _getUserId(); - const user = { - ext: { - ...(payloadUserEids && { eids: payloadUserEids }), - ...(payload.gdpr_consent && { consent: payload.gdpr_consent }), - ...(vads && { vads }) - } - }; if (payloadRegs === undefined) { payloadRegs = ('gdpr_applies' in payload) && { ext: { @@ -161,7 +176,7 @@ export const spec = { tmax, cur: [currency], source, - ...(Object.keys(user.ext).length && { user }), + ...(payloadUser && { user: payloadUser }), ...(payloadRegs && {regs: payloadRegs}), ...(payloadDevice && { device: payloadDevice }), ...(payloadSite && { site: payloadSite }), @@ -190,7 +205,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, currency, bidResponses); + _addBidResponse(getBidFromResponse(respItem, LOG_ERROR_MESS), bidsMap, currency, bidResponses); }); } if (errorMessage) logError(errorMessage); @@ -260,13 +275,7 @@ function makeBanner(bannerParams) { if (bannerSizes) { const sizes = parseSizesInput(bannerSizes); if (sizes.length) { - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); - + const format = sizesToSizeTuples(bannerSizes).map(sizeTupleToRtbSize); return { format }; } } @@ -306,17 +315,6 @@ function buildImpObject(bid) { } } -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - function _addBidResponse(serverBid, bidsMap, currency, bidResponses) { if (!serverBid) return; let errorMessage; diff --git a/test/spec/libraries/processResponse_spec.js b/test/spec/libraries/processResponse_spec.js new file mode 100644 index 00000000000..8f682bd87f5 --- /dev/null +++ b/test/spec/libraries/processResponse_spec.js @@ -0,0 +1,60 @@ +import { getBidFromResponse } from '../../../libraries/processResponse/index.js'; +import {expect} from 'chai/index.js'; + +describe('processResponse', function () { + const respItem = { + 'bid': [ + { + 'price': 0.504, + 'ext': { + 'visx': { + 'events': { + 'runtime': '//t.visx.net/track/status/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/{STATUS_CODE}' + } + }, + 'prebid': { + 'events': { + 'pending': '//t.visx.net/track/pending/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/', + 'win': '//t.visx.net/track/win/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/', + 'bid_timeout': '//t.visx.net/track/bid_timeout/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/', + 'runtime': '//t.visx.net/track/status/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/{STATUS_CODE}' + }, + 'meta': { + 'mediaType': 'banner' + } + } + }, + 'impid': '2b642c27bdcf8f', + 'auid': 929004, + 'h': 250, + 'cur': 'EUR', + 'adomain': [ + '' + ], + 'w': 300, + 'id': '9b6c7e04-0a09-4add-8ba9-0c8b98304de3' + } + ], + 'seat': '1429601' + }; + const LOG_ERROR_MESS = { + 'noAuid': 'Bid from response has no auid parameter - ', + 'noAdm': 'Bid from response has no adm parameter - ', + 'noBid': 'Array of bid objects is empty', + 'noImpId': 'Bid from response has no impid parameter - ', + 'noPlacementCode': 'Can\'t find in requested bids the bid with auid - ', + 'emptyUids': 'Uids should not be empty', + 'emptySeatbid': 'Seatbid array from response has an empty item', + 'emptyResponse': 'Response is empty', + 'hasEmptySeatbidArray': 'Response has empty seatbid array', + 'hasNoArrayOfBids': 'Seatbid from response has no array of bid objects - ', + 'notAllowedCurrency': 'Currency is not supported - ', + 'currencyMismatch': 'Currency from the request is not match currency from the response - ', + 'onlyVideoInstream': 'Only video instream supported', + 'videoMissing': 'Bid request videoType property is missing - ' + }; + it('returns bid when respItem and LOG_ERROR_MESS is passed', function () { + let response = getBidFromResponse(respItem, LOG_ERROR_MESS); + expect(response).not.include.any.keys('emptyResponse', 'hasNoArrayOfBids', 'emptySeatbid'); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 0653d7a8ce7..db928e4d802 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; import { makeSlot } from '../integration/faker/googletag.js'; +import { mergeDeep } from '../../../src/utils.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -2161,4 +2162,183 @@ describe('VisxAdapter', function () { expect(request.data.user.ext.vads).to.be.a('string'); }); }); + + describe('ortb2 data', function () { + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': 903535 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + } + ]; + const bidderRequest = { + timeout: 3000, + refererInfo: { + page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'user': { + 'keywords': 'x,y', + 'data': [ + { + 'name': 'exampleprovider.de', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '1' + } + ] + }, + { + 'ext': { + 'segtax': 601, + 'segclass': '5' + }, + 'segment': [ + { + 'id': '140' + } + ], + 'name': 'pa.openx.net' + }, + { + 'ext': { + 'segtax': 601, + 'segclass': '5' + }, + 'segment': [ + { + 'id': '140' + } + ], + 'name': 'ads.pubmatic.com' + } + ], + 'ext': { + 'data': { + 'registered': true, + 'interests': [ + 'ads' + ] + } + } + } + } + }; + + it('should pass interests if ortb2 has interests in user data', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.user.ext.data.interests).not.to.be.undefined; + }); + + it('should pass device if ortb2 has device', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.device).not.to.be.undefined; + }); + + it('should pass site if ortb2 has site', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.site).not.to.be.undefined; + }); + + it('should merge if user object exists', function () { + const user = { + 'ext': { + 'vads': 'cXaIRA425BmynEN1ratEnc_5e', + 'data': { + 'registered': true, + 'interests': [ + 'ads' + ] + } + }, + 'keywords': 'x,y', + 'data': [ + { + 'name': 'exampleprovider.de', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '1' + } + ] + } + ] + }; + const userOrtb2 = { + 'keywords': 'x,y', + 'data': [ + { + 'name': 'exampleprovider.de', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '1' + } + ] + } + ], + 'ext': { + 'data': { + 'registered': true, + 'interests': [ + 'ads' + ] + } + } + } + const userReq = mergeDeep(user, userOrtb2); + expect(userReq.ext.vads).not.to.be.undefined; + }); + }); }); From 795e92bb6bfe306ecf89c36a9f6073c25aa20258 Mon Sep 17 00:00:00 2001 From: Rupesh Lakhani <35333377+AskRupert-DM@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:38:38 +0100 Subject: [PATCH 0272/1097] Update ozoneBidAdapter.js (#11885) big fix for ozsize key --- modules/ozoneBidAdapter.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 9968072eff4..7a4a5a9717c 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -22,7 +22,7 @@ const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.2'; +const OZONEVERSION = '2.9.3'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], @@ -535,7 +535,7 @@ export const spec = { for (let j = 0; j < sb.bid.length; j++) { let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); - const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); + let {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); thisBid.meta = {advertiserDomains: thisBid.adomain || []}; let videoContext = null; @@ -567,8 +567,8 @@ export const spec = { this.setBidMediaTypeIfNotExist(thisBid, BANNER); } if (enhancedAdserverTargeting) { - let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid, defaultWidth, defaultHeight); + logInfo('Going to iterate allBidsForThisBidId', deepClone(allBidsForThisBidid)); Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; @@ -606,6 +606,7 @@ export const spec = { } } let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); + winningBid = ozoneAddStandardProperties(winningBid, defaultWidth, defaultHeight); adserverTargeting[whitelabelPrefix + '_auc_id'] = String(aucId); // was request.bidderRequest.auctionId adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); adserverTargeting[whitelabelPrefix + '_bid'] = 'true'; @@ -899,7 +900,7 @@ export const spec = { } }; export function injectAdIdsIntoAllBidResponses(seatbid) { - logInfo('injectAdIdsIntoAllBidResponses', seatbid); + logInfo('injectAdIdsIntoAllBidResponses', deepClone(seatbid)); for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { @@ -951,7 +952,7 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) } return {'seat': winningSeat, 'bid': thisBidWinner}; } -export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { +export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid, defaultWidth, defaultHeight) { let objBids = {}; for (let j = 0; j < serverResponseSeatBid.length; j++) { let theseBids = serverResponseSeatBid[j].bid; @@ -960,10 +961,11 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { if (theseBids[k].impid === matchBidId) { if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid if (objBids[thisSeat]['price'] < theseBids[k].price) { - objBids[thisSeat] = theseBids[k]; + objBids[thisSeat] = ozoneAddStandardProperties(theseBids[k], defaultWidth, defaultHeight); } } else { objBids[thisSeat] = theseBids[k]; + objBids[thisSeat] = ozoneAddStandardProperties(theseBids[k], defaultWidth, defaultHeight); } } } From 67774dee96881ecc594b568b7d34587e4c1b9842 Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 28 Jun 2024 18:22:12 +0200 Subject: [PATCH 0273/1097] AdagioRtdProvider: ensure fallback when adUnit.ortb2Imp is missing (#11886) --- modules/adagioRtdProvider.js | 16 +++++++++------- test/spec/modules/adagioRtdProvider_spec.js | 12 ++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index 16bd8fc2ae2..2c6628eb846 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -278,15 +278,18 @@ function onGetBidRequestData(bidReqConfig, callback, config) { const adUnits = bidReqConfig.adUnits || getGlobal().adUnits || []; adUnits.forEach(adUnit => { + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; const ortb2Imp = deepAccess(adUnit, 'ortb2Imp'); + // A divId is required to compute the slot position and later to track viewability. // If nothing has been explicitly set, we try to get the divId from the GPT slot and fallback to the adUnit code in last resort. - if (!deepAccess(ortb2Imp, 'ext.data.divId')) { - const divId = getGptSlotInfoForAdUnitCode(adUnit.code).divId; + let divId = deepAccess(ortb2Imp, 'ext.data.divId') + if (!divId) { + divId = getGptSlotInfoForAdUnitCode(adUnit.code).divId; deepSetValue(ortb2Imp, `ext.data.divId`, divId || adUnit.code); } - const slotPosition = getSlotPosition(adUnit); + const slotPosition = getSlotPosition(divId); deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition); // It is expected that the publisher set a `adUnits[].ortb2Imp.ext.data.placement` value. @@ -430,7 +433,7 @@ function getElementFromTopWindow(element, currentWindow) { } }; -function getSlotPosition(adUnit) { +function getSlotPosition(divId) { if (!isSafeFrameWindow() && !canAccessWindowTop()) { return ''; } @@ -451,16 +454,15 @@ function getSlotPosition(adUnit) { // window.top based computing const wt = getWindowTop(); const d = wt.document; - const adUnitElementId = deepAccess(adUnit, 'ortb2Imp.ext.data.divId'); let domElement; if (inIframe() === true) { const ws = getWindowSelf(); - const currentElement = ws.document.getElementById(adUnitElementId); + const currentElement = ws.document.getElementById(divId); domElement = getElementFromTopWindow(currentElement, ws); } else { - domElement = wt.document.getElementById(adUnitElementId); + domElement = wt.document.getElementById(divId); } if (!domElement) { diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index ad469d29b37..3fbd1ca20e8 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -436,6 +436,18 @@ describe('Adagio Rtd Provider', function () { expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.not.exist; }); + + it('ensure we create the `ortb2Imp` object if it does not exist', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; + + const bidRequest = utils.deepClone(bidReqConfig); + delete bidRequest.adUnits[0].ortb2Imp; + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + }); }); }); From 0c560a39830d693011122417cd9f296cdfd2f9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Millet?= Date: Fri, 28 Jun 2024 19:23:33 +0200 Subject: [PATCH 0274/1097] Dailymotion bid adapter: Fix user sync parsing (#11887) This fixes an issue in the server response parsing for user sync, where it was not using the body from the response. I took the occasion to also convert this member to camelCase for consistency with the others. Co-authored-by: Kevin Siow --- modules/dailymotionBidAdapter.js | 2 +- .../modules/dailymotionBidAdapter_spec.js | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index b268d497723..746767555fd 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -224,7 +224,7 @@ export const spec = { const pixelSyncs = []; serverResponses.forEach((response) => { - (response.user_syncs || []).forEach((syncUrl) => { + (response?.body?.userSyncs || []).forEach((syncUrl) => { if (syncUrl.type === 'image') { pixelSyncs.push({ url: syncUrl.url, type: 'image' }); } diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 4f8862982ad..3ec45fc1bba 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -647,7 +647,7 @@ describe('dailymotionBidAdapterTests', () => { // No permissions { - const responses = [{ user_syncs: [{ url: 'https://usersyncurl.com', type: 'image' }] }]; + const responses = [{ body: { userSyncs: [{ url: 'https://usersyncurl.com', type: 'image' }] } }]; const syncOptions = { iframeEnabled: false, pixelEnabled: false }; expect(config.runWithBidder( @@ -656,7 +656,7 @@ describe('dailymotionBidAdapterTests', () => { )).to.eql([]); } - // Has permissions but no user_syncs urls + // Has permissions but no userSyncs urls { const responses = [{}]; const syncOptions = { iframeEnabled: false, pixelEnabled: true }; @@ -667,14 +667,16 @@ describe('dailymotionBidAdapterTests', () => { )).to.eql([]); } - // Return user_syncs urls for pixels + // Return userSyncs urls for pixels { const responses = [{ - user_syncs: [ - { url: 'https://usersyncurl.com', type: 'image' }, - { url: 'https://usersyncurl2.com', type: 'image' }, - { url: 'https://usersyncurl3.com', type: 'iframe' } - ], + body: { + userSyncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + } }]; const syncOptions = { iframeEnabled: false, pixelEnabled: true }; @@ -688,14 +690,16 @@ describe('dailymotionBidAdapterTests', () => { ]); } - // Return user_syncs urls for iframes + // Return userSyncs urls for iframes { const responses = [{ - user_syncs: [ - { url: 'https://usersyncurl.com', type: 'image' }, - { url: 'https://usersyncurl2.com', type: 'image' }, - { url: 'https://usersyncurl3.com', type: 'iframe' } - ], + body: { + userSyncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + } }]; const syncOptions = { iframeEnabled: true, pixelEnabled: true }; From d236eb134e31239d6985c48675a45bf0b3ef2c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Udi=20Talias=20=E2=9A=9B=EF=B8=8F?= Date: Sun, 30 Jun 2024 15:41:46 +0300 Subject: [PATCH 0275/1097] Various Adapters: Use Vidazoo utils lib to remove code duplications (#11888) * vidazooUtils - single request configurations * fix: shines rtb adapter use vidazoo bidder utils * fix: added vidazoo utils to illimin adapter * fix: illumin bid adapter using vidazooUtils * fix: twistDigital adapter use vidazooUtils * fix: tagoras adapter use vidazooUtils * fix: kueezRtb adapter use vidazooUtils * fix: removed parrable and digitrust id systems --- libraries/vidazooUtils/bidderUtils.js | 19 +- modules/illuminBidAdapter.js | 313 +------------ modules/kueezRtbBidAdapter.js | 329 +------------ modules/shinezRtbBidAdapter.js | 316 +------------ modules/tagorasBidAdapter.js | 329 +------------ modules/twistDigitalBidAdapter.js | 431 +----------------- modules/vidazooBidAdapter.js | 6 +- test/spec/modules/illuminBidAdapter_spec.js | 39 +- test/spec/modules/kueezRtbBidAdapter_spec.js | 41 +- test/spec/modules/shinezRtbBidAdapter_spec.js | 40 +- test/spec/modules/tagorasBidAdapter_spec.js | 41 +- .../modules/twistDigitalBidAdapter_spec.js | 35 +- 12 files changed, 191 insertions(+), 1748 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 99579fbf7ff..2adf5c12324 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -200,6 +200,7 @@ export function appendUserIdsToRequestPayload(payloadRef, userIds) { let key; _each(userIds, (userId, idSystemProviderName) => { key = `uid.${idSystemProviderName}`; + switch (idSystemProviderName) { case 'lipb': payloadRef[key] = userId.lipbid; @@ -217,7 +218,7 @@ export function getVidazooSessionId(storage) { return getStorageItem(storage, SESSION_ID_KEY) || ''; } -export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, webSessionId, storage, bidderVersion, bidderCode, getUniqueRequestData) { +export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, storage, bidderVersion, bidderCode, getUniqueRequestData) { const { params, bidId, @@ -234,7 +235,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder const {ext} = params; let {bidFloor} = params; const hashUrl = hashCode(topWindowUrl); - const uniqueRequestData = isFn(getUniqueRequestData) ? getUniqueRequestData(hashUrl) : {}; + const uniqueRequestData = isFn(getUniqueRequestData) ? getUniqueRequestData(hashUrl, bid) : {}; const uniqueDealId = getUniqueDealId(storage, hashUrl); const pId = extractPID(params); const isStorageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); @@ -285,7 +286,6 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder bidderRequestsCount: bidderRequestsCount, bidderWinsCount: bidderWinsCount, bidderTimeout: bidderTimeout, - webSessionId: webSessionId, ...uniqueRequestData }; @@ -331,13 +331,13 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder return data; } -export function createInterpretResponseFn(bidderCode) { +export function createInterpretResponseFn(bidderCode, allowSingleRequest) { return function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const singleRequestMode = config.getConfig(`${bidderCode}.singleRequest`); + const singleRequestMode = allowSingleRequest && config.getConfig(`${bidderCode}.singleRequest`); const reqBidId = deepAccess(request, 'data.bidId'); const {results} = serverResponse.body; @@ -410,12 +410,12 @@ export function createInterpretResponseFn(bidderCode) { } } -export function createBuildRequestsFn(createRequestDomain, createUniqueRequestData, webSessionId, storage, bidderCode, bidderVersion) { +export function createBuildRequestsFn(createRequestDomain, createUniqueRequestData, storage, bidderCode, bidderVersion, allowSingleRequest) { function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const {params} = bid; const cId = extractCID(params); const subDomain = extractSubDomain(params); - const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, webSessionId, storage, bidderVersion, bidderCode, createUniqueRequestData); + const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, storage, bidderVersion, bidderCode, createUniqueRequestData); const dto = { method: 'POST', url: `${createRequestDomain(subDomain)}/prebid/multi/${cId}`, data: data }; @@ -428,7 +428,7 @@ export function createBuildRequestsFn(createRequestDomain, createUniqueRequestDa const subDomain = extractSubDomain(params); const data = bidRequests.map(bid => { const sizes = parseSizesInput(bid.sizes); - return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, webSessionId, storage, bidderVersion, bidderCode, createUniqueRequestData) + return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, storage, bidderVersion, bidderCode, createUniqueRequestData) }); const chunkSize = Math.min(20, config.getConfig(`${bidderCode}.chunkSize`) || 10); @@ -445,11 +445,10 @@ export function createBuildRequestsFn(createRequestDomain, createUniqueRequestDa } return function buildRequests(validBidRequests, bidderRequest) { - // TODO: does the fallback make sense here? const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const bidderTimeout = config.getConfig('bidderTimeout'); - const singleRequestMode = config.getConfig('vidazoo.singleRequest'); + const singleRequestMode = allowSingleRequest && config.getConfig(`${bidderCode}.singleRequest`); const requests = []; diff --git a/modules/illuminBidAdapter.js b/modules/illuminBidAdapter.js index e830c27a32b..0e76e471f4b 100644 --- a/modules/illuminBidAdapter.js +++ b/modules/illuminBidAdapter.js @@ -1,322 +1,27 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {config} from '../src/config.js'; +import { + isBidRequestValid, createUserSyncGetter, createInterpretResponseFn, createBuildRequestsFn +} from '../libraries/vidazooUtils/bidderUtils.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'illumin'; const BIDDER_VERSION = '1.0.0'; const GVLID = 149; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.illumin.com`; } -export function extractCID(params) { - return params.cId; -} - -export function extractPID(params) { - return params.pId; -} - -export function extractSubDomain(params) { - return params.subDomain; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - - switch (idSystemProviderName) { - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.illumin.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.illumin.com/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.illumin.com/api/sync/iframe', imageSyncUrl: 'https://sync.illumin.com/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index ba33f3ade9a..c0fb17672af 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -1,338 +1,43 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {config} from '../src/config.js'; +import { + createBuildRequestsFn, + createInterpretResponseFn, + createUserSyncGetter, + isBidRequestValid +} from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1165; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'kueezrtb'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.kueezrtb.com`; } -export function extractCID(params) { - return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; -} - -export function extractPID(params) { - return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; -} - -export function extractSubDomain(params) { - return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { +function createUniqueRequestData(hashUrl, bid) { const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, auctionId, transactionId, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - // TODO: fix auctionId/transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: auctionId, - transactionId: transactionId, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data + return { + auctionId, + transactionId, }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; } -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = bidderRequest.timeout ?? config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.kueezrtb.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.kueezrtb.com/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.kueezrtb.com/api/sync/iframe', + imageSyncUrl: 'https://sync.kueezrtb.com/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/shinezRtbBidAdapter.js b/modules/shinezRtbBidAdapter.js index 490ea908960..f2034bd1992 100644 --- a/modules/shinezRtbBidAdapter.js +++ b/modules/shinezRtbBidAdapter.js @@ -1,321 +1,29 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {config} from '../src/config.js'; +import { + isBidRequestValid, + createBuildRequestsFn, + createInterpretResponseFn, createUserSyncGetter +} from '../libraries/vidazooUtils/bidderUtils.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'shinezRtb'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.sweetgum.io`; } -export function extractCID(params) { - return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; -} - -export function extractPID(params) { - return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; -} - -export function extractSubDomain(params) { - return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); - switch (idSystemProviderName) { - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.sweetgum.io/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.sweetgum.io/api/sync/image/${params}` - }); - } - return syncs; -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.sweetgum.io/api/sync/iframe', + imageSyncUrl: 'https://sync.sweetgum.io/api/sync/image', +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/tagorasBidAdapter.js b/modules/tagorasBidAdapter.js index 0138ba3daf9..6ce54a5a895 100644 --- a/modules/tagorasBidAdapter.js +++ b/modules/tagorasBidAdapter.js @@ -1,333 +1,30 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {config} from '../src/config.js'; +import { + createBuildRequestsFn, + createInterpretResponseFn, + createUserSyncGetter, + isBidRequestValid +} from '../libraries/vidazooUtils/bidderUtils.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'tagoras'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.tagoras.io`; } -export function extractCID(params) { - return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; -} - -export function extractPID(params) { - return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; -} - -export function extractSubDomain(params) { - return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.tagoras.io/api/sync/iframe/${params}` - }); - } else if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.tagoras.io/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.tagoras.io/api/sync/iframe', + imageSyncUrl: 'https://sync.tagoras.io/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js index 8a5f3c0ed56..bed9e531a26 100644 --- a/modules/twistDigitalBidAdapter.js +++ b/modules/twistDigitalBidAdapter.js @@ -1,441 +1,28 @@ -import { - _each, - deepAccess, - isFn, - parseSizesInput, - parseUrl, - uniques, - isArray, - formatQS, - triggerPixel -} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {bidderSettings} from '../src/bidderSettings.js'; -import {config} from '../src/config.js'; -import {chunk} from '../libraries/chunk/chunk.js'; -import {extractCID, extractPID, extractSubDomain} from '../libraries/vidazooUtils/bidderUtils.js'; +import { + isBidRequestValid, createInterpretResponseFn, createUserSyncGetter, createBuildRequestsFn, onBidWon +} from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1292; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'twistdigital'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; -export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.twist.win`; } -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - const {ext} = params; - let {bidFloor} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const pId = extractPID(params); - const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); - const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); - const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); - const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - isStorageAllowed: isStorageAllowed, - gpid: gpid, - cat: cat, - contentData, - userData: userData, - pagecat: pagecat, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout, - webSessionId: webSessionId - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (bidderRequest.paapi?.enabled) { - const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); - if (fledge) { - data.fledge = fledge; - } - } +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, true); - _each(ext, (value, key) => { - data['ext.' + key] = value; - }); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, true); - return data; -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const {params} = bid; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout); - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - return dto; -} - -function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { - const {params} = bidRequests[0]; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = bidRequests.map(bid => { - const sizes = parseSizesInput(bid.sizes); - return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) - }); - const chunkSize = Math.min(20, config.getConfig('twistdigital.chunkSize') || 10); - - const chunkedData = chunk(data, chunkSize); - return chunkedData.map(chunk => { - return { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: { - bids: chunk - } - }; - }); -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - case 'lipb': - payloadRef[key] = userId.lipbid; - break; - case 'parrableId': - payloadRef[key] = userId.eid; - break; - case 'id5id': - payloadRef[key] = userId.uid; - break; - default: - payloadRef[key] = userId; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - - const singleRequestMode = config.getConfig('twistdigital.singleRequest'); - - const requests = []; - - if (singleRequestMode) { - // banner bids are sent as a single request - const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); - if (bannerBidRequests.length > 0) { - const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); - requests.push(...singleRequests); - } - - // video bids are sent as a single request for each bid - - const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); - videoBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } else { - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - - const singleRequestMode = config.getConfig('twistdigital.singleRequest'); - const reqBidId = deepAccess(request, 'data.bidId'); - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach((result, i) => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - bidId, - nurl, - advertiserDomains, - metaData, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: (singleRequestMode && bidId) ? bidId : reqBidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (nurl) { - response.nurl = nurl; - } - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.twist.win/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.twist.win/api/sync/image/${params}` - }); - } - return syncs; -} - -/** - * @param {Bid} bid - */ -function onBidWon(bid) { - if (!bid.nurl) { - return; - } - const wonBid = { - adId: bid.adId, - creativeId: bid.creativeId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - adUnitCode: bid.adUnitCode, - cpm: bid.cpm, - currency: bid.currency, - originalCpm: bid.originalCpm, - originalCurrency: bid.originalCurrency, - netRevenue: bid.netRevenue, - mediaType: bid.mediaType, - timeToRespond: bid.timeToRespond, - status: bid.status, - }; - const qs = formatQS(wonBid); - const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; - triggerPixel(url); -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { - const storageKey = `u_${key}`; - const now = Date.now(); - const data = getStorageItem(storageKey); - let uniqueId; - - if (!data || !data.value || now - data.created > expiry) { - uniqueId = `${key}_${now.toString()}`; - setStorageItem(storageKey, uniqueId); - } else { - uniqueId = data.value; - } - - return uniqueId; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - const created = timestamp || Date.now(); - const data = JSON.stringify({value, created}); - storage.setDataInLocalStorage(key, data); - } catch (e) { - } -} - -export function tryParseJSON(value) { - try { - return JSON.parse(value); - } catch (e) { - return value; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.twist.win/api/sync/iframe', imageSyncUrl: 'https://sync.twist.win/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 21ba444c69c..ed732a4814a 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -32,12 +32,12 @@ function createUniqueRequestData(hashUrl) { const vdzhum = getCacheOpt(storage, OPT_TIME_KEY); return { - dealId: dealId, sessionId: sessionId, ptrace: ptrace, vdzhum: vdzhum + dealId: dealId, sessionId: sessionId, ptrace: ptrace, vdzhum: vdzhum, webSessionId: webSessionId }; } -const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, webSessionId, storage, BIDDER_CODE, BIDDER_VERSION); -const interpretResponse = createInterpretResponseFn(BIDDER_CODE); +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, true); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, true); const getUserSyncs = createUserSyncGetter({ iframeSyncUrl: 'https://sync.cootlogix.com/api/sync/iframe', imageSyncUrl: 'https://sync.cootlogix.com/api/sync/image' }); diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index c689307416f..3e809862b95 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -2,6 +2,14 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/illuminBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { hashCode, extractPID, extractCID, @@ -10,12 +18,7 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/illuminBidAdapter.js'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -325,7 +328,12 @@ describe('IlluminBidAdapter', function () { startdelay: 0 } }, - gpid: '0123456789' + gpid: '0123456789', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -387,6 +395,11 @@ describe('IlluminBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -562,13 +575,13 @@ describe('IlluminBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -576,7 +589,7 @@ describe('IlluminBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -600,8 +613,8 @@ describe('IlluminBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -612,7 +625,7 @@ describe('IlluminBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index ebd11885af4..dd3ea4bfb2b 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -2,6 +2,14 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/kueezRtbBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { hashCode, extractPID, extractCID, @@ -10,12 +18,7 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/kueezRtbBidAdapter.js'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -322,7 +325,12 @@ describe('KueezRtbBidAdapter', function () { startdelay: 0 } }, - gpid: '' + gpid: '', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -384,6 +392,11 @@ describe('KueezRtbBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -526,8 +539,6 @@ describe('KueezRtbBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -580,13 +591,13 @@ describe('KueezRtbBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -594,7 +605,7 @@ describe('KueezRtbBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -618,8 +629,8 @@ describe('KueezRtbBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -630,7 +641,7 @@ describe('KueezRtbBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 93864064dd8..2e4c35cb065 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -2,6 +2,9 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/shinezRtbBidAdapter'; +import { hashCode, extractPID, extractCID, @@ -10,13 +13,12 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/shinezRtbBidAdapter'; -import * as utils from 'src/utils.js'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; +import {parseUrl, deepClone} from 'src/utils.js'; import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; -import {deepAccess} from 'src/utils.js'; export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -181,7 +183,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -328,7 +330,12 @@ describe('ShinezRtbBidAdapter', function () { startdelay: 0 } }, - gpid: '0123456789' + gpid: '0123456789', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -390,6 +397,11 @@ describe('ShinezRtbBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -463,7 +475,7 @@ describe('ShinezRtbBidAdapter', function () { }); it('should get meta from response metaData', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); + const serverResponse = deepClone(SERVER_RESPONSE); serverResponse.body.results[0].metaData = { advertiserDomains: ['sweetgum.io'], agencyName: 'Agency Name', @@ -496,7 +508,7 @@ describe('ShinezRtbBidAdapter', function () { }); it('should take default TTL', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); + const serverResponse = deepClone(SERVER_RESPONSE); delete serverResponse.body.results[0].exp; const responses = adapter.interpretResponse(serverResponse, REQUEST); expect(responses).to.have.length(1); @@ -507,7 +519,7 @@ describe('ShinezRtbBidAdapter', function () { describe('user id system', function () { TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); - const bid = utils.deepClone(BID); + const bid = deepClone(BID); const userId = (function () { switch (idSystemProvider) { @@ -565,13 +577,13 @@ describe('ShinezRtbBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -579,7 +591,7 @@ describe('ShinezRtbBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -603,8 +615,8 @@ describe('ShinezRtbBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -615,7 +627,7 @@ describe('ShinezRtbBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 7559567dcff..d68088affa1 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -2,6 +2,14 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/tagorasBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { hashCode, extractPID, extractCID, @@ -10,12 +18,7 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/tagorasBidAdapter'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -324,7 +327,12 @@ describe('TagorasBidAdapter', function () { startdelay: 0 } }, - gpid: '' + gpid: '', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -385,6 +393,11 @@ describe('TagorasBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [] } }); }); @@ -527,8 +540,6 @@ describe('TagorasBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -581,13 +592,13 @@ describe('TagorasBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -595,7 +606,7 @@ describe('TagorasBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -619,8 +630,8 @@ describe('TagorasBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -631,7 +642,7 @@ describe('TagorasBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 1a73d2f6897..674414bb526 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -2,12 +2,7 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, - hashCode, - getStorageItem, - setStorageItem, - tryParseJSON, - getUniqueDealId, - webSessionId + storage } from 'modules/twistDigitalBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; @@ -18,7 +13,12 @@ import {deepSetValue} from 'src/utils.js'; import { extractPID, extractCID, - extractSubDomain + extractSubDomain, + hashCode, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId } from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -357,7 +357,6 @@ describe('TwistDigitalBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, - webSessionId: webSessionId, mediaTypes: { video: { api: [2], @@ -455,8 +454,7 @@ describe('TwistDigitalBidAdapter', function () { name: 'example.com', segment: [{id: '243'}], }, - ], - webSessionId: webSessionId + ] } }); }); @@ -542,8 +540,7 @@ describe('TwistDigitalBidAdapter', function () { name: 'example.com', segment: [{id: '243'}], }, - ], - webSessionId: webSessionId + ] }; const REQUEST_DATA2 = utils.deepClone(REQUEST_DATA); @@ -740,8 +737,6 @@ describe('TwistDigitalBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -807,13 +802,13 @@ describe('TwistDigitalBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -821,7 +816,7 @@ describe('TwistDigitalBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -845,8 +840,8 @@ describe('TwistDigitalBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -857,7 +852,7 @@ describe('TwistDigitalBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); From 4f2ae43570ec60ea587231f1ebc607f65d176530 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 1 Jul 2024 05:11:21 -0700 Subject: [PATCH 0276/1097] Core: add location method for cross-frame creatives and update creatives (#11863) * Core: add location method for cross-frame creatives and update creatives * improvements --- creative/constants.js | 3 +- creative/crossDomain.js | 20 ++++++++-- .../gpt/x-domain/creative.html | 2 +- .../creative-renderer-display/renderer.js | 2 +- .../creative-renderer-native/renderer.js | 2 +- src/adRendering.js | 27 ++++++++++++- src/constants.js | 2 + src/prebid.js | 3 +- .../spec/creative/crossDomainCreative_spec.js | 40 ++++++++++++++++++- test/spec/unit/pbjs_api_spec.js | 7 +++- 10 files changed, 94 insertions(+), 14 deletions(-) diff --git a/creative/constants.js b/creative/constants.js index d02c4c9d5e4..5f807c69f87 100644 --- a/creative/constants.js +++ b/creative/constants.js @@ -1,6 +1,7 @@ // eslint-disable-next-line prebid/validate-imports -import { AD_RENDER_FAILED_REASON, EVENTS, MESSAGES } from '../src/constants.js'; +import {AD_RENDER_FAILED_REASON, EVENTS, MESSAGES} from '../src/constants.js'; +export {PB_LOCATOR} from '../src/constants.js'; export const MESSAGE_REQUEST = MESSAGES.REQUEST; export const MESSAGE_RESPONSE = MESSAGES.RESPONSE; export const MESSAGE_EVENT = MESSAGES.EVENT; diff --git a/creative/crossDomain.js b/creative/crossDomain.js index a851885bfc0..5799b817aa5 100644 --- a/creative/crossDomain.js +++ b/creative/crossDomain.js @@ -1,9 +1,11 @@ import { ERROR_EXCEPTION, - EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED, + EVENT_AD_RENDER_FAILED, + EVENT_AD_RENDER_SUCCEEDED, MESSAGE_EVENT, MESSAGE_REQUEST, - MESSAGE_RESPONSE + MESSAGE_RESPONSE, + PB_LOCATOR } from './constants.js'; const mkFrame = (() => { @@ -24,14 +26,24 @@ const mkFrame = (() => { }; })(); +function isPrebidWindow(win) { + return !!win.frames[PB_LOCATOR]; +} + export function renderer(win) { + let target = win.parent; + while (target !== win.top && !isPrebidWindow(target)) { + target = target.parent; + } + if (!isPrebidWindow(target)) target = win.parent; + return function ({adId, pubUrl, clickUrl}) { const pubDomain = new URL(pubUrl, window.location).origin; function sendMessage(type, payload, responseListener) { const channel = new MessageChannel(); channel.port1.onmessage = guard(responseListener); - win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]); + target.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]); } function onError(e) { @@ -77,7 +89,7 @@ export function renderer(win) { W.Promise.resolve(W.render(data, {sendMessage, mkFrame}, win)).then( () => sendMessage(MESSAGE_EVENT, {event: EVENT_AD_RENDER_SUCCEEDED}), onError - ) + ); }); win.document.body.appendChild(renderer); } diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 0d0f63cbf1b..e5ddd480a8b 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + + + + + + + + + + + + + + +

Prebid.js Banner Ad Unit Test

+
+ +
+
+ + + diff --git a/test/spec/e2e/banner/basic_banner_ad.spec.js b/test/spec/e2e/banner/basic_banner_ad.spec.js index 511b1002d80..2d055743e8f 100644 --- a/test/spec/e2e/banner/basic_banner_ad.spec.js +++ b/test/spec/e2e/banner/basic_banner_ad.spec.js @@ -2,6 +2,7 @@ const expect = require('chai').expect; const {setupTest, testPageURL} = require('../../../helpers/testing-utils'); const TEST_PAGE_URL = testPageURL('banner.html?pbjs_debug=true'); +const SYNC_PAGE_URL = testPageURL('banner_sync.html?pbjs_debug=true'); const CREATIVE_IFRAME_ID = 'google_ads_iframe_/19968336/header-bid-tag-0_0'; const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="' + CREATIVE_IFRAME_ID + '"]'; @@ -14,20 +15,25 @@ const EXPECTED_TARGETING_KEYS = { 'hb_bidder_appnexus': 'appnexus' }; -setupTest({ - url: TEST_PAGE_URL, - waitFor: CREATIVE_IFRAME_CSS_SELECTOR, - expectGAMCreative: true -}, 'Prebid.js Banner Ad Unit Test', function () { - it('should load the targeting keys with correct values', async function () { - const result = await browser.execute(function () { - return window.pbjs.getAdserverTargeting('div-gpt-ad-1460505748561-1'); - }); - const targetingKeys = result['div-gpt-ad-1460505748561-1']; +Object.entries({ + 'synchronously': SYNC_PAGE_URL, + 'asynchronously': TEST_PAGE_URL, +}).forEach(([t, testPage]) => { + setupTest({ + url: testPage, + waitFor: CREATIVE_IFRAME_CSS_SELECTOR, + expectGAMCreative: true + }, `Prebid.js Banner Ad Unit Test (loading ${t})`, function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { + return window.pbjs.getAdserverTargeting('div-gpt-ad-1460505748561-1'); + }); + const targetingKeys = result['div-gpt-ad-1460505748561-1']; - expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); - expect(targetingKeys.hb_adid).to.be.a('string'); - expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); - expect(targetingKeys.hb_size).to.satisfy((size) => size === '300x250' || '300x600'); + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + expect(targetingKeys.hb_size).to.satisfy((size) => size === '300x250' || '300x600'); + }); }); }); From eada00f54f14d2faf105aef52e76d5a56c3fe05e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 8 Jul 2024 08:18:59 -0700 Subject: [PATCH 0312/1097] Less aggressive linter check workflow (#11945) --- .github/workflows/linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 2142a5d6c35..034e0eddee7 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -30,7 +30,7 @@ jobs: run: npm ci - name: Get the diff - run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge > __changed_files.txt + run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge | grep '^\(modules\|src\|libraries\|creative\)/.*\.js$' > __changed_files.txt || true - name: Run linter on base branch run: npx eslint --no-inline-config --format json $(cat __changed_files.txt | xargs stat --printf '%n\n' 2> /dev/null) > __base.json || true From ba82380de861a19fcb858827de4cdaa42a02c308 Mon Sep 17 00:00:00 2001 From: onlsol <48312668+onlsol@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:15:20 +0400 Subject: [PATCH 0313/1097] DSPx Bid Adapter: add ortb2 content, topics support (#11941) * DSPx Bid Adapter: add ortb2 content, topics support DSPx Bid Adapter: add ortb2 content, topics support * DSPx Bid Adapter: add ortb2 content, topics support --------- Co-authored-by: avj --- modules/dspxBidAdapter.js | 230 +++++++++++++++++------ test/spec/modules/dspxBidAdapter_spec.js | 195 ++++++++++++++++++- 2 files changed, 364 insertions(+), 61 deletions(-) diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 2b819789ec1..b2610610cf2 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,4 +1,5 @@ -import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn} from '../src/utils.js'; +import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn, isEmptyStr, isArray} from '../src/utils.js'; + import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; @@ -7,7 +8,6 @@ import {includes} from '../src/polyfill.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest */ - const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; @@ -63,24 +63,18 @@ export const spec = { rnd: rnd, ref: referrer, bid_id: bidId, - pbver: '$prebid.version$' + pbver: '$prebid.version$', }; + payload.pfilter = {}; if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } if (bidderRequest && bidderRequest.gdprConsent) { - if (payload.pfilter !== undefined) { - if (!payload.pfilter.gdpr_consent) { - payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies; - } - } else { - payload.pfilter = { - 'gdpr_consent': bidderRequest.gdprConsent.consentString, - 'gdpr': bidderRequest.gdprConsent.gdprApplies - }; + if (!payload.pfilter.gdpr_consent) { + payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies; } } @@ -94,48 +88,10 @@ export const spec = { payload.prebidDevMode = 1; } - // fill userId params - if (bidRequest.userId) { - if (bidRequest.userId.netId) { - payload.did_netid = bidRequest.userId.netId; - } - if (bidRequest.userId.id5id) { - payload.did_id5 = bidRequest.userId.id5id.uid || '0'; - if (bidRequest.userId.id5id.ext.linkType !== undefined) { - payload.did_id5_linktype = bidRequest.userId.id5id.ext.linkType; - } - } - let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); - if (uId2) { - payload.did_uid2 = uId2; - } - let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); - if (sharedId) { - payload.did_sharedid = sharedId; - } - let pubcId = deepAccess(bidRequest, 'userId.pubcid'); - if (pubcId) { - payload.did_pubcid = pubcId; - } - let crumbsPubcid = deepAccess(bidRequest, 'crumbs.pubcid'); - if (crumbsPubcid) { - payload.did_cpubcid = crumbsPubcid; - } - } - - if (bidRequest.schain) { - payload.schain = bidRequest.schain; - } - - if (payload.pfilter === undefined || !payload.pfilter.floorprice) { + if (!payload.pfilter.floorprice) { let bidFloor = getBidFloor(bidRequest); if (bidFloor > 0) { - if (payload.pfilter !== undefined) { - payload.pfilter.floorprice = bidFloor; - } else { - payload.pfilter = { 'floorprice': bidFloor }; - } - // payload.bidFloor = bidFloor; + payload.pfilter.floorprice = bidFloor; } } @@ -146,6 +102,7 @@ export const spec = { payload.pbcode = pbcode; } + // media types payload.media_types = convertMediaInfoForRequest(mediaTypesInfo); if (mediaTypesInfo[VIDEO] !== undefined) { payload.vctx = getVideoContext(bidRequest); @@ -159,6 +116,33 @@ export const spec = { .forEach(key => payload.vpl[key] = videoParams[key]); } + // iab content + let content = deepAccess(bidderRequest, 'ortb2.site.content'); + if (content) { + let stringContent = siteContentToString(content); + if (stringContent) { + payload.pfilter.iab_content = stringContent; + } + } + + // Google Topics + const segments = extractUserSegments(bidderRequest); + if (segments) { + assignDefinedValues(payload, { + segtx: segments.segtax, + segcl: segments.segclass, + segs: segments.segments + }); + } + + // schain + if (bidRequest.schain) { + payload.schain = bidRequest.schain; + } + + // fill userId params + fillUsersIds(bidRequest, payload); + return { method: 'GET', url: endpoint, @@ -257,6 +241,74 @@ export const spec = { } } +/** + * Adds userIds to payload + * + * @param bidRequest + * @param payload + */ +function fillUsersIds(bidRequest, payload) { + if (bidRequest.hasOwnProperty('userId')) { + let didMapping = { + did_netid: 'userId.netId', + did_id5: 'userId.id5id.uid', + did_id5_linktype: 'userId.id5id.ext.linkType', + did_uid2: 'userId.uid2', + did_sharedid: 'userId.sharedid', + did_pubcid: 'userId.pubcid', + did_uqid: 'userId.utiq', + did_cruid: 'userId.criteoid', + did_euid: 'userId.euid', + // did_tdid: 'unifiedId', + did_tdid: 'userId.tdid', + did_ppuid: function() { + let path = 'userId.pubProvidedId'; + let value = deepAccess(bidRequest, path); + if (isArray(value)) { + for (const rec of value) { + if (rec.uids && rec.uids.length > 0) { + for (let i = 0; i < rec.uids.length; i++) { + if ('id' in rec.uids[i] && deepAccess(rec.uids[i], 'ext.stype') === 'ppuid') { + return (rec.uids[i].atype ?? '') + ':' + rec.source + ':' + rec.uids[i].id; + } + } + } + } + } + return undefined; + }, + did_cpubcid: 'crumbs.pubcid' + }; + for (let paramName in didMapping) { + let path = didMapping[paramName]; + + // handle function + if (typeof path == 'function') { + let value = path(paramName); + if (value) { + payload[paramName] = value; + } + continue; + } + // direct access + let value = deepAccess(bidRequest, path); + if (typeof value == 'string' || typeof value == 'number') { + payload[paramName] = value; + } else if (typeof value == 'object') { + // trying to find string ID value + if (typeof deepAccess(bidRequest, path + '.id') == 'string') { + payload[paramName] = deepAccess(bidRequest, path + '.id'); + } else { + if (Object.keys(value).length > 0) { + logError(`DSPx: WARNING: fillUserIds had to use first key in user object to get value for bid.userId key: ${path}.`); + payload[paramName] = value[Object.keys(value)[0]]; + } + } + } + } + } +} + function appendToUrl(url, what) { if (!what) { return url; @@ -276,7 +328,7 @@ function objectToQueryString(obj, prefix) { : encodeURIComponent(k) + '=' + encodeURIComponent(v)); } } - return str.join('&'); + return str.filter(n => n).join('&'); } /** @@ -508,4 +560,74 @@ function createOutstreamEmbedCode(bid) { return fragment; } +/** + * Convert site.content to string + * @param content + */ +function siteContentToString(content) { + if (!content) { + return ''; + } + let stringKeys = ['id', 'title', 'series', 'season', 'artist', 'genre', 'isrc', 'url', 'keywords']; + let intKeys = ['episode', 'context', 'livestream']; + let arrKeys = ['cat']; + let retArr = []; + arrKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && Array.isArray(val)) { + retArr.push(k + ':' + val.join('|')); + } + }); + intKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && typeof val === 'number') { + retArr.push(k + ':' + val); + } + }); + stringKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && typeof val === 'string') { + retArr.push(k + ':' + encodeURIComponent(val)); + } + }); + return retArr.join(','); +} + +/** + * Assigns multiple values to the specified keys on an object if the values are not undefined. + * @param {Object} target - The object to which the values will be assigned. + * @param {Object} values - An object containing key-value pairs to be assigned. + */ +function assignDefinedValues(target, values) { + for (const key in values) { + if (values[key] !== undefined) { + target[key] = values[key]; + } + } +} + +/** + * Extracts user segments/topics from the bid request object + * @param {Object} bid - The bid request object + * @returns {{segclass: *, segtax: *, segments: *}|undefined} - User segments/topics or undefined if not found + */ +function extractUserSegments(bid) { + const userData = deepAccess(bid, 'ortb2.user.data') || []; + for (const dataObj of userData) { + if (dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0) { + const segments = dataObj.segment + .filter(seg => seg.id && !isEmptyStr(seg.id) && isFinite(seg.id)) + .map(seg => Number(seg.id)); + if (segments.length > 0) { + return { + segtax: deepAccess(dataObj, 'ext.segtax'), + segclass: deepAccess(dataObj, 'ext.segclass'), + segments: segments.join(',') + }; + } + } + } + return undefined; +} + registerBidder(spec); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 2d9e05cca25..8f02e51f131 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -1,7 +1,9 @@ import { expect } from 'chai'; +import { config } from 'src/config.js'; import { spec } from 'modules/dspxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { deepClone } from '../../../src/utils'; +import {BANNER} from '../../../src/mediaTypes'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; @@ -33,11 +35,18 @@ describe('dspxAdapter', function () { }); it('should return false when required params are not passed', function () { - let invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = { - 'someIncorrectParam': 0 - }; + const invalidBid = { + bidId: '30b31c1838de1e', + bidder: 'dspx', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + someIncorrectParam: 0 + } + } expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -77,7 +86,26 @@ describe('dspxAdapter', function () { 'sharedid': { 'id': '01EXPPGZ9C8NKG1MTXVHV98505', 'third': '01EXPPGZ9C8NKG1MTXVHV98505' - } + }, + 'pubProvidedId': [{ + 'source': 'puburl2.com', + 'uids': [{ + 'id': 'pubid2' + }, { + 'id': 'pubid2-123' + }] + }, { + 'source': 'puburl.com', + 'uids': [{ + 'id': 'pubid1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + }] + }], + 'euid': {}, + 'tdid': 'tdid_ID', }, 'crumbs': { 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61' @@ -228,12 +256,54 @@ describe('dspxAdapter', function () { } }; + // With ortb2 + var bidderRequestWithORTB = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + }, + ortb2: { + source: {}, + site: { + domain: 'buyer', + publisher: { + domain: 'buyer' + }, + page: 'http://buyer/schain.php?ver=8.5.0-pre:latest-dev-build&pbjs_debug=true', + ref: 'http://buyer/pbjsv/', + content: { + id: 'contentID', + episode: 1, + title: 'contentTitle', + series: 'contentSeries', + season: 'contentSeason 3', + artist: 'contentArtist', + genre: 'rock', + isrc: 'contentIsrc', + url: 'https://content-url.com/', + context: 1, + keywords: 'kw1,kw2,keqword 3', + livestream: 0, + cat: [ + 'IAB1-1', + 'IAB1-2', + 'IAB2-10' + ] + } + }, + } + }; + var request1 = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; it('sends bid request to our endpoint via GET', function () { expect(request1.method).to.equal('GET'); expect(request1.url).to.equal(ENDPOINT_URL); let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_id5=ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x&did_id5_linktype=2&did_uid2=456&did_sharedid=01EXPPGZ9C8NKG1MTXVHV98505&did_pubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&did_cpubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&schain%5Bver%5D=1.0&schain%5Bcomplete%5D=1&schain%5Bnodes%5D%5B0%5D%5Basi%5D=example.com&schain%5Bnodes%5D%5B0%5D%5Bsid%5D=0&schain%5Bnodes%5D%5B0%5D%5Bhp%5D=1&schain%5Bnodes%5D%5B0%5D%5Brid%5D=bidrequestid&schain%5Bnodes%5D%5B0%5D%5Bdomain%5D=example.com&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250&schain%5Bver%5D=1.0&schain%5Bcomplete%5D=1&schain%5Bnodes%5D%5B0%5D%5Basi%5D=example.com&schain%5Bnodes%5D%5B0%5D%5Bsid%5D=0&schain%5Bnodes%5D%5B0%5D%5Bhp%5D=1&schain%5Bnodes%5D%5B0%5D%5Brid%5D=bidrequestid&schain%5Bnodes%5D%5B0%5D%5Bdomain%5D=example.com&did_netid=123&did_id5=ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x&did_id5_linktype=2&did_uid2=456&did_sharedid=01EXPPGZ9C8NKG1MTXVHV98505&did_pubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&did_tdid=tdid_ID&did_ppuid=1%3Apuburl.com%3Apubid1&did_cpubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; @@ -281,6 +351,14 @@ describe('dspxAdapter', function () { expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=107&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Btest%5D=1&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bmimes%5D%5B0%5D=video%2Fmp4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); }); + var request7 = spec.buildRequests([bidRequests[5]], bidderRequestWithORTB)[0]; + it('ortb2 iab_content test', function () { + expect(request7.method).to.equal('GET'); + expect(request7.url).to.equal('http://localhost'); + let data = request7.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=107&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Btest%5D=1&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&pfilter%5Biab_content%5D=cat%3AIAB1-1%7CIAB1-2%7CIAB2-10%2Cepisode%3A1%2Ccontext%3A1%2Cid%3AcontentID%2Ctitle%3AcontentTitle%2Cseries%3AcontentSeries%2Cseason%3AcontentSeason%25203%2Cartist%3AcontentArtist%2Cgenre%3Arock%2Cisrc%3AcontentIsrc%2Curl%3Ahttps%253A%252F%252Fcontent-url.com%252F%2Ckeywords%3Akw1%252Ckw2%252Ckeqword%25203&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bmimes%5D%5B0%5D=video%2Fmp4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); + }); + // bidfloor tests const getFloorResponse = {currency: 'EUR', floor: 5}; let testBidRequest = deepClone(bidRequests[1]); @@ -321,6 +399,109 @@ describe('dspxAdapter', function () { }); }); + describe('google topics handling', () => { + afterEach(() => { + config.resetConfig(); + }); + + const REQPARAMS = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + } + }; + + const defaultRequest = { + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'private_auction': 0, + 'geo': { + 'country': 'DE' + } + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e1', + 'bidderRequestId': '22edbae2733bf61', + 'auctionId': '1d1a030790a475', + 'adUnitCode': 'testDiv1', + }; + + it('does pass segtax, segclass, segments for google topics data', () => { + const GOOGLE_TOPICS_DATA = { + ortb2: { + user: { + data: [ + { + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + {id: '717'}, {id: '808'}, + ] + } + ] + }, + }, + } + config.setConfig(GOOGLE_TOPICS_DATA); + const request = spec.buildRequests([defaultRequest], { ...REQPARAMS, ...GOOGLE_TOPICS_DATA })[0]; + expect(request.data).to.contain('segtx=600&segcl=v1&segs=717%2C808'); + }); + + it('does not pass topics params for invalid topics data', () => { + const INVALID_TOPICS_DATA = { + ortb2: { + user: { + data: [ + { + segment: [] + }, + { + segment: [{id: ''}] + }, + { + segment: [{id: null}] + }, + { + segment: [{id: 'dummy'}, {id: '123'}] + }, + { + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + name: 'dummy' + } + ] + }, + ] + } + } + }; + + config.setConfig(INVALID_TOPICS_DATA); + let request = spec.buildRequests([defaultRequest], { ...REQPARAMS, ...INVALID_TOPICS_DATA })[0]; + expect(request.data).to.not.contain('segtax'); + expect(request.data).to.not.contain('segclass'); + expect(request.data).to.not.contain('segments'); + }); + }); + describe('interpretResponse', function () { let serverResponse = { 'body': { From 5d2fc68a0abfe200a219179fafd21dea50657722 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:28:39 +0300 Subject: [PATCH 0314/1097] IntentIQ Analytics Adapter: initial release (#11930) * IntentIQ Analytics Module * update intentiq analytics adapter * remove percentage and change group * update analytics adapter and tests * updated flow * remove 'this' * rename privacy parameter * add callback timeout * Extract only used parameters from CryptoJS * add new unit tests * change callback timeout order * added tests and small fixes * change saving logic * support "html5" and "cookie" storage types * support storage type, update flow * add documentation * small updates * IntentIQ Analytics Module * Multiple modules: clean up unit tests (#11630) * Test chunking * update some bidder eid tests * split eid tests into each userId submodule * cleanup userId_spec * add TEST_PAT config * fix idx, lmp * clean up userId_spec * fix double run, invibes, intentIq * small fixes * undo package-lock changes * update colors, remove empty test * 8pod analytics: clean up interval handler * update intentiq analytics adapter * undo unnecessary changes * undo change by mistake * update params and documentation * turn back storage clearing * fix linter error * fix wording and spelling mistakes * change test to handle full url to check other ids not reported --------- Co-authored-by: Eyvaz <62054743+eyvazahmadzada@users.noreply.github.com> Co-authored-by: Eyvaz Ahmadzada Co-authored-by: Demetrio Girardi --- modules/intentIqAnalyticsAdapter.js | 234 ++++++++++++++++++ modules/intentIqAnalyticsAdapter.md | 27 ++ modules/intentIqIdSystem.md | 0 .../modules/intentIqAnalyticsAdapter_spec.js | 172 +++++++++++++ 4 files changed, 433 insertions(+) create mode 100644 modules/intentIqAnalyticsAdapter.js create mode 100644 modules/intentIqAnalyticsAdapter.md mode change 100755 => 100644 modules/intentIqIdSystem.md create mode 100644 test/spec/modules/intentIqAnalyticsAdapter_spec.js diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js new file mode 100644 index 00000000000..10ce8097bf1 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.js @@ -0,0 +1,234 @@ +import { logInfo, logError } from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { config } from '../src/config.js'; +import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; + +const MODULE_NAME = 'iiqAnalytics' +const analyticsType = 'endpoint'; +const defaultUrl = 'https://reports.intentiq.com/report'; +const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME }); +const prebidVersion = '$prebid.version$'; +export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); + +const FIRST_PARTY_KEY = '_iiq_fdata'; +const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; +const JSVERSION = 0.1 + +const PARAMS_NAMES = { + abTestGroup: 'abGroup', + pbPauseUntil: 'pbPauseUntil', + pbMonitoringEnabled: 'pbMonitoringEnabled', + isInTestGroup: 'isInTestGroup', + enhanceRequests: 'enhanceRequests', + wasSubscribedForPrebid: 'wasSubscribedForPrebid', + hadEids: 'hadEids', + ABTestingConfigurationSource: 'ABTestingConfigurationSource', + lateConfiguration: 'lateConfiguration', + jsversion: 'jsversion', + eidsNames: 'eidsNames', + requestRtt: 'rtt', + clientType: 'clientType', + adserverDeviceType: 'AdserverDeviceType', + terminationCause: 'terminationCause', + callCount: 'callCount', + manualCallCount: 'mcc', + pubprovidedidsFailedToregister: 'ppcc', + noDataCount: 'noDataCount', + profile: 'profile', + isProfileDeterministic: 'pidDeterministic', + siteId: 'sid', + hadEidsInLocalStorage: 'idls', + auctionStartTime: 'ast', + eidsReadTime: 'eidt', + agentId: 'aid', + auctionEidsLength: 'aeidln', + wasServerCalled: 'wsrvcll', + referrer: 'vrref', + isInBrowserBlacklist: 'inbbl', + prebidVersion: 'pbjsver', + partnerId: 'partnerId' +}; + +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { + initOptions: { + lsValueInitialized: false, + partner: null, + fpid: null, + currentGroup: null, + dataInLs: null, + eidl: null, + lsIdsInitialized: false, + manualReport: false + }, + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + bidWon(args); + break; + default: + break; + } + } +}); + +// Events needed +const { + BID_WON +} = EVENTS; + +function readData(key) { + try { + if (storage.hasLocalStorage()) { + return storage.getDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } + } catch (error) { + logError(error); + } +} + +function initLsValues() { + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; + iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData(FIRST_PARTY_KEY)); + let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId'); + if (iiqArr && iiqArr.length > 0) iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; + if (!iiqArr) iiqArr = []; + if (iiqArr.length == 0) { + iiqArr.push({ + 'params': { + 'partner': -1, + 'group': 'U' + } + }) + } + if (iiqArr && iiqArr.length > 0) { + if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) { + iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; + iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; + } + } +} + +function initReadLsIds() { + if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner == -1) return; + try { + iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; + let iData = readData(FIRST_PARTY_DATA_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner) + if (iData) { + iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; + let pData = JSON.parse(iData); + iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; + iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; + } + } catch (e) { + logError(e) + } +} + +function bidWon(args) { + if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { initLsValues(); } + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { initReadLsIds(); } + if (!iiqAnalyticsAnalyticsAdapter.initOptions.manualReport) { + ajax(constructFullUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); + } + + logInfo('IIQ ANALYTICS -> BID WON') +} + +function getRandom(start, end) { + return Math.floor((Math.random() * (end - start + 1)) + start); +} + +export function preparePayload(data) { + let result = getDefaultDataObject(); + + result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; + result[PARAMS_NAMES.prebidVersion] = prebidVersion; + result[PARAMS_NAMES.referrer] = getReferrer(); + + result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; + + result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup == 'A'; + + result[PARAMS_NAMES.agentId] = REPORTER_ID; + + fillPrebidEventData(data, result); + + fillEidsData(result); + + return result; +} + +function fillEidsData(result) { + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + result[PARAMS_NAMES.hadEidsInLocalStorage] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl && iiqAnalyticsAnalyticsAdapter.initOptions.eidl > 0; + result[PARAMS_NAMES.auctionEidsLength] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl || -1; + } +} + +function fillPrebidEventData(eventData, result) { + if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; } + if (eventData.cpm) { result.cpm = eventData.cpm; } + if (eventData.currency) { result.currency = eventData.currency; } + if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; } + if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; } + if (eventData.status) { result.status = eventData.status; } + if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; } + + result.biddingPlatformId = 1; + result.partnerAuctionId = 'BW'; +} + +function getDefaultDataObject() { + return { + 'inbbl': false, + 'pbjsver': prebidVersion, + 'partnerAuctionId': 'BW', + 'reportSource': 'pbjs', + 'abGroup': 'U', + 'jsversion': JSVERSION, + 'partnerId': -1, + 'biddingPlatformId': 1, + 'idls': false, + 'ast': -1, + 'aeidln': -1 + } +} + +function constructFullUrl(data) { + let report = [] + data = btoa(JSON.stringify(data)) + report.push(data) + return defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + + '&mct=1' + + ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid) + ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + + '&agid=' + REPORTER_ID + + '&jsver=' + JSVERSION + + '&vrref=' + getReferrer() + + '&source=pbjs' + + '&payload=' + JSON.stringify(report) +} + +export function getReferrer() { + return document.referrer; +} + +iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics; + +iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { + iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: iiqAnalyticsAnalyticsAdapter, + code: MODULE_NAME +}); + +export default iiqAnalyticsAnalyticsAdapter; diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md new file mode 100644 index 00000000000..905d88a9b21 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: iiqAnalytics +Module Type: Analytics Adapter +Maintainer: julian@intentiq.com + +# Description + +By using this Intent IQ adapter, you will be able to obtain comprehensive analytics and metrics regarding the performance of the Intent IQ Unified ID module. This includes how the module impacts your revenue, CPMs, and fill rates related to bidders and domains. + +## Intent IQ Universal ID Registration + +No registration for this module is required. + +## Intent IQ Universal IDConfiguration + +IMPORTANT: only effective when Intent IQ Universal ID module is installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) + +No additional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module. + +#### Example Configuration + +```js +pbjs.enableAnalytics({ + provider: 'iiqAnalytics' +}); +``` diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md old mode 100755 new mode 100644 diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..9a204dde31b --- /dev/null +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -0,0 +1,172 @@ +import { expect } from 'chai'; +import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; +import { config } from 'src/config.js'; +import { EVENTS } from 'src/constants.js'; +import * as events from 'src/events.js'; +import { getStorageManager } from 'src/storageManager.js'; +import sinon from 'sinon'; +import { FIRST_PARTY_KEY } from '../../../modules/intentIqIdSystem'; +import { REPORTER_ID, getReferrer, preparePayload } from '../../../modules/intentIqAnalyticsAdapter'; + +const partner = 10; +const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; + +const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); + +const USERID_CONFIG = [ + { + 'name': 'intentIqId', + 'params': { + 'partner': partner, + 'unpack': null, + }, + 'storage': { + 'type': 'html5', + 'name': 'intentIqId', + 'expires': 60, + 'refreshInSeconds': 14400 + } + } +]; + +let wonRequest = { + 'bidderCode': 'pubmatic', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '23caeb34c55da51', + 'requestId': '87615b45ca4973', + 'transactionId': '5e69fd76-8c86-496a-85ce-41ae55787a50', + 'auctionId': '0cbd3a43-ff45-47b8-b002-16d3946b23bf', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 5, + 'currency': 'USD', + 'ttl': 300, + 'referrer': '', + 'adapterCode': 'pubmatic', + 'originalCpm': 5, + 'originalCurrency': 'USD', + 'responseTimestamp': 1669644710345, + 'requestTimestamp': 1669644710109, + 'bidder': 'testbidder', + 'adUnitCode': 'addUnitCode', + 'timeToRespond': 236, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '728x90', + 'status': 'rendered' +}; + +describe('IntentIQ tests all', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG); + sinon.stub(events, 'getEvents').returns([]); + iiqAnalyticsAnalyticsAdapter.enableAnalytics({ + provider: 'iiqAnalytics', + }); + iiqAnalyticsAnalyticsAdapter.initOptions = { + lsValueInitialized: false, + partner: null, + fpid: null, + userGroup: null, + currentGroup: null, + dataInLs: null, + eidl: null, + lsIdsInitialized: false, + manualReport: false + }; + if (iiqAnalyticsAnalyticsAdapter.track.restore) { + iiqAnalyticsAnalyticsAdapter.track.restore(); + } + sinon.spy(iiqAnalyticsAnalyticsAdapter, 'track'); + }); + + afterEach(function () { + logErrorStub.restore(); + config.getConfig.restore(); + events.getEvents.restore(); + iiqAnalyticsAnalyticsAdapter.disableAnalytics(); + if (iiqAnalyticsAnalyticsAdapter.track.restore) { + iiqAnalyticsAnalyticsAdapter.track.restore(); + } + localStorage.clear(); + server.reset(); + }); + + it('IIQ Analytical Adapter bid win report', function () { + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); + expect(request.url).to.contain('&jsver=0.1&vrref=http://localhost:9876/'); + expect(request.url).to.contain('&payload='); + expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + }); + + it('should initialize with default configurations', function () { + expect(iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized).to.be.false; + }); + + it('should handle BID_WON event with group configuration from local storage', function () { + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); + expect(request.url).to.contain('&jsver=0.1&vrref=http://localhost:9876/'); + expect(request.url).to.contain('iiqid=testpcid'); + }); + + it('should handle BID_WON event with default group configuration', function () { + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + const data = preparePayload(wonRequest); + const base64String = btoa(JSON.stringify(data)); + const payload = `[%22${base64String}%22]`; + expect(request.url).to.equal( + `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344&agid=${REPORTER_ID}&jsver=0.1&vrref=${getReferrer()}&source=pbjs&payload=${payload}` + ); + }); + + it('should not send request if manualReport is true', function () { + iiqAnalyticsAnalyticsAdapter.initOptions.manualReport = true; + events.emit(EVENTS.BID_WON, wonRequest); + expect(server.requests.length).to.equal(0); + }); + + it('should read data from local storage', function () { + localStorage.setItem(FIRST_PARTY_KEY, '{"group": "A"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid", "eidl": 10}'); + events.emit(EVENTS.BID_WON, wonRequest); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs).to.equal('testpcid'); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.eidl).to.equal(10); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('A'); + }); + + it('should handle initialization values from local storage', function () { + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); + events.emit(EVENTS.BID_WON, wonRequest); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('B'); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; + }); +}); From 2a644906bd60276ba26cfc86787f39b0db3c89f4 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 8 Jul 2024 14:47:49 -0700 Subject: [PATCH 0315/1097] UA utils: fix tests (#11947) --- test/spec/libraries/userAgentUtils_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spec/libraries/userAgentUtils_spec.js b/test/spec/libraries/userAgentUtils_spec.js index 17a36d6dbf9..0a72b51588b 100644 --- a/test/spec/libraries/userAgentUtils_spec.js +++ b/test/spec/libraries/userAgentUtils_spec.js @@ -102,6 +102,7 @@ describe('Test user agent categorization', () => { it('user agent is other', () => { const otherUserAgent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' window.navigator.__defineGetter__('userAgent', () => otherUserAgent); + window.navigator.__defineGetter__('appVersion', () => ''); expect(getOS()).to.equal(osTypes.OTHER); }) }) From e8964d4affcdadede55489d9b02e2a29e89422b8 Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Mon, 8 Jul 2024 17:53:39 -0400 Subject: [PATCH 0316/1097] Core: Add Session Storage Manager & Contxtful RTD Provider: use session storage (#11928) * feat: sessionstorage in storagemanager * fix: use storage manager * fix: lint * fix: storage from rtd * doc: no changes needed * refactor storageManager/sessionStorage --------- Co-authored-by: Demetrio Girardi --- modules/contxtfulRtdProvider.js | 23 +++- modules/contxtfulRtdProvider.md | 1 + src/storageManager.js | 131 +++++++++------------ test/spec/unit/core/storageManager_spec.js | 119 +++++++++++-------- 4 files changed, 146 insertions(+), 128 deletions(-) diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 30bc1e87775..03050a6a64f 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -17,12 +17,19 @@ import { isArray, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; const MODULE_NAME = 'contxtful'; const MODULE = `${MODULE_NAME}RtdProvider`; const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io'; +const storageManager = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: MODULE_NAME +}); + let rxApi = null; let isFirstBidRequestCall = true; @@ -35,10 +42,19 @@ function getRxEngineReceptivity(requester) { return rxApi?.receptivity(requester); } +function getItemFromSessionStorage(key) { + let value = null; + try { + // Use the Storage Manager + value = storageManager.getDataFromSessionStorage(key, null); + } catch (error) { + } + + return value; +} + function loadSessionReceptivity(requester) { - // TODO: commented out because of rule violations - /* - let sessionStorageValue = sessionStorage.getItem(requester); + let sessionStorageValue = getItemFromSessionStorage(requester); if (!sessionStorageValue) { return null; } @@ -56,7 +72,6 @@ function loadSessionReceptivity(requester) { } catch { return null; } - */ } /** diff --git a/modules/contxtfulRtdProvider.md b/modules/contxtfulRtdProvider.md index 71a641db4ad..622b353c27a 100644 --- a/modules/contxtfulRtdProvider.md +++ b/modules/contxtfulRtdProvider.md @@ -63,6 +63,7 @@ pbjs.setConfig({ } }); ``` + ## Parameters | Name | Type | Scope | Description | diff --git a/src/storageManager.js b/src/storageManager.js index d2d26461eac..493c44f056e 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -88,27 +88,6 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is return schedule(cb, STORAGE_TYPE_COOKIES, done); }; - /** - * @returns {boolean} - */ - const localStorageIsEnabled = function (done) { - let cb = function (result) { - if (result && result.valid) { - try { - localStorage.setItem('prebid.cookieTest', '1'); - return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) { - } finally { - try { - localStorage.removeItem('prebid.cookieTest'); - } catch (error) {} - } - } - return false; - } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } - /** * @returns {boolean} */ @@ -122,60 +101,69 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is return schedule(cb, STORAGE_TYPE_COOKIES, done); } - /** - * @param {string} key - * @param {string} value - */ - const setDataInLocalStorage = function (key, value, done) { - let cb = function (result) { - if (result && result.valid && hasLocalStorage()) { - window.localStorage.setItem(key, value); - } - } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } - - /** - * @param {string} key - * @returns {(string|null)} - */ - const getDataFromLocalStorage = function (key, done) { - let cb = function (result) { - if (result && result.valid && hasLocalStorage()) { - return window.localStorage.getItem(key); - } - return null; - } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } + function storageMethods(name) { + const capName = name.charAt(0).toUpperCase() + name.substring(1); + const backend = () => window[name]; - /** - * @param {string} key - */ - const removeDataFromLocalStorage = function (key, done) { - let cb = function (result) { - if (result && result.valid && hasLocalStorage()) { - window.localStorage.removeItem(key); + const hasStorage = function (done) { + let cb = function (result) { + if (result && result.valid) { + try { + return !!backend(); + } catch (e) { + logError(`${name} api disabled`); + } + } + return false; } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } - /** - * @returns {boolean} - */ - const hasLocalStorage = function (done) { - let cb = function (result) { - if (result && result.valid) { - try { - return !!window.localStorage; - } catch (e) { - logError('Local storage api disabled'); + return { + [`has${capName}`]: hasStorage, + [`${name}IsEnabled`](done) { + let cb = function (result) { + if (result && result.valid) { + try { + backend().setItem('prebid.cookieTest', '1'); + return backend().getItem('prebid.cookieTest') === '1'; + } catch (error) { + } finally { + try { + backend().removeItem('prebid.cookieTest'); + } catch (error) {} + } + } + return false; } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); + }, + [`setDataIn${capName}`](key, value, done) { + let cb = function (result) { + if (result && result.valid && hasStorage()) { + backend().setItem(key, value); + } + } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); + }, + [`getDataFrom${capName}`](key, done) { + let cb = function (result) { + if (result && result.valid && hasStorage()) { + return backend().getItem(key); + } + return null; + } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); + }, + [`removeDataFrom${capName}`](key, done) { + let cb = function (result) { + if (result && result.valid && hasStorage()) { + backend().removeItem(key); + } + } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } - return false; } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** @@ -211,12 +199,9 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is return { setCookie, getCookie, - localStorageIsEnabled, cookiesAreEnabled, - setDataInLocalStorage, - getDataFromLocalStorage, - removeDataFromLocalStorage, - hasLocalStorage, + ...storageMethods('localStorage'), + ...storageMethods('sessionStorage'), findSimilarCookies } } diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index edead126c2c..25471a80677 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -26,15 +26,15 @@ describe('storage manager', function() { hook.ready(); }); - beforeEach(function() { + beforeEach(function () { resetData(); }); - afterEach(function() { + afterEach(function () { config.resetConfig(); }) - it('should allow to set cookie for core modules without checking gdpr enforcements', function() { + it('should allow to set cookie for core modules without checking gdpr enforcements', function () { const coreStorage = getCoreStorageManager(); let date = new Date(); date.setTime(date.getTime() + (24 * 60 * 60 * 1000)); @@ -43,7 +43,7 @@ describe('storage manager', function() { expect(coreStorage.getCookie('hello')).to.equal('world'); }); - it('should add done callbacks to storageCallbacks array', function() { + it('should add done callbacks to storageCallbacks array', function () { let noop = sinon.spy(); const coreStorage = newStorageManager(); @@ -55,11 +55,15 @@ describe('storage manager', function() { coreStorage.getDataFromLocalStorage('foo', noop); coreStorage.removeDataFromLocalStorage('foo', noop); coreStorage.hasLocalStorage(noop); + coreStorage.setDataInSessionStorage('foo', 'bar', noop); + coreStorage.getDataFromSessionStorage('foo', noop); + coreStorage.removeDataFromSessionStorage('foo', noop); + coreStorage.hasSessionStorage(noop); - expect(storageCallbacks.length).to.equal(8); + expect(storageCallbacks.length).to.equal(12); }); - it('should allow bidder to access device if gdpr enforcement module is not included', function() { + it('should allow bidder to access device if gdpr enforcement module is not included', function () { let deviceAccessSpy = sinon.spy(utils, 'hasDeviceAccess'); const storage = newStorageManager(); storage.setCookie('foo1', 'baz1'); @@ -87,12 +91,16 @@ describe('storage manager', function() { })); }); - it('should deny access if activity is denied', () => { - isAllowed.returns(false); - const mgr = mkManager(MODULE_TYPE_PREBID, 'mockMod'); - mgr.setDataInLocalStorage('testKey', 'val'); - expect(mgr.getDataFromLocalStorage('testKey')).to.not.exist; - }); + ['Local', 'Session'].forEach(type => { + describe(`${type} storage`, () => { + it('should deny access if activity is denied', () => { + isAllowed.returns(false); + const mgr = mkManager(MODULE_TYPE_PREBID, 'mockMod'); + mgr[`setDataIn${type}Storage`]('testKey', 'val'); + expect(mgr[`getDataFrom${type}Storage`]('testKey')).to.not.exist; + }); + }) + }) it('should use bidder aliases when possible', () => { adapterManager.registerBidAdapter({callBids: sinon.stub(), getSpec: () => ({})}, 'mockBidder'); @@ -103,57 +111,66 @@ describe('storage manager', function() { [ACTIVITY_PARAM_COMPONENT_NAME]: 'mockAlias' })) }) - }) - - describe('localstorage forbidden access in 3rd-party context', function() { - let errorLogSpy; - let originalLocalStorage; - const localStorageMock = { get: () => { throw Error } }; + }); - beforeEach(function() { - originalLocalStorage = window.localStorage; - Object.defineProperty(window, 'localStorage', localStorageMock); - errorLogSpy = sinon.spy(utils, 'logError'); - }); + ['localStorage', 'sessionStorage'].forEach(storage => { + const Storage = storage.charAt(0).toUpperCase() + storage.substring(1); - afterEach(function() { - Object.defineProperty(window, 'localStorage', { get: () => originalLocalStorage }); - errorLogSpy.restore(); - }) + describe(`${storage} forbidden access in 3rd-party context`, function () { + let errorLogSpy; + let originalStorage; + const storageMock = { + get: () => { + throw Error + } + }; - it('should not throw if the localstorage is not accessible when setting/getting/removing from localstorage', function() { - const coreStorage = newStorageManager(); + beforeEach(function () { + originalStorage = window[storage]; + Object.defineProperty(window, storage, storageMock); + errorLogSpy = sinon.spy(utils, 'logError'); + }); - coreStorage.setDataInLocalStorage('key', 'value'); - const val = coreStorage.getDataFromLocalStorage('key'); - coreStorage.removeDataFromLocalStorage('key'); + afterEach(function () { + Object.defineProperty(window, storage, {get: () => originalStorage}); + errorLogSpy.restore(); + }) - expect(val).to.be.null; - sinon.assert.calledThrice(errorLogSpy); - }) - }) + it('should not throw if storage is not accessible when setting/getting/removing', function () { + const coreStorage = newStorageManager(); - describe('localstorage is enabled', function() { - let localStorage; + coreStorage[`setDataIn${Storage}`]('key', 'value'); + const val = coreStorage[`getDataFrom${Storage}`]('key'); + coreStorage[`removeDataFrom${Storage}`]('key'); - beforeEach(function() { - localStorage = window.localStorage; - localStorage.clear(); + expect(val).to.be.null; + sinon.assert.calledThrice(errorLogSpy); + }); }); + }); - afterEach(function() { - localStorage.clear(); - }) + ['localStorage', 'sessionStorage'].forEach(storage => { + describe(`${storage} is enabled`, function () { + let store; + beforeEach(function () { + store = window[storage]; + store.clear(); + }); - it('should remove side-effect after checking', function () { - const storage = newStorageManager(); + afterEach(function () { + store.clear(); + }) - localStorage.setItem('unrelated', 'dummy'); - const val = storage.localStorageIsEnabled(); + it('should remove side-effect after checking', function () { + const storageMgr = newStorageManager(); - expect(val).to.be.true; - expect(localStorage.length).to.be.eq(1); - expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); + store.setItem('unrelated', 'dummy'); + const val = storageMgr[`${storage}IsEnabled`](); + + expect(val).to.be.true; + expect(store.length).to.be.eq(1); + expect(store.getItem('unrelated')).to.be.eq('dummy'); + }); }); }); From c4360d36a3867f27fb6e6a86b84151f5b0b71298 Mon Sep 17 00:00:00 2001 From: Bernhard Bohne Date: Tue, 9 Jul 2024 13:54:55 +0200 Subject: [PATCH 0317/1097] Smaato: Add UserSyncs (#11932) --- modules/smaatoBidAdapter.js | 19 ++++++++++- test/spec/modules/smaatoBidAdapter_spec.js | 37 +++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index f6dbcfe6071..f8a363cb084 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -19,10 +19,11 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.1' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.2' const TTL = 300; const CURRENCY = 'USD'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; +const SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' export const spec = { code: BIDDER_CODE, @@ -196,6 +197,22 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + if (syncOptions && syncOptions.pixelEnabled) { + let gdprParams = ''; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `&gdpr_consent=${gdprConsent.consentString}`; + } + } + + return [{ + type: 'image', + url: SYNC_URL + gdprParams + }]; + } + return []; } } diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 9052fbbcdfd..302a5fa1aa6 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -12,6 +12,8 @@ import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +const SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' + const ADTYPE_IMG = 'Img'; const ADTYPE_VIDEO = 'Video'; const ADTYPE_NATIVE = 'Native'; @@ -1667,8 +1669,41 @@ describe('smaatoBidAdapterTest', () => { }); describe('getUserSyncs', () => { - it('returns no pixels', () => { + it('when pixelEnabled false then returns no pixels', () => { expect(spec.getUserSyncs()).to.be.empty }) + + it('when pixelEnabled true then returns pixel', () => { + expect(spec.getUserSyncs({pixelEnabled: true}, null, null, null)).to.deep.equal( + [ + { + type: 'image', + url: SYNC_URL + } + ] + ) + }) + + it('when pixelEnabled true and gdprConsent then returns pixel with gdpr params', () => { + expect(spec.getUserSyncs({pixelEnabled: true}, null, {gdprApplies: true, consentString: CONSENT_STRING}, null)).to.deep.equal( + [ + { + type: 'image', + url: `${SYNC_URL}&gdpr=1&gdpr_consent=${CONSENT_STRING}` + } + ] + ) + }) + + it('when pixelEnabled true and gdprConsent without gdpr then returns pixel with gdpr_consent', () => { + expect(spec.getUserSyncs({pixelEnabled: true}, null, {consentString: CONSENT_STRING}, null), null).to.deep.equal( + [ + { + type: 'image', + url: `${SYNC_URL}&gdpr_consent=${CONSENT_STRING}` + } + ] + ) + }) }) }); From b26107c586686f2247cf67efac7d9ad3d835d6a8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 9 Jul 2024 15:32:29 -0400 Subject: [PATCH 0318/1097] Validation module: jsdoc fixes (#11952) * Validation module: jsdoc fixes * Update eids.js * Update eids.js * Update index.js --- modules/rtdModule/index.js | 8 +++--- modules/userId/eids.js | 3 +++ modules/validationFpdModule/index.js | 39 ++++++++++++++++------------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 0c654fc28b0..18736c6b0ec 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -188,10 +188,12 @@ let _dataProviders = []; let _userConsent; /** - * Register a RTD submodule. + * Register a Real-Time Data (RTD) submodule. * - * @param {RtdSubmodule} submodule - * @returns {function()} a de-registration function that will unregister the module when called. + * @param {Object} submodule The RTD submodule to register. + * @param {string} submodule.name The name of the RTD submodule. + * @param {number} [submodule.gvlid] The Global Vendor List ID (GVLID) of the RTD submodule. + * @returns {function(): void} A de-registration function that will unregister the module when called. */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); diff --git a/modules/userId/eids.js b/modules/userId/eids.js index e5f7e3b8fb2..930dd34b23d 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,4 +1,7 @@ import {deepAccess, deepClone, isFn, isPlainObject, isStr} from '../../src/utils.js'; +/* + * @typedef {import('../modules/userId/index.js').SubmoduleContainer} SubmoduleContainer + */ export const EID_CONFIG = new Map(); diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js index 70af9d30ec3..2330c41099c 100644 --- a/modules/validationFpdModule/index.js +++ b/modules/validationFpdModule/index.js @@ -13,8 +13,8 @@ let optout; /** * Check if data passed is empty - * @param {*} value to test against - * @returns {Boolean} is value empty + * @param {*} data to test against + * @returns {Boolean} is data empty */ function isEmptyData(data) { let check = true; @@ -30,10 +30,10 @@ function isEmptyData(data) { /** * Check if required keys exist in data object - * @param {Object} data object - * @param {Array} array of required keys - * @param {String} object path (for printing warning) - * @param {Number} index of object value in the data array (for printing warning) + * @param {Object} obj data object + * @param {Array} required array of required keys + * @param {String} parent object path (for printing warning) + * @param {Number} i index of object value in the data array (for printing warning) * @returns {Boolean} is requirements fulfilled */ function getRequiredData(obj, required, parent, i) { @@ -51,8 +51,8 @@ function getRequiredData(obj, required, parent, i) { /** * Check if data type is valid - * @param {*} value to test against - * @param {Object} object containing type definition and if should be array bool + * @param {*} data value to test against + * @param {Object} mapping object containing type definition and if should be array bool * @returns {Boolean} is type fulfilled */ function typeValidation(data, mapping) { @@ -77,10 +77,10 @@ function typeValidation(data, mapping) { /** * Validates ortb2 data arrays and filters out invalid data - * @param {Array} ortb2 data array - * @param {Object} object defining child type and if array - * @param {String} config path of data array - * @param {String} parent path for logging warnings + * @param {Array} arr ortb2 data array + * @param {Object} child object defining child type and if array + * @param {String} path config path of data array + * @param {String} parent parent path for logging warnings * @returns {Array} validated/filtered data */ export function filterArrayData(arr, child, path, parent) { @@ -136,9 +136,9 @@ export function filterArrayData(arr, child, path, parent) { /** * Validates ortb2 object and filters out invalid data - * @param {Object} ortb2 object - * @param {String} config path of data array - * @param {String} parent path for logging warnings + * @param {Object} fpd ortb2 object + * @param {String} path config path of data array + * @param {String} parent parent path for logging warnings * @returns {Object} validated/filtered data */ export function validateFpd(fpd, path = '', parent = '') { @@ -190,6 +190,8 @@ export function validateFpd(fpd, path = '', parent = '') { /** * Run validation on global and bidder config data for ortb2 + * @param {Object} data global and bidder config data + * @returns {Object} validated data */ function runValidations(data) { return { @@ -200,6 +202,9 @@ function runValidations(data) { /** * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init + * @param {Object} fpdConf configuration object + * @param {Object} data ortb2 data + * @returns {Object} processed data */ export function processFpd(fpdConf, data) { // Checks for existsnece of pubcid optout cookie/storage @@ -210,11 +215,11 @@ export function processFpd(fpdConf, data) { return (!fpdConf.skipValidations) ? runValidations(data) : data; } -/** @type {firstPartyDataSubmodule} */ +/** @type {{name: string, queue: number, processFpd: function}} */ export const validationSubmodule = { name: 'validation', queue: 1, processFpd } -submodule('firstPartyData', validationSubmodule) +submodule('firstPartyData', validationSubmodule); From 4343dda4f8608e7e74965e9ba60f3661785e7ac2 Mon Sep 17 00:00:00 2001 From: Denis Anoykin Date: Wed, 10 Jul 2024 04:12:14 +0200 Subject: [PATCH 0319/1097] AdvRed Analytics Adapter : initial release (#11703) * Initial version of AdvRed analytics adapter * Initial version of AdvRed analytics adapter --- modules/advRedAnalyticsAdapter.js | 198 ++++++++++++++++++ modules/advRedAnalyticsAdapter.md | 30 +++ .../modules/advRedAnalyticsAdapter_spec.js | 114 ++++++++++ 3 files changed, 342 insertions(+) create mode 100644 modules/advRedAnalyticsAdapter.js create mode 100644 modules/advRedAnalyticsAdapter.md create mode 100644 test/spec/modules/advRedAnalyticsAdapter_spec.js diff --git a/modules/advRedAnalyticsAdapter.js b/modules/advRedAnalyticsAdapter.js new file mode 100644 index 00000000000..8ad30ed351d --- /dev/null +++ b/modules/advRedAnalyticsAdapter.js @@ -0,0 +1,198 @@ +import {generateUUID, logInfo} from '../src/utils.js' +import {ajaxBuilder} from '../src/ajax.js' +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' +import adapterManager from '../src/adapterManager.js' +import {EVENTS} from '../src/constants.js' +import {getRefererInfo} from '../src/refererDetection.js'; + +/** + * advRedAnalyticsAdapter.js - analytics adapter for AdvRed + */ +const DEFAULT_EVENT_URL = 'https://api.adv.red/api/event' + +let ajax = ajaxBuilder(10000) +let pwId +let initOptions +let flushInterval +let queue = [] + +let advRedAnalytics = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType: 'endpoint'}), { + track({eventType, args}) { + handleEvent(eventType, args) + } +}) + +function sendEvents() { + if (queue.length > 0) { + const message = { + pwId: pwId, + publisherId: initOptions.publisherId, + events: queue, + pageUrl: getRefererInfo().page + } + queue = [] + + const url = initOptions.url ? initOptions.url : DEFAULT_EVENT_URL + ajax( + url, + () => logInfo('AdvRed Analytics sent ' + queue.length + ' events'), + JSON.stringify(message), + { + method: 'POST', + contentType: 'application/json', + withCredentials: true + } + ) + } +} + +function convertAdUnit(adUnit) { + if (!adUnit) return adUnit + + const shortAdUnit = {} + shortAdUnit.code = adUnit.code + shortAdUnit.sizes = adUnit.sizes + return shortAdUnit +} + +function convertBid(bid) { + if (!bid) return bid + + const shortBid = {} + shortBid.adUnitCode = bid.adUnitCode + shortBid.bidder = bid.bidder + shortBid.cpm = bid.cpm + shortBid.currency = bid.currency + shortBid.mediaTypes = bid.mediaTypes + shortBid.sizes = bid.sizes + shortBid.serverResponseTimeMs = bid.serverResponseTimeMs + return shortBid +} + +function convertAuctionInit(origEvent) { + let shortEvent = {} + shortEvent.auctionId = origEvent.auctionId + shortEvent.timeout = origEvent.timeout + shortEvent.adUnits = origEvent.adUnits && origEvent.adUnits.map(convertAdUnit) + return shortEvent +} + +function convertBidRequested(origEvent) { + let shortEvent = {} + shortEvent.bidderCode = origEvent.bidderCode + shortEvent.bids = origEvent.bids && origEvent.bids.map(convertBid) + shortEvent.timeout = origEvent.timeout + return shortEvent +} + +function convertBidTimeout(origEvent) { + let shortEvent = {} + shortEvent.bids = origEvent && origEvent.map ? origEvent.map(convertBid) : origEvent + return shortEvent +} + +function convertBidderError(origEvent) { + let shortEvent = {} + shortEvent.bids = origEvent.bidderRequest && origEvent.bidderRequest.bids && origEvent.bidderRequest.bids.map(convertBid) + return shortEvent +} + +function convertAuctionEnd(origEvent) { + let shortEvent = {} + shortEvent.adUnitCodes = origEvent.adUnitCodes + shortEvent.bidsReceived = origEvent.bidsReceived && origEvent.bidsReceived.map(convertBid) + shortEvent.noBids = origEvent.noBids && origEvent.noBids.map(convertBid) + return shortEvent +} + +function convertBidWon(origEvent) { + let shortEvent = {} + shortEvent.adUnitCode = origEvent.adUnitCode + shortEvent.bidderCode = origEvent.bidderCode + shortEvent.mediaType = origEvent.mediaType + shortEvent.netRevenue = origEvent.netRevenue + shortEvent.cpm = origEvent.cpm + shortEvent.size = origEvent.size + shortEvent.currency = origEvent.currency + return shortEvent +} + +function handleEvent(eventType, origEvent) { + try { + origEvent = origEvent ? JSON.parse(JSON.stringify(origEvent)) : {} + } catch (e) { + } + + let shortEvent + switch (eventType) { + case EVENTS.AUCTION_INIT: { + shortEvent = convertAuctionInit(origEvent) + break + } + case EVENTS.BID_REQUESTED: { + shortEvent = convertBidRequested(origEvent) + break + } + case EVENTS.BID_TIMEOUT: { + shortEvent = convertBidTimeout(origEvent) + break + } + case EVENTS.BIDDER_ERROR: { + shortEvent = convertBidderError(origEvent) + break + } + case EVENTS.AUCTION_END: { + shortEvent = convertAuctionEnd(origEvent) + break + } + case EVENTS.BID_WON: { + shortEvent = convertBidWon(origEvent) + break + } + default: + return + } + + shortEvent.eventType = eventType + shortEvent.auctionId = origEvent.auctionId + shortEvent.timestamp = origEvent.timestamp || Date.now() + + sendEvent(shortEvent) +} + +function sendEvent(event) { + queue.push(event) + + if (event.eventType === EVENTS.AUCTION_END) { + sendEvents() + } +} + +advRedAnalytics.originEnableAnalytics = advRedAnalytics.enableAnalytics +advRedAnalytics.enableAnalytics = function (config) { + initOptions = config.options || {} + pwId = generateUUID() + flushInterval = setInterval(sendEvents, 1000) + + advRedAnalytics.originEnableAnalytics(config) +} + +advRedAnalytics.originDisableAnalytics = advRedAnalytics.disableAnalytics +advRedAnalytics.disableAnalytics = function () { + clearInterval(flushInterval) + sendEvents() + advRedAnalytics.originDisableAnalytics() +} + +adapterManager.registerAnalyticsAdapter({ + adapter: advRedAnalytics, + code: 'advRed' +}) + +advRedAnalytics.getOptions = function () { + return initOptions +} + +advRedAnalytics.sendEvents = sendEvents + +export default advRedAnalytics diff --git a/modules/advRedAnalyticsAdapter.md b/modules/advRedAnalyticsAdapter.md new file mode 100644 index 00000000000..59345dfd01e --- /dev/null +++ b/modules/advRedAnalyticsAdapter.md @@ -0,0 +1,30 @@ +# Overview +``` +Module Name: AdvRed Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@adv.red +``` + +### Usage + +The AdvRed analytics adapter can be used by all clients after approval. For more information, +please visit + +### Analytics Options +| Param enableAnalytics | Scope | Type | Description | Example | +|-----------------------|----------|--------|------------------------------------------------------|----------------------------------------| +| provider | Required | String | The name of this Adapter. | `'advRed'` | +| params | Required | Object | Details of module params. | | +| params.publisherId | Required | String | This is the Publisher ID value obtained from AdvRed. | `'123456'` | +| params.url | Optional | String | Custom URL of the endpoint to collect the events | `'https://pub1.api.adv.red/api/event'` | + +### Example Configuration + +```javascript +pbjs.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: '123456' // change to the Publisher ID you received from AdvRed + } +}); +``` diff --git a/test/spec/modules/advRedAnalyticsAdapter_spec.js b/test/spec/modules/advRedAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..c493710ab53 --- /dev/null +++ b/test/spec/modules/advRedAnalyticsAdapter_spec.js @@ -0,0 +1,114 @@ +import advRedAnalytics from 'modules/advRedAnalyticsAdapter.js'; +import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; +import {expectEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; +import sinon from 'sinon'; + +let events = require('src/events'); + +describe('AdvRed Analytics Adapter', function () { + let bidWonEvent = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'adId': '1ebb82ec35375e', + 'mediaType': 'banner', + 'cpm': 0.5, + 'requestId': '1582271863760569973', + 'creative_id': '96846035', + 'creativeId': '96846035', + 'ttl': 60, + 'currency': 'USD', + 'netRevenue': true, + 'auctionId': '9c7b70b9-b6ab-4439-9e71-b7b382797c18', + 'responseTimestamp': 1537521629657, + 'requestTimestamp': 1537521629331, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 326, + 'size': '300x250', + 'status': 'rendered', + 'eventType': 'bidWon', + 'ad': 'some ad', + 'adUrl': 'ad url' + }; + + describe('AdvRed Analytic tests', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + advRedAnalytics.disableAnalytics(); + events.getEvents.restore(); + }); + + it('support custom endpoint', function () { + let custom_endpoint = 'custom url'; + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + url: custom_endpoint, + publisherId: '1234567890' + } + }); + + expect(advRedAnalytics.getOptions().url).to.equal(custom_endpoint); + }); + + it('bid won event', function() { + let publisherId = '1234567890'; + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: publisherId + } + }); + + events.emit(EVENTS.BID_WON, bidWonEvent); + advRedAnalytics.sendEvents(); + + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://api.adv.red/api/event'); + + const message = JSON.parse(server.requests[0].requestBody); + expect(message.pwId).to.exist; + expect(message.publisherId).to.equal(publisherId); + expect(message.events.length).to.equal(1); + expect(message.events[0].eventType).to.equal('bidWon'); + expect(message.events[0].ad).to.be.undefined; + expect(message.events[0].adUrl).to.be.undefined; + }); + + it('track event', function () { + sinon.spy(advRedAnalytics, 'track'); + + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: '1234567890' + } + }); + + expectEvents().to.beTrackedBy(advRedAnalytics.track); + }); + }); + + describe('pageUrl detection', function () { + afterEach(function () { + advRedAnalytics.disableAnalytics() + }); + it('check pageUrl property', function () { + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: '1234567890' + } + }); + + const message = JSON.parse(server.requests[0].requestBody); + expect(message.pageUrl).to.equal(window.top.location.href); + }); + }); +}); From 10fcd205613b71f5398041f17cd238ece790ab61 Mon Sep 17 00:00:00 2001 From: Kevin Siow Date: Wed, 10 Jul 2024 13:53:25 +0200 Subject: [PATCH 0320/1097] Dailymotion Bid Adapter: add consent enforcement to read the advertising cookie (#11950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dailymotion Bid Adapter: add consent enforcement to read the advertising cookie * [x] Feature * Add consent enforcement before reading the advertising cookie * If Dailymotion does not have consent from the user, it does not transmit any cookie in the request to the Prebid server (previously the cookie was sent but not used) * Dailymotion Bid Adapter: no fallback for startdelay and plcmt * Dailymotion Bid Adapter: more concise cookie enforcement --------- Co-authored-by: Sébastien Millet Co-authored-by: Kevin Siow --- modules/dailymotionBidAdapter.js | 150 +++--- .../modules/dailymotionBidAdapter_spec.js | 501 +++++++++++++++++- 2 files changed, 582 insertions(+), 69 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 746767555fd..4f8f73816fe 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -130,76 +130,96 @@ export const spec = { * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ - buildRequests: (validBidRequests = [], bidderRequest) => validBidRequests.map(bid => ({ - method: 'POST', - url: 'https://pb.dmxleo.com', - data: { - pbv: '$prebid.version$', - bidder_request: { - gdprConsent: { - apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), - consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), - // Cast boolean in any case (eg: if value is int) to ensure type - gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), - }, - refererInfo: { - page: deepAccess(bidderRequest, 'refererInfo.page', ''), + buildRequests: function(validBidRequests = [], bidderRequest) { + // check consent to be able to read user cookie + const allowCookieReading = + // No GDPR applies + !deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || + // OR GDPR applies and we have global consent + deepAccess(bidderRequest, 'gdprConsent.vendorData.hasGlobalConsent') === true || + ( + // Vendor consent + deepAccess(bidderRequest, 'gdprConsent.vendorData.vendor.consents.573') === true && + // Purposes + [1, 3, 4].every(v => deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true) && + // Flexible purposes + [2, 7, 9, 10].every(v => + deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true || + deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.legitimateInterests.${v}`) === true + ) + ); + + return validBidRequests.map(bid => ({ + method: 'POST', + url: 'https://pb.dmxleo.com', + data: { + pbv: '$prebid.version$', + bidder_request: { + gdprConsent: { + apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), + consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + // Cast boolean in any case (eg: if value is int) to ensure type + gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), + }, + refererInfo: { + page: deepAccess(bidderRequest, 'refererInfo.page', ''), + }, + uspConsent: deepAccess(bidderRequest, 'uspConsent', ''), + gppConsent: { + gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || + deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), + applicableSections: deepAccess(bidderRequest, 'gppConsent.applicableSections') || + deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []), + }, }, - uspConsent: deepAccess(bidderRequest, 'uspConsent', ''), - gppConsent: { - gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || - deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), - applicableSections: deepAccess(bidderRequest, 'gppConsent.applicableSections') || - deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []), + config: { + api_key: bid.params.apiKey }, - }, - config: { - api_key: bid.params.apiKey - }, - // Cast boolean in any case (value should be 0 or 1) to ensure type - coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), - // In app context, we need to retrieve additional informations - ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { - appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), - appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), - } : {}), - ...(deepAccess(bidderRequest, 'ortb2.device') ? { - device: { - lmt: deepAccess(bidderRequest, 'ortb2.device.lmt', null), - ifa: deepAccess(bidderRequest, 'ortb2.device.ifa', ''), - atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), - }, - } : {}), - request: { - adUnitCode: deepAccess(bid, 'adUnitCode', ''), - auctionId: deepAccess(bid, 'auctionId', ''), - bidId: deepAccess(bid, 'bidId', ''), - mediaTypes: { - video: { - api: bid.mediaTypes?.[VIDEO]?.api || [], - mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], - minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, - maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, - playbackmethod: bid.mediaTypes?.[VIDEO]?.playbackmethod || [], - plcmt: bid.mediaTypes?.[VIDEO]?.plcmt || 1, // Fallback to instream considering logic of `isBidRequestValid` - protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], - skip: bid.mediaTypes?.[VIDEO]?.skip || 0, - skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, - skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0, - startdelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, - w: bid.mediaTypes?.[VIDEO]?.w || 0, - h: bid.mediaTypes?.[VIDEO]?.h || 0, + // Cast boolean in any case (value should be 0 or 1) to ensure type + coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), + // In app context, we need to retrieve additional informations + ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { + appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), + appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), + } : {}), + ...(deepAccess(bidderRequest, 'ortb2.device') ? { + device: { + lmt: deepAccess(bidderRequest, 'ortb2.device.lmt', null), + ifa: deepAccess(bidderRequest, 'ortb2.device.ifa', ''), + atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), + }, + } : {}), + request: { + adUnitCode: deepAccess(bid, 'adUnitCode', ''), + auctionId: deepAccess(bid, 'auctionId', ''), + bidId: deepAccess(bid, 'bidId', ''), + mediaTypes: { + video: { + api: bid.mediaTypes?.[VIDEO]?.api || [], + mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], + minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, + maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, + playbackmethod: bid.mediaTypes?.[VIDEO]?.playbackmethod || [], + plcmt: bid.mediaTypes?.[VIDEO]?.plcmt, + protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], + skip: bid.mediaTypes?.[VIDEO]?.skip || 0, + skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, + skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0, + startdelay: bid.mediaTypes?.[VIDEO]?.startdelay, + w: bid.mediaTypes?.[VIDEO]?.w || 0, + h: bid.mediaTypes?.[VIDEO]?.h || 0, + }, }, + sizes: bid.sizes || [], }, - sizes: bid.sizes || [], + video_metadata: getVideoMetadata(bid, bidderRequest), }, - video_metadata: getVideoMetadata(bid, bidderRequest), - }, - options: { - withCredentials: true, - crossOrigin: true, - }, - })), + options: { + withCredentials: allowCookieReading, + crossOrigin: true, + }, + })); + }, /** * Map the response from the server into a list of bids. diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 3ec45fc1bba..dd8f3fa5630 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -165,6 +165,7 @@ describe('dailymotionBidAdapterTests', () => { const { data: reqData } = request; + expect(request.options.withCredentials).to.eql(false); expect(request.url).to.equal('https://pb.dmxleo.com'); expect(reqData.pbv).to.eql('$prebid.version$'); @@ -178,7 +179,6 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.true; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, @@ -204,6 +204,499 @@ describe('dailymotionBidAdapterTests', () => { }); }); + it('validates buildRequests with global consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: true + } + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests without gdpr applying', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: false, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent, no legitimate interest', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent, with legitimate interest', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with insufficient consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + it('validates buildRequests with content values from App', () => { const bidRequestData = [{ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', @@ -479,7 +972,7 @@ describe('dailymotionBidAdapterTests', () => { minduration: 0, maxduration: 0, playbackmethod: [], - plcmt: 1, + plcmt: undefined, protocols: [], skip: 0, skipafter: 0, @@ -559,12 +1052,12 @@ describe('dailymotionBidAdapterTests', () => { minduration: 0, maxduration: 0, playbackmethod: [], - plcmt: 1, + plcmt: undefined, protocols: [], skip: 0, skipafter: 0, skipmin: 0, - startdelay: 0, + startdelay: undefined, w: 0, h: 0, }, From c8b4b5b374e2d4871bb90f2802671403251f6b1b Mon Sep 17 00:00:00 2001 From: danijel-ristic <168181386+danijel-ristic@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:08:32 +0200 Subject: [PATCH 0321/1097] TargetVideo Bid Adapter : add video support (#11867) * Add video support * Refactor code to library * Fix lint errors * Fix code duplication * Fix lint errors --------- Co-authored-by: Danijel Ristic --- libraries/targetVideoUtils/bidderUtils.js | 178 ++++++++++++ libraries/targetVideoUtils/constants.js | 23 ++ modules/bridBidAdapter.js | 94 +++--- modules/nextMillenniumBidAdapter.js | 28 +- modules/targetVideoBidAdapter.js | 272 ++++++++---------- modules/targetVideoBidAdapter.md | 23 +- .../modules/targetVideoBidAdapter_spec.js | 123 +++++++- 7 files changed, 501 insertions(+), 240 deletions(-) create mode 100644 libraries/targetVideoUtils/bidderUtils.js create mode 100644 libraries/targetVideoUtils/constants.js diff --git a/libraries/targetVideoUtils/bidderUtils.js b/libraries/targetVideoUtils/bidderUtils.js new file mode 100644 index 00000000000..f18540818cb --- /dev/null +++ b/libraries/targetVideoUtils/bidderUtils.js @@ -0,0 +1,178 @@ +import {VIDEO} from '../../src/mediaTypes.js'; +import {getRefererInfo} from '../../src/refererDetection.js'; +import {createTrackPixelHtml, deepAccess, getBidRequest} from '../../src/utils.js'; + +export function getSizes(request) { + let sizes = request.sizes; + if (!sizes && request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + if (Array.isArray(sizes) && !Array.isArray(sizes[0])) { + sizes = [sizes[0], sizes[1]]; + } + if (!Array.isArray(sizes) || !Array.isArray(sizes[0])) { + sizes = [[0, 0]]; + } + + return sizes; +} + +export function formatRequest({payload, url, bidderRequest, bidId}) { + const request = { + method: 'POST', + data: JSON.stringify(payload), + url, + options: { + withCredentials: true, + } + } + + if (bidderRequest) { + request.bidderRequest = bidderRequest; + } + + if (bidId) { + request.bidId = bidId; + } + + return request; +} + +export function createVideoTag(bid) { + const tag = {}; + tag.id = parseInt(bid.params.placementId, 10); + tag.gpid = 'targetVideo'; + tag.sizes = getSizes(bid); + tag.primary_size = tag.sizes[0]; + tag.ad_types = [VIDEO]; + tag.uuid = bid.bidId; + tag.allow_smaller_sizes = false; + tag.use_pmt_rule = false; + tag.prebid = true; + tag.disable_psa = true; + tag.hb_source = 1; + tag.require_asset_url = true; + tag.video = { + playback_method: 2, + skippable: true + }; + + return tag; +} + +export function bannerBid(serverBid, rtbBid, bidderRequest, margin) { + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const sizes = getSizes(bidRequest); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm / margin, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + width: sizes[0][0], + height: sizes[0][1], + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + if (rtbBid.rtb.video) { + Object.assign(bid, { + vastImpUrl: rtbBid.notify_url, + ad: getBannerHtml(rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url)), + ttl: 3600 + }); + } + + return bid; +} + +export function videoBid(serverBid, requestId, currency, params, ttl) { + const {ad, adUrl, vastUrl, vastXml} = getAd(serverBid); + + const bid = { + requestId, + params, + currency, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.adid || serverBid.crid, + netRevenue: false, + ttl, + meta: { + advertiserDomains: serverBid.adomain || [] + } + }; + + if (vastUrl || vastXml) { + bid.mediaType = VIDEO; + if (vastUrl) bid.vastUrl = vastUrl; + if (vastXml) bid.vastXml = vastXml; + } else { + bid.ad = ad; + bid.adUrl = adUrl; + }; + + return bid; +} + +export function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && tag.ads.find(ad => ad.rtb); +} + +export function getBannerHtml(vastUrl) { + return ` + + + + + + + +
+ + + + `; +} + +export function getAd(bid) { + let ad, adUrl, vastXml, vastUrl; + + switch (deepAccess(bid, 'ext.prebid.type')) { + case VIDEO: + if (bid.adm.substr(0, 4) === 'http') { + vastUrl = bid.adm; + } else { + vastXml = bid.adm; + }; + break; + default: + if (bid.adm && bid.nurl) { + ad = bid.adm; + ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } else if (bid.adm) { + ad = bid.adm; + } else if (bid.nurl) { + adUrl = bid.nurl; + }; + } + + return {ad, adUrl, vastXml, vastUrl}; +} + +export function getSiteObj() { + const refInfo = (getRefererInfo && getRefererInfo()) || {}; + + return { + page: refInfo.page, + ref: refInfo.ref, + domain: refInfo.domain + } +} diff --git a/libraries/targetVideoUtils/constants.js b/libraries/targetVideoUtils/constants.js new file mode 100644 index 00000000000..8ce94c0eaeb --- /dev/null +++ b/libraries/targetVideoUtils/constants.js @@ -0,0 +1,23 @@ +const SOURCE = 'pbjs'; +const GVLID = 786; +const MARGIN = 1.35; +const BIDDER_CODE = 'targetVideo'; + +const TIME_TO_LIVE = 300; +const BANNER_ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; +const VIDEO_ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; +const VIDEO_PARAMS = [ + 'api', 'linearity', 'maxduration', 'mimes', 'minduration', + 'plcmt', 'playbackmethod', 'protocols', 'startdelay' +]; + +export { + SOURCE, + GVLID, + MARGIN, + BIDDER_CODE, + TIME_TO_LIVE, + BANNER_ENDPOINT_URL, + VIDEO_ENDPOINT_URL, + VIDEO_PARAMS +} diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js index f3fe1541886..527cb9d5d5d 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -1,7 +1,7 @@ -import {createTrackPixelHtml, _each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; +import {_each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getRefererInfo} from '../src/refererDetection.js'; +import {getAd, getSiteObj} from '../libraries/targetVideoUtils/bidderUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -180,50 +180,50 @@ export const spec = { } -/** - * Helper function to get ad - * - * @param {object} bid The bid. - * @return {object} ad object. - */ -function getAd(bid) { - let ad, adUrl, vastXml, vastUrl; - - switch (deepAccess(bid, 'ext.prebid.type')) { - case VIDEO: - if (bid.adm.substr(0, 4) === 'http') { - vastUrl = bid.adm; - } else { - vastXml = bid.adm; - }; - break; - default: - if (bid.adm && bid.nurl) { - ad = bid.adm; - ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - ad = bid.adm; - } else if (bid.nurl) { - adUrl = bid.nurl; - }; - } - - return {ad, adUrl, vastXml, vastUrl}; -} - -/** - * Helper function to get site object - * - * @return {object} siteObj. - */ -function getSiteObj() { - const refInfo = (getRefererInfo && getRefererInfo()) || {}; - - return { - page: refInfo.page, - ref: refInfo.ref, - domain: refInfo.domain - }; -} +// /** +// * Helper function to get ad +// * +// * @param {object} bid The bid. +// * @return {object} ad object. +// */ +// function getAd(bid) { +// let ad, adUrl, vastXml, vastUrl; + +// switch (deepAccess(bid, 'ext.prebid.type')) { +// case VIDEO: +// if (bid.adm.substr(0, 4) === 'http') { +// vastUrl = bid.adm; +// } else { +// vastXml = bid.adm; +// }; +// break; +// default: +// if (bid.adm && bid.nurl) { +// ad = bid.adm; +// ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); +// } else if (bid.adm) { +// ad = bid.adm; +// } else if (bid.nurl) { +// adUrl = bid.nurl; +// }; +// } + +// return {ad, adUrl, vastXml, vastUrl}; +// } + +// /** +// * Helper function to get site object +// * +// * @return {object} siteObj. +// */ +// function getSiteObj() { +// const refInfo = (getRefererInfo && getRefererInfo()) || {}; + +// return { +// page: refInfo.page, +// ref: refInfo.ref, +// domain: refInfo.domain +// }; +// } registerBidder(spec); diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 65f530d9e58..5e9be67c6bb 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,6 +1,5 @@ import { _each, - createTrackPixelHtml, deepAccess, deepSetValue, getBidIdParameter, @@ -12,6 +11,7 @@ import { parseUrl, triggerPixel, } from '../src/utils.js'; +import {getAd} from '../libraries/targetVideoUtils/bidderUtils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import { EVENTS } from '../src/constants.js'; @@ -455,32 +455,6 @@ function getTopWindow(curWindow, nesting = 0) { }; } -function getAd(bid) { - let ad, adUrl, vastXml, vastUrl; - - switch (deepAccess(bid, 'ext.prebid.type')) { - case VIDEO: - if (bid.adm.substr(0, 4) === 'http') { - vastUrl = bid.adm; - } else { - vastXml = bid.adm; - }; - - break; - default: - if (bid.adm && bid.nurl) { - ad = bid.adm; - ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - ad = bid.adm; - } else if (bid.nurl) { - adUrl = bid.nurl; - }; - }; - - return {ad, adUrl, vastXml, vastUrl}; -} - function getSiteObj() { const refInfo = (getRefererInfo && getRefererInfo()) || {}; diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index 282f322c36a..fd5d79d08b7 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -1,24 +1,19 @@ -import {find} from '../src/polyfill.js'; -import {getBidRequest} from '../src/utils.js'; +import {_each, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {formatRequest, getRtbBid, getSiteObj, videoBid, bannerBid, createVideoTag} from '../libraries/targetVideoUtils/bidderUtils.js'; +import {SOURCE, GVLID, BIDDER_CODE, VIDEO_PARAMS, BANNER_ENDPOINT_URL, VIDEO_ENDPOINT_URL, MARGIN, TIME_TO_LIVE} from '../libraries/targetVideoUtils/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ -const SOURCE = 'pbjs'; -const BIDDER_CODE = 'targetVideo'; -const ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; -const MARGIN = 1.35; -const GVLID = 786; - export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -37,35 +32,114 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { - const tags = bidRequests.map(createVideoTag); - const schain = bidRequests[0].schain; - const payload = { - tags: tags, - sdk: { - source: SOURCE, - version: '$prebid.version$' - }, - schain: schain + const requests = []; + const sdk = { + source: SOURCE, + version: '$prebid.version$' }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - - if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { - let ac = bidderRequest.gdprConsent.addtlConsent; - let acStr = ac.substring(ac.indexOf('~') + 1); - payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + for (let {params, bidId, sizes, mediaTypes} of bidRequests) { + for (const mediaType in mediaTypes) { + switch (mediaType) { + case VIDEO: { + const video = mediaTypes[VIDEO]; + const placementId = params.placementId; + const site = getSiteObj(); + + if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; + + const payload = { + sdk, + id: bidderRequest.bidderRequestId, + site, + imp: [] + } + + const imp = { + ext: { + prebid: { + storedrequest: { id: placementId } + } + }, + video: getDefinedParams(video, VIDEO_PARAMS) + } + + if (video.playerSize) { + imp.video = Object.assign( + imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {} + ); + } else if (video.w && video.h) { + imp.video.w = video.w; + imp.video.h = video.h; + } + + payload.imp.push(imp); + + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const uspConsent = bidderRequest && bidderRequest.uspConsent; + + if (gdprConsent || uspConsent) { + payload.regs = { ext: {} }; + + if (uspConsent) { + payload.regs.ext.us_privacy = uspConsent; + }; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + payload.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + }; + + if (typeof gdprConsent.consentString !== 'undefined') { + payload.user = { + ext: { consent: gdprConsent.consentString } + }; + }; + }; + }; + + if (bidRequests[0].schain) { + payload.schain = bidRequests[0].schain; + } + + requests.push(formatRequest({ payload, url: VIDEO_ENDPOINT_URL, bidId })); + break; + } + + case BANNER: { + const tags = bidRequests.map(createVideoTag); + const schain = bidRequests[0].schain; + + const payload = { + tags, + sdk, + schain, + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + let ac = bidderRequest.gdprConsent.addtlConsent; + let acStr = ac.substring(ac.indexOf('~') + 1); + payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + } + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent + } + + return formatRequest({ payload, url: BANNER_ENDPOINT_URL, bidderRequest }); + } + } } } - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent - } - - return formatRequest(payload, bidderRequest); + return requests; }, /** @@ -74,139 +148,33 @@ export const spec = { * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, { bidderRequest }) { + interpretResponse: function(serverResponse, { bidderRequest, ...bidRequest }) { serverResponse = serverResponse.body; + const currency = serverResponse.cur; const bids = []; if (serverResponse.tags) { serverResponse.tags.forEach(serverBid => { const rtbBid = getRtbBid(serverBid); if (rtbBid && rtbBid.cpm !== 0 && rtbBid.ad_type == VIDEO) { - bids.push(newBid(serverBid, rtbBid, bidderRequest)); + bids.push(bannerBid(serverBid, rtbBid, bidderRequest, MARGIN)); } }); } - return bids; - } - -} - -function getSizes(request) { - let sizes = request.sizes; - if (!sizes && request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) { - sizes = request.mediaTypes.banner.sizes; - } - if (Array.isArray(sizes) && !Array.isArray(sizes[0])) { - sizes = [sizes[0], sizes[1]]; - } - if (!Array.isArray(sizes) || !Array.isArray(sizes[0])) { - sizes = [[0, 0]]; - } - - return sizes; -} + if (serverResponse.seatbid) { + _each(serverResponse.seatbid, (resp) => { + _each(resp.bid, (bid) => { + const requestId = bidRequest.bidId; + const params = bidRequest.params; -function formatRequest(payload, bidderRequest) { - const options = { - withCredentials: true - }; - const request = { - method: 'POST', - url: ENDPOINT_URL, - data: JSON.stringify(payload), - bidderRequest, - options - }; - - return request; -} - -/** - * Create video auction. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function createVideoTag(bid) { - const tag = {}; - tag.id = parseInt(bid.params.placementId, 10); - tag.gpid = 'targetVideo'; - tag.sizes = getSizes(bid); - tag.primary_size = tag.sizes[0]; - tag.ad_types = [VIDEO]; - tag.uuid = bid.bidId; - tag.allow_smaller_sizes = false; - tag.use_pmt_rule = false; - tag.prebid = true; - tag.disable_psa = true; - tag.hb_source = 1; - tag.require_asset_url = true; - tag.video = { - playback_method: 2, - skippable: true - }; - - return tag; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); - const sizes = getSizes(bidRequest); - const bid = { - requestId: serverBid.uuid, - cpm: rtbBid.cpm / MARGIN, - creativeId: rtbBid.creative_id, - dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - width: sizes[0][0], - height: sizes[0][1], - ttl: 300, - adUnitCode: bidRequest.adUnitCode, - appnexus: { - buyerMemberId: rtbBid.buyer_member_id, - dealPriority: rtbBid.deal_priority, - dealCode: rtbBid.deal_code + bids.push(videoBid(bid, requestId, currency, params, TIME_TO_LIVE)); + }); + }); } - }; - - if (rtbBid.rtb.video) { - Object.assign(bid, { - vastImpUrl: rtbBid.notify_url, - ad: getBannerHtml(rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url)), - ttl: 3600 - }); - } - - return bid; -} -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} - -function getBannerHtml(vastUrl) { - return ` - - - - - - - -
- - - - `; + return bids; + } } registerBidder(spec); diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md index 557c9f94410..a34ad0aff27 100644 --- a/modules/targetVideoBidAdapter.md +++ b/modules/targetVideoBidAdapter.md @@ -3,17 +3,17 @@ ``` Module Name: Target Video Bid Adapter Module Type: Bidder Adapter -Maintainer: grajzer@gmail.com +Maintainers: grajzer@gmail.com, danijel.ristic@target-video.com ``` # Description Connects to Appnexus exchange for bids. -TargetVideo bid adapter supports Banner. +TargetVideo bid adapter supports Banner and Video. # Test Parameters -``` +```js var adUnits = [ // Banner adUnit { @@ -29,6 +29,23 @@ var adUnits = [ placementId: 13232361 } }] + }, + // Video adUnit + { + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } + }, + bids: [{ + bidder: 'targetVideo', + params: { + placementId: 12345, + reserve: 0, + } + }] } ]; ``` diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index 8180183e6d7..442d7e7ef0b 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -1,27 +1,42 @@ import { spec } from '../../../modules/targetVideoBidAdapter.js' describe('TargetVideo Bid Adapter', function() { + const bidder = 'targetVideo'; + const params = { + placementId: 12345, + }; + const bannerRequest = [{ - bidder: 'targetVideo', + bidder, + params, mediaTypes: { banner: { sizes: [[300, 250]], } }, - params: { - placementId: 12345, + }]; + + const videoRequest = [{ + bidder, + params, + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } } }]; it('Test the bid validation function', function() { - const validBid = spec.isBidRequestValid(bannerRequest[0]); + const validBid = spec.isBidRequestValid(bannerRequest[0]) && spec.isBidRequestValid(videoRequest[0]); const invalidBid = spec.isBidRequestValid(null); expect(validBid).to.be.true; expect(invalidBid).to.be.false; }); - it('Test the request processing function', function () { + it('Test the BANNER request processing function', function() { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); expect(request).to.not.be.empty; @@ -36,7 +51,20 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.tags[0].ad_types[0]).to.equal('video'); }); - it('Handle nobid responses', function () { + it('Test the VIDEO request processing function', function() { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request[0].data); + expect(payload).to.not.be.empty; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal(12345); + }) + + it('Handle BANNER nobid responses', function() { const responseBody = { 'version': '0.0.1', 'tags': [{ @@ -48,11 +76,24 @@ describe('TargetVideo Bid Adapter', function() { }; const bidderRequest = null; - const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); expect(bidResponse.length).to.equal(0); }); - it('Test the response parsing function', function () { + it('Handle VIDEO nobid responses', function() { + const responseBody = { + 'id': 'test-id', + 'cur': 'USD', + 'seatbid': [], + 'nbr': 0 + }; + const bidderRequest = null; + + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); + expect(bidResponse.length).to.equal(0); + }) + + it('Test the BANNER response parsing function', function() { const responseBody = { 'tags': [{ 'uuid': '84ab500420319d', @@ -82,7 +123,7 @@ describe('TargetVideo Bid Adapter', function() { }] }; - const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); expect(bidResponse).to.not.be.empty; const bid = bidResponse[0]; @@ -94,7 +135,43 @@ describe('TargetVideo Bid Adapter', function() { expect(bid.ad).to.include('initPlayer') }); - it('Test GDPR consent information is present in the request', function () { + it('Test the VIDEO response parsing function', function() { + const responseBody = { + 'id': 'test-id', + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'id': '5044997188309660254', + 'price': 10, + 'adm': 'test ad', + 'adid': '97517771', + 'crid': '97517771', + 'adomain': ['domain.com'], + 'w': 640, + 'h': 480 + }], + 'seat': 'bidder' + }] + }; + const bidderRequest = { + bidderCode: 'brid', + bidderRequestId: '22edbae2733bf6', + bids: videoRequest + }; + + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal('test ad'); + expect(bid.cpm).to.equal(10); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.currency).to.equal('USD'); + }) + + it('Test BANNER GDPR consent information is present in the request', function() { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { 'bidderCode': 'targetVideo', @@ -119,7 +196,7 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); }); - it('Test US Privacy string is present in the request', function() { + it('Test BANNER US Privacy string is present in the request', function() { let consentString = '1YA-'; let bidderRequest = { 'bidderCode': 'targetVideo', @@ -136,4 +213,28 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.us_privacy).to.exist; expect(payload.us_privacy).to.exist.and.to.equal(consentString); }); + + it('Test VIDEO GDPR and USP consents are present in the request', function() { + let gdprConsentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'targetVideo', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': uspConsentString, + 'gdprConsent': { + consentString: gdprConsentString, + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' + } + }; + bidderRequest.bids = videoRequest; + + const request = spec.buildRequests(videoRequest, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.user.ext.consent).to.equal(gdprConsentString); + expect(payload.regs.ext.us_privacy).to.equal(uspConsentString); + expect(payload.regs.ext.gdpr).to.equal(1); + }); }); From 37d742cdafb9531001e15338021891ab1da9130f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 10 Jul 2024 09:57:29 -0700 Subject: [PATCH 0322/1097] Dynamic creatives: fix exception on rendering (#11956) --- creative/crossDomain.js | 9 +++-- .../gpt/x-domain/creative.html | 2 +- .../creative-renderer-display/renderer.js | 2 +- .../creative-renderer-native/renderer.js | 2 +- .../spec/creative/crossDomainCreative_spec.js | 35 ++++++++++++++----- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/creative/crossDomain.js b/creative/crossDomain.js index 5799b817aa5..d3524f61d4b 100644 --- a/creative/crossDomain.js +++ b/creative/crossDomain.js @@ -32,10 +32,13 @@ function isPrebidWindow(win) { export function renderer(win) { let target = win.parent; - while (target !== win.top && !isPrebidWindow(target)) { - target = target.parent; + try { + while (target !== win.top && !isPrebidWindow(target)) { + target = target.parent; + } + if (!isPrebidWindow(target)) target = win.parent; + } catch (e) { } - if (!isPrebidWindow(target)) target = win.parent; return function ({adId, pubUrl, clickUrl}) { const pubDomain = new URL(pubUrl, window.location).origin; diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index e5ddd480a8b..63842b00882 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + + + + + + ` + + bidResponses[0].ad = ad.replace('', trackingTag + ''); + return bidResponses; } diff --git a/test/spec/modules/eightPodAnalyticsAdapter_spec.js b/test/spec/modules/eightPodAnalyticsAdapter_spec.js index 930b15bda31..55a09db03b4 100644 --- a/test/spec/modules/eightPodAnalyticsAdapter_spec.js +++ b/test/spec/modules/eightPodAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import analyticsAdapter, { storage, queue, context, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; +import analyticsAdapter, { storage, queue, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import eightPodAnalytics from 'modules/eightPodAnalyticsAdapter.js'; @@ -45,11 +45,12 @@ describe('eightPodAnalyticAdapter', function() { it('should subscribe on messageEvents', function() { getDataFromLocalStorageStub.returns(JSON.stringify([])); sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + sandbox.spy(eightPodAnalytics, 'getEventFromLocalStorage'); analyticsAdapter.setupPage(); - sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 1); - sandbox.assert.callCount(addEventListenerSpy, 1); + sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 0); + sandbox.assert.callCount(analyticsAdapter.getEventFromLocalStorage, 1); }); it('should receive saved events list', function() { @@ -67,6 +68,7 @@ describe('eightPodAnalyticAdapter', function() { beforeEach(function() { setupPageStub = sandbox.stub(eightPodAnalytics, 'setupPage'); + eightPodAnalytics.resetContext(); }); afterEach(function() { @@ -79,16 +81,18 @@ describe('eightPodAnalyticAdapter', function() { }) sandbox.assert.callCount(setupPageStub, 0); - expect(context).to.deep.equal(undefined) + expect(analyticsAdapter.getContext()).to.deep.equal({}) }); it('should call setup page and get context', function() { eightPodAnalytics.track({ eventType: BID_WON, args: { + adUnitCode: 'adUnitCode', bidder: 'eightPod', creativeId: 'creativeId', seatBidId: 'seatBidId', + cid: 'campaignId', params: [ { publisherId: 'publisherId', @@ -99,23 +103,27 @@ describe('eightPodAnalyticAdapter', function() { }) sandbox.assert.callCount(setupPageStub, 1); - expect(context).to.deep.equal({ - bidId: 'seatBidId', - campaignId: 'campaignId', - placementId: 'placementId', - publisherId: 'publisherId', - variantId: 'creativeId' + expect(analyticsAdapter.getContext()).to.deep.equal({ + adUnitCode: { + bidId: 'seatBidId', + campaignId: 'campaignId', + placementId: 'placementId', + publisherId: 'publisherId', + variantId: 'creativeId' + } }) }); }); describe('trackEvent', function() { let getContextStub, getTimeStub; + const adUnitCode = 'adUnitCode'; beforeEach(function() { getContextStub = sandbox.stub(eightPodAnalytics, 'getContext'); getTimeStub = sandbox.stub(Date.prototype, 'getTime').returns(1234); eightPodAnalytics.resetQueue(); + eightPodAnalytics.resetContext(); }); afterEach(function() { @@ -124,7 +132,7 @@ describe('eightPodAnalyticAdapter', function() { }); it('should add event to the queue', function() { - getContextStub.returns({}); + getContextStub.returns({adUnitCode: {}}); const event1 = { detail: { @@ -169,9 +177,10 @@ describe('eightPodAnalyticAdapter', function() { value: 2 } } - trackEvent(event1) + + trackEvent(event1, adUnitCode) expect(queue).to.deep.equal([result1]); - trackEvent(event2); + trackEvent(event2, adUnitCode); expect(queue).to.deep.equal([result1, result2]); }); }); From ff155746cf7104ed40b036c21c7be4cb67a49233 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jul 2024 08:12:59 -0700 Subject: [PATCH 0325/1097] sizeMapping: do not require configuration (#11920) --- modules/sizeMapping.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/sizeMapping.js b/modules/sizeMapping.js index eab85aa3d93..9b2a37d0235 100644 --- a/modules/sizeMapping.js +++ b/modules/sizeMapping.js @@ -4,7 +4,6 @@ import {includes} from '../src/polyfill.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {setupAdUnitMediaTypes} from '../src/adapterManager.js'; -let installed = false; let sizeConfig = []; /** @@ -24,11 +23,9 @@ let sizeConfig = []; */ export function setSizeConfig(config) { sizeConfig = config; - if (!installed) { - setupAdUnitMediaTypes.before((next, adUnit, labels) => next(processAdUnitsForLabels(adUnit, labels), labels)); - installed = true; - } } + +setupAdUnitMediaTypes.before((next, adUnit, labels) => next(processAdUnitsForLabels(adUnit, labels), labels)); config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig)); /** From 9b8b6e3d83a2ad5669e93ac9786595ad7e435f52 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jul 2024 08:25:31 -0700 Subject: [PATCH 0326/1097] Core: Remove default value for unused timeoutBuffer config (#11960) --- src/config.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/config.js b/src/config.js index 1fc9b4ea895..e9c37a8d329 100644 --- a/src/config.js +++ b/src/config.js @@ -38,8 +38,6 @@ const DEFAULT_DEVICE_ACCESS = true; const DEFAULT_MAX_NESTED_IFRAMES = 10; const DEFAULT_MAXBID_VALUE = 5000 -const DEFAULT_TIMEOUTBUFFER = 400; - const DEFAULT_IFRAMES_CONFIG = {}; export const RANDOM = 'random'; @@ -157,8 +155,6 @@ export function newConfig() { */ deviceAccess: DEFAULT_DEVICE_ACCESS, - // timeout buffer to adjust for bidder CDN latency - timeoutBuffer: DEFAULT_TIMEOUTBUFFER, disableAjaxTimeout: DEFAULT_DISABLE_AJAX_TIMEOUT, // default max nested iframes for referer detection From 5074acd18b028faa6cdd8f542c29676a6fad1fc9 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jul 2024 10:00:01 -0700 Subject: [PATCH 0327/1097] PAAPI: fix bug where configuration is not picked up correctly by the PBS adapter (#11899) --- modules/paapi.js | 83 ++--- modules/prebidServerBidAdapter/index.js | 4 +- .../prebidServerBidAdapter/ortbConverter.js | 1 + test/spec/modules/paapi_spec.js | 288 +++++++++++------- .../modules/prebidServerBidAdapter_spec.js | 20 +- 5 files changed, 246 insertions(+), 150 deletions(-) diff --git a/modules/paapi.js b/modules/paapi.js index 8ddd1912c29..9ae2c870e5d 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -54,6 +54,7 @@ export function init(cfg) { } getHook('addPaapiConfig').before(addPaapiConfigHook); +getHook('makeBidRequests').before(addPaapiData); getHook('makeBidRequests').after(markForFledge); events.on(EVENTS.AUCTION_END, onAuctionEnd); @@ -332,38 +333,50 @@ function getRequestedSize(adUnit) { })(); } +export function addPaapiData(next, adUnits, ...args) { + if (isFledgeSupported() && moduleConfig.enabled) { + adUnits.forEach(adUnit => { + // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + const igsAe = adUnit.ortb2Imp?.ext?.igs != null + ? adUnit.ortb2Imp.ext.igs.ae || 1 + : null; + const extAe = adUnit.ortb2Imp?.ext?.ae; + if (igsAe !== extAe && igsAe != null && extAe != null) { + logWarn(MODULE, `Ad unit defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, adUnit); + } + const ae = igsAe ?? extAe ?? moduleConfig.defaultForSlots; + if (ae) { + deepSetValue(adUnit, 'ortb2Imp.ext.ae', ae); + adUnit.ortb2Imp.ext.igs = Object.assign({ + ae: ae, + biddable: 1 + }, adUnit.ortb2Imp.ext.igs); + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + deepSetValue(adUnit, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); + } + adUnit.bids.forEach(bidReq => { + if (!getFledgeConfig(bidReq.bidder).enabled) { + deepSetValue(bidReq, 'ortb2Imp.ext.ae', 0); + bidReq.ortb2Imp.ext.igs = {ae: 0, biddable: 0}; + } + }) + } + }) + } + next(adUnits, ...args); +} + export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { bidderRequests.forEach((bidderReq) => { - const {enabled, ae} = getFledgeConfig(bidderReq.bidderCode); + const {enabled} = getFledgeConfig(bidderReq.bidderCode); Object.assign(bidderReq, { paapi: { enabled, componentSeller: !!moduleConfig.componentSeller?.auctionConfig } }); - bidderReq.bids.forEach(bidReq => { - // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md - const igsAe = bidReq.ortb2Imp?.ext?.igs != null - ? bidReq.ortb2Imp.ext.igs.ae || 1 - : null; - const extAe = bidReq.ortb2Imp?.ext?.ae; - if (igsAe !== extAe && igsAe != null && extAe != null) { - logWarn(MODULE, `Bid request defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, bidReq); - } - const bidAe = igsAe ?? extAe ?? ae; - if (bidAe) { - deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidAe); - bidReq.ortb2Imp.ext.igs = Object.assign({ - ae: bidAe, - biddable: 1 - }, bidReq.ortb2Imp.ext.igs); - const requestedSize = getRequestedSize(bidReq); - if (requestedSize) { - deepSetValue(bidReq, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); - } - } - }); }); } next(bidderRequests); @@ -378,18 +391,6 @@ export function setImpExtAe(imp, bidRequest, context) { registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); -function paapiResponseParser(configs, response, context) { - configs.forEach((config) => { - const impCtx = context.impContext[config.impid]; - if (!impCtx?.imp?.ext?.ae) { - logWarn(MODULE, 'Received auction configuration for an impression that was not in the request or did not ask for it', config, impCtx?.imp); - } else { - impCtx.paapiConfigs = impCtx.paapiConfigs || []; - impCtx.paapiConfigs.push(config); - } - }); -} - export function parseExtIgi(response, ortbResponse, context) { paapiResponseParser( (ortbResponse.ext?.igi || []).flatMap(igi => { @@ -411,6 +412,18 @@ export function parseExtIgi(response, ortbResponse, context) { ) } +function paapiResponseParser(configs, response, context) { + configs.forEach((config) => { + const impCtx = context.impContext[config.impid]; + if (!impCtx?.imp?.ext?.ae) { + logWarn(MODULE, 'Received auction configuration for an impression that was not in the request or did not ask for it', config, impCtx?.imp); + } else { + impCtx.paapiConfigs = impCtx.paapiConfigs || []; + impCtx.paapiConfigs.push(config); + } + }); +} + // to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up // fledge response processing in two steps: first aggregate all the auction configs by their imp... diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index c0da66c031b..edae21e97a7 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -515,7 +515,9 @@ export function PrebidServer() { } }, onFledge: (params) => { - addPaapiConfig({auctionId: bidRequests[0].auctionId, ...params}, {config: params.config}); + config.runWithBidder(params.bidder, () => { + addPaapiConfig({auctionId: bidRequests[0].auctionId, ...params}, {config: params.config}); + }) } }) } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index bb033271b3c..242c65c7dfa 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -241,6 +241,7 @@ const PBS_CONVERTER = ortbConverter({ adUnitCode: impCtx.adUnit.code, ortb2: bidderReq?.ortb2, ortb2Imp: bidReq?.ortb2Imp, + bidder: cfg.bidder, config: cfg.config }; })); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 77c8a6d7dda..cc839307c8e 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -7,7 +7,7 @@ import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import { - addPaapiConfigHook, + addPaapiConfigHook, addPaapiData, buyersToAuctionConfigs, getPAAPIConfig, getPAAPISize, @@ -652,110 +652,171 @@ describe('paapi module', () => { config.resetConfig(); }); - function mark() { - return Object.fromEntries( - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ).map(b => [b.bidderCode, b]) - ); - } - - function expectFledgeFlags(...enableFlags) { - const bidRequests = mark(); - expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); - bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); + describe('makeBidRequests', () => { + function mark() { + return Object.fromEntries( + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ).map(b => [b.bidderCode, b]) + ); + } - expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); - bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); + function expectFledgeFlags(...enableFlags) { + const bidRequests = mark(); + expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); + bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); - Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { - if (bid.ortb2Imp?.ext?.ae) { - sinon.assert.match(bid.ortb2Imp.ext.igs, { - ae: bid.ortb2Imp.ext.ae, - biddable: 1 - }); - } - }); - } + expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); + bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); - describe('with setConfig()', () => { - it('should set paapi.enabled correctly per bidder', function () { - config.setConfig({ - bidderSequence: 'fixed', - paapi: { - enabled: true, - bidders: ['appnexus'], - defaultForSlots: 1, + Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { + if (bid.ortb2Imp?.ext?.ae) { + sinon.assert.match(bid.ortb2Imp.ext.igs, { + ae: bid.ortb2Imp.ext.ae, + biddable: 1 + }); } }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); - }); + } - it('should set paapi.enabled correctly for all bidders', function () { - config.setConfig({ - bidderSequence: 'fixed', - paapi: { - enabled: true, - defaultForSlots: 1, - } + describe('with setConfig()', () => { + it('should set paapi.enabled correctly per bidder', function () { + config.setConfig({ + bidderSequence: 'fixed', + paapi: { + enabled: true, + bidders: ['appnexus'], + defaultForSlots: 1, + } + }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: 0}); }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); - }); - Object.entries({ - 'not set': { - cfg: {}, - componentSeller: false - }, - 'set': { - cfg: { - componentSeller: { - auctionConfig: { - decisionLogicURL: 'publisher.example' - } - } - }, - componentSeller: true - } - }).forEach(([t, {cfg, componentSeller}]) => { - it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { + it('should set paapi.enabled correctly for all bidders', function () { config.setConfig({ + bidderSequence: 'fixed', paapi: { enabled: true, defaultForSlots: 1, - ...cfg } }); - Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + }); + + Object.entries({ + 'not set': { + cfg: {}, + componentSeller: false + }, + 'set': { + cfg: { + componentSeller: { + auctionConfig: { + decisionLogicURL: 'publisher.example' + } + } + }, + componentSeller: true + } + }).forEach(([t, {cfg, componentSeller}]) => { + it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + ...cfg + } + }); + Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); + }); }); }); + }); + describe('addPaapiData', () => { + function getEnrichedAdUnits() { + const next = sinon.stub(); + addPaapiData(next, adUnits); + sinon.assert.calledWith(next, adUnits); + return adUnits; + } + + function getImpExt() { + const next = sinon.stub(); + addPaapiData(next, adUnits); + sinon.assert.calledWith(next, adUnits); + return { + global: adUnits[0].ortb2Imp?.ext, + ...Object.fromEntries(adUnits[0].bids.map(bid => [bid.bidder, bid.ortb2Imp?.ext])) + } + } it('should not override pub-defined ext.ae', () => { config.setConfig({ - bidderSequence: 'fixed', paapi: { enabled: true, defaultForSlots: 1, } }); Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); - expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + sinon.assert.match(getImpExt(), { + global: { + ae: 0, + }, + rubicon: undefined, + appnexus: undefined + }); }); + it('should override per-bidder when excluded via paapi.bidders', () => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + bidders: ['rubicon'] + } + }) + sinon.assert.match(getImpExt(), { + global: { + ae: 1, + igs: { + ae: 1, + biddable: 1 + } + }, + rubicon: undefined, + appnexus: { + ae: 0, + igs: { + ae: 0, + biddable: 0 + } + } + }) + }) + it('should populate ext.igs when request has ext.ae', () => { config.setConfig({ - bidderSequence: 'fixed', paapi: { enabled: true } }); Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); - expectFledgeFlags({enabled: true, ae: 3}, {enabled: true, ae: 3}); + sinon.assert.match(getImpExt(), { + global: { + ae: 3, + igs: { + ae: 3, + biddable: 1 + } + }, + rubicon: undefined, + appnexus: undefined, + }); }); it('should not override pub-defined ext.igs', () => { @@ -765,16 +826,17 @@ describe('paapi module', () => { } }); Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); - const bidReqs = mark(); - Object.values(bidReqs).flatMap(req => req.bids).forEach(bid => { - sinon.assert.match(bid.ortb2Imp.ext, { + sinon.assert.match(getImpExt(), { + global: { ae: 1, igs: { ae: 1, biddable: 0 } - }); - }); + }, + rubicon: undefined, + appnexus: undefined + }) }); it('should fill ext.ae from ext.igs, if defined', () => { @@ -784,60 +846,64 @@ describe('paapi module', () => { } }); Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + sinon.assert.match(getImpExt(), { + global: { + ae: 1, + igs: { + ae: 1, + biddable: 1 + } + }, + appnexus: undefined, + rubicon: undefined + }) }); - }); - describe('ortb2Imp.ext.paapi.requestedSize', () => { - beforeEach(() => { - config.setConfig({ - paapi: { - enabled: true, - defaultForSlots: 1, - } + describe('ortb2Imp.ext.paapi.requestedSize', () => { + beforeEach(() => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + } + }); }); - }); - it('should default to value returned by getPAAPISize', () => { - getPAAPISizeStub.returns([123, 321]); - Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { - sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + it('should default to value returned by getPAAPISize', () => { + getPAAPISizeStub.returns([123, 321]); + expect(getImpExt().global.paapi).to.eql({ requestedSize: { width: 123, height: 321 } }); }); - }); - it('should not be overridden, if provided by the pub', () => { - adUnits[0].ortb2Imp = { - ext: { - paapi: { - requestedSize: { - width: '123px', - height: '321px' + it('should not be overridden, if provided by the pub', () => { + adUnits[0].ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: '123px', + height: '321px' + } } } - } - }; - Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { - sinon.assert.match(bidRequest.ortb2Imp.ext.paapi, { + }; + expect(getImpExt().global.paapi).to.eql({ requestedSize: { width: '123px', height: '321px' } - }); + }) + sinon.assert.notCalled(getPAAPISizeStub); }); - sinon.assert.notCalled(getPAAPISizeStub); - }); - it('should not be set if adUnit has no banner sizes', () => { - adUnits[0].mediaTypes = { - video: {} - }; - Object.values(mark()).flatMap(b => b.bids).forEach(bidRequest => { - expect(bidRequest.ortb2Imp?.ext?.paapi?.requestedSize).to.not.exist; + it('should not be set if adUnit has no banner sizes', () => { + adUnits[0].mediaTypes = { + video: {} + }; + expect(getImpExt().global?.paapi?.requestedSize).to.not.exist; }); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index d44a67d2acc..a6d91a6309b 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -825,7 +825,7 @@ describe('S2S Adapter', function () { }) }) }) - + it('should set customHeaders correctly when publisher has provided it', () => { let configWithCustomHeaders = utils.deepClone(CONFIG); configWithCustomHeaders.customHeaders = { customHeader1: 'customHeader1Value' }; @@ -3578,18 +3578,32 @@ describe('S2S Adapter', function () { sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), sinon.match({config: {id: 2}})) } - it('calls addComponentAuction alongside addBidResponse', function () { + it('calls addPaapiConfig alongside addBidResponse', function () { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); expect(addBidResponse.called).to.be.true; expectFledgeCalls(); }); - it('calls addComponentAuction when there is no bid in the response', () => { + it('calls addPaapiConfig when there is no bid in the response', () => { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); expect(addBidResponse.called).to.be.false; expectFledgeCalls(); + }); + + it('wraps call in runWithBidder', () => { + let fail = false; + fledgeStub.callsFake(({bidder}) => { + try { + expect(bidder).to.exist.and.to.eql(config.getCurrentBidder()); + } catch (e) { + fail = true; + } + }); + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); + expect(fail).to.be.false; }) }); }); From efbf6ad7ef734dc98329856d49bfbb2aa8e1b64d Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 11 Jul 2024 14:50:46 -0400 Subject: [PATCH 0328/1097] Update eightPodAnalyticsAdapter.js (#11962) --- modules/eightPodAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js index 559a8107ba2..6dab3b0c107 100644 --- a/modules/eightPodAnalyticsAdapter.js +++ b/modules/eightPodAnalyticsAdapter.js @@ -56,7 +56,7 @@ let eightPodAnalytics = Object.assign(adapter({url: trackerUrl, analyticsType}), window.addEventListener('message', async (event) => { const data = event.data; - const frameElement = event.source.frameElement; + const frameElement = event.source?.frameElement; const parentElement = frameElement?.parentElement; const adUnitCode = parentElement?.id; From 1bd87b71241e598e46dfdeb86a8172f0fd63dea1 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 11 Jul 2024 22:54:21 +0200 Subject: [PATCH 0329/1097] gptPreAuction: pass publisher provided signals to GPT (#11946) * 10997 set pps to gam display * update * update * review changes * module handling * code sharing * linting fixes * Rename setPPSConfig * Filter out adIds that have no auction * use eql instead of JSON for deep equals --------- Co-authored-by: Marcin Komorski Co-authored-by: Demetrio Girardi --- libraries/dfpUtils/dfpUtils.js | 7 ++ libraries/gptUtils/gptUtils.js | 24 +++- modules/dfpAdServerVideo.js | 48 +++----- modules/dfpAdpod.js | 8 +- modules/gptPreAuction.js | 67 ++++++++++- src/targeting.js | 46 ++++---- test/spec/modules/gptPreAuction_spec.js | 144 +++++++++++++++++++++++- 7 files changed, 279 insertions(+), 65 deletions(-) diff --git a/libraries/dfpUtils/dfpUtils.js b/libraries/dfpUtils/dfpUtils.js index 0f070b15ba2..d7df13824c7 100644 --- a/libraries/dfpUtils/dfpUtils.js +++ b/libraries/dfpUtils/dfpUtils.js @@ -11,3 +11,10 @@ export const DFP_ENDPOINT = { host: 'securepubads.g.doubleclick.net', pathname: '/gampad/ads' } + +export const setGdprConsent = (gdprConsent, queryParams) => { + if (!gdprConsent) { return; } + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } +} diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js index 950f28c618f..25c1de03538 100644 --- a/libraries/gptUtils/gptUtils.js +++ b/libraries/gptUtils/gptUtils.js @@ -1,5 +1,6 @@ +import { CLIENT_SECTIONS } from '../../src/fpd/oneClient.js'; import {find} from '../../src/polyfill.js'; -import {compareCodeAndSlot, isGptPubadsDefined} from '../../src/utils.js'; +import {compareCodeAndSlot, deepAccess, isGptPubadsDefined, uniques} from '../../src/utils.js'; /** * Returns filter function to match adUnitCode in slot @@ -35,3 +36,24 @@ export function getGptSlotInfoForAdUnitCode(adUnitCode) { } return {}; } + +export const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; + +export function getSignals(fpd) { + const signals = Object.entries({ + [taxonomies[0]]: getSegments(fpd, ['user.data'], 4), + [taxonomies[1]]: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) + }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) + .filter(ob => ob); + + return signals; +} + +export function getSegments(fpd, sections, segtax) { + return sections + .flatMap(section => deepAccess(fpd, section) || []) + .filter(datum => datum.ext?.segtax === segtax) + .flatMap(datum => datum.segment?.map(seg => seg.id)) + .filter(ob => ob) + .filter(uniques) +} diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 8325af56b20..367520870e3 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,8 +2,18 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import {registerVideoSupport} from '../src/adServerManager.js'; -import {targeting} from '../src/targeting.js'; +import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent } from '../libraries/dfpUtils/dfpUtils.js'; +import { getSignals } from '../libraries/gptUtils/gptUtils.js'; +import { registerVideoSupport } from '../src/adServerManager.js'; +import { gdprDataHandler } from '../src/adapterManager.js'; +import { getPPID } from '../src/adserver.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { config } from '../src/config.js'; +import { EVENTS } from '../src/constants.js'; +import * as events from '../src/events.js'; +import { getHook } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { targeting } from '../src/targeting.js'; import { buildUrl, deepAccess, @@ -12,19 +22,8 @@ import { isNumber, logError, parseSizesInput, - parseUrl, - uniques + parseUrl } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {getHook} from '../src/hook.js'; -import {auctionManager} from '../src/auctionManager.js'; -import {gdprDataHandler} from '../src/adapterManager.js'; -import * as events from '../src/events.js'; -import {EVENTS} from '../src/constants.js'; -import {getPPID} from '../src/adserver.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; -import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; /** * @typedef {Object} DfpVideoParams * @@ -115,11 +114,7 @@ export function buildDfpVideoUrl(options) { const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } - } + setGdprConsent(gdprConsent, queryParams); if (!queryParams.ppid) { const ppid = getPPID(); @@ -171,20 +166,7 @@ export function buildDfpVideoUrl(options) { const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ?? auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global; - function getSegments(sections, segtax) { - return sections - .flatMap(section => deepAccess(fpd, section) || []) - .filter(datum => datum.ext?.segtax === segtax) - .flatMap(datum => datum.segment?.map(seg => seg.id)) - .filter(ob => ob) - .filter(uniques) - } - - const signals = Object.entries({ - IAB_AUDIENCE_1_1: getSegments(['user.data'], 4), - IAB_CONTENT_2_2: getSegments(CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) - }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) - .filter(ob => ob); + const signals = getSignals(fpd); if (signals.length) { queryParams.ppsj = btoa(JSON.stringify({ diff --git a/modules/dfpAdpod.js b/modules/dfpAdpod.js index a5bd48f60e4..1675954459c 100644 --- a/modules/dfpAdpod.js +++ b/modules/dfpAdpod.js @@ -1,7 +1,7 @@ import {submodule} from '../src/hook.js'; import {buildUrl, deepAccess, formatQS, logError, parseSizesInput} from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; -import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT} from '../libraries/dfpUtils/dfpUtils.js'; +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent} from '../libraries/dfpUtils/dfpUtils.js'; import {gdprDataHandler} from '../src/consentHandler.js'; import {registerVideoSupport} from '../src/adServerManager.js'; @@ -79,11 +79,7 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { ); const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } - } + setGdprConsent(gdprConsent, queryParams); const masterTag = buildUrl({ ...DFP_ENDPOINT, diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 65b1bf24eef..29b9257d325 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -1,19 +1,68 @@ +import { getSignals as getSignalsFn, getSegments as getSegmentsFn, taxonomies } from '../libraries/gptUtils/gptUtils.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { config } from '../src/config.js'; +import { TARGETING_KEYS } from '../src/constants.js'; +import { getHook } from '../src/hook.js'; +import { find } from '../src/polyfill.js'; import { deepAccess, + deepSetValue, isAdUnitCodeMatchingSlot, isGptPubadsDefined, logInfo, + logWarn, pick, - deepSetValue, logWarn + uniques } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {getHook} from '../src/hook.js'; -import {find} from '../src/polyfill.js'; const MODULE_NAME = 'GPT Pre-Auction'; export let _currentConfig = {}; let hooksAdded = false; +export function getSegments(fpd, sections, segtax) { + return getSegmentsFn(fpd, sections, segtax); +} + +export function getSignals(fpd) { + return getSignalsFn(fpd); +} + +export function getSignalsArrayByAuctionsIds(auctionIds, index = auctionManager.index) { + const signals = auctionIds + .map(auctionId => index.getAuction({ auctionId })?.getFPD()?.global) + .map(getSignals) + .filter(fpd => fpd); + + return signals; +} + +export function getSignalsIntersection(signals) { + const result = {}; + taxonomies.forEach((taxonomy) => { + const allValues = signals + .flatMap(x => x) + .filter(x => x.taxonomy === taxonomy) + .map(x => x.values); + result[taxonomy] = allValues.length ? ( + allValues.reduce((commonElements, subArray) => { + return commonElements.filter(element => subArray.includes(element)); + }) + ) : [] + result[taxonomy] = { values: result[taxonomy] }; + }) + return result; +} + +export function getAuctionsIdsFromTargeting(targeting, am = auctionManager) { + return Object.values(targeting) + .flatMap(x => Object.entries(x)) + .filter((entry) => entry[0] === TARGETING_KEYS.AD_ID || entry[0].startsWith(TARGETING_KEYS.AD_ID + '_')) + .flatMap(entry => entry[1]) + .map(adId => am.findBidByAdId(adId)?.auctionId) + .filter(id => id != null) + .filter(uniques); +} + export const appendGptSlots = adUnits => { const { customGptSlotMatching } = _currentConfig; @@ -153,6 +202,14 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { return fn.call(this, adUnits, ...args); }; +const setPpsConfigFromTargetingSet = (next, targetingSet) => { + // set gpt config + const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); + const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); + window.googletag.setConfig && window.googletag.setConfig({pps: { taxonomies: signals }}); + next(targetingSet); +}; + const handleSetGptConfig = moduleConfig => { _currentConfig = pick(moduleConfig, [ 'enabled', enabled => enabled !== false, @@ -166,12 +223,14 @@ const handleSetGptConfig = moduleConfig => { if (_currentConfig.enabled) { if (!hooksAdded) { getHook('makeBidRequests').before(makeBidRequestsHook); + getHook('targetingDone').after(setPpsConfigFromTargetingSet) hooksAdded = true; } } else { logInfo(`${MODULE_NAME}: Turning off module`); _currentConfig = {}; getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); + getHook('targetingDone').getHooks({hook: setPpsConfigFromTargetingSet}).remove(); hooksAdded = false; } }; diff --git a/src/targeting.js b/src/targeting.js index 0c4874fc50b..9a2ea5d66fa 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,3 +1,21 @@ +import { auctionManager } from './auctionManager.js'; +import { getTTL } from './bidTTL.js'; +import { bidderSettings } from './bidderSettings.js'; +import { config } from './config.js'; +import { + BID_STATUS, + DEFAULT_TARGETING_KEYS, + EVENTS, + JSON_MAPPING, + NATIVE_KEYS, + STATUS, + TARGETING_KEYS +} from './constants.js'; +import * as events from './events.js'; +import { hook } from './hook.js'; +import { ADPOD } from './mediaTypes.js'; +import { NATIVE_TARGETING_KEYS } from './native.js'; +import { find, includes } from './polyfill.js'; import { deepAccess, deepClone, @@ -14,25 +32,7 @@ import { timestamp, uniques, } from './utils.js'; -import {config} from './config.js'; -import {NATIVE_TARGETING_KEYS} from './native.js'; -import {auctionManager} from './auctionManager.js'; -import {ADPOD} from './mediaTypes.js'; -import {hook} from './hook.js'; -import {bidderSettings} from './bidderSettings.js'; -import {find, includes} from './polyfill.js'; -import { - BID_STATUS, - DEFAULT_TARGETING_KEYS, - EVENTS, - JSON_MAPPING, - NATIVE_KEYS, - STATUS, - TARGETING_KEYS -} from './constants.js'; -import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; -import {getTTL} from './bidTTL.js'; -import * as events from './events.js'; +import { getHighestCpm, getOldestHighestCpmBid } from './utils/reducers.js'; var pbTargetingKeys = []; @@ -139,7 +139,7 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { * @param {Array} adUnitCodes * @param customSlotMatching * @param getSlots - * @return {{[p: string]: any}} + * @return {Object.} */ export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()) { return getSlots().reduce((auToSlots, slot) => { @@ -461,10 +461,16 @@ export function newTargeting(auctionManager) { }); }); + targeting.targetingDone(targetingSet); + // emit event events.emit(EVENTS.SET_TARGETING, targetingSet); }, 'setTargetingForGPT'); + targeting.targetingDone = hook('sync', function (targetingSet) { + return targetingSet; + }, 'targetingDone'); + /** * normlizes input to a `adUnit.code` array * @param {(string|string[])} adUnitCode [description] diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 5caa95404dc..88062f2b785 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -2,10 +2,16 @@ import { appendGptSlots, appendPbAdSlot, _currentConfig, - makeBidRequestsHook + makeBidRequestsHook, + getAuctionsIdsFromTargeting, + getSegments, + getSignals, + getSignalsArrayByAuctionsIds, + getSignalsIntersection } from 'modules/gptPreAuction.js'; import { config } from 'src/config.js'; import { makeSlot } from '../integration/faker/googletag.js'; +import { taxonomies } from '../../../libraries/gptUtils/gptUtils.js'; describe('GPT pre-auction module', () => { let sandbox; @@ -25,6 +31,87 @@ describe('GPT pre-auction module', () => { makeSlot({ code: 'slotCode4', divId: 'div5' }) ]; + const mockTargeting = {'/123456/header-bid-tag-0': {'hb_deal_rubicon': '1234', 'hb_deal': '1234', 'hb_pb': '0.53', 'hb_adid': '148018fe5e', 'hb_bidder': 'rubicon', 'foobar': '300x250', 'hb_pb_rubicon': '0.53', 'hb_adid_rubicon': '148018fe5e', 'hb_bidder_rubicon': 'rubicon', 'hb_deal_appnexus': '4321', 'hb_pb_appnexus': '0.1', 'hb_adid_appnexus': '567891011', 'hb_bidder_appnexus': 'appnexus'}} + + const mockAuctionManager = { + findBidByAdId(adId) { + const bidsMap = { + '148018fe5e': { + auctionId: mocksAuctions[0].auctionId + }, + '567891011': { + auctionId: mocksAuctions[1].auctionId + }, + }; + return bidsMap[adId]; + }, + index: { + getAuction({ auctionId }) { + return mocksAuctions.find(auction => auction.auctionId === auctionId); + } + } + } + + const mocksAuctions = [ + { + auctionId: '1111', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '1' + }, { + id: '2' + }] + }], + } + } + }) + }, + { + auctionId: '234234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }] + }] + } + } + }), + }, { + auctionId: '234324234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }, { + id: '3' + }] + }] + } + } + }) + }, + ] + describe('appendPbAdSlot', () => { // sets up our document body to test the pbAdSlot dom actions against document.body.innerHTML = '
test1
' + @@ -454,4 +541,59 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); }); + + describe('pps gpt config', () => { + it('should parse segments from fpd', () => { + const twoSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 4); + expect(JSON.stringify(twoSegments)).to.equal(JSON.stringify(['1', '2'])); + const zeroSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 6); + expect(zeroSegments).to.length(0); + }); + + it('should return signals from fpd', () => { + const signals = getSignals(mocksAuctions[0].getFPD().global); + const expectedSignals = [{ taxonomy: taxonomies[0], values: ['1', '2'] }]; + expect(signals).to.eql(expectedSignals); + }); + + it('should properly get auctions ids from targeting', () => { + const auctionsIds = getAuctionsIdsFromTargeting(mockTargeting, mockAuctionManager); + expect(auctionsIds).to.eql([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId]) + }); + + it('should filter out adIds that do not map to any auction', () => { + const auctionsIds = getAuctionsIdsFromTargeting({ + ...mockTargeting, + 'au': {'hb_adid': 'missing'}, + }, mockAuctionManager); + expect(auctionsIds).to.eql([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId]); + }) + + it('should properly return empty array of auction ids for invalid targeting', () => { + let auctionsIds = getAuctionsIdsFromTargeting({}, mockAuctionManager); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + auctionsIds = getAuctionsIdsFromTargeting({'/123456/header-bid-tag-0/bg': {'invalidContent': '123'}}, mockAuctionManager); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + }); + + it('should properly get signals from auctions', () => { + const signals = getSignalsArrayByAuctionsIds(['1111', '234234', '234324234'], mockAuctionManager.index); + const intersection = getSignalsIntersection(signals); + const expectedResult = { IAB_AUDIENCE_1_1: { values: ['2'] }, IAB_CONTENT_2_2: { values: [] } }; + expect(JSON.stringify(intersection)).to.be.equal(JSON.stringify(expectedResult)); + }); + + it('should return empty signals array for empty auctions ids array', () => { + const signals = getSignalsArrayByAuctionsIds([], mockAuctionManager.index); + expect(Array.isArray(signals)).to.equal(true); + expect(signals).to.length(0); + }); + + it('should return properly formatted object for getSignalsIntersection invoked with empty array', () => { + const signals = getSignalsIntersection([]); + expect(Object.keys(signals)).to.contain.members(taxonomies); + }); + }); }); From 3ae842339769a4c36e8bd0e4d5b293ecca723994 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 11 Jul 2024 23:27:13 +0000 Subject: [PATCH 0330/1097] Prebid 9.5.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59645e31c64..361a17d2bd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.5.0-pre", + "version": "9.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.5.0-pre", + "version": "9.5.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index adbead90d43..ae2e59a1f51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.5.0-pre", + "version": "9.5.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From d285f79ac090f02ca5d05c42716ce89b57264d2b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 11 Jul 2024 23:27:13 +0000 Subject: [PATCH 0331/1097] Increment version to 9.6.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 361a17d2bd2..09f7b34ea8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.5.0", + "version": "9.6.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.5.0", + "version": "9.6.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index ae2e59a1f51..6cbff559615 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.5.0", + "version": "9.6.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From af742ce3357dbb6c4d2b498a9462a3936c4db09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20Chr=C3=A9tien?= Date: Fri, 12 Jul 2024 03:29:40 +0200 Subject: [PATCH 0332/1097] Adagio Bid Adapter|Analytics Adapter: use rtd uid as auctionid (#11958) * AdagioAnalyticsAdapter: use adagio rtd.uid as auctionId * AdagioBidAdapter: use adagio rtd.uid as auctionId * AdagioAnalyticsAdapter: use common code --------- Co-authored-by: Olivier --- modules/adagioAnalyticsAdapter.js | 69 +++---- modules/adagioBidAdapter.js | 1 - .../modules/adagioAnalyticsAdapter_spec.js | 171 ++++++++---------- test/spec/modules/adagioBidAdapter_spec.js | 31 +++- 4 files changed, 127 insertions(+), 145 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index bb5de41d3ce..ddcf178c5fb 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -2,12 +2,13 @@ * Analytics Adapter for Adagio */ +import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; +import { deepAccess, logError, logInfo } from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { getWindowTop, getWindowSelf, deepAccess, logInfo, logError } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; const emptyUrl = ''; @@ -19,6 +20,13 @@ const PREBID_VERSION = '$prebid.version$'; const ENDPOINT = 'https://c.4dex.io/pba.gif'; const CURRENCY_USD = 'USD'; const ADAGIO_CODE = 'adagio'; + +export const _internal = { + getAdagioNs: function() { + return _ADAGIO; + } +}; + const cache = { auctions: {}, getAuction: function(auctionId, adUnitCode) { @@ -48,34 +56,6 @@ const cache = { }; const enc = window.encodeURIComponent; -/** -/* BEGIN ADAGIO.JS CODE - */ - -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -}; - -function getCurrentWindow() { - return currentWindow; -}; - -let currentWindow; - -const adagioEnqueue = function adagioEnqueue(action, data) { - getCurrentWindow().ADAGIO.queue.push({ action, data, ts: Date.now() }); -}; - -/** - * END ADAGIO.JS CODE - */ - /** * UTILS FUNCTIONS */ @@ -194,7 +174,7 @@ function getTargetedAuctionId(bid) { */ function handlerAuctionInit(event) { - const w = getCurrentWindow(); + const w = getBestWindowForAdagio(); const prebidAuctionId = event.auctionId; const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode); @@ -206,6 +186,8 @@ function handlerAuctionInit(event) { logInfo(`Adagio is not on the bid requests for auction '${prebidAuctionId}'`) return; } + const rtdUid = deepAccess(adagioBidRequest, 'ortb2.site.ext.data.adg_rtd.uid'); + cache.addPrebidAuctionIdRef(prebidAuctionId, rtdUid); cache.auctions[prebidAuctionId] = {}; @@ -250,22 +232,22 @@ function handlerAuctionInit(event) { // We assume that all Adagio bids for a same adunit have the same params. const params = adagioAdUnitBids[0].params; - const adagioAuctionId = params.adagioAuctionId; - cache.addPrebidAuctionIdRef(prebidAuctionId, adagioAuctionId); - // Get all media types requested for Adagio. const adagioMediaTypes = removeDuplicates( adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), mediaTypeKey => mediaTypeKey ).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort(); + // if adagio was involved in the auction we identified it with rtdUid, if not use the prebid auctionId + let auctionId = rtdUid || prebidAuctionId; + const qp = { v: 0, pbjsv: PREBID_VERSION, org_id: params.organizationId, site: params.site, pv_id: params.pageviewId, - auct_id: adagioAuctionId, + auct_id: auctionId, adu_code: adUnitCode, url_dmn: w.location.hostname, pgtyp: params.pagetype, @@ -344,7 +326,6 @@ function handlerBidWon(event) { (event.latestTargetedAuctionId && event.latestTargetedAuctionId !== event.auctionId) ? cache.getAdagioAuctionId(event.auctionId) : null); - cache.updateAuction(auctionId, event.adUnitCode, { win_bdr: event.bidder, win_mt: getMediaTypeAlias(event.mediaType), @@ -407,7 +388,11 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { try { if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) { - adagioEnqueue('pb-analytics-event', { eventName: eventType, args }); + _internal.getAdagioNs().queue.push({ + action: 'pb-analytics-event', + data: { eventName: eventType, args }, + ts: Date.now() + }); } } catch (error) { logError('Error on Adagio Analytics Adapter - adagio.js', error); @@ -418,13 +403,7 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics; adagioAdapter.enableAnalytics = config => { - const w = (canAccessTopWindow()) ? getWindowTop() : getWindowSelf(); - currentWindow = w; - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.versions = w.ADAGIO.versions || {}; - w.ADAGIO.versions.adagioAnalyticsAdapter = VERSION; + _internal.getAdagioNs().versions.adagioAnalyticsAdapter = VERSION; adagioAdapter.originEnableAnalytics(config); } diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index a4aaa64bf4f..9737a8da65d 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -724,7 +724,6 @@ export const spec = { // Those params are not sent to the server. // They are used for further operations on analytics adapter. validBidRequests.forEach(rawBidRequest => { - rawBidRequest.params.adagioAuctionId = aucId rawBidRequest.params.pageviewId = pageviewId }); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 663da9c4fb8..ec3e2e1a28e 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -1,9 +1,9 @@ -import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter.js'; -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { server } from 'test/mocks/xhr.js'; import * as prebidGlobal from 'src/prebidGlobal.js'; +import * as utils from 'src/utils.js'; +import adagioAnalyticsAdapter, { _internal } from 'modules/adagioAnalyticsAdapter.js'; import { EVENTS } from 'src/constants.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); @@ -14,23 +14,18 @@ describe('adagio analytics adapter - adagio.js', () => { beforeEach(() => { sandbox = sinon.createSandbox(); - sandbox.stub(events, 'getEvents').returns([]); - const w = utils.getWindowTop(); - adapterManager.registerAnalyticsAdapter({ code: 'adagio', adapter: adagioAnalyticsAdapter }); - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.queue = w.ADAGIO.queue || []; - - adagioQueuePushSpy = sandbox.spy(w.ADAGIO.queue, 'push'); + adagioQueuePushSpy = sandbox.spy(_internal.getAdagioNs().queue, 'push'); }); afterEach(() => { + _internal.getAdagioNs().queue = []; sandbox.restore(); }); @@ -93,7 +88,6 @@ describe('adagio analytics adapter - adagio.js', () => { // Step 1-3: Send events Object.entries(testEvents).forEach(([ev, payload]) => events.emit(ev, payload)); - function eventItem(eventName, args) { return sinon.match({ action: 'pb-analytics-event', @@ -108,79 +102,12 @@ describe('adagio analytics adapter - adagio.js', () => { Object.entries(testEvents).forEach(([ev, payload]) => sinon.assert.calledWith(adagioQueuePushSpy, eventItem(ev, payload))); }); }); - - describe('no track', () => { - beforeEach(() => { - sandbox.stub(utils, 'getWindowTop').throws(); - - adapterManager.enableAnalytics({ - provider: 'adagio' - }); - }); - - afterEach(() => { - adagioAnalyticsAdapter.disableAnalytics(); - sandbox.restore(); - }); - - it('builds and sends auction data', () => { - let bidRequest = { - bids: [{ - adUnitCode: 'div-1', - params: { - features: { - siteId: '2', - placement: 'pave_top', - pagetype: 'article', - category: 'IAB12,IAB12-2', - device: '2', - } - } - }, { - adUnitCode: 'div-2', - params: { - features: { - siteId: '2', - placement: 'ban_top', - pagetype: 'article', - category: 'IAB12,IAB12-2', - device: '2', - } - }, - }], - }; - let bidResponse = { - bidderCode: 'adagio', - width: 300, - height: 250, - statusMessage: 'Bid available', - cpm: 6.2189757658226075, - currency: '', - netRevenue: false, - adUnitCode: 'div-1', - timeToRespond: 132, - }; - - // Step 1: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, bidRequest); - - // Step 2: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bidResponse); - - // Step 3: Send auction end event - events.emit(EVENTS.AUCTION_END, {}); - - utils.getWindowTop.restore(); - - sandbox.assert.callCount(adagioQueuePushSpy, 0); - }); - }); }); const AUCTION_ID = '25c6d7f5-699a-4bfc-87c9-996f915341fa'; -const AUCTION_ID_ADAGIO = '6fc53663-bde5-427b-ab63-baa9ed296f47' +const RTD_AUCTION_ID = '753b3784-12a1-44c2-9d08-d0e4ee910e69'; +const RTD_AUCTION_ID_CACHE = '04d991be-8f7d-4491-930b-2b7eefb3c447'; const AUCTION_ID_CACHE = 'b43d24a0-13d4-406d-8176-3181402bafc4'; -const AUCTION_ID_CACHE_ADAGIO = 'a9cae98f-efb5-477e-9259-27350044f8db'; const BID_ADAGIO = { bidder: 'adagio', @@ -264,6 +191,14 @@ const PARAMS_ADG = { testVersion: 'version', }; +const ORTB_DATA = { + pagetype: 'article', +}; + +const ADG_RTD = { + 'uid': RTD_AUCTION_ID, +}; + const AUCTION_INIT_ANOTHER = { 'auctionId': AUCTION_ID, 'timestamp': 1519767010567, @@ -387,6 +322,18 @@ const AUCTION_INIT_ANOTHER = { 'timeout': 3000, 'refererInfo': { 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } + } } }, { 'bidderCode': 'adagio', @@ -395,8 +342,7 @@ const AUCTION_INIT_ANOTHER = { 'bids': [ { 'bidder': 'adagio', 'params': { - ...PARAMS_ADG, - adagioAuctionId: AUCTION_ID_ADAGIO + ...PARAMS_ADG }, 'mediaTypes': { 'banner': { @@ -416,6 +362,18 @@ const AUCTION_INIT_ANOTHER = { 'timeout': 3000, 'refererInfo': { 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } + } } } ], @@ -524,6 +482,19 @@ const AUCTION_INIT_CACHE = { 'timeout': 3000, 'refererInfo': { 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD, + 'uid': RTD_AUCTION_ID_CACHE + }, + ...ORTB_DATA + } + } + } } }, { 'bidderCode': 'adagio', @@ -532,8 +503,7 @@ const AUCTION_INIT_CACHE = { 'bids': [ { 'bidder': 'adagio', 'params': { - ...PARAMS_ADG, - adagioAuctionId: AUCTION_ID_CACHE_ADAGIO + ...PARAMS_ADG }, 'mediaTypes': { 'banner': { @@ -553,6 +523,19 @@ const AUCTION_INIT_CACHE = { 'timeout': 3000, 'refererInfo': { 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD, + 'uid': RTD_AUCTION_ID_CACHE + }, + ...ORTB_DATA + } + } + } } } ], @@ -632,6 +615,7 @@ describe('adagio analytics adapter', () => { }); afterEach(() => { + _internal.getAdagioNs().queue = []; sandbox.restore(); }); @@ -676,7 +660,7 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -708,7 +692,7 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('3'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.win_bdr).to.equal('another'); expect(search.win_mt).to.equal('ban'); @@ -743,13 +727,14 @@ describe('adagio analytics adapter', () => { expect(server.requests.length).to.equal(5, 'requests count'); { + // the first request is getting cached we expect to see its auction id later when it's re-used const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID_CACHE); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -772,7 +757,7 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -804,8 +789,8 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('3'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); - expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.auct_id_c).to.equal(RTD_AUCTION_ID_CACHE); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.win_bdr).to.equal('adagio'); expect(search.win_mt).to.equal('ban'); @@ -821,8 +806,8 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('4'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); - expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.auct_id_c).to.equal(RTD_AUCTION_ID_CACHE); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.rndr).to.equal('0'); } diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 4f942e21c0e..7c1b3421970 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -277,8 +277,6 @@ describe('Adagio bid adapter', () => { it('should send bid request to ENDPOINT_PB via POST', function() { sandbox.stub(_internal, 'getDevice').returns({ a: 'a' }); sandbox.stub(_internal, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); - // sandbox.stub(_internal, 'getPageviewId').returns('1234-567'); - // sandbox.stub(utils, 'generateUUID').returns('blabla'); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); @@ -292,7 +290,31 @@ describe('Adagio bid adapter', () => { expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); - it('should use a custom generated auctionId and remove transactionId', function() { + it('should use a custom generated auctionId from ortb2.site.ext.data.adg_rtd.uid when available', function() { + const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' + + const bid01 = new BidRequestBuilder().withParams().build(); + let ortb = { + ortb2: { + site: { + ext: { + data: { + adg_rtd: { + uid: expectedAuctionId + } + } + } + } + } + } + const bidderRequest = new BidderRequestBuilder(ortb).build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.adUnits[0].auctionId).eq(expectedAuctionId); + expect(requests[0].data.adUnits[0].transactionId).to.not.exist; + }); + + it('should use a custom generated auctionId when ortb2.site.ext.data.adg_rtd.uid is absent and remove transactionId', function() { const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' sandbox.stub(utils, 'generateUUID').returns(expectedAuctionId); @@ -305,9 +327,7 @@ describe('Adagio bid adapter', () => { }); it('should enrich prebid bid requests params', function() { - const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' const expectedPageviewId = '56befc26-8cf0-472d-b105-73896df8eb89'; - sandbox.stub(utils, 'generateUUID').returns(expectedAuctionId); sandbox.stub(_internal, 'getAdagioNs').returns({ pageviewId: expectedPageviewId }); const bid01 = new BidRequestBuilder().withParams().build(); @@ -315,7 +335,6 @@ describe('Adagio bid adapter', () => { spec.buildRequests([bid01], bidderRequest); - expect(bid01.params.adagioAuctionId).eq(expectedAuctionId); expect(bid01.params.pageviewId).eq(expectedPageviewId); }); From 85e6ef1af621a04a337d6538a8d2d6f61e5bd4c4 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Fri, 12 Jul 2024 10:23:26 -0400 Subject: [PATCH 0333/1097] removes idx and loop to create 5 syncs (#37) (#11968) --- modules/kargoBidAdapter.js | 25 ++++++++++------------- test/spec/modules/kargoBidAdapter_spec.js | 15 +++++++------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 91d067ab67d..8429228d7af 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -48,7 +48,7 @@ const SUA_ATTRIBUTES = [ const CERBERUS = Object.freeze({ KEY: 'krg_crb', - SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', SYNC_COUNT: 5, PAGE_VIEW_ID: 'pageViewId', PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', @@ -274,19 +274,16 @@ function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { return syncs; } if (syncOptions.iframeEnabled && seed && clientId) { - for (let i = 0; i < CERBERUS.SYNC_COUNT; i++) { - syncs.push({ - type: 'iframe', - url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) - .replace('{SEED}', seed) - .replace('{INDEX}', i) - .replace('{GDPR}', gdpr) - .replace('{GDPR_CONSENT}', gdprConsentString) - .replace('{US_PRIVACY}', usPrivacy || '') - .replace('{GPP_STRING}', gppString) - .replace('{GPP_SID}', gppApplicableSections) - }); - } + syncs.push({ + type: 'iframe', + url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) + .replace('{SEED}', seed) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') + .replace('{GPP_STRING}', gppString) + .replace('{GPP_SID}', gppApplicableSections) + }) } return syncs; } diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 590d98969c3..e24c34dd1ab 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1885,16 +1885,15 @@ describe('kargo adapter tests', function() { describe('getUserSyncs', function() { let crb = {}; const clientId = 'random-client-id-string'; - const baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid='; + const baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid='; - function buildSyncUrls(baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=') { + function buildSyncUrls(baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=') { let syncs = []; - for (let i = 0; i < 5; i++) { - syncs.push({ - type: 'iframe', - url: baseUrl.replace(/idx=\d+&/, `idx=${i}&`), - }); - } + + syncs.push({ + type: 'iframe', + url: baseUrl + }); return syncs; } From 20bf00e9e15879f8aaf9ebcb067a287500624100 Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 12 Jul 2024 20:58:33 +0200 Subject: [PATCH 0334/1097] AdagioBidAdapter: GPP: remove useless logic (#11971) --- modules/adagioBidAdapter.js | 19 ++---- test/spec/modules/adagioBidAdapter_spec.js | 67 +++------------------- 2 files changed, 11 insertions(+), 75 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 9737a8da65d..9bcebf9205e 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -20,11 +20,11 @@ import { import { getRefererInfo, parseDomain } from '../src/refererDetection.js'; import { OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; +import { _ADAGIO } from '../libraries/adagioUtils/adagioUtils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { find } from '../src/polyfill.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; -import { _ADAGIO } from '../libraries/adagioUtils/adagioUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { userSync } from '../src/userSync.js'; @@ -176,17 +176,6 @@ function _getUspConsent(bidderRequest) { return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; } -function _getGppConsent(bidderRequest) { - let gpp = deepAccess(bidderRequest, 'gppConsent.gppString') - let gppSid = deepAccess(bidderRequest, 'gppConsent.applicableSections') - - if (!gpp || !gppSid) { - gpp = deepAccess(bidderRequest, 'ortb2.regs.gpp', '') - gppSid = deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []) - } - return { gpp, gppSid } -} - function _getSchain(bidRequest) { return deepAccess(bidRequest, 'schain'); } @@ -559,7 +548,7 @@ export const spec = { const gdprConsent = _getGdprConsent(bidderRequest) || {}; const uspConsent = _getUspConsent(bidderRequest) || {}; const coppa = _getCoppa(); - const gppConsent = _getGppConsent(bidderRequest) + const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const schain = _getSchain(validBidRequests[0]); const eids = _getEids(validBidRequests[0]) || []; const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled') @@ -747,8 +736,8 @@ export const spec = { gdpr: gdprConsent, coppa: coppa, ccpa: uspConsent, - gpp: gppConsent.gpp, - gppSid: gppConsent.gppSid, + gpp: gpp || '', + gppSid: gppSid || [], dsa: dsa // populated if exists }, schain: schain, diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 7c1b3421970..629771de9d6 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -801,77 +801,24 @@ describe('Adagio bid adapter', () => { describe('with GPP', function() { const bid01 = new BidRequestBuilder().withParams().build(); - const regsGpp = 'regs_gpp_consent_string'; - const regsApplicableSections = [2]; + const gpp = 'gpp_consent_string'; + const gppSid = [1]; - const ortb2Gpp = 'ortb2_gpp_consent_string'; - const ortb2GppSid = [1]; - - context('When GPP in regs module', function() { - it('send gpp and gppSid to the server', function() { - const bidderRequest = new BidderRequestBuilder({ - gppConsent: { - gppString: regsGpp, - applicableSections: regsApplicableSections, - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data.regs.gpp).to.equal(regsGpp); - expect(requests[0].data.regs.gppSid).to.equal(regsApplicableSections); - }); - }); - - context('When GPP partially defined in regs module', function() { - it('send gpp and gppSid coming from ortb2 to the server', function() { - const bidderRequest = new BidderRequestBuilder({ - gppConsent: { - gppString: regsGpp, - }, - ortb2: { - regs: { - gpp: ortb2Gpp, - gpp_sid: ortb2GppSid, - } - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data.regs.gpp).to.equal(ortb2Gpp); - expect(requests[0].data.regs.gppSid).to.equal(ortb2GppSid); - }); - - it('send empty gpp and gppSid if no ortb2 fields to the server', function() { - const bidderRequest = new BidderRequestBuilder({ - gppConsent: { - gppString: regsGpp, - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data.regs.gpp).to.equal(''); - expect(requests[0].data.regs.gppSid).to.be.empty; - }); - }); - - context('When GPP defined in ortb2 module', function() { + context('When GPP is defined', function() { it('send gpp and gppSid coming from ortb2 to the server', function() { const bidderRequest = new BidderRequestBuilder({ ortb2: { regs: { - gpp: ortb2Gpp, - gpp_sid: ortb2GppSid, + gpp, + gpp_sid: gppSid, } } }).build(); const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.regs.gpp).to.equal(ortb2Gpp); - expect(requests[0].data.regs.gppSid).to.equal(ortb2GppSid); + expect(requests[0].data.regs.gpp).to.equal(gpp); + expect(requests[0].data.regs.gppSid).to.equal(gppSid); }); }); From eccf0647217fed4f581b144dbd9bc11299d2e028 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Mon, 15 Jul 2024 12:50:05 +0200 Subject: [PATCH 0335/1097] amxId fix (#11973) Co-authored-by: Gabriel Chicoye --- modules/nexx360BidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index b4f7cf50ffe..29700c14a6c 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -125,7 +125,7 @@ const converter = ortbConverter({ deepSetValue(request, 'ext.localStorage.nexx360Id', nexx360LocalStorage.nexx360Id); } const amxId = getAmxId(); - if (amxId) deepSetValue(request, 'ext.localStorage.amxId', amxId()); + if (amxId) deepSetValue(request, 'ext.localStorage.amxId', amxId); deepSetValue(request, 'ext.version', '$prebid.version$'); deepSetValue(request, 'ext.source', 'prebid.js'); deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); From 6a85621d20266f43ce1d724c05b3388b274cc4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20Chr=C3=A9tien?= Date: Mon, 15 Jul 2024 12:55:59 +0200 Subject: [PATCH 0336/1097] AdagioAnalyticsAdapter: send PBA for all auctions (#11961) * AdagioAnalyticsAdapter: send PBA for all auctions * AdagioAnalyticsAdapter: typo * AdagioAnalyticsAdapter: update md file --------- Co-authored-by: Olivier --- modules/adagioAnalyticsAdapter.js | 128 +++++++---- modules/adagioAnalyticsAdapter.md | 4 + .../modules/adagioAnalyticsAdapter_spec.js | 207 +++++++++++++----- 3 files changed, 243 insertions(+), 96 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index ddcf178c5fb..033809e7f1b 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -3,7 +3,7 @@ */ import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; -import { deepAccess, logError, logInfo } from '../src/utils.js'; +import { deepAccess, logError, logInfo, logWarn } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -130,6 +130,10 @@ function getCurrencyData(bid) { * @param {Object} qp */ function sendRequest(qp) { + if (!qp.org_id || !qp.site) { + logInfo('request is missing org_id or site, skipping beacon.'); + return; + } // Removing null values qp = Object.keys(qp).reduce((acc, key) => { if (qp[key] !== null) { @@ -180,33 +184,22 @@ function handlerAuctionInit(event) { const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode); // Check if Adagio is on the bid requests. - // If not, we don't need to track the auction. const adagioBidRequest = event.bidderRequests.find(bidRequest => isAdagio(bidRequest.bidderCode)); - if (!adagioBidRequest) { - logInfo(`Adagio is not on the bid requests for auction '${prebidAuctionId}'`) - return; - } + const rtdUid = deepAccess(adagioBidRequest, 'ortb2.site.ext.data.adg_rtd.uid'); cache.addPrebidAuctionIdRef(prebidAuctionId, rtdUid); cache.auctions[prebidAuctionId] = {}; adUnitCodes.forEach(adUnitCode => { + // event.adUnits are splitted by mediatypes const adUnits = event.adUnits.filter(adUnit => adUnit.code === adUnitCode); - // Get all bidders configures for the ad unit. - const bidders = removeDuplicates( - adUnits.map(adUnit => adUnit.bids.map(bid => ({bidder: bid.bidder, params: bid.params}))).flat(), - bidder => bidder.bidder - ); - - // Check if Adagio is configured for the ad unit. - // If not, we don't need to track the ad unit. - const adagioBidder = bidders.find(bidder => isAdagio(bidder.bidder)); - if (!adagioBidder) { - logInfo(`Adagio is not configured for ad unit '${adUnitCode}'`); - return; - } + // Get all bidders configured for the ad unit. + // AdUnits with the same code can have a different bidder list, aggregate all of them. + const biddersAggregate = adUnits.reduce((bidders, adUnit) => bidders.concat(adUnit.bids.map(bid => bid.bidder)), []) + // remove duplicates + const bidders = [...new Set(biddersAggregate)]; // Get all media types and banner sizes configured for the ad unit. const mediaTypes = adUnits.map(adUnit => adUnit.mediaTypes); @@ -221,45 +214,56 @@ function handlerAuctionInit(event) { bannerSize => bannerSize ).sort(); - // Get all Adagio bids for the ad unit from the bidRequest. - // If no bids, we don't need to track the ad unit. - const adagioAdUnitBids = adagioBidRequest.bids.filter(bid => bid.adUnitCode === adUnitCode); - if (deepAccess(adagioAdUnitBids, 'length', 0) <= 0) { - logInfo(`Adagio is not on the bid requests for ad unit '${adUnitCode}' and auction '${prebidAuctionId}'`) - return; - } - // Get Adagio params from the first bid. - // We assume that all Adagio bids for a same adunit have the same params. - const params = adagioAdUnitBids[0].params; + const sortedBidderCodes = bidders.sort() - // Get all media types requested for Adagio. - const adagioMediaTypes = removeDuplicates( - adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), - mediaTypeKey => mediaTypeKey - ).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort(); + const bidSrcMapper = (bidder) => { + const request = event.bidderRequests.find(br => br.bidderCode === bidder) + return request ? request.bids[0].src : null + } + const biddersSrc = sortedBidderCodes.map(bidSrcMapper).join(','); // if adagio was involved in the auction we identified it with rtdUid, if not use the prebid auctionId - let auctionId = rtdUid || prebidAuctionId; + const auctionId = rtdUid || prebidAuctionId; + + const adgRtdSession = deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.adg_rtd.session', {}); const qp = { + org_id: adagioAdapter.options.organizationId, + site: adagioAdapter.options.site, v: 0, pbjsv: PREBID_VERSION, - org_id: params.organizationId, - site: params.site, - pv_id: params.pageviewId, + pv_id: _internal.getAdagioNs().pageviewId, auct_id: auctionId, adu_code: adUnitCode, url_dmn: w.location.hostname, - pgtyp: params.pagetype, - plcmt: params.placement, - t_n: params.testName || null, - t_v: params.testVersion || null, mts: mediaTypesKeys.join(','), ban_szs: bannerSizes.join(','), - bdrs: bidders.map(bidder => bidder.bidder).sort().join(','), - adg_mts: adagioMediaTypes.join(',') + bdrs: sortedBidderCodes.join(','), + pgtyp: deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.pagetype', null), + plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.placement', null), + t_n: adgRtdSession.testName || null, + t_v: adgRtdSession.testVersion || null, + s_id: adgRtdSession.id || null, + s_new: adgRtdSession.new || null, + bdrs_src: biddersSrc, }; + if (adagioBidRequest && adagioBidRequest.bids) { + const adagioAdUnitBids = adagioBidRequest.bids.filter(bid => bid.adUnitCode === adUnitCode); + if (adagioAdUnitBids.length > 0) { + // Get all media types requested for Adagio. + const adagioMediaTypes = removeDuplicates( + adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), + mediaTypeKey => mediaTypeKey + ).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort(); + + qp.adg_mts = adagioMediaTypes.join(','); + // for backward compatibility: if we didn't find organizationId & site but we have a bid from adagio we might still find it in params + qp.org_id = qp.org_id || adagioAdUnitBids[0].params.organizationId; + qp.site = qp.site || adagioAdUnitBids[0].params.site; + } + } + cache.auctions[prebidAuctionId][adUnitCode] = qp; sendNewBeacon(prebidAuctionId, adUnitCode); }); @@ -306,13 +310,21 @@ function handlerAuctionEnd(event) { return bid ? getCurrencyData(bid).netCpm : null } + const perfNavigation = performance.getEntriesByType('navigation')[0]; + cache.updateAuction(auctionId, adUnitCode, { bdrs_bid: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidResponseMapper).join(','), - bdrs_cpm: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidCpmMapper).join(',') + bdrs_cpm: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidCpmMapper).join(','), + // check timings at the end of the auction to leave time to the browser to update it + dom_i: Math.round(perfNavigation['domInteractive']) || null, + dom_c: Math.round(perfNavigation['domComplete']) || null, + loa_e: Math.round(perfNavigation['loadEventEnd']) || null, }); + sendNewBeacon(auctionId, adUnitCode); }); } + function handlerBidWon(event) { let auctionId = getTargetedAuctionId(event); @@ -326,6 +338,9 @@ function handlerBidWon(event) { (event.latestTargetedAuctionId && event.latestTargetedAuctionId !== event.auctionId) ? cache.getAdagioAuctionId(event.auctionId) : null); + + const perfNavigation = performance.getEntriesByType('navigation')[0]; + cache.updateAuction(auctionId, event.adUnitCode, { win_bdr: event.bidder, win_mt: getMediaTypeAlias(event.mediaType), @@ -334,6 +349,11 @@ function handlerBidWon(event) { win_net_cpm: currencyData.netCpm, win_og_cpm: currencyData.orginalCpm, + // check timings at the end of the auction to leave time to the browser to update it + dom_i: Math.round(perfNavigation['domInteractive']) || null, + dom_c: Math.round(perfNavigation['domComplete']) || null, + loa_e: Math.round(perfNavigation['loadEventEnd']) || null, + // cache bid id auct_id_c: adagioAuctionCacheId, }); @@ -405,6 +425,24 @@ adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics; adagioAdapter.enableAnalytics = config => { _internal.getAdagioNs().versions.adagioAnalyticsAdapter = VERSION; + let modules = getGlobal().installedModules; + if (modules && (!modules.length || modules.indexOf('adagioRtdProvider') === -1 || modules.indexOf('rtdModule') === -1)) { + logError('Adagio Analytics Adapter requires rtdModule & adagioRtdProvider modules which are not installed. No beacon will be sent'); + return; + } + + adagioAdapter.options = config.options || {}; + if (!adagioAdapter.options.organizationId) { + logWarn('Adagio Analytics Adapter: organizationId is required and is missing will try to fallback on params.'); + } else { + adagioAdapter.options.organizationId = adagioAdapter.options.organizationId.toString(); // allows publisher to pass it as a number + } + if (!adagioAdapter.options.site) { + logWarn('Adagio Analytics Adapter: site is required and is missing will try to fallback on params.'); + } else if (typeof adagioAdapter.options.site !== 'string') { + logWarn('Adagio Analytics Adapter: site should be a string will try to fallback on params.'); + adagioAdapter.options.site = undefined; + } adagioAdapter.originEnableAnalytics(config); } diff --git a/modules/adagioAnalyticsAdapter.md b/modules/adagioAnalyticsAdapter.md index 9fc2cb0bb88..916f9ec9c58 100644 --- a/modules/adagioAnalyticsAdapter.md +++ b/modules/adagioAnalyticsAdapter.md @@ -13,5 +13,9 @@ Analytics adapter for Adagio ```js pbjs.enableAnalytics({ provider: 'adagio', + options: { + organizationId: '1000', // Required. Provided by Adagio + site: 'my-website', // Required. Provided by Adagio + } }); ``` diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index ec3e2e1a28e..2c1f259ea35 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -16,11 +16,17 @@ describe('adagio analytics adapter - adagio.js', () => { sandbox = sinon.createSandbox(); sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + installedModules: ['adagioRtdProvider', 'rtdModule'] + }); + adapterManager.registerAnalyticsAdapter({ code: 'adagio', adapter: adagioAnalyticsAdapter }); + _internal.getAdagioNs().pageviewId = 'a68e6d70-213b-496c-be0a-c468ff387106'; + adagioQueuePushSpy = sandbox.spy(_internal.getAdagioNs().queue, 'push'); }); @@ -32,7 +38,11 @@ describe('adagio analytics adapter - adagio.js', () => { describe('track', () => { beforeEach(() => { adapterManager.enableAnalytics({ - provider: 'adagio' + provider: 'adagio', + options: { + organizationId: '1001', + site: 'test-com', + } }); }); @@ -108,6 +118,7 @@ const AUCTION_ID = '25c6d7f5-699a-4bfc-87c9-996f915341fa'; const RTD_AUCTION_ID = '753b3784-12a1-44c2-9d08-d0e4ee910e69'; const RTD_AUCTION_ID_CACHE = '04d991be-8f7d-4491-930b-2b7eefb3c447'; const AUCTION_ID_CACHE = 'b43d24a0-13d4-406d-8176-3181402bafc4'; +const SESSION_ID = 'c4f9e517-a592-45af-9560-ca191823d591'; const BID_ADAGIO = { bidder: 'adagio', @@ -181,14 +192,7 @@ const BID_CACHED = Object.assign({}, BID_ADAGIO, { }); const PARAMS_ADG = { - organizationId: '1001', - site: 'test-com', - pageviewId: 'a68e6d70-213b-496c-be0a-c468ff387106', environment: 'desktop', - pagetype: 'article', - placement: 'pave_top', - testName: 'test', - testVersion: 'version', }; const ORTB_DATA = { @@ -197,6 +201,11 @@ const ORTB_DATA = { const ADG_RTD = { 'uid': RTD_AUCTION_ID, + 'session': { + 'testName': 'test', + 'testVersion': 'version', + 'id': SESSION_ID, + } }; const AUCTION_INIT_ANOTHER = { @@ -236,7 +245,14 @@ const AUCTION_INIT_ANOTHER = { ...PARAMS_ADG }, }, ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, }, { 'code': '/19968336/footer-bid-tag-1', 'mediaTypes': { @@ -256,7 +272,14 @@ const AUCTION_INIT_ANOTHER = { 'publisherId': '1001' }, } ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, } ], 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], 'bidderRequests': [ { @@ -414,7 +437,14 @@ const AUCTION_INIT_CACHE = { ...PARAMS_ADG }, }, ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, }, { 'code': '/19968336/footer-bid-tag-1', 'mediaTypes': { @@ -434,7 +464,14 @@ const AUCTION_INIT_CACHE = { 'publisherId': '1001' }, } ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, } ], 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], 'bidderRequests': [ { @@ -604,10 +641,27 @@ describe('adagio analytics adapter', () => { let sandbox; beforeEach(() => { - sandbox = sinon.sandbox.create(); + sandbox = sinon.createSandbox(); sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + installedModules: ['adagioRtdProvider', 'rtdModule'], + convertCurrency: (cpm, from, to) => { + const convKeys = { + 'GBP-EUR': 0.7, + 'EUR-GBP': 1.3, + 'USD-EUR': 0.8, + 'EUR-USD': 1.2, + 'USD-GBP': 0.6, + 'GBP-USD': 1.6, + }; + return cpm * (convKeys[`${from}-${to}`] || 1); + } + }); + + _internal.getAdagioNs().pageviewId = 'a68e6d70-213b-496c-be0a-c468ff387106'; + adapterManager.registerAnalyticsAdapter({ code: 'adagio', adapter: adagioAnalyticsAdapter @@ -622,7 +676,11 @@ describe('adagio analytics adapter', () => { describe('track', () => { beforeEach(() => { adapterManager.enableAnalytics({ - provider: 'adagio' + provider: 'adagio', + options: { + organizationId: '1001', + site: 'test-com', + } }); }); @@ -631,20 +689,6 @@ describe('adagio analytics adapter', () => { }); it('builds and sends auction data', () => { - sandbox.stub(prebidGlobal, 'getGlobal').returns({ - convertCurrency: (cpm, from, to) => { - const convKeys = { - 'GBP-EUR': 0.7, - 'EUR-GBP': 1.3, - 'USD-EUR': 0.8, - 'EUR-USD': 1.2, - 'USD-GBP': 0.6, - 'GBP-USD': 1.6, - }; - return cpm * (convKeys[`${from}-${to}`] || 1); - } - }); - events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); @@ -652,7 +696,7 @@ describe('adagio analytics adapter', () => { events.emit(EVENTS.BID_WON, MOCK.BID_WON.another); events.emit(EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); - expect(server.requests.length).to.equal(3, 'requests count'); + expect(server.requests.length).to.equal(5, 'requests count'); { const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); expect(protocol).to.equal('https'); @@ -660,6 +704,7 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.s_id).to.equal(SESSION_ID); expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); @@ -679,7 +724,16 @@ describe('adagio analytics adapter', () => { expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); expect(search.bdrs_bid).to.equal('1,1,0'); @@ -687,7 +741,17 @@ describe('adagio analytics adapter', () => { } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); @@ -703,20 +767,6 @@ describe('adagio analytics adapter', () => { }); it('builds and sends auction data with a cached bid win', () => { - sandbox.stub(prebidGlobal, 'getGlobal').returns({ - convertCurrency: (cpm, from, to) => { - const convKeys = { - 'GBP-EUR': 0.7, - 'EUR-GBP': 1.3, - 'USD-EUR': 0.8, - 'EUR-USD': 1.2, - 'USD-GBP': 0.6, - 'GBP-USD': 1.6, - }; - return cpm * (convKeys[`${from}-${to}`] || 1); - } - }); - events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); @@ -725,7 +775,7 @@ describe('adagio analytics adapter', () => { events.emit(EVENTS.BID_WON, MOCK.BID_WON.bidcached); events.emit(EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED.bidcached); - expect(server.requests.length).to.equal(5, 'requests count'); + expect(server.requests.length).to.equal(8, 'requests count'); { // the first request is getting cached we expect to see its auction id later when it's re-used const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); @@ -735,6 +785,7 @@ describe('adagio analytics adapter', () => { expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); expect(search.auct_id).to.equal(RTD_AUCTION_ID_CACHE); + expect(search.s_id).to.equal(SESSION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -757,6 +808,27 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID_CACHE); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.org_id).to.equal('1001'); + expect(search.site).to.equal('test-com'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.url_dmn).to.equal(window.location.hostname); + expect(search.pgtyp).to.equal('article'); + expect(search.plcmt).to.equal('pave_top'); + expect(search.mts).to.equal('ban'); + expect(search.ban_szs).to.equal('640x480'); + expect(search.bdrs).to.equal('another'); + expect(search.adg_mts).to.not.exist; + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.pbjsv).to.equal('$prebid.version$'); expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); @@ -772,11 +844,24 @@ describe('adagio analytics adapter', () => { } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); expect(search.bdrs_bid).to.equal('0,0,0'); @@ -784,7 +869,18 @@ describe('adagio analytics adapter', () => { } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[5].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.rndr).to.not.exist; + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[6].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); @@ -801,7 +897,7 @@ describe('adagio analytics adapter', () => { } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[7].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); @@ -809,12 +905,20 @@ describe('adagio analytics adapter', () => { expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.auct_id_c).to.equal(RTD_AUCTION_ID_CACHE); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.win_bdr).to.equal('adagio'); + expect(search.win_mt).to.equal('ban'); + expect(search.win_ban_sz).to.equal('728x90'); + expect(search.win_net_cpm).to.equal('1.42'); + expect(search.win_og_cpm).to.equal('1.42'); expect(search.rndr).to.equal('0'); } }); it('send an "empty" cpm when adserver currency != USD and convertCurrency() is undefined', () => { - sandbox.stub(prebidGlobal, 'getGlobal').returns({}); + sandbox.restore(); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + installedModules: ['adagioRtdProvider', 'rtdModule'] + }); events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); @@ -823,14 +927,15 @@ describe('adagio analytics adapter', () => { events.emit(EVENTS.BID_WON, MOCK.BID_WON.another); events.emit(EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); - expect(server.requests.length).to.equal(3, 'requests count'); + expect(server.requests.length).to.equal(5, 'requests count'); // fail to compute bidder cpm and send an "empty" cpm { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); + expect(search.s_id).to.equal(SESSION_ID); expect(search.v).to.equal('2'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); From 57f745387884ca95646053bd9f8781bdf7ffb2ee Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:53:50 -0400 Subject: [PATCH 0337/1097] add global clearAllAuctions method (#11912) --- src/prebid.js | 4 ++++ test/spec/unit/pbjs_api_spec.js | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/prebid.js b/src/prebid.js index 2283f8487e8..e91af3e4d04 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -869,6 +869,10 @@ pbjsInstance.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); }; +pbjsInstance.clearAllAuctions = function () { + auctionManager.clearAllAuctions(); +}; + if (FEATURES.VIDEO) { /** * Mark the winning bid as used, should only be used in conjunction with video diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 9bd71fb5a7a..de267b793b2 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -3757,4 +3757,15 @@ describe('Unit: Prebid Module', function () { sinon.assert.calledOnce(adapterManager.callBidBillableBidder); }); }); + + describe('clearAllAuctions', () => { + after(() => { + resetAuction(); + }); + it('clears auction data', function () { + expect(auctionManager.getBidsReceived().length).to.not.equal(0); + $$PREBID_GLOBAL$$.clearAllAuctions(); + expect(auctionManager.getBidsReceived().length).to.equal(0); + }); + }); }); From ef9388fdaeacc587ab2d4d91545edf8e6245175b Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 15 Jul 2024 21:55:27 -0400 Subject: [PATCH 0338/1097] ConnectIdSystem.js: fix storage bypass (#11964) * Update connectIdSystem.js: fix storage bypass * Update connectIdSystem_spec.js * Update connectIdSystem.js * Update connectIdSystem_spec.js * Update connectIdSystem.js * Fix tests --------- Co-authored-by: Demetrio Girardi --- modules/connectIdSystem.js | 8 ++-- test/spec/modules/connectIdSystem_spec.js | 55 +++++++++++------------ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 22f92941312..6926c69d453 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -318,9 +318,11 @@ export const connectIdSubmodule = { */ userHasOptedOut() { try { - // TODO FIX THIS RULES VIOLATION - // eslint-disable-next-line - return localStorage.getItem(OVERRIDE_OPT_OUT_KEY) === '1'; + if (storage.localStorageIsEnabled()) { + return storage.getDataFromLocalStorage(OVERRIDE_OPT_OUT_KEY) === '1'; + } else { + return true; + } } catch { return false; } diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 686c3d63a63..3009ecef769 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -549,24 +549,28 @@ describe('Yahoo ConnectID Submodule', () => { expect(result.callback).to.be.a('function'); }); + function mockOptout(value) { + getLocalStorageStub.callsFake((key) => { + if (key === 'connectIdOptOut') return value; + }) + } + it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { - localStorage.setItem('connectIdOptOut', '1'); + mockOptout('1'); expect(invokeGetIdAPI({ he: HASHED_EMAIL, pixelId: PIXEL_ID }, consentData)).to.be.undefined; - localStorage.removeItem('connectIdOptOut'); }); it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { - localStorage.setItem('connectIdOptOut', 'true'); + mockOptout('true'); let result = invokeGetIdAPI({ he: HASHED_EMAIL, pixelId: PIXEL_ID }, consentData); expect(result).to.be.an('object').that.has.all.keys('callback'); expect(result.callback).to.be.a('function'); - localStorage.removeItem('connectIdOptOut'); }); it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { @@ -804,6 +808,25 @@ describe('Yahoo ConnectID Submodule', () => { }); }); }); + describe('userHasOptedOut()', () => { + it('should return a function', () => { + expect(connectIdSubmodule.userHasOptedOut).to.be.a('function'); + }); + + it('should return false when local storage key has not been set function', () => { + expect(connectIdSubmodule.userHasOptedOut()).to.be.false; + }); + + it('should return true when local storage key has been set to "1"', () => { + getLocalStorageStub.returns('1'); + expect(connectIdSubmodule.userHasOptedOut()).to.be.true; + }); + + it('should return false when local storage key has not been set to "1"', () => { + getLocalStorageStub.returns('hello'); + expect(connectIdSubmodule.userHasOptedOut()).to.be.false; + }); + }); }); describe('decode()', () => { @@ -884,28 +907,4 @@ describe('Yahoo ConnectID Submodule', () => { })).to.be.true; }); }); - - describe('userHasOptedOut()', () => { - afterEach(() => { - localStorage.removeItem('connectIdOptOut'); - }); - - it('should return a function', () => { - expect(connectIdSubmodule.userHasOptedOut).to.be.a('function'); - }); - - it('should return false when local storage key has not been set function', () => { - expect(connectIdSubmodule.userHasOptedOut()).to.be.false; - }); - - it('should return true when local storage key has been set to "1"', () => { - localStorage.setItem('connectIdOptOut', '1'); - expect(connectIdSubmodule.userHasOptedOut()).to.be.true; - }); - - it('should return false when local storage key has not been set to "1"', () => { - localStorage.setItem('connectIdOptOut', 'hello'); - expect(connectIdSubmodule.userHasOptedOut()).to.be.false; - }); - }); }); From 62f50cb2df7ab8fd2939fcd3bd9745dd255b4b49 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:29:29 +0300 Subject: [PATCH 0339/1097] Smarthub bid adapter: alias vimayx (#11874) * update adapter SmartHub: add aliases * SmartHub: add alias VimayX * refactor due reviews * code have been improved * Update smarthubBidAdapter.js * Update smarthubBidAdapter.js * Smarthub: reuse teqblaze utility code --------- Co-authored-by: Victor Co-authored-by: Patrick McCann Co-authored-by: Demetrio Girardi --- libraries/teqblazeUtils/bidderUtils.js | 26 ++- modules/smarthubBidAdapter.js | 222 ++++--------------- test/spec/modules/smarthubBidAdapter_spec.js | 10 +- 3 files changed, 66 insertions(+), 192 deletions(-) diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index 42db3194308..ce061ac7f3c 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -188,20 +188,24 @@ export const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = return buildRequestsBase({ adUrl, validBidRequests, bidderRequest, placementProcessingFunction }); }; -export const interpretResponse = (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); +export function interpretResponseBuilder({addtlBidValidation = (bid) => true} = {}) { + return function (serverResponse) { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem) && addtlBidValidation(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } } + + return response; } +} - return response; -}; +export const interpretResponse = interpretResponseBuilder(); export const getUserSyncs = (syncUrl) => (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { const type = syncOptions.iframeEnabled ? 'iframe' : 'image'; diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index b5970fbb9ee..baf4c358736 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -1,23 +1,28 @@ -import {deepAccess, isFn, logError, logMessage} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import { + buildPlacementProcessingFunction, + buildRequestsBase, + interpretResponseBuilder, + isBidRequestValid +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'smarthub'; const ALIASES = [ {code: 'markapp', skipPbsAliasing: true}, {code: 'jdpmedia', skipPbsAliasing: true}, {code: 'tredio', skipPbsAliasing: true}, + {code: 'vimayx', skipPbsAliasing: true}, ]; const BASE_URLS = { smarthub: 'https://prebid.smart-hub.io/pbjs', markapp: 'https://markapp-prebid.smart-hub.io/pbjs', jdpmedia: 'https://jdpmedia-prebid.smart-hub.io/pbjs', - tredio: 'https://tredio-prebid.smart-hub.io/pbjs' + tredio: 'https://tredio-prebid.smart-hub.io/pbjs', + vimayx: 'https://vimayx-prebid.smart-hub.io/pbjs', }; -function getUrl(partnerName) { +const _getUrl = (partnerName) => { const aliases = ALIASES.map(el => el.code); if (aliases.includes(partnerName)) { return BASE_URLS[partnerName]; @@ -26,187 +31,50 @@ function getUrl(partnerName) { return `${BASE_URLS[BIDDER_CODE]}?partnerName=${partnerName}`; } -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency || !bid.hasOwnProperty('netRevenue')) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.width && bid.height && (bid.vastUrl || bid.vastXml)); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes, bidder } = bid; - const schain = bid.schain || {}; - const { partnerName, seat, token, iabCat, minBidfloor, pos } = params; - const bidfloor = getBidFloor(bid); - - const plcmt = { - partnerName: String(partnerName || bidder).toLowerCase(), - seat, - token, - iabCat, - minBidfloor, - pos, - bidId, - schain, - bidfloor, - }; - - if (mediaTypes && mediaTypes[BANNER]) { - plcmt.adFormat = BANNER; - plcmt.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - plcmt.adFormat = VIDEO; - plcmt.playerSize = mediaTypes[VIDEO].playerSize; - plcmt.minduration = mediaTypes[VIDEO].minduration; - plcmt.maxduration = mediaTypes[VIDEO].maxduration; - plcmt.mimes = mediaTypes[VIDEO].mimes; - plcmt.protocols = mediaTypes[VIDEO].protocols; - plcmt.startdelay = mediaTypes[VIDEO].startdelay; - plcmt.placement = mediaTypes[VIDEO].plcmt; - plcmt.plcmt = mediaTypes[VIDEO].plcmt; // https://github.com/prebid/Prebid.js/issues/10452 - plcmt.skip = mediaTypes[VIDEO].skip; - plcmt.skipafter = mediaTypes[VIDEO].skipafter; - plcmt.minbitrate = mediaTypes[VIDEO].minbitrate; - plcmt.maxbitrate = mediaTypes[VIDEO].maxbitrate; - plcmt.delivery = mediaTypes[VIDEO].delivery; - plcmt.playbackmethod = mediaTypes[VIDEO].playbackmethod; - plcmt.api = mediaTypes[VIDEO].api; - plcmt.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - plcmt.native = mediaTypes[NATIVE]; - plcmt.adFormat = NATIVE; - } - - return plcmt; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', +const getPartnerName = (bid) => String(bid.params?.partnerName || bid.bidder).toLowerCase(); + +const getPlacementReqData = buildPlacementProcessingFunction({ + addPlacementType() {}, + addCustomFieldsToPlacement(bid, bidderRequest, placement) { + const { seat, token, iabCat, minBidfloor, pos } = bid.params; + Object.assign(placement, { + partnerName: getPartnerName(bid), + seat, + token, + iabCat, + minBidfloor, + pos, }); - return bidFloor.floor; - } catch (e) { - logError(e); - return 0; - } -} - -function buildRequestParams(bidderRequest = {}, placements = []) { - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - return { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; +}) + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const bidsByPartner = validBidRequests.reduce((bidsByPartner, bid) => { + const partner = getPartnerName(bid); + (bidsByPartner[partner] = bidsByPartner[partner] || []).push(bid); + return bidsByPartner; + }, {}); + return Object.entries(bidsByPartner).map(([partner, validBidRequests]) => { + return buildRequestsBase({ + adUrl: _getUrl(partner), + bidderRequest, + validBidRequests, + placementProcessingFunction: getPlacementReqData + }) + }) } export const spec = { code: BIDDER_CODE, aliases: ALIASES, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.seat && params.token); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; + isBidRequestValid: isBidRequestValid(['seat', 'token'], 'every'), + buildRequests, + interpretResponse: interpretResponseBuilder({ + addtlBidValidation(bid) { + return bid.hasOwnProperty('netRevenue') } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const tempObj = {}; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const data = getPlacementReqData(bid); - tempObj[data.partnerName] = tempObj[data.partnerName] || []; - tempObj[data.partnerName].push(data); - } - - return Object.keys(tempObj).map(key => { - const request = buildRequestParams(bidderRequest, tempObj[key]); - return { - method: 'POST', - url: getUrl(key), - data: request, - } - }); - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - } + }) }; registerBidder(spec); diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index a5e8787e21a..eb5fe58093d 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -106,7 +106,9 @@ describe('SmartHubBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + }, refererInfo: { page: 'https://test.com' }, @@ -166,7 +168,7 @@ describe('SmartHubBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr.consentString).to.be.a('string'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -212,8 +214,8 @@ describe('SmartHubBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest[0].data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr.consentString).to.be.a('string'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); From db59494441c4b0b258ea795923c51b110cacab8e Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 16 Jul 2024 08:27:30 -0400 Subject: [PATCH 0340/1097] uid2IdSystem_shared.js : better logging messages (#11969) * uid2IdSystem_shared.js: better logging messages * Update uid2IdSystem_shared.js --- modules/uid2IdSystem_shared.js | 90 +++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 36e5d414941..a9acfd6291e 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -7,12 +7,22 @@ function isValidIdentity(identity) { return !!(typeof identity === 'object' && identity !== null && identity.advertising_token && identity.identity_expires && identity.refresh_from && identity.refresh_token && identity.refresh_expires); } +// Helper function to prepend message +function prependMessage(message) { + return `UID2 shared library - ${message}`; +} + +// Wrapper function for logInfo +function logInfoWrapper(logInfo, ...args) { + logInfo(prependMessage(args[0]), ...args.slice(1)); +} + // This is extracted from an in-progress API client. Once it's available via NPM, this class should be replaced with the NPM package. export class Uid2ApiClient { constructor(opts, clientId, logInfo, logWarn) { this._baseUrl = opts.baseUrl; this._clientVersion = clientId; - this._logInfo = logInfo; + this._logInfo = (...args) => logInfoWrapper(logInfo, ...args); this._logWarn = logWarn; } @@ -35,7 +45,7 @@ export class Uid2ApiClient { if (this.isValidRefreshResponse(response)) { if (response.status === 'success') { return { status: response.status, identity: response.body }; } return response; - } else { return `Response didn't contain a valid status`; } + } else { return prependMessage(`Response didn't contain a valid status`); } } callRefreshApi(refreshDetails) { const url = this._baseUrl + '/v2/token/refresh'; @@ -53,7 +63,7 @@ export class Uid2ApiClient { this._logInfo('No response decryption key available, assuming unencrypted JSON'); const response = JSON.parse(responseText); const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + if (typeof result === 'string') { rejectPromise(prependMessage(result)); } else { resolvePromise(result); } } else { this._logInfo('Decrypting refresh API response'); const encodeResp = this.createArrayBuffer(atob(responseText)); @@ -69,12 +79,12 @@ export class Uid2ApiClient { this._logInfo('Decrypted to:', decryptedResponse); const response = JSON.parse(decryptedResponse); const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } - }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); - }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); + if (typeof result === 'string') { rejectPromise(prependMessage(result)); } else { resolvePromise(result); } + }, (reason) => this._logWarn(prependMessage(`Call to UID2 API failed`), reason)); + }, (reason) => this._logWarn(prependMessage(`Call to UID2 API failed`), reason)); } } catch (_err) { - rejectPromise(responseText); + rejectPromise(prependMessage(responseText)); } }, error: (error, xhr) => { @@ -82,9 +92,9 @@ export class Uid2ApiClient { this._logInfo('Error status, assuming unencrypted JSON'); const response = JSON.parse(xhr.responseText); const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + if (typeof result === 'string') { rejectPromise(prependMessage(result)); } else { resolvePromise(result); } } catch (_e) { - rejectPromise(error) + rejectPromise(prependMessage(error)); } } }, refreshDetails.refresh_token, { method: 'POST', @@ -99,7 +109,7 @@ export class Uid2StorageManager { this._storage = storage; this._preferLocalStorage = preferLocalStorage; this._storageName = storageName; - this._logInfo = logInfo; + this._logInfo = (...args) => logInfoWrapper(logInfo, ...args); } readCookie(cookieName) { return this._storage.cookiesAreEnabled() ? this._storage.getCookie(cookieName) : null; @@ -392,7 +402,7 @@ if (FEATURES.UID2_CSTG) { this._baseUrl = opts.baseUrl; this._serverPublicKey = opts.cstg.serverPublicKey; this._subscriptionId = opts.cstg.subscriptionId; - this._logInfo = logInfo; + this._logInfo = (...args) => logInfoWrapper(logInfo, ...args); this._logWarn = logWarn; } @@ -512,11 +522,11 @@ if (FEATURES.UID2_CSTG) { // A 200 should always be a success response. // Something has gone wrong. rejectPromise( - `API error: Response body was invalid for HTTP status 200: ${decryptedResponse}` + prependMessage(`API error: Response body was invalid for HTTP status 200: ${decryptedResponse}`) ); } } catch (err) { - rejectPromise(err); + rejectPromise(prependMessage(err)); } }, error: (error, xhr) => { @@ -524,32 +534,32 @@ if (FEATURES.UID2_CSTG) { if (xhr.status === 400) { const response = JSON.parse(xhr.responseText); if (this.isCstgApiClientErrorResponse(response)) { - rejectPromise(`Client error: ${response.message}`); + rejectPromise(prependMessage(`Client error: ${response.message}`)); } else { // A 400 should always be a client error. // Something has gone wrong. rejectPromise( - `API error: Response body was invalid for HTTP status 400: ${xhr.responseText}` + prependMessage(`UID2 API error: Response body was invalid for HTTP status 400: ${xhr.responseText}`) ); } } else if (xhr.status === 403) { const response = JSON.parse(xhr.responseText); if (this.isCstgApiForbiddenResponse(xhr)) { - rejectPromise(`Forbidden: ${response.message}`); + rejectPromise(prependMessage(`Forbidden: ${response.message}`)); } else { // A 403 should always be a forbidden response. // Something has gone wrong. rejectPromise( - `API error: Response body was invalid for HTTP status 403: ${xhr.responseText}` + prependMessage(`UID2 API error: Response body was invalid for HTTP status 403: ${xhr.responseText}`) ); } } else { rejectPromise( - `API error: Unexpected HTTP status ${xhr.status}: ${error}` + prependMessage(`UID2 API error: Unexpected HTTP status ${xhr.status}: ${error}`) ); } } catch (_e) { - rejectPromise(error); + rejectPromise(prependMessage(error)); } }, }, @@ -678,43 +688,45 @@ if (FEATURES.UID2_CSTG) { } export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { + const logInfo = (...args) => logInfoWrapper(_logInfo, ...args); + let suppliedToken = null; const preferLocalStorage = (config.storage !== 'cookie'); - const storageManager = new Uid2StorageManager(prebidStorageManager, preferLocalStorage, config.internalStorage, _logInfo); - _logInfo(`Module is using ${preferLocalStorage ? 'local storage' : 'cookies'} for internal storage.`); + const storageManager = new Uid2StorageManager(prebidStorageManager, preferLocalStorage, config.internalStorage, logInfo); + logInfo(`Module is using ${preferLocalStorage ? 'local storage' : 'cookies'} for internal storage.`); const isCstgEnabled = clientSideTokenGenerator && clientSideTokenGenerator.isCSTGOptionsValid(config.cstg, _logWarn); if (isCstgEnabled) { - _logInfo(`Module is using client-side token generation.`); + logInfo(`Module is using client-side token generation.`); // Ignores config.paramToken and config.serverCookieName if any is provided suppliedToken = null; } else if (config.paramToken) { suppliedToken = config.paramToken; - _logInfo('Read token from params', suppliedToken); + logInfo('Read token from params', suppliedToken); } else if (config.serverCookieName) { suppliedToken = storageManager.readProvidedCookie(config.serverCookieName); - _logInfo('Read token from server-supplied cookie', suppliedToken); + logInfo('Read token from server-supplied cookie', suppliedToken); } let storedTokens = storageManager.getStoredValueWithFallback(); - _logInfo('Loaded module-stored tokens:', storedTokens); + logInfo('Loaded module-stored tokens:', storedTokens); if (storedTokens && typeof storedTokens === 'string') { // Stored value is a plain token - if no token is supplied, just use the stored value. if (!suppliedToken && !isCstgEnabled) { - _logInfo('Returning legacy cookie value.'); + logInfo('Returning legacy cookie value.'); return { id: storedTokens }; } // Otherwise, ignore the legacy value - it should get over-written later anyway. - _logInfo('Discarding superseded legacy cookie.'); + logInfo('Discarding superseded legacy cookie.'); storedTokens = null; } if (suppliedToken && storedTokens) { if (storedTokens.originalToken?.advertising_token !== suppliedToken.advertising_token) { - _logInfo('Server supplied new token - ignoring stored value.', storedTokens.originalToken?.advertising_token, suppliedToken.advertising_token); + logInfo('Server supplied new token - ignoring stored value.', storedTokens.originalToken?.advertising_token, suppliedToken.advertising_token); // Stored token wasn't originally sourced from the provided token - ignore the stored value. A new user has logged in? storedTokens = null; } @@ -723,16 +735,16 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { if (FEATURES.UID2_CSTG && isCstgEnabled) { const cstgIdentity = clientSideTokenGenerator.getValidIdentity(config.cstg, _logWarn); if (cstgIdentity) { - if (storedTokens && clientSideTokenGenerator.isStoredTokenInvalid(cstgIdentity, storedTokens, _logInfo, _logWarn)) { + if (storedTokens && clientSideTokenGenerator.isStoredTokenInvalid(cstgIdentity, storedTokens, logInfo, _logWarn)) { storedTokens = null; } if (!storedTokens || Date.now() > storedTokens.latestToken.refresh_expires) { - const promise = clientSideTokenGenerator.generateTokenAndStore(config.apiBaseUrl, config.cstg, cstgIdentity, storageManager, _logInfo, _logWarn); - _logInfo('Generate token using CSTG'); + const promise = clientSideTokenGenerator.generateTokenAndStore(config.apiBaseUrl, config.cstg, cstgIdentity, storageManager, logInfo, _logWarn); + logInfo('Generate token using CSTG'); return { callback: (cb) => { promise.then((result) => { - _logInfo('Token generation responded, passing the new token on.', result); + logInfo('Token generation responded, passing the new token on.', result); cb(result); }); } }; @@ -742,25 +754,25 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { const useSuppliedToken = !(storedTokens?.latestToken) || (suppliedToken && suppliedToken.identity_expires > storedTokens.latestToken.identity_expires); const newestAvailableToken = useSuppliedToken ? suppliedToken : storedTokens.latestToken; - _logInfo('UID2 module selected latest token', useSuppliedToken, newestAvailableToken); + logInfo('UID2 module selected latest token', useSuppliedToken, newestAvailableToken); if ((!newestAvailableToken || Date.now() > newestAvailableToken.refresh_expires)) { - _logInfo('Newest available token is expired and not refreshable.'); + logInfo('Newest available token is expired and not refreshable.'); return { id: null }; } if (Date.now() > newestAvailableToken.identity_expires) { - const promise = refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, _logInfo, _logWarn); - _logInfo('Token is expired but can be refreshed, attempting refresh.'); + const promise = refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, logInfo, _logWarn); + logInfo('Token is expired but can be refreshed, attempting refresh.'); return { callback: (cb) => { promise.then((result) => { - _logInfo('Refresh reponded, passing the updated token on.', result); + logInfo('Refresh reponded, passing the updated token on.', result); cb(result); }); } }; } // If should refresh (but don't need to), refresh in the background. if (Date.now() > newestAvailableToken.refresh_from) { - _logInfo(`Refreshing token in background with low priority.`); - refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, _logInfo, _logWarn); + logInfo(`Refreshing token in background with low priority.`); + refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, logInfo, _logWarn); } const tokens = { originalToken: suppliedToken ?? storedTokens?.originalToken, From bb5dcefb08344708297b8ddfcdfa0945ef66a3b2 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 16 Jul 2024 08:30:15 -0400 Subject: [PATCH 0341/1097] Rise utils: initial commit (#11951) * Create index.js * Update stnBidAdapter.js * Update index.js * Update stnBidAdapter.js * Update telariaBidAdapter.js * Update telariaBidAdapter.js * Update shinezBidAdapter.js * Update riseBidAdapter.js * Update openwebBidAdapter.js * Update publirBidAdapter.js * Update shinezBidAdapter.js * Update riseBidAdapter.js * Update openwebBidAdapter.js * Update publirBidAdapter.js * Update index.js * Update openwebBidAdapter.js * Update shinezBidAdapter.js * Update openwebBidAdapter.js * Update shinezBidAdapter.js * Update index.js * Update shinezBidAdapter.js * Update riseBidAdapter.js * Update openwebBidAdapter.js * Update index.js * Update stnBidAdapter.js * Update index.js * Update index.js * Update minutemediaBidAdapter.js * Update publirBidAdapter.js * Update index.js * Update minutemediaBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update publirBidAdapter.js * Update index.js * Update index.js * Update publirBidAdapter.js * Update publirBidAdapter.js --- libraries/riseUtils/index.js | 330 ++++++++++++++++++++++++ modules/minutemediaBidAdapter.js | 419 ++---------------------------- modules/openwebBidAdapter.js | 414 ++---------------------------- modules/publirBidAdapter.js | 293 ++------------------- modules/riseBidAdapter.js | 419 ++---------------------------- modules/shinezBidAdapter.js | 378 ++-------------------------- modules/stnBidAdapter.js | 420 ++----------------------------- modules/telariaBidAdapter.js | 34 +-- 8 files changed, 449 insertions(+), 2258 deletions(-) create mode 100644 libraries/riseUtils/index.js diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js new file mode 100644 index 00000000000..07dec275ecc --- /dev/null +++ b/libraries/riseUtils/index.js @@ -0,0 +1,330 @@ +import { + isArray, + isFn, + deepAccess, + isEmpty, + contains, + isInteger, + getBidIdParameter +} from '../../src/utils.js'; +import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import {config} from '../../src/config.js'; + +export function getFloor(bid, mediaType) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + return floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0; +} + +export function getSizesArray(bid, mediaType) { + let sizesArray = []; + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } + + return sizesArray; +} + +export function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +export function getEncodedValIfNotEmpty(val) { + return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; +} + +export function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return 'iframe'; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return 'pixel'; + } +} + +export function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +export function getEndpoint(testMode, baseUrl, modes) { + const protocol = baseUrl.startsWith('http') ? '' : 'https://'; + const url = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; + return testMode + ? `${protocol}${url}${modes.TEST}` + : `${protocol}${url}${modes.PRODUCTION}`; +} + +export function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\\sce|palm|smartphone|iemobile/i.test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\\bdtv\\b|sonydtv|inettvbrowser|\\btv\\b/i.test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + +export function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + +export function generateBidParameters(bid, bidderRequest) { + const { params } = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: bid.ortb2Imp?.ext?.tid || '', + coppa: 0, + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + + const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); + if (api) { + bidObject.api = api; + } + + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`); + if (coppa) { + bidObject.coppa = 1; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + + const protocols = deepAccess(bid, `mediaTypes.video.protocols`); + if (protocols) { + bidObject.protocols = protocols; + } + + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; + } + } + + return bidObject; +} + +export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || DEFAULT_CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.creativeId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + return bidResponse; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +export function generateGeneralParams(generalObject, bidderRequest, adapterVersion) { + const domain = window.location.hostname; + const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; + const { bidderCode } = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = bidderRequest.timeout; + const adapVer = adapterVersion || '6.0.0'; + + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: adapVer, + auction_start: bidderRequest.auctionStart, + publisher_id: generalBidParams.org, + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), + tmax: timeout + }; + + const userIdsParam = getBidIdParameter('userId', generalObject); + if (userIdsParam) { + generalParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = bidderRequest.ortb2 || {}; + if (ortb2Metadata.site) { + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + generalParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + } + + return generalParams; +} diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index a724a0446a4..4e83c5c6db4 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -2,33 +2,28 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/'; +const BASE_URL = 'https://hb.minutemedia-prebid.com/'; const MODES = { PRODUCTION: 'hb-mm-multi', TEST: 'hb-multi-mm-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -51,50 +46,24 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const combinedRequestsObject = {}; - // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, BASE_URL, MODES), data: combinedRequestsObject - } + }; }, - interpretResponse: function ({body}) { + interpretResponse: function ({ body }) { const bidResponses = []; if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.creativeId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -104,20 +73,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel - } - }) - syncs.push(...pixels) + }; + }); + syncs.push(...pixels); } } return syncs; @@ -135,349 +104,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - return scStr; -} - -/** - * Get encoded node value - * @param val {string} - * @returns {string} - */ -function getEncodedValIfNotEmpty(val) { - return !isEmpty(val) ? encodeURIComponent(val) : ''; -} - -/** - * Get preferred user-sync method based on publisher configuration - * @param bidderCode {string} - * @returns {string} - */ -function getAllowedSyncMethod(filterSettings, bidderCode) { - const iframeConfigsToCheck = ['all', 'iframe']; - const pixelConfigToCheck = 'image'; - if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { - return SUPPORTED_SYNC_METHODS.IFRAME; - } - if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { - return SUPPORTED_SYNC_METHODS.PIXEL; - } -} - -/** - * Check if sync rule is supported - * @param syncRule {Object} - * @param bidderCode {string} - * @returns {boolean} - */ -function isSyncMethodAllowed(syncRule, bidderCode) { - if (!syncRule) { - return false; - } - const isInclude = syncRule.filter === 'include'; - const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * Get the seller endpoint - * @param testMode {boolean} - * @returns {string} - */ -function getEndpoint(testMode) { - return testMode - ? SELLER_ENDPOINT + MODES.TEST - : SELLER_ENDPOINT + MODES.PRODUCTION; -} - -/** - * get device type - * @param uad {ua} - * @returns {string} - */ -function getDeviceType(ua) { - if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i - .test(ua.toLowerCase())) { - return '5'; - } - if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i - .test(ua.toLowerCase())) { - return '4'; - } - if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i - .test(ua.toLowerCase())) { - return '3'; - } - return '1'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - } - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams -} diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index f083647c480..60364f41d3c 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -2,33 +2,28 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - triggerPixel, - isInteger, - getBidIdParameter, - getDNT + triggerPixel } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.openwebmp.com/'; +const BASE_URL = 'https://hb.openwebmp.com/'; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -65,7 +60,7 @@ export const spec = { return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, BASE_URL, MODES), data: combinedRequestsObject } }, @@ -74,32 +69,7 @@ export const spec = { if (body && body.bids && body.bids.length) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.creativeId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -109,20 +79,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel } - }) - syncs.push(...pixels) + }); + syncs.push(...pixels); } } return syncs; @@ -140,351 +110,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - return scStr; -} - -/** - * Get encoded node value - * @param val {string} - * @returns {string} - */ -function getEncodedValIfNotEmpty(val) { - return !isEmpty(val) ? encodeURIComponent(val) : ''; -} - -/** - * Get preferred user-sync method based on publisher configuration - * @param bidderCode {string} - * @returns {string} - */ -function getAllowedSyncMethod(filterSettings, bidderCode) { - const iframeConfigsToCheck = ['all', 'iframe']; - const pixelConfigToCheck = 'image'; - if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { - return SUPPORTED_SYNC_METHODS.IFRAME; - } - if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { - return SUPPORTED_SYNC_METHODS.PIXEL; - } -} - -/** - * Check if sync rule is supported - * @param syncRule {Object} - * @param bidderCode {string} - * @returns {boolean} - */ -function isSyncMethodAllowed(syncRule, bidderCode) { - if (!syncRule) { - return false; - } - const isInclude = syncRule.filter === 'include'; - const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * Get the seller endpoint - * @param testMode {boolean} - * @returns {string} - */ -function getEndpoint(testMode) { - return testMode - ? SELLER_ENDPOINT + MODES.TEST - : SELLER_ENDPOINT + MODES.PRODUCTION; -} - -/** - * get device type - * @param uad {ua} - * @returns {string} - */ -function getDeviceType(ua) { - if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i - .test(ua.toLowerCase())) { - return '5'; - } - if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i - .test(ua.toLowerCase())) { - return '4'; - } - if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i - .test(ua.toLowerCase())) { - return '3'; - } - return '1'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`); - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = deepAccess(bidderRequest, 'refererInfo.domain') || window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode, timeout} = bidderRequest; - const generalBidParams = generalObject.params; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: getDNT() ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - } - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.auctionStart) { - generalParams.auction_start = bidderRequest.auctionStart; - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/publirBidAdapter.js b/modules/publirBidAdapter.js index 432e123458c..2cf55aa86cb 100644 --- a/modules/publirBidAdapter.js +++ b/modules/publirBidAdapter.js @@ -2,32 +2,26 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, timestamp, triggerPixel, - getDNT, - getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; +import { + generateBidsParams, + generateGeneralParams, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'publir'; const ADAPTER_VERSION = '1.0.0'; const TTL = 360; const CURRENCY = 'USD'; -const DEFAULT_SELLER_ENDPOINT = 'https://prebid.publir.com/publirPrebidEndPoint'; +const BASE_URL = 'https://prebid.publir.com/publirPrebidEndPoint'; const DEFAULT_IMPS_ENDPOINT = 'https://prebidimpst.publir.com/publirPrebidImpressionTracker'; -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { @@ -40,25 +34,26 @@ export const spec = { logWarn('pubId is a mandatory param for Publir adapter'); return false; } - return true; }, buildRequests: function (validBidRequests, bidderRequest) { - const reqObj = {}; + const combinedRequestsObject = {}; + const generalObject = validBidRequests[0]; - reqObj.params = generatePubGeneralsParams(generalObject, bidderRequest); - reqObj.bids = generatePubBidParams(validBidRequests, bidderRequest); - reqObj.bids.timestamp = timestamp(); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + combinedRequestsObject.bids.timestamp = timestamp(); + let options = { withCredentials: false }; return { method: 'POST', - url: DEFAULT_SELLER_ENDPOINT, - data: reqObj, + url: BASE_URL, + data: combinedRequestsObject, options - } + }; }, interpretResponse: function ({ body }) { const bidResponses = []; @@ -109,20 +104,20 @@ export const spec = { const syncs = []; for (const response of serverResponses) { if (response.body && response.body.params) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel - } - }) - syncs.push(...pixels) + }; + }); + syncs.push(...pixels); } } } @@ -141,251 +136,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - return scStr; -} - -/** - * Get encoded node value - * @param val {string} - * @returns {string} - */ -function getEncodedValIfNotEmpty(val) { - return !isEmpty(val) ? encodeURIComponent(val) : ''; -} - -function getAllowedSyncMethod(filterSettings, bidderCode) { - const iframeConfigsToCheck = ['all', 'iframe']; - const pixelConfigToCheck = 'image'; - if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { - return SUPPORTED_SYNC_METHODS.IFRAME; - } - if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { - return SUPPORTED_SYNC_METHODS.PIXEL; - } -} - -/** - * Check if sync rule is supported - * @param syncRule {Object} - * @param bidderCode {string} - * @returns {boolean} - */ -function isSyncMethodAllowed(syncRule, bidderCode) { - if (!syncRule) { - return false; - } - const isInclude = syncRule.filter === 'include'; - const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * get device type - * @param ua {ua} - * @returns {string} - */ -function getDeviceType(ua) { - if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(ua.toLowerCase())) { - return '5'; - } - if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(ua.toLowerCase())) { - return '4'; - } - if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i.test(ua.toLowerCase())) { - return '3'; - } - return '1'; -} - -function generatePubBidParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const { params } = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - transactionId: bid.ortb2Imp?.ext?.tid, - coppa: 0 - }; - - const pubId = params.pubId; - if (pubId) { - bidObject.pubId = pubId; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -function getLocalStorage(cookieObjName) { - if (storage.localStorageIsEnabled()) { - const lstData = storage.getDataFromLocalStorage(cookieObjName); - return lstData; - } - return ''; -} - -/** - * Generate params that are common between all bids - * @param generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generatePubGeneralsParams(generalObject, bidderRequest) { - const domain = bidderRequest.refererInfo; - const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; - const { bidderCode } = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: bidderRequest.auctionStart, - publisher_id: generalBidParams.pubId, - publisher_name: domain, - site_domain: domain, - dnt: getDNT() ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout, - user_cookie: getLocalStorage('_publir_prebid_creative') - }; - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 72170706fc0..236c048982a 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -2,18 +2,17 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'rise'; @@ -21,15 +20,11 @@ const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_GVLID = 1043; -const DEFAULT_SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const BASE_URL = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -59,48 +54,23 @@ export const spec = { // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; - const rtbDomain = generalObject.params.rtbDomain; + const rtbDomain = generalObject.params.rtbDomain || BASE_URL; combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); return { method: 'POST', - url: getEndpoint(testMode, rtbDomain), + url: getEndpoint(testMode, rtbDomain, MODES), data: combinedRequestsObject } }, - interpretResponse: function ({body}) { + interpretResponse: function ({ body }) { const bidResponses = []; if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.creativeId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -110,20 +80,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel } - }) - syncs.push(...pixels) + }); + syncs.push(...pixels); } } return syncs; @@ -141,354 +111,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @param mediaType {string} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - return scStr; -} - -/** - * Get encoded node value - * @param val {string} - * @returns {string} - */ -function getEncodedValIfNotEmpty(val) { - return !isEmpty(val) ? encodeURIComponent(val) : ''; -} - -/** - * Get preferred user-sync method based on publisher configuration - * @param bidderCode {string} - * @returns {string} - */ -function getAllowedSyncMethod(filterSettings, bidderCode) { - const iframeConfigsToCheck = ['all', 'iframe']; - const pixelConfigToCheck = 'image'; - if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { - return SUPPORTED_SYNC_METHODS.IFRAME; - } - if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { - return SUPPORTED_SYNC_METHODS.PIXEL; - } -} - -/** - * Check if sync rule is supported - * @param syncRule {Object} - * @param bidderCode {string} - * @returns {boolean} - */ -function isSyncMethodAllowed(syncRule, bidderCode) { - if (!syncRule) { - return false; - } - const isInclude = syncRule.filter === 'include'; - const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * Get the seller endpoint - * @param testMode {boolean} - * @param rtbDomain {string} - * @returns {string} - */ -function getEndpoint(testMode, rtbDomain) { - const SELLER_ENDPOINT = rtbDomain ? `https://${rtbDomain}/` : DEFAULT_SELLER_ENDPOINT; - return testMode - ? SELLER_ENDPOINT + MODES.TEST - : SELLER_ENDPOINT + MODES.PRODUCTION; -} - -/** - * get device type - * @param uad {ua} - * @returns {string} - */ -function getDeviceType(ua) { - if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i - .test(ua.toLowerCase())) { - return '5'; - } - if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i - .test(ua.toLowerCase())) { - return '4'; - } - if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i - .test(ua.toLowerCase())) { - return '3'; - } - return '1'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - transactionId: bid.ortb2Imp?.ext?.tid, - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - }; - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest && bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - // TODO: is 'ref' the right value here? - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - // TODO: does the fallback make sense here? - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 993c069ded0..89f39284bed 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -2,33 +2,28 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'shinez'; const ADAPTER_VERSION = '1.0.0'; const TTL = 360; const CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.sweetgum.io/'; +const BASE_URL = 'https://hb.sweetgum.io/'; const MODES = { PRODUCTION: 'hb-sz-multi', TEST: 'hb-multi-sz-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -59,7 +54,7 @@ export const spec = { return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, BASE_URL, MODES), data: combinedRequestsObject } }, @@ -68,32 +63,7 @@ export const spec = { if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -103,20 +73,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel } - }) - syncs.push(...pixels) + }); + syncs.push(...pixels); } } return syncs; @@ -134,317 +104,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - return scStr; -} - -/** - * Get encoded node value - * @param val {string} - * @returns {string} - */ -function getEncodedValIfNotEmpty(val) { - return !isEmpty(val) ? encodeURIComponent(val) : ''; -} - -/** - * Get preferred user-sync method based on publisher configuration - * @param bidderCode {string} - * @returns {string} - */ -function getAllowedSyncMethod(filterSettings, bidderCode) { - const iframeConfigsToCheck = ['all', 'iframe']; - const pixelConfigToCheck = 'image'; - if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { - return SUPPORTED_SYNC_METHODS.IFRAME; - } - if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { - return SUPPORTED_SYNC_METHODS.PIXEL; - } -} - -/** - * Check if sync rule is supported - * @param syncRule {Object} - * @param bidderCode {string} - * @returns {boolean} - */ -function isSyncMethodAllowed(syncRule, bidderCode) { - if (!syncRule) { - return false; - } - const isInclude = syncRule.filter === 'include'; - const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * Get the seller endpoint - * @param testMode {boolean} - * @returns {string} - */ -function getEndpoint(testMode) { - return testMode - ? SELLER_ENDPOINT + MODES.TEST - : SELLER_ENDPOINT + MODES.PRODUCTION; -} - -/** - * get device type - * @param uad {ua} - * @returns {string} - */ -function getDeviceType(ua) { - if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i - .test(ua.toLowerCase())) { - return '5'; - } - if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i - .test(ua.toLowerCase())) { - return '4'; - } - if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i - .test(ua.toLowerCase())) { - return '3'; - } - return '1'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - session_id: getBidIdParameter('auctionId', generalObject), - tmax: timeout - }; - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest.ortb2 && bidderRequest.ortb2.site) { - generalParams.referrer = bidderRequest.ortb2.site.ref; - generalParams.page_url = bidderRequest.ortb2.site.page; - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = generalParams.referrer || deepAccess(bidderRequest, 'refererInfo.referer'); - generalParams.page_url = generalParams.page_url || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index af4f4c45d38..ba922c0fd57 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -2,33 +2,32 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; - -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const BIDDER_CODE = 'stn'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.stngo.com/'; -const MODES = { +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; + +export const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +export const BIDDER_CODE = 'stn'; +export const ADAPTER_VERSION = '6.1.0'; +export const TTL = 360; +export const DEFAULT_CURRENCY = 'USD'; +export const SELLER_ENDPOINT = 'https://hb.stngo.com/'; +export const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' -} -const SUPPORTED_SYNC_METHODS = { +}; +export const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -50,7 +49,6 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const combinedRequestsObject = {}; - // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; @@ -59,41 +57,16 @@ export const spec = { return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, SELLER_ENDPOINT, MODES), data: combinedRequestsObject } }, - interpretResponse: function ({body}) { + interpretResponse: function ({ body }) { const bidResponses = []; if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.creativeId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -134,352 +107,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @param mediaType {String} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the ad sizes array from the bid - * @param bid {bid} - * @param mediaType {String} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${getEncodedValIfNotEmpty(node.hp)},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - return scStr; -} - -/** - * Get encoded node value - * @param val {string} - * @returns {string} - */ -function getEncodedValIfNotEmpty(val) { - return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; -} - -/** - * Get preferred user-sync method based on publisher configuration - * @param filterSettings {Object} - * @param bidderCode {string} - * @returns {string} - */ -function getAllowedSyncMethod(filterSettings, bidderCode) { - const iframeConfigsToCheck = ['all', 'iframe']; - const pixelConfigToCheck = 'image'; - if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { - return SUPPORTED_SYNC_METHODS.IFRAME; - } - if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { - return SUPPORTED_SYNC_METHODS.PIXEL; - } -} - -/** - * Check if sync rule is supported - * @param syncRule {Object} - * @param bidderCode {string} - * @returns {boolean} - */ -function isSyncMethodAllowed(syncRule, bidderCode) { - if (!syncRule) { - return false; - } - const isInclude = syncRule.filter === 'include'; - const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * Get the seller endpoint - * @param testMode {boolean} - * @returns {string} - */ -function getEndpoint(testMode) { - return testMode - ? SELLER_ENDPOINT + MODES.TEST - : SELLER_ENDPOINT + MODES.PRODUCTION; -} - -/** - * get device type - * @param ua {ua} - * @returns {string} - */ -function getDeviceType(ua) { - if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i - .test(ua.toLowerCase())) { - return '5'; - } - if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i - .test(ua.toLowerCase())) { - return '4'; - } - if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i - .test(ua.toLowerCase())) { - return '3'; - } - return '1'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - } - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams -} diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 38eefd447a8..4ad544aaa50 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -1,6 +1,7 @@ import { logError, isEmpty, deepAccess, triggerPixel, logWarn, isArray } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; +import {getSupplyChain} from '../libraries/riseUtils/index.js'; const BIDDER_CODE = 'telaria'; const DOMAIN = 'tremorhub.com'; @@ -127,37 +128,6 @@ function getDefaultSrcPageUrl() { return encodeURIComponent(document.location.href); } -function getEncodedValIfNotEmpty(val) { - return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; -} - -/** - * Converts the schain object to a url param value. Please refer to - * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md - * (schain for non ORTB section) for more information - * @param schainObject - * @returns {string} - */ -function getSupplyChainAsUrlParam(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - - let scStr = `&schain=${schainObject.ver},${schainObject.complete}`; - - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${getEncodedValIfNotEmpty(node.hp)},`; - scStr += `${getEncodedValIfNotEmpty(node.rid)},`; - scStr += `${getEncodedValIfNotEmpty(node.name)},`; - scStr += `${getEncodedValIfNotEmpty(node.domain)}`; - }); - - return scStr; -} - function getUrlParams(params, schainFromBidRequest) { let urlSuffix = ''; @@ -167,7 +137,7 @@ function getUrlParams(params, schainFromBidRequest) { urlSuffix += `&${key}=${params[key]}`; } } - urlSuffix += getSupplyChainAsUrlParam(!isEmpty(schainFromBidRequest) ? schainFromBidRequest : params['schain']); + urlSuffix += getSupplyChain(!isEmpty(schainFromBidRequest) ? schainFromBidRequest : params['schain']); } return urlSuffix; From 53119225b6f2db0bd3ae70b5b5342ce159b7f5df Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 16 Jul 2024 11:06:10 -0400 Subject: [PATCH 0342/1097] Update adkernelBidAdapter.js (#11983) --- modules/adkernelBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 20cd9233391..374507c8d45 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -97,7 +97,6 @@ export const spec = { {code: 'digiad'}, {code: 'monetix'}, {code: 'hyperbrainz'}, - {code: 'globalsun'}, {code: 'voisetech'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 71c108761216811cdc9b795f255aa59a4140fc1e Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Tue, 16 Jul 2024 19:46:10 +0200 Subject: [PATCH 0343/1097] Nexx360 Bid Adapter: 1accord alias added (#11984) * amxId fix * 1accord alias added * test fix --------- Co-authored-by: Gabriel Chicoye --- modules/nexx360BidAdapter.js | 3 ++- test/spec/modules/nexx360BidAdapter_spec.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 29700c14a6c..69032fb151b 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -22,7 +22,7 @@ const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstre const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '4.1'; +const BIDDER_VERSION = '4.2'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; @@ -33,6 +33,7 @@ const ALIASES = [ { code: 'league-m', gvlid: 965 }, { code: 'prjads' }, { code: 'pubtech' }, + { code: '1accord', gvlid: 965 }, ]; export const storage = getStorageManager({ diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 06cbec347ff..48fa0e2a021 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -405,7 +405,7 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); - expect(requestContent.ext.bidderVersion).to.be.eql('4.1'); + expect(requestContent.ext.bidderVersion).to.be.eql('4.2'); expect(requestContent.ext.source).to.be.eql('prebid.js'); }); From 721ea31816c2254768fcdfb85d15e57e58a22448 Mon Sep 17 00:00:00 2001 From: Javier ResetDigital <113473439+javimartos@users.noreply.github.com> Date: Tue, 16 Jul 2024 19:26:11 -0300 Subject: [PATCH 0344/1097] Update resetdigitalBidAdapter.md (#11985) Update resetdigital adapter documentation --- modules/resetdigitalBidAdapter.md | 99 +++++++++++++++++-------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/modules/resetdigitalBidAdapter.md b/modules/resetdigitalBidAdapter.md index a368c7f5633..64b600a7cc0 100644 --- a/modules/resetdigitalBidAdapter.md +++ b/modules/resetdigitalBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: ResetDigital Bidder Adapter Module Type: Bidder Adapter -Maintainer: bruce@resetdigital.co +Maintainer: BidderSupport@resetdigital.co ``` # Description @@ -15,49 +15,58 @@ Video is supported but requires a publisher supplied renderer at this time. ## Web ``` - var adUnits = [ - { - code: 'your-div', - mediaTypes: { - banner: { - sizes: [[300,250]] - }, - - }, - bids: [ - { - bidder: "resetdigital", - params: { - pubId: "your-pub-id", - site_id: "your-site-id", - forceBid: true, - } - } - ] - } - ]; - - - var videoAdUnits = [ - { - code: 'your-div', - mediaTypes: { - video: { - playerSize: [640, 480] - }, - - }, - bids: [ - { - bidder: "resetdigital", - params: { - pubId: "your-pub-id", - site_id: "your-site-id", - forceBid: true, - } - } - ] - } - ]; +// Define the ad units for banner ads +var adUnits = [ + { + code: 'your-div', // Replace with the actual ad unit code + mediaTypes: { + banner: { + sizes: [[300, 250]] // Define the sizes for banner ads + } + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", // (required) Replace with your publisher ID + site_id: "your-site-id", // Replace with your site ID + endpoint: 'https://ads.resetsrv.com', // (optional) Endpoint URL for the ad server + forceBid: true, // Optional parameter to force the bid + zoneId: { // (optional) Zone ID parameters + placementId: "", // Optional ID used for reports + deals: "", // Optional string of deal IDs, comma-separated + test: 1 // Set to 1 to force the bidder to respond with a creative + } + } + } + ] + } +]; +// Define the ad units for video ads +var videoAdUnits = [ + { + code: 'your-div', // Replace with the actual video ad unit code + mediaTypes: { + video: { + playerSize: [640, 480] // Define the player size for video ads + } + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", // (required) Replace with your publisher ID + site_id: "your-site-id", // Replace with your site ID + forceBid: true, // Optional parameter to force the bid + zoneId: { // (optional) Zone ID parameters + placementId: "", // Optional ID used for reports + deals: "", // Optional string of deal IDs, comma-separated + test: 1 // Set to 1 to force the bidder to respond with a creative + } + } + } + ] + } +]; ``` From b561a150adfc71e23d4b33b82c65638a55e686a4 Mon Sep 17 00:00:00 2001 From: Andrea Tumbarello Date: Wed, 17 Jul 2024 11:34:54 +0200 Subject: [PATCH 0345/1097] AIDEM Bid Adapter: Added gvlid param for Europe GDPR compliance (#11987) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object * PlacementId is now a required parameter Added optional rateLimit parameter Added publisherId, siteId, placementId in win notice payload Added unit tests * Revert to optional placementId parameter Added missing semicolons * Extended win notice * Added arbitrary ext field to win notice * Moved aidemBidAdapter implementation to comply with ortbConverter * disabled video-specific tests * Fixed getConfig cleanup of consent management (Issue #10658) * Fixed getConfig cleanup of consent management (Issue #10658) * Fixed getConfig cleanup of consent management (Issue #10658) * Fixed getConfig cleanup of consent management (Issue #10658) * Added gvlid param for Europe GDPR compliance --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> --- modules/aidemBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 3b55682b217..07aee262baa 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -10,6 +10,7 @@ const BIDDER_CODE = 'aidem'; const BASE_URL = 'https://zero.aidemsrv.com'; const LOCAL_BASE_URL = 'http://127.0.0.1:8787'; +const GVLID = 1218 const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const REQUIRED_VIDEO_PARAMS = [ 'mimes', 'protocols', 'context' ]; @@ -232,6 +233,7 @@ function hasValidParameters(bidRequest) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function(bidRequest) { logInfo('bid: ', bidRequest); From 730d679f89ded68cfe8960a590e8aaa04d6f5426 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Wed, 17 Jul 2024 12:35:38 +0300 Subject: [PATCH 0346/1097] Adkernel Bid Adapter: add global_sun alias (#11986) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 374507c8d45..36a359170c4 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -97,7 +97,8 @@ export const spec = { {code: 'digiad'}, {code: 'monetix'}, {code: 'hyperbrainz'}, - {code: 'voisetech'} + {code: 'voisetech'}, + {code: 'global_sun'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From cdd3b5a032732d52c7b1a4eb7f6a3e03d972b675 Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:10:04 +0530 Subject: [PATCH 0347/1097] update prebid-serer bidder params for impressions (#11982) --- modules/insticatorBidAdapter.js | 66 ++++++++++--------- .../spec/modules/insticatorBidAdapter_spec.js | 9 ++- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 1ddd9334dc4..4ab7f5927f9 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -107,19 +107,8 @@ function buildVideo(bidRequest) { const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - if (!w && playerSize) { - if (Array.isArray(playerSize[0])) { - w = parseInt(playerSize[0][0], 10); - } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { - w = parseInt(playerSize[0], 10); - } - } - if (!h && playerSize) { - if (Array.isArray(playerSize[0])) { - h = parseInt(playerSize[0][1], 10); - } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { - h = parseInt(playerSize[1], 10); - } + if (!h && !w && playerSize) { + ({w, h} = parsePlayerSizeToWidthHeight(playerSize, w, h)); } const bidRequestVideo = deepAccess(bidRequest, 'mediaTypes.video'); @@ -173,6 +162,14 @@ function buildImpression(bidRequest) { }, } + if (bidRequest?.params?.adUnitId) { + deepSetValue(imp, 'ext.prebid.bidder.insticator.adUnitId', bidRequest.params.adUnitId); + } + + if (bidRequest?.params?.publisherId) { + deepSetValue(imp, 'ext.prebid.bidder.insticator.publisherId', bidRequest.params.publisherId); + } + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); if (!isNaN(bidFloor)) { @@ -247,7 +244,7 @@ function buildDevice(bidRequest) { const device = { w: window.innerWidth, h: window.innerHeight, - js: true, + js: 1, ext: { localStorage: storage.localStorageIsEnabled(), cookies: storage.cookiesAreEnabled(), @@ -569,24 +566,12 @@ function validateVideo(bid) { let w = deepAccess(bid, 'mediaTypes.video.w'); let h = deepAccess(bid, 'mediaTypes.video.h'); const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!w && playerSize) { - if (Array.isArray(playerSize[0])) { - w = parseInt(playerSize[0][0], 10); - } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { - w = parseInt(playerSize[0], 10); - } - } - if (!h && playerSize) { - if (Array.isArray(playerSize[0])) { - h = parseInt(playerSize[0][1], 10); - } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { - h = parseInt(playerSize[1], 10); - } + + if (!h && !w && playerSize) { + ({w, h} = parsePlayerSizeToWidthHeight(playerSize, w, h)); } - const videoSize = [ - w, - h, - ]; + + const videoSize = [w, h]; if ( !validateSize(videoSize) @@ -625,6 +610,25 @@ function validateVideo(bid) { return true; } +function parsePlayerSizeToWidthHeight(playerSize, w, h) { + if (!w && playerSize) { + if (Array.isArray(playerSize[0])) { + w = parseInt(playerSize[0][0], 10); + } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { + w = parseInt(playerSize[0], 10); + } + } + if (!h && playerSize) { + if (Array.isArray(playerSize[0])) { + h = parseInt(playerSize[0][1], 10); + } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { + h = parseInt(playerSize[1], 10); + } + } + + return { w, h }; +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 64033c5a7d4..158fdebeb76 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -389,7 +389,7 @@ describe('InsticatorBidAdapter', function () { expect(data.device).to.be.an('object'); expect(data.device.w).to.equal(window.innerWidth); expect(data.device.h).to.equal(window.innerHeight); - expect(data.device.js).to.equal(true); + expect(data.device.js).to.equal(1); expect(data.device.ext).to.be.an('object'); expect(data.device.ext.localStorage).to.equal(true); expect(data.device.ext.cookies).to.equal(false); @@ -439,6 +439,13 @@ describe('InsticatorBidAdapter', function () { insticator: { adUnitId: bidRequest.params.adUnitId, }, + prebid: { + bidder: { + insticator: { + adUnitId: bidRequest.params.adUnitId, + } + } + } } }]); expect(data.ext).to.be.an('object'); From 40b906d17bcfda2b369924de2278ced73975edc2 Mon Sep 17 00:00:00 2001 From: Oleg Date: Wed, 17 Jul 2024 12:41:23 +0300 Subject: [PATCH 0348/1097] kimberliteBidAdapter: video media type support (#11981) * Kimberlite bid adapter (#1) * initial: bid adapter * styling * Fix: lint (#2) * Fix: lint (#4) * review fixes (#6) * Change: filling request.ext.prebid section (#7) * Video support * Fix: tests * Adapter's version update * No video defaults --- modules/kimberliteBidAdapter.js | 31 ++- modules/kimberliteBidAdapter.md | 31 ++- .../spec/modules/kimberliteBidAdapter_spec.js | 202 ++++++++++++------ 3 files changed, 191 insertions(+), 73 deletions(-) diff --git a/modules/kimberliteBidAdapter.js b/modules/kimberliteBidAdapter.js index 72df921e18f..6ad8b9eda05 100644 --- a/modules/kimberliteBidAdapter.js +++ b/modules/kimberliteBidAdapter.js @@ -1,13 +1,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { deepSetValue } from '../src/utils.js'; +import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; -const VERSION = '1.0.0'; +const VERSION = '1.1.0'; const BIDDER_CODE = 'kimberlite'; const METHOD = 'POST'; -const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; +export const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; const VERSION_INFO = { ver: '$prebid.version$', @@ -16,7 +17,6 @@ const VERSION_INFO = { const converter = ortbConverter({ context: { - mediaType: BANNER, netRevenue: true, ttl: 300 }, @@ -35,18 +35,32 @@ const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.tagid = bidRequest.params.placementId; return imp; - } + }, + + bidResponse: function (buildBidResponse, bid, context) { + if (!bid.price) return; + + const [type] = Object.keys(context.bidRequest.mediaTypes); + if (Object.values(ORTB_MTYPES).includes(type)) { + context.mediaType = type; + } + + const bidResponse = buildBidResponse(bid, context); + return bidResponse; + }, }); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: (bidRequest = {}) => { const { params, mediaTypes } = bidRequest; let isValid = Boolean(params && params.placementId); if (mediaTypes && mediaTypes[BANNER]) { isValid = isValid && Boolean(mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + isValid = isValid && Boolean(mediaTypes[VIDEO].mimes); } else { isValid = false; } @@ -58,7 +72,10 @@ export const spec = { return { method: METHOD, url: ENDPOINT_URL, - data: converter.toORTB({ bidderRequest, bidRequests }) + data: converter.toORTB({ + bidRequests, + bidderRequest + }) } }, diff --git a/modules/kimberliteBidAdapter.md b/modules/kimberliteBidAdapter.md index c165f1073aa..06749f2c8e0 100644 --- a/modules/kimberliteBidAdapter.md +++ b/modules/kimberliteBidAdapter.md @@ -20,7 +20,7 @@ var adUnits = [ code: 'test-div', mediaTypes: { banner: { - sizes: [[320, 250], [640, 480]], + sizes: [[320, 250], [640, 480]], // Required. } }, bids: [ @@ -34,3 +34,32 @@ var adUnits = [ } ] ``` + +## Video AdUnit + +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + // ORTB 2.5 options. + mimes: ['video/mp4'], // Required. + // Other options are optional. + placement: 1, + protocols: [3, 6], + linearity: 1, + startdelay: 0 + } + }, + bids: [ + { + bidder: "kimberlite", + params: { + placementId: 'testVideo' + } + } + ] + } +] +``` diff --git a/test/spec/modules/kimberliteBidAdapter_spec.js b/test/spec/modules/kimberliteBidAdapter_spec.js index 1480f1cc768..c0394a2090b 100644 --- a/test/spec/modules/kimberliteBidAdapter_spec.js +++ b/test/spec/modules/kimberliteBidAdapter_spec.js @@ -1,6 +1,6 @@ -import { spec } from 'modules/kimberliteBidAdapter.js'; +import { spec, ENDPOINT_URL } from 'modules/kimberliteBidAdapter.js'; import { assert } from 'chai'; -import { BANNER } from '../../../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; const BIDDER_CODE = 'kimberlite'; @@ -8,74 +8,104 @@ describe('kimberliteBidAdapter', function () { const sizes = [[640, 480]]; describe('isBidRequestValid', function () { - let bidRequest; + let bidRequests; beforeEach(function () { - bidRequest = { - mediaTypes: { - [BANNER]: { - sizes: [[320, 240]] + bidRequests = [ + { + mediaTypes: { + [BANNER]: { + sizes: [[320, 240]] + } + }, + params: { + placementId: 'test-placement' } }, - params: { - placementId: 'test-placement' + { + mediaTypes: { + [VIDEO]: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' + } } - }; + ]; }); - it('pass on valid bidRequest', function () { - assert.isTrue(spec.isBidRequestValid(bidRequest)); + it('pass on valid banner bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequests[0])); }); it('fails on missed placementId', function () { - delete bidRequest.params.placementId; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].params.placementId; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty banner', function () { - delete bidRequest.mediaTypes.banner; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty banner.sizes', function () { - delete bidRequest.mediaTypes.banner.sizes; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].mediaTypes.banner.sizes; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty request', function () { assert.isFalse(spec.isBidRequestValid()); }); + + it('pass on valid video bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequests[1])); + }); + + it('fails on missed video.mimes', function () { + delete bidRequests[1].mediaTypes.video.mimes; + assert.isFalse(spec.isBidRequestValid(bidRequests[1])); + }); }); describe('buildRequests', function () { let bidRequests, bidderRequest; beforeEach(function () { - bidRequests = [{ - mediaTypes: { - [BANNER]: {sizes: sizes} + bidRequests = [ + { + mediaTypes: { + [BANNER]: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } }, - params: { - placementId: 'test-placement' + { + mediaTypes: { + [VIDEO]: { + mimes: ['video/mp4'], + } + }, + params: { + placementId: 'test-placement' + } } - }]; + ]; bidderRequest = { refererInfo: { domain: 'example.com', page: 'https://www.example.com/test.html', - }, - bids: [{ - mediaTypes: { - [BANNER]: {sizes: sizes} - } - }] + } }; }); it('valid bid request', function () { const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.equal(bidRequest.url, ENDPOINT_URL); assert.ok(bidRequest.data); const requestData = bidRequest.data; @@ -87,10 +117,10 @@ describe('kimberliteBidAdapter', function () { expect(requestData.ext).to.be.an('Object').and.have.all.keys('prebid'); expect(requestData.ext.prebid).to.be.an('Object').and.have.all.keys('ver', 'adapterVer'); - const impData = requestData.imp[0]; - expect(impData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); + const impBannerData = requestData.imp[0]; + expect(impBannerData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); - const bannerData = impData.banner; + const bannerData = impBannerData.banner; expect(bannerData.format).to.be.an('array').and.is.not.empty; const formatData = bannerData.format[0]; @@ -98,30 +128,44 @@ describe('kimberliteBidAdapter', function () { assert.equal(formatData.w, sizes[0][0]); assert.equal(formatData.h, sizes[0][1]); + + if (FEATURES.VIDEO) { + const impVideoData = requestData.imp[1]; + expect(impVideoData.video).is.to.be.an('Object').and.have.all.keys(['mimes']); + + const videoData = impVideoData.video; + expect(videoData.mimes).to.be.an('array').and.is.not.empty; + expect(videoData.mimes[0]).to.be.a('string').that.equals('video/mp4'); + } }); }); describe('interpretResponse', function () { - let bidderResponse, bidderRequest, bidRequest, expectedBid; + let bidderResponse, bidderRequest, bidRequest, expectedBids; const requestId = '07fba8b0-8812-4dc6-b91e-4a525d81729c'; - const bidId = '222209853178'; - const impId = 'imp-id'; - const crId = 'creative-id'; - const adm = 'landing'; + const bannerAdm = 'landing'; + const videoAdm = 'test vast'; beforeEach(function () { bidderResponse = { body: { id: requestId, seatbid: [{ - bid: [{ - crid: crId, - id: bidId, - impid: impId, - price: 1, - adm: adm - }] + bid: [ + { + crid: 1, + impid: 1, + price: 1, + adm: bannerAdm + }, + { + crid: 2, + impid: 2, + price: 1, + adm: videoAdm + } + ] }] } }; @@ -131,36 +175,64 @@ describe('kimberliteBidAdapter', function () { domain: 'example.com', page: 'https://www.example.com/test.html', }, - bids: [{ - bidId: impId, - mediaTypes: { - [BANNER]: {sizes: sizes} + bids: [ + { + bidId: 1, + mediaTypes: { + banner: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } }, - params: { - placementId: 'test-placement' + { + bidId: 2, + mediaTypes: { + video: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' + } } - }] + ] }; - expectedBid = { - mediaType: 'banner', - requestId: 'imp-id', - seatBidId: '222209853178', - cpm: 1, - creative_id: 'creative-id', - creativeId: 'creative-id', - ttl: 300, - netRevenue: true, - ad: adm, - meta: {} - }; + expectedBids = [ + { + mediaType: 'banner', + requestId: 1, + cpm: 1, + creative_id: 1, + creativeId: 1, + ttl: 300, + netRevenue: true, + ad: bannerAdm, + meta: {} + }, + { + mediaType: 'video', + requestId: 2, + cpm: 1, + creative_id: 2, + creativeId: 2, + ttl: 300, + netRevenue: true, + vastXml: videoAdm, + meta: {} + }, + ]; bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); }); it('pass on valid request', function () { const bids = spec.interpretResponse(bidderResponse, bidRequest); - assert.deepEqual(bids[0], expectedBid); + assert.deepEqual(bids[0], expectedBids[0]); + if (FEATURES.VIDEO) { + assert.deepEqual(bids[1], expectedBids[1]); + } }); it('fails on empty response', function () { From 8e555ec5e550f44e1df4952cb46a23b56df504e5 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 17 Jul 2024 13:45:27 +0000 Subject: [PATCH 0349/1097] Prebid 9.6.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09f7b34ea8b..9a60bcf8710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.6.0-pre", + "version": "9.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.6.0-pre", + "version": "9.6.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 6cbff559615..3f9b14c8283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.6.0-pre", + "version": "9.6.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 5ef3952ed3d51acba3aa4fc8848f96934f17457f Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 17 Jul 2024 13:45:27 +0000 Subject: [PATCH 0350/1097] Increment version to 9.7.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a60bcf8710..3ca36a169ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.6.0", + "version": "9.7.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.6.0", + "version": "9.7.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 3f9b14c8283..de8afe546ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.6.0", + "version": "9.7.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 62099b9542578e44b8446f7bac8b389c4fac9cb5 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 18 Jul 2024 02:07:14 +0200 Subject: [PATCH 0351/1097] Prebid Core: Adding idImportLibrary to activity controls (#11976) * 11705 Adding idImportLibrary to activity controls * module type fix * adds tests --------- Co-authored-by: Marcin Komorski --- modules/idImportLibrary.js | 20 ++++++++++++++------ test/spec/modules/idImportLibrary_spec.js | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/modules/idImportLibrary.js b/modules/idImportLibrary.js index f6819a485e0..8cfa7e47c7c 100644 --- a/modules/idImportLibrary.js +++ b/modules/idImportLibrary.js @@ -1,15 +1,19 @@ -import { logInfo, logError } from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {ajax} from '../src/ajax.js'; -import {config} from '../src/config.js'; import MD5 from 'crypto-js/md5.js'; +import { ACTIVITY_ENRICH_UFPD } from '../src/activities/activities.js'; +import { activityParams } from '../src/activities/activityParams.js'; +import { MODULE_TYPE_PREBID } from '../src/activities/modules.js'; +import { isActivityAllowed } from '../src/activities/rules.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { logError, logInfo } from '../src/utils.js'; let email; let conf; const LOG_PRE_FIX = 'ID-Library: '; const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; -const CONF_DEFAULT_FULL_BODY_SCAN = false; -const CONF_DEFAULT_INPUT_SCAN = false; +export const CONF_DEFAULT_FULL_BODY_SCAN = false; +export const CONF_DEFAULT_INPUT_SCAN = false; const OBSERVER_CONFIG = { subtree: true, attributes: true, @@ -257,6 +261,10 @@ export function setConfig(config) { _logError('The required url is not configured'); return; } + if (!isActivityAllowed(ACTIVITY_ENRICH_UFPD, activityParams(MODULE_TYPE_PREBID, 'idImportLibrary'))) { + _logError('Permission for id import was denied by CMP'); + return; + } if (typeof config.debounce !== 'number') { config.debounce = CONF_DEFAULT_OBSERVER_DEBOUNCE_MS; _logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS); diff --git a/test/spec/modules/idImportLibrary_spec.js b/test/spec/modules/idImportLibrary_spec.js index d5b3e32546d..6045eb0bda0 100644 --- a/test/spec/modules/idImportLibrary_spec.js +++ b/test/spec/modules/idImportLibrary_spec.js @@ -4,6 +4,10 @@ import * as idImportlibrary from 'modules/idImportLibrary.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {config} from 'src/config.js'; import {hook} from '../../../src/hook.js'; +import * as activities from '../../../src/activities/rules.js'; +import { ACTIVITY_ENRICH_UFPD } from '../../../src/activities/activities.js'; +import { CONF_DEFAULT_FULL_BODY_SCAN, CONF_DEFAULT_INPUT_SCAN } from '../../../modules/idImportLibrary.js'; + var expect = require('chai').expect; const mockMutationObserver = { @@ -86,6 +90,16 @@ describe('IdImportLibrary Tests', function () { idImportlibrary.setConfig(config); expect(config.inputscan).to.be.equal(true); }); + it('results when activity is not allowed', function () { + sandbox.stub(activities, 'isActivityAllowed').callsFake((activity) => { + return !(activity === ACTIVITY_ENRICH_UFPD); + }); + let config = { 'url': 'URL', 'debounce': 0 }; + idImportlibrary.setConfig(config); + sinon.assert.called(utils.logError); + expect(config.inputscan).to.be.not.equal(CONF_DEFAULT_INPUT_SCAN); + expect(config.fullscan).to.be.not.equal(CONF_DEFAULT_FULL_BODY_SCAN); + }); }); describe('Test with email is found', function () { let mutationObserverStub; From 37ffcf1c76fec6fef25a9398f5f191bac6abd7ea Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 17 Jul 2024 20:57:19 -0400 Subject: [PATCH 0352/1097] CleanmedianetBidAdapter.js: bug fix on plcmt (#11891) * CleanmedianetBidAdapter.js: bug fix on plcmt? * Update gamoshiBidAdapter.js * Update gamoshiBidAdapter_spec.js * Update cleanmedianetBidAdapter_spec.js --- modules/cleanmedianetBidAdapter.js | 2 +- modules/gamoshiBidAdapter.js | 2 +- test/spec/modules/cleanmedianetBidAdapter_spec.js | 2 +- test/spec/modules/gamoshiBidAdapter_spec.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index a0e85032798..01178d63872 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { maxduration: bidRequest.mediaTypes.video.maxduration, api: bidRequest.mediaTypes.video.api, skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, - placement: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, + plcmt: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 6681dc328d0..cfc2e32ca67 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { maxduration: bidRequest.mediaTypes.video.maxduration, api: bidRequest.mediaTypes.video.api, skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, - placement: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, + plcmt: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index d1ec37c4999..58270438772 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -395,7 +395,7 @@ describe('CleanmedianetAdapter', () => { expect(response.data.imp[0].video.mimes).to.equal(bidRequestWithVideo.mediaTypes.video.mimes); expect(response.data.imp[0].video.skip).to.not.exist; - expect(response.data.imp[0].video.placement).to.equal(1); + expect(response.data.imp[0].video.plcmt).to.equal(1); expect(response.data.imp[0].video.minduration).to.equal(1); expect(response.data.imp[0].video.playbackmethod).to.equal(1); expect(response.data.imp[0].video.startdelay).to.equal(1); diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index f41750cfe71..8f9818ed901 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -398,7 +398,7 @@ describe('GamoshiAdapter', () => { expect(response.data.imp[0].video.mimes).to.equal(bidRequestWithVideo.mediaTypes.video.mimes); expect(response.data.imp[0].video.skip).to.not.exist; - expect(response.data.imp[0].video.placement).to.equal(1); + expect(response.data.imp[0].video.plcmt).to.equal(1); expect(response.data.imp[0].video.minduration).to.equal(1); expect(response.data.imp[0].video.playbackmethod).to.equal(1); expect(response.data.imp[0].video.startdelay).to.equal(1); From b865952181fab757e7d78aa3a72ab0efbd1550e2 Mon Sep 17 00:00:00 2001 From: ownAdx <135326256+ownAdx-prebid@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:31:25 +0530 Subject: [PATCH 0353/1097] OwnAdX Bid Adapter : initial release (#11855) * remove changes from renderer.js library * changes done --- modules/ownadxBidAdapter.js | 99 ++++++++++++++++++++ test/spec/modules/ownadxBidAdapter_spec.js | 103 +++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 modules/ownadxBidAdapter.js create mode 100644 test/spec/modules/ownadxBidAdapter_spec.js diff --git a/modules/ownadxBidAdapter.js b/modules/ownadxBidAdapter.js new file mode 100644 index 00000000000..5843735f314 --- /dev/null +++ b/modules/ownadxBidAdapter.js @@ -0,0 +1,99 @@ +import { parseSizesInput, isEmpty } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + +const BIDDER_CODE = 'ownadx'; +const CUR = 'USD'; +const CREATIVE_TTL = 300; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.tokenId && bid.params.sspId && bid.params.seatId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param validBidRequests + * @param bidderRequest + * @return Array Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); + let mtype = 0; + if (bidRequest.mediaTypes[BANNER]) { + mtype = 1; + } else { + mtype = 2; + } + + let tkn = bidRequest.params.tokenId; + let seatid = bidRequest.params.seatId; + let sspid = bidRequest.params.sspId; + + const payload = { + sizes: sizes, + slotBidId: bidRequest.bidId, + PageUrl: bidderRequest.refererInfo.page, + mediaChannel: mtype + }; + return { + method: 'POST', + url: `https://pbs-js.prebid-ownadx.com/publisher/prebid/${seatid}/${sspid}?token=${tkn}`, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse) { + const response = serverResponse.body; + const bids = []; + if (isEmpty(response)) { + return bids; + } + const responseBid = { + width: response.width, + height: response.height, + token: response.tokenId, + ttl: CREATIVE_TTL, + requestId: response.slotBidId, + aType: response.adType || '1', + cpm: response.cpm, + creativeId: response.creativeId || 0, + netRevenue: response.netRevenue || false, + currency: response.currency || CUR, + meta: { + mediaType: response.mediaType || BANNER, + advertiserDomains: response.advertiserDomains || [] + }, + ad: response.adm + }; + bids.push(responseBid); + return bids; + } + +}; + +registerBidder(spec); diff --git a/test/spec/modules/ownadxBidAdapter_spec.js b/test/spec/modules/ownadxBidAdapter_spec.js new file mode 100644 index 00000000000..13d69b3a261 --- /dev/null +++ b/test/spec/modules/ownadxBidAdapter_spec.js @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ownadxBidAdapter.js'; + +describe('ownadx', function () { + const METHOD = 'POST'; + const URL = 'https://pbs-js.prebid-ownadx.com/publisher/prebid/9/1231?token=3f2941af4f7e446f9a19ca6045f8cff4'; + + const bidRequest = { + bidder: 'ownadx', + params: { + tokenId: '3f2941af4f7e446f9a19ca6045f8cff4', + sspId: '1231', + seatId: '9' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + sizes: [ + [300, 250], + [300, 600] + ], + bidId: 'bid-id-123456', + adUnitCode: 'ad-unit-code-1', + bidderRequestId: 'bidder-request-id-123456', + auctionId: 'auction-id-123456', + transactionId: 'transaction-id-123456' + }; + + describe('isBidRequestValid', function () { + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + refererInfo: { + page: 'https://www.test.com', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://www.test.com' + ], + canonicalUrl: null + } + }; + + it('should build correct POST request for banner bid', function () { + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(request).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://pbs-js.prebid-ownadx.com/publisher/prebid/9/1231?token=3f2941af4f7e446f9a19ca6045f8cff4'); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload.sizes).to.be.an('array'); + expect(payload.slotBidId).to.be.a('string'); + expect(payload.PageUrl).to.be.a('string'); + expect(payload.mediaChannel).to.be.a('number'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + body: { + tokenId: '3f2941af4f7e446f9a19ca6045f8cff4', + bid: 'BID-XXXX-XXXX', + width: '300', + height: '250', + cpm: '0.7', + adm: '

Ad from OwnAdX

', + slotBidId: 'bid-id-123456', + adType: '1', + statusText: 'Success' + } + }; + + let expectedResponse = [{ + token: '3f2941af4f7e446f9a19ca6045f8cff4', + requestId: 'bid-id-123456', + cpm: '0.7', + currency: 'USD', + aType: '1', + netRevenue: false, + width: '300', + height: '250', + creativeId: 0, + ttl: 300, + ad: '

Ad from OwnAdX

', + meta: { + mediaType: 'banner', + advertiserDomains: [] + } + }]; + + it('should correctly interpret valid banner response', function () { + let result = spec.interpretResponse(serverResponse); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); From 43e7f3f23e76cd51a1f1875285e95e482d5b6f05 Mon Sep 17 00:00:00 2001 From: yoppe <38334778+saitoukun@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:42:18 +0900 Subject: [PATCH 0354/1097] =?UTF-8?q?Revert=20"Appnexus=20Bid=20Adapter:?= =?UTF-8?q?=20parse=20the=20currency=20from=20the=20bid=20if=20specified?= =?UTF-8?q?=20(#=E2=80=A6"=20(#11995)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 2fef9c29351d2b4ada4a3ba6b008e616a15a0af8. --- modules/appnexusBidAdapter.js | 2 +- test/spec/modules/appnexusBidAdapter_spec.js | 33 -------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 62b62d20216..387df07a8b4 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -566,7 +566,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { cpm: rtbBid.cpm, creativeId: rtbBid.creative_id, dealId: rtbBid.deal_id, - currency: rtbBid.publisher_currency_codename || 'USD', + currency: 'USD', netRevenue: true, ttl: 300, adUnitCode: bidRequest.adUnitCode, diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 511a9a42a26..7fca35249b8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1783,7 +1783,6 @@ describe('AppNexusAdapter', function () { 'cpm': 0.5, 'cpm_publisher_currency': 0.5, 'publisher_currency_code': '$', - 'publisher_currency_codename': 'USD', 'client_initiated_ad_counting': true, 'viewability': { 'config': '' @@ -1868,38 +1867,6 @@ describe('AppNexusAdapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('should parse non-default currency', function () { - let eurCpmResponse = deepClone(response); - eurCpmResponse.tags[0].ads[0].publisher_currency_codename = 'EUR'; - - let bidderRequest = { - bidderCode: 'appnexus', - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - }; - - let result = spec.interpretResponse({ body: eurCpmResponse }, { bidderRequest }); - expect(result[0].currency).to.equal('EUR'); - }); - - it('should parse default currency', function () { - let defaultCpmResponse = deepClone(response); - delete defaultCpmResponse.tags[0].ads[0].publisher_currency_codename; - - let bidderRequest = { - bidderCode: 'appnexus', - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - }; - - let result = spec.interpretResponse({ body: defaultCpmResponse }, { bidderRequest }); - expect(result[0].currency).to.equal('USD'); - }); - it('should reject 0 cpm bids', function () { let zeroCpmResponse = deepClone(response); zeroCpmResponse.tags[0].ads[0].cpm = 0; From dc6c0c35ead5f44f3fc35852a84956a6c9036bed Mon Sep 17 00:00:00 2001 From: sgounder-viant Date: Thu, 18 Jul 2024 18:51:32 -0400 Subject: [PATCH 0355/1097] Modified endpoint (#12002) --- modules/viantOrtbBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/viantOrtbBidAdapter.js b/modules/viantOrtbBidAdapter.js index b4448715f7a..cfc4c450db8 100644 --- a/modules/viantOrtbBidAdapter.js +++ b/modules/viantOrtbBidAdapter.js @@ -5,7 +5,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js' import {deepAccess, getBidIdParameter, logError} from '../src/utils.js'; const BIDDER_CODE = 'viant'; -const ENDPOINT = 'https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder' +const ENDPOINT = 'https://bidders-us.adelphic.net/d/rtb/v25/prebid/bidder' const ADAPTER_VERSION = '2.0.0'; const DEFAULT_BID_TTL = 300; From 465096c4ab99964e9210e969c404382e13142c3b Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 19 Jul 2024 10:12:21 -0400 Subject: [PATCH 0356/1097] Update viantOrtbBidAdapter_spec.js --- test/spec/modules/viantOrtbBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index a289faf3573..67ae8b07821 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -268,7 +268,7 @@ describe('viantOrtbBidAdapter', function () { }); it('sends bid requests to the correct endpoint', function () { const url = testBuildRequests(baseBannerBidRequests, baseBidderRequest)[0].url; - expect(url).to.equal('https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder'); + expect(url).to.equal('https://bidders-us.adelphic.net/d/rtb/v25/prebid/bidder'); }); it('sends site', function () { From 7110bc792c4783395979f5f3ecaa8c2f51f2e98a Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:11:10 -0400 Subject: [PATCH 0357/1097] MobianRtdModule: Add more signals from API endpoint to first-party data (#11999) * Add more signals from mobian's contextual API endpoint to first-party data object * remove copy-pasted typing * add mobianRtdProvider to list of RTD submodules in .submodules.json * fix unexpected behavior when API response is interpreted as string * update test to account for case of broken json * use pre-existing safe load function --- modules/.submodules.json | 1 + modules/mobianRtdProvider.js | 52 +++++++++---- test/spec/modules/mobianRtdProvider_spec.js | 85 ++++++++++++++++----- 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index 126380d3631..ea128e91579 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -87,6 +87,7 @@ "jwplayerRtdProvider", "medianetRtdProvider", "mgidRtdProvider", + "mobianRtdProvider", "neuwoRtdProvider", "oneKeyRtdProvider", "optimeraRtdProvider", diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 818a35f9302..76626a7c020 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -1,12 +1,10 @@ /** * This module adds the Mobian RTD provider to the real time data module * The {@link module:modules/realTimeData} module is required - * @module modules/anonymisedRtdProvider - * @requires module:modules/realTimeData */ import { submodule } from '../src/hook.js'; import { ajaxBuilder } from '../src/ajax.js'; -import { deepSetValue } from '../src/utils.js'; +import { deepSetValue, safeJSONParse } from '../src/utils.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -24,6 +22,7 @@ export const mobianBrandSafetySubmodule = { function init() { return true; } + function getBidRequestData(bidReqConfig, callback, config) { const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const pageUrl = encodeURIComponent(getPageUrl()); @@ -33,20 +32,47 @@ function getBidRequestData(bidReqConfig, callback, config) { return new Promise((resolve) => { ajax(requestUrl, { - success: function(response) { - const risks = ['garm_high_risk', 'garm_medium_risk', 'garm_low_risk', 'garm_no_risk']; - const riskLevels = ['high_risk', 'medium_risk', 'low_risk', 'no_risk']; - - let mobianGarmRisk = 'unknown'; - for (let i = 0; i < risks.length; i++) { - if (response[risks[i]]) { - mobianGarmRisk = riskLevels[i]; - break; + success: function(responseData) { + let response = safeJSONParse(responseData); + if (!response) { + resolve({}); + callback(); + return; + } + + let mobianGarmRisk = response.garm_risk || 'unknown'; + + if (mobianGarmRisk === 'unknown') { + const risks = ['garm_high_risk', 'garm_medium_risk', 'garm_low_risk', 'garm_no_risk']; + const riskLevels = ['high', 'medium', 'low', 'none']; + + for (let i = 0; i < risks.length; i++) { + if (response[risks[i]]) { + mobianGarmRisk = riskLevels[i]; + break; + } } } + + const garmCategories = Object.keys(response) + .filter(key => key.startsWith('garm_content_category_') && response[key]) + .map(key => key.replace('garm_content_category_', '')); + + const sentiment = Object.keys(response) + .find(key => key.startsWith('sentiment_') && response[key]) + ?.replace('sentiment_', '') || 'unknown'; + + const emotions = Object.keys(response) + .filter(key => key.startsWith('emotion_') && response[key]) + .map(key => key.replace('emotion_', '')); + const risk = { - 'mobianGarmRisk': mobianGarmRisk + 'mobianGarmRisk': mobianGarmRisk, + 'garmContentCategories': garmCategories, + 'sentiment': sentiment, + 'emotions': emotions }; + resolve(risk); deepSetValue(ortb2Site.ext, 'data.mobian', risk); callback() diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index f16cb99dcd9..6f7da8888fc 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -25,81 +25,132 @@ describe('Mobian RTD Submodule', function () { it('should return no_risk when server responds with garm_no_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success({ + callbacks.success(JSON.stringify({ garm_no_risk: true, garm_low_risk: false, garm_medium_risk: false, garm_high_risk: false - }); + })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('no_risk'); + expect(risk['mobianGarmRisk']).to.equal('none'); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); - it('should return low_risk when server responds with garm_no_risk', function () { + it('should return low_risk when server responds with garm_low_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success({ + callbacks.success(JSON.stringify({ garm_no_risk: false, garm_low_risk: true, garm_medium_risk: false, garm_high_risk: false - }); + })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('low_risk'); + expect(risk['mobianGarmRisk']).to.equal('low'); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); it('should return medium_risk when server responds with garm_medium_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success({ + callbacks.success(JSON.stringify({ garm_no_risk: false, garm_low_risk: false, garm_medium_risk: true, garm_high_risk: false - }); + })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('medium_risk'); + expect(risk['mobianGarmRisk']).to.equal('medium'); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); it('should return high_risk when server responds with garm_high_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success({ + callbacks.success(JSON.stringify({ garm_no_risk: false, garm_low_risk: false, garm_medium_risk: false, garm_high_risk: true - }); + })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('high_risk'); + expect(risk['mobianGarmRisk']).to.equal('high'); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); - it('should return unknown when server response is not of the expected shape', function () { + it('should return empty object when server response is not valid JSON', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success('unexpected output not even of the right type'); }); + const originalConfig = JSON.parse(JSON.stringify(bidReqConfig)); + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.deep.equal({}); + // Check that bidReqConfig hasn't been modified + expect(bidReqConfig).to.deep.equal(originalConfig); + }); + }); + + it('should handle response with direct garm_risk field', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + garm_risk: 'low', + sentiment_positive: true, + emotion_joy: true + })); + }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('unknown'); - expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); + expect(risk).to.deep.equal({ + mobianGarmRisk: 'low', + garmContentCategories: [], + sentiment: 'positive', + emotions: ['joy'] + }); + }); + }); + + it('should handle response with GARM content categories, sentiment, and emotions', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + garm_risk: 'medium', + garm_content_category_arms: true, + garm_content_category_crime: true, + sentiment_negative: true, + emotion_anger: true, + emotion_fear: true + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.deep.equal({ + mobianGarmRisk: 'medium', + garmContentCategories: ['arms', 'crime'], + sentiment: 'negative', + emotions: ['anger', 'fear'] + }); + }); + }); + + it('should handle error response', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.error(); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { + expect(risk).to.deep.equal({}); }); }); }); From d38a06d7ea15449a5d5ccb9974d1e7afd504a137 Mon Sep 17 00:00:00 2001 From: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Date: Sat, 20 Jul 2024 01:46:37 +0530 Subject: [PATCH 0358/1097] docereeAdManager Bid Adapter : Updated bid adapter (#11996) * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Update docereeAdManagerBidAdapter.js --------- Co-authored-by: lokesh-doceree Co-authored-by: Patrick McCann --- modules/docereeAdManagerBidAdapter.js | 11 ++++------- test/spec/modules/docereeAdManagerBidAdapter_spec.js | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index d3765f5a130..05310298f03 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -74,16 +74,11 @@ function getPayload(bid, userData) { if (!userData || !bid) { return false; } - const { bidId, params } = bid; const { placementId } = params; const { userid, - email, - firstname, - lastname, specialization, - hcpid, gender, city, state, @@ -94,11 +89,12 @@ function getPayload(bid, userData) { hashedmobile, country, organization, - dob, + platformUid, + mobile } = userData; const data = { - userid: userid || '', + userid: platformUid || userid || '', email: email || '', firstname: firstname || '', lastname: lastname || '', @@ -119,6 +115,7 @@ function getPayload(bid, userData) { organization: organization || '', dob: dob || '', userconsent: 1, + mobile: mobile || '' }; return { data, diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js index 26b054f4e29..e367aab9678 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -25,6 +25,7 @@ describe('docereeadmanager', function () { userid: '', zipcode: '', userconsent: '', + platformUid: '' }, }, }, From cda06f40bb1958f77888c0ae0bb486c5c55ac8d7 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 19 Jul 2024 16:19:17 -0400 Subject: [PATCH 0359/1097] Update docereeAdManagerBidAdapter.js --- modules/docereeAdManagerBidAdapter.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index 05310298f03..f97bfbba6eb 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -95,11 +95,11 @@ function getPayload(bid, userData) { const data = { userid: platformUid || userid || '', - email: email || '', - firstname: firstname || '', - lastname: lastname || '', + email: '', + firstname: '', + lastname: '', specialization: specialization || '', - hcpid: hcpid || '', + hcpid: '', gender: gender || '', city: city || '', state: state || '', @@ -113,7 +113,7 @@ function getPayload(bid, userData) { hashedmobile: hashedmobile || '', country: country || '', organization: organization || '', - dob: dob || '', + dob: '', userconsent: 1, mobile: mobile || '' }; From 115e7c6239dd25b87a5192fe85baaec5f6872cca Mon Sep 17 00:00:00 2001 From: Amit Biton <56631148+amitbiton01@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:09:41 +0300 Subject: [PATCH 0360/1097] Vidazoo - update build request (#11918) * Add support for OMID parameters * Add DSA to bidder request in vidazooUtils * Update vidazooBidAdapter_spec to include omid source details In this commit, vidazooBidAdapter_spec has been updated to include an additional API key, 7, in the 'api' array. Furthermore, 'source' object with 'ext', 'omidpn' and 'omidpv' fields has been added. This update enhances the configuration and source details of the adapter specification. * fix Strings must use singlequote --- libraries/vidazooUtils/bidderUtils.js | 16 ++++++++++++++++ test/spec/modules/vidazooBidAdapter_spec.js | 16 ++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 2adf5c12324..4b9f2fe37d0 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -324,6 +324,22 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder } } + const api = deepAccess(mediaTypes, 'video.api', []); + if (api.includes(7)) { + const sourceExt = deepAccess(bidderRequest, 'ortb2.source.ext'); + if (sourceExt?.omidpv) { + data.omidpv = sourceExt.omidpv; + } + if (sourceExt?.omidpn) { + data.omidpn = sourceExt.omidpn; + } + } + + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + data.dsa = dsa; + } + _each(ext, (value, key) => { data['ext.' + key] = value; }); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index c5da6eebdd9..0f4a476ada3 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -92,7 +92,7 @@ const VIDEO_BID = { 'minduration': 0, 'startdelay': 0, 'linearity': 1, - 'api': [2], + 'api': [2, 7], 'placement': 1 } } @@ -157,8 +157,14 @@ const BIDDER_REQUEST = { segment: [{id: '243'}], }, ], + }, + source: { + ext: { + omidpn: 'MyIntegrationPartner', + omidpv: '7.1' + } } - }, + } }; const SERVER_RESPONSE = { @@ -359,7 +365,7 @@ describe('VidazooBidAdapter', function () { webSessionId: webSessionId, mediaTypes: { video: { - api: [2], + api: [2, 7], context: 'instream', linearity: 1, maxduration: 60, @@ -373,7 +379,9 @@ describe('VidazooBidAdapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + omidpn: 'MyIntegrationPartner', + omidpv: '7.1' } }) ; From 1192400fe1ca5bdf5b03f235b4019f5437d25354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdravko=20Kosanovi=C4=87?= <41286499+zkosanovic@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:33:18 +0200 Subject: [PATCH 0361/1097] Rise Utils: Bugfixes (#12012) * Rise Utils: Get domain from refererInfo if present * Rise Utils: Loop should default to 0 instead of '' --- libraries/riseUtils/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index 07dec275ecc..abd70d357e0 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -123,7 +123,7 @@ export function generateBidParameters(bid, bidderRequest) { sizes: sizesArray, floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), + loop: bid.bidderRequestsCount || 0, bidderRequestId: getBidIdParameter('bidderRequestId', bid), transactionId: bid.ortb2Imp?.ext?.tid || '', coppa: 0, @@ -324,6 +324,7 @@ export function generateGeneralParams(generalObject, bidderRequest, adapterVersi if (bidderRequest && bidderRequest.refererInfo) { generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + generalParams.page_domain = deepAccess(bidderRequest, 'refererInfo.domain') || deepAccess(window, 'location.hostname'); } return generalParams; From 80781d0aba2a3da719e208884a59af7072b02214 Mon Sep 17 00:00:00 2001 From: Eugene Dorfman Date: Mon, 22 Jul 2024 16:39:05 +0200 Subject: [PATCH 0362/1097] 51d module update doc (#12013) - clarified the free availability of the api - improved configuration description --- modules/51DegreesRtdProvider.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md index 424a559797d..18f346d37a8 100644 --- a/modules/51DegreesRtdProvider.md +++ b/modules/51DegreesRtdProvider.md @@ -35,7 +35,9 @@ gulp build --modules="rtdModule,51DegreesRtdProvider,appnexusBidAdapter,..." ### Prerequisites #### Resource Key -In order to use the module please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/tWrhNfY6) - choose the following properties: + +In order to use the module please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/HNZ75HT1) - choose the following properties: + * DeviceId * DeviceType * HardwareVendor @@ -45,11 +47,13 @@ In order to use the module please first obtain a Resource Key using the [Configu * PlatformVersion * ScreenPixelsHeight * ScreenPixelsWidth +* ScreenPixelsPhysicalHeight +* ScreenPixelsPhysicalWidth * ScreenInchesHeight * ScreenInchesWidth -* PixelRatio (optional) +* PixelRatio -PixelRatio is desirable, but it's a paid property requiring a paid license. Free API service is limited. Please check [51Degrees pricing](https://51degrees.com/pricing) to choose a plan that suits your needs. +The Cloud API is **free** to integrate and use. To increase limits please check [51Degrees pricing](https://51degrees.com/pricing). #### User Agent Client Hint (UA-CH) Permissions @@ -89,20 +93,20 @@ In summary we recommend using `Delegate-CH` http-equiv as the preferred method o ### Configuration -This module is configured as part of the `realTimeData.dataProviders` +This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to at least 250 ms and make sure `waitForIt` is set to `true` for the `51Degrees` RTD provider. ```javascript pbjs.setConfig({ - debug: true, // turn on for testing, remove in production + debug: false, // turn on for testing, remove in production realTimeData: { - auctionDelay: 1000, // should be set lower in production use + auctionDelay: 250, dataProviders: [ { name: '51Degrees', waitForIt: true, // should be true, otherwise the auctionDelay will be ignored params: { - // Get your resource key from https://configure.51degrees.com/tWrhNfY6 to connect to cloud.51degrees.com resourceKey: '', + // Get your resource key from https://configure.51degrees.com/HNZ75HT1 // alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen end point // onPremiseJSUrl: 'https://localhost/51Degrees.core.js' }, From 196d0405953aa2ed3f387e90d58dea9c5efdebe9 Mon Sep 17 00:00:00 2001 From: Kevin Siow Date: Mon, 22 Jul 2024 16:51:00 +0200 Subject: [PATCH 0363/1097] Dailymotion Bid Adapter: send user sync status in request (#11975) Co-authored-by: Kevin Siow --- modules/dailymotionBidAdapter.js | 19 +++++++ .../modules/dailymotionBidAdapter_spec.js | 50 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 4f8f73816fe..1e40d72f780 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -1,6 +1,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { userSync } from '../src/userSync.js'; /** * Get video metadata from bid request @@ -90,6 +92,22 @@ function getVideoMetadata(bidRequest, bidderRequest) { return videoMetadata; } +/** + * Check if user sync is enabled for Dailymotion + * + * @return boolean True if user sync is enabled + */ +function isUserSyncEnabled() { + const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled'); + + if (!syncEnabled) return false; + + const canSyncWithIframe = userSync.canBidderRegisterSync('iframe', 'dailymotion'); + const canSyncWithPixel = userSync.canBidderRegisterSync('image', 'dailymotion'); + + return !!(canSyncWithIframe || canSyncWithPixel); +} + export const spec = { code: 'dailymotion', gvlid: 573, @@ -189,6 +207,7 @@ export const spec = { atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), }, } : {}), + userSyncEnabled: isUserSyncEnabled(), request: { adUnitCode: deepAccess(bid, 'adUnitCode', ''), auctionId: deepAccess(bid, 'auctionId', ''), diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index dd8f3fa5630..74e94d2444e 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -158,6 +158,18 @@ describe('dailymotionBidAdapterTests', () => { }, }; + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestData, bidderRequestData), @@ -169,6 +181,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -790,6 +803,18 @@ describe('dailymotionBidAdapterTests', () => { }, }; + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: ['dailymotion'], + filter: 'include' + } + } + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestData, bidderRequestData), @@ -800,6 +825,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -942,6 +968,22 @@ describe('dailymotionBidAdapterTests', () => { }, }; + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: ['dailymotion'], + filter: 'include' + }, + iframe: { + bidders: ['dailymotion'], + filter: 'exclude', + }, + } + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestData, bidderRequestData), @@ -952,6 +994,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -1012,6 +1055,12 @@ describe('dailymotionBidAdapterTests', () => { }, }]; + config.setConfig({ + userSync: { + syncEnabled: false, + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestDataWithApi, {}), @@ -1025,6 +1074,7 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.false; expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.false; expect(reqData.bidder_request).to.eql({ gdprConsent: { apiVersion: 1, From 264db8c0286feaa7f2ec7e738d2509796f9c1920 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Mon, 22 Jul 2024 17:25:45 +0100 Subject: [PATCH 0364/1097] Sharethrough bid adapter add ortb2 device (#11785) * Sharethrough Bid Adapter: Add full ORTB2 device data to request payload * Sharethrough Bid Adapter: Add test to verify presence of ORTB2 device data in request --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/sharethroughBidAdapter.js | 5 +++ .../modules/sharethroughBidAdapter_spec.js | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 92d36b0b699..efa0bb3471b 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -68,6 +68,11 @@ export const sharethroughAdapterSpec = { req.device.ext['cdep'] = bidderRequest.ortb2.device.ext.cdep; } + // if present, merge device object from ortb2 into `req.device` + if (bidderRequest?.ortb2?.device) { + mergeDeep(req.device, bidderRequest.ortb2.device); + } + req.user = nullish(firstPartyData.user, {}); if (!req.user.ext) req.user.ext = {}; req.user.ext.eids = bidRequests[0].userIdAsEids || []; diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 8fc29a2cef3..77619dee528 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -346,6 +346,41 @@ describe('sharethrough adapter spec', function () { expect(openRtbReq.user.ext.eids).to.deep.equal([]); }); + + it('should add ORTB2 device data to the request', () => { + const bidderRequestWithOrtb2Device = { + ...bidderRequest, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }, + }; + + const [request] = spec.buildRequests(bidRequests, bidderRequestWithOrtb2Device); + + expect(request.data.device.w).to.equal(bidderRequestWithOrtb2Device.ortb2.device.w); + expect(request.data.device.h).to.equal(bidderRequestWithOrtb2Device.ortb2.device.h); + expect(request.data.device.dnt).to.equal(bidderRequestWithOrtb2Device.ortb2.device.dnt); + expect(request.data.device.ua).to.equal(bidderRequestWithOrtb2Device.ortb2.device.ua); + expect(request.data.device.language).to.equal(bidderRequestWithOrtb2Device.ortb2.device.language); + expect(request.data.device.devicetype).to.equal(bidderRequestWithOrtb2Device.ortb2.device.devicetype); + expect(request.data.device.make).to.equal(bidderRequestWithOrtb2Device.ortb2.device.make); + expect(request.data.device.model).to.equal(bidderRequestWithOrtb2Device.ortb2.device.model); + expect(request.data.device.os).to.equal(bidderRequestWithOrtb2Device.ortb2.device.os); + expect(request.data.device.osv).to.equal(bidderRequestWithOrtb2Device.ortb2.device.osv); + }); }); describe('no referer provided', () => { From 996b2352b8b3ebbe6d206dd42b33220c391be420 Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:32:14 -0400 Subject: [PATCH 0365/1097] remove reference to garm in output of mobian brand-safety (#12014) --- modules/mobianRtdProvider.js | 20 +--- test/spec/modules/mobianRtdProvider_spec.js | 110 ++++++-------------- 2 files changed, 35 insertions(+), 95 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 76626a7c020..71b6ed2d51d 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -40,21 +40,9 @@ function getBidRequestData(bidReqConfig, callback, config) { return; } - let mobianGarmRisk = response.garm_risk || 'unknown'; + let mobianRisk = response.garm_risk || 'unknown'; - if (mobianGarmRisk === 'unknown') { - const risks = ['garm_high_risk', 'garm_medium_risk', 'garm_low_risk', 'garm_no_risk']; - const riskLevels = ['high', 'medium', 'low', 'none']; - - for (let i = 0; i < risks.length; i++) { - if (response[risks[i]]) { - mobianGarmRisk = riskLevels[i]; - break; - } - } - } - - const garmCategories = Object.keys(response) + const categories = Object.keys(response) .filter(key => key.startsWith('garm_content_category_') && response[key]) .map(key => key.replace('garm_content_category_', '')); @@ -67,8 +55,8 @@ function getBidRequestData(bidReqConfig, callback, config) { .map(key => key.replace('emotion_', '')); const risk = { - 'mobianGarmRisk': mobianGarmRisk, - 'garmContentCategories': garmCategories, + 'risk': mobianRisk, + 'contentCategories': categories, 'sentiment': sentiment, 'emotions': emotions }; diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index 6f7da8888fc..43f6cbaf569 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -23,70 +23,63 @@ describe('Mobian RTD Submodule', function () { ajaxStub.restore(); }); - it('should return no_risk when server responds with garm_no_risk', function () { + it('should return risk level when server responds with garm_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ - garm_no_risk: true, - garm_low_risk: false, - garm_medium_risk: false, - garm_high_risk: false - })); - }); - - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('none'); - expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); - }); - }); - - it('should return low_risk when server responds with garm_low_risk', function () { - ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success(JSON.stringify({ - garm_no_risk: false, - garm_low_risk: true, - garm_medium_risk: false, - garm_high_risk: false + garm_risk: 'low', + sentiment_positive: true, + emotion_joy: true })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('low'); + expect(risk).to.deep.equal({ + risk: 'low', + contentCategories: [], + sentiment: 'positive', + emotions: ['joy'] + }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); - it('should return medium_risk when server responds with garm_medium_risk', function () { + it('should handle response with GARM content categories, sentiment, and emotions', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ - garm_no_risk: false, - garm_low_risk: false, - garm_medium_risk: true, - garm_high_risk: false + garm_risk: 'medium', + garm_content_category_arms: true, + garm_content_category_crime: true, + sentiment_negative: true, + emotion_anger: true, + emotion_fear: true })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('medium'); + expect(risk).to.deep.equal({ + risk: 'medium', + contentCategories: ['arms', 'crime'], + sentiment: 'negative', + emotions: ['anger', 'fear'] + }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); - it('should return high_risk when server responds with garm_high_risk', function () { + it('should return unknown risk when garm_risk is not present', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ - garm_no_risk: false, - garm_low_risk: false, - garm_medium_risk: false, - garm_high_risk: true + sentiment_neutral: true })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.have.property('mobianGarmRisk'); - expect(risk['mobianGarmRisk']).to.equal('high'); + expect(risk).to.deep.equal({ + risk: 'unknown', + contentCategories: [], + sentiment: 'neutral', + emotions: [] + }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); @@ -103,47 +96,6 @@ describe('Mobian RTD Submodule', function () { }); }); - it('should handle response with direct garm_risk field', function () { - ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success(JSON.stringify({ - garm_risk: 'low', - sentiment_positive: true, - emotion_joy: true - })); - }); - - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({ - mobianGarmRisk: 'low', - garmContentCategories: [], - sentiment: 'positive', - emotions: ['joy'] - }); - }); - }); - - it('should handle response with GARM content categories, sentiment, and emotions', function () { - ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success(JSON.stringify({ - garm_risk: 'medium', - garm_content_category_arms: true, - garm_content_category_crime: true, - sentiment_negative: true, - emotion_anger: true, - emotion_fear: true - })); - }); - - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({ - mobianGarmRisk: 'medium', - garmContentCategories: ['arms', 'crime'], - sentiment: 'negative', - emotions: ['anger', 'fear'] - }); - }); - }); - it('should handle error response', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.error(); From f658dbf8ccfa9916505310f7486dca7bb149a083 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 23 Jul 2024 11:00:51 -0400 Subject: [PATCH 0366/1097] Update PULL_REQUEST_TEMPLATE.md (#12019) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b225be162a8..367ace94d37 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -41,7 +41,7 @@ For any user facing change, submit a link to a PR on the docs repo at https://gi } ``` -Be sure to test the integration with your adserver using the [Hello World](/integrationExamples/gpt/hello_world.html) sample page. --> +Be sure to test the integration with your adserver using the [Hello World](https://github.com/prebid/Prebid.js/blob/master/integrationExamples/gpt/hello_world.html) sample page. --> ## Other information From 0c6b844d8cad89773dc0544fa8979e04b3928942 Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:39:24 +0530 Subject: [PATCH 0367/1097] Reading pmp from ortb2Imp object (#12020) --- modules/pubmaticBidAdapter.js | 3 ++- test/spec/modules/pubmaticBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 85018a73a54..405e419dac8 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -687,7 +687,8 @@ function _createImpressionObject(bid, bidderRequest) { }, bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY, displaymanager: 'Prebid.js', - displaymanagerver: '$prebid.version$' // prebid version + displaymanagerver: '$prebid.version$', // prebid version + pmp: bid.ortb2Imp?.pmp || undefined }; _addPMPDealsInImpression(impObj, bid); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 82715ac518a..48f7a19e7d0 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2974,6 +2974,24 @@ describe('PubMatic adapter', function () { expect(data.device).to.include.any.keys('connectiontype'); } }); + + it('should send imp.pmp in request if pmp json is present in adUnit ortb2Imp object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + originalBidRequests[0].ortb2Imp.pmp = { + 'private_auction': 0, + 'deals': [{ 'id': '5678' }] + } + const bidRequest = spec.buildRequests(originalBidRequests); + let data = JSON.parse(bidRequest.data); + expect(data.imp[0].pmp).to.exist.and.to.be.an('object'); + }) + + it('should not send imp.pmp in request if pmp json is not present in adUnit ortb2Imp object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + const bidRequest = spec.buildRequests(originalBidRequests); + let data = JSON.parse(bidRequest.data); + expect(data.imp[0].pmp).to.deep.equal(undefined); + }) }); it('Request params dctr check', function () { From 9135793288213d1741feeb3d78810f718b568059 Mon Sep 17 00:00:00 2001 From: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:59:07 +0200 Subject: [PATCH 0368/1097] ID5 User Id module - use userId storage mechanism to store request nb (#11965) --- modules/id5IdSystem.js | 107 +++------------------- test/spec/modules/id5IdSystem_spec.js | 123 ++++++-------------------- 2 files changed, 39 insertions(+), 191 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 1f369f5a7a1..95e40337e79 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -13,14 +13,13 @@ import { isPlainObject, logError, logInfo, - logWarn, - safeJSONParse + logWarn } from '../src/utils.js'; import {fetch} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; -import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; +import {gppDataHandler, uspDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {GreedyPromise} from '../src/utils/promise.js'; import {loadExternalScript} from '../src/adloader.js'; @@ -34,19 +33,12 @@ import {loadExternalScript} from '../src/adloader.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; -const NB_EXP_DAYS = 30; export const ID5_STORAGE_NAME = 'id5id'; -export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; -const LOCAL_STORAGE = 'html5'; const LOG_PREFIX = 'User ID - ID5 submodule: '; const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; const ID5_DOMAIN = 'id5-sync.com'; const TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; -// order the legacy cookie names in reverse priority order so the last -// cookie in the array is the most preferred to use -const LEGACY_COOKIE_NAMES = ['pbjs-id5id', 'id5id.1st', 'id5id']; - export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @@ -231,7 +223,7 @@ export const id5IdSubmodule = { * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @param {Object} cacheIdObj - existing id, if any - * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. + * @return {IdResponse} A response object that contains id. */ extendId(config, consentData, cacheIdObj) { if (!hasWriteConsentToLocalStorage(consentData)) { @@ -239,10 +231,10 @@ export const id5IdSubmodule = { return cacheIdObj; } - const partnerId = validateConfig(config) ? config.params.partner : 0; - incrementNb(partnerId); - logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); + if (cacheIdObj) { + cacheIdObj.nbPage = incrementNb(cacheIdObj) + } return cacheIdObj; }, eids: { @@ -401,8 +393,8 @@ export class IdFetchFlow { const params = this.submoduleConfig.params; const hasGdpr = (this.gdprConsentData && typeof this.gdprConsentData.gdprApplies === 'boolean' && this.gdprConsentData.gdprApplies) ? 1 : 0; const referer = getRefererInfo(); - const signature = (this.cacheIdObj && this.cacheIdObj.signature) ? this.cacheIdObj.signature : getLegacyCookieSignature(); - const nbPage = incrementAndResetNb(params.partner); + const signature = this.cacheIdObj ? this.cacheIdObj.signature : undefined; + const nbPage = incrementNb(this.cacheIdObj); const trueLinkInfo = window.id5Bootstrap ? window.id5Bootstrap.getTrueLinkInfo() : {booted: false}; const data = { @@ -456,7 +448,6 @@ export class IdFetchFlow { #processFetchCallResponse(fetchCallResponse) { try { if (fetchCallResponse.privacy) { - storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); if (window.id5Bootstrap && window.id5Bootstrap.setPrivacy) { window.id5Bootstrap.setPrivacy(fetchCallResponse.privacy); } @@ -507,89 +498,19 @@ function validateConfig(config) { logError(LOG_PREFIX + 'storage required to be set'); return false; } - - // in a future release, we may return false if storage type or name are not set as required - if (config.storage.type !== LOCAL_STORAGE) { - logWarn(LOG_PREFIX + `storage type recommended to be '${LOCAL_STORAGE}'. In a future release this may become a strict requirement`); - } - // in a future release, we may return false if storage type or name are not set as required if (config.storage.name !== ID5_STORAGE_NAME) { - logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'. In a future release this may become a strict requirement`); + logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'.`); } return true; } -export function expDaysStr(expDays) { - return (new Date(Date.now() + (1000 * 60 * 60 * 24 * expDays))).toUTCString(); -} - -export function nbCacheName(partnerId) { - return `${ID5_STORAGE_NAME}_${partnerId}_nb`; -} - -export function storeNbInCache(partnerId, nb) { - storeInLocalStorage(nbCacheName(partnerId), nb, NB_EXP_DAYS); -} - -export function getNbFromCache(partnerId) { - let cacheNb = getFromLocalStorage(nbCacheName(partnerId)); - return (cacheNb) ? parseInt(cacheNb) : 0; -} - -function incrementNb(partnerId) { - const nb = (getNbFromCache(partnerId) + 1); - storeNbInCache(partnerId, nb); - return nb; -} - -function incrementAndResetNb(partnerId) { - const result = incrementNb(partnerId); - storeNbInCache(partnerId, 0); - return result; -} - -function getLegacyCookieSignature() { - let legacyStoredValue; - LEGACY_COOKIE_NAMES.forEach(function (cookie) { - if (storage.getCookie(cookie)) { - legacyStoredValue = safeJSONParse(storage.getCookie(cookie)) || legacyStoredValue; - } - }); - return (legacyStoredValue && legacyStoredValue.signature) || ''; -} - -/** - * This will make sure we check for expiration before accessing local storage - * @param {string} key - */ -export function getFromLocalStorage(key) { - const storedValueExp = storage.getDataFromLocalStorage(`${key}_exp`); - // empty string means no expiration set - if (storedValueExp === '') { - return storage.getDataFromLocalStorage(key); - } else if (storedValueExp) { - if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { - return storage.getDataFromLocalStorage(key); - } +function incrementNb(cachedObj) { + if (cachedObj && cachedObj.nbPage !== undefined) { + return cachedObj.nbPage + 1; + } else { + return 1; } - // if we got here, then we have an expired item or we didn't set an - // expiration initially somehow, so we need to remove the item from the - // local storage - storage.removeDataFromLocalStorage(key); - return null; -} - -/** - * Ensure that we always set an expiration in local storage since - * by default it's not required - * @param {string} key - * @param {any} value - * @param {number} expDays - */ -export function storeInLocalStorage(key, value, expDays) { - storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); - storage.setDataInLocalStorage(`${key}`, value); } /** diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index eae5fd21310..2703c6681e7 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -93,6 +93,15 @@ describe('ID5 ID System', function () { 'Content-Type': 'application/json' }; + function expDaysStr(expDays) { + return (new Date(Date.now() + (1000 * 60 * 60 * 24 * expDays))).toUTCString(); + } + + function storeInStorage(key, value, expDays) { + id5System.storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); + id5System.storage.setDataInLocalStorage(`${key}`, value); + } + function getId5FetchConfig(partner = ID5_TEST_PARTNER_ID, storageName = id5System.ID5_STORAGE_NAME, storageType = 'html5') { return { name: ID5_MODULE_NAME, @@ -637,8 +646,6 @@ describe('ID5 ID System', function () { it('should call the ID5 server with nb=1 when no stored value exists and reset after', async function () { const xhrServerMock = new XhrServerMock(server); - const TEST_PARTNER_ID = 189; - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(TEST_PARTNER_ID)); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); @@ -648,28 +655,28 @@ describe('ID5 ID System', function () { expect(requestBody.nbPage).is.eq(1); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - await submoduleResponsePromise; + const response = await submoduleResponsePromise; - expect(id5System.getNbFromCache(TEST_PARTNER_ID)).is.eq(0); + expect(response.nbPage).is.undefined; }); it('should call the ID5 server with incremented nb when stored value exists and reset after', async function () { const xhrServerMock = new XhrServerMock(server); const TEST_PARTNER_ID = 189; const config = getId5FetchConfig(TEST_PARTNER_ID); - id5System.storeNbInCache(TEST_PARTNER_ID, 1); + const storedObj = {...ID5_STORED_OBJ, nbPage: 1}; // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(config, undefined, ID5_STORED_OBJ); + const submoduleResponsePromise = callSubmoduleGetId(config, undefined, storedObj); const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.nbPage).is.eq(2); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - await submoduleResponsePromise; + const response = await submoduleResponsePromise; - expect(id5System.getNbFromCache(TEST_PARTNER_ID)).is.eq(0); + expect(response.nbPage).is.undefined; }); it('should call the ID5 server with ab_testing object when abTesting is turned on', async function () { @@ -720,45 +727,6 @@ describe('ID5 ID System', function () { await submoduleResponsePromise; }); - it('should store the privacy object from the ID5 server response', async function () { - const xhrServerMock = new XhrServerMock(server); - - // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - - const privacy = { - jurisdiction: 'gdpr', - id5_consent: true - }; - - const fetchRequest = await xhrServerMock.expectFetchRequest(); - const responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = privacy; - - fetchRequest.respond(200, responseHeader, JSON.stringify(responseObject)); - await submoduleResponsePromise; - - expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.eq(JSON.stringify(privacy)); - coreStorage.removeDataFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME); - }); - - it('should not store a privacy object if not part of ID5 server response', async function () { - const xhrServerMock = new XhrServerMock(server); - coreStorage.removeDataFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME); - - // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - - const fetchRequest = await xhrServerMock.expectFetchRequest(); - const responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = undefined; - - fetchRequest.respond(200, responseHeader, JSON.stringify(responseObject)); - await submoduleResponsePromise; - - expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.null; - }); - describe('with successful external module call', function () { const MOCK_RESPONSE = { ...ID5_JSON_RESPONSE, @@ -926,7 +894,6 @@ describe('ID5 ID System', function () { sinon.stub(events, 'getEvents').returns([]); coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()); adUnits = [getAdUnitMock()]; }); @@ -934,19 +901,18 @@ describe('ID5 ID System', function () { events.getEvents.restore(); coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst'); sandbox.restore(); }); it('should add stored ID from cache to bids', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); - requestBidsHook(function () { + requestBidsHook(wrapAsyncExpects(done, () => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); @@ -964,11 +930,11 @@ describe('ID5 ID System', function () { }); }); done(); - }, {adUnits}); + }), {adUnits}); }); it('should add stored EUID from cache to bids', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); @@ -997,7 +963,7 @@ describe('ID5 ID System', function () { }); it('should add stored TRUE_LINK_ID from cache to bids', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); @@ -1041,48 +1007,20 @@ describe('ID5 ID System', function () { }, {adUnits}); }); - it('should set nb=1 in cache when no stored nb value exists and cached ID', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); - - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - requestBidsHook((adUnitConfig) => { - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); - done(); - }, {adUnits}); - }); - - it('should increment nb in cache when stored nb value exists and cached ID', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); - - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - requestBidsHook(() => { - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); - done(); - }, {adUnits}); - }); - it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { const xhrServerMock = new XhrServerMock(server); - const initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); - id5System.storeInLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`, id5System.expDaysStr(-1), 1); + let storedObject = ID5_STORED_OBJ; + storedObject.nbPage = 1; + const initialLocalStorageValue = JSON.stringify(storedObject); + storeInStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); + storeInStorage(`${id5System.ID5_STORAGE_NAME}_last`, expDaysStr(-1), 1); - id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; id5Config.userSync.auctionDelay = 0; // do not trigger callback before auction init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(id5Config); - return new Promise((resolve) => { requestBidsHook(() => { resolve(); @@ -1095,18 +1033,7 @@ describe('ID5 ID System', function () { const requestBody = JSON.parse(request.requestBody); expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); expect(requestBody.nbPage).is.eq(2); - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); request.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); - - return new Promise(function (resolve) { - (function waitForCondition() { - if (id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); - setTimeout(waitForCondition, 30); - })(); - }); - }).then(() => { - expect(decodeURIComponent(id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); }); }); }); From 9d5bedef42fe6cedd20cce43cd993dea620d492c Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 24 Jul 2024 10:06:39 -0600 Subject: [PATCH 0369/1097] Readme : fix broken link to docs (#12031) * Readme : fix broken link to docs * Update README.md * Update README.md --------- Co-authored-by: Patrick McCann --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f890f055104..609baf82c08 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://circleci.com/gh/prebid/Prebid.js.svg?style=svg)](https://circleci.com/gh/prebid/Prebid.js) -[![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](https://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") [![Coverage Status](https://coveralls.io/repos/github/prebid/Prebid.js/badge.svg)](https://coveralls.io/github/prebid/Prebid.js) # Prebid.js @@ -7,8 +7,8 @@ > A free and open source library for publishers to quickly implement header bidding. This README is for developers who want to contribute to Prebid.js. -Additional documentation can be found at [the Prebid homepage](http://prebid.org). -Working examples can be found in [the developer docs](http://prebid.org/dev-docs/getting-started.html). +Additional documentation can be found at [the Prebid.js documentation homepage](https://docs.prebid.org/prebid/prebidjs.html). +Working examples can be found in [the developer docs](https://prebid.org/dev-docs/getting-started.html). Prebid.js is open source software that is offered for free as a convenience. While it is designed to help companies address legal requirements associated with header bidding, we cannot and do not warrant that your use of Prebid.js will satisfy legal requirements. You are solely responsible for ensuring that your use of Prebid.js complies with all applicable laws. We strongly encourage you to obtain legal advice when using Prebid.js to ensure your implementation complies with all laws where you operate. @@ -374,7 +374,7 @@ The results will be in *Note*: Starting in June 2016, all pull requests to Prebid.js need to include tests with greater than 80% code coverage before they can be merged. For more information, see [#421](https://github.com/prebid/Prebid.js/issues/421). -For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http://prebid.org/dev-docs/testing-prebid.html). +For instructions on writing tests for Prebid.js, see [Testing Prebid.js](https://prebid.org/dev-docs/testing-prebid.html). ### Supported Browsers From 6b2b59ea135a6bfdea569374af0f77a2ddf09e23 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 25 Jul 2024 23:24:03 +0200 Subject: [PATCH 0370/1097] Core: ORTB video params validation (work on dupe) (#11970) * Core: Video: add ORTB video params validation * AdagioBidAdapter: use video helper for ortb fields validation * Core: Video: improve validateOrtbVideoFields() * Core: Video: use compacted Map for ORTB_VIDEO_PARAMS --- libraries/ortbConverter/processors/video.js | 26 +---- modules/adagioBidAdapter.js | 60 ++--------- src/prebid.js | 3 +- src/video.js | 79 ++++++++++++++- test/spec/modules/adagioBidAdapter_spec.js | 1 - test/spec/video_spec.js | 104 +++++++++++++++++++- 6 files changed, 195 insertions(+), 78 deletions(-) diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index caa855566eb..85ab1cd6ecf 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -1,30 +1,7 @@ import {deepAccess, isEmpty, logWarn, mergeDeep, sizesToSizeTuples, sizeTupleToRtbSize} from '../../../src/utils.js'; import {VIDEO} from '../../../src/mediaTypes.js'; -// parameters that share the same name & semantics between pbjs adUnits and imp.video -const ORTB_VIDEO_PARAMS = new Set([ - 'pos', - 'placement', - 'plcmt', - 'api', - 'mimes', - 'protocols', - 'playbackmethod', - 'minduration', - 'maxduration', - 'w', - 'h', - 'startdelay', - 'placement', - 'linearity', - 'skip', - 'skipmin', - 'skipafter', - 'minbitrate', - 'maxbitrate', - 'delivery', - 'playbackend' -]); +import {ORTB_VIDEO_PARAMS} from '../../../src/video.js'; export function fillVideoImp(imp, bidRequest, context) { if (context.mediaType && context.mediaType !== VIDEO) return; @@ -32,6 +9,7 @@ export function fillVideoImp(imp, bidRequest, context) { const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); if (!isEmpty(videoParams)) { const video = Object.fromEntries( + // Parameters that share the same name & semantics between pbjs adUnits and imp.video Object.entries(videoParams) .filter(([name]) => ORTB_VIDEO_PARAMS.has(name)) ); diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 9bcebf9205e..cb125d4446e 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,9 +8,7 @@ import { getDNT, getWindowSelf, isArray, - isArrayOfNums, isFn, - isInteger, isNumber, isStr, logError, @@ -18,7 +16,7 @@ import { logWarn, } from '../src/utils.js'; import { getRefererInfo, parseDomain } from '../src/refererDetection.js'; -import { OUTSTREAM } from '../src/video.js'; +import { OUTSTREAM, validateOrtbVideoFields } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; import { _ADAGIO } from '../libraries/adagioUtils/adagioUtils.js'; import { config } from '../src/config.js'; @@ -40,39 +38,6 @@ export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER. const CURRENCY = 'USD'; -// This provide a whitelist and a basic validation of OpenRTB 2.5 options used by the Adagio SSP. -// Accept all options but 'protocol', 'companionad', 'companiontype', 'ext' -// https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf -export const ORTB_VIDEO_PARAMS = { - 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), - 'minduration': (value) => isInteger(value), - 'maxduration': (value) => isInteger(value), - 'protocols': (value) => isArrayOfNums(value), - 'w': (value) => isInteger(value), - 'h': (value) => isInteger(value), - 'startdelay': (value) => isInteger(value), - 'placement': (value) => { - logWarn(LOG_PREFIX, 'The OpenRTB video param `placement` is deprecated and should not be used anymore.'); - return isInteger(value) - }, - 'plcmt': (value) => isInteger(value), - 'linearity': (value) => isInteger(value), - 'skip': (value) => [1, 0].includes(value), - 'skipmin': (value) => isInteger(value), - 'skipafter': (value) => isInteger(value), - 'sequence': (value) => isInteger(value), - 'battr': (value) => isArrayOfNums(value), - 'maxextended': (value) => isInteger(value), - 'minbitrate': (value) => isInteger(value), - 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => isInteger(value), - 'playbackmethod': (value) => isArrayOfNums(value), - 'playbackend': (value) => isInteger(value), - 'delivery': (value) => isArrayOfNums(value), - 'pos': (value) => isInteger(value), - 'api': (value) => isArrayOfNums(value) -}; - /** * Returns the window.ADAGIO global object used to store Adagio data. * This object is created in window.top if possible, otherwise in window.self. @@ -186,6 +151,12 @@ function _getEids(bidRequest) { } } +/** + * Merge and compute video params set at mediaTypes and bidder params level + * + * @param {object} bidRequest - copy of the original bidRequest object. + * @returns {void} + */ function _buildVideoBidRequest(bidRequest) { const videoAdUnitParams = deepAccess(bidRequest, 'mediaTypes.video', {}); const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); @@ -206,22 +177,11 @@ function _buildVideoBidRequest(bidRequest) { }; if (videoParams.context && videoParams.context === OUTSTREAM) { - bidRequest.mediaTypes.video.playerName = getPlayerName(bidRequest); + videoParams.playerName = getPlayerName(bidRequest); } - // Only whitelisted OpenRTB options need to be validated. - // Other options will still remain in the `mediaTypes.video` object - // sent in the ad-request, but will be ignored by the SSP. - Object.keys(ORTB_VIDEO_PARAMS).forEach(paramName => { - if (videoParams.hasOwnProperty(paramName)) { - if (ORTB_VIDEO_PARAMS[paramName](videoParams[paramName])) { - bidRequest.mediaTypes.video[paramName] = videoParams[paramName]; - } else { - delete bidRequest.mediaTypes.video[paramName]; - logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); - } - } - }); + bidRequest.mediaTypes.video = videoParams; + validateOrtbVideoFields(bidRequest); } function _parseNativeBidResponse(bid) { diff --git a/src/prebid.js b/src/prebid.js index e91af3e4d04..c92ab8f5a89 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -41,7 +41,7 @@ import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; import {insertLocatorFrame, renderAdDirect} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; -import {fillVideoDefaults} from './video.js'; +import {fillVideoDefaults, validateOrtbVideoFields} from './video.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -134,6 +134,7 @@ function validateVideoMediaType(adUnit) { delete validatedAdUnit.mediaTypes.video.playerSize; } } + validateOrtbVideoFields(validatedAdUnit); return validatedAdUnit; } diff --git a/src/video.js b/src/video.js index f8de2b98861..9be9adec4c5 100644 --- a/src/video.js +++ b/src/video.js @@ -1,4 +1,4 @@ -import {deepAccess, logError} from './utils.js'; +import {deepAccess, isArrayOfNums, isInteger, isNumber, isPlainObject, isStr, logError, logWarn} from './utils.js'; import {config} from '../src/config.js'; import {hook} from './hook.js'; import {auctionManager} from './auctionManager.js'; @@ -6,6 +6,47 @@ import {auctionManager} from './auctionManager.js'; export const OUTSTREAM = 'outstream'; export const INSTREAM = 'instream'; +/** + * List of OpenRTB 2.x video object properties with simple validators. + * Not included: `companionad`, `durfloors`, `ext` + * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md + */ +export const ORTB_VIDEO_PARAMS = new Map([ + [ 'mimes', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string') ], + [ 'minduration', isInteger ], + [ 'maxduration', isInteger ], + [ 'startdelay', isInteger ], + [ 'maxseq', isInteger ], + [ 'poddur', isInteger ], + [ 'protocols', isArrayOfNums ], + [ 'w', isInteger ], + [ 'h', isInteger ], + [ 'podid', isStr ], + [ 'podseq', isInteger ], + [ 'rqddurs', isArrayOfNums ], + [ 'placement', isInteger ], // deprecated, see plcmt + [ 'plcmt', isInteger ], + [ 'linearity', isInteger ], + [ 'skip', value => [1, 0].includes(value) ], + [ 'skipmin', isInteger ], + [ 'skipafter', isInteger ], + [ 'sequence', isInteger ], // deprecated + [ 'slotinpod', isInteger ], + [ 'mincpmpersec', isNumber ], + [ 'battr', isArrayOfNums ], + [ 'maxextended', isInteger ], + [ 'minbitrate', isInteger ], + [ 'maxbitrate', isInteger ], + [ 'boxingallowed', isInteger ], + [ 'playbackmethod', isArrayOfNums ], + [ 'playbackend', isInteger ], + [ 'delivery', isArrayOfNums ], + [ 'pos', isInteger ], + [ 'api', isArrayOfNums ], + [ 'companiontype', isArrayOfNums ], + [ 'poddedupe', isArrayOfNums ] +]); + export function fillVideoDefaults(adUnit) { const video = adUnit?.mediaTypes?.video; if (video != null && video.plcmt == null) { @@ -17,6 +58,42 @@ export function fillVideoDefaults(adUnit) { } } +/** + * validateOrtbVideoFields mutates the `adUnit.mediaTypes.video` object by removing invalid ortb properties (default). + * The onInvalidParam callback can be used to handle invalid properties differently. + * Other properties are ignored and kept as is. + * + * @param {Object} adUnit - The adUnit object. + * @param {Function} onInvalidParam - The callback function to be called with key, value, and adUnit. + * @returns {void} + */ +export function validateOrtbVideoFields(adUnit, onInvalidParam) { + const videoParams = adUnit?.mediaTypes?.video; + + if (!isPlainObject(videoParams)) { + logWarn(`validateOrtbVideoFields: videoParams must be an object.`); + return; + } + + if (videoParams != null) { + Object.entries(videoParams) + .forEach(([key, value]) => { + if (!ORTB_VIDEO_PARAMS.has(key)) { + return + } + const isValid = ORTB_VIDEO_PARAMS.get(key)(value); + if (!isValid) { + if (typeof onInvalidParam === 'function') { + onInvalidParam(key, value, adUnit); + } else { + delete videoParams[key]; + logWarn(`Invalid prop in adUnit "${adUnit.code}": Invalid value for mediaTypes.video.${key} ORTB property. The property has been removed.`); + } + } + }); + } +} + /** * @typedef {object} VideoBid * @property {string} adId id of the bid diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 629771de9d6..75b0635bbef 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -603,7 +603,6 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); - sinon.assert.calledTwice(utils.logWarn.withArgs(sinon.match(new RegExp(/^Adagio: The OpenRTB/)))); }); }); diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 3252c58c687..18955771049 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,12 +1,26 @@ -import {fillVideoDefaults, isValidVideoBid} from 'src/video.js'; +import {fillVideoDefaults, isValidVideoBid, validateOrtbVideoFields} from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; +import * as utils from '../../src/utils.js'; describe('video.js', function () { + let sandbox; + let utilsMock; + before(() => { hook.ready(); }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + utilsMock = sandbox.mock(utils); + }) + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }); + describe('fillVideoDefaults', () => { function fillDefaults(videoMediaType = {}) { const adUnit = {mediaTypes: {video: videoMediaType}}; @@ -77,6 +91,94 @@ describe('video.js', function () { }) }) + describe('validateOrtbVideoFields', () => { + it('remove incorrect ortb properties, and keep non ortb ones', () => { + sandbox.spy(utils, 'logWarn'); + + const mt = { + content: 'outstream', + + mimes: ['video/mp4'], + minduration: 5, + maxduration: 15, + startdelay: 0, + maxseq: 0, + poddur: 0, + protocols: [7], + w: 600, + h: 480, + podid: 'an-id', + podseq: 0, + rqddurs: [5], + placement: 1, + plcmt: 1, + linearity: 1, + skip: 0, + skipmin: 3, + skipafter: 3, + sequence: 0, + slotinpod: 0, + mincpmpersec: 2.5, + battr: [6, 7], + maxextended: 0, + minbitrate: 800, + maxbitrate: 1000, + boxingallowed: 1, + playbackmethod: [1], + playbackend: 1, + delivery: [2], + pos: 0, + api: 6, // -- INVALID + companiontype: [1, 2, 3], + poddedupe: [1], + + otherOne: 'test', + }; + + const expected = {...mt}; + delete expected.api; + + const adUnit = { + code: 'adUnitCode', + mediaTypes: { video: mt } + }; + validateOrtbVideoFields(adUnit); + + expect(adUnit.mediaTypes.video).to.eql(expected); + sinon.assert.callCount(utils.logWarn, 1); + }); + + it('Early return when 1st param is not a plain object', () => { + sandbox.spy(utils, 'logWarn'); + + validateOrtbVideoFields(); + validateOrtbVideoFields([]); + validateOrtbVideoFields(null); + validateOrtbVideoFields('hello'); + validateOrtbVideoFields(() => {}); + + sinon.assert.callCount(utils.logWarn, 5); + }); + + it('Calls onInvalidParam when a property is invalid', () => { + const onInvalidParam = sandbox.spy(); + const adUnit = { + code: 'adUnitCode', + mediaTypes: { + video: { + content: 'outstream', + mimes: ['video/mp4'], + api: 6 + } + } + }; + validateOrtbVideoFields(adUnit, onInvalidParam); + + sinon.assert.calledOnce(onInvalidParam); + sinon.assert.calledWith(onInvalidParam, 'api', 6, adUnit); + }); + }) + describe('isValidVideoBid', () => { it('validates valid instream bids', function () { const bid = { From e4efe07a15fce200c45d57911661703c20ae1ba8 Mon Sep 17 00:00:00 2001 From: maelmrgt <77864748+maelmrgt@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:26:23 +0200 Subject: [PATCH 0371/1097] Greenbids RTD provider: debug flag (#12037) * feat(rtd): add flag to force filtering of rtd module * creating test_branch * add test file * feat(rtd): add debug flag to remove bidders from auction * del test file * bump * review --- modules/greenbidsAnalyticsAdapter.js | 11 +++++++---- modules/greenbidsRtdProvider.js | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index 0aac98b453e..156ae0332a2 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -6,7 +6,7 @@ import {deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName} const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.3.0'; +export const ANALYTICS_VERSION = '2.3.1'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; @@ -197,9 +197,12 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER }, handleAuctionEnd(auctionEndArgs) { const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); - this.sendEventMessage('/', - this.createBidMessage(auctionEndArgs, cachedAuction) - ); + const isFilteringForced = getParameterByName('greenbids_force_filtering'); + if (!isFilteringForced) { + this.sendEventMessage('/', + this.createBidMessage(auctionEndArgs, cachedAuction) + ) + }; }, handleBidTimeout(timeoutBids) { timeoutBids.forEach((bid) => { diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js index 5496fc71c4e..e7423abf115 100644 --- a/modules/greenbidsRtdProvider.js +++ b/modules/greenbidsRtdProvider.js @@ -1,11 +1,11 @@ -import { logError, deepClone, generateUUID, deepSetValue, deepAccess } from '../src/utils.js'; +import { logError, logInfo, logWarn, deepClone, generateUUID, deepSetValue, deepAccess, getParameterByName } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; const MODULE_NAME = 'greenbidsRtdProvider'; -const MODULE_VERSION = '2.0.0'; +const MODULE_VERSION = '2.0.1'; const ENDPOINT = 'https://t.greenbids.ai'; const rtdOptions = {}; @@ -46,6 +46,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { function createPromise(reqBidsConfigObj, greenbidsId) { return new Promise((resolve) => { const timeoutId = setTimeout(() => { + logWarn('GreenbidsRtdProvider: Greenbids API timeout, skipping shaping'); resolve(reqBidsConfigObj); }, rtdOptions.timeout); ajax( @@ -57,6 +58,7 @@ function createPromise(reqBidsConfigObj, greenbidsId) { }, error: () => { clearTimeout(timeoutId); + logWarn('GreenbidsRtdProvider: Greenbids API response error, skipping shaping'); resolve(reqBidsConfigObj); }, }, @@ -73,11 +75,16 @@ function createPromise(reqBidsConfigObj, greenbidsId) { function processSuccessResponse(response, timeoutId, reqBidsConfigObj, greenbidsId) { clearTimeout(timeoutId); - const responseAdUnits = JSON.parse(response); - updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits, greenbidsId); + try { + const responseAdUnits = JSON.parse(response); + updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits, greenbidsId); + } catch (e) { + logWarn('GreenbidsRtdProvider: Greenbids API response parsing error, skipping shaping'); + } } function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { + const isFilteringForced = getParameterByName('greenbids_force_filtering'); adUnits.forEach((adUnit) => { const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); if (matchingAdUnit) { @@ -86,7 +93,10 @@ function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { keptInAuction: matchingAdUnit.bidders, isExploration: matchingAdUnit.isExploration }); - if (!matchingAdUnit.isExploration) { + if (isFilteringForced) { + adUnit.bids = []; + logInfo('Greenbids Rtd: filtering flag detected, forcing filtering of Rtd module.'); + } else if (!matchingAdUnit.isExploration) { removeFalseBidders(adUnit, matchingAdUnit); } } From c68d96c33fddd819b45f79c8f8dacb50ff6a5ece Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Thu, 25 Jul 2024 15:27:59 -0600 Subject: [PATCH 0372/1097] Update default to maintenance (#12022) --- .github/release-drafter.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index a3246cffd6d..cb1a634f613 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -3,14 +3,15 @@ name-template: 'Prebid $RESOLVED_VERSION Release' tag-template: '$RESOLVED_VERSION' categories: - title: '🚀 New Features' - label: 'feature' + label: 'feature' - title: '🛠 Maintenance' - label: 'maintenance' + label: 'maintenance' - title: '🐛 Bug Fixes' - labels: - - 'fix' - - 'bugfix' - - 'bug' + labels: + - 'fix' + - 'bugfix' + - 'bug' + default: maintenance change-template: '- $TITLE (#$NUMBER)' version-resolver: major: From 530cf2bb1481ca3f173cea71bee50cadf1eb93eb Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 25 Jul 2024 17:30:11 -0400 Subject: [PATCH 0373/1097] Revert "Update default to maintenance (#12022)" (#12040) This reverts commit c68d96c33fddd819b45f79c8f8dacb50ff6a5ece. --- .github/release-drafter.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index cb1a634f613..a3246cffd6d 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -3,15 +3,14 @@ name-template: 'Prebid $RESOLVED_VERSION Release' tag-template: '$RESOLVED_VERSION' categories: - title: '🚀 New Features' - label: 'feature' + label: 'feature' - title: '🛠 Maintenance' - label: 'maintenance' + label: 'maintenance' - title: '🐛 Bug Fixes' - labels: - - 'fix' - - 'bugfix' - - 'bug' - default: maintenance + labels: + - 'fix' + - 'bugfix' + - 'bug' change-template: '- $TITLE (#$NUMBER)' version-resolver: major: From fa78a8b41abfb5d3e3ad4032bbe0b77565f65d67 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 25 Jul 2024 19:39:01 -0400 Subject: [PATCH 0374/1097] Update release-drafter.yml (#12041) --- .github/release-drafter.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index a3246cffd6d..5876dfa0138 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,6 +1,10 @@ name-template: 'Prebid $RESOLVED_VERSION Release' tag-template: '$RESOLVED_VERSION' +autolabeler: + - label: 'maintenance' + title: + - '/^(?!.*(bug|initial|release|fix)).*$/i' categories: - title: '🚀 New Features' label: 'feature' From 5cefb01069b694e89d18b1035e15f67abef47f98 Mon Sep 17 00:00:00 2001 From: Luca Corbo Date: Fri, 26 Jul 2024 03:33:04 +0200 Subject: [PATCH 0375/1097] WURFL RTD submodule: initial version (#11840) * WURFL Rtd Provider: initial version * WURFL Rtd Provider: import fetch method from ajax.js module * WURFL Rtd Provider: unit tests * WURFL Rtd Provider: list wurflRtdProvider in the .submodules.json file * WURFL Rtd Provider: remove wurfl from adloader.js * WURFL Rtd Provider: update to use loadExternalScript * WURFL Rtd Provider: update to use sendBeacon from ajax.js --- .../gpt/wurflRtdProvider_example.html | 106 ++++++ modules/.submodules.json | 3 +- modules/wurflRtdProvider.js | 213 ++++++++++++ modules/wurflRtdProvider.md | 67 ++++ src/adloader.js | 1 + test/spec/modules/wurflRtdProvider_spec.js | 324 ++++++++++++++++++ 6 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/gpt/wurflRtdProvider_example.html create mode 100644 modules/wurflRtdProvider.js create mode 100644 modules/wurflRtdProvider.md create mode 100644 test/spec/modules/wurflRtdProvider_spec.js diff --git a/integrationExamples/gpt/wurflRtdProvider_example.html b/integrationExamples/gpt/wurflRtdProvider_example.html new file mode 100644 index 00000000000..f2bfe7f76b7 --- /dev/null +++ b/integrationExamples/gpt/wurflRtdProvider_example.html @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/modules/.submodules.json b/modules/.submodules.json index ea128e91579..6b4b0d21d54 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -100,7 +100,8 @@ "sirdataRtdProvider", "symitriDapRtdProvider", "timeoutRtdProvider", - "weboramaRtdProvider" + "weboramaRtdProvider", + "wurflRtdProvider" ], "fpdModule": [ "validationFpdModule", diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js new file mode 100644 index 00000000000..94bb8c6440a --- /dev/null +++ b/modules/wurflRtdProvider.js @@ -0,0 +1,213 @@ +import { submodule } from '../src/hook.js'; +import { fetch, sendBeacon } from '../src/ajax.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { + mergeDeep, + prefixLog, +} from '../src/utils.js'; + +// Constants +const REAL_TIME_MODULE = 'realTimeData'; +const MODULE_NAME = 'wurfl'; + +// WURFL_JS_HOST is the host for the WURFL service endpoints +const WURFL_JS_HOST = 'https://prebid.wurflcloud.com'; +// WURFL_JS_ENDPOINT_PATH is the path for the WURFL.js endpoint used to load WURFL data +const WURFL_JS_ENDPOINT_PATH = '/wurfl.js'; +// STATS_ENDPOINT_PATH is the path for the stats endpoint used to send analytics data +const STATS_ENDPOINT_PATH = '/v1/prebid/stats'; + +const logger = prefixLog('[WURFL RTD Submodule]'); + +// enrichedBidders holds a list of prebid bidder names, of bidders which have been +// injected with WURFL data +const enrichedBidders = new Set(); + +/** + * init initializes the WURFL RTD submodule + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +const init = (config, userConsent) => { + logger.logMessage('initialized'); + return true; +} + +/** + * getBidRequestData enriches the OpenRTB 2.0 device data with WURFL data + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} callback Called on completion + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => { + const altHost = config.params?.altHost ?? null; + const isDebug = config.params?.debug ?? false; + + const bidders = new Set(); + reqBidsConfigObj.adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + bidders.add(bid.bidder); + }); + }); + + let host = WURFL_JS_HOST; + if (altHost) { + host = altHost; + } + + const url = new URL(host); + url.pathname = WURFL_JS_ENDPOINT_PATH; + + if (isDebug) { + url.searchParams.set('debug', 'true') + } + + url.searchParams.set('mode', 'prebid') + logger.logMessage('url', url.toString()); + + try { + loadExternalScript(url.toString(), MODULE_NAME, () => { + logger.logMessage('script injected'); + window.WURFLPromises.complete.then((res) => { + logger.logMessage('received data', res); + if (!res.wurfl_pbjs) { + logger.logError('invalid WURFL.js for Prebid response'); + } else { + enrichBidderRequests(reqBidsConfigObj, bidders, res); + } + callback(); + }); + }); + } catch (err) { + logger.logError(err); + callback(); + } +} + +/** + * enrichBidderRequests enriches the OpenRTB 2.0 device data with WURFL data for Business Edition + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Array} bidders List of bidders + * @param {Object} wjsResponse WURFL.js response + */ +function enrichBidderRequests(reqBidsConfigObj, bidders, wjsResponse) { + const authBidders = wjsResponse.wurfl_pbjs?.authorized_bidders ?? {}; + const caps = wjsResponse.wurfl_pbjs?.caps ?? []; + + bidders.forEach((bidderCode) => { + if (bidderCode in authBidders) { + // inject WURFL data + enrichedBidders.add(bidderCode); + const data = bidderData(wjsResponse.WURFL, caps, authBidders[bidderCode]); + logger.logMessage(`injecting data for ${bidderCode}: `, data); + enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + return; + } + // inject WURFL low entropy data + const data = lowEntropyData(wjsResponse.WURFL, wjsResponse.wurfl_pbjs?.low_entropy_caps); + logger.logMessage(`injecting low entropy data for ${bidderCode}: `, data); + enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + }); +} + +/** + * bidderData returns the WURFL data for a bidder + * @param {Object} wurflData WURFL data + * @param {Array} caps Capability list + * @param {Array} filter Filter list + * @returns {Object} Bidder data + */ +export const bidderData = (wurflData, caps, filter) => { + const data = {}; + caps.forEach((cap, index) => { + if (!filter.includes(index)) { + return; + } + if (cap in wurflData) { + data[cap] = wurflData[cap]; + } + }); + return data; +} + +/** + * lowEntropyData returns the WURFL low entropy data + * @param {Object} wurflData WURFL data + * @param {Array} lowEntropyCaps Low entropy capability list + * @returns {Object} Bidder data + */ +export const lowEntropyData = (wurflData, lowEntropyCaps) => { + const data = {}; + lowEntropyCaps.forEach((cap, _) => { + let value = wurflData[cap]; + if (cap == 'complete_device_name') { + value = value.replace(/Apple (iP(hone|ad|od)).*/, 'Apple iP$2'); + } + data[cap] = value; + }); + return data; +} + +/** + * enrichBidderRequest enriches the bidder request with WURFL data + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {String} bidderCode Bidder code + * @param {Object} wurflData WURFL data + */ +export const enrichBidderRequest = (reqBidsConfigObj, bidderCode, wurflData) => { + const ortb2data = { + 'device': { + 'ext': { + 'wurfl': wurflData, + } + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidderCode]: ortb2data }); +} + +/** + * onAuctionEndEvent is called when the auction ends + * @param {Object} auctionDetails Auction details + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +function onAuctionEndEvent(auctionDetails, config, userConsent) { + const altHost = config.params?.altHost ?? null; + + let host = WURFL_JS_HOST; + if (altHost) { + host = altHost; + } + + const url = new URL(host); + url.pathname = STATS_ENDPOINT_PATH; + + if (enrichedBidders.size === 0) { + return; + } + + var payload = JSON.stringify({ bidders: [...enrichedBidders] }); + const sentBeacon = sendBeacon(url.toString(), payload); + if (sentBeacon) { + return; + } + + fetch(url.toString(), { + method: 'POST', + body: payload, + mode: 'no-cors', + keepalive: true + }); +} + +// The WURFL submodule +export const wurflSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData, + onAuctionEndEvent, +} + +// Register the WURFL submodule as submodule of realTimeData +submodule(REAL_TIME_MODULE, wurflSubmodule); diff --git a/modules/wurflRtdProvider.md b/modules/wurflRtdProvider.md new file mode 100644 index 00000000000..d656add3543 --- /dev/null +++ b/modules/wurflRtdProvider.md @@ -0,0 +1,67 @@ +# WURFL Real-time Data Submodule + +## Overview + + Module Name: WURFL Rtd Provider + Module Type: Rtd Provider + Maintainer: prebid@scientiamobile.com + +## Description + +The WURFL RTD module enriches the OpenRTB 2.0 device data with [WURFL data](https://www.scientiamobile.com/wurfl-js-business-edition-at-the-intersection-of-javascript-and-enterprise/). +The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilites like `is_mobile`, `complete_device_name` and `form_factor`. + +For a more detailed analysis bidders can subscribe to detect iPhone and iPad models and receive additional [WURFL device capabilities](https://www.scientiamobile.com/capabilities/?products%5B%5D=wurfl-js). + +## User-Agent Client Hints + +WURFL.js is fully compatible with Chromium's User-Agent Client Hints (UA-CH) initiative. If User-Agent Client Hints are absent in the HTTP headers that WURFL.js receives, the service will automatically fall back to using the User-Agent Client Hints' JS API to fetch [high entropy client hint values](https://wicg.github.io/ua-client-hints/#getHighEntropyValues) from the client device. However, we recommend that you explicitly opt-in/advertise support for User-Agent Client Hints on your website and delegate them to the WURFL.js service for the fastest detection experience. Our documentation regarding implementing User-Agent Client Hint support [is available here](https://docs.scientiamobile.com/guides/implementing-useragent-clienthints). + +## Usage + +### Build +``` +gulp build --modules="wurflRtdProvider,appnexusBidAdapter,..." +``` + +### Configuration + +Use `setConfig` to instruct Prebid.js to initilize the WURFL RTD module, as specified below. + +This module is configured as part of the `realTimeData.dataProviders` + +```javascript +var TIMEOUT = 1000; +pbjs.setConfig({ + realTimeData: { + auctionDelay: TIMEOUT, + dataProviders: [{ + name: 'wurfl', + waitForIt: true, + params: { + debug: false + } + }] + } +}); +``` + +### Parameters + +| Name | Type | Description | Default | +| :------------------------ | :------------ | :--------------------------------------------------------------- |:----------------- | +| name | String | Real time data module name | Always 'wurfl' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | | +| params.altHost | String | Alternate host to connect to WURFL.js | | +| params.debug | Boolean | Enable debug | `false` | + +## Testing + +To view an example of how the WURFL RTD module works : + +`gulp serve --modules=wurflRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/wurflRtdProvider_example.html` diff --git a/src/adloader.js b/src/adloader.js index 79ea6e017bb..d1bc881adb5 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -32,6 +32,7 @@ const _approvedLoadExternalJSList = [ 'dynamicAdBoost', '51Degrees', 'symitridap', + 'wurfl', // UserId Submodules 'justtag', 'tncId', diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js new file mode 100644 index 00000000000..5b1cc5b751f --- /dev/null +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -0,0 +1,324 @@ +import { + bidderData, + enrichBidderRequest, + lowEntropyData, + wurflSubmodule, +} from 'modules/wurflRtdProvider'; +import * as ajaxModule from 'src/ajax'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +describe('wurflRtdProvider', function () { + describe('wurflSubmodule', function () { + const altHost = 'http://example.local/wurfl.js'; + const wurfl_pbjs = { + low_entropy_caps: ['complete_device_name', 'form_factor', 'is_mobile'], + caps: [ + 'advertised_browser', + 'advertised_browser_version', + 'advertised_device_os', + 'advertised_device_os_version', + 'brand_name', + 'complete_device_name', + 'form_factor', + 'is_app_webview', + 'is_full_desktop', + 'is_mobile', + 'is_robot', + 'is_smartphone', + 'is_smarttv', + 'is_tablet', + 'manufacturer_name', + 'marketing_name' + ], + authorized_bidders: { + 'bidder1': [0, 1, 2, 3, 4, 5, 6, 7, 10, 13, 15], + 'bidder2': [5, 6, 7, 8, 9, 10, 11, 12, 13, 14], + } + } + + const WURFL = { + advertised_browser: 'Chrome', + advertised_browser_version: '125.0.6422.76', + advertised_device_os: 'Linux', + advertised_device_os_version: '6.5.0', + brand_name: 'Google', + complete_device_name: 'Google Chrome', + form_factor: 'Desktop', + is_app_webview: !1, + is_full_desktop: !0, + is_mobile: !1, + is_robot: !1, + is_smartphone: !1, + is_smarttv: !1, + is_tablet: !1, + manufacturer_name: '', + marketing_name: '', + } + + // expected analytics values + const expectedStatsURL = 'https://prebid.wurflcloud.com/v1/prebid/stats'; + const expectedData = JSON.stringify({ bidders: ['bidder1', 'bidder2'] }); + + let sandbox; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + window.WURFLPromises = { + init: new Promise(function(resolve, reject) { resolve({ WURFL, wurfl_pbjs }) }), + complete: new Promise(function(resolve, reject) { resolve({ WURFL, wurfl_pbjs }) }), + }; + }); + + afterEach(() => { + // Restore the original functions + sandbox.restore(); + window.WURFLPromises = undefined; + }); + + // Bid request config + const reqBidsConfigObj = { + adUnits: [{ + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' }, + { bidder: 'bidder3' }, + ] + }], + ortb2Fragments: { + bidder: {}, + } + }; + + it('initialises the WURFL RTD provider', function () { + expect(wurflSubmodule.init()).to.be.true; + }); + + it('should enrich the bid request data', (done) => { + const expectedURL = new URL(altHost); + expectedURL.searchParams.set('debug', true); + expectedURL.searchParams.set('mode', 'prebid'); + + const callback = () => { + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({ + bidder1: { + device: { + ext: { + wurfl: { + advertised_browser: 'Chrome', + advertised_browser_version: '125.0.6422.76', + advertised_device_os: 'Linux', + advertised_device_os_version: '6.5.0', + brand_name: 'Google', + complete_device_name: 'Google Chrome', + form_factor: 'Desktop', + is_app_webview: !1, + is_robot: !1, + is_tablet: !1, + marketing_name: '', + }, + }, + }, + }, + bidder2: { + device: { + ext: { + wurfl: { + complete_device_name: 'Google Chrome', + form_factor: 'Desktop', + is_app_webview: !1, + is_full_desktop: !0, + is_mobile: !1, + is_robot: !1, + is_smartphone: !1, + is_smarttv: !1, + is_tablet: !1, + manufacturer_name: '', + }, + }, + }, + }, + bidder3: { + device: { + ext: { + wurfl: { + complete_device_name: 'Google Chrome', + form_factor: 'Desktop', + is_mobile: !1, + }, + }, + }, + }, + }); + done(); + }; + + const config = { + params: { + altHost: altHost, + debug: true, + } + }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + expect(loadExternalScriptStub.calledOnce).to.be.true; + const loadExternalScriptCall = loadExternalScriptStub.getCall(0); + expect(loadExternalScriptCall.args[0]).to.equal(expectedURL.toString()); + expect(loadExternalScriptCall.args[1]).to.equal('wurfl'); + }); + + it('onAuctionEndEvent: should send analytics data using navigator.sendBeacon, if available', () => { + const auctionDetails = {}; + const config = {}; + const userConsent = {}; + + const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon'); + + // Call the function + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Assertions + expect(sendBeaconStub.calledOnce).to.be.true; + expect(sendBeaconStub.calledWithExactly(expectedStatsURL, expectedData)).to.be.true; + }); + + it('onAuctionEndEvent: should send analytics data using fetch as fallback, if navigator.sendBeacon is not available', () => { + const auctionDetails = {}; + const config = {}; + const userConsent = {}; + + const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon').value(undefined); + const windowFetchStub = sandbox.stub(window, 'fetch'); + const fetchAjaxStub = sandbox.stub(ajaxModule, 'fetch'); + + // Call the function + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Assertions + expect(sendBeaconStub.called).to.be.false; + + expect(fetchAjaxStub.calledOnce).to.be.true; + const fetchAjaxCall = fetchAjaxStub.getCall(0); + expect(fetchAjaxCall.args[0]).to.equal(expectedStatsURL); + expect(fetchAjaxCall.args[1].method).to.equal('POST'); + expect(fetchAjaxCall.args[1].body).to.equal(expectedData); + expect(fetchAjaxCall.args[1].mode).to.equal('no-cors'); + }); + }); + + describe('bidderData', () => { + it('should return the WURFL data for a bidder', () => { + const wjsData = { + capability1: 'value1', + capability2: 'value2', + capability3: 'value3', + }; + const caps = ['capability1', 'capability2', 'capability3']; + const filter = [0, 2]; + + const result = bidderData(wjsData, caps, filter); + + expect(result).to.deep.equal({ + capability1: 'value1', + capability3: 'value3', + }); + }); + + it('should return an empty object if the filter is empty', () => { + const wjsData = { + capability1: 'value1', + capability2: 'value2', + capability3: 'value3', + }; + const caps = ['capability1', 'capability3']; + const filter = []; + + const result = bidderData(wjsData, caps, filter); + + expect(result).to.deep.equal({}); + }); + }); + + describe('lowEntropyData', () => { + it('should return the correct low entropy data for Apple devices', () => { + const wjsData = { + complete_device_name: 'Apple iPhone X', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const lowEntropyCaps = ['complete_device_name', 'form_factor', 'is_mobile']; + const expectedData = { + complete_device_name: 'Apple iPhone', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const result = lowEntropyData(wjsData, lowEntropyCaps); + expect(result).to.deep.equal(expectedData); + }); + + it('should return the correct low entropy data for Android devices', () => { + const wjsData = { + complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const lowEntropyCaps = ['complete_device_name', 'form_factor', 'is_mobile']; + const expectedData = { + complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const result = lowEntropyData(wjsData, lowEntropyCaps); + expect(result).to.deep.equal(expectedData); + }); + + it('should return an empty object if the lowEntropyCaps array is empty', () => { + const wjsData = { + complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const lowEntropyCaps = []; + const expectedData = {}; + const result = lowEntropyData(wjsData, lowEntropyCaps); + expect(result).to.deep.equal(expectedData); + }); + }); + + describe('enrichBidderRequest', () => { + it('should enrich the bidder request with WURFL data', () => { + const reqBidsConfigObj = { + ortb2Fragments: { + bidder: { + exampleBidder: { + device: { + ua: 'user-agent', + } + } + } + } + }; + const bidderCode = 'exampleBidder'; + const wjsData = { + capability1: 'value1', + capability2: 'value2' + }; + + enrichBidderRequest(reqBidsConfigObj, bidderCode, wjsData); + + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({ + exampleBidder: { + device: { + ua: 'user-agent', + ext: { + wurfl: { + capability1: 'value1', + capability2: 'value2' + } + } + } + } + }); + }); + }); +}); From d6a58aeb12a0d9618c92a88634f3cf0c3884f46b Mon Sep 17 00:00:00 2001 From: tongwu-sh Date: Fri, 26 Jul 2024 09:35:14 +0800 Subject: [PATCH 0376/1097] ttd bid adapter: configurable endpoint (#12004) * support endpoint params for ttd adapter * update to support http2 endpoint only * remove dup code * add missing file * fix battr issue * fix warning * update connection type * add 5g --------- Co-authored-by: Tong Wu --- libraries/connectionInfo/connectionUtils.js | 33 +++++++++++++++ modules/prismaBidAdapter.js | 28 +------------ modules/ttdBidAdapter.js | 46 +++++++-------------- test/spec/modules/ttdBidAdapter_spec.js | 11 ++++- 4 files changed, 58 insertions(+), 60 deletions(-) create mode 100644 libraries/connectionInfo/connectionUtils.js diff --git a/libraries/connectionInfo/connectionUtils.js b/libraries/connectionInfo/connectionUtils.js new file mode 100644 index 00000000000..29fed27b91d --- /dev/null +++ b/libraries/connectionInfo/connectionUtils.js @@ -0,0 +1,33 @@ +/** + * Returns the type of connection. + * + * @returns {number} - Type of connection. + */ +export function getConnectionType() { + const connection = navigator.connection || navigator.webkitConnection; + if (!connection) { + return 0; + } + switch (connection.type) { + case 'ethernet': + return 1; + case 'wifi': + return 2; + case 'wimax': + return 6; + default: + switch (connection.effectiveType) { + case 'slow-2g': + case '2g': + return 4; + case '3g': + return 5; + case '4g': + return 6; + case '5g': + return 7; + default: + return connection.type == 'cellular' ? 3 : 0; + } + } +} diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js index b42c4b8af3f..9f7d37dcebe 100644 --- a/modules/prismaBidAdapter.js +++ b/modules/prismaBidAdapter.js @@ -3,6 +3,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; +import {getConnectionType} from '../libraries/connectionInfo/connectionUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -20,33 +21,6 @@ const METRICS_TRACKER_URL = 'https://prisma.nexx360.io/track-imp'; const GVLID = 965; -function getConnectionType() { - const connection = navigator.connection || navigator.webkitConnection; - if (!connection) { - return 0; - } - switch (connection.type) { - case 'ethernet': - return 1; - case 'wifi': - return 2; - case 'cellular': - switch (connection.effectiveType) { - case 'slow-2g': - case '2g': - return 4; - case '3g': - return 5; - case '4g': - return 6; - default: - return 3; - } - default: - return 0; - } -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index d7705f2f5df..e9d0d3ca9f1 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -2,7 +2,8 @@ import * as utils from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import {isNumber} from '../src/utils.js'; +import { isNumber } from '../src/utils.js'; +import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -13,10 +14,11 @@ import {isNumber} from '../src/utils.js'; * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ -const BIDADAPTERVERSION = 'TTD-PREBID-2023.09.05'; +const BIDADAPTERVERSION = 'TTD-PREBID-2024.07.26'; const BIDDER_CODE = 'ttd'; const BIDDER_CODE_LONG = 'thetradedesk'; const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/'; +const BIDDER_ENDPOINT_HTTP2 = 'https://d2.adsrvr.org/bid/bidder/'; const USER_SYNC_ENDPOINT = 'https://match.adsrvr.org'; const MEDIA_TYPE = { @@ -99,33 +101,6 @@ function getDevice(firstPartyData) { return device; }; -function getConnectionType() { - const connection = navigator.connection || navigator.webkitConnection; - if (!connection) { - return 0; - } - switch (connection.type) { - case 'ethernet': - return 1; - case 'wifi': - return 2; - case 'cellular': - switch (connection.effectiveType) { - case 'slow-2g': - case '2g': - return 4; - case '3g': - return 5; - case '4g': - return 6; - default: - return 3; - } - default: - return 0; - } -} - function getUser(bidderRequest, firstPartyData) { let user = {}; if (bidderRequest.gdprConsent) { @@ -241,7 +216,7 @@ function banner(bid) { }, optionalParams); - const battr = utils.deepAccess(bid, 'ortb2Imp.battr'); + const battr = utils.deepAccess(bid, 'ortb2Imp.banner.battr'); if (battr) { banner.battr = battr; } @@ -318,7 +293,7 @@ function video(bid) { video.maxbitrate = maxbitrate; } - const battr = utils.deepAccess(bid, 'ortb2Imp.battr'); + const battr = utils.deepAccess(bid, 'ortb2Imp.video.battr'); if (battr) { video.battr = battr; } @@ -327,6 +302,13 @@ function video(bid) { } } +function selectEndpoint(params) { + if (params.useHttp2) { + return BIDDER_ENDPOINT_HTTP2; + } + return BIDDER_ENDPOINT; +} + export const spec = { code: BIDDER_CODE, gvlid: 21, @@ -443,7 +425,7 @@ export const spec = { topLevel.pmp = firstPartyData.pmp } - let url = BIDDER_ENDPOINT + bidderRequest.bids[0].params.supplySourceId; + let url = selectEndpoint(bidderRequest.bids[0].params) + bidderRequest.bids[0].params.supplySourceId; let serverRequest = { method: 'POST', diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 1fe504ba8e8..9f98a734d1c 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -277,6 +277,13 @@ describe('ttdBidAdapter', function () { expect(url).to.equal('https://direct.adsrvr.org/bid/bidder/supplier'); }); + it('sends bid requests to the correct custom endpoint', function () { + let bannerBidRequestsWithCustomEndpoint = deepClone(baseBannerBidRequests); + bannerBidRequestsWithCustomEndpoint[0].params.useHttp2 = true; + const url = testBuildRequests(bannerBidRequestsWithCustomEndpoint, baseBidderRequest).url; + expect(url).to.equal('https://d2.adsrvr.org/bid/bidder/supplier'); + }); + it('sends publisher id', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.site).to.be.not.null; @@ -442,7 +449,9 @@ describe('ttdBidAdapter', function () { let clonedBannerRequests = deepClone(baseBannerBidRequests); const battr = [1, 2, 3]; clonedBannerRequests[0].ortb2Imp = { - battr: battr + banner: { + battr: battr + } }; const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; expect(requestBody.imp[0].banner.battr).to.equal(battr); From 450f3a6b52cc3a8ac645f2c5acdd738cde2a8725 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 26 Jul 2024 13:18:46 +0000 Subject: [PATCH 0377/1097] Prebid 9.7.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ca36a169ae..1882e462e6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.7.0-pre", + "version": "9.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.7.0-pre", + "version": "9.7.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index de8afe546ef..ddd87513771 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.7.0-pre", + "version": "9.7.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From ac84d657721baff14b54f758e58dfeb557c857bc Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 26 Jul 2024 13:18:47 +0000 Subject: [PATCH 0378/1097] Increment version to 9.8.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1882e462e6f..19a83a7b6ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.7.0", + "version": "9.8.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.7.0", + "version": "9.8.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index ddd87513771..db93bbb4317 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.7.0", + "version": "9.8.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From d97968247b9e3c337bd1b26a280d5aebf49627ed Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:47:37 +0300 Subject: [PATCH 0379/1097] Rubicon Bid Adapter: fix hb_size undefined value for native media type (#12039) * Fix hb_size undefined value for native media type * Add unit test for native bids width and height --- modules/rubiconBidAdapter.js | 6 +++--- test/spec/modules/rubiconBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 64bcdf78399..457c52d0750 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -219,9 +219,9 @@ export const converter = ortbConverter({ const {bidRequest} = context; let [parseSizeWidth, parseSizeHeight] = bidRequest.mediaTypes.video?.context === 'outstream' ? parseSizes(bidRequest, VIDEO) : [undefined, undefined]; - - bidResponse.width = bid.w || parseSizeWidth || bidResponse.playerWidth; - bidResponse.height = bid.h || parseSizeHeight || bidResponse.playerHeight; + // 0 by default to avoid undefined size + bidResponse.width = bid.w || parseSizeWidth || bidResponse.playerWidth || 0; + bidResponse.height = bid.h || parseSizeHeight || bidResponse.playerHeight || 0; if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = outstreamRenderer(bidResponse); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 9e25300e10b..a5d25da9123 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -3987,6 +3987,16 @@ describe('the rubicon adapter', function () { let bids = spec.interpretResponse({body: response}, {data: request}); expect(bids).to.have.nested.property('[0].native'); }); + it('should set 0 to bids width and height if `w` and `h` in response object not defined', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + let response = getNativeResponse({impid: request.imp[0].id}); + delete response.seatbid[0].bid[0].w; + delete response.seatbid[0].bid[0].h + let bids = spec.interpretResponse({body: response}, {data: request}); + expect(bids[0].width).to.equal(0); + expect(bids[0].height).to.equal(0); + }) }); } From fa3f86aabd3ed70852f20d384b51b4366ad4d3b1 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 29 Jul 2024 17:25:38 -0400 Subject: [PATCH 0380/1097] Update omsBidAdapter.js (#12048) fixes https://github.com/prebid/Prebid.js/issues/12047 --- modules/omsBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index e6c8f8b098e..2fec1c1d7a9 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -136,7 +136,7 @@ function buildRequests(bidReqs, bidderRequest) { } function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + if (!bid.params || !bid.params.publisherId) { return false; } From 295fc5e058958ec8919b52e67fb7dbe87f991304 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 30 Jul 2024 10:50:48 -0400 Subject: [PATCH 0381/1097] GitHub Actions: Update jscpd.yml (#12045) Eases up on the detection a bit --- .github/workflows/jscpd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml index 315fbb2ff09..de5f1408dff 100644 --- a/.github/workflows/jscpd.yml +++ b/.github/workflows/jscpd.yml @@ -29,7 +29,7 @@ jobs: run: | echo '{ "threshold": 20, - "minTokens": 50, + "minTokens": 100, "reporters": [ "json" ], From cd13fc40277c07d293855c5685843623015cdeb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdravko=20Kosanovi=C4=87?= <41286499+zkosanovic@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:51:53 +0200 Subject: [PATCH 0382/1097] Rise Utils: Fix typo (#12058) --- libraries/riseUtils/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index abd70d357e0..25c7183d552 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -324,7 +324,7 @@ export function generateGeneralParams(generalObject, bidderRequest, adapterVersi if (bidderRequest && bidderRequest.refererInfo) { generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - generalParams.page_domain = deepAccess(bidderRequest, 'refererInfo.domain') || deepAccess(window, 'location.hostname'); + generalParams.site_domain = deepAccess(bidderRequest, 'refererInfo.domain') || deepAccess(window, 'location.hostname'); } return generalParams; From a103a2e1df4e1c0568c5cf6994b15b7e49bdd08d Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Tue, 30 Jul 2024 21:04:15 +0200 Subject: [PATCH 0383/1097] CORE: prevent unbound growth of suspendedTimeouts and possible NaN values (#12059) * prevent unbound growth of suspendedTimeouts and possible NaN values in timeOutOfFocus * Add tests * Reintroduce outOfFocusStart default to 0 --------- Co-authored-by: Demetrio Girardi --- src/utils/focusTimeout.js | 23 +++++++++---- src/utils/ttlCollection.js | 2 +- test/spec/unit/utils/focusTimeout_spec.js | 39 +++++++++++++++++++---- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/utils/focusTimeout.js b/src/utils/focusTimeout.js index 0ba66cc4efc..0c54bacec97 100644 --- a/src/utils/focusTimeout.js +++ b/src/utils/focusTimeout.js @@ -1,16 +1,27 @@ -let outOfFocusStart; +let outOfFocusStart = null; // enforce null otherwise it could be undefined and the callback wouldn't execute let timeOutOfFocus = 0; let suspendedTimeouts = []; -document.addEventListener('visibilitychange', () => { +function trackTimeOutOfFocus() { if (document.hidden) { outOfFocusStart = Date.now() } else { - timeOutOfFocus += Date.now() - outOfFocusStart - suspendedTimeouts.forEach(({ callback, startTime, setTimerId }) => setTimerId(setFocusTimeout(callback, timeOutOfFocus - startTime)())) + timeOutOfFocus += Date.now() - (outOfFocusStart ?? 0); // when the page is loaded in hidden state outOfFocusStart is undefined, which results in timeoutOffset being NaN outOfFocusStart = null; + suspendedTimeouts.forEach(({ callback, startTime, setTimerId }) => setTimerId(setFocusTimeout(callback, timeOutOfFocus - startTime)())); + suspendedTimeouts = []; } -}); +} + +document.addEventListener('visibilitychange', trackTimeOutOfFocus); + +export function reset() { + outOfFocusStart = null; + timeOutOfFocus = 0; + suspendedTimeouts = []; + document.removeEventListener('visibilitychange', trackTimeOutOfFocus); + document.addEventListener('visibilitychange', trackTimeOutOfFocus); +} /** * Wraps native setTimeout function in order to count time only when page is focused @@ -19,7 +30,7 @@ document.addEventListener('visibilitychange', () => { * @param {number} [milliseconds] - Minimum duration (in milliseconds) that the callback will be executed after * @returns {function(*): (number)} - Getter function for current timer id */ -export default function setFocusTimeout(callback, milliseconds) { +export function setFocusTimeout(callback, milliseconds) { const startTime = timeOutOfFocus; let timerId = setTimeout(() => { if (timeOutOfFocus === startTime && outOfFocusStart == null) { diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index b6e0a5198df..ffc11433d06 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -1,6 +1,6 @@ import {GreedyPromise} from './promise.js'; import {binarySearch, logError, timestamp} from '../utils.js'; -import setFocusTimeout from './focusTimeout.js'; +import {setFocusTimeout} from './focusTimeout.js'; /** * Create a set-like collection that automatically forgets items after a certain time. diff --git a/test/spec/unit/utils/focusTimeout_spec.js b/test/spec/unit/utils/focusTimeout_spec.js index ed7b1c0c2f3..3fcc4af18fe 100644 --- a/test/spec/unit/utils/focusTimeout_spec.js +++ b/test/spec/unit/utils/focusTimeout_spec.js @@ -1,4 +1,4 @@ -import setFocusTimeout from '../../../../src/utils/focusTimeout'; +import {setFocusTimeout, reset} from '../../../../src/utils/focusTimeout'; export const setDocumentHidden = (hidden) => { Object.defineProperty(document, 'hidden', { @@ -9,10 +9,12 @@ export const setDocumentHidden = (hidden) => { }; describe('focusTimeout', () => { - let clock; + let clock, callback; beforeEach(() => { + reset() clock = sinon.useFakeTimers(); + callback = sinon.spy(); }); afterEach(() => { @@ -20,14 +22,21 @@ describe('focusTimeout', () => { }) it('should invoke callback when page is visible', () => { - let callback = sinon.stub(); setFocusTimeout(callback, 2000); clock.tick(2000); expect(callback.called).to.be.true; }); + it('should not choke if page starts hidden', () => { + setDocumentHidden(true); + reset(); + setDocumentHidden(false); + setFocusTimeout(callback, 1000); + clock.tick(1000); + sinon.assert.called(callback); + }) + it('should not invoke callback if page was hidden', () => { - let callback = sinon.stub(); setFocusTimeout(callback, 2000); setDocumentHidden(true); clock.tick(3000); @@ -35,7 +44,6 @@ describe('focusTimeout', () => { }); it('should defer callback execution when page is hidden', () => { - let callback = sinon.stub(); setFocusTimeout(callback, 4000); clock.tick(2000); setDocumentHidden(true); @@ -46,8 +54,27 @@ describe('focusTimeout', () => { expect(callback.called).to.be.true; }); + it('should not execute deferred callbacks again', () => { + setDocumentHidden(true); + setFocusTimeout(callback, 1000); + clock.tick(2000); + [false, true, false].forEach(setDocumentHidden); + clock.tick(2000); + sinon.assert.calledOnce(callback); + }); + + it('should run callbacks that expire while page is hidden', () => { + setFocusTimeout(callback, 1000); + clock.tick(500); + setDocumentHidden(true); + clock.tick(1000); + setDocumentHidden(false); + sinon.assert.notCalled(callback); + clock.tick(1000); + sinon.assert.called(callback); + }) + it('should return updated timerId after page was showed again', () => { - let callback = sinon.stub(); const getCurrentTimerId = setFocusTimeout(callback, 4000); const oldTimerId = getCurrentTimerId(); clock.tick(2000); From d7f69fe414718175ebac29455899c974e014c94b Mon Sep 17 00:00:00 2001 From: Daria Boyko Date: Wed, 31 Jul 2024 14:11:33 +0300 Subject: [PATCH 0384/1097] admixerBidAdapter: fix bid floor (#12062) * Update README.md update * Add admixerwl alias for admixerBidAdapter. * fix bid floor on admixerBidAdapter * add spaces --------- Co-authored-by: AdmixerTech <35560933+AdmixerTech@users.noreply.github.com> Co-authored-by: AdmixerTech Co-authored-by: Yaroslav Masenko Co-authored-by: Daria Boyko --- modules/admixerBidAdapter.js | 17 ++++++++++++----- test/spec/modules/admixerBidAdapter_spec.js | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index e979bd07b51..4deeaee5206 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,4 +1,4 @@ -import {isStr, logError} from '../src/utils.js'; +import {isStr, logError, isFn, deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; @@ -71,15 +71,15 @@ export const spec = { if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } - let bidFloor = getBidFloor(bidderRequest); - if (bidFloor) { - payload.bidFloor = bidFloor; - } } validRequest.forEach((bid) => { let imp = {}; Object.keys(bid).forEach(key => imp[key] = bid[key]); imp.ortb2 && delete imp.ortb2; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + imp.bidFloor = bidFloor; + } payload.imps.push(imp); }); @@ -117,10 +117,16 @@ export const spec = { return pixels; } }; + function getEndpointUrl(code) { return find(ALIASES, (val) => val.code === code)?.endpoint || ENDPOINT_URL; } + function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + try { const bidFloor = bid.getFloor({ currency: 'USD', @@ -132,4 +138,5 @@ function getBidFloor(bid) { return 0; } } + registerBidder(spec); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index e53457e03c4..1da6a58bea3 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -226,12 +226,12 @@ describe('AdmixerAdapter', function () { }, }; it('gets floor', function () { - bidderRequest.getFloor = () => { + validRequest[0].getFloor = () => { return { floor: 0.6 }; }; const request = spec.buildRequests(validRequest, bidderRequest); const payload = request.data; - expect(payload.bidFloor).to.deep.equal(0.6); + expect(payload.imps[0].bidFloor).to.deep.equal(0.6); }); }); From aa4d4307dadc1e8a6a8934a022630de3a6066f09 Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:25:30 -0400 Subject: [PATCH 0385/1097] Contxtful RTD Provider: Pass module config (#12034) * feat: pass prebid config * doc: config * fix: better event registration * fix: tagId check --- modules/contxtfulRtdProvider.js | 27 +++++++------ .../spec/modules/contxtfulRtdProvider_spec.js | 38 +++++++++---------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 03050a6a64f..d4bcd94ff4a 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -101,8 +101,7 @@ function init(config) { rxApi = null; try { - const { version, customer, hostname } = extractParameters(config); - initCustomer(version, customer, hostname); + initCustomer(config); return true; } catch (error) { logError(MODULE, error); @@ -137,35 +136,39 @@ export function extractParameters(config) { /** * Initialize sub module for a customer. * This will load the external resources for the sub module. - * @param { String } version - * @param { String } customer - * @param { String } hostname + * @param { String } config */ -function initCustomer(version, customer, hostname) { +function initCustomer(config) { + const { version, customer, hostname } = extractParameters(config); const CONNECTOR_URL = buildUrl({ protocol: 'https', host: hostname, pathname: `/${version}/prebid/${customer}/connector/rxConnector.js`, }); - const externalScript = loadExternalScript(CONNECTOR_URL, MODULE_NAME); - addExternalScriptEventListener(externalScript, customer); + addConnectorEventListener(customer, config); + loadExternalScript(CONNECTOR_URL, MODULE_NAME); } /** * Add event listener to the script tag for the expected events from the external script. - * @param { HTMLScriptElement } script + * @param { String } tagId + * @param { String } prebidConfig */ -function addExternalScriptEventListener(script, tagId) { - script.addEventListener( +function addConnectorEventListener(tagId, prebidConfig) { + window.addEventListener( 'rxConnectorIsReady', - async ({ detail: rxConnector }) => { + async ({ detail: { [tagId]: rxConnector } }) => { + if (!rxConnector) { + return; + } // Fetch the customer configuration const { rxApiBuilder, fetchConfig } = rxConnector; let config = await fetchConfig(tagId); if (!config) { return; } + config['prebid'] = prebidConfig || {}; rxApi = await rxApiBuilder(config); } ); diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index 5dda23caafc..ad79b051393 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -17,7 +17,7 @@ const RX_CONNECTOR_MOCK = { }; const TIMEOUT = 10; -const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: RX_CONNECTOR_MOCK }); +const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: {[CUSTOMER]: RX_CONNECTOR_MOCK}, bubbles: true }); function writeToStorage(requester, timeDiff) { let rx = RX_FROM_SESSION_STORAGE; @@ -157,30 +157,30 @@ describe('contxtfulRtdProvider', function () { }); describe('init', function () { - it('gets the RX API returned by an external script', (done) => { + it('uses the RX API to get receptivity', (done) => { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); setTimeout(() => { contxtfulSubmodule.getTargetingData(['ad-slot'], config); - expect(RX_CONNECTOR_MOCK.fetchConfig.callCount, 'fetchConfig').to.be.equal(1); - expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount, 'rxApiBuilder').to.be.equal(1); + expect(RX_API_MOCK.receptivity.callCount, 'receptivity 42').to.be.equal(1); + expect(RX_API_MOCK.receptivity.firstCall.returnValue, 'receptivity').to.be.equal(RX_FROM_API); done(); }, TIMEOUT); }); }); describe('init', function () { - it('uses the RX API to get receptivity', (done) => { + it('gets the RX API returned by an external script', (done) => { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); setTimeout(() => { contxtfulSubmodule.getTargetingData(['ad-slot'], config); - expect(RX_API_MOCK.receptivity.callCount, 'receptivity 42').to.be.equal(1); - expect(RX_API_MOCK.receptivity.firstCall.returnValue, 'receptivity').to.be.equal(RX_FROM_API); + expect(RX_CONNECTOR_MOCK.fetchConfig.callCount, 'fetchConfig').at.least(1); + expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount, 'rxApiBuilder').at.least(1); done(); }, TIMEOUT); }); @@ -245,7 +245,7 @@ describe('contxtfulRtdProvider', function () { it('adds receptivity to the ad units using the RX API', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); setTimeout(() => { let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); @@ -278,7 +278,7 @@ describe('contxtfulRtdProvider', function () { let config = buildInitConfig(VERSION, CUSTOMER); config.params.adServerTargeting = false; contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); setTimeout(() => { let _ = contxtfulSubmodule.getTargetingData(adUnits, config); @@ -291,7 +291,7 @@ describe('contxtfulRtdProvider', function () { let config = buildInitConfig(VERSION, CUSTOMER); config.params.adServerTargeting = false; contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); setTimeout(() => { let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); @@ -375,7 +375,7 @@ describe('contxtfulRtdProvider', function () { describe('getBidRequestData', function () { it('calls once the onDone callback', function (done) { contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); let reqBidsConfigObj = { ortb2Fragments: { @@ -397,7 +397,7 @@ describe('contxtfulRtdProvider', function () { it('does not write receptivity to the global OpenRTB 2 fragment', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); let reqBidsConfigObj = { ortb2Fragments: { @@ -419,7 +419,7 @@ describe('contxtfulRtdProvider', function () { it('writes receptivity to the configured bidder OpenRTB 2 fragments', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); let reqBidsConfigObj = { ortb2Fragments: { @@ -505,7 +505,7 @@ describe('contxtfulRtdProvider', function () { it('uses the RX API', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); let reqBidsConfigObj = { ortb2Fragments: { @@ -515,8 +515,8 @@ describe('contxtfulRtdProvider', function () { }; setTimeout(() => { - expect(RX_CONNECTOR_MOCK.fetchConfig.callCount).to.equal(1); - expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount).to.equal(1); + expect(RX_CONNECTOR_MOCK.fetchConfig.callCount).at.least(1); + expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount).at.least(1); const onDoneSpy = sinon.spy(); contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); expect(onDoneSpy.callCount).to.equal(1); @@ -530,7 +530,7 @@ describe('contxtfulRtdProvider', function () { it('adds receptivity to the reqBidsConfigObj', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); - loadExternalScriptTag.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); let reqBidsConfigObj = { ortb2Fragments: { From b98353b46ba291c97d443f655c8307fef7c16fc5 Mon Sep 17 00:00:00 2001 From: Sajid Mahmood Date: Wed, 31 Jul 2024 07:33:02 -0400 Subject: [PATCH 0386/1097] IX Bid Adapter: propagate atype in uids (#12050) Co-authored-by: Sajid Mahmood --- modules/ixBidAdapter.js | 1 - test/spec/modules/ixBidAdapter_spec.js | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index e182634b52a..776af4ab067 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -659,7 +659,6 @@ function getEidInfo(allEids) { rtiPartner: SOURCE_RTI_MAPPING[eid.source] }; } - delete eid.uids[0].atype; toSend.push(eid); if (toSend.length >= MAX_EID_SOURCES) { break; diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 42c0c2afdf5..47583031982 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -856,7 +856,8 @@ describe('IndexexchangeAdapter', function () { source: 'identityinc.com', uids: [ { - id: 'identityid' + id: 'identityid', + atype: 1 } ] } @@ -1383,8 +1384,14 @@ describe('IndexexchangeAdapter', function () { it('identity data in impression should have correct format and value (single identity partner)', function () { const impression = payload.user.eids; + expect(impression).to.be.an('array'); + expect(impression).to.have.lengthOf(1); expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); + expect(impression[0].uids).to.be.an('array'); + expect(impression[0].uids).to.have.lengthOf(1); expect(impression[0].uids[0].id).to.equal(testCopy.IdentityIp.data.uids[0].id); + expect(impression[0].uids[0].atype).to.exist; + expect(impression[0].uids[0].atype).to.equal(testCopy.IdentityIp.data.uids[0].atype); }); }); From 7289e2bad24045b5c63176fc78266467adb5ceaa Mon Sep 17 00:00:00 2001 From: Phaneendra Hegde Date: Wed, 31 Jul 2024 19:12:19 +0530 Subject: [PATCH 0387/1097] Pubxai Analytics Adapter: add additional event listener to collect bidRejected data (#12063) * send BidRejected Events to capture floored bids * fix tests * send pubx_id as query param --------- Co-authored-by: tej656 --- modules/pubxaiAnalyticsAdapter.js | 13 +++++++++---- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index afa8b01bfca..f8ec72cde75 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -157,15 +157,19 @@ const track = ({ eventType, args }) => { // handle invalid bids, and remove them from the adUnit cache case EVENTS.BID_TIMEOUT: args.map(extractBid).forEach((bid) => { - bid.renderStatus = 3; + bid.bidType = 3; auctionCache[bid.auctionId].bids.push(bid); }); break; // handle valid bid responses and record them as part of an auction case EVENTS.BID_RESPONSE: - const bid = Object.assign(extractBid(args), { renderStatus: 2 }); + const bid = Object.assign(extractBid(args), { bidType: 2 }); auctionCache[bid.auctionId].bids.push(bid); break; + case EVENTS.BID_REJECTED: + const rejectedBid = Object.assign(extractBid(args), { bidType: 1 }); + auctionCache[rejectedBid.auctionId].bids.push(rejectedBid); + break; // capture extra information from the auction, and if there were no bids // (and so no chance of a win) send the auction case EVENTS.AUCTION_END: @@ -183,7 +187,7 @@ const track = ({ eventType, args }) => { timestamp: args.timestamp, }); if ( - auctionCache[args.auctionId].bids.every((bid) => bid.renderStatus === 3) + auctionCache[args.auctionId].bids.every((bid) => bid.bidType === 3) ) { prepareSend(args.auctionId); } @@ -201,7 +205,7 @@ const track = ({ eventType, args }) => { isFloorSkipped: floorDetail?.skipped || false, isWinningBid: true, renderedSize: args.size, - renderStatus: 4, + bidType: 4, }); winningBid.adServerData = getAdServerDataForBid(winningBid); auctionCache[winningBid.auctionId].winningBid = winningBid; @@ -283,6 +287,7 @@ const prepareSend = (auctionId) => { auctionTimestamp: auctionData.auctionDetail.timestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, prebidVersion: '$prebid.version$', + pubxId: initOptions.pubxId, }, }); sendCache[pubxaiAnalyticsRequestUrl].push(data); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 237a2d32d54..abc52b00439 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -31,9 +31,10 @@ describe('pubxai analytics adapter', () => { }); describe('track', () => { + const pubxId = '6c415fc0-8b0e-4cf5-be73-01526a4db625'; let initOptions = { samplingRate: '1', - pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625', + pubxId: pubxId, }; let originalVS; @@ -148,7 +149,7 @@ describe('pubxai analytics adapter', () => { timeout: 1000, config: { samplingRate: '1', - pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625', + pubxId: pubxId, }, }, bidRequested: { @@ -529,7 +530,7 @@ describe('pubxai analytics adapter', () => { null, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', sizes: '300x250', - renderStatus: 2, + bidType: 2, requestTimestamp: 1616654312804, creativeId: 96846035, currency: 'USD', @@ -651,7 +652,7 @@ describe('pubxai analytics adapter', () => { placementId: 13144370, renderedSize: '300x250', sizes: '300x250', - renderStatus: 4, + bidType: 4, responseTimestamp: 1616654313071, requestTimestamp: 1616654312804, status: 'rendered', @@ -764,6 +765,7 @@ describe('pubxai analytics adapter', () => { auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', prebidVersion: '$prebid.version$', + pubxId: pubxId, }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ @@ -807,6 +809,7 @@ describe('pubxai analytics adapter', () => { auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', prebidVersion: '$prebid.version$', + pubxId: pubxId, }); // Step 9: check that the data sent in the request is correct @@ -932,6 +935,7 @@ describe('pubxai analytics adapter', () => { auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', prebidVersion: '$prebid.version$', + pubxId: pubxId, }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ @@ -1048,6 +1052,7 @@ describe('pubxai analytics adapter', () => { auctionTimestamp: '1616654312804', pubxaiAnalyticsVersion: 'v2.0.0', prebidVersion: '$prebid.version$', + pubxId: pubxId, }); expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ From be3744030296b9b5caedb94ce1904e8f242280cf Mon Sep 17 00:00:00 2001 From: Oleksandr Yermakov <47504677+sanychtasher@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:34:22 +0300 Subject: [PATCH 0388/1097] mgid bid adapters: refactoring for trimmer code (#12057) * added consts for values * added copy method * moved extractDomainFromHost * added method triggerNurlWithCpm * added getUserSyncs * less formatting mgidBid * less formatting undertoneBid * less formatting utils * renamed adm to nativeAdm * back changes for other adaptes * back changes * moved getUserSyncs to lib pkg * less changes * fixed errors * less changes * fixed errors * fixed tests --------- Co-authored-by: Oleksandr Yermakov --- libraries/mgidUtils/mgidUtils.js | 73 ++++++++++++++++ modules/mgidBidAdapter.js | 137 +++++++------------------------ modules/mgidXBidAdapter.js | 68 +-------------- modules/redtramBidAdapter.js | 8 +- modules/undertoneBidAdapter.js | 20 +---- src/utils.js | 28 +++++++ 6 files changed, 135 insertions(+), 199 deletions(-) create mode 100644 libraries/mgidUtils/mgidUtils.js diff --git a/libraries/mgidUtils/mgidUtils.js b/libraries/mgidUtils/mgidUtils.js new file mode 100644 index 00000000000..9ac84e231b7 --- /dev/null +++ b/libraries/mgidUtils/mgidUtils.js @@ -0,0 +1,73 @@ +import { + isPlainObject, + isArray, + isStr, + isNumber, +} from '../../src/utils.js'; +import { config } from '../../src/config.js'; +import { USERSYNC_DEFAULT_CONFIG } from '../../src/userSync.js'; + +const PIXEL_SYNC_URL = 'https://cm.mgid.com/i.gif'; +const IFRAME_SYNC_URL = 'https://cm.mgid.com/i.html'; + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const spb = isPlainObject(config.getConfig('userSync')) && + isNumber(config.getConfig('userSync').syncsPerBidder) + ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; + + if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + let pixels = []; + if (serverResponses && + isArray(serverResponses) && + serverResponses.length > 0 && + isPlainObject(serverResponses[0].body) && + isPlainObject(serverResponses[0].body.ext) && + isArray(serverResponses[0].body.ext.cm) && + serverResponses[0].body.ext.cm.length > 0) { + pixels = serverResponses[0].body.ext.cm; + } + + const syncs = []; + const query = []; + query.push('cbuster={cbuster}'); + query.push('gdpr_consent=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); + if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + query.push('gdpr=1'); + } else { + query.push('gdpr=0'); + } + if (isPlainObject(uspConsent) && uspConsent?.consentString) { + query.push(`us_privacy=${encodeURIComponent(uspConsent?.consentString)}`); + } + if (isPlainObject(gppConsent) && gppConsent?.gppString) { + query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); + } + if (config.getConfig('coppa')) { + query.push('coppa=1') + } + const q = query.join('&') + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: IFRAME_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) + }); + } else if (syncOptions.pixelEnabled) { + if (pixels.length === 0) { + for (let i = 0; i < spb; i++) { + syncs.push({ + type: 'image', + url: PIXEL_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) // randomly selects partner if sync required + }); + } + } else { + for (let i = 0; i < spb && i < pixels.length; i++) { + syncs.push({ + type: 'image', + url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + q.replace('{cbuster}', Math.round(new Date().getTime())) + }); + } + } + } + return syncs; + } +} diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 5cfef325d2f..70a9290aa4d 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -8,10 +8,12 @@ import { parseUrl, isEmpty, triggerPixel, + triggerNurlWithCpm, logWarn, isFn, isNumber, isBoolean, + extractDomainFromHost, isInteger, deepSetValue, getBidIdParameter, setOnAny } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -19,7 +21,7 @@ import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {USERSYNC_DEFAULT_CONFIG} from '../src/userSync.js'; +import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -231,7 +233,7 @@ export const spec = { const page = deepAccess(bidderRequest, 'refererInfo.page') || info.location if (!isStr(deepAccess(request.site, 'domain'))) { const hostname = parseUrl(page).hostname; - request.site.domain = extractDomainFromHost(hostname) || hostname + request.site.domain = extractDomainFromHostExceptLocalhost(hostname) || hostname } if (!isStr(deepAccess(request.site, 'page'))) { request.site.page = page @@ -344,13 +346,7 @@ export const spec = { }, onBidWon: (bid) => { const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (isStr(bid.nurl) && bid.nurl !== '') { - bid.nurl = bid.nurl.replace( - /\${AUCTION_PRICE}/, - cpm - ); - triggerPixel(bid.nurl); - } + triggerNurlWithCpm(bid, cpm) if (bid.isBurl) { if (bid.mediaType === BANNER) { bid.ad = bid.ad.replace( @@ -367,68 +363,7 @@ export const spec = { } logInfo(LOG_INFO_PREFIX + `onBidWon`); }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - logInfo(LOG_INFO_PREFIX + `getUserSyncs`); - const spb = isPlainObject(config.getConfig('userSync')) && - isNumber(config.getConfig('userSync').syncsPerBidder) - ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; - - if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - let pixels = []; - if (serverResponses && - isArray(serverResponses) && - serverResponses.length > 0 && - isPlainObject(serverResponses[0].body) && - isPlainObject(serverResponses[0].body.ext) && - isArray(serverResponses[0].body.ext.cm) && - serverResponses[0].body.ext.cm.length > 0) { - pixels = serverResponses[0].body.ext.cm; - } - - const syncs = []; - const query = []; - query.push('cbuster={cbuster}'); - query.push('gdpr_consent=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); - if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { - query.push('gdpr=1'); - } else { - query.push('gdpr=0'); - } - if (isPlainObject(uspConsent) && uspConsent?.consentString) { - query.push(`us_privacy=${encodeURIComponent(uspConsent?.consentString)}`); - } - if (isPlainObject(gppConsent) && gppConsent?.gppString) { - query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); - } - if (config.getConfig('coppa')) { - query.push('coppa=1') - } - const q = query.join('&') - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: 'https://cm.mgid.com/i.html?' + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } else if (syncOptions.pixelEnabled) { - if (pixels.length === 0) { - for (let i = 0; i < spb; i++) { - syncs.push({ - type: 'image', - url: 'https://cm.mgid.com/i.gif?' + q.replace('{cbuster}', Math.round(new Date().getTime())) // randomly selects partner if sync required - }); - } - } else { - for (let i = 0; i < spb && i < pixels.length; i++) { - syncs.push({ - type: 'image', - url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } - } - } - return syncs; - } - } + getUserSyncs: getUserSyncs, }; registerBidder(spec); @@ -479,25 +414,11 @@ function setMediaType(bid, newBid) { } } -function extractDomainFromHost(pageHost) { +function extractDomainFromHostExceptLocalhost(pageHost) { if (pageHost === 'localhost') { return 'localhost' } - let domain = null; - try { - let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); - if (domains != null && domains.length > 0) { - domain = domains[0]; - for (let i = 1; i < domains.length; i++) { - if (domains[i].length > domain.length) { - domain = domains[i]; - } - } - } - } catch (e) { - domain = null; - } - return domain; + return extractDomainFromHost(pageHost) } function getLanguage() { @@ -675,33 +596,25 @@ function commonNativeRequestObject(nativeAsset, params) { function parseNativeResponse(bid, newBid) { newBid.native = {}; if (bid.hasOwnProperty('adm')) { - let adm = ''; + let nativeAdm = ''; try { - adm = JSON.parse(bid.adm); + nativeAdm = JSON.parse(bid.adm); } catch (ex) { logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native response for ad response: ' + newBid.adm); return; } - if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { + if (nativeAdm && nativeAdm.native && nativeAdm.native.assets && nativeAdm.native.assets.length > 0) { newBid.mediaType = NATIVE; - for (let i = 0, len = adm.native.assets.length; i < len; i++) { - switch (adm.native.assets[i].id) { + for (let i = 0, len = nativeAdm.native.assets.length; i < len; i++) { + switch (nativeAdm.native.assets[i].id) { case NATIVE_ASSETS.TITLE.ID: - newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; + newBid.native.title = nativeAdm.native.assets[i].title && nativeAdm.native.assets[i].title.text; break; case NATIVE_ASSETS.IMAGE.ID: - newBid.native.image = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; + newBid.native.image = copyFromAdmAsset(nativeAdm.native.assets[i]); break; case NATIVE_ASSETS.ICON.ID: - newBid.native.icon = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; + newBid.native.icon = copyFromAdmAsset(nativeAdm.native.assets[i]); break; case NATIVE_ASSETS.SPONSOREDBY.ID: case NATIVE_ASSETS.SPONSORED.ID: @@ -711,14 +624,14 @@ function parseNativeResponse(bid, newBid) { case NATIVE_ASSETS.BODY.ID: case NATIVE_ASSETS.DISPLAYURL.ID: case NATIVE_ASSETS.CTA.ID: - newBid.native[spec.NATIVE_ASSET_ID_TO_KEY_MAP[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; + newBid.native[spec.NATIVE_ASSET_ID_TO_KEY_MAP[nativeAdm.native.assets[i].id]] = nativeAdm.native.assets[i].data && nativeAdm.native.assets[i].data.value; break; } } - newBid.native.clickUrl = adm.native.link && adm.native.link.url; - newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; - newBid.native.impressionTrackers = adm.native.imptrackers || []; - newBid.native.jstracker = adm.native.jstracker || []; + newBid.native.clickUrl = nativeAdm.native.link && nativeAdm.native.link.url; + newBid.native.clickTrackers = (nativeAdm.native.link && nativeAdm.native.link.clicktrackers) || []; + newBid.native.impressionTrackers = nativeAdm.native.imptrackers || []; + newBid.native.jstracker = nativeAdm.native.jstracker || []; newBid.width = 0; newBid.height = 0; } @@ -778,3 +691,11 @@ function getBidFloor(bid, cur) { } return {floor: bidFloor, cur: cur} } + +function copyFromAdmAsset(asset) { + return { + url: asset.img && asset.img.url, + height: asset.img && asset.img.h, + width: asset.img && asset.img.w, + } +} diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index 471e8fb2754..48648389bcf 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -1,15 +1,11 @@ -import { isPlainObject, isNumber, isArray, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { USERSYNC_DEFAULT_CONFIG } from '../src/userSync.js'; import { isBidRequestValid, buildRequestsBase, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; +import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js' const BIDDER_CODE = 'mgidX'; const GVLID = 358; const AD_URL = 'https://#{REGION}#.mgid.com/pbjs'; -const PIXEL_SYNC_URL = 'https://cm.mgid.com/i.gif'; -const IFRAME_SYNC_URL = 'https://cm.mgid.com/i.html'; const buildRequests = (validBidRequests = [], bidderRequest = {}) => { const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest }); @@ -33,67 +29,7 @@ export const spec = { buildRequests, interpretResponse, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - const spb = isPlainObject(config.getConfig('userSync')) && - isNumber(config.getConfig('userSync').syncsPerBidder) - ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; - - if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - let pixels = []; - if (serverResponses && - isArray(serverResponses) && - serverResponses.length > 0 && - isPlainObject(serverResponses[0].body) && - isPlainObject(serverResponses[0].body.ext) && - isArray(serverResponses[0].body.ext.cm) && - serverResponses[0].body.ext.cm.length > 0) { - pixels = serverResponses[0].body.ext.cm; - } - - const syncs = []; - const query = []; - query.push('cbuster={cbuster}'); - query.push('gdpr_consent=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); - if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { - query.push('gdpr=1'); - } else { - query.push('gdpr=0'); - } - if (isPlainObject(uspConsent) && uspConsent?.consentString) { - query.push(`us_privacy=${encodeURIComponent(uspConsent?.consentString)}`); - } - if (isPlainObject(gppConsent) && gppConsent?.gppString) { - query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); - } - if (config.getConfig('coppa')) { - query.push('coppa=1') - } - const q = query.join('&') - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: IFRAME_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } else if (syncOptions.pixelEnabled) { - if (pixels.length === 0) { - for (let i = 0; i < spb; i++) { - syncs.push({ - type: 'image', - url: PIXEL_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) // randomly selects partner if sync required - }); - } - } else { - for (let i = 0; i < spb && i < pixels.length; i++) { - syncs.push({ - type: 'image', - url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } - } - } - return syncs; - } - } + getUserSyncs: getUserSyncs, }; registerBidder(spec); diff --git a/modules/redtramBidAdapter.js b/modules/redtramBidAdapter.js index e1dc0e2a148..726b2d53f1c 100644 --- a/modules/redtramBidAdapter.js +++ b/modules/redtramBidAdapter.js @@ -1,9 +1,8 @@ import { isFn, - isStr, deepAccess, getWindowTop, - triggerPixel + triggerNurlWithCpm } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; @@ -145,10 +144,7 @@ export const spec = { onBidWon: (bid) => { const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (isStr(bid.nurl) && bid.nurl !== '') { - bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); - triggerPixel(bid.nurl); - } + triggerNurlWithCpm(bid, cpm) } }; diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index c7e8102ffc9..1ea2f2c1ce6 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,7 +2,7 @@ * Adapter to send bids to Undertone */ -import {deepAccess, parseUrl} from '../src/utils.js'; +import {deepAccess, parseUrl, extractDomainFromHost} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -26,24 +26,6 @@ function getBidFloor(bidRequest, mediaType) { return (floor && floor.currency === 'USD' && floor.floor) || 0; } -function extractDomainFromHost(pageHost) { - let domain = null; - try { - let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); - if (domains != null && domains.length > 0) { - domain = domains[0]; - for (let i = 1; i < domains.length; i++) { - if (domains[i].length > domain.length) { - domain = domains[i]; - } - } - } - } catch (e) { - domain = null; - } - return domain; -} - function getGdprQueryParams(gdprConsent) { if (!gdprConsent) { return null; diff --git a/src/utils.js b/src/utils.js index ec227c6d74b..64880b4a462 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1258,3 +1258,31 @@ export function setOnAny(collection, key) { } return undefined; } + +export function extractDomainFromHost(pageHost) { + let domain = null; + try { + let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); + if (domains != null && domains.length > 0) { + domain = domains[0]; + for (let i = 1; i < domains.length; i++) { + if (domains[i].length > domain.length) { + domain = domains[i]; + } + } + } + } catch (e) { + domain = null; + } + return domain; +} + +export function triggerNurlWithCpm(bid, cpm) { + if (isStr(bid.nurl) && bid.nurl !== '') { + bid.nurl = bid.nurl.replace( + /\${AUCTION_PRICE}/, + cpm + ); + triggerPixel(bid.nurl); + } +} From cbec10c836f83267149ef89331c5672e7a9fb12e Mon Sep 17 00:00:00 2001 From: Pubrise Date: Wed, 31 Jul 2024 20:29:53 +0300 Subject: [PATCH 0389/1097] new adapter (#12067) --- modules/pubriseBidAdapter.js | 19 + modules/pubriseBidAdapter.md | 79 +++ test/spec/modules/pubriseBidAdapter_spec.js | 514 ++++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 modules/pubriseBidAdapter.js create mode 100755 modules/pubriseBidAdapter.md create mode 100644 test/spec/modules/pubriseBidAdapter_spec.js diff --git a/modules/pubriseBidAdapter.js b/modules/pubriseBidAdapter.js new file mode 100644 index 00000000000..646546329db --- /dev/null +++ b/modules/pubriseBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'pubrise'; +const AD_URL = 'https://backend.pubrise.ai/pbjs'; +const SYNC_URL = 'https://sync.pubrise.ai'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/pubriseBidAdapter.md b/modules/pubriseBidAdapter.md new file mode 100755 index 00000000000..4c130954392 --- /dev/null +++ b/modules/pubriseBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Pubrise Bidder Adapter +Module Type: Pubrise Bidder Adapter +Maintainer: prebid@pubrise.ai +``` + +# Description + +Connects to Pubrise exchange for bids. +Pubrise bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'pubrise', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'pubrise', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'pubrise', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js new file mode 100644 index 00000000000..6374c4f1b7f --- /dev/null +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -0,0 +1,514 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/pubriseBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'pubrise'; + +describe('PubriseBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://backend.pubrise.ai/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.pubrise.ai/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.pubrise.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sync.pubrise.ai/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); From 2f43ed6e85d19f2efbe9bc31189df5ed7c75fe47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Millet?= Date: Wed, 31 Jul 2024 19:35:17 +0200 Subject: [PATCH 0390/1097] Dailymotion bid adapter: add player name (#12068) This new parameter allows the Prebid integration to send which player will be displaying the video and the ad. This value is then processed server-side to validate integration and distinguish the usage of video metadata. As we are now able to differentiate which player is used, we also deprecate the Dailymotion-only `xid` parameter in favor of the standard `video.id` one. --- modules/dailymotionBidAdapter.js | 2 +- modules/dailymotionBidAdapter.md | 18 ++++++++------ .../modules/dailymotionBidAdapter_spec.js | 24 +++++++++---------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 1e40d72f780..eccdbbbe982 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -64,7 +64,6 @@ function getVideoMetadata(bidRequest, bidderRequest) { title: videoParams.title || deepAccess(contentObj, 'title', ''), url: videoParams.url || deepAccess(contentObj, 'url', ''), topics: videoParams.topics || '', - xid: videoParams.xid || '', isCreatedForKids: typeof videoParams.isCreatedForKids === 'boolean' ? videoParams.isCreatedForKids : null, @@ -79,6 +78,7 @@ function getVideoMetadata(bidRequest, bidderRequest) { autoplay: typeof videoParams.autoplay === 'boolean' ? videoParams.autoplay : null, + playerName: videoParams.playerName || deepAccess(contentObj, 'playerName', ''), playerVolume: ( typeof videoParams.playerVolume === 'number' && videoParams.playerVolume >= 0 && diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 7ff3fd47d74..21cc6c49205 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -88,7 +88,7 @@ Please note that failing to set these will result in the adapter not bidding at To allow better targeting, you should provide as much context about the video as possible. There are three ways of doing this depending on if you're using Dailymotion player or a third party one. -If you are using the Dailymotion player, you should only provide the video `xid` in your ad unit, example: +If you are using the Dailymotion player, you must provide the video `xid` in the `video.id` field of your ad unit, example: ```javascript const adUnits = [ @@ -98,7 +98,10 @@ const adUnits = [ params: { apiKey: 'dailymotion-testing', video: { - xid: 'x123456' // Dailymotion infrastructure unique video ID + id: 'x123456' // Dailymotion infrastructure unique video ID + autoplay: false, + playerName: 'dailymotion', + playerVolume: 8 }, } }], @@ -117,9 +120,9 @@ const adUnits = [ ``` This will automatically fetch the most up-to-date information about the video. -If you provide any other metadata in addition to the `xid`, they will be ignored. +Please note that if you provide any video metadata not listed above, they will be replaced by the ones fetched from the `video.id`. -If you are using a third party video player, you should not provide any `xid` and instead fill the following members: +If you are using a third party video player, you should fill the following members: ```javascript const adUnits = [ @@ -143,6 +146,7 @@ const adUnits = [ isCreatedForKids: false, videoViewsInSession: 1, autoplay: false, + playerName: 'video.js', playerVolume: 8 } } @@ -181,14 +185,14 @@ Each of the following video metadata fields can be added in bids.params.video. * `title` - Video title * `url` - URL of the content * `topics` - Main topics for the video, comma separated -* `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) * `isCreatedForKids` - [The content is created for children as primary audience](https://faq.dailymotion.com/hc/en-us/articles/360020920159-Content-created-for-kids) -The following contextual informations can also be added in bids.params.video. +The following contextual information can also be added in bids.params.video. -* `videoViewsInSession` - Number of videos viewed within the current user session * `autoplay` - Playback was launched without user interaction +* `playerName` - Name of the player used to display the video * `playerVolume` - Player volume between 0 (muted, 0%) and 10 (100%) +* `videoViewsInSession` - Number of videos viewed within the current user session If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will collect the following values and fallback to bids.params.video values when applicable. See the mapping below. diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 74e94d2444e..750bf340261 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -116,11 +116,11 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, isCreatedForKids: true, videoViewsInSession: 2, autoplay: true, + playerName: 'dailymotion', playerVolume: 8, }, }, @@ -204,7 +204,6 @@ describe('dailymotionBidAdapterTests', () => { title: bidRequestData[0].params.video.title, url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, - xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidRequestData[0].params.video.livestream, isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, @@ -212,6 +211,7 @@ describe('dailymotionBidAdapterTests', () => { siteOrAppCat: [], videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, autoplay: bidRequestData[0].params.video.autoplay, + playerName: bidRequestData[0].params.video.playerName, playerVolume: bidRequestData[0].params.video.playerVolume, }, }); @@ -254,11 +254,11 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, isCreatedForKids: true, videoViewsInSession: 2, autoplay: true, + playerName: 'dailymotion', playerVolume: 8, }, }, @@ -344,11 +344,11 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, isCreatedForKids: true, videoViewsInSession: 2, autoplay: true, + playerName: 'dailymotion', playerVolume: 8, }, }, @@ -431,11 +431,11 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, isCreatedForKids: true, videoViewsInSession: 2, autoplay: true, + playerName: 'dailymotion', playerVolume: 8, }, }, @@ -537,11 +537,11 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, isCreatedForKids: true, videoViewsInSession: 2, autoplay: true, + playerName: 'dailymotion', playerVolume: 8, }, }, @@ -645,11 +645,11 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, isCreatedForKids: true, videoViewsInSession: 2, autoplay: true, + playerName: 'dailymotion', playerVolume: 8, }, }, @@ -745,12 +745,12 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, // Test invalid values isCreatedForKids: 'false', videoViewsInSession: -1, autoplay: 'true', + playerName: 'dailymotion', playerVolume: 12, }, }, @@ -855,7 +855,6 @@ describe('dailymotionBidAdapterTests', () => { title: bidRequestData[0].params.video.title, url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, - xid: bidRequestData[0].params.video.xid, // Overriden through bidder params duration: bidderRequestData.ortb2.app.content.len, livestream: !!bidRequestData[0].params.video.livestream, @@ -864,6 +863,7 @@ describe('dailymotionBidAdapterTests', () => { siteOrAppCat: [], videoViewsInSession: null, autoplay: null, + playerName: 'dailymotion', playerVolume: null, }, }); @@ -889,10 +889,10 @@ describe('dailymotionBidAdapterTests', () => { private: false, title: 'test video', topics: 'topic_1, topic_2', - xid: 'x123456', isCreatedForKids: false, videoViewsInSession: 10, autoplay: false, + playerName: 'dailymotion', playerVolume: 0, }, }, @@ -1035,7 +1035,6 @@ describe('dailymotionBidAdapterTests', () => { title: bidderRequestData.ortb2.site.content.title, url: bidderRequestData.ortb2.site.content.url, topics: bidRequestData[0].params.video.topics, - xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidderRequestData.ortb2.site.content.livestream, isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, @@ -1043,6 +1042,7 @@ describe('dailymotionBidAdapterTests', () => { siteOrAppCat: bidderRequestData.ortb2.site.content.cat, videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, autoplay: bidRequestData[0].params.video.autoplay, + playerName: bidRequestData[0].params.video.playerName, playerVolume: bidRequestData[0].params.video.playerVolume, }, }); @@ -1127,13 +1127,13 @@ describe('dailymotionBidAdapterTests', () => { title: '', url: '', topics: '', - xid: '', livestream: false, isCreatedForKids: null, context: { siteOrAppCat: [], videoViewsInSession: null, autoplay: null, + playerName: '', playerVolume: null, }, }); From 62e6d89173b535f05f864f0d3252f9948cea79c4 Mon Sep 17 00:00:00 2001 From: Milica <110067445+GMilica@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:35:58 +0200 Subject: [PATCH 0391/1097] Update cwire adapter for new inventory management (#12066) --- modules/cwireBidAdapter.js | 22 ++++++++++++--------- modules/cwireBidAdapter.md | 39 ++++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index 191ff7758da..6fbe401bfde 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -151,14 +151,18 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { - logError('placementId not provided or not a number'); - return false; - } + if (!bid.params?.domainId || !isNumber(bid.params.domainId)) { + logError('domainId not provided or not a number'); + if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or not a number'); + return false; + } - if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { - logError('pageId not provided or not a number'); - return false; + if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided or not a number'); + return false; + } + return true; } return true; }, @@ -176,8 +180,8 @@ export const spec = { // process bid requests let processed = validBidRequests .map(bid => slotDimensions(bid)) - // Flattens the pageId and placement Id for backwards compatibility. - .map((bid) => ({...bid, pageId: bid.params?.pageId, placementId: bid.params?.placementId})); + // Flattens the pageId, domainId and placement Id for backwards compatibility. + .map((bid) => ({...bid, pageId: bid.params?.pageId, domainId: bid.params?.domainId, placementId: bid.params?.placementId})); const extensions = getCwExtension(); const payload = { diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 9804250b906..1d4f3c039c8 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -14,14 +14,15 @@ Prebid.js Adapter for C-Wire. Below, the list of C-WIRE params and where they can be set. -| Param name | URL parameter | AdUnit config | Type | Required | -|-------------|:-------------:|:-------------:|:--------:|:-------------:| -| pageId | | x | number | YES | -| placementId | | x | number | YES | -| cwgroups | x | | string | NO | -| cwcreative | x | | string | NO | -| cwdebug | x | | boolean | NO | -| cwfeatures | x | | string | NO | +| Param name | URL parameter | AdUnit config | Type | Required | +|-------------|:-------------:|:-------------:|:--------:|:--------:| +| pageId | | x | number | NO | +| domainId | | x | number | YES | +| placementId | | x | number | NO | +| cwgroups | x | | string | NO | +| cwcreative | x | | string | NO | +| cwdebug | x | | boolean | NO | +| cwfeatures | x | | string | NO | ### adUnit configuration @@ -38,12 +39,30 @@ var adUnits = [ } }, params: { - pageId: 1422, // required - number - placementId: 2211521, // required - number + domainId: 1422, // required - number + placementId: 2211521, // optional - number } }] } ]; +// old version for the compatibility +var adUnits = [ + { + code: 'target_div_id', // REQUIRED + bids: [{ + bidder: 'cwire', + mediaTypes: { + banner: { + sizes: [[400, 600]], + } + }, + params: { + pageId: 1422, // required - number + placementId: 2211521, // required - number + } + }] + } +]; ``` ### URL parameters From 39fa308345db8ee1b02a1c98cc95e116428a5b1c Mon Sep 17 00:00:00 2001 From: mjaworskiccx <50406214+mjaworskiccx@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:41:55 +0200 Subject: [PATCH 0392/1097] Ccx bid adapter: Protected Audence, add request param imp.ext.ae (#12055) * adomain support * adomain support * adomain support * adomain support * adomain support * video params * docs changes * Clickonometrics adapter update * Revert "Revert "Clickonometrics Bid Adapter : add gvlid (#9198)" (#9216)" This reverts commit 6d114e83725b403fadd889202b449de225db7275. * Test fix * tests * fledge * fledge --------- Co-authored-by: Michal Jaworski <76069238+mkjsoft@users.noreply.github.com> --- modules/ccxBidAdapter.js | 8 ++- test/spec/modules/ccxBidAdapter_spec.js | 81 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index b1fcb29e3d0..0a305a651cb 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -65,7 +65,7 @@ function _validateSizes (sizeObj, type) { return true } -function _buildBid (bid) { +function _buildBid (bid, bidderRequest) { let placement = {} placement.id = bid.bidId placement.secure = 1 @@ -105,6 +105,10 @@ function _buildBid (bid) { placement.ext = {'pid': bid.params.placementId} + if (bidderRequest.paapi?.enabled) { + placement.ext.ae = bid?.ortb2Imp?.ext?.ae + } + return placement } @@ -197,7 +201,7 @@ export const spec = { } _each(validBidRequests, function (bid) { - requestBody.imp.push(_buildBid(bid)) + requestBody.imp.push(_buildBid(bid, bidderRequest)) }) // Return the server request return { diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index cbae441e7e7..1e345691bcf 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; import { spec } from 'modules/ccxBidAdapter.js'; import * as utils from 'src/utils.js'; @@ -39,6 +40,7 @@ describe('ccxAdapter', function () { transactionId: 'aefddd38-cfa0-48ab-8bdd-325de4bab5f9' } ]; + describe('isBidRequestValid', function () { it('Valid bid requests', function () { expect(spec.isBidRequestValid(bids[0])).to.be.true; @@ -75,6 +77,7 @@ describe('ccxAdapter', function () { expect(spec.isBidRequestValid(bidsClone[0])).to.be.true; }); }); + describe('buildRequests', function () { it('No valid bids', function () { expect(spec.buildRequests([])).to.be.undefined; @@ -173,6 +176,7 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); + it('Valid bid request - sizes old style', function () { let bidsClone = utils.deepClone(bids); delete (bidsClone[0].mediaTypes); @@ -218,6 +222,7 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); + it('Valid bid request - sizes old style - no media type', function () { let bidsClone = utils.deepClone(bids); delete (bidsClone[0].mediaTypes); @@ -385,6 +390,7 @@ describe('ccxAdapter', function () { expect(spec.interpretResponse({})).to.be.empty; }); }); + describe('getUserSyncs', function () { it('Valid syncs - all', function () { let syncOptions = { @@ -434,6 +440,7 @@ describe('ccxAdapter', function () { expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.be.empty; }); }); + describe('mediaTypesVideoParams', function () { it('Valid video mediaTypes', function () { let bids = [ @@ -488,4 +495,78 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); }); + + describe('FLEDGE', function () { + it('should properly build a request when FLEDGE is enabled', function () { + let bidderRequest = { + paapi: { + enabled: true + } + }; + let bids = [ + { + adUnitCode: 'banner', + auctionId: '0b9de793-8eda-481e-a548-aaaaaaaaaaa1', + bidId: '2e56e1af51ccc1', + bidder: 'ccx', + bidderRequestId: '17e7b9f58accc1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 609 + }, + sizes: [[300, 250]], + transactionId: 'befddd38-cfa0-48ab-8bdd-bbbbbbbbbbb1', + ortb2Imp: { + ext: { + ae: 1 + } + } + } + ]; + + let ortbRequest = spec.buildRequests(bids, syncAddFPDToBidderRequest(bidderRequest)); + let data = JSON.parse(ortbRequest.data); + expect(data.imp[0].ext.ae).to.equal(1); + }); + + it('should properly build a request when FLEDGE is disabled', function () { + let bidderRequest = { + paapi: { + enabled: false + } + }; + let bids = [ + { + adUnitCode: 'banner', + auctionId: '0b9de793-8eda-481e-a548-aaaaaaaaaaa2', + bidId: '2e56e1af51ccc2', + bidder: 'ccx', + bidderRequestId: '17e7b9f58accc2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 610 + }, + sizes: [[300, 250]], + transactionId: 'befddd38-cfa0-48ab-8bdd-bbbbbbbbbbb2', + ortb2Imp: { + ext: { + ae: 1 + } + } + } + ]; + + let ortbRequest = spec.buildRequests(bids, syncAddFPDToBidderRequest(bidderRequest)); + let data = JSON.parse(ortbRequest.data); + expect(data.imp[0].ext.ae).to.be.undefined; + }); + }); }); From 0d021fe4714e2180ef6fd26e97d9309d92b1854e Mon Sep 17 00:00:00 2001 From: Phaneendra Hegde Date: Wed, 31 Jul 2024 23:12:20 +0530 Subject: [PATCH 0393/1097] PubxAI Rtd module update: Make the endpoint call optional; read from local storage. (#12064) * Made RTD endpoint optional. Read the floors from local storage in case dynamic endpoint is not provided. * Updated specs to include localStorage tests * fix import error * use sessionStorage to fetchfloorData * updated tests * Fix the example code in the documentation * fix unit tests --------- Co-authored-by: tej656 --- modules/pubxaiRtdProvider.js | 56 ++++++++++++++------- modules/pubxaiRtdProvider.md | 4 +- test/spec/modules/pubxaiRtdProvider_spec.js | 42 ++++++++++++++-- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/modules/pubxaiRtdProvider.js b/modules/pubxaiRtdProvider.js index b958856df00..4528b29cf11 100644 --- a/modules/pubxaiRtdProvider.js +++ b/modules/pubxaiRtdProvider.js @@ -2,6 +2,8 @@ import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { submodule } from '../src/hook.js'; import { deepAccess } from '../src/utils.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; /** * This RTD module has a dependency on the priceFloors module. * We utilize the createFloorsDataForAuction function from the priceFloors module to incorporate price floors data into the current auction. @@ -16,6 +18,7 @@ export const FloorsApiStatus = Object.freeze({ SUCCESS: 'SUCCESS', ERROR: 'ERROR', }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); export const FLOORS_EVENT_HANDLE = 'floorsApi'; export const FLOORS_END_POINT = 'https://floor.pbxai.com/'; export const FLOOR_PROVIDER = 'PubxFloorProvider'; @@ -80,31 +83,48 @@ export const setFloorsApiStatus = (status) => { export const getUrl = (provider) => { const { pubxId, endpoint } = deepAccess(provider, 'params'); - return `${endpoint || FLOORS_END_POINT}?pubxId=${pubxId}&page=${ - window.location.href - }`; + if (!endpoint) { + return null; // Indicate that no endpoint is provided + } + return `${endpoint || FLOORS_END_POINT}?pubxId=${pubxId}&page=${window.location.href}`; }; export const fetchFloorRules = async (provider) => { return new Promise((resolve, reject) => { setFloorsApiStatus(FloorsApiStatus.IN_PROGRESS); - ajax(getUrl(provider), { - success: (responseText, response) => { - try { - if (response && response.response) { - const floorsResponse = JSON.parse(response.response); - resolve(floorsResponse); - } else { - resolve(null); + const url = getUrl(provider); + if (url) { + // Fetch from remote endpoint + ajax(url, { + success: (responseText, response) => { + try { + if (response && response.response) { + const floorsResponse = JSON.parse(response.response); + resolve(floorsResponse); + } else { + resolve(null); + } + } catch (error) { + reject(error); } - } catch (error) { - reject(error); + }, + error: (responseText, response) => { + reject(response); + }, + }); + } else { + // Fetch from local storage + try { + const localData = storage.getDataFromSessionStorage('pubx:dynamicFloors') || window.__pubxDynamicFloors__; + if (localData) { + resolve(JSON.parse(localData)); + } else { + resolve(null); } - }, - error: (responseText, response) => { - reject(response); - }, - }); + } catch (error) { + reject(error); + } + } }); }; diff --git a/modules/pubxaiRtdProvider.md b/modules/pubxaiRtdProvider.md index d7d89857c62..2b89d3baa04 100644 --- a/modules/pubxaiRtdProvider.md +++ b/modules/pubxaiRtdProvider.md @@ -32,7 +32,7 @@ pbjs.setConfig({ ..., realTimeData: { auctionDelay: AUCTION_DELAY, - dataProviders: { + dataProviders: [{ name: "pubxai", waitForIt: true, params: { @@ -42,7 +42,7 @@ pbjs.setConfig({ enforcement: ``, // (optional) data: `` // (optional) } - } + }] } // rest of the config ..., diff --git a/test/spec/modules/pubxaiRtdProvider_spec.js b/test/spec/modules/pubxaiRtdProvider_spec.js index b645b830246..6ffa4952992 100644 --- a/test/spec/modules/pubxaiRtdProvider_spec.js +++ b/test/spec/modules/pubxaiRtdProvider_spec.js @@ -1,6 +1,7 @@ import * as priceFloors from '../../../modules/priceFloors'; import { FLOORS_END_POINT, + storage, FLOORS_EVENT_HANDLE, FloorsApiStatus, beforeInit, @@ -45,6 +46,7 @@ const resetGlobals = () => { window.__pubxFloorsConfig__ = undefined; window.__pubxFloorsApiStatus__ = undefined; window.__pubxFloorRulesPromise__ = null; + localStorage.removeItem('pubx:dynamicFloors'); }; const fakeServer = ( @@ -119,7 +121,7 @@ describe('pubxaiRtdProvider', () => { stub.restore(); }); it('createFloorsDataForAuction called once before and once after __pubxFloorRulesPromise__. Also getBidRequestData executed only once', async () => { - pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => {}); + pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => { }); assert(priceFloors.createFloorsDataForAuction.calledOnce); await window.__pubxFloorRulesPromise__; assert(priceFloors.createFloorsDataForAuction.calledTwice); @@ -129,7 +131,7 @@ describe('pubxaiRtdProvider', () => { reqBidsConfigObj.auctionId ) ); - pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => {}); + pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => { }); await window.__pubxFloorRulesPromise__; assert(priceFloors.createFloorsDataForAuction.calledTwice); }); @@ -137,6 +139,16 @@ describe('pubxaiRtdProvider', () => { describe('fetchFloorRules', () => { const providerConfig = getConfig(); const floorsResponse = getFloorsResponse(); + let storageStub; + + beforeEach(() => { + storageStub = sinon.stub(storage, 'getDataFromSessionStorage'); + }); + + afterEach(() => { + storageStub.restore(); + }); + it('success with floors response', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer(floorsResponse); @@ -145,6 +157,7 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + it('success with no floors response', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer(undefined); @@ -153,6 +166,7 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + it('API call error', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer(undefined, undefined, 404); @@ -167,6 +181,7 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + it('Wrong API response', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer('floorsResponse'); @@ -181,6 +196,25 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + + it('success with local data response', (done) => { + const localFloorsResponse = getFloorsResponse(); + storageStub.withArgs('pubx:dynamicFloors').returns(JSON.stringify(localFloorsResponse)); + const promise = fetchFloorRules({ params: {} }); + promise.then((res) => { + expect(res).to.deep.equal(localFloorsResponse); + done(); + }); + }); + + it('no local data response', (done) => { + storageStub.withArgs('pubx:dynamicFloors').returns(null); + const promise = fetchFloorRules({ params: {} }); + promise.then((res) => { + expect(res).to.deep.equal(null); + done(); + }); + }); }); describe('setPriceFloors', () => { const providerConfig = getConfig(); @@ -383,9 +417,7 @@ describe('pubxaiRtdProvider', () => { expect(FLOORS_END_POINT).to.equal('https://floor.pbxai.com/'); }); it('standard case', () => { - expect(getUrl(provider)).to.equal( - `https://floor.pbxai.com/?pubxId=12345&page=${window.location.href}` - ); + expect(getUrl(provider)).to.equal(null); }); it('custom url provided', () => { provider.params.endpoint = 'https://custom.floor.com/'; From 2ada5d131bd27896dc738ac2f96a164f89e71d82 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 31 Jul 2024 18:52:31 -0700 Subject: [PATCH 0394/1097] fix 8podAnalytics tests (#12071) --- modules/eightPodAnalyticsAdapter.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js index 6dab3b0c107..e91f9412ef5 100644 --- a/modules/eightPodAnalyticsAdapter.js +++ b/modules/eightPodAnalyticsAdapter.js @@ -63,7 +63,9 @@ let eightPodAnalytics = Object.assign(adapter({url: trackerUrl, analyticsType}), trackEvent(data, adUnitCode); }); - setInterval(sendEvents, 10_000); + if (!this._interval) { + this._interval = setInterval(sendEvents, 10_000); + } }, resetQueue() { queue = []; @@ -182,6 +184,16 @@ eightPodAnalytics.enableAnalytics = function (config) { eightPodAnalytics.eventSubscribe(); }; +eightPodAnalytics.disableAnalytics = ((orig) => { + return function () { + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + return orig.apply(this, arguments); + } +})(eightPodAnalytics.disableAnalytics) + /** * Register Analytics Adapter */ From c0d565845dd8749b794807287cc28f5c5d902644 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 1 Aug 2024 03:43:12 -0700 Subject: [PATCH 0395/1097] Multiple modules: extract deviceMemory / hardwareConcurrency to library, add codeQL warnings (#12070) * Custom codeQL rules / hardwareConcurrency and deviceMemory * move deviceMemory / hardwareConcurrency to a library * reuse library code for deviceMemory & co --- .github/codeql/codeql-config.yml | 3 ++ .github/codeql/queries/deviceMemory.ql | 14 ++++++++ .github/codeql/queries/hardwareConcurrency.ql | 14 ++++++++ .github/codeql/queries/prebid.qll | 36 +++++++++++++++++++ .github/codeql/queries/qlpack.yml | 8 +++++ .../navigatorData/navigatorData.js | 2 +- modules/discoveryBidAdapter.js | 2 +- modules/mediagoBidAdapter.js | 8 ++--- modules/teadsBidAdapter.js | 7 ++-- test/spec/modules/discoveryBidAdapter_spec.js | 2 +- 10 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 .github/codeql/queries/deviceMemory.ql create mode 100644 .github/codeql/queries/hardwareConcurrency.ql create mode 100644 .github/codeql/queries/prebid.qll create mode 100644 .github/codeql/queries/qlpack.yml rename src/fpd/navigator.js => libraries/navigatorData/navigatorData.js (99%) diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 2e8465003e4..8d3788e8956 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -2,3 +2,6 @@ paths: - src - modules - libraries +queries: + - name: Prebid queries + uses: ./.github/codeql/queries diff --git a/.github/codeql/queries/deviceMemory.ql b/.github/codeql/queries/deviceMemory.ql new file mode 100644 index 00000000000..6f650abf0e1 --- /dev/null +++ b/.github/codeql/queries/deviceMemory.ql @@ -0,0 +1,14 @@ +/** + * @id prebid/device-memory + * @name Access to navigator.deviceMemory + * @kind problem + * @problem.severity warning + * @description Finds uses of deviceMemory + */ + +import prebid + +from SourceNode nav +where + nav = windowPropertyRead("navigator") +select nav.getAPropertyRead("deviceMemory"), "deviceMemory is an indicator of fingerprinting" diff --git a/.github/codeql/queries/hardwareConcurrency.ql b/.github/codeql/queries/hardwareConcurrency.ql new file mode 100644 index 00000000000..350dbd1ae81 --- /dev/null +++ b/.github/codeql/queries/hardwareConcurrency.ql @@ -0,0 +1,14 @@ +/** + * @id prebid/hardware-concurrency + * @name Access to navigator.hardwareConcurrency + * @kind problem + * @problem.severity warning + * @description Finds uses of hardwareConcurrency + */ + +import prebid + +from SourceNode nav +where + nav = windowPropertyRead("navigator") +select nav.getAPropertyRead("hardwareConcurrency"), "hardwareConcurrency is an indicator of fingerprinting" diff --git a/.github/codeql/queries/prebid.qll b/.github/codeql/queries/prebid.qll new file mode 100644 index 00000000000..02fb5adc93c --- /dev/null +++ b/.github/codeql/queries/prebid.qll @@ -0,0 +1,36 @@ +import javascript +import DataFlow + +SourceNode otherWindow() { + result = globalVarRef("top") or + result = globalVarRef("self") or + result = globalVarRef("parent") or + result = globalVarRef("frames").getAPropertyRead() or + result = DOM::documentRef().getAPropertyRead("defaultView") +} + +SourceNode connectedWindow(SourceNode win) { + result = win.getAPropertyRead("self") or + result = win.getAPropertyRead("top") or + result = win.getAPropertyRead("parent") or + result = win.getAPropertyRead("frames").getAPropertyRead() or + result = win.getAPropertyRead("document").getAPropertyRead("defaultView") +} + +SourceNode relatedWindow(SourceNode win) { + result = connectedWindow(win) or + result = relatedWindow+(connectedWindow(win)) +} + +SourceNode anyWindow() { + result = otherWindow() or + result = relatedWindow(otherWindow()) +} + +/* + Matches uses of property `prop` done on any window object. +*/ +SourceNode windowPropertyRead(string prop) { + result = globalVarRef(prop) or + result = anyWindow().getAPropertyRead(prop) +} diff --git a/.github/codeql/queries/qlpack.yml b/.github/codeql/queries/qlpack.yml new file mode 100644 index 00000000000..72e90d3de9b --- /dev/null +++ b/.github/codeql/queries/qlpack.yml @@ -0,0 +1,8 @@ +--- +library: false +warnOnImplicitThis: false +name: queries +version: 0.0.1 +dependencies: + codeql/javascript-all: ^1.1.1 + codeql/javascript-queries: ^1.1.0 diff --git a/src/fpd/navigator.js b/libraries/navigatorData/navigatorData.js similarity index 99% rename from src/fpd/navigator.js rename to libraries/navigatorData/navigatorData.js index 80025f88640..f1a34fc51eb 100644 --- a/src/fpd/navigator.js +++ b/libraries/navigatorData/navigatorData.js @@ -26,4 +26,4 @@ export function getDM(win = window) { dm = undefined; } return dm; -}; +} diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 696faa86a0a..41fac90d62a 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -2,11 +2,11 @@ import * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { getHLen, getHC, getDM } from '../src/fpd/navigator.js'; import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink, getReferrer } from '../libraries/fpdUtils/pageInfo.js'; import { getDevice, getScreenSize } from '../libraries/fpdUtils/deviceInfo.js'; import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; +import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 5c6c62b95fe..0f05449eee8 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -9,6 +9,7 @@ import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLin import { getDevice } from '../libraries/fpdUtils/deviceInfo.js'; import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; +import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; // import { config } from '../src/config.js'; // import { isPubcidEnabled } from './pubCommonId.js'; @@ -230,7 +231,6 @@ function getParam(validBidRequests, bidderRequest) { const timeout = bidderRequest.timeout || 2000; const firstPartyData = bidderRequest.ortb2; - const topWindow = window.top; const title = getPageTitle(); const desc = getPageDescription(); const keywords = getPageKeywords(); @@ -267,12 +267,12 @@ function getParam(validBidRequests, bidderRequest) { title: title ? title.slice(0, 100) : undefined, desc: desc ? desc.slice(0, 300) : undefined, keywords: keywords ? keywords.slice(0, 100) : undefined, - hLen: topWindow.history?.length || undefined, + hLen: getHLen(), }, device: { nbw: getConnectionDownLink(), - hc: topWindow.navigator?.hardwareConcurrency || undefined, - dm: topWindow.navigator?.deviceMemory || undefined, + hc: getHC(), + dm: getDM() } }, user: { diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 8f775f2580d..73ea5dae575 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -2,6 +2,7 @@ import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParame import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js'; +import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -65,11 +66,11 @@ export const spec = { deviceHeight: screen.height, devicePixelRatio: topWindow.devicePixelRatio, screenOrientation: screen.orientation?.type, - historyLength: topWindow.history?.length, + historyLength: getHLen(), viewportHeight: topWindow.visualViewport?.height, viewportWidth: topWindow.visualViewport?.width, - hardwareConcurrency: topWindow.navigator?.hardwareConcurrency, - deviceMemory: topWindow.navigator?.deviceMemory, + hardwareConcurrency: getHC(), + deviceMemory: getDM(), hb_version: '$prebid.version$', ...getSharedViewerIdParameters(validBidRequests), ...getFirstPartyTeadsIdParameter(validBidRequests) diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index ffb733a2cb2..f3b35e5cda6 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -10,7 +10,7 @@ import { } from 'modules/discoveryBidAdapter.js'; import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from '../../../libraries/fpdUtils/pageInfo.js'; import * as utils from 'src/utils.js'; -import { getHLen, getHC, getDM } from '../../../src/fpd/navigator.js'; +import {getDM, getHC, getHLen} from '../../../libraries/navigatorData/navigatorData.js'; describe('discovery:BidAdapterTests', function () { let sandbox; From b1fb6dd02cc6712c9e69a47fc1ac6b91c39af825 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:42:08 +0300 Subject: [PATCH 0396/1097] Smarthub: add alias FelixAds (#12072) * update adapter SmartHub: add aliases * SmartHub: add alias FelixAds --------- Co-authored-by: Victor Co-authored-by: Patrick McCann --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index baf4c358736..367011d68d5 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -12,6 +12,7 @@ const ALIASES = [ {code: 'markapp', skipPbsAliasing: true}, {code: 'jdpmedia', skipPbsAliasing: true}, {code: 'tredio', skipPbsAliasing: true}, + {code: 'felixads', skipPbsAliasing: true}, {code: 'vimayx', skipPbsAliasing: true}, ]; const BASE_URLS = { @@ -19,6 +20,7 @@ const BASE_URLS = { markapp: 'https://markapp-prebid.smart-hub.io/pbjs', jdpmedia: 'https://jdpmedia-prebid.smart-hub.io/pbjs', tredio: 'https://tredio-prebid.smart-hub.io/pbjs', + felixads: 'https://felixads-prebid.smart-hub.io/pbjs', vimayx: 'https://vimayx-prebid.smart-hub.io/pbjs', }; From 1947d97de1e5cac6dafff270b4ff570469d6e051 Mon Sep 17 00:00:00 2001 From: Oleksandr Yermakov <47504677+sanychtasher@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:53:20 +0300 Subject: [PATCH 0397/1097] increment version (#12075) Co-authored-by: Oleksandr Yermakov --- modules/mgidBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 70a9290aa4d..a384b9d676c 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -88,7 +88,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAs _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { - VERSION: '1.7', + VERSION: '1.8', code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE], From cf98f85fda395b70bfe5816b2481b318417aac5c Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 1 Aug 2024 12:41:45 -0700 Subject: [PATCH 0398/1097] ortbConverter: do not override EIDS provided as first party data (#12076) * ortbConverter: do not override EIDS provided as first party data * update tests --- modules/userId/index.js | 2 +- test/spec/modules/openxBidAdapter_spec.js | 2 +- .../modules/trafficgateBidAdapter_spec.js | 2 +- test/spec/ortbConverter/userId_spec.js | 25 +++++++++++++++++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index b8d013df1c6..31083fd1e47 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -1196,7 +1196,7 @@ module('userId', attachIdSystem, { postInstallAllowed: true }); export function setOrtbUserExtEids(ortbRequest, bidderRequest, context) { const eids = deepAccess(context, 'bidRequests.0.userIdAsEids'); if (eids && Object.keys(eids).length > 0) { - deepSetValue(ortbRequest, 'user.ext.eids', eids); + deepSetValue(ortbRequest, 'user.ext.eids', eids.concat(ortbRequest.user?.ext?.eids || [])); } } registerOrtbProcessor({type: REQUEST, name: 'userExtEids', fn: setOrtbUserExtEids}); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index ad4ee1e74ce..e895574b9aa 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1025,7 +1025,7 @@ describe('OpenxRtbAdapter', function () { // enrich bid request with userId key/value const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); + expect(request[0].data.user.ext.eids).to.eql(userIdAsEids); }); it(`when no user ids are available, it should not send any extended ids`, function () { diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 9c564606186..7fbc566d375 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -1006,7 +1006,7 @@ describe('TrafficgateOpenxRtbAdapter', function () { // enrich bid request with userId key/value const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); + expect(request[0].data.user.ext.eids).to.eql(userIdAsEids); }); it(`when no user ids are available, it should not send any extended ids`, function () { diff --git a/test/spec/ortbConverter/userId_spec.js b/test/spec/ortbConverter/userId_spec.js index 04a4d39ee48..2084cb0cfae 100644 --- a/test/spec/ortbConverter/userId_spec.js +++ b/test/spec/ortbConverter/userId_spec.js @@ -6,13 +6,34 @@ describe('pbjs - ortb user eids', () => { setOrtbUserExtEids(req, {}, { bidRequests: [ { - userIdAsEids: {e: 'id'} + userIdAsEids: [{e: 'id'}] } ] }); - expect(req.user.ext.eids).to.eql({e: 'id'}); + expect(req.user.ext.eids).to.eql([{e: 'id'}]); }); + it('should not override eids from fpd', () => { + const req = { + user: { + ext: { + eids: [{existing: 'id'}] + } + } + }; + setOrtbUserExtEids(req, {}, { + bidRequests: [ + { + userIdAsEids: [{nw: 'id'}] + } + ] + }); + expect(req.user.ext.eids).to.eql([ + {nw: 'id'}, + {existing: 'id'}, + ]) + }) + it('has no effect if requests have no eids', () => { const req = {}; setOrtbUserExtEids(req, {}, [{}]); From 0b63c8fd9d252cede1e6186a6e0b3cc4f5e08f9a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 2 Aug 2024 15:06:14 +0000 Subject: [PATCH 0399/1097] Prebid 9.8.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19a83a7b6ec..e9a08ecc18c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.8.0-pre", + "version": "9.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.8.0-pre", + "version": "9.8.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index db93bbb4317..b4725197258 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.8.0-pre", + "version": "9.8.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 33366a3f7d6eaa7ec8c0435ff13f933ebad91344 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 2 Aug 2024 15:06:14 +0000 Subject: [PATCH 0400/1097] Increment version to 9.9.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9a08ecc18c..21ceda42b53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.8.0", + "version": "9.9.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.8.0", + "version": "9.9.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index b4725197258..7502dc8794e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.8.0", + "version": "9.9.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From d5c4ecc1c0da536784f1ca7bee311e46b842bcab Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:31:36 -0400 Subject: [PATCH 0401/1097] add mobian contextual variables directly to site.ext.data rather than creating a subobject (#12082) --- modules/mobianRtdProvider.js | 51 +++++++-- test/spec/modules/mobianRtdProvider_spec.js | 115 ++++++++++++++++---- 2 files changed, 134 insertions(+), 32 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 71b6ed2d51d..251a8fd53d8 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -22,7 +22,6 @@ export const mobianBrandSafetySubmodule = { function init() { return true; } - function getBidRequestData(bidReqConfig, callback, config) { const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const pageUrl = encodeURIComponent(getPageUrl()); @@ -54,25 +53,55 @@ function getBidRequestData(bidReqConfig, callback, config) { .filter(key => key.startsWith('emotion_') && response[key]) .map(key => key.replace('emotion_', '')); - const risk = { - 'risk': mobianRisk, - 'contentCategories': categories, - 'sentiment': sentiment, - 'emotions': emotions + const categoryFlags = { + adult: categories.includes('adult'), + arms: categories.includes('arms'), + crime: categories.includes('crime'), + death_injury: categories.includes('death_injury'), + piracy: categories.includes('piracy'), + hate_speech: categories.includes('hate_speech'), + obscenity: categories.includes('obscenity'), + drugs: categories.includes('drugs'), + spam: categories.includes('spam'), + terrorism: categories.includes('terrorism'), + debated_issue: categories.includes('debated_issue') + }; + + const emotionFlags = { + love: emotions.includes('love'), + joy: emotions.includes('joy'), + anger: emotions.includes('anger'), + surprise: emotions.includes('surprise'), + sadness: emotions.includes('sadness'), + fear: emotions.includes('fear') }; - resolve(risk); - deepSetValue(ortb2Site.ext, 'data.mobian', risk); - callback() + deepSetValue(ortb2Site.ext, 'data.mobianRisk', mobianRisk); + deepSetValue(ortb2Site.ext, 'data.mobianSentiment', sentiment); + + Object.entries(categoryFlags).forEach(([category, value]) => { + deepSetValue(ortb2Site.ext, `data.mobianCategory${category.charAt(0).toUpperCase() + category.slice(1)}`, value); + }); + + Object.entries(emotionFlags).forEach(([emotion, value]) => { + deepSetValue(ortb2Site.ext, `data.mobianEmotion${emotion.charAt(0).toUpperCase() + emotion.slice(1)}`, value); + }); + + resolve({ + risk: mobianRisk, + sentiment: sentiment, + categoryFlags: categoryFlags, + emotionFlags: emotionFlags + }); + callback(); }, error: function () { resolve({}); - callback() + callback(); } }); }); } - function getPageUrl() { return window.location.href; } diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index 43f6cbaf569..bf34cc6387f 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -12,7 +12,9 @@ describe('Mobian RTD Submodule', function () { ortb2Fragments: { global: { site: { - ext: {} + ext: { + data: {} + } } } } @@ -23,7 +25,7 @@ describe('Mobian RTD Submodule', function () { ajaxStub.restore(); }); - it('should return risk level when server responds with garm_risk', function () { + it('should set individual key-value pairs when server responds with garm_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ garm_risk: 'low', @@ -32,14 +34,37 @@ describe('Mobian RTD Submodule', function () { })); }); - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({ + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ risk: 'low', - contentCategories: [], sentiment: 'positive', - emotions: ['joy'] + categoryFlags: { + adult: false, + arms: false, + crime: false, + death_injury: false, + piracy: false, + hate_speech: false, + obscenity: false, + drugs: false, + spam: false, + terrorism: false, + debated_issue: false + }, + emotionFlags: { + love: false, + joy: true, + anger: false, + surprise: false, + sadness: false, + fear: false + } + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'low', + mobianSentiment: 'positive', + mobianEmotionJoy: true }); - expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); @@ -55,14 +80,40 @@ describe('Mobian RTD Submodule', function () { })); }); - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({ + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ risk: 'medium', - contentCategories: ['arms', 'crime'], sentiment: 'negative', - emotions: ['anger', 'fear'] + categoryFlags: { + adult: false, + arms: true, + crime: true, + death_injury: false, + piracy: false, + hate_speech: false, + obscenity: false, + drugs: false, + spam: false, + terrorism: false, + debated_issue: false + }, + emotionFlags: { + love: false, + joy: false, + anger: true, + surprise: false, + sadness: false, + fear: true + } + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'medium', + mobianSentiment: 'negative', + mobianCategoryArms: true, + mobianCategoryCrime: true, + mobianEmotionAnger: true, + mobianEmotionFear: true }); - expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); @@ -73,14 +124,36 @@ describe('Mobian RTD Submodule', function () { })); }); - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({ + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ risk: 'unknown', - contentCategories: [], sentiment: 'neutral', - emotions: [] + categoryFlags: { + adult: false, + arms: false, + crime: false, + death_injury: false, + piracy: false, + hate_speech: false, + obscenity: false, + drugs: false, + spam: false, + terrorism: false, + debated_issue: false + }, + emotionFlags: { + love: false, + joy: false, + anger: false, + surprise: false, + sadness: false, + fear: false + } + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'unknown', + mobianSentiment: 'neutral' }); - expect(bidReqConfig.ortb2Fragments.global.site.ext.data.mobian).to.deep.equal(risk); }); }); @@ -89,8 +162,8 @@ describe('Mobian RTD Submodule', function () { callbacks.success('unexpected output not even of the right type'); }); const originalConfig = JSON.parse(JSON.stringify(bidReqConfig)); - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({}); + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({}); // Check that bidReqConfig hasn't been modified expect(bidReqConfig).to.deep.equal(originalConfig); }); @@ -101,8 +174,8 @@ describe('Mobian RTD Submodule', function () { callbacks.error(); }); - return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((risk) => { - expect(risk).to.deep.equal({}); + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({}); }); }); }); From bfea83eb30c6d01381d36ad9cd31215ac2c89cbe Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Sat, 3 Aug 2024 11:16:12 -0400 Subject: [PATCH 0402/1097] reduce cardinality of fields added to site.ext.data (#12085) --- modules/mobianRtdProvider.js | 47 +++--------- test/spec/modules/mobianRtdProvider_spec.js | 84 ++++----------------- 2 files changed, 25 insertions(+), 106 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 251a8fd53d8..d71948046b9 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -22,6 +22,7 @@ export const mobianBrandSafetySubmodule = { function init() { return true; } + function getBidRequestData(bidReqConfig, callback, config) { const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const pageUrl = encodeURIComponent(getPageUrl()); @@ -41,7 +42,7 @@ function getBidRequestData(bidReqConfig, callback, config) { let mobianRisk = response.garm_risk || 'unknown'; - const categories = Object.keys(response) + const contentCategories = Object.keys(response) .filter(key => key.startsWith('garm_content_category_') && response[key]) .map(key => key.replace('garm_content_category_', '')); @@ -53,46 +54,19 @@ function getBidRequestData(bidReqConfig, callback, config) { .filter(key => key.startsWith('emotion_') && response[key]) .map(key => key.replace('emotion_', '')); - const categoryFlags = { - adult: categories.includes('adult'), - arms: categories.includes('arms'), - crime: categories.includes('crime'), - death_injury: categories.includes('death_injury'), - piracy: categories.includes('piracy'), - hate_speech: categories.includes('hate_speech'), - obscenity: categories.includes('obscenity'), - drugs: categories.includes('drugs'), - spam: categories.includes('spam'), - terrorism: categories.includes('terrorism'), - debated_issue: categories.includes('debated_issue') - }; - - const emotionFlags = { - love: emotions.includes('love'), - joy: emotions.includes('joy'), - anger: emotions.includes('anger'), - surprise: emotions.includes('surprise'), - sadness: emotions.includes('sadness'), - fear: emotions.includes('fear') + const risk = { + risk: mobianRisk, + contentCategories: contentCategories, + sentiment: sentiment, + emotions: emotions }; deepSetValue(ortb2Site.ext, 'data.mobianRisk', mobianRisk); + deepSetValue(ortb2Site.ext, 'data.mobianContentCategories', contentCategories); deepSetValue(ortb2Site.ext, 'data.mobianSentiment', sentiment); + deepSetValue(ortb2Site.ext, 'data.mobianEmotions', emotions); - Object.entries(categoryFlags).forEach(([category, value]) => { - deepSetValue(ortb2Site.ext, `data.mobianCategory${category.charAt(0).toUpperCase() + category.slice(1)}`, value); - }); - - Object.entries(emotionFlags).forEach(([emotion, value]) => { - deepSetValue(ortb2Site.ext, `data.mobianEmotion${emotion.charAt(0).toUpperCase() + emotion.slice(1)}`, value); - }); - - resolve({ - risk: mobianRisk, - sentiment: sentiment, - categoryFlags: categoryFlags, - emotionFlags: emotionFlags - }); + resolve(risk); callback(); }, error: function () { @@ -102,6 +76,7 @@ function getBidRequestData(bidReqConfig, callback, config) { }); }); } + function getPageUrl() { return window.location.href; } diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index bf34cc6387f..717745c02f2 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -25,7 +25,7 @@ describe('Mobian RTD Submodule', function () { ajaxStub.restore(); }); - it('should set individual key-value pairs when server responds with garm_risk', function () { + it('should set key-value pairs when server responds with garm_risk', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ garm_risk: 'low', @@ -37,33 +37,15 @@ describe('Mobian RTD Submodule', function () { return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { expect(result).to.deep.equal({ risk: 'low', + contentCategories: [], sentiment: 'positive', - categoryFlags: { - adult: false, - arms: false, - crime: false, - death_injury: false, - piracy: false, - hate_speech: false, - obscenity: false, - drugs: false, - spam: false, - terrorism: false, - debated_issue: false - }, - emotionFlags: { - love: false, - joy: true, - anger: false, - surprise: false, - sadness: false, - fear: false - } + emotions: ['joy'] }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'low', + mobianContentCategories: [], mobianSentiment: 'positive', - mobianEmotionJoy: true + mobianEmotions: ['joy'] }); }); }); @@ -83,36 +65,15 @@ describe('Mobian RTD Submodule', function () { return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { expect(result).to.deep.equal({ risk: 'medium', + contentCategories: ['arms', 'crime'], sentiment: 'negative', - categoryFlags: { - adult: false, - arms: true, - crime: true, - death_injury: false, - piracy: false, - hate_speech: false, - obscenity: false, - drugs: false, - spam: false, - terrorism: false, - debated_issue: false - }, - emotionFlags: { - love: false, - joy: false, - anger: true, - surprise: false, - sadness: false, - fear: true - } + emotions: ['anger', 'fear'] }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'medium', + mobianContentCategories: ['arms', 'crime'], mobianSentiment: 'negative', - mobianCategoryArms: true, - mobianCategoryCrime: true, - mobianEmotionAnger: true, - mobianEmotionFear: true + mobianEmotions: ['anger', 'fear'] }); }); }); @@ -127,32 +88,15 @@ describe('Mobian RTD Submodule', function () { return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { expect(result).to.deep.equal({ risk: 'unknown', + contentCategories: [], sentiment: 'neutral', - categoryFlags: { - adult: false, - arms: false, - crime: false, - death_injury: false, - piracy: false, - hate_speech: false, - obscenity: false, - drugs: false, - spam: false, - terrorism: false, - debated_issue: false - }, - emotionFlags: { - love: false, - joy: false, - anger: false, - surprise: false, - sadness: false, - fear: false - } + emotions: [] }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'unknown', - mobianSentiment: 'neutral' + mobianContentCategories: [], + mobianSentiment: 'neutral', + mobianEmotions: [] }); }); }); From 625185b6891f9661426080ae3ba09b4fb654d133 Mon Sep 17 00:00:00 2001 From: tudou <42998776+lhxx121@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:00:19 +0800 Subject: [PATCH 0403/1097] Discovery Bid Adapter: remove calls to navigator (#12088) * Discovery Bid Adapter: delete the fingerprint related code * Discovery Bid Adapter: delete the fingerprint related code --------- Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 4 +- modules/mediagoBidAdapter.js | 4 +- test/spec/modules/discoveryBidAdapter_spec.js | 48 +------------------ 3 files changed, 3 insertions(+), 53 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 41fac90d62a..0dbb1a3a6cb 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -6,7 +6,7 @@ import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLin import { getDevice, getScreenSize } from '../libraries/fpdUtils/deviceInfo.js'; import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; -import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; +import { getHLen } from '../libraries/navigatorData/navigatorData.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -291,8 +291,6 @@ function getParam(validBidRequests, bidderRequest) { }, device: { nbw: getConnectionDownLink(), - hc: getHC(), - dm: getDM() } } } catch (error) {} diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 0f05449eee8..1811f62e6ba 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -9,7 +9,7 @@ import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLin import { getDevice } from '../libraries/fpdUtils/deviceInfo.js'; import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; -import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; +import { getHLen } from '../libraries/navigatorData/navigatorData.js'; // import { config } from '../src/config.js'; // import { isPubcidEnabled } from './pubCommonId.js'; @@ -271,8 +271,6 @@ function getParam(validBidRequests, bidderRequest) { }, device: { nbw: getConnectionDownLink(), - hc: getHC(), - dm: getDM() } }, user: { diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index f3b35e5cda6..e65243fd15a 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -10,7 +10,7 @@ import { } from 'modules/discoveryBidAdapter.js'; import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from '../../../libraries/fpdUtils/pageInfo.js'; import * as utils from 'src/utils.js'; -import {getDM, getHC, getHLen} from '../../../libraries/navigatorData/navigatorData.js'; +import {getHLen} from '../../../libraries/navigatorData/navigatorData.js'; describe('discovery:BidAdapterTests', function () { let sandbox; @@ -660,51 +660,5 @@ describe('discovery Bid Adapter Tests', function () { expect(result).be.undefined; }); }); - - describe('getHC', () => { - it('should return the correct value of hardwareConcurrency when accessible', () => { - const mockWindow = { - top: { - navigator: { - hardwareConcurrency: 4 - } - } - }; - const result = getHC(mockWindow); - expect(result).to.equal(4); - }); - it('should return undefined when accessing win.top.navigator.hardwareConcurrency throws an error', () => { - const mockWindow = { - get top() { - throw new Error('Access denied'); - } - }; - const result = getHC(mockWindow); - expect(result).be.undefined; - }); - }); - - describe('getDM', () => { - it('should return the correct value of deviceMemory when accessible', () => { - const mockWindow = { - top: { - navigator: { - deviceMemory: 4 - } - } - }; - const result = getDM(mockWindow); - expect(result).to.equal(4); - }); - it('should return undefined when accessing win.top.navigator.deviceMemory throws an error', () => { - const mockWindow = { - get top() { - throw new Error('Access denied'); - } - }; - const result = getDM(mockWindow); - expect(result).be.undefined; - }); - }); }); }); From 49cf6102bbff0d4ceac507d947e5dc33a46fbfbf Mon Sep 17 00:00:00 2001 From: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:40:16 +0530 Subject: [PATCH 0404/1097] Doceree AdManager Bid Adapter : changes in fields and test coverage (#12090) * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Update docereeAdManagerBidAdapter.js * added test cases for payload formation in DocereeAdManager * Added support for publisherUrl * added some parameters --------- Co-authored-by: lokesh-doceree Co-authored-by: Patrick McCann --- modules/docereeAdManagerBidAdapter.js | 22 +++-- .../docereeAdManagerBidAdapter_spec.js | 96 +++++++++++++++++-- 2 files changed, 101 insertions(+), 17 deletions(-) diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index f97bfbba6eb..d115e785969 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -70,14 +70,19 @@ export const spec = { }, }; -function getPayload(bid, userData) { +export function getPayload(bid, userData) { if (!userData || !bid) { return false; } const { bidId, params } = bid; - const { placementId } = params; + const { placementId, publisherUrl } = params; const { userid, + email, + firstname, + lastname, + hcpid, + dob, specialization, gender, city, @@ -95,11 +100,11 @@ function getPayload(bid, userData) { const data = { userid: platformUid || userid || '', - email: '', - firstname: '', - lastname: '', + email: email || '', + firstname: firstname || '', + lastname: lastname || '', specialization: specialization || '', - hcpid: '', + hcpid: hcpid || '', gender: gender || '', city: city || '', state: state || '', @@ -113,9 +118,10 @@ function getPayload(bid, userData) { hashedmobile: hashedmobile || '', country: country || '', organization: organization || '', - dob: '', + dob: dob || '', userconsent: 1, - mobile: mobile || '' + mobile: mobile || '', + pageurl: publisherUrl || '' }; return { data, diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js index e367aab9678..b231d5572bf 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/docereeAdManagerBidAdapter.js'; +import { spec, getPayload } from '../../../modules/docereeAdManagerBidAdapter.js'; import { config } from '../../../src/config.js'; describe('docereeadmanager', function () { @@ -7,25 +7,25 @@ describe('docereeadmanager', function () { docereeadmanager: { user: { data: { + userId: '', email: '', firstname: '', lastname: '', - mobile: '', specialization: '', - organization: '', hcpid: '', - dob: '', gender: '', city: '', state: '', - country: '', + zipcode: '', + hashedNPI: '', hashedhcpid: '', hashedemail: '', hashedmobile: '', - userid: '', - zipcode: '', - userconsent: '', - platformUid: '' + country: '', + organization: '', + dob: '', + platformUid: '', + mobile: '', }, }, }, @@ -35,6 +35,7 @@ describe('docereeadmanager', function () { bidder: 'docereeadmanager', params: { placementId: 'DOC-19-1', + publisherUrl: 'xxxxxx.com/xxxx', gdpr: '1', gdprconsent: 'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', @@ -123,4 +124,81 @@ describe('docereeadmanager', function () { .empty; }); }); + + describe('payload', function() { + it('should return payload with the correct data', function() { + const data = { + userId: 'xxxxx', + email: 'xxxx@mail.com', + firstname: 'Xxxxx', + lastname: 'Xxxxxx', + specialization: 'Xxxxxxxxx', + hcpid: 'xxxxxxx', + gender: 'Xxxx', + city: 'Xxxxx', + state: 'Xxxxxx', + zipcode: 'XXXXXX', + hashedNPI: 'xxxxxx', + hashedhcpid: 'xxxxxxx', + hashedemail: 'xxxxxxx', + hashedmobile: 'xxxxxxx', + country: 'Xxxxxx', + organization: 'Xxxxxx', + dob: 'xx-xx-xxxx', + platformUid: 'Xx.xxx.xxxxxx', + mobile: 'XXXXXXXXXX', + } + bid = {...bid, params: {...bid.params, placementId: 'DOC-19-1'}} + const payload = getPayload(bid, data); + const payloadData = payload.data; + expect(payloadData).to.have.all.keys( + 'userid', + 'email', + 'firstname', + 'lastname', + 'specialization', + 'hcpid', + 'gender', + 'city', + 'state', + 'zipcode', + 'hashedNPI', + 'pb', + 'adunit', + 'requestId', + 'hashedhcpid', + 'hashedemail', + 'hashedmobile', + 'country', + 'organization', + 'dob', + 'userconsent', + 'mobile', + 'pageurl' + ); + expect(payloadData.userid).to.equal('Xx.xxx.xxxxxx'); + expect(payloadData.email).to.equal('xxxx@mail.com'); + expect(payloadData.firstname).to.equal('Xxxxx'); + expect(payloadData.lastname).to.equal('Xxxxxx'); + expect(payloadData.specialization).to.equal('Xxxxxxxxx'); + expect(payloadData.hcpid).to.equal('xxxxxxx'); + expect(payloadData.gender).to.equal('Xxxx'); + expect(payloadData.city).to.equal('Xxxxx'); + expect(payloadData.state).to.equal('Xxxxxx'); + expect(payloadData.zipcode).to.equal('XXXXXX'); + expect(payloadData.hashedNPI).to.equal('xxxxxx'); + expect(payloadData.pb).to.equal(1); + expect(payloadData.userconsent).to.equal(1); + expect(payloadData.dob).to.equal('xx-xx-xxxx'); + expect(payloadData.organization).to.equal('Xxxxxx'); + expect(payloadData.country).to.equal('Xxxxxx'); + expect(payloadData.hashedmobile).to.equal('xxxxxxx'); + expect(payloadData.hashedemail).to.equal('xxxxxxx'); + expect(payloadData.hashedhcpid).to.equal('xxxxxxx'); + expect(payloadData.requestId).to.equal('testing'); + expect(payloadData.mobile).to.equal('XXXXXXXXXX'); + expect(payloadData.adunit).to.equal('DOC-19-1'); + expect(payloadData.pageurl).to.equal('xxxxxx.com/xxxx'); + }) + }) }); From 8c4936a5e58a2713623e8b90f80f7552fc752db2 Mon Sep 17 00:00:00 2001 From: afifmahdi95 <55173899+afifmahdi95@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:12:59 +0700 Subject: [PATCH 0405/1097] Feature change on Prebid Request (#12089) Co-authored-by: Ahmad Afif Mahdi --- modules/pstudioBidAdapter.js | 13 +++++-------- modules/pstudioBidAdapter.md | 10 ++++++---- test/spec/modules/pstudioBidAdapter_spec.js | 10 +++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js index 1265d5c546f..cc9310e174b 100644 --- a/modules/pstudioBidAdapter.js +++ b/modules/pstudioBidAdapter.js @@ -6,8 +6,6 @@ import { isNumber, generateUUID, isEmpty, - isFn, - isPlainObject, } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -59,7 +57,7 @@ export const spec = { isBidRequestValid: function (bid) { const params = bid.params || {}; - return !!params.pubid && !!params.floorPrice && isVideoRequestValid(bid); + return !!params.pubid && !!params.adtagid && isVideoRequestValid(bid); }, buildRequests: function (validBidRequests, bidderRequest) { @@ -145,7 +143,7 @@ function buildRequestData(bid, bidderRequest) { function buildBaseObject(bid, bidderRequest) { const firstPartyData = prepareFirstPartyData(bidderRequest.ortb2); - const { pubid, bcat, badv, bapp } = bid.params; + const { pubid, adtagid, bcat, badv, bapp } = bid.params; const { userId } = bid; const uid2Token = userId?.uid2?.id; @@ -168,8 +166,7 @@ function buildBaseObject(bid, bidderRequest) { return { id: bid.bidId, pubid, - floor_price: getBidFloor(bid), - adtagid: bid.adUnitCode, + adtagid: adtagid, ...(bcat && { bcat }), ...(badv && { badv }), ...(bapp && { bapp }), @@ -417,7 +414,7 @@ function validateSizes(sizes) { ); } -function getBidFloor(bid) { +/* function getBidFloor(bid) { if (!isFn(bid.getFloor)) { return bid.params.floorPrice ? bid.params.floorPrice : null; } @@ -431,6 +428,6 @@ function getBidFloor(bid) { return floor.floor; } return null; -} +} */ registerBidder(spec); diff --git a/modules/pstudioBidAdapter.md b/modules/pstudioBidAdapter.md index 0c8c6927f43..a4b3e098cfc 100644 --- a/modules/pstudioBidAdapter.md +++ b/modules/pstudioBidAdapter.md @@ -31,7 +31,8 @@ var adUnits = [ params: { // id of test publisher pubid: '22430f9d-9610-432c-aabe-6134256f11af', - floorPrice: 1.25, + // id of test adtag id + adtagid: '6f3173b9-5623-4a4f-8c62-2b1d24ceb4e6', }, }, ], @@ -53,7 +54,8 @@ var adUnits = [ params: { // id of test publisher pubid: '22430f9d-9610-432c-aabe-6134256f11af', - floorPrice: 1.25, + // id of test adtag id + adtagid: '097c601f-ad09-495b-b70b-d9cf6f1edbc1', }, }, ], @@ -79,7 +81,7 @@ var adUnits = [ bidder: 'pstudio', params: { pubid: '22430f9d-9610-432c-aabe-6134256f11af', // required - floorPrice: 1.15, // required + adtagid: 'b9be4c35-3c12-4fa9-96ba-34b90276208c', // required bcat: ['IAB1-1', 'IAB1-3'], // optional badv: ['nike.com'], // optional bapp: ['com.foo.mygame'], // optional @@ -121,7 +123,7 @@ var videoAdUnits = [ bidder: 'pstudio', params: { pubid: '22430f9d-9610-432c-aabe-6134256f11af', - floorPrice: 1.25, + adtagid: '46e348cf-b79d-43e5-81bc-5954cdf15d7e', badv: ['adidas.com'], }, }, diff --git a/test/spec/modules/pstudioBidAdapter_spec.js b/test/spec/modules/pstudioBidAdapter_spec.js index 52ecb820ed3..2ec38e8dcda 100644 --- a/test/spec/modules/pstudioBidAdapter_spec.js +++ b/test/spec/modules/pstudioBidAdapter_spec.js @@ -18,7 +18,7 @@ describe('PStudioAdapter', function () { bidder: 'pstudio', params: { pubid: '258c2a8d-d2ad-4c31-a2a5-e63001186456', - floorPrice: 1.15, + adtagid: 'aae1aabb-6699-4b5a-9c3f-9ed034b1932c', }, adUnitCode: 'test-div-1', mediaTypes: { @@ -38,7 +38,7 @@ describe('PStudioAdapter', function () { bidder: 'pstudio', params: { pubid: '258c2a8d-d2ad-4c31-a2a5-e63001186456', - floorPrice: 1.15, + adtagid: '34833639-f17c-40bc-9c4b-222b1b7459c7', }, adUnitCode: 'test-div-1', mediaTypes: { @@ -197,7 +197,7 @@ describe('PStudioAdapter', function () { it('should return false when publisher id not found', function () { const localBid = deepClone(bannerBid); delete localBid.params.pubid; - delete localBid.params.floorPrice; + delete localBid.params.adtagid; expect(spec.isBidRequestValid(localBid)).to.equal(false); }); @@ -232,7 +232,7 @@ describe('PStudioAdapter', function () { it('should properly map ids in request payload', function () { expect(bannerPayload.id).to.equal(bannerBid.bidId); - expect(bannerPayload.adtagid).to.equal(bannerBid.adUnitCode); + expect(bannerPayload.adtagid).to.equal(bannerBid.params.adtagid); }); it('should properly map banner mediaType in request payload', function () { @@ -266,7 +266,7 @@ describe('PStudioAdapter', function () { it('should properly set required bidder params in request payload', function () { expect(bannerPayload.pubid).to.equal(bannerBid.params.pubid); - expect(bannerPayload.floor_price).to.equal(bannerBid.params.floorPrice); + expect(bannerPayload.adtagid).to.equal(bannerBid.params.adtagid); }); it('should omit optional bidder params or first-party data from bid request if they are not provided', function () { From 781787e8bc576abe9ef4c8c39b5816e628181820 Mon Sep 17 00:00:00 2001 From: CMDezz <53512796+CMDezz@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:15:35 +0700 Subject: [PATCH 0406/1097] EClickAds Bid Adapter : initial release (#12087) * Initial new bid adapter: ElickAds * EClickAds: update bid adapter * EClickAds: Update getDevice func * EClickAds: update bidder --------- Co-authored-by: vietlv14 Co-authored-by: LeViet --- modules/eclickadsBidAdapter.js | 80 +++++++ modules/eclickadsBidAdapter.md | 55 +++++ test/spec/modules/eclickadsBidAdapter_spec.js | 214 ++++++++++++++++++ 3 files changed, 349 insertions(+) create mode 100644 modules/eclickadsBidAdapter.js create mode 100644 modules/eclickadsBidAdapter.md create mode 100644 test/spec/modules/eclickadsBidAdapter_spec.js diff --git a/modules/eclickadsBidAdapter.js b/modules/eclickadsBidAdapter.js new file mode 100644 index 00000000000..27e3926afe3 --- /dev/null +++ b/modules/eclickadsBidAdapter.js @@ -0,0 +1,80 @@ +import { NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getDevice } from '../libraries/fpdUtils/deviceInfo.js'; + +// ***** ECLICKADS ADAPTER ***** +export const BIDDER_CODE = 'eclickads'; +const DEFAULT_CURRENCY = ['USD']; +const DEFAULT_TTL = 1000; +export const ENDPOINT = 'https://g.eclick.vn/rtb_hb_request?fosp_uid='; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [NATIVE], + isBidRequestValid: (bid) => { + return !!bid && !!bid.params && !!bid.bidder && !!bid.params.zid; + }, + buildRequests: (validBidRequests = [], bidderRequest) => { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + const ortb2ConfigFPD = bidderRequest.ortb2.site.ext?.data || {}; + const ortb2Device = bidderRequest.ortb2.device; + const ortb2Site = bidderRequest.ortb2.site; + + const isMobile = getDevice(); + const imp = []; + const fENDPOINT = ENDPOINT + (ortb2ConfigFPD.fosp_uid || ''); + const request = { + deviceWidth: ortb2Device.w, + deviceHeight: ortb2Device.h, + ua: ortb2Device.ua, + language: ortb2Device.language, + device: isMobile ? 'mobile' : 'desktop', + host: ortb2Site.domain, + page: ortb2Site.page, + imp, + myvne_id: ortb2ConfigFPD.myvne_id || '', + orig_aid: ortb2ConfigFPD.orig_aid, + fosp_aid: ortb2ConfigFPD.fosp_aid, + fosp_uid: ortb2ConfigFPD.fosp_uid, + id: ortb2ConfigFPD.id, + }; + + validBidRequests.map((bid) => { + imp.push({ + requestId: bid.bidId, + adUnitCode: bid.adUnitCode, + zid: bid.params.zid, + }); + }); + + return { + method: 'POST', + url: fENDPOINT, + data: request, + }; + }, + interpretResponse: (serverResponse) => { + const seatbid = serverResponse.body?.seatbid || []; + return seatbid.reduce((bids, bid) => { + return [ + ...bids, + { + id: bid.id, + impid: bid.impid, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + ttl: bid.ttl || DEFAULT_TTL, + requestId: bid.requestId, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + currency: bid.currency || DEFAULT_CURRENCY, + adserverTargeting: { + hb_ad_eclickads: bid.ad, + }, + }, + ]; + }, []); + }, +}; +registerBidder(spec); diff --git a/modules/eclickadsBidAdapter.md b/modules/eclickadsBidAdapter.md new file mode 100644 index 00000000000..39ba19d5249 --- /dev/null +++ b/modules/eclickadsBidAdapter.md @@ -0,0 +1,55 @@ +# Overview + +Module Name: EClickAds Bid Adapter +Type: Bidder Adapter +Maintainer: vietlv14@fpt.com + +# Description + +This module connects to EClickAds exchange for bidding NATIVE ADS via prebid.js + +# Test Parameters + +``` +var adUnits = [{ + code: 'test-div', + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + body: { + required: true, + len: 350 + }, + url: { + required: true + }, + image: { + required: true, + sizes : [300, 175] + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'eclickads', + params: { + zid:"7096" + } + }] +}]; +``` + +# Notes: + +- EClickAdsBidAdapter need serveral params inside bidder config as following + - user.myvne_id + - site.orig_aid + - site.fosp_aid + - site.id + - site.orig_aid +- EClickAdsBidAdapter will set bid.adserverTargeting.hb_ad_eclickads targeting key while submitting bid to AdServer diff --git a/test/spec/modules/eclickadsBidAdapter_spec.js b/test/spec/modules/eclickadsBidAdapter_spec.js new file mode 100644 index 00000000000..aadb8f41a64 --- /dev/null +++ b/test/spec/modules/eclickadsBidAdapter_spec.js @@ -0,0 +1,214 @@ +import { expect } from 'chai'; +import { + spec, + ENDPOINT, + BIDDER_CODE, +} from '../../../modules/eclickadsBidAdapter.js'; +import { NATIVE, BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { deepClone } from '../../../src/utils.js'; +import { config } from '../../../src/config.js'; + +describe('eclickadsBidAdapter', () => { + const bidItem = { + bidder: BIDDER_CODE, + params: { + zid: '7096', + }, + }; + const eclickadsBidderConfigData = { + orig_aid: 'xqf7zdmg7the65ac.1718271138.des', + fosp_aid: '1013000403', + fosp_uid: '7aab24a4663258a2c1d76a08b20f7e6e', + id: '84b2a41c4299bb9b8924423e', + myvne_id: '1013000403', + }; + const bidRequest = { + code: 'test-div', + size: [[320, 85]], + mediaTypes: { + [NATIVE]: { + title: { + required: true, + }, + body: { + required: true, + }, + image: { + required: true, + }, + sponsoredBy: { + required: true, + }, + icon: { + required: false, + }, + }, + }, + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + site: { + name: 'example', + domain: 'page.example.com', + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + ext: { + data: eclickadsBidderConfigData, + }, + }, + }, + }; + + describe('isBidRequestValid', () => { + it('should return false when atleast one of required params is missing', () => { + const bid = deepClone(bidItem); + delete bid.params.zid; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return true if there is correct required params and mediatype', () => { + bidItem.params.mediaTypes == NATIVE; + expect(spec.isBidRequestValid(bidItem)).to.be.true; + }); + it('should return true if there is no size', () => { + const bid = deepClone(bidItem); + delete bid.params.size; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + }); + + describe('buildRequests', () => { + const bidList = [bidItem]; + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(bidList, bidRequest) + ); + const _data = request.data; + + it('should be create a request to server with POST method, data, valid url', () => { + expect(request).to.be.exist; + expect(_data).to.be.exist; + expect(request.method).to.be.exist; + expect(request.method).equal('POST'); + expect(request.url).to.be.exist; + expect(request.url).equal(ENDPOINT + eclickadsBidderConfigData.fosp_uid); + }); + it('should return valid data format if bid array is valid', () => { + expect(_data).to.be.an('object'); + expect(_data).to.has.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'host', + 'ua', + 'page', + 'imp', + 'device', + 'myvne_id', + 'orig_aid', + 'fosp_aid', + 'fosp_uid', + 'id' + ); + expect(_data.deviceWidth).to.be.an('number'); + expect(_data.deviceHeight).to.be.an('number'); + expect(_data.device).to.be.an('string'); + expect(_data.language).to.be.an('string'); + expect(_data.host).to.be.an('string').that.is.equal('page.example.com'); + expect(_data.page) + .to.be.an('string') + .that.is.equal('https://page.example.com/here.html'); + expect(_data.imp).to.be.an('array'); + expect(_data.myvne_id).to.be.an('string'); + expect(_data.orig_aid).to.be.an('string'); + expect(_data.fosp_aid).to.be.an('string'); + expect(_data.myvne_id).to.be.an('string'); + expect(_data.fosp_uid).to.be.an('string'); + expect(_data.id).to.be.an('string'); + }); + + it('should return empty array if there is no bidItem passed', () => { + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests([], bidRequest) + ); + const _data = request.data; + expect(_data.imp).to.be.an('array').that.is.empty; + }); + + it('should return the number of imp equal to the number of bidItem', () => { + expect(_data.imp).to.have.lengthOf(bidList.length); + }); + + it('have to contain required params and correct format for sending to EClickAds', () => { + const item = _data.imp[0]; + expect(item.zid).to.be.an('string'); + }); + }); + + describe('interpretResponse', () => { + const expectedResponse = { + id: '84b2a41c4299bb9b8924423e', + seat: '35809', + seatbid: [ + { + id: 'DBCCDFD5-AACC-424E-8225-4160D35CBE5D', + impid: '35ea1073c745d6c', + adUnitCode: '8871826dc92e', + requestId: '1122839202z3v', + creativeId: '112233ss921v', + netRevenue: true, + currency: ['VND'], + cpm: 0.1844, + ad: 'eclickads_ad_p', + }, + ], + }; + + const response = spec.interpretResponse({ body: expectedResponse }); + + it('should return an array of offers', () => { + expect(response).to.be.an('array'); + }); + + it('should return empty array if there is no offer from server response', () => { + const emptyOfferResponse = deepClone(expectedResponse); + emptyOfferResponse.seatbid = []; + const response = spec.interpretResponse({ body: emptyOfferResponse }); + expect(response).to.be.an('array').that.is.empty; + }); + + it('should return empty array if seatbid from server response is null or missing', () => { + const nullOfferResponse = deepClone(expectedResponse); + nullOfferResponse.seatbid = null; + const response = spec.interpretResponse({ body: nullOfferResponse }); + expect(response).to.be.an('array').that.is.empty; + }); + + it('should return empty array if server response is get error - empty', () => { + const response = spec.interpretResponse({ body: undefined }); + expect(response).to.be.an('array').that.is.empty; + }); + + it('should return correct format, params for each of offers from server response', () => { + const offer = response[0]; + expect(offer.id).to.be.an('string').that.is.not.empty; + expect(offer.impid).to.be.an('string').that.is.not.empty; + expect(offer.requestId).to.be.an('string').that.is.not.empty; + expect(offer.creativeId).to.be.an('string').that.is.not.empty; + expect(offer.netRevenue).to.be.an('boolean'); + expect(offer.ttl).to.be.an('number'); + expect(offer.cpm).to.be.an('number').greaterThan(0); + expect(offer.adserverTargeting).to.be.an('object'); + expect(offer.adserverTargeting['hb_ad_eclickads']).to.be.an('string').that + .is.not.empty; + }); + }); +}); From 7f15127316fe0ca05aa715dada8cf61158cbd2d6 Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Mon, 5 Aug 2024 06:17:45 -0700 Subject: [PATCH 0407/1097] GumGum Bid Adapter: Send new tpl paramter which is topmostLocation (#12069) * I added a new wl parameter to the payload. * Changed wl parameter name to tpl and pulled the data from topmostLocation --- modules/gumgumBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index ecab3d0b970..7f48a562177 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -29,13 +29,14 @@ let invalidRequestIds = {}; let pageViewId = null; // TODO: potential 0 values for browserParams sent to ad server -function _getBrowserParams(topWindowUrl) { +function _getBrowserParams(topWindowUrl, mosttopLocation) { const paramRegex = paramName => new RegExp(`[?#&](${paramName}=(.*?))($|&)`, 'i'); let browserParams = {}; let topWindow; let topScreen; let topUrl; + let mosttopURL let ggad; let ggdeal; let ns; @@ -74,6 +75,7 @@ function _getBrowserParams(topWindowUrl) { topWindow = global.top; topScreen = topWindow.screen; topUrl = topWindowUrl || ''; + mosttopURL = mosttopLocation || ''; } catch (error) { logError(error); return browserParams; @@ -85,6 +87,7 @@ function _getBrowserParams(topWindowUrl) { sw: topScreen.width, sh: topScreen.height, pu: stripGGParams(topUrl), + tpl: mosttopURL, ce: storage.cookiesAreEnabled(), dpr: topWindow.devicePixelRatio || 1, jcsi: JSON.stringify(JCSI), @@ -304,6 +307,7 @@ function buildRequests(validBidRequests, bidderRequest) { const timeout = bidderRequest && bidderRequest.timeout const coppa = config.getConfig('coppa') === true ? 1 : 0; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; + const mosttopLocation = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.topmostLocation _each(validBidRequests, bidRequest => { const { bidId, @@ -443,7 +447,7 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl)) + data: Object.assign(data, _getBrowserParams(topWindowUrl, mosttopLocation)) }); }); return bids; From b8659d41749193b4a10675509d49d3765b94432a Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Mon, 5 Aug 2024 09:24:26 -0400 Subject: [PATCH 0408/1097] Navegg UserID Submodule: conform with pub storage configuration (#12032) * dev/ simple solution * dev: working script and working tests * dev/ final adjustments * dev: minor-changes * hotfix/ importing only the necessary functions --- modules/naveggIdSystem.js | 120 ++++++++-------- modules/naveggIdSystem.md | 8 ++ test/spec/modules/naveggIdSystem_spec.js | 172 +++++++++++++++++++---- 3 files changed, 211 insertions(+), 89 deletions(-) diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 42c6b113566..6f1964b11df 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -6,9 +6,9 @@ */ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { ajax } from '../src/ajax.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -19,61 +19,72 @@ const MODULE_NAME = 'naveggId'; const OLD_NAVEGG_ID = 'nid'; const NAVEGG_ID = 'nvggid'; const BASE_URL = 'https://id.navegg.com/uid/'; -const DEFAULT_EXPIRE = 8 * 24 * 3600 * 1000; -const INVALID_EXPIRE = 3600 * 1000; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); -function getNaveggIdFromApi() { - const callbacks = { - success: response => { - if (response) { - try { - const responseObj = JSON.parse(response); - writeCookie(NAVEGG_ID, responseObj[NAVEGG_ID]); - } catch (error) { - logError(error); +function getIdFromAPI() { + const resp = function (callback) { + ajaxBuilder()( + BASE_URL, + response => { + if (response) { + let responseObj; + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); + const fallbackValue = getNaveggIdFromLocalStorage() || getOldCookie(); + callback(fallbackValue); + } + + if (responseObj && responseObj[NAVEGG_ID]) { + callback(responseObj[NAVEGG_ID]); + } else { + const fallbackValue = getNaveggIdFromLocalStorage() || getOldCookie(); + callback(fallbackValue); + } } - } - }, - error: error => { - logError('Navegg ID fetch encountered an error', error); - } + }, + error => { + logError('Navegg ID fetch encountered an error', error); + const fallbackValue = getNaveggIdFromLocalStorage() || getOldCookie(); + callback(fallbackValue); + }, + {method: 'GET', withCredentials: false}); }; - ajax(BASE_URL, callbacks, undefined, { method: 'GET', withCredentials: false }); -} - -function writeCookie(key, value) { - try { - if (storage.cookiesAreEnabled) { - let expTime = new Date(); - const expires = value ? DEFAULT_EXPIRE : INVALID_EXPIRE; - expTime.setTime(expTime.getTime() + expires); - storage.setCookie(key, value, expTime.toUTCString(), 'none'); - } - } catch (e) { - logError(e); - } + return resp; } -function readnaveggIdFromLocalStorage() { - return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(NAVEGG_ID) : null; +/** + * @returns {string | null} + */ +function readNvgIdFromCookie() { + return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nvg') ? storage.findSimilarCookies('nvg')[0] : null) : null; } - -function readnaveggIDFromCookie() { - return storage.cookiesAreEnabled ? storage.getCookie(NAVEGG_ID) : null; +/** + * @returns {string | null} + */ +function readNavIdFromCookie() { + return storage.cookiesAreEnabled() ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; } - -function readoldnaveggIDFromCookie() { - return storage.cookiesAreEnabled ? storage.getCookie(OLD_NAVEGG_ID) : null; +/** + * @returns {string | null} + */ +function readOldNaveggIdFromCookie() { + return storage.cookiesAreEnabled() ? storage.getCookie(OLD_NAVEGG_ID) : null; } - -function readnvgIDFromCookie() { - return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nvg') ? storage.findSimilarCookies('nvg')[0] : null) : null; +/** + * @returns {string | null} + */ +function getOldCookie() { + const oldCookie = readOldNaveggIdFromCookie() || readNvgIdFromCookie() || readNavIdFromCookie(); + return oldCookie; } - -function readnavIDFromCookie() { - return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; +/** + * @returns {string | null} + */ +function getNaveggIdFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(NAVEGG_ID) : null; } /** @type {Submodule} */ @@ -95,23 +106,16 @@ export const naveggIdSubmodule = { 'naveggId': naveggIdVal.split('|')[0] } : undefined; }, + /** * performs action to obtain id and return a value in the callback's response argument * @function * @param {SubmoduleConfig} config * @return {{id: string | undefined } | undefined} */ - getId() { - const naveggIdString = readnaveggIdFromLocalStorage() || readnaveggIDFromCookie() || getNaveggIdFromApi() || readoldnaveggIDFromCookie() || readnvgIDFromCookie() || readnavIDFromCookie(); - - if (typeof naveggIdString == 'string' && naveggIdString) { - try { - return { id: naveggIdString }; - } catch (error) { - logError(error); - } - } - return undefined; + getId(config, consentData) { + const resp = getIdFromAPI() + return {callback: resp} }, eids: { 'naveggId': { diff --git a/modules/naveggIdSystem.md b/modules/naveggIdSystem.md index f758fbc9d5d..81d9841c78f 100644 --- a/modules/naveggIdSystem.md +++ b/modules/naveggIdSystem.md @@ -10,6 +10,11 @@ pbjs.setConfig({ userSync: { userIds: [{ name: 'naveggId', + storage: { + name : 'nvggid', + type : 'cookie&html5', + expires: 8 + } }] } }); @@ -20,3 +25,6 @@ The below parameters apply only to the naveggID integration. | Param under usersync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | ID of the module - `"naveggId"` | `"naveggId"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"nvggid"` | +| storage.type | Required | String | Must be "`cookie`", "`html5`" or "`cookie&html5`". This is where the results of the user ID will be stored. | `"cookie&html5"` | +| storage.expires | Required | Integer | How long (in days) the user ID information will be stored. | `8` | diff --git a/test/spec/modules/naveggIdSystem_spec.js b/test/spec/modules/naveggIdSystem_spec.js index 2c4f1cda859..4907a63abde 100644 --- a/test/spec/modules/naveggIdSystem_spec.js +++ b/test/spec/modules/naveggIdSystem_spec.js @@ -1,45 +1,155 @@ -import { naveggIdSubmodule, storage } from 'modules/naveggIdSystem.js'; +import { naveggIdSubmodule, storage, getIdFromAPI } from 'modules/naveggIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as ajaxLib from 'src/ajax.js'; -describe('naveggId', function () { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getDataFromLocalStorage'); +const NAVEGGID_CONFIG_COOKIE_HTML5 = { + storage: { + name: 'nvggid', + type: 'cookie&html5', + expires: 8 + } +} + +const MOCK_RESPONSE = { + nvggid: 'test_nvggid' +} + +const MOCK_RESPONSE_NULL = { + nvggid: null +} + +function mockResponse(responseText, isSuccess = true) { + return function(url, callbacks) { + if (isSuccess) { + callbacks.success(responseText) + } else { + callbacks.error(new Error('Mock Error')) + } + } +} + +function deleteAllCookies() { + document.cookie.split(';').forEach(cookie => { + const eqPos = cookie.indexOf('='); + const name = eqPos > -1 ? cookie.substring(0, eqPos) : cookie; + document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; + }); +} + +function setLocalStorage() { + storage.setDataInLocalStorage('nvggid', 'localstorage_value'); +} + +describe('getId', function () { + let ajaxStub, ajaxBuilderStub; + + beforeEach(function() { + ajaxStub = sinon.stub(); + ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').returns(ajaxStub); + }); + + afterEach(function() { + ajaxBuilderStub.restore(); + deleteAllCookies(); + storage.removeDataFromLocalStorage('nvggid'); + }); + + it('should get the value from the existing localstorage', function() { + setLocalStorage(); + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; + + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('localstorage_value')).to.be.true; }); - afterEach(() => { - sandbox.restore(); + + it('should get the value from a nid cookie', function() { + storage.setCookie('nid', 'old_nid_cookie', storage.expires) + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; + + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('old_nid_cookie')).to.be.true; }); - it('should NOT find navegg id', function () { - let id = naveggIdSubmodule.getId(); + it('should get the value from a nav cookie', function() { + storage.setCookie('navId', 'old_nav_cookie', storage.expires) + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; - expect(id).to.be.undefined; + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('old_nav_cookie')).to.be.true; }); - it('getId() should return "test-nid" id from cookie OLD_NAVEGG_ID', function() { - sinon.stub(storage, 'getCookie').withArgs('nid').returns('test-nid'); - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'test-nid'}) - }) + it('should get the value from an old nvg cookie', function() { + storage.setCookie('nvgid', 'old_nvg_cookie', storage.expires) + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; + + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) - it('getId() should return "test-nvggid" id from local storage NAVEGG_ID', function() { - storage.getDataFromLocalStorage.callsFake(() => 'test-ninvggidd') + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('old_nvg_cookie')).to.be.true; + }); - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'test-ninvggidd'}) - }) + it('should return correct value from API response', function(done) { + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; - it('getId() should return "test-nvggid" id from local storage NAV0', function() { - storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nav0') + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE)); + } + }); + apiCallback(callback) - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'nvgid-nav0'}) - }) + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('test_nvggid')).to.be.true; + done(); + }); - it('getId() should return "test-nvggid" id from local storage NVG0', function() { - storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nvg0') + it('should return no value from API response', function(done) { + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'nvgid-nvg0'}) - }) + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith(undefined)).to.be.true; + done(); + }); }); From 58dd99a929cc12de4495200e3d842b03eaa38791 Mon Sep 17 00:00:00 2001 From: danijel-ristic <168181386+danijel-ristic@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:45:18 +0200 Subject: [PATCH 0409/1097] Target Video Ad Server Module: initial release (#11761) * Add target video ad server video support * Fix url decoding * Update server url * Update server subdomain * Add cust_params merging, add all targeting data * fix cust_params append function * Update ad server video module --------- Co-authored-by: Danijel Ristic --- modules/targetVideoAdServerVideo.js | 96 ++++++++++ modules/targetVideoAdServerVideo.md | 26 +++ .../modules/targetVideoAdServerVideo_spec.js | 179 ++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 modules/targetVideoAdServerVideo.js create mode 100644 modules/targetVideoAdServerVideo.md create mode 100644 test/spec/modules/targetVideoAdServerVideo_spec.js diff --git a/modules/targetVideoAdServerVideo.js b/modules/targetVideoAdServerVideo.js new file mode 100644 index 00000000000..e54aa1ab141 --- /dev/null +++ b/modules/targetVideoAdServerVideo.js @@ -0,0 +1,96 @@ +import { registerVideoSupport } from '../src/adServerManager.js'; +import { targeting } from '../src/targeting.js'; +import { buildUrl, isEmpty, isPlainObject, logError, parseUrl } from '../src/utils.js'; + +/** + * Merge all the bid data and publisher-supplied options into a single URL, and then return it. + * @param {Object} options - The options object. + * @param {Object} options.params - params property. + * @param {string} options.params.iu - Required iu property. + * @param {Object} options.adUnit - adUnit property. + * @param {Object} [options.bid] - Optional bid property. + * @returns {string|undefined} The generated URL (if neither bid nor adUnit property is provided the generated string is undefined). + */ +export function buildVideoUrl(options) { + if (!options.params || !options.params?.iu) { + logError('buildVideoUrl: Missing required properties.'); + return; + } + + if (!isPlainObject(options.adUnit) && !isPlainObject(options.bid)) { + logError('buildVideoUrl: Requires either \'adUnit\' or \'bid\' options value'); + return; + } + + const isURL = /^(https?:\/\/)/i; + const defaultParameters = { + autoplay: '[autoplay]', + mute: '[vpmute]', + page_url: '[page_url]', + cachebuster: '[timestamp]', + consent: '[consent]', + } + + const adUnit = options.adUnit; + const bid = options.bid || targeting.getWinningBids(adUnit.code)[0]; + const allTargetingData = getAllTargetingData(options); + const custParams = options.params.cust_params; + let iu = options.params.iu; + + if (isURL.test(iu)) { + const urlComponents = parseUrl(iu, {noDecodeWholeURL: true}); + + for (const [key, value] of Object.entries({...allTargetingData, ...bid.adserverTargeting, ...defaultParameters})) { + if (!urlComponents.search.hasOwnProperty(key)) { + urlComponents.search[key] = value; + } + } + + if (urlComponents.search.cust_params) { + for (const [key, value] of Object.entries(custParams)) { + if (!urlComponents.search.cust_params.includes(key)) { + urlComponents.search.cust_params += '%26' + key + '%3D' + value; + } + } + } + + if (!isEmpty(custParams) && !urlComponents.search.cust_params) { + urlComponents.search.cust_params = Object.entries(custParams).map(([key, value]) => key + '%3D' + value).join('%26'); + } + + return buildUrl(urlComponents); + } + + const search = { + iu, + ...defaultParameters, + ...allTargetingData, + ...bid.adserverTargeting, + }; + + if (!isEmpty(custParams)) { + search.cust_params = Object.entries(custParams).map(([key, value]) => key + '%3D' + value).join('%26'); + } + + return buildUrl({ + protocol: 'https', + host: 'vid.tvserve.io', + pathname: '/ads/bid', + search + }); +} + +function getAllTargetingData(options) { + let allTargetingData = {}; + const adUnit = options && options.adUnit; + if (adUnit) { + let allTargeting = targeting.getAllTargeting(adUnit.code); + allTargetingData = allTargeting ? allTargeting[adUnit.code] : {}; + } + + return allTargetingData; +} + +registerVideoSupport('targetVideo', { + buildVideoUrl, +}); diff --git a/modules/targetVideoAdServerVideo.md b/modules/targetVideoAdServerVideo.md new file mode 100644 index 00000000000..89592b32478 --- /dev/null +++ b/modules/targetVideoAdServerVideo.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: Target Video Ad Server Video +Module Type: Ad Server Video +Maintainer: danijel.ristic@target-video.com +``` + +# Description + +Ad Server Video for target-video.com. Contact danijel.ristic@target-video.com for information. + +# Integration + +The helper function takes the form: + + pbjs.adServers.targetVideo.buildVideoUrl(options) + +Where: + +* **`options`:** configuration object: + * **`params`:** + * **`iu`:** required property used to construct valid VAST tag URL + * **`adUnit`:** ad unit that is being filled + * **`bid` [optional]:** if you override the hardcoded `pbjs.adServers.dfp.buildVideoUrl(...)` logic that picks the first bid you *must* pass in the `bid` object you select + * **`url`:** VAST tag URL, similar to the value returned by `pbjs.adServers.dfp.buildVideoUrl(...)` diff --git a/test/spec/modules/targetVideoAdServerVideo_spec.js b/test/spec/modules/targetVideoAdServerVideo_spec.js new file mode 100644 index 00000000000..a254d76f2c9 --- /dev/null +++ b/test/spec/modules/targetVideoAdServerVideo_spec.js @@ -0,0 +1,179 @@ +import {expect} from 'chai'; +import {buildVideoUrl} from 'modules/targetVideoAdServerVideo.js'; +import {targeting} from 'src/targeting.js'; +import * as utils from 'src/utils.js'; +import {hook} from '../../../src/hook.js'; +import AD_UNIT from 'test/fixtures/video/adUnit.json'; + +describe('TargetVideo Ad Server Video', function() { + before(() => { + hook.ready(); + }); + + let sandbox, bid, adUnit; + const unitUrl = { iu: 'https://example.com/ads/bid?iu=/video' }; + const unitId = { iu: '/video' }; + const allTargeting = { + 'hb_format': 'video', + 'hb_source': 'client', + 'hb_size': '640x480', + 'hb_pb': '5.00', + 'hb_adid': '2c4f6cc3ba128a', + 'hb_bidder': 'testBidder2', + 'hb_format_testBidder2': 'video', + 'hb_source_testBidder2': 'client', + 'hb_size_testBidder2': '640x480', + 'hb_pb_testBidder2': '5.00', + 'hb_adid_testBidder2': '2c4f6cc3ba128a', + 'hb_bidder_testBidder2': 'testBidder2', + 'hb_format_targetVideo': 'video', + 'hb_source_targetVideo': 'client', + 'hb_size_targetVideo': '640x480', + 'hb_pb_targetVideo': '5.00', + 'hb_adid_targetVideo': '44e0b5f2e5cace', + 'hb_bidder_targetVideo': 'targetVideo' + }; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + bid = { + videoCacheKey: '123', + adserverTargeting: { + hb_adid: 'ad_id', + hb_cache_id: '123', + hb_uuid: '123', + }, + }; + adUnit = utils.deepClone(AD_UNIT); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return undefined if required properties are missing', () => { + const url1 = buildVideoUrl({ params: {} }); + const url2 = buildVideoUrl({ adUnit: {} }); + const url3 = buildVideoUrl({ bid: {} }); + + expect(url1).to.be.undefined; + expect(url2).to.be.undefined; + expect(url3).to.be.undefined; + }); + + it('should require options.adUnit or options.bid', () => { + const options = { + params: { ...unitUrl } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url1 = buildVideoUrl(options); + expect(url1).to.be.undefined; + + const url2 = buildVideoUrl(Object.assign(options, { adUnit })); + expect(url2).to.be.string; + + const url3 = buildVideoUrl(Object.assign(options, { bid })); + expect(url3).to.be.string; + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); + + it('should build URL correctly with valid parameters', () => { + const optionsUrl = { + params: { ...unitUrl } + }; + + const optionsId = { + params: { ...unitId } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url1 = buildVideoUrl(Object.assign(optionsUrl, { bid, adUnit })); + const url2 = buildVideoUrl(Object.assign(optionsId, { bid, adUnit })); + + expect(url1).to.include('https://example.com/ads/bid?iu=/video'); + expect(url1).to.include('hb_adid=ad_id'); + expect(url1).to.include('hb_cache_id=123'); + expect(url1).to.include('hb_uuid=123'); + + expect(url2).to.include('https://vid.tvserve.io/ads/bid?iu=/video'); + expect(url2).to.include('hb_adid=ad_id'); + expect(url2).to.include('hb_cache_id=123'); + expect(url2).to.include('hb_uuid=123'); + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); + + it('should include default query parameters', () => { + const options = { + params: { ...unitUrl } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url = buildVideoUrl(Object.assign(options, { bid, adUnit })); + + expect(url).to.include('autoplay=[autoplay]'); + expect(url).to.include('mute=[vpmute]'); + expect(url).to.include('page_url=[page_url]'); + expect(url).to.include('cachebuster=[timestamp]'); + expect(url).to.include('consent=[consent]'); + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); + + it('should add cust_params correctly', () => { + const optionsUrl = { + params: { + ...unitUrl, + cust_params: { + targeting_1: 'foo', + targeting_2: 'bar' + } + } + }; + + const optionsId = { + params: { + ...unitId, + cust_params: { + targeting_1: 'foo', + targeting_2: 'bar' + } + } + }; + + const optionsToMergeCustParams = { + params: { + iu: 'https://example.com/ads/bid?iu=/video&cust_params=targeting_1%3Dbaz', + cust_params: { + targeting_1: 'foo', + targeting_2: 'bar' + } + } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url1 = buildVideoUrl(Object.assign(optionsUrl, { bid, adUnit })); + const url2 = buildVideoUrl(Object.assign(optionsId, { bid, adUnit })); + const url3 = buildVideoUrl(Object.assign(optionsToMergeCustParams, { bid, adUnit })); + + expect(url1).to.include('cust_params=targeting_1%3Dfoo%26targeting_2%3Dbar'); + expect(url2).to.include('cust_params=targeting_1%3Dfoo%26targeting_2%3Dbar'); + expect(url3).to.include('cust_params=targeting_1%3Dbaz%26targeting_2%3Dbar'); + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); +}); From e10a58c0c5abbf31141fade29fa67dea77f4b03d Mon Sep 17 00:00:00 2001 From: mkomorski Date: Mon, 5 Aug 2024 15:52:36 +0200 Subject: [PATCH 0410/1097] #9573 adding onAddRenderSucceeded to bidder spec (#11998) Co-authored-by: Marcin Komorski --- src/adRendering.js | 3 +++ src/adapterManager.js | 4 ++++ test/spec/unit/adRendering_spec.js | 28 +++++++++++++++++++++- test/spec/unit/core/adapterManager_spec.js | 10 ++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/adRendering.js b/src/adRendering.js index 9eb894ec190..e4acfd2d69a 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -18,6 +18,7 @@ import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; import {GreedyPromise} from './utils/promise.js'; +import adapterManager from './adapterManager.js'; const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; @@ -68,6 +69,8 @@ export function emitAdRenderSucceeded({ doc, bid, id }) { if (bid) data.bid = bid; if (id) data.adId = id; + adapterManager.callAdRenderSucceededBidder(bid.adapterCode || bid.bidder, bid); + events.emit(AD_RENDER_SUCCEEDED, data); } diff --git a/src/adapterManager.js b/src/adapterManager.js index 2d4bd7a5f6b..a571ba62ddc 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -688,6 +688,10 @@ adapterManager.callBidderError = function(bidder, error, bidderRequest) { tryCallBidderMethod(bidder, 'onBidderError', param); }; +adapterManager.callAdRenderSucceededBidder = function (bidder, bid) { + tryCallBidderMethod(bidder, 'onAdRenderSucceeded', bid); +} + function resolveAlias(alias) { const seen = new Set(); while (_aliasRegistry.hasOwnProperty(alias) && !seen.has(alias)) { diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index 4d0962a0b2c..971e92224e9 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -1,7 +1,7 @@ import * as events from 'src/events.js'; import * as utils from 'src/utils.js'; import { - doRender, getBidToRender, + doRender, emitAdRenderSucceeded, getBidToRender, getRenderingData, handleCreativeEvent, handleNativeMessage, @@ -12,6 +12,7 @@ import {expect} from 'chai/index.mjs'; import {config} from 'src/config.js'; import {VIDEO} from '../../../src/mediaTypes.js'; import {auctionManager} from '../../../src/auctionManager.js'; +import adapterManager from '../../../src/adapterManager.js'; describe('adRendering', () => { let sandbox; @@ -267,4 +268,29 @@ describe('adRendering', () => { sinon.assert.calledWith(fireTrackers, data, bid); }) }) + + describe('onAdRenderSucceeded', () => { + let mockAdapterSpec, bids; + beforeEach(() => { + mockAdapterSpec = { + onAdRenderSucceeded: sinon.stub() + }; + adapterManager.bidderRegistry['mockBidder'] = { + bidder: 'mockBidder', + getSpec: function () { return mockAdapterSpec; }, + }; + bids = [ + { bidder: 'mockBidder', params: { placementId: 'id' } }, + ]; + }); + + afterEach(function () { + delete adapterManager.bidderRegistry['mockBidder']; + }); + + it('should invoke onAddRenderSucceeded on emitAdRenderSucceeded', () => { + emitAdRenderSucceeded({ bid: bids[0] }); + sinon.assert.called(mockAdapterSpec.onAdRenderSucceeded); + }); + }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 9132aaeb903..a68634501ac 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -428,6 +428,16 @@ describe('adapterManager tests', function () { sinon.assert.notCalled(criteoSpec.onBidViewable); }) }); + + describe('onAdRenderSucceeded', function () { + beforeEach(() => { + criteoSpec.onAdRenderSucceeded = sinon.stub() + }); + it('should call spec\'s onAdRenderSucceeded callback', function () { + adapterManager.callAdRenderSucceededBidder(bids[0].bidder, bids[0]); + sinon.assert.called(criteoSpec.onAdRenderSucceeded); + }); + }); }) describe('onBidderError', function () { const bidder = 'appnexus'; From 81459cd6374a582b6377599196507a5b38a169ce Mon Sep 17 00:00:00 2001 From: Jonathan Nadarajah <50102657+jogury@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:23:19 +0200 Subject: [PATCH 0411/1097] Ogury Adapter add gpid in bid request (#12091) --- modules/oguryBidAdapter.js | 7 ++- test/spec/modules/oguryBidAdapter_spec.js | 54 +++++++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 3cf78da4e3a..0dd07a26e2c 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -1,7 +1,7 @@ 'use strict'; import {BANNER} from '../src/mediaTypes.js'; -import {getWindowSelf, getWindowTop, isFn, logWarn} from '../src/utils.js'; +import {getWindowSelf, getWindowTop, isFn, logWarn, deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ajax} from '../src/ajax.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; @@ -12,7 +12,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.6.0'; +const ADAPTER_VERSION = '1.6.1'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -129,6 +129,8 @@ function buildRequests(validBidRequests, bidderRequest) { openRtbBidRequestBanner.user.ext.eids = bidRequest.userIdAsEids } + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + openRtbBidRequestBanner.imp.push({ id: bidRequest.bidId, tagid: bidRequest.params.adUnitId, @@ -138,6 +140,7 @@ function buildRequests(validBidRequests, bidderRequest) { }, ext: { ...bidRequest.params, + ...(gpid && {gpid}), timeSpentOnPage: document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 } }); diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index ea923a18e57..861c50b1a98 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -13,6 +13,11 @@ describe('OguryBidAdapter', function () { bidRequests = [ { adUnitCode: 'adUnitCode', + ortb2Imp: { + ext: { + gpid: 'gpid' + } + }, auctionId: 'auctionId', bidId: 'bidId', bidder: 'ogury', @@ -394,6 +399,7 @@ describe('OguryBidAdapter', function () { }, ext: { ...bidRequests[0].params, + gpid: bidRequests[0].ortb2Imp.ext.gpid, timeSpentOnPage: stubbedCurrentTime } }, { @@ -441,7 +447,7 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.6.0' + adapterversion: '1.6.1' }, device: { w: stubbedWidth, @@ -824,7 +830,47 @@ describe('OguryBidAdapter', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); expect(request.data).to.deep.equal(expectedRequestWithUnsupportedFloorCurrency); }); - }); + + it('should not add gpid if ortb2 undefined', () => { + const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) + + delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; + delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; + + const validBidRequests = utils.deepClone(bidRequests); + delete validBidRequests[0].ortb2Imp.ext.gpid; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + }); + + it('should not add gpid if gpid undefined', () => { + const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) + + delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; + delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; + + const validBidRequests = utils.deepClone(bidRequests); + validBidRequests[0] = { + ...validBidRequests[0], + ortb2Imp: { + ext: {} + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + }); + + it('should send gpid in bid request', function() { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestObject); + expect(request.data.imp[0].ext.gpid).to.be.a('string'); + expect(request.data.imp[1].ext.gpid).to.be.undefined + }) + }) describe('interpretResponse', function () { let openRtbBidResponse = { @@ -891,7 +937,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.6.0', + adapterVersion: '1.6.1', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -908,7 +954,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.6.0', + adapterVersion: '1.6.1', prebidVersion: '$prebid.version$' }] From 22d913e59e75f01ac7a3b35cfd7989db3e1c2136 Mon Sep 17 00:00:00 2001 From: MykhailoTeqBlaze Date: Wed, 7 Aug 2024 16:04:21 +0300 Subject: [PATCH 0412/1097] fix isBidRequestValid() (#12093) --- libraries/teqblazeUtils/bidderUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index ce061ac7f3c..96195eed70d 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -102,7 +102,7 @@ const checkIfObjectHasKey = (keys, obj, mode = 'some') => { const val = obj[key]; if (mode === 'some' && val) return true; - if (!val) return false; + if (mode === 'every' && !val) return false; } return mode === 'every'; From 256556202a284065da1ac235391882ef4def7032 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Thu, 8 Aug 2024 05:01:09 -0700 Subject: [PATCH 0413/1097] openxBidAdapter remove PAF, bugfix ortbConverter response (#12105) --- modules/openxBidAdapter.js | 7 +-- test/spec/modules/openxBidAdapter_spec.js | 63 ++++++----------------- 2 files changed, 16 insertions(+), 54 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 8b16aa1a84e..19da19e661f 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -80,11 +80,6 @@ const converter = ortbConverter({ bidResponse.meta.advertiserId = bid.ext.buyer_id; bidResponse.meta.brandId = bid.ext.brand_id; } - const {ortbResponse} = context; - if (ortbResponse.ext && ortbResponse.ext.paf) { - bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); - bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); - } return bidResponse; }, response(buildResponse, bidResponses, ortbResponse, context) { @@ -117,7 +112,7 @@ const converter = ortbConverter({ paapi: fledgeAuctionConfigs, } } else { - return response.bids + return response } }, overrides: { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index e895574b9aa..dbc036c860b 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1109,7 +1109,7 @@ describe('OpenxRtbAdapter', function () { let bid; context('when there is an nbr response', function () { - let bids; + let response; beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1131,16 +1131,16 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = {nbr: 0}; // Unknown error - bids = spec.interpretResponse({body: bidResponse}, bidRequest); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); it('should not return any bids', function () { - expect(bids.length).to.equal(0); + expect(response.bids.length).to.equal(0); }); }); context('when no seatbid in response', function () { - let bids; + let response; beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1162,16 +1162,16 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = {ext: {}, id: 'test-bid-id'}; - bids = spec.interpretResponse({body: bidResponse}, bidRequest); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); it('should not return any bids', function () { - expect(bids.length).to.equal(0); + expect(response.bids.length).to.equal(0); }); }); context('when there is no response', function () { - let bids; + let response; beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1193,11 +1193,11 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = ''; // Unknown error - bids = spec.interpretResponse({body: bidResponse}, bidRequest); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); it('should not return any bids', function () { - expect(bids.length).to.equal(0); + expect(response.bids.length).to.equal(0); }); }); @@ -1232,19 +1232,11 @@ describe('OpenxRtbAdapter', function () { ext: { dsp_id: '123', buyer_id: '456', - brand_id: '789', - paf: { - content_id: 'paf_content_id' - } + brand_id: '789' } }] }], - cur: 'AUS', - ext: { - paf: { - transmission: {version: '12'} - } - } + cur: 'AUS' }; context('when there is a response, the common response properties', function () { @@ -1253,7 +1245,7 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = deepClone(SAMPLE_BID_RESPONSE); - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; }); it('should return a price', function () { @@ -1308,33 +1300,8 @@ describe('OpenxRtbAdapter', function () { it('should return adomain', function () { expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); }); - - it('should return paf fields', function () { - const paf = { - transmission: {version: '12'}, - content_id: 'paf_content_id' - } - expect(bid.meta.paf).to.deep.equal(paf); - }); }); - context('when there is more than one response', () => { - let bids; - beforeEach(function () { - bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - bidResponse = deepClone(SAMPLE_BID_RESPONSE); - bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); - bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - - bids = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - it('should not confuse paf content_id', () => { - expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); - }); - }) - context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ @@ -1371,7 +1338,7 @@ describe('OpenxRtbAdapter', function () { cur: 'AUS' }; - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; }); it('should return the proper mediaType', function () { @@ -1420,14 +1387,14 @@ describe('OpenxRtbAdapter', function () { }); it('should return the proper mediaType', function () { - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); it('should return the proper mediaType', function () { const winUrl = 'https//my.win.url'; bidResponse.seatbid[0].bid[0].nurl = winUrl - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; expect(bid.vastUrl).to.equal(winUrl); }); From 6998a3d7f52de38117e80f3a14ecd8300f16e428 Mon Sep 17 00:00:00 2001 From: Dmitry Sinev Date: Thu, 8 Aug 2024 15:04:14 +0300 Subject: [PATCH 0414/1097] Appnexus Bid Adapter: fix parse of the encoded string to check for ast_override_div argument (#12106) --- modules/appnexusBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 387df07a8b4..4935158d21c 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -747,7 +747,7 @@ function bidToTag(bid) { // page.html?ast_override_div=divId:creativeId,divId2:creativeId2 const overrides = getParameterByName('ast_override_div'); if (isStr(overrides) && overrides !== '') { - const adUnitOverride = overrides.split(',').find((pair) => pair.startsWith(`${bid.adUnitCode}:`)); + const adUnitOverride = decodeURIComponent(overrides).split(',').find((pair) => pair.startsWith(`${bid.adUnitCode}:`)); if (adUnitOverride) { const forceCreativeId = adUnitOverride.split(':')[1]; if (forceCreativeId) { From 439d6edf08860da91fe6f4b2ab4427be31ff0ce7 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Thu, 8 Aug 2024 14:07:41 +0200 Subject: [PATCH 0415/1097] Richaudience Bid Adapter : add compability with DSA (#12099) * Update richaudienceBidAdapter.md Update maintainer e-mail to integrations@richaudience.com * Add richaudienceBidAdapter.js file * Add richaudienceBidAdapter_spec.js * Update richaudienceBidAdapter.js * RichaudienceBidAdapter add compability with DSA * RichaudienceBidAdapter add compability with DSA * RichaudienceBidAdapter add compability with DSA --------- Co-authored-by: Patrick McCann Co-authored-by: sergigimenez --- modules/richaudienceBidAdapter.js | 9 ++- .../modules/richaudienceBidAdapter_spec.js | 62 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index c6a3a5427bd..776b855dfba 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -55,7 +55,9 @@ export const spec = { cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','), schain: bid.schain, - gpid: raiSetPbAdSlot(bid) + gpid: raiSetPbAdSlot(bid), + dsa: setDSA(bid), + userData: deepAccess(bid, 'ortb2.user.data') }; REFERER = (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null) @@ -373,3 +375,8 @@ function raiGetTimeoutURL(data) { } return url } + +function setDSA(bid) { + let dsa = bid?.ortb2?.regs?.ext?.dsa ? bid?.ortb2?.regs?.ext?.dsa : null; + return dsa; +} diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index d2b173f53df..782955103b3 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -35,6 +35,53 @@ describe('Richaudience adapter tests', function () { user: {} }]; + var DEFAULT_PARAMS_NEW_DSA = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: 2, + pubrender: 1, + datatopub: 1, + transparency: [ + { + domain: 'richaudience.com', + dsaparams: [1, 3, 6] + }, + { + domain: 'adpone.com', + dsaparams: [8, 10, 12] + }, + { + domain: 'sunmedia.com', + dsaparams: [14, 16, 18] + } + ] + } + } + } + }, + user: {} + }]; + var DEFAULT_PARAMS_NEW_SIZES_GPID = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', @@ -893,6 +940,21 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('schain').to.deep.equal(schain); }) + it('should pass DSA', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_DSA, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('dsa').property('dsarequired').and.to.equal(2) + expect(requestContent).to.have.property('dsa').property('pubrender').and.to.equal(1); + expect(requestContent).to.have.property('dsa').property('datatopub').and.to.equal(1); + expect(requestContent.dsa.transparency[0]).to.have.property('domain').and.to.equal('richaudience.com'); + }) + it('should pass gpid', function() { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { gdprConsent: { From 5f7e86eaa0f94937444a787e510e292c3e07071f Mon Sep 17 00:00:00 2001 From: Alexander Pykhteyev Date: Thu, 8 Aug 2024 20:20:52 +0700 Subject: [PATCH 0416/1097] Add new TGM adapter (#12100) Co-authored-by: apykhteyev --- modules/limelightDigitalBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 6c589f536c2..fc5d608f133 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -32,7 +32,7 @@ function isBidResponseValid(bid) { export const spec = { code: BIDDER_CODE, - aliases: ['pll', 'iionads', 'apester', 'adsyield'], + aliases: ['pll', 'iionads', 'apester', 'adsyield', 'tgm'], supportedMediaTypes: [BANNER, VIDEO], /** From c0048a34ae3caadd04cc54e80d9260a883aaded9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 8 Aug 2024 20:00:44 +0000 Subject: [PATCH 0417/1097] Prebid 9.9.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21ceda42b53..2a1040da177 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.9.0-pre", + "version": "9.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.9.0-pre", + "version": "9.9.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 7502dc8794e..ae5c36806fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.9.0-pre", + "version": "9.9.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 6b96cba91867304b7c344d786e7eb3d4d76aa5da Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 8 Aug 2024 20:00:44 +0000 Subject: [PATCH 0418/1097] Increment version to 9.10.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a1040da177..e2945d258a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.9.0", + "version": "9.10.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.9.0", + "version": "9.10.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index ae5c36806fd..95b24e00427 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.9.0", + "version": "9.10.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 2a62970870872c61fda56948b94b8a1649208987 Mon Sep 17 00:00:00 2001 From: Gena Date: Fri, 9 Aug 2024 17:31:41 +0300 Subject: [PATCH 0419/1097] deprecate old copper6 alias (#12112) --- modules/adtelligentBidAdapter.js | 2 -- test/spec/modules/adtelligentBidAdapter_spec.js | 1 - 2 files changed, 3 deletions(-) diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index afdc49a71f4..6d4c0b6ed6a 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -25,7 +25,6 @@ const HOST_GETTERS = { janet: () => 'ghb.bidder.jmgads.com', ocm: () => 'ghb.cenarius.orangeclickmedia.com', '9dotsmedia': () => 'ghb.platform.audiodots.com', - copper6: () => 'ghb.app.copper6.com', indicue: () => 'ghb.console.indicue.com', } const getUri = function (bidderCode) { @@ -48,7 +47,6 @@ export const spec = { { code: 'selectmedia', gvlid: 775 }, { code: 'ocm', gvlid: 1148 }, '9dotsmedia', - 'copper6', 'indicue', ], supportedMediaTypes: [VIDEO, BANNER], diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 0acbaa06f5b..b03bba95357 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -16,7 +16,6 @@ const aliasEP = { 'janet': 'https://ghb.bidder.jmgads.com/v2/auction/', 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', - 'copper6': 'https://ghb.app.copper6.com/v2/auction/', 'indicue': 'https://ghb.console.indicue.com/v2/auction/', }; From 776b7fd253a079133f3c8bc7b5cbe933f30ba984 Mon Sep 17 00:00:00 2001 From: Alexander Pykhteyev Date: Sat, 10 Aug 2024 02:23:19 +0700 Subject: [PATCH 0420/1097] LimelightDigital Adapter: Add support of ortb2 and ortb2Imp objects (#12078) * Add support of ortb2 and ortb2Imp objects * Fix tests * Fix tests --------- Co-authored-by: apykhteyev --- modules/limelightDigitalBidAdapter.js | 3 ++ .../limelightDigitalBidAdapter_spec.js | 30 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index fc5d608f133..f69ae8f76eb 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -128,6 +128,8 @@ function buildRequest(winTop, host, adUnits, bidderRequest) { deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, adUnits: adUnits, + ortb2: bidderRequest?.ortb2, + refererInfo: bidderRequest?.refererInfo, sua: bidderRequest?.ortb2?.device?.sua, page: bidderRequest?.ortb2?.site?.page || bidderRequest?.refererInfo?.page } @@ -164,6 +166,7 @@ function buildPlacement(bidRequest) { } }), type: bidRequest.params.adUnitType.toUpperCase(), + ortb2Imp: bidRequest.ortb2Imp, publisherId: bidRequest.params.publisherId, userIdAsEids: bidRequest.userIdAsEids, supplyChain: bidRequest.schain, diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 51cf8cd14ee..b13beb26d28 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -26,7 +26,11 @@ describe('limelightDigitalAdapter', function () { }, ortb2Imp: { ext: { - tid: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -70,7 +74,11 @@ describe('limelightDigitalAdapter', function () { sizes: [[350, 200]], ortb2Imp: { ext: { - tid: '068867d1-46ec-40bb-9fa0-e24611786fb4', + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -120,7 +128,11 @@ describe('limelightDigitalAdapter', function () { sizes: [[800, 600]], ortb2Imp: { ext: { + gpid: '/1111/homepage#300x250', tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -169,7 +181,11 @@ describe('limelightDigitalAdapter', function () { }, ortb2Imp: { ext: { + gpid: '/1111/homepage#300x250', tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -235,7 +251,9 @@ describe('limelightDigitalAdapter', function () { 'secure', 'adUnits', 'sua', - 'page' + 'page', + 'ortb2', + 'refererInfo' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); @@ -254,7 +272,8 @@ describe('limelightDigitalAdapter', function () { 'custom2', 'custom3', 'custom4', - 'custom5' + 'custom5', + 'ortb2Imp' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -268,6 +287,7 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.custom3).to.be.a('string'); expect(adUnit.custom4).to.be.a('string'); expect(adUnit.custom5).to.be.a('string'); + expect(adUnit.ortb2Imp).to.be.an('object'); }) expect(data.sua.browsers).to.be.a('array'); expect(data.sua.platform).to.be.a('array'); @@ -275,6 +295,7 @@ describe('limelightDigitalAdapter', function () { expect(data.sua.architecture).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.page).to.be.equal('testPage'); + expect(data.ortb2).to.be.an('object'); }) }) it('Returns valid URL', function () { @@ -719,4 +740,5 @@ function validateAdUnit(adUnit, bid) { expect(adUnit.publisherId).to.equal(bid.params.publisherId); expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); expect(adUnit.supplyChain).to.deep.equal(bid.schain); + expect(adUnit.ortb2Imp).to.deep.equal(bid.ortb2Imp); } From 284058f56faf0216c1c3a30697749037dff01898 Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Sat, 10 Aug 2024 01:31:13 +0300 Subject: [PATCH 0421/1097] update configuration example (#12109) --- modules/intentIqIdSystem.md | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 74208169eaa..8ddbaf08f60 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -44,38 +44,13 @@ Please find below list of paramters that could be used in configuring Intent IQ ### Configuration example -```javascript -pbjs.setConfig({ - userSync: { - userIds: [ - { - name: "intentIqId", - params: { - partner: 123456, // valid partner id - callback: (data, group) => window.pbjs.requestBids(), - }, - storage: { - type: "html5", - name: "intentIqId", // set localstorage with this name - expires: 60, - refreshInSeconds: 4 * 3600, // refresh ID every 4 hours to ensure it's fresh - }, - }, - ], - syncDelay: 3000, - }, -}); -``` - ```javascript pbjs.setConfig({ userSync: { userIds: [{ name: "intentIqId", params: { - partner: 123456 // valid partner id - pcid: PCID_VARIABLE, // string value, dynamically loaded into a variable before setting the configuration - pai: PAI_VARIABLE , // string value, dynamically loaded into a variable before setting the configuration + partner: 123456, // valid partner id timeoutInMillis: 500, browserBlackList: "chrome", callback: (data, group) => window.pbjs.requestBids() @@ -83,10 +58,10 @@ pbjs.setConfig({ storage: { type: "html5", name: "intentIqId", // set localstorage with this name - expires: 60 + expires: 0, + refreshInSeconds: 0 } - }], - syncDelay: 3000 + }] } }); ``` From b74b3668caa810bc010d87e359fb654594bc529e Mon Sep 17 00:00:00 2001 From: Bohdan <25197509+BohdanVV@users.noreply.github.com> Date: Sun, 11 Aug 2024 14:19:59 +0200 Subject: [PATCH 0422/1097] FPD Enrichment: Replace device values `w` and `h` with screen size; add `ext.vpw` and `ext.vph` (#12108) --- src/fpd/enrichment.js | 13 ++++++++++-- test/spec/fpd/enrichment_spec.js | 21 ++++++++++++++++--- test/spec/modules/asoBidAdapter_spec.js | 12 +++++------ .../modules/prebidServerBidAdapter_spec.js | 12 +++++------ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 5024a0ee184..4c3fd4b6c07 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -99,8 +99,13 @@ const ENRICHMENTS = { }, device() { return winFallback((win) => { - const w = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; - const h = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + // screen.width and screen.height are the physical dimensions of the screen + const w = win.screen.width; + const h = win.screen.height; + + // vpw and vph are the viewport dimensions of the browser window + const vpw = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; + const vph = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; const device = { w, @@ -108,6 +113,10 @@ const ENRICHMENTS = { dnt: getDNT() ? 1 : 0, ua: win.navigator.userAgent, language: win.navigator.language.split('-').shift(), + ext: { + vpw, + vph, + }, }; if (win.navigator?.webdriver) { diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 7fa9075e802..cec6597f2d4 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -34,7 +34,11 @@ describe('FPD enrichment', () => { }, document: { querySelector: sinon.stub() - } + }, + screen: { + width: 1, + height: 1, + }, }; } @@ -156,8 +160,8 @@ describe('FPD enrichment', () => { }); testWindows(() => win, () => { it('sets w/h', () => { - win.innerHeight = 123; - win.innerWidth = 321; + win.screen.width = 321; + win.screen.height = 123; return fpd().then(ortb2 => { sinon.assert.match(ortb2.device, { w: 321, @@ -166,6 +170,17 @@ describe('FPD enrichment', () => { }); }); + it('sets ext.vpw/vph', () => { + win.innerWidth = 12; + win.innerHeight = 21; + return fpd().then(ortb2 => { + sinon.assert.match(ortb2.device.ext, { + vpw: 12, + vph: 21, + }); + }); + }); + describe('ext.webdriver', () => { it('when navigator.webdriver is available', () => { win.navigator.webdriver = true; diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index 7839e0ef227..a37e4647c7a 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -200,8 +200,8 @@ describe('Adserver.Online bidding adapter', function () { expect(payload.site.page).to.equal('https://example.com/page.html'); expect(payload.device).to.exist; - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.device.w).to.equal(window.screen.width); + expect(payload.device.h).to.equal(window.screen.height); expect(payload.imp).to.have.lengthOf(1); @@ -229,8 +229,8 @@ describe('Adserver.Online bidding adapter', function () { expect(payload.site.page).to.equal('https://example.com/page.html'); expect(payload.device).to.exist; - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.device.w).to.equal(window.screen.width); + expect(payload.device.h).to.equal(window.screen.height); expect(payload.imp).to.have.lengthOf(1); @@ -258,8 +258,8 @@ describe('Adserver.Online bidding adapter', function () { expect(payload.site.page).to.equal('https://example.com/page.html'); expect(payload.device).to.exist; - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.device.w).to.equal(window.screen.width); + expect(payload.device.h).to.equal(window.screen.height); expect(payload.imp).to.have.lengthOf(1); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a6d91a6309b..46d7b313f3a 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1088,8 +1088,8 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.innerWidth, - h: window.innerHeight + w: window.screen.width, + h: window.screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1120,8 +1120,8 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.innerWidth, - h: window.innerHeight + w: window.screen.width, + h: window.screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1480,8 +1480,8 @@ describe('S2S Adapter', function () { adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { - w: window.innerWidth, - h: window.innerHeight + w: window.screen.width, + h: window.screen.height, }) expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); From 09e74847b2d71600044f1f4a8c64376f9d7100cc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Sun, 11 Aug 2024 05:32:23 -0700 Subject: [PATCH 0423/1097] Core: fix broken native resizing (#12096) * Core: fix broken native resizing * fix test --- src/secureCreatives.js | 2 +- test/spec/unit/secureCreatives_spec.js | 47 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 9fa01c990c2..630d4986c3e 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -115,7 +115,7 @@ function handleNativeRequest(reply, data, adObject) { reply(getAllAssetsMessage(data, adObject)); break; default: - handleNativeMessage(data, adObject, {resizeFn: getResizer(adObject)}) + handleNativeMessage(data, adObject, {resizeFn: getResizer(data.adId, adObject)}) } } diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 664ba51ff1f..95301302dab 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -413,6 +413,53 @@ describe('secureCreatives', () => { stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; }); }); + + describe('resizing', () => { + let container, slot; + before(() => { + const [gtag, atag] = [window.googletag, window.apntag]; + delete window.googletag; + delete window.apntag; + after(() => { + window.googletag = gtag; + window.apntag = atag; + }) + }) + beforeEach(() => { + pushBidResponseToAuction({ + adUnitCode: 'mock-au' + }); + container = document.createElement('div'); + container.id = 'mock-au'; + slot = document.createElement('div'); + container.appendChild(slot); + document.body.appendChild(container) + }); + afterEach(() => { + if (container) { + document.body.removeChild(container); + } + }) + it('should handle resize request', () => { + const ev = makeEvent({ + data: JSON.stringify({ + adId: bidId, + message: 'Prebid Native', + action: 'resizeNativeHeight', + width: 123, + height: 321 + }), + source: { + postMessage: sinon.stub() + }, + origin: 'any origin' + }); + return receive(ev).then(() => { + expect(slot.style.width).to.eql('123px'); + expect(slot.style.height).to.eql('321px'); + }); + }) + }) }); describe('Prebid Event', () => { From 54efa3c0f14e9f24c33c8c891b0f8b55320829c5 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Sun, 11 Aug 2024 14:37:01 +0200 Subject: [PATCH 0424/1097] Weborama RTD Module: BUGFIX on user-centric profile validation (#12095) * update unit tests * fix code and doc * improve integration example * add extra test * improve logging * fix lint issues --- .../gpt/weboramaRtdProvider_example.html | 330 +++++++++--------- modules/weboramaRtdProvider.js | 286 +++++++++------ modules/weboramaRtdProvider.md | 22 +- test/spec/modules/weboramaRtdProvider_spec.js | 102 +++++- 4 files changed, 458 insertions(+), 282 deletions(-) diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index 7e75721103f..82b97696371 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -1,187 +1,201 @@ - - - - weborama rtd submodule example - - - - + + + + weborama rtd submodule example + + +
-

- test webo rtd submodule with prebid.js -

+

test webo rtd submodule with prebid.js

Basic Prebid.js Example

Div-1
-
- +
+
- - - - \ No newline at end of file + + diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 82feec252eb..67822234840 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -100,9 +100,7 @@ * @typedef {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} CommonConf */ -import { - getGlobal -} from '../src/prebidGlobal.js'; +import { getGlobal } from '../src/prebidGlobal.js'; import { deepAccess, deepClone, @@ -111,28 +109,18 @@ import { isBoolean, isEmpty, isFn, + isNumber, isPlainObject, isStr, - logWarn, mergeDeep, prefixLog, } from '../src/utils.js'; -import { - submodule -} from '../src/hook.js'; -import { - ajax -} from '../src/ajax.js'; -import { - getStorageManager -} from '../src/storageManager.js'; -import { - MODULE_TYPE_RTD -} from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; import adapterManager from '../src/adapterManager.js'; -import { - tryAppendQueryString -} from '../libraries/urlUtils/urlUtils.js'; +import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -167,7 +155,7 @@ const logger = prefixLog('[WeboramaRTD]'); export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, - moduleName: SUBMODULE_NAME + moduleName: SUBMODULE_NAME, }); /** @@ -216,10 +204,14 @@ class WeboramaRtdProvider { sendToBidders: true, onData: () => { /* do nothing */ - } + }, }; /** @type {ModuleParams} */ - const moduleParams = Object.assign({}, globalDefaults, moduleConfig?.params || {}); + const moduleParams = Object.assign( + {}, + globalDefaults, + moduleConfig?.params || {} + ); // reset profiles @@ -229,13 +221,24 @@ class WeboramaRtdProvider { const weboCtxRequiredFields = ['token']; - this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, { - requiredFields: weboCtxRequiredFields, - }); - this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION, { - userConsent: userConsent || {}, - }); - this.#components.SfbxLiteData.initialized = this.#initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); + this.#components.WeboCtx.initialized = this.#initSubSection( + moduleParams, + WEBO_CTX_CONF_SECTION, + { + requiredFields: weboCtxRequiredFields, + } + ); + this.#components.WeboUserData.initialized = this.#initSubSection( + moduleParams, + WEBO_USER_DATA_CONF_SECTION, + { + userConsent: userConsent || {}, + } + ); + this.#components.SfbxLiteData.initialized = this.#initSubSection( + moduleParams, + SFBX_LITE_DATA_CONF_SECTION + ); return Object.values(this.#components).some((c) => c.initialized); } @@ -264,15 +267,21 @@ class WeboramaRtdProvider { /** @type {WeboCtxConf} */ const weboCtxConf = moduleParams.weboCtxConf || {}; - this.#fetchContextualProfile(weboCtxConf, (data) => { - logger.logMessage('fetchContextualProfile on getBidRequestData is done'); + this.#fetchContextualProfile( + weboCtxConf, + (data) => { + logger.logMessage( + 'fetchContextualProfile on getBidRequestData is done' + ); - this.#setWeboContextualProfile(data); - }, () => { - this.#handleBidRequestData(reqBidsConfigObj, moduleParams); + this.#setWeboContextualProfile(data); + }, + () => { + this.#handleBidRequestData(reqBidsConfigObj, moduleParams); - onDone(); - }); + onDone(); + } + ); } /** @@ -344,17 +353,23 @@ class WeboramaRtdProvider { extra = extra || {}; const requiredFields = extra?.requiredFields || []; - requiredFields.forEach(field => { + requiredFields.forEach((field) => { if (!(field in weboSectionConf)) { throw `missing required field '${field}'`; } }); - if (isPlainObject(extra?.userConsent?.gdpr) && !this.#checkTCFv2(extra.userConsent.gdpr)) { + if ( + isPlainObject(extra?.userConsent?.gdpr) && + !this.#checkTCFv2(extra.userConsent.gdpr) + ) { throw 'gdpr consent not ok'; } } catch (e) { - logger.logError(`unable to initialize: error on '${subSection}' configuration:`, e); + logger.logError( + `unable to initialize: error on '${subSection}' configuration:`, + e + ); return false; } @@ -382,14 +397,18 @@ class WeboramaRtdProvider { return true; } - if (deepAccess(gdpr, 'vendorData.vendor.consents') && - deepAccess(gdpr, 'vendorData.purpose.consents')) { - return gdpr.vendorData.vendor.consents[GVLID] === true && // check weborama vendor id + if ( + deepAccess(gdpr, 'vendorData.vendor.consents') && + deepAccess(gdpr, 'vendorData.purpose.consents') + ) { + return ( + gdpr.vendorData.vendor.consents[GVLID] === true && // check weborama vendor id gdpr.vendorData.purpose.consents[1] === true && // info storage access gdpr.vendorData.purpose.consents[3] === true && // create personalized ads gdpr.vendorData.purpose.consents[4] === true && // select personalized ads gdpr.vendorData.purpose.consents[5] === true && // create personalized content - gdpr.vendorData.purpose.consents[6] === true; // select personalized content + gdpr.vendorData.purpose.consents[6] === true + ); // select personalized content } return true; @@ -439,7 +458,9 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #coerceSetPrebidTargeting(submoduleParams) { try { - submoduleParams.setPrebidTargeting = this.#wrapValidatorCallback(submoduleParams.setPrebidTargeting); + submoduleParams.setPrebidTargeting = this.#wrapValidatorCallback( + submoduleParams.setPrebidTargeting + ); } catch (e) { throw `invalid setPrebidTargeting: ${e}`; } @@ -458,10 +479,13 @@ class WeboramaRtdProvider { let sendToBidders = submoduleParams.sendToBidders; if (isPlainObject(sendToBidders)) { - const sendToBiddersMap = Object.entries(sendToBidders).reduce((map, [key, value]) => { - map[key] = this.#wrapValidatorCallback(value); - return map; - }, {}); + const sendToBiddersMap = Object.entries(sendToBidders).reduce( + (map, [key, value]) => { + map[key] = this.#wrapValidatorCallback(value); + return map; + }, + {} + ); submoduleParams.sendToBidders = (bid, adUnitCode) => { const bidder = bid.bidder; @@ -482,8 +506,10 @@ class WeboramaRtdProvider { } try { - submoduleParams.sendToBidders = this.#wrapValidatorCallback(submoduleParams.sendToBidders, - (bid) => bid.bidder); + submoduleParams.sendToBidders = this.#wrapValidatorCallback( + submoduleParams.sendToBidders, + (bid) => bid.bidder + ); } catch (e) { throw `invalid sendToBidders: ${e}`; } @@ -516,9 +542,9 @@ class WeboramaRtdProvider { const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; try { - adUnits.forEach( - adUnit => adUnit.bids?.forEach( - bid => profileHandlers.forEach(ph => { + adUnits.forEach((adUnit) => + adUnit.bids?.forEach((bid) => + profileHandlers.forEach((ph) => { // logger.logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); const [data, metadata] = this.#copyDataAndMetadata(ph); @@ -534,12 +560,15 @@ class WeboramaRtdProvider { logger.logError('unable to send data to bidders:', e); } - profileHandlers.forEach(ph => { + profileHandlers.forEach((ph) => { try { const [data, metadata] = this.#copyDataAndMetadata(ph); ph.onData(data, metadata); } catch (e) { - logger.logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e); + logger.logError( + `error while execute onData callback with ${ph.metadata.source}-based data:`, + e + ); } }); } @@ -569,7 +598,8 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #fetchContextualProfile(weboCtxConf, onSuccess, onDone) { const token = weboCtxConf.token; - const baseURLProfileAPI = weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; + const baseURLProfileAPI = + weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; let path = '/profile'; let queryString = ''; @@ -583,7 +613,10 @@ class WeboramaRtdProvider { try { assetID = weboCtxConf.assetID(); } catch (e) { - logger.logError('unexpected error while fetching asset id from callback', e); + logger.logError( + 'unexpected error while fetching asset id from callback', + e + ); onDone(); @@ -660,30 +693,43 @@ class WeboramaRtdProvider { */ // eslint-disable-next-line no-dupe-class-members #buildProfileHandlers(moduleParams) { - const steps = [{ - component: this.#components.WeboCtx, - conf: moduleParams?.weboCtxConf, - }, { - component: this.#components.WeboUserData, - conf: moduleParams?.weboUserDataConf, - }, { - component: this.#components.SfbxLiteData, - conf: moduleParams?.sfbxLiteDataConf, - }]; - - return steps.filter(step => step.component.initialized).reduce((ph, { component, conf }) => { - const user = component.user; - const source = component.source; - const callback = component.callbackBuilder(component /* equivalent to this */); - const profileHandler = this.#buildProfileHandler(conf, callback, user, source); - if (profileHandler) { - ph.push(profileHandler); - } else { - logger.logMessage(`skip ${source} profile: no data`); - } + const steps = [ + { + component: this.#components.WeboCtx, + conf: moduleParams?.weboCtxConf, + }, + { + component: this.#components.WeboUserData, + conf: moduleParams?.weboUserDataConf, + }, + { + component: this.#components.SfbxLiteData, + conf: moduleParams?.sfbxLiteDataConf, + }, + ]; + + return steps + .filter((step) => step.component.initialized) + .reduce((ph, { component, conf }) => { + const user = component.user; + const source = component.source; + const callback = component.callbackBuilder( + component /* equivalent to this */ + ); + const profileHandler = this.#buildProfileHandler( + conf, + callback, + user, + source + ); + if (profileHandler) { + ph.push(profileHandler); + } else { + logger.logMessage(`skip ${source} profile: no data`); + } - return ph; - }, []); + return ph; + }, []); } /** @@ -823,13 +869,22 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #handleBidViaORTB2(reqBidsConfigObj, bidder, profile, metadata) { if (isBoolean(metadata.user)) { - logger.logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); + logger.logMessage( + `bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd` + ); const section = metadata.user ? 'user' : 'site'; const path = `${section}.ext.data`; - this.#setBidderOrtb2(reqBidsConfigObj.ortb2Fragments?.bidder, bidder, path, profile) + this.#setBidderOrtb2( + reqBidsConfigObj.ortb2Fragments?.bidder, + bidder, + path, + profile + ); } else { - logger.logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`); + logger.logMessage( + `SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric` + ); } } /** @@ -845,7 +900,7 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #setBidderOrtb2(bidderOrtb2Fragments, bidder, path, profile) { const base = `${bidder}.${path}`; - this.#assignProfileToObject(bidderOrtb2Fragments, base, profile) + this.#assignProfileToObject(bidderOrtb2Fragments, base, profile); } /** * assign profile to object @@ -861,7 +916,7 @@ class WeboramaRtdProvider { Object.entries(profile).forEach(([key, values]) => { const path = `${base}.${key}`; deepSetValue(destination, path, values); - }) + }); } /** @@ -913,6 +968,7 @@ class WeboramaRtdProvider { /** * check if profile is valid + * a valid profile must be a plain object and every value should be an array of strings or numbers * @param {*} profile * @returns {boolean} */ @@ -921,7 +977,18 @@ export function isValidProfile(profile) { return false; } - return Object.values(profile).every((field) => isArray(field) && field.every(isStr)); + return Object.values(profile).every( + (field) => isArray(field) && field.every(isStrOrNumber) + ); +} + +/** + * Return if the object is a string or number + * @param {*} object object to test + * @return {Boolean} if object is a string or number + */ +function isStrOrNumber(object) { + return isStr(object) || isNumber(object); } /** @@ -943,7 +1010,7 @@ function getContextualProfile(component /* equivalent to this */) { const defaultContextualProfile = weboCtxConf.defaultProfile || {}; return [defaultContextualProfile, true]; - } + }; } /** @@ -958,13 +1025,15 @@ function getWeboUserDataProfile(component /* equivalent to this */) { * @returns {[Profile,boolean]} weboUserData profile + isDefault boolean flag */ return function (weboUserDataConf) { - return getDataFromLocalStorage(weboUserDataConf, + return getDataFromLocalStorage( + weboUserDataConf, () => component.data, - (data) => component.data = data, + (data) => (component.data = data), DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, LOCAL_STORAGE_USER_TARGETING_SECTION, - WEBO_USER_DATA_SOURCE_LABEL); - } + WEBO_USER_DATA_SOURCE_LABEL + ); + }; } /** @@ -979,13 +1048,15 @@ function getSfbxLiteDataProfile(component /* equivalent to this */) { * @returns {[Profile,boolean]} sfbxLiteData profile + isDefault boolean flag */ return function getSfbxLiteDataProfile(sfbxLiteDataConf) { - return getDataFromLocalStorage(sfbxLiteDataConf, + return getDataFromLocalStorage( + sfbxLiteDataConf, () => component.data, - (data) => component.data = data, + (data) => (component.data = data), DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY, LOCAL_STORAGE_LITE_TARGETING_SECTION, - SFBX_LITE_DATA_SOURCE_LABEL); - } + SFBX_LITE_DATA_SOURCE_LABEL + ); + }; } /** @@ -1008,11 +1079,23 @@ function getSfbxLiteDataProfile(component /* equivalent to this */) { * @param {string} source * @returns {[Profile,boolean]} webo (user|lite) data profile + isDefault boolean flag */ -function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalStorageProfileKey, targetingSection, source) { +function getDataFromLocalStorage( + weboDataConf, + cacheGet, + cacheSet, + defaultLocalStorageProfileKey, + targetingSection, + source +) { const defaultProfile = weboDataConf.defaultProfile || {}; - if (storage.hasLocalStorage() && storage.localStorageIsEnabled() && !cacheGet()) { - const localStorageProfileKey = weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; + if ( + storage.hasLocalStorage() && + storage.localStorageIsEnabled() && + !cacheGet() + ) { + const localStorageProfileKey = + weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; const entry = storage.getDataFromLocalStorage(localStorageProfileKey); if (entry) { @@ -1022,9 +1105,12 @@ function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalS const profile = data[targetingSection]; const valid = isValidProfile(profile); if (!valid) { - logWarn(`found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}`); + logger.logMessage( + `WARNING: found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}: `, + profile + ); - return; + return [defaultProfile, true]; } if (!isEmpty(data)) { diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index a8fc692ba74..11b4368a502 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -67,7 +67,7 @@ pbjs.setConfig({ enabled: true, }, sfbxLiteDataConf: { // sfbx-lite site-centric configuration, *omit if not needed* - enabled: true, + enabled: true, }, } }, @@ -215,7 +215,7 @@ A better look on the `Object` type ```javascript sendToBidders: { appnexus: true, // send profile to appnexus on all ad units - pubmatic: ['adUnitCode1'],// send profile to pubmatic on this ad units + pubmatic: ['adUnitCode1'],// send profile to pubmatic on this ad units } ``` @@ -253,7 +253,7 @@ To be possible customize the way we send data to bidders via this callback: sendToBidders: function(bid, adUnitCode, data, metadata){ if (bid.bidder == 'other'){ /* use bid object to store data based on this specific logic, like in the example below */ - + bid.params = bid.params || {}; bid.params['some_specific_key'] = data; @@ -374,7 +374,7 @@ pbjs.que.push(function () { sendToBidders: true, // override param.sendToBidders. default is true defaultProfile: { // optional, used if nothing is found webo_cs: [...], // wam custom segments - webo_audiences: [...], // wam audiences + webo_audiences: [...], // wam audiences }, enabled: true, }, @@ -485,18 +485,18 @@ pbjs.que.push(function () { params: { weboCtxConf: { token: "to-be-defined", // mandatory - setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits sendToBidders: ['appnexus',...], // overide, send to only some bidders enabled: true, }, weboUserDataConf: { accountId: 12345, // recommended - setPrebidTargeting: ['adUnitCode2',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode2',...], // set target only on certain adunits sendToBidders: ['rubicon',...], // overide, send to only some bidders enabled: true, }, sfbxLiteDataConf: { - setPrebidTargeting: ['adUnitCode3',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode3',...], // set target only on certain adunits sendToBidders: ['smartadserver',...], // overide, send to only some bidders enabled: true, } @@ -546,15 +546,15 @@ pbjs.que.push(function () { }, weboUserDataConf: { accountId: 12345, // recommended - setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits sendToBidders: { // send to only some bidders and adunits - 'appnexus': true, // all adunits for appnexus + 'appnexus': true, // all adunits for appnexus 'pubmatic': ['adUnitCode1',...] // some adunits for pubmatic // other bidders will be ignored }, defaultProfile: { // optional - webo_cs: ['Red'], - webo_audiences: ['bam'] + webo_cs: ['Red'], // using label + webo_audiences: [12345] // using id }, localStorageProfileKey: 'webo_wam2gam_entry', // default enabled: true, diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index d562d9ffd13..964c42eff07 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -1509,7 +1509,7 @@ describe('weboramaRtdProvider', function() { } }; const data = { - webo_cs: ['foo', 'bar'], + webo_cs: [12, 345], webo_audiences: ['baz'], }; @@ -1620,7 +1620,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -1746,7 +1746,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -1868,7 +1868,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2013,7 +2013,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2156,7 +2156,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2217,7 +2217,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2289,7 +2289,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ foo: ['bar'], webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }); expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: { @@ -2312,7 +2312,7 @@ describe('weboramaRtdProvider', function() { it('should use default profile in case of nothing on local storage', function() { const defaultProfile = { - webo_audiences: ['baz'] + webo_audiences: [12345] }; const moduleConfig = { params: { @@ -2377,9 +2377,85 @@ describe('weboramaRtdProvider', function() { }) }); + it('should use default profile in case of something malformed on local storage', function() { + const defaultProfile = { + webo_audiences: [12345] + }; + const moduleConfig = { + params: { + weboUserDataConf: { + accoundId: 12345, + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } + } + }; + + const malformed = { + targeting: { + webo_audiences: 'must be an array, not a string', + }, + }; + + sandbox.stub(storage, 'hasLocalStorage').returns(true); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(malformed)); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + }) + }); + it('should use default profile if cant read from local storage', function() { const defaultProfile = { - webo_audiences: ['baz'] + webo_audiences: [12345] }; let onDataResponse = {}; const moduleConfig = { @@ -2489,7 +2565,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2550,7 +2626,7 @@ describe('weboramaRtdProvider', function() { expect(targeting).to.deep.equal({ 'adunit1': { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], webo_foo: ['bar'], }, 'adunit2': data, @@ -2569,7 +2645,7 @@ describe('weboramaRtdProvider', function() { ext: { data: { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], webo_bar: ['baz'], } }, From 50d30c432394a6c2170922c8898ab75095eff0bb Mon Sep 17 00:00:00 2001 From: Pavlo Kavulych <72217414+Chucky-choo@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:37:30 +0300 Subject: [PATCH 0425/1097] Anyclip Bid Adapter : refactor bid adapter (#12030) * update anyclip adapter * remove coppa * fix jsdoc warnings * create bidderUtils * delete duplicate code * refactor --------- Co-authored-by: Chucky-choo --- libraries/xeUtils/bidderUtils.js | 158 +++++ modules/anyclipBidAdapter.js | 235 ++------ modules/driftpixelBidAdapter.js | 208 +------ modules/iqxBidAdapter.js | 193 +----- modules/lm_kiviadsBidAdapter.js | 201 +------ modules/xeBidAdapter.js | 210 +------ test/spec/modules/anyclipBidAdapter_spec.js | 550 ++++++++++++++---- .../spec/modules/driftpixelBidAdapter_spec.js | 12 +- test/spec/modules/iqxBidAdapter_spec.js | 12 +- .../spec/modules/lm_kiviadsBidAdapter_spec.js | 12 +- test/spec/modules/xeBidAdapter_spec.js | 92 +-- 11 files changed, 687 insertions(+), 1196 deletions(-) create mode 100644 libraries/xeUtils/bidderUtils.js diff --git a/libraries/xeUtils/bidderUtils.js b/libraries/xeUtils/bidderUtils.js new file mode 100644 index 00000000000..abbd14db6a9 --- /dev/null +++ b/libraries/xeUtils/bidderUtils.js @@ -0,0 +1,158 @@ +import {deepAccess, getBidIdParameter, isFn, logError, isArray, parseSizesInput} from '../../src/utils.js'; +import {getAdUnitSizes} from '../sizeUtils/sizeUtils.js'; +import {findIndex} from '../../src/polyfill.js'; + +export function getBidFloor(bid, currency = 'USD') { + if (!isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency, + mediaType: '*', + size: '*' + }); + + if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === currency) { + return floor.floor; + } + + return null; +} + +export function isBidRequestValid(bid) { + if (bid && typeof bid.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } + + if (!getBidIdParameter('env', bid.params) || !getBidIdParameter('pid', bid.params)) { + logError('Env or pid is not present in bidder params'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video') && !isArray(deepAccess(bid, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; + } + + return true; +} + +export function buildRequests(validBidRequests, bidderRequest, endpoint) { + const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; + const requests = validBidRequests.map(req => { + const request = {}; + request.bidId = req.bidId; + request.banner = deepAccess(req, 'mediaTypes.banner'); + request.auctionId = req.ortb2?.source?.tid; + request.transactionId = req.ortb2Imp?.ext?.tid; + request.sizes = parseSizesInput(getAdUnitSizes(req)); + request.schain = req.schain; + request.location = { + page: refererInfo.page, + location: refererInfo.location, + domain: refererInfo.domain, + whost: window.location.host, + ref: refererInfo.ref, + isAmp: refererInfo.isAmp + }; + request.device = { + ua: navigator.userAgent, + lang: navigator.language + }; + request.env = { + env: req.params.env, + pid: req.params.pid + }; + request.ortb2 = req.ortb2; + request.ortb2Imp = req.ortb2Imp; + request.tz = new Date().getTimezoneOffset(); + request.ext = req.params.ext; + request.bc = req.bidRequestsCount; + request.floor = getBidFloor(req); + + if (req.userIdAsEids && req.userIdAsEids.length !== 0) { + request.userEids = req.userIdAsEids; + } else { + request.userEids = []; + } + if (gdprConsent.gdprApplies) { + request.gdprApplies = Number(gdprConsent.gdprApplies); + request.consentString = gdprConsent.consentString; + } else { + request.gdprApplies = 0; + request.consentString = ''; + } + if (uspConsent) { + request.usPrivacy = uspConsent; + } else { + request.usPrivacy = ''; + } + const video = deepAccess(req, 'mediaTypes.video'); + if (video) { + request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); + request.video = video; + } + + return request; + }); + + return { + method: 'POST', + url: endpoint + '/bid', + data: JSON.stringify(requests), + withCredentials: true, + bidderRequest, + options: { + contentType: 'application/json', + } + }; +} + +export function interpretResponse(serverResponse, {bidderRequest}) { + const response = []; + if (!isArray(deepAccess(serverResponse, 'body.data'))) { + return response; + } + + serverResponse.body.data.forEach(serverBid => { + const bidIndex = findIndex(bidderRequest.bids, (bidRequest) => { + return bidRequest.bidId === serverBid.requestId; + }); + + if (bidIndex !== -1) { + const bid = { + requestId: serverBid.requestId, + dealId: bidderRequest.dealId || null, + ...serverBid + }; + response.push(bid); + } + }); + + return response; +} + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); + + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; + + pixels.forEach(pixel => { + const [type, url] = pixel; + const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; + if (type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push(sync) + } else if (type === 'image' && syncOptions.pixelEnabled) { + syncs.push(sync) + } + }); + } + + return syncs; +} diff --git a/modules/anyclipBidAdapter.js b/modules/anyclipBidAdapter.js index cb9103899a4..8a5906ebc93 100644 --- a/modules/anyclipBidAdapter.js +++ b/modules/anyclipBidAdapter.js @@ -1,213 +1,54 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {deepAccess, isArray, isFn, logError, logInfo} from '../src/utils.js'; -import {config} from '../src/config.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest - * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec - */ +import { + buildRequests, + getUserSyncs, + interpretResponse, +} from '../libraries/xeUtils/bidderUtils.js'; +import {deepAccess, getBidIdParameter, isArray, logError} from '../src/utils.js'; const BIDDER_CODE = 'anyclip'; -const ENDPOINT_URL = 'https://prebid.anyclip.com'; -const DEFAULT_CURRENCY = 'USD'; -const NET_REVENUE = false; +const ENDPOINT = 'https://prebid.anyclip.com'; + +function isBidRequestValid(bid) { + if (bid && typeof bid.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } -/* - * Get the bid floor value from the bidRequest object, either using the getFloor function or by accessing the 'params.floor' property. - * If the bid floor cannot be determined, return 0 as a fallback value. - */ -function getBidFloor(bidRequest) { - if (!isFn(bidRequest.getFloor)) { - return deepAccess(bidRequest, 'params.floor', 0); + if (!getBidIdParameter('publisherId', bid.params) || !getBidIdParameter('supplyTagId', bid.params)) { + logError('PublisherId or supplyTagId is not present in bidder params'); + return false; } - try { - const bidFloor = bidRequest.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; + if (deepAccess(bid, 'mediaTypes.video') && !isArray(deepAccess(bid, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; } + + return true; } -/** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - /** - * @param {object} bid - * @return {boolean} - */ - isBidRequestValid: (bid = {}) => { - const bidder = deepAccess(bid, 'bidder'); - const params = deepAccess(bid, 'params', {}); - const mediaTypes = deepAccess(bid, 'mediaTypes', {}); - const banner = deepAccess(mediaTypes, BANNER, {}); - - const isValidBidder = (bidder === BIDDER_CODE); - const isValidSize = (Boolean(banner.sizes) && isArray(mediaTypes[BANNER].sizes) && mediaTypes[BANNER].sizes.length > 0); - const hasSizes = mediaTypes[BANNER] ? isValidSize : false; - const hasRequiredBidParams = Boolean(params.publisherId && params.supplyTagId); - - const isValid = isValidBidder && hasSizes && hasRequiredBidParams; - if (!isValid) { - logError(`Invalid bid request: isValidBidder: ${isValidBidder}, hasSizes: ${hasSizes}, hasRequiredBidParams: ${hasRequiredBidParams}`); - } - return isValid; - }, - - /** - * @param {BidRequest[]} validBidRequests - * @param {*} bidderRequest - * @return {ServerRequest} - */ + aliases: ['anyclip'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, buildRequests: (validBidRequests, bidderRequest) => { - const bidRequest = validBidRequests[0]; - - let refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; - } - - const timeout = bidderRequest.timeout; - const timeoutAdjustment = timeout - ((20 / 100) * timeout); // timeout adjustment - 20% - - if (isPubTagAvailable()) { - // Options - const options = { - publisherId: bidRequest.params.publisherId, - supplyTagId: bidRequest.params.supplyTagId, - url: refererInfo.page, - domain: refererInfo.domain, - prebidTimeout: timeoutAdjustment, - gpid: bidRequest.adUnitCode, - ext: { - transactionId: bidRequest.transactionId - }, - sizes: bidRequest.sizes.map((size) => { - return {width: size[0], height: size[1]} - }) - } - // Floor - const floor = parseFloat(getBidFloor(bidRequest)); - if (!isNaN(floor)) { - options.ext.floor = floor; - } - // Supply Chain (Schain) - if (bidRequest?.schain) { - options.schain = bidRequest.schain - } - // GDPR & Consent (EU) - if (bidderRequest?.gdprConsent) { - options.gdpr = (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - options.consent = bidderRequest.gdprConsent.consentString; - } - // GPP - if (bidderRequest?.gppConsent?.gppString) { - options.gpp = { - gppVersion: bidderRequest.gppConsent.gppVersion, - sectionList: bidderRequest.gppConsent.sectionList, - applicableSections: bidderRequest.gppConsent.applicableSections, - gppString: bidderRequest.gppConsent.gppString - } - } - // CCPA (US Privacy) - if (bidderRequest?.uspConsent) { - options.usPrivacy = bidderRequest.uspConsent; - } - // COPPA - if (config.getConfig('coppa') === true) { - options.coppa = 1; - } - // Eids - if (bidRequest?.userIdAsEids) { - const eids = bidRequest.userIdAsEids; - if (eids && eids.length) { - options.eids = eids; - } - } - - // Request bids - const requestBidsPromise = window._anyclip.pubTag.requestBids(options); - if (requestBidsPromise !== undefined) { - requestBidsPromise - .then(() => { - logInfo('PubTag requestBids done'); - }) - .catch((err) => { - logError('PubTag requestBids error', err); - }); - } - - // Request - const payload = { - tmax: timeoutAdjustment - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: payload, - bidRequest - } - } + const builtRequests = buildRequests(validBidRequests, bidderRequest, ENDPOINT) + const requests = JSON.parse(builtRequests.data) + const updatedRequests = requests.map(req => ({ + ...req, + env: { + publisherId: validBidRequests[0].params.publisherId, + supplyTagId: validBidRequests[0].params.supplyTagId, + floor: req.floor + }, + })) + return {...builtRequests, data: JSON.stringify(updatedRequests)} }, - - /** - * @param {*} serverResponse - * @param {ServerRequest} bidRequest - * @return {Bid[]} - */ - interpretResponse: (serverResponse, { bidRequest }) => { - const bids = []; - - if (bidRequest && isPubTagAvailable()) { - const bidResponse = window._anyclip.pubTag.getBids(bidRequest.transactionId); - if (bidResponse) { - const { adServer } = bidResponse; - if (adServer) { - bids.push({ - requestId: bidRequest.bidId, - creativeId: adServer.bid.creativeId, - cpm: bidResponse.cpm, - width: adServer.bid.width, - height: adServer.bid.height, - currency: adServer.bid.currency || DEFAULT_CURRENCY, - netRevenue: NET_REVENUE, - ttl: adServer.bid.ttl, - ad: adServer.bid.ad, - meta: adServer.bid.meta - }); - } - } - } - - return bids; - }, - - /** - * @param {Bid} bid - */ - onBidWon: (bid) => { - if (isPubTagAvailable()) { - window._anyclip.pubTag.bidWon(bid); - } - } -} - -/** - * @return {boolean} - */ -const isPubTagAvailable = () => { - return !!(window._anyclip && window._anyclip.pubTag); + interpretResponse, + getUserSyncs } registerBidder(spec); diff --git a/modules/driftpixelBidAdapter.js b/modules/driftpixelBidAdapter.js index 707945ff45a..5dd0d3a5835 100644 --- a/modules/driftpixelBidAdapter.js +++ b/modules/driftpixelBidAdapter.js @@ -1,220 +1,16 @@ -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; -import {findIndex} from '../src/polyfill.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - */ - -const CUR = 'USD'; const BIDDER_CODE = 'driftpixel'; const ENDPOINT = 'https://pbjs.driftpixel.live'; -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(bid) { - if (bid && typeof bid.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', bid.params) || !getBidIdParameter('pid', bid.params)) { - logError('Env or pid is not present in bidder params'); - return false; - } - - if (deepAccess(bid, 'mediaTypes.video') && !isArray(deepAccess(bid, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - request.auctionId = req.ortb2?.source?.tid; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - pid: req.params.pid - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT + '/bid', - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - options: { - contentType: 'application/json', - } - }; -} - -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(serverResponse, {bidderRequest}) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bidIndex = findIndex(bidderRequest.bids, (bidRequest) => { - return bidRequest.bidId === serverBid.requestId; - }); - - if (bidIndex !== -1) { - const bid = { - requestId: serverBid.requestId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - } - }); - - return response; -} - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [type, url] = pixel; - const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, aliases: ['driftpixel'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/modules/iqxBidAdapter.js b/modules/iqxBidAdapter.js index 1bef158c4a2..e859dfd2c01 100644 --- a/modules/iqxBidAdapter.js +++ b/modules/iqxBidAdapter.js @@ -1,205 +1,16 @@ -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -const CUR = 'USD'; const BIDDER_CODE = 'iqx'; const ENDPOINT = 'https://pbjs.iqzonertb.live'; -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(req) { - if (req && typeof req.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) { - logError('Env or pid is not present in bidder params'); - return false; - } - - if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - request.auctionId = req.ortb2?.source?.tid; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - pid: req.params.pid - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT + '/bid', - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - options: { - contentType: 'application/json', - } - }; -} - -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(serverResponse, {bidderRequest}) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bid = { - requestId: bidderRequest.bidId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - }); - - return response; -} - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [type, url] = pixel; - const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, aliases: ['iqx'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/modules/lm_kiviadsBidAdapter.js b/modules/lm_kiviadsBidAdapter.js index 7c3085047c4..7295eb33258 100644 --- a/modules/lm_kiviadsBidAdapter.js +++ b/modules/lm_kiviadsBidAdapter.js @@ -1,213 +1,16 @@ -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - */ - -const CUR = 'USD'; const BIDDER_CODE = 'lm_kiviads'; const ENDPOINT = 'https://pbjs.kiviads.live'; -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(req) { - if (req && typeof req.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) { - logError('Env or pid is not present in bidder params'); - return false; - } - - if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - request.auctionId = req.ortb2?.source?.tid; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - pid: req.params.pid - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT + '/bid', - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - options: { - contentType: 'application/json', - } - }; -} - -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(serverResponse, {bidderRequest}) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bid = { - requestId: bidderRequest.bidId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - }); - - return response; -} - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [type, url] = pixel; - const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, aliases: ['kivi'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/modules/xeBidAdapter.js b/modules/xeBidAdapter.js index a813b9aa2a3..9bd6f8adbac 100644 --- a/modules/xeBidAdapter.js +++ b/modules/xeBidAdapter.js @@ -1,214 +1,16 @@ -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, logError, isArray, getBidIdParameter} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - */ - -const CUR = 'USD'; const BIDDER_CODE = 'xe'; const ENDPOINT = 'https://pbjs.xe.works/bid'; -/** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ -function isBidRequestValid(req) { - if (req && typeof req.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', req.params) || !getBidIdParameter('placement', req.params)) { - logError('Env or placement is not present in bidder params'); - return false; - } - - if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const { refererInfo = {}, gdprConsent = {}, uspConsent } = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - request.auctionId = req.auctionId; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - placement: req.params.placement - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - options: { - contentType: 'application/json' - } - }; -} - -/** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(serverResponse, { bidderRequest }) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bid = { - requestId: bidderRequest.bidId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - }); - - return response; -} - -/** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [ type, url ] = pixel; - const sync = { type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}` }; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, - aliases: [ 'xeworks', 'lunamediax' ], - supportedMediaTypes: [ BANNER, VIDEO ], + aliases: ['xeworks', 'lunamediax'], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/test/spec/modules/anyclipBidAdapter_spec.js b/test/spec/modules/anyclipBidAdapter_spec.js index 3de36f9fe06..9f34184d378 100644 --- a/test/spec/modules/anyclipBidAdapter_spec.js +++ b/test/spec/modules/anyclipBidAdapter_spec.js @@ -1,160 +1,458 @@ -import { expect } from 'chai'; -import { spec } from 'modules/anyclipBidAdapter.js'; +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/anyclipBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; -describe('anyclipBidAdapter', function () { - afterEach(function () { - global._anyclip = undefined; - }); +const ENDPOINT = 'https://prebid.anyclip.com'; - let bid; - - function mockBidRequest() { - return { - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [728, 90], - [468, 60] - ] - } - }, - bidder: 'anyclip', - params: { - publisherId: '12345', - supplyTagId: '-mptNo0BycUG4oCDgGrU' - } - }; - }; +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'anyclip', + params: { + publisherId: 'anyclip', + supplyTagId: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; +const videoBidderRequest = { + bidderCode: 'anyclip', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'anyclip', + bids: [{bidId: 'qwerty'}] +}; + +describe('anyclipBidAdapter', () => { describe('isBidRequestValid', function () { - this.beforeEach(function () { - bid = mockBidRequest(); + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return true if all required fields are present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('should return false when required publisherId param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.publisherId; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false if bidder does not correspond', function () { - bid.bidder = 'abc'; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should return false when required supplyTagId param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.supplyTagId; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false if params object is missing', function () { - delete bid.params; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false if publisherId is missing from params', function () { - delete bid.params.publisherId; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); }); - it('should return false if supplyTagId is missing from params', function () { - delete bid.params.supplyTagId; - expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); }); - it('should return false if mediaTypes is missing', function () { - delete bid.mediaTypes; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); }); - it('should return false if banner is missing from mediaTypes ', function () { - delete bid.mediaTypes.banner; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + publisherId: 'anyclip', + supplyTagId: '40', + floor: null + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); }); - it('should return false if sizes is missing from banner object', function () { - delete bid.mediaTypes.banner.sizes; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); }); - it('should return false if sizes is not an array', function () { - bid.mediaTypes.banner.sizes = 'test'; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); }); - it('should return false if sizes is an empty array', function () { - bid.mediaTypes.banner.sizes = []; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); }); - }); - describe('buildRequests', function () { - let bidderRequest = { - refererInfo: { - page: 'http://example.com', - domain: 'example.com', - }, - timeout: 3000 - }; - - this.beforeEach(function () { - bid = mockBidRequest(); - Object.assign(bid, { - adUnitCode: '1', - transactionId: '123', - sizes: bid.mediaTypes.banner.sizes + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } }); }); - it('when pubtag is not available, return undefined', function () { - expect(spec.buildRequests([bid], bidderRequest)).to.undefined; + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0].env; + expect(request).to.have.property('floor').and.to.equal(5); }); - it('when pubtag is available, creates a ServerRequest object with method, URL and data', function() { - global._anyclip = { - PubTag: function() {}, - pubTag: { - requestBids: function() {} + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' } }; - expect(spec.buildRequests([bid], bidderRequest)).to.exist; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); }); }); - describe('interpretResponse', function() { - it('should return an empty array when parsing a no bid response', function () { - const response = {}; - const request = {}; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - it('should return bids array', function() { - const response = {}; - const request = { - bidRequest: { - bidId: 'test-bidId', - transactionId: '123' + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['anyclip'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['anyclip']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] } }; - global._anyclip = { - PubTag: function() {}, - pubTag: { - getBids: function(transactionId) { - return { - adServer: { - bid: { - ad: 'test-ad', - creativeId: 'test-crId', - meta: { - advertiserDomains: ['anyclip.com'] - }, - width: 300, - height: 250, - ttl: 300 - } - }, - cpm: 1.23, + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] } - } + }] } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = {getFloor: 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = {getFloor: () => 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({floor: 'string', currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].ad).to.equal('test-ad'); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].creativeId).to.equal('test-crId'); - expect(bids[0].netRevenue).to.false; - expect(bids[0].meta.advertiserDomains[0]).to.equal('anyclip.com'); + const result = getBidFloor(bid); + expect(result).to.equal(5); }); }); -}); +}) diff --git a/test/spec/modules/driftpixelBidAdapter_spec.js b/test/spec/modules/driftpixelBidAdapter_spec.js index 2af09e3a690..da84235b404 100644 --- a/test/spec/modules/driftpixelBidAdapter_spec.js +++ b/test/spec/modules/driftpixelBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; import {config} from 'src/config.js'; -import {spec, getBidFloor} from 'modules/driftpixelBidAdapter.js'; +import {spec} from 'modules/driftpixelBidAdapter.js'; import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.driftpixel.live'; @@ -112,7 +113,6 @@ describe('driftpixelBidAdapter', () => { expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ @@ -225,14 +225,6 @@ describe('driftpixelBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ diff --git a/test/spec/modules/iqxBidAdapter_spec.js b/test/spec/modules/iqxBidAdapter_spec.js index f5e680c8e0b..29b03a4ed3a 100644 --- a/test/spec/modules/iqxBidAdapter_spec.js +++ b/test/spec/modules/iqxBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; import {config} from 'src/config.js'; -import {spec, getBidFloor} from 'modules/iqxBidAdapter.js'; +import {spec} from 'modules/iqxBidAdapter.js'; import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.iqzonertb.live'; @@ -101,7 +102,6 @@ describe('iqxBidAdapter', () => { expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ @@ -214,14 +214,6 @@ describe('iqxBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ diff --git a/test/spec/modules/lm_kiviadsBidAdapter_spec.js b/test/spec/modules/lm_kiviadsBidAdapter_spec.js index 68ac73289cd..645dd756c19 100644 --- a/test/spec/modules/lm_kiviadsBidAdapter_spec.js +++ b/test/spec/modules/lm_kiviadsBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; import {config} from 'src/config.js'; -import {spec, getBidFloor} from 'modules/lm_kiviadsBidAdapter.js'; +import {spec} from 'modules/lm_kiviadsBidAdapter.js'; import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.kiviads.live'; @@ -101,7 +102,6 @@ describe('lm_kiviadsBidAdapter', () => { expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ @@ -214,14 +214,6 @@ describe('lm_kiviadsBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ diff --git a/test/spec/modules/xeBidAdapter_spec.js b/test/spec/modules/xeBidAdapter_spec.js index 914b0cacd71..e15ab6251ea 100644 --- a/test/spec/modules/xeBidAdapter_spec.js +++ b/test/spec/modules/xeBidAdapter_spec.js @@ -1,8 +1,8 @@ -import { expect } from 'chai'; -import { config } from 'src/config.js'; -import { spec, getBidFloor } from 'modules/xeBidAdapter.js'; -import { deepClone } from 'src/utils'; -import { createEidsArray } from 'modules/userId/eids.js'; +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/xeBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.xe.works/bid'; @@ -10,7 +10,11 @@ const defaultRequest = { adUnitCode: 'test', bidId: '1', requestId: 'qwerty', - auctionId: 'auctionId', + ortb2: { + source: { + tid: 'auctionId' + } + }, ortb2Imp: { ext: { tid: 'tr1', @@ -27,7 +31,7 @@ const defaultRequest = { bidder: 'xe', params: { env: 'xe', - placement: 'test-banner', + pid: '40', ext: {} }, bidRequestsCount: 1 @@ -41,6 +45,17 @@ defaultRequestVideo.mediaTypes = { skipppable: true } }; + +const videoBidderRequest = { + bidderCode: 'xe', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'xe', + bids: [{bidId: 'qwerty'}] +}; + describe('xeBidAdapter', () => { describe('isBidRequestValid', function () { it('should return false when request params is missing', function () { @@ -55,9 +70,9 @@ describe('xeBidAdapter', () => { expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false when required placement param is missing', function () { + it('should return false when required pid param is missing', function () { const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params.placement; + delete invalidRequest.params.pid; expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); @@ -80,7 +95,7 @@ describe('xeBidAdapter', () => { it('should send request with correct structure', function () { const request = spec.buildRequests([defaultRequest], {}); expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT); + expect(request.url).to.equal(ENDPOINT + '/bid'); expect(request.options).to.have.property('contentType').and.to.equal('application/json'); expect(request).to.have.property('data'); }); @@ -88,22 +103,21 @@ describe('xeBidAdapter', () => { it('should build basic request structure', function () { const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); - expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.auctionId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); expect(request).to.have.property('bc').and.to.equal(1); expect(request).to.have.property('floor').and.to.equal(null); - expect(request).to.have.property('banner').and.to.deep.equal({ sizes: [[300, 250], [300, 200]] }); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); expect(request).to.have.property('gdprApplies').and.to.equal(0); expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ env: 'xe', - placement: 'test-banner' + pid: '40' }); expect(request).to.have.property('device').and.to.deep.equal({ ua: navigator.userAgent, @@ -186,7 +200,7 @@ describe('xeBidAdapter', () => { it('should build request with valid bidfloor', function () { const bfRequest = deepClone(defaultRequest); - bfRequest.getFloor = () => ({ floor: 5, currency: 'USD' }); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; expect(request).to.have.property('floor').and.to.equal(5); }); @@ -211,19 +225,11 @@ describe('xeBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ - { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, - { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} ]; const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); @@ -263,19 +269,19 @@ describe('xeBidAdapter', () => { height: 250, ttl: 600, meta: { - advertiserDomains: ['xe.works'] + advertiserDomains: ['xe'] }, ext: { pixels: [ - [ 'iframe', 'surl1' ], - [ 'image', 'surl2' ], + ['iframe', 'surl1'], + ['image', 'surl2'], ] } }] } }; - const validResponse = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequest }); + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); const bid = validResponse[0]; expect(validResponse).to.be.an('array').that.is.not.empty; expect(bid.requestId).to.equal('qwerty'); @@ -284,7 +290,7 @@ describe('xeBidAdapter', () => { expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.ttl).to.equal(600); - expect(bid.meta).to.deep.equal({ advertiserDomains: ['xe.works'] }); + expect(bid.meta).to.deep.equal({advertiserDomains: ['xe']}); }); it('should interpret valid banner response', function () { @@ -305,7 +311,7 @@ describe('xeBidAdapter', () => { } }; - const validResponseBanner = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequest }); + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); const bid = validResponseBanner[0]; expect(validResponseBanner).to.be.an('array').that.is.not.empty; expect(bid.mediaType).to.equal('banner'); @@ -331,7 +337,7 @@ describe('xeBidAdapter', () => { } }; - const validResponseBanner = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequestVideo }); + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); const bid = validResponseBanner[0]; expect(validResponseBanner).to.be.an('array').that.is.not.empty; expect(bid.mediaType).to.equal('video'); @@ -358,8 +364,8 @@ describe('xeBidAdapter', () => { requestId: 'qwerty', ext: { pixels: [ - [ 'iframe', 'surl1?a=b' ], - [ 'image', 'surl2?a=b' ], + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], ] } }] @@ -377,8 +383,8 @@ describe('xeBidAdapter', () => { requestId: 'qwerty', ext: { pixels: [ - [ 'iframe', 'surl1?a=b' ], - [ 'image', 'surl2?a=b' ], + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], ] } }] @@ -396,8 +402,8 @@ describe('xeBidAdapter', () => { requestId: 'qwerty', ext: { pixels: [ - [ 'iframe', 'surl1?a=b' ], - [ 'image', 'surl2?a=b' ], + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], ] } }] @@ -414,20 +420,20 @@ describe('xeBidAdapter', () => { describe('getBidFloor', function () { it('should return null when getFloor is not a function', () => { - const bid = { getFloor: 2 }; + const bid = {getFloor: 2}; const result = getBidFloor(bid); expect(result).to.be.null; }); it('should return null when getFloor doesnt return an object', () => { - const bid = { getFloor: () => 2 }; + const bid = {getFloor: () => 2}; const result = getBidFloor(bid); expect(result).to.be.null; }); it('should return null when floor is not a number', () => { const bid = { - getFloor: () => ({ floor: 'string', currency: 'USD' }) + getFloor: () => ({floor: 'string', currency: 'USD'}) }; const result = getBidFloor(bid); expect(result).to.be.null; @@ -435,7 +441,7 @@ describe('xeBidAdapter', () => { it('should return null when currency is not USD', () => { const bid = { - getFloor: () => ({ floor: 5, currency: 'EUR' }) + getFloor: () => ({floor: 5, currency: 'EUR'}) }; const result = getBidFloor(bid); expect(result).to.be.null; @@ -443,7 +449,7 @@ describe('xeBidAdapter', () => { it('should return floor value when everything is correct', () => { const bid = { - getFloor: () => ({ floor: 5, currency: 'USD' }) + getFloor: () => ({floor: 5, currency: 'USD'}) }; const result = getBidFloor(bid); expect(result).to.equal(5); From 549e2225195a1b0c372e554e103c12785e55979b Mon Sep 17 00:00:00 2001 From: Pavlo Kavulych <72217414+Chucky-choo@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:38:17 +0300 Subject: [PATCH 0426/1097] Digital Matter Bid Adapter: initial release (#12114) * update anyclip adapter * remove coppa * fix jsdoc warnings * create bidderUtils * delete duplicate code * refactor * add digitalMatter Bid Adapter --------- Co-authored-by: Chucky-choo --- modules/digitalMatterBidAdapter.js | 23 + modules/digitalMatterBidAdapter.md | 50 ++ .../modules/digitalMatterBidAdapter_spec.js | 458 ++++++++++++++++++ 3 files changed, 531 insertions(+) create mode 100644 modules/digitalMatterBidAdapter.js create mode 100644 modules/digitalMatterBidAdapter.md create mode 100644 test/spec/modules/digitalMatterBidAdapter_spec.js diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js new file mode 100644 index 00000000000..9676ed6e95f --- /dev/null +++ b/modules/digitalMatterBidAdapter.js @@ -0,0 +1,23 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { + buildRequests, + getUserSyncs, + interpretResponse, + isBidRequestValid +} from '../libraries/xeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'digitalmatter'; +const ENDPOINT = 'https://prebid.di-change.live'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['digitalmatter'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/digitalMatterBidAdapter.md b/modules/digitalMatterBidAdapter.md new file mode 100644 index 00000000000..1b8426497e9 --- /dev/null +++ b/modules/digitalMatterBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Digital Matter Bidder Adapter +Module Type: Digital Matter Bidder Adapter +Maintainer: di-change@digitalmatter.ai +``` + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'digitalmatter', + params: { + env: 'digitalmatter', + pid: '40', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } + }, + bids: [{ + bidder: 'digitalmatter', + params: { + env: 'digitalmatter', + pid: '40', + ext: {} + } + }] + } +]; +``` diff --git a/test/spec/modules/digitalMatterBidAdapter_spec.js b/test/spec/modules/digitalMatterBidAdapter_spec.js new file mode 100644 index 00000000000..deb31a9da02 --- /dev/null +++ b/test/spec/modules/digitalMatterBidAdapter_spec.js @@ -0,0 +1,458 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/digitalMatterBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; + +const ENDPOINT = 'https://prebid.di-change.live'; + +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'digitalmatter', + params: { + env: 'digitalmatter', + pid: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; + +const videoBidderRequest = { + bidderCode: 'digitalmatter', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'digitalmatter', + bids: [{bidId: 'qwerty'}] +}; + +describe('digitalmatterBidAdapter', () => { + describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required env param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.env; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required pid param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.pid; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + env: 'digitalmatter', + pid: '40' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); + }); + + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['digitalmatter'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['digitalmatter']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = {getFloor: 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = {getFloor: () => 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({floor: 'string', currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}) From 0b05b080ab8e87d6f627e875c9c73cf7f9a06d04 Mon Sep 17 00:00:00 2001 From: Sajid Mahmood Date: Wed, 14 Aug 2024 11:02:06 -0400 Subject: [PATCH 0427/1097] IX Bid Adapter: Remove client FT pbjs_allow_all_eids (#12117) Co-authored-by: Sajid Mahmood --- modules/ixBidAdapter.js | 17 ++----------- test/spec/modules/ixBidAdapter_spec.js | 35 ++++++++------------------ 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 776af4ab067..0ec60b51ca2 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -58,19 +58,7 @@ const SOURCE_RTI_MAPPING = { 'neustar.biz': 'fabrickId', 'zeotap.com': 'zeotapIdPlus', 'uidapi.com': 'UID2', - 'adserver.org': 'TDID', - 'id5-sync.com': '', // ID5 Universal ID, configured as id5Id - 'crwdcntrl.net': '', // Lotame Panorama ID, lotamePanoramaId - 'epsilon.com': '', // Publisher Link, publinkId - 'audigent.com': '', // Hadron ID from Audigent, hadronId - 'pubcid.org': '', // SharedID, pubcid - 'utiq.com': '', // Utiq - 'criteo.com': '', // Criteo - 'euid.eu': '', // EUID - 'intimatemerger.com': '', - '33across.com': '', - 'liveintent.indexexchange.com': '', - 'google.com': '' + 'adserver.org': 'TDID' }; const PROVIDERS = [ 'lipbid', @@ -648,10 +636,9 @@ function getEidInfo(allEids) { if (isArray(allEids)) { for (const eid of allEids) { const isSourceMapped = SOURCE_RTI_MAPPING.hasOwnProperty(eid.source); - const allowAllEidsFeatureEnabled = FEATURE_TOGGLES.isFeatureEnabled('pbjs_allow_all_eids'); const hasUids = deepAccess(eid, 'uids.0'); - if ((isSourceMapped || allowAllEidsFeatureEnabled) && hasUids) { + if (hasUids) { seenSources[eid.source] = true; if (isSourceMapped && SOURCE_RTI_MAPPING[eid.source] !== '') { diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 47583031982..e0162617be3 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -1527,9 +1527,7 @@ describe('IndexexchangeAdapter', function () { body: { ext: { pbjs_allow_all_eids: { - test: { - activated: false - } + activated: true } } } @@ -1544,11 +1542,6 @@ describe('IndexexchangeAdapter', function () { afterEach(function () { delete window.headertag; - serverResponse.body.ext.features = { - pbjs_allow_all_eids: { - activated: false - } - }; validIdentityResponse = {} }); @@ -1562,12 +1555,6 @@ describe('IndexexchangeAdapter', function () { }); it('IX adapter filters eids from prebid past the maximum eid limit', function () { - serverResponse.body.ext.features = { - pbjs_allow_all_eids: { - activated: true - } - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); let eid_sent_from_prebid = generateEid(55); cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); @@ -1609,12 +1596,6 @@ describe('IndexexchangeAdapter', function () { } } }; - serverResponse.body.ext.features = { - pbjs_allow_all_eids: { - activated: true - } - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); let eid_sent_from_prebid = generateEid(49); cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); @@ -1635,15 +1616,21 @@ describe('IndexexchangeAdapter', function () { expect(payload.ext.ixdiag.eidLength).to.equal(49); }); - it('All incoming eids are from unsupported source with feature toggle off', function () { - FEATURE_TOGGLES.setFeatureToggles(serverResponse); + it('Has incoming eids with no uid', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - let eid_sent_from_prebid = generateEid(20); + let eid_sent_from_prebid = [ + { + source: 'catijah.org' + }, + { + source: 'bagel.com' + } + ]; cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = extractPayload(request); expect(payload.user.eids).to.be.undefined - expect(payload.ext.ixdiag.eidLength).to.equal(20); + expect(payload.ext.ixdiag.eidLength).to.equal(2); }); it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { From 57acaa1785531bbb58be5526b0865080b4fa4908 Mon Sep 17 00:00:00 2001 From: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:43:29 +0200 Subject: [PATCH 0428/1097] SeedingAlliance Adapter: rework to properly use openRTB standard internally (#12101) * rework adapter to only use openRTB data internally * added comments * fix lint errors --- modules/seedingAllianceBidAdapter.js | 219 +++++++----------- .../modules/seedingAllianceAdapter_spec.js | 100 +++----- 2 files changed, 109 insertions(+), 210 deletions(-) diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index f998df27d5c..e5fbebc8ffe 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -3,10 +3,10 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; -import {_map, generateUUID, deepSetValue, isArray, isEmpty, replaceAuctionPrice} from '../src/utils.js'; +import {generateUUID, deepSetValue, isEmpty, replaceAuctionPrice} from '../src/utils.js'; import {config} from '../src/config.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {getStorageManager} from '../src/storageManager.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const GVL_ID = 371; const BIDDER_CODE = 'seedingAlliance'; @@ -14,18 +14,33 @@ const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; const NATIVENDO_KEY = 'nativendo_id'; -const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; - export const storage = getStorageManager({bidderCode: BIDDER_CODE}); -const NATIVE_PARAMS = { - title: { id: 0, name: 'title' }, - body: { id: 1, name: 'data', type: 2 }, - sponsoredBy: { id: 2, name: 'data', type: 1 }, - image: { id: 3, type: 3, name: 'img' }, - cta: { id: 4, type: 12, name: 'data' }, - icon: { id: 5, type: 1, name: 'img' } -}; +const converter = ortbConverter({ + context: { + ttl: 360, + netRevenue: true + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + // set basic page, this might be updated later by adunit param + deepSetValue(request, 'site.page', bidderRequest.refererInfo.page); + deepSetValue(request, 'regs.ext.pb_ver', '$prebid.version$'); + deepSetValue(request, 'cur', [config.getConfig('currency') || DEFAULT_CUR]); + + // As this is client side, we get needed info from headers + delete request.device; + + return request; + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + // add tagid from params + imp.tagid = bidRequest.params.adUnitId; + + return imp; + } +}); export const spec = { code: BIDDER_CODE, @@ -37,105 +52,22 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let url = bidderRequest.refererInfo.page; + const oRtbRequest = converter.toORTB({bidRequests: validBidRequests, bidderRequest}); let eids = getEids(validBidRequests[0]); - const imps = validBidRequests.map((bidRequest, id) => { - const imp = { - id: String(id + 1), - tagid: bidRequest.params.adUnitId - }; - - /** - * Native Ad - */ - if (bidRequest.nativeParams) { - const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { - const props = NATIVE_PARAMS[key]; - - if (props) { - let wmin, hmin, w, h; - let aRatios = nativeAsset.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - if (nativeAsset.sizes) { - const sizes = flatten(nativeAsset.sizes); - w = parseInt(sizes[0], 10); - h = parseInt(sizes[1], 10); - } - - const asset = { - id: props.id, - required: nativeAsset.required & 1 - }; - - asset[props.name] = { - len: nativeAsset.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } else { - // TODO Filter impressions with required assets we don't support - } - }).filter(Boolean); - - imp.native = { - request: { - assets - } - }; - } else { - let sizes = transformSizes(bidRequest.sizes); - - imp.banner = { - format: sizes, - w: sizes[0] ? sizes[0].w : 0, - h: sizes[0] ? sizes[0].h : 0 - } - } - + // check for url in params and set in site object + validBidRequests.forEach(bidRequest => { if (bidRequest.params.url) { - url = bidRequest.params.url; + deepSetValue(oRtbRequest, 'site.page', bidRequest.params.url); } - - return imp; }); - const request = { - id: bidderRequest.bidderRequestId, - site: { - page: url - }, - cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], - imp: imps, - tmax: bidderRequest.timeout, - regs: { - ext: { - gdpr: 0, - pb_ver: '$prebid.version$' - } - } - }; - if (bidderRequest.gdprConsent) { - request.user = {}; + oRtbRequest.user = {}; - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); - deepSetValue(request, 'user.ext.eids', eids); + deepSetValue(oRtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(oRtbRequest, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + deepSetValue(oRtbRequest, 'user.ext.eids', eids); } let endpoint = config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL; @@ -143,7 +75,7 @@ export const spec = { return { method: 'POST', url: endpoint, - data: JSON.stringify(request), + data: JSON.stringify(oRtbRequest), bidRequests: validBidRequests }; }, @@ -156,14 +88,13 @@ export const spec = { const { seatbid, cur } = serverResponse.body; const bidResponses = (typeof seatbid != 'undefined') ? flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { - result[bid.impid - 1] = bid; + result[bid.impid] = bid; return result; }, []) : []; - return bidRequests - .map((bidRequest, id) => { - const bidResponse = bidResponses[id]; - + .map((bidRequest) => { + const bidId = bidRequest.bidId; + const bidResponse = bidResponses[bidId]; const type = bidRequest.nativeParams ? NATIVE : BANNER; if (bidResponse) { @@ -181,7 +112,7 @@ export const spec = { }; if (type === NATIVE) { - bidObject.native = parseNative(bidResponse); + bidObject.native = parseNative(bidResponse, bidRequest.nativeParams); bidObject.mediaType = NATIVE; } @@ -238,32 +169,23 @@ const getEids = (bidRequest) => { return eids; } -function transformSizes(requestSizes) { - if (!isArray(requestSizes)) { - return []; - } - - if (requestSizes.length === 2 && !isArray(requestSizes[0])) { - return [{ - w: parseInt(requestSizes[0], 10), - h: parseInt(requestSizes[1], 10) - }]; - } else if (isArray(requestSizes[0])) { - return requestSizes.map(item => ({ - w: parseInt(item[0], 10), - h: parseInt(item[1], 10) - })); - } - - return []; -} - function flatten(arr) { return [].concat(...arr); } -function parseNative(bid) { - const { assets, link, imptrackers } = bid.adm.native; +function parseNative(bid, nativeParams) { + let native; + if (typeof bid.adm === 'string') { + try { + native = JSON.parse(bid.adm).native; + } catch (e) { + return; + } + } else { + native = bid.adm.native; + } + + const { assets, link, imptrackers } = native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -286,13 +208,34 @@ function parseNative(bid) { impressionTrackers: imptrackers || undefined }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; + let nativeParamKeys = Object.keys(nativeParams); + let id = 0; + + nativeParamKeys.forEach(nativeParam => { + assets.forEach(asset => { + if (asset.id == id) { + switch (nativeParam) { + case 'title': + result.title = asset.title.text; + break; + case 'body': + case 'cta': + case 'sponsoredBy': + result[nativeParam] = asset.data.value; + break; + case 'image': + case 'icon': + result[nativeParam] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } + id++; }); return result; diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 45d1544d100..e3ff85e9c83 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -2,8 +2,6 @@ import {assert, expect} from 'chai'; import {getStorageManager} from 'src/storageManager.js'; import {spec} from 'modules/seedingAllianceBidAdapter.js'; -import { NATIVE } from 'src/mediaTypes.js'; -import { config } from 'src/config.js'; describe('SeedingAlliance adapter', function () { let serverResponse, bidRequest, bidResponses; @@ -14,6 +12,14 @@ describe('SeedingAlliance adapter', function () { } }; + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + mediaType: { + native: {} + } + }]; + describe('isBidRequestValid', function () { it('should return true when required params found', function () { assert(spec.isBidRequestValid(bid)); @@ -27,11 +33,6 @@ describe('SeedingAlliance adapter', function () { describe('buildRequests', function () { it('should send request with correct structure', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {} - }]; - let request = spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }); assert.equal(request.method, 'POST'); @@ -40,65 +41,19 @@ describe('SeedingAlliance adapter', function () { it('should have default request structure', function () { let keys = 'site,cur,imp,regs'.split(','); - let validBidRequests = [{ - bidId: 'bidId', - params: {} - }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); let data = Object.keys(request); - assert.deepEqual(keys, data); + assert.includeDeepMembers(data, keys); }); it('Verify the site url', function () { let siteUrl = 'https://www.yourdomain.tld/your-directory/'; - let validBidRequests = [{ - bidId: 'bidId', - params: { - url: siteUrl - } - }]; + validBidRequests[0].params.url = siteUrl; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); assert.equal(request.site.page, siteUrl); }); - - it('Verify native asset ids', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - nativeParams: { - body: { - required: true, - len: 350 - }, - image: { - required: true - }, - title: { - required: true - }, - sponsoredBy: { - required: true - }, - cta: { - required: true - }, - icon: { - required: true - } - } - }]; - - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; - - assert.equal(assets[0].id, 1); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 0); - assert.equal(assets[3].id, 2); - assert.equal(assets[4].id, 4); - assert.equal(assets[5].id, 5); - }); }); describe('check user ID functionality', function () { @@ -183,23 +138,24 @@ describe('SeedingAlliance adapter', function () { const goodNativeResponse = { body: { cur: 'EUR', - id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + id: 'bidid1', seatbid: [ { seat: 'seedingAlliance', bid: [{ - adm: { - native: { - assets: [ - {id: 0, title: {text: 'this is a title'}} - ], - imptrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], - link: { - clicktrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], - url: 'https://domain.for/ad/' - } - } - }, + adm: JSON.stringify({ + native: { + assets: [ + {id: 0, title: {text: 'this is a title'}}, + {id: 1, img: {url: 'https://domain.for/img.jpg'}}, + ], + imptrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], + link: { + clicktrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], + url: 'https://domain.for/ad/' + } + } + }), impid: 1, price: 0.55 }] @@ -211,7 +167,7 @@ describe('SeedingAlliance adapter', function () { const goodBannerResponse = { body: { cur: 'EUR', - id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + id: 'bidid1', seatbid: [ { seat: 'seedingAlliance', @@ -229,18 +185,18 @@ describe('SeedingAlliance adapter', function () { const badResponse = { body: { cur: 'EUR', - id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + id: 'bidid1', seatbid: [] }}; const bidNativeRequest = { data: {}, - bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + bidRequests: [{bidId: '1', nativeParams: {title: {required: true, len: 800}, image: {required: true, sizes: [300, 250]}}}] }; const bidBannerRequest = { data: {}, - bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] + bidRequests: [{bidId: '1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { From e0651f657bd6d1a3dfa8053df007d97cf8034b1d Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 14 Aug 2024 14:22:20 -0400 Subject: [PATCH 0429/1097] saambaaBidAdapter.js: make alias of advangelist (#11992) * saambaaBidAdapter.js: reuse repeated code block * Update saambaaBidAdapter.js * Update advangelistsBidAdapter.js * Update saambaaBidAdapter.js * Update advangelistsBidAdapter.js * Update saambaaBidAdapter.js * Update advangelistsBidAdapter.js * Create index.js * Update saambaaBidAdapter.js * Update advangelistsBidAdapter.js * Update index.js * Update advangelistsBidAdapter.js * Update saambaaBidAdapter.js * Update index.js * Update advangelistsBidAdapter.js * Update saambaaBidAdapter.js * Update index.js * Update advangelistsBidAdapter.js * Update saambaaBidAdapter.js * Update advangelistsBidAdapter.js * Delete modules/saambaaBidAdapter.js * Update advangelistsBidAdapter.js * Update beachfrontBidAdapter.js * Update beachfrontBidAdapter.js * Update beachfrontBidAdapter.js * Update nextrollBidAdapter.js * Update beachfrontBidAdapter.js * Update index.js * Update advangelistsBidAdapter.js * Update beachfrontBidAdapter.js * Create saambaaBidAdapter.js * fix file name --------- Co-authored-by: Chris Huie --- libraries/advangUtils/index.js | 228 ++++++++++++++++ modules/advangelistsBidAdapter.js | 322 ++--------------------- modules/beachfrontBidAdapter.js | 101 +------ modules/nextrollBidAdapter.js | 24 +- modules/saambaaBidAdapter.js | 420 +----------------------------- 5 files changed, 251 insertions(+), 844 deletions(-) create mode 100644 libraries/advangUtils/index.js diff --git a/libraries/advangUtils/index.js b/libraries/advangUtils/index.js new file mode 100644 index 00000000000..08c8e43cea3 --- /dev/null +++ b/libraries/advangUtils/index.js @@ -0,0 +1,228 @@ +import { deepAccess, generateUUID, isFn, parseSizesInput, parseUrl } from '../../src/utils.js'; +import { config } from '../../src/config.js'; +import { find, includes } from '../../src/polyfill.js'; + +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +export function isBannerBid(bid) { + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +export function isVideoBid(bid) { + return deepAccess(bid, 'mediaTypes.video'); +} + +export function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +export function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + +export function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +export function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +export function getVideoBidParam(bid, key) { + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); +} + +export function getBannerBidParam(bid, key) { + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); +} + +export function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +export function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +export function getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +export function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +export function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +export function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +export function parseSizes(sizes) { + return parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +export function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +export function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +export function getTopWindowReferrer(bidderRequest) { + return bidderRequest?.refererInfo?.ref || ''; +} + +export function getTopWindowLocation(bidderRequest) { + return parseUrl(bidderRequest?.refererInfo?.page, {decodeSearchAsString: true}); +} + +export function getVideoTargetingParams(bid, VIDEO_TARGETING) { + const result = {}; + const excludeProps = ['playerSize', 'context', 'w', 'h']; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => !includes(excludeProps, key)) + .forEach(key => { + result[ key ] = bid.mediaTypes.video[ key ]; + }); + Object.keys(Object(bid.params.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.params.video[ key ]; + }); + return result; +} + +export function createRequestData(bid, bidderRequest, isVideo, getBidParam, getSizes, getBidFloor, BIDDER_CODE, ADAPTER_VERSION) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(bidderRequest); + let paramSize = getBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getSizes(bid); + } + + const firstSize = getFirstSize(sizes); + let floor = getBidFloor(bid) || (isVideo ? 0.5 : 0.1); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': Math.min(3000, bidderRequest.timeout), + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': {} + }, + 'user': { + 'ext': {} + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBidParam(bid, 'placement'); + let impType = isVideo ? { + 'video': Object.assign({ + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, getVideoTargetingParams(bid)) + } : { + 'banner': { + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h + } + }; + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + ...impType + }); + } + + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index a916e07a963..3a571831505 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -1,25 +1,23 @@ -import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; +import { isEmpty } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { createRequestData, getBannerBidFloor, getBannerBidParam, getBannerSizes, getVideoBidFloor, getVideoBidParam, getVideoSizes, isBannerBidValid, isVideoBid, isVideoBidValid } from '../libraries/advangUtils/index.js'; const ADAPTER_VERSION = '1.0'; const BIDDER_CODE = 'advangelists'; - -export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; -export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; -export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; -export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; let pubid = ''; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], - + aliases: ['saambaa'], isBidRequestValid(bidRequest) { - if (typeof bidRequest != 'undefined') { + if (typeof bidRequest !== 'undefined') { if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } return true; @@ -35,7 +33,7 @@ export const spec = { requests.push({ method: 'POST', url: VIDEO_ENDPOINT + pubid, - data: createVideoRequestData(bid, bidderRequest), + data: createRequestData(bid, bidderRequest, true, getVideoBidParam, getVideoSizes, getVideoBidFloor), bidRequest: bid }); }); @@ -45,16 +43,16 @@ export const spec = { requests.push({ method: 'POST', url: BANNER_ENDPOINT + pubid, - data: createBannerRequestData(bid, bidderRequest), + data: createRequestData(bid, bidderRequest, false, getBannerBidParam, getBannerSizes, getBannerBidFloor, BIDDER_CODE, ADAPTER_VERSION), bidRequest: bid }); }); return requests; }, - interpretResponse(serverResponse, {bidRequest}) { + interpretResponse(serverResponse, { bidRequest }) { let response = serverResponse.body; - if (response !== null && isEmpty(response) == false) { + if (response !== null && isEmpty(response) === false) { if (isVideoBid(bidRequest)) { let bidResponse = { requestId: response.id, @@ -63,11 +61,11 @@ export const spec = { height: response.seatbid[0].bid[0].h, ttl: response.seatbid[0].bid[0].ttl || 60, creativeId: response.seatbid[0].bid[0].crid, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, mediaType: VIDEO, netRevenue: true - } + }; if (response.seatbid[0].bid[0].adm) { bidResponse.vastXml = response.seatbid[0].bid[0].adm; @@ -93,298 +91,10 @@ export const spec = { meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, mediaType: BANNER, netRevenue: true - } + }; } } } }; -function isBannerBid(bid) { - return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function isVideoBid(bid) { - return deepAccess(bid, 'mediaTypes.video'); -} - -function getBannerBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; - return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); -} - -function getVideoBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; - return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); -} - -function isVideoBidValid(bid) { - return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); -} - -function isBannerBidValid(bid) { - return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); -} - -function getVideoBidParam(bid, key) { - return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); -} - -function getBannerBidParam(bid, key) { - return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function getDoNotTrack() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -function findAndFillParam(o, key, value) { - try { - if (typeof value === 'function') { - o[key] = value(); - } else { - o[key] = value; - } - } catch (ex) {} -} - -function getOsVersion() { - let clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); - return cs ? cs.s : 'unknown'; -} - -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function getTopWindowReferrer(bidderRequest) { - return bidderRequest?.refererInfo?.ref || ''; -} - -function getVideoTargetingParams(bid) { - const result = {}; - const excludeProps = ['playerSize', 'context', 'w', 'h']; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => !includes(excludeProps, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} - -function createVideoRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(bidderRequest); - - let sizes = getVideoSizes(bid); - let firstSize = getFirstSize(sizes); - let bidfloor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 2 : getVideoBidFloor(bid); - let video = getVideoTargetingParams(bid); - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1, - 'os': getOsVersion() - }, - 'at': 2, - 'site': {}, - 'tmax': Math.min(3000, bidderRequest.timeout), - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getVideoBidParam(bid, 'placement'); - - for (let j = 0; j < sizes.length; j++) { - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': bidfloor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'video': Object.assign({ - 'id': generateUUID(), - 'pos': 0, - 'w': firstSize.w, - 'h': firstSize.h, - 'mimes': DEFAULT_MIMES - }, video) - - }); - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} - -function getTopWindowLocation(bidderRequest) { - return parseUrl(bidderRequest?.refererInfo?.page, {decodeSearchAsString: true}); -} - -function createBannerRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(bidderRequest); - - let sizes = getBannerSizes(bid); - let bidfloor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 2 : getBannerBidFloor(bid); - - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1 - }, - 'at': 2, - 'site': {}, - 'tmax': Math.min(3000, bidderRequest.timeout), - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getBannerBidParam(bid, 'placement'); - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': bidfloor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'banner': { - 'id': generateUUID(), - 'pos': 0, - 'w': size['w'], - 'h': size['h'] - } - }); - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} - registerBidder(spec); diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 8f4a9e3e46d..d05769de010 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -4,16 +4,14 @@ import { deepSetValue, getUniqueIdentifierStr, isArray, - isFn, logWarn, - parseSizesInput, - parseUrl, formatQS } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; +import {find} from '../src/polyfill.js'; +import { getFirstSize, getOsVersion, getVideoSizes, getBannerSizes, isConnectedTV, getDoNotTrack, isMobile, isBannerBid, isVideoBid, getBannerBidFloor, getVideoBidFloor, getVideoTargetingParams, getTopWindowLocation } from '../libraries/advangUtils/index.js'; const ADAPTER_VERSION = '1.21'; const ADAPTER_NAME = 'BFIO_PREBID'; @@ -222,69 +220,6 @@ function createRenderer(bidRequest) { return renderer; } -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function getOsVersion() { - let clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); - return cs ? cs.s : 'unknown'; -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function getDoNotTrack() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -function isVideoBid(bid) { - return deepAccess(bid, 'mediaTypes.video'); -} - -function isBannerBid(bid) { - return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - function getVideoBidParam(bid, key) { return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); } @@ -298,16 +233,6 @@ function getPlayerBidParam(bid, key, defaultValue) { return param === undefined ? defaultValue : param; } -function getBannerBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'banner', size: '*' }) : {}; - return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); -} - -function getVideoBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'video', size: '*' }) : {}; - return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); -} - function isVideoBidValid(bid) { return isVideoBid(bid) && getVideoBidParam(bid, 'appId') && getVideoBidParam(bid, 'bidfloor'); } @@ -316,10 +241,6 @@ function isBannerBidValid(bid) { return isBannerBid(bid) && getBannerBidParam(bid, 'appId') && getBannerBidParam(bid, 'bidfloor'); } -function getTopWindowLocation(bidderRequest) { - return parseUrl(bidderRequest?.refererInfo?.page, { decodeSearchAsString: true }); -} - function getEids(bid) { return SUPPORTED_USER_IDS .map(getUserId(bid)) @@ -347,26 +268,10 @@ function formatEid(id, source, rtiPartner, atype) { }; } -function getVideoTargetingParams(bid) { - const result = {}; - const excludeProps = ['playerSize', 'context', 'w', 'h']; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => !includes(excludeProps, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} - function createVideoRequestData(bid, bidderRequest) { let sizes = getVideoSizes(bid); let firstSize = getFirstSize(sizes); - let video = getVideoTargetingParams(bid); + let video = getVideoTargetingParams(bid, VIDEO_TARGETING); let appId = getVideoBidParam(bid, 'appId'); let bidfloor = getVideoBidFloor(bid); let tagid = getVideoBidParam(bid, 'tagid'); diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index 8a41efe4dcc..689f2120de2 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -11,6 +11,7 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getOsVersion } from '../libraries/advangUtils/index.js'; import {find} from '../src/polyfill.js'; @@ -312,7 +313,7 @@ function _getDevice(_bidRequest) { ua: navigator.userAgent, language: navigator['language'], os: _getOs(navigator.userAgent.toLowerCase()), - osv: _getOsVersion(navigator.userAgent) + osv: getOsVersion() }; } @@ -343,25 +344,4 @@ function _getOs(userAgent) { }) || 'etc'; } -function _getOsVersion(userAgent) { - const clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(userAgent)); - return cs ? cs.s : 'unknown'; -} - registerBidder(spec); diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js index da6e7028abe..3e33496b7d9 100644 --- a/modules/saambaaBidAdapter.js +++ b/modules/saambaaBidAdapter.js @@ -1,419 +1,3 @@ -// TODO: this adapter appears to have no tests - -import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; - -const ADAPTER_VERSION = '1.0'; -const BIDDER_CODE = 'saambaa'; - -export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; -export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; -export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; -export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; - -let pubid = ''; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - isBidRequestValid(bidRequest) { - if (typeof bidRequest != 'undefined') { - if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } - if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } - return true; - } else { return false; } - }, - - buildRequests(bids, bidderRequest) { - let requests = []; - let videoBids = bids.filter(bid => isVideoBidValid(bid)); - let bannerBids = bids.filter(bid => isBannerBidValid(bid)); - videoBids.forEach(bid => { - pubid = getVideoBidParam(bid, 'pubid'); - requests.push({ - method: 'POST', - url: VIDEO_ENDPOINT + pubid, - data: createVideoRequestData(bid, bidderRequest), - bidRequest: bid - }); - }); - - bannerBids.forEach(bid => { - pubid = getBannerBidParam(bid, 'pubid'); - - requests.push({ - method: 'POST', - url: BANNER_ENDPOINT + pubid, - data: createBannerRequestData(bid, bidderRequest), - bidRequest: bid - }); - }); - return requests; - }, - - interpretResponse(serverResponse, {bidRequest}) { - let response = serverResponse.body; - if (response !== null && isEmpty(response) == false) { - if (isVideoBid(bidRequest)) { - let bidResponse = { - requestId: response.id, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, - mediaType: VIDEO, - netRevenue: true - } - - if (response.seatbid[0].bid[0].adm) { - bidResponse.vastXml = response.seatbid[0].bid[0].adm; - bidResponse.adResponse = { - content: response.seatbid[0].bid[0].adm - }; - } else { - bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; - } - - return bidResponse; - } else { - return { - requestId: response.id, - bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, - mediaType: BANNER, - netRevenue: true - } - } - } - } -}; - -function isBannerBid(bid) { - return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function isVideoBid(bid) { - return deepAccess(bid, 'mediaTypes.video'); -} - -function getBannerBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; - return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); -} - -function getVideoBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; - return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); -} - -function isVideoBidValid(bid) { - return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); -} - -function isBannerBidValid(bid) { - return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); -} - -function getVideoBidParam(bid, key) { - return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); -} - -function getBannerBidParam(bid, key) { - return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function getDoNotTrack() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -function findAndFillParam(o, key, value) { - try { - if (typeof value === 'function') { - o[key] = value(); - } else { - o[key] = value; - } - } catch (ex) {} -} - -function getOsVersion() { - let clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); - return cs ? cs.s : 'unknown'; -} - -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } -} - -function getVideoTargetingParams(bid) { - const result = {}; - const excludeProps = ['playerSize', 'context', 'w', 'h']; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => !includes(excludeProps, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} - -function createVideoRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); - - // if size is explicitly given via adapter params - let paramSize = getVideoBidParam(bid, 'size'); - let sizes = []; - let coppa = config.getConfig('coppa'); - - if (typeof paramSize !== 'undefined' && paramSize != '') { - sizes = parseSizes(paramSize); - } else { - sizes = getVideoSizes(bid); - } - const firstSize = getFirstSize(sizes); - let floor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 0.5 : getVideoBidFloor(bid); - let video = getVideoTargetingParams(bid); - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1, - 'os': getOsVersion() - }, - 'at': 2, - 'site': {}, - 'tmax': 3000, - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getVideoBidParam(bid, 'placement'); - - for (let j = 0; j < sizes.length; j++) { - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': floor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'video': Object.assign({ - 'id': generateUUID(), - 'pos': 0, - 'w': firstSize.w, - 'h': firstSize.h, - 'mimes': DEFAULT_MIMES - }, video) - - }); - } - if (coppa) { - o.regs.ext = {'coppa': 1}; - } - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} - -function getTopWindowLocation(bidderRequest) { - return parseUrl(bidderRequest?.refererInfo?.page || '', { decodeSearchAsString: true }); -} - -function createBannerRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); - - // if size is explicitly given via adapter params - - let paramSize = getBannerBidParam(bid, 'size'); - let sizes = []; - let coppa = config.getConfig('coppa'); - if (typeof paramSize !== 'undefined' && paramSize != '') { - sizes = parseSizes(paramSize); - } else { - sizes = getBannerSizes(bid); - } - - let floor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 0.1 : getBannerBidFloor(bid); - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1 - }, - 'at': 2, - 'site': {}, - 'tmax': 3000, - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getBannerBidParam(bid, 'placement'); - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': floor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'banner': { - 'id': generateUUID(), - 'pos': 0, - 'w': size['w'], - 'h': size['h'] - } - }); - } - if (coppa) { - o.regs.ext = {'coppa': 1}; - } - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} +import { spec } from './advangelistsBidAdapter.js'; // eslint-disable-line prebid/validate-imports +import { registerBidder } from '../src/adapters/bidderFactory.js'; registerBidder(spec); From 88cc9f1ee5d0fccc7788b0868a1163094d0d1bb2 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 15 Aug 2024 17:00:51 +0000 Subject: [PATCH 0430/1097] Prebid 9.10.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2945d258a7..53383905517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.10.0-pre", + "version": "9.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.10.0-pre", + "version": "9.10.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 95b24e00427..2ef935a5119 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.10.0-pre", + "version": "9.10.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From faceb406aaf612b349c956eb239b1c1133a0748d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 15 Aug 2024 17:00:52 +0000 Subject: [PATCH 0431/1097] Increment version to 9.11.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53383905517..5a4071ec14d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.10.0", + "version": "9.11.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.10.0", + "version": "9.11.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 2ef935a5119..3a7189fca59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.10.0", + "version": "9.11.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 1409a4a492fde146836dce09d70634e75ea05ed0 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 16 Aug 2024 06:44:34 -0700 Subject: [PATCH 0432/1097] Core: make sure adUnitCodes are unique in auction events (#12127) --- src/prebid.js | 4 ++++ test/spec/unit/pbjs_api_spec.js | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index c92ab8f5a89..0eaccf5a8a6 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -503,6 +503,9 @@ pbjsInstance.requestBids = (function() { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + if (adUnitCodes != null && !Array.isArray(adUnitCodes)) { + adUnitCodes = [adUnitCodes]; + } if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); @@ -510,6 +513,7 @@ pbjsInstance.requestBids = (function() { // otherwise derive adUnitCodes from adUnits adUnitCodes = adUnits && adUnits.map(unit => unit.code); } + adUnitCodes = adUnitCodes.filter(uniques); const ortb2Fragments = { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index de267b793b2..558fb17dc42 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1843,7 +1843,24 @@ describe('Unit: Prebid Module', function () { adUnitCodes: 'two' }); sinon.assert.calledWith(startAuctionStub, sinon.match({ - adUnits: [{code: 'two'}] + adUnits: [{code: 'two'}], + adUnitCodes: ['two'] + })); + }); + + it('does not repeat ad unit codes on twin ad units', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{code: 'au1'}, {code: 'au2'}, {code: 'au1'}, {code: 'au2'}], + }); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + adUnitCodes: ['au1', 'au2'] + })); + }); + + it('filters out repeated ad unit codes from input', () => { + $$PREBID_GLOBAL$$.requestBids({adUnitCodes: ['au1', 'au1', 'au2']}); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + adUnitCodes: ['au1', 'au2'] })); }); From edb2d9d4195096915537a7994baef1b140ebea4b Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:52:08 -0400 Subject: [PATCH 0433/1097] Mobian RTD provider: update API endpoint (#12121) * update API endpoint for mobian RTD provider * lint mobian RTD provider --- modules/mobianRtdProvider.js | 24 ++---- test/spec/modules/mobianRtdProvider_spec.js | 87 +++++++++++++++------ 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index d71948046b9..6499c3519f5 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -10,7 +10,7 @@ import { deepSetValue, safeJSONParse } from '../src/utils.js'; * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule */ -export const MOBIAN_URL = 'https://impact-api-prod.themobian.com/brand_safety'; +export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async'; /** @type {RtdSubmodule} */ export const mobianBrandSafetySubmodule = { @@ -26,7 +26,7 @@ function init() { function getBidRequestData(bidReqConfig, callback, config) { const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; const pageUrl = encodeURIComponent(getPageUrl()); - const requestUrl = `${MOBIAN_URL}/by_url?url=${pageUrl}`; + const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; const ajax = ajaxBuilder(); @@ -34,25 +34,17 @@ function getBidRequestData(bidReqConfig, callback, config) { ajax(requestUrl, { success: function(responseData) { let response = safeJSONParse(responseData); - if (!response) { + if (!response || !response.meta.has_results) { resolve({}); callback(); return; } - let mobianRisk = response.garm_risk || 'unknown'; - - const contentCategories = Object.keys(response) - .filter(key => key.startsWith('garm_content_category_') && response[key]) - .map(key => key.replace('garm_content_category_', '')); - - const sentiment = Object.keys(response) - .find(key => key.startsWith('sentiment_') && response[key]) - ?.replace('sentiment_', '') || 'unknown'; - - const emotions = Object.keys(response) - .filter(key => key.startsWith('emotion_') && response[key]) - .map(key => key.replace('emotion_', '')); + const results = response.results; + const mobianRisk = results.mobianRisk || 'unknown'; + const contentCategories = results.mobianContentCategories || []; + const sentiment = results.mobianSentiment || 'unknown'; + const emotions = results.mobianEmotions || []; const risk = { risk: mobianRisk, diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index 717745c02f2..278d8fdef92 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -25,12 +25,19 @@ describe('Mobian RTD Submodule', function () { ajaxStub.restore(); }); - it('should set key-value pairs when server responds with garm_risk', function () { + it('should set key-value pairs when server responds with valid data', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ - garm_risk: 'low', - sentiment_positive: true, - emotion_joy: true + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'low', + mobianSentiment: 'positive', + mobianContentCategories: [], + mobianEmotions: ['joy'] + } })); }); @@ -50,15 +57,19 @@ describe('Mobian RTD Submodule', function () { }); }); - it('should handle response with GARM content categories, sentiment, and emotions', function () { + it('should handle response with content categories and multiple emotions', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ - garm_risk: 'medium', - garm_content_category_arms: true, - garm_content_category_crime: true, - sentiment_negative: true, - emotion_anger: true, - emotion_fear: true + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'medium', + mobianSentiment: 'negative', + mobianContentCategories: ['arms', 'crime'], + mobianEmotions: ['anger', 'fear'] + } })); }); @@ -78,26 +89,22 @@ describe('Mobian RTD Submodule', function () { }); }); - it('should return unknown risk when garm_risk is not present', function () { + it('should return empty object when server responds with has_results: false', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ - sentiment_neutral: true + meta: { + url: 'https://example.com', + has_results: false + }, + results: {} })); }); return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { - expect(result).to.deep.equal({ - risk: 'unknown', - contentCategories: [], - sentiment: 'neutral', - emotions: [] - }); - expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ - mobianRisk: 'unknown', - mobianContentCategories: [], - mobianSentiment: 'neutral', - mobianEmotions: [] - }); + expect(result).to.deep.equal({}); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.not.have.any.keys( + 'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions' + ); }); }); @@ -122,4 +129,34 @@ describe('Mobian RTD Submodule', function () { expect(result).to.deep.equal({}); }); }); + + it('should use default values when fields are missing in the response', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'high' + // Missing other fields + } + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ + risk: 'high', + contentCategories: [], + sentiment: 'unknown', + emotions: [] + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'high', + mobianContentCategories: [], + mobianSentiment: 'unknown', + mobianEmotions: [] + }); + }); + }); }); From c30a7fe2e9e05756331e72228ce5862e133821e7 Mon Sep 17 00:00:00 2001 From: mp4symitri Date: Fri, 16 Aug 2024 19:56:32 +0530 Subject: [PATCH 0434/1097] Initial Commit for Symitri Analytics Adapter (#12132) Co-authored-by: Manan --- modules/symitriAnalyticsAdapter.js | 61 +++++++++++++ modules/symitriAnalyticsAdapter.md | 35 ++++++++ .../modules/symitriAnalyticsAdapter_spec.js | 90 +++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 modules/symitriAnalyticsAdapter.js create mode 100644 modules/symitriAnalyticsAdapter.md create mode 100644 test/spec/modules/symitriAnalyticsAdapter_spec.js diff --git a/modules/symitriAnalyticsAdapter.js b/modules/symitriAnalyticsAdapter.js new file mode 100644 index 00000000000..89dc27886e3 --- /dev/null +++ b/modules/symitriAnalyticsAdapter.js @@ -0,0 +1,61 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { logMessage } from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; + +const analyticsType = 'endpoint'; +const url = 'https://ProdSymPrebidEventhub1.servicebus.windows.net/prebid-said-1/messages'; + +const { BID_WON } = EVENTS; + +let initOptions; + +let symitriAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + logMessage('##### symitriAnalytics :: Event Triggered : ' + eventType); + sendEvent(args); + break; + default: + logMessage('##### symitriAnalytics :: Event Triggered : ' + eventType); + break; + } + } +}); + +function sendEvent(payload) { + try { + if (initOptions.apiAuthToken) { + const body = JSON.stringify(payload); + logMessage('##### symitriAnalytics :: sendEvent ', payload); + let cb = { + success: () => { + logMessage('##### symitriAnalytics :: Bid Reported Successfully'); + }, + error: (request, error) => { + logMessage('##### symitriAnalytics :: Bid Report Failed' + error); + } + }; + + ajax(url, cb, body, { + method: 'POST', + customHeaders: {'Content-Type': 'application/atom+xml;type=entry;charset=utf-8', 'Authorization': initOptions.apiAuthToken} + }); + } + } catch (err) { logMessage('##### symitriAnalytics :: error' + err) } +} + +symitriAnalytics.originEnableAnalytics = symitriAnalytics.enableAnalytics; +symitriAnalytics.enableAnalytics = function (config) { + initOptions = config.options; + symitriAnalytics.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: symitriAnalytics, + code: 'symitri' +}); + +export default symitriAnalytics; diff --git a/modules/symitriAnalyticsAdapter.md b/modules/symitriAnalyticsAdapter.md new file mode 100644 index 00000000000..d7da72ae166 --- /dev/null +++ b/modules/symitriAnalyticsAdapter.md @@ -0,0 +1,35 @@ +### Overview + + Symitri Analytics Adapter. + +### Integration + + 1) Build the symitriAnalyticsAdapter module into the Prebid.js package with: + + ``` + gulp build --modules=symitriAnalyticsAdapter,... + ``` + + 2) Use `enableAnalytics` to instruct Prebid.js to initilaize the symitriAnalyticsAdapter module, as specified below. + +### Configuration + +``` + pbjs.enableAnalytics({ + provider: 'symitri', + options: { + 'apiAuthToken': '' + } + }); + ``` + +Please reach out to your Symitri account representative(Prebid@symitri.com) to get provisioned on the DAP platform. + + +### Testing +To view an example of available segments returned by dap: +``` +‘gulp serve --modules=rtdModule,symitriDapRtdProvider,symitriAnalyticsAdapter,appnexusBidAdapter,sovrnBidAdapter’ +``` +and then point your browser at: +"http://localhost:9999/integrationExamples/gpt/symitridap_segments_example.html" diff --git a/test/spec/modules/symitriAnalyticsAdapter_spec.js b/test/spec/modules/symitriAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..c02d5b55696 --- /dev/null +++ b/test/spec/modules/symitriAnalyticsAdapter_spec.js @@ -0,0 +1,90 @@ +import symitriAnalyticsAdapter from 'modules/symitriAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; + +let events = require('src/events'); + +describe('symitri analytics adapter', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + describe('track', function () { + let initOptionsValid = { + apiAuthToken: 'TOKEN1234' + }; + let initOptionsInValid = { + }; + + let bidWon = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '393976d8770041', + 'requestId': '263efc09896d0c', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'responseTimestamp': 1576823894050, + 'requestTimestamp': 1576823893838, + 'bidder': 'appnexus', + 'timeToRespond': 212, + 'status': 'rendered' + }; + + adapterManager.registerAnalyticsAdapter({ + code: 'symitri', + adapter: symitriAnalyticsAdapter + }); + + afterEach(function () { + symitriAnalyticsAdapter.disableAnalytics(); + }); + + it('Test with valid apiAuthToken', function () { + adapterManager.enableAnalytics({ + provider: 'symitri', + options: initOptionsValid + }); + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + }); + + it('Test with missing apiAuthToken', function () { + adapterManager.enableAnalytics({ + provider: 'symitri', + options: initOptionsInValid + }); + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(0); + }); + + it('Test correct winning bid is sent to server', function () { + adapterManager.enableAnalytics({ + provider: 'symitri', + options: initOptionsValid + }); + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + let winEventData = JSON.parse(server.requests[0].requestBody); + expect(winEventData).to.deep.equal(bidWon); + let authToken = server.requests[0].requestHeaders['Authorization']; + expect(authToken).to.equal(initOptionsValid.apiAuthToken); + }); + }); +}); From f649e11aca09f1937755c4dacaebc11e5dc41d93 Mon Sep 17 00:00:00 2001 From: dream-djaxtech <62751516+dream-djaxtech@users.noreply.github.com> Date: Sat, 17 Aug 2024 19:27:52 +0530 Subject: [PATCH 0435/1097] Djax Bid Adapter : initial release (#12120) * Djax bid adapter files added * fix linting issue * Linting issue fixed * Update djaxBidAdapter.js * Update djaxBidAdapter_spec.js * Update djaxBidAdapter_spec.js --------- Co-authored-by: Chris Huie Co-authored-by: Patrick McCann --- modules/djaxBidAdapter.js | 113 +++++++++++++++++++++++ modules/djaxBidAdapter.md | 51 ++++++++++ test/spec/modules/djaxBidAdapter_spec.js | 100 ++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 modules/djaxBidAdapter.js create mode 100644 modules/djaxBidAdapter.md create mode 100644 test/spec/modules/djaxBidAdapter_spec.js diff --git a/modules/djaxBidAdapter.js b/modules/djaxBidAdapter.js new file mode 100644 index 00000000000..7a9e359f520 --- /dev/null +++ b/modules/djaxBidAdapter.js @@ -0,0 +1,113 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; +import {Renderer} from '../src/Renderer.js'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'djax'; +const DOMAIN = 'https://revphpe.djaxbidder.com/header_bidding_vast/'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +function outstreamRender(bidAd) { + bidAd.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bidAd.width, bidAd.height], + width: bidAd.width, + height: bidAd.height, + targetId: bidAd.adUnitCode, + adResponse: bidAd.adResponse, + rendererOptions: { + showVolume: false, + allowFullscreen: false + } + }); + }); +} + +function createRenderer(bidAd, rendererParams, adUnitCode) { + const renderer = Renderer.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false, + config: {'player_height': bidAd.height, 'player_width': bidAd.width}, + adUnitCode + }); + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function sendResponseToServer(data) { + ajax(DOMAIN + 'www/admin/plugins/Prebid/tracking/track.php', null, JSON.stringify(data), { + withCredentials: false, + method: 'POST', + crossOrigin: true + }); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + + isBidRequestValid: function(bid) { + return (typeof bid.params !== 'undefined' && parseInt(utils.getValue(bid.params, 'publisherId')) > 0); + }, + + buildRequests: function(validBidRequests) { + return { + method: 'POST', + url: DOMAIN + 'www/admin/plugins/Prebid/getAd.php', + options: { + withCredentials: false, + crossOrigin: true + }, + data: validBidRequests, + }; + }, + + interpretResponse: function(serverResponse, request) { + const response = serverResponse.body; + const bidResponses = []; + var bidRequestResponses = []; + + utils._each(response, function(bidAd) { + bidAd.adResponse = { + content: bidAd.vastXml, + height: bidAd.height, + width: bidAd.width + }; + + bidAd.renderer = bidAd.context === 'outstream' ? createRenderer(bidAd, { + id: bidAd.adUnitCode, + url: RENDERER_URL + }, bidAd.adUnitCode) : undefined; + bidResponses.push(bidAd); + }); + + bidRequestResponses.push({ + function: 'saveResponses', + request: request, + response: bidResponses + }); + sendResponseToServer(bidRequestResponses); + return bidResponses; + }, + + onBidWon: function(bid) { + let wonBids = []; + wonBids.push(bid); + wonBids[0].function = 'onBidWon'; + sendResponseToServer(wonBids); + }, + + onTimeout: function(details) { + details.unshift({ 'function': 'onTimeout' }); + sendResponseToServer(details); + } +}; + +registerBidder(spec); diff --git a/modules/djaxBidAdapter.md b/modules/djaxBidAdapter.md new file mode 100644 index 00000000000..d36a92de458 --- /dev/null +++ b/modules/djaxBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: Djax Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@djaxtech.com +``` + +# Description + +Module that connects to Djax + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "djax", + params: { + publisherId: '2' // string - required + } + } + ] + } + ]; +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [[480, 320]], // a display size + } + }, + bids: [ + { + bidder: "djax", + params: { + publisherId: '12' // string - required + } + } + ] + } + ]; \ No newline at end of file diff --git a/test/spec/modules/djaxBidAdapter_spec.js b/test/spec/modules/djaxBidAdapter_spec.js new file mode 100644 index 00000000000..afa9a36eab7 --- /dev/null +++ b/test/spec/modules/djaxBidAdapter_spec.js @@ -0,0 +1,100 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec } from '../../../modules/djaxBidAdapter.js'; + +const DOMAIN = 'https://revphpe.djaxbidder.com/header_bidding_vast/'; +const ENDPOINT = DOMAIN + 'www/admin/plugins/Prebid/getAd.php'; + +describe('Djax Adapter', function() { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('should exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValidForBanner', () => { + let bid = { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'sizes': [[300, 250]], + 'bidId': '26e88c3c703e66', + 'bidderRequestId': '1a8ff729f6c1a3', + 'auctionId': 'cb65d954-ffe1-4f4a-8603-02b521c00333', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequestsForBanner', () => { + let bidRequests = [ + { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'sizes': [[300, 250]], + 'bidId': '26e88c3c703e66', + 'bidderRequestId': '1a8ff729f6c1a3', + 'auctionId': 'cb65d954-ffe1-4f4a-8603-02b521c00333' + } + ]; + + it('sends bid request to ENDPOINT via POST', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.contain(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponseForBanner', () => { + let bidRequests = [ + { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'sizes': [[300, 250]], + 'bidId': '26e88c3c703e66', + 'bidderRequestId': '1a8ff729f6c1a3', + 'auctionId': 'cb65d954-ffe1-4f4a-8603-02b521c00333' + } + ]; + + it('handles nobid responses', () => { + var request = spec.buildRequests(bidRequests); + let response = ''; + let result = spec.interpretResponse(response, request[0]); + expect(result.length).to.equal(0); + }); + }); +}); From 4835e086693c70f04234321e74e48d01a8a9d928 Mon Sep 17 00:00:00 2001 From: Nikhil <137479857+NikhilGopalChennissery@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:54:21 +0530 Subject: [PATCH 0436/1097] Preciso : Added new library to remove code duplication in bid adapter (#11868) * Preciso : Added new library to remove code duplication in bid adapter * modified bidfloor mapping logic * error fix * error fix * import bidUtils.js in IdxBidadapter to reduce the code duplicataion * import bidUtils.js in IdxBidadapter to reduce the code duplicataion * Error fix * Imported bidUtils library into redtram bid adapter * import common library in mediabramaBidAdapter * import common library in loganBidAdapter to remove code duplication * removed storageManaser from library * renderer.js changes reverted --- integrationExamples/gpt/precisoExample.html | 170 +++++++++++ libraries/precisoUtils/bidUtils.js | 103 +++++++ libraries/precisoUtils/bidUtilsCommon.js | 173 ++++++++++++ modules/idxBidAdapter.js | 28 +- modules/loganBidAdapter.js | 90 +----- modules/mediabramaBidAdapter.js | 147 +--------- modules/precisoBidAdapter.js | 232 ++++----------- modules/redtramBidAdapter.js | 142 +--------- .../precisoUtils/bidUtilsCommon_spec.js | 267 ++++++++++++++++++ .../libraries/precisoUtils/bidUtils_spec.js | 150 ++++++++++ test/spec/modules/precisoBidAdapter_spec.js | 77 +++-- 11 files changed, 973 insertions(+), 606 deletions(-) create mode 100644 integrationExamples/gpt/precisoExample.html create mode 100644 libraries/precisoUtils/bidUtils.js create mode 100644 libraries/precisoUtils/bidUtilsCommon.js create mode 100644 test/spec/libraries/precisoUtils/bidUtilsCommon_spec.js create mode 100644 test/spec/libraries/precisoUtils/bidUtils_spec.js diff --git a/integrationExamples/gpt/precisoExample.html b/integrationExamples/gpt/precisoExample.html new file mode 100644 index 00000000000..04f44b28345 --- /dev/null +++ b/integrationExamples/gpt/precisoExample.html @@ -0,0 +1,170 @@ + + + + + + + + + + + + + +

Basic Prebid.js Example with Preciso Bidder

+

Adslot-1

+
+ +
+ +
+

Adslot-2

+ +
+ + + + diff --git a/libraries/precisoUtils/bidUtils.js b/libraries/precisoUtils/bidUtils.js new file mode 100644 index 00000000000..95278cd1013 --- /dev/null +++ b/libraries/precisoUtils/bidUtils.js @@ -0,0 +1,103 @@ +import { convertOrtbRequestToProprietaryNative } from '../../src/native.js'; +import { replaceAuctionPrice } from '../../src/utils.js'; +import { ajax } from '../../src/ajax.js'; +import { consentCheck } from './bidUtilsCommon.js'; + +export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + var city = Intl.DateTimeFormat().resolvedOptions().timeZone; + let req = { + // bidRequest: bidderRequest, + id: validBidRequests[0].auctionId, + cur: validBidRequests[0].params.currency || ['USD'], + imp: validBidRequests.map(req => { + const { bidId, sizes } = req + const impValue = { + id: bidId, + bidfloor: req.params.bidFloor, + bidfloorcur: req.params.currency + } + if (req.mediaTypes.banner) { + impValue.banner = { + format: (req.mediaTypes.banner.sizes || sizes).map(size => { + return { w: size[0], h: size[1] } + }), + + } + } + return impValue + }), + user: { + id: validBidRequests[0].userId.pubcid || '', + buyeruid: validBidRequests[0].buyerUid || '', + geo: { + country: validBidRequests[0].params.region || city, + region: validBidRequests[0].params.region || city, + }, + + }, + device: validBidRequests[0].ortb2.device, + site: validBidRequests[0].ortb2.site, + source: validBidRequests[0].ortb2.source, + bcat: validBidRequests[0].ortb2.bcat || validBidRequests[0].params.bcat, + badv: validBidRequests[0].ortb2.badv || validBidRequests[0].params.badv, + wlang: validBidRequests[0].ortb2.wlang || validBidRequests[0].params.wlang, + }; + if (req.device && req.device != 'undefined') { + req.device.geo = { + country: req.user.geo.country, + region: req.user.geo.region, + + }; + }; + req.site.publisher = { + publisherId: validBidRequests[0].params.publisherId + }; + + // req.language.indexOf('-') != -1 && (req.language = req.language.split('-')[0]) + consentCheck(bidderRequest, req); + return { + method: 'POST', + url: endpoint, + data: req, + + }; +} + +export function interpretResponse(serverResponse) { + const bidsValue = [] + const bidResponse = serverResponse.body + bidResponse.seatbid.forEach(seat => { + seat.bid.forEach(bid => { + bidsValue.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + ad: macroReplace(bid.adm, bid.price), + currency: 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: bid.adomain || '', + }, + }) + }) + }) + return bidsValue +} + +export function onBidWon(bid) { + if (bid.nurl) { + const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.price); + ajax(resolvedNurl); + } +} + +/* replacing auction_price macro from adm */ +function macroReplace(adm, cpm) { + let replacedadm = replaceAuctionPrice(adm, cpm); + return replacedadm; +} diff --git a/libraries/precisoUtils/bidUtilsCommon.js b/libraries/precisoUtils/bidUtilsCommon.js new file mode 100644 index 00000000000..cb9699035fb --- /dev/null +++ b/libraries/precisoUtils/bidUtilsCommon.js @@ -0,0 +1,173 @@ +import { config } from '../../src/config.js'; +import { + isFn, + isStr, + deepAccess, + getWindowTop, + triggerPixel +} from '../../src/utils.js'; +import { BANNER, VIDEO, NATIVE } from '../../src/mediaTypes.js'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency || !bid.meta) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastXml || bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +export function isBidRequestValid(bid) { + return Boolean(bid.bidId && bid.params && bid.params.placementId); +} + +export const buildBidRequests = (adurl) => (validBidRequests = [], bidderRequest) => { + const winTop = getWindowTop(); + const location = winTop.location; + const placements = []; + + const request = { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + host: location.host, + page: location.pathname, + placements: placements + }; + consentCheck(bidderRequest, request); + + // if (bidderRequest) { + // if (bidderRequest.uspConsent) { + // request.ccpa = bidderRequest.uspConsent; + // } + // if (bidderRequest.gdprConsent) { + // request.gdpr = bidderRequest.gdprConsent; + // } + // } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + bidfloor: getBidFloor(bid) + }; + + if (typeof bid.userId !== 'undefined') { + placement.userId = bid.userId; + } + + const mediaType = bid.mediaTypes; + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.adFormat = BANNER; + } + + placements.push(placement); + } + + return { + method: 'POST', + url: adurl, + data: request + }; +} + +export function interpretResponse(serverResponse) { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; +} + +export function consentCheck(bidderRequest, req) { + if (bidderRequest) { + if (bidderRequest.uspConsent) { + req.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + req.gdpr = bidderRequest.gdprConsent + } + if (bidderRequest.gppConsent) { + req.gpp = bidderRequest.gppConsent; + } + } +} + +export const buildUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + const isCk2trk = syncEndpoint.includes('ck.2trk.info'); + + // Base sync URL + let syncUrl = isCk2trk ? syncEndpoint : `${syncEndpoint}/${syncType}?pbjs=1`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } else { + syncUrl += isCk2trk ? `&gdpr=0&gdpr_consent=` : ''; + } + + if (isCk2trk) { + syncUrl += uspConsent ? `&us_privacy=${uspConsent}` : `&us_privacy=`; + syncUrl += (syncOptions.iframeEnabled) ? `&t=4` : `&t=2` + } else { + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + } + + return [{ + type: syncType, + url: syncUrl + }]; +} + +export function bidWinReport (bid) { + const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + if (isStr(bid.nurl) && bid.nurl !== '') { + bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); + triggerPixel(bid.nurl); + } +} diff --git a/modules/idxBidAdapter.js b/modules/idxBidAdapter.js index 48739275788..12adb4058ae 100644 --- a/modules/idxBidAdapter.js +++ b/modules/idxBidAdapter.js @@ -1,6 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' import { isArray, isNumber } from '../src/utils.js' +import { interpretResponse } from '../libraries/precisoUtils/bidUtils.js'; const BIDDER_CODE = 'idx' const ENDPOINT_URL = 'https://dev-event.dxmdp.com/rest/api/v1/bid' @@ -49,32 +50,7 @@ export const spec = { } } }, - interpretResponse: function (serverResponse) { - const response = serverResponse.body - - const bids = [] - - response.seatbid.forEach(seat => { - seat.bid.forEach(bid => { - bids.push({ - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - ad: bid.adm, - currency: 'USD', - netRevenue: true, - ttl: 300, - meta: { - advertiserDomains: bid.adomain || [], - }, - }) - }) - }) - - return bids - }, + interpretResponse, } registerBidder(spec) diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js index bec23cddc2d..4e2652e452f 100644 --- a/modules/loganBidAdapter.js +++ b/modules/loganBidAdapter.js @@ -1,54 +1,18 @@ -import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; +import { getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { buildUserSyncs, interpretResponse, isBidRequestValid, getBidFloor, consentCheck } from '../libraries/precisoUtils/bidUtilsCommon.js'; const BIDDER_CODE = 'logan'; const AD_URL = 'https://USeast2.logan.ai/pbjs'; -const SYNC_URL = 'https://ssp-cookie.logan.ai' - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastXml || bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const SYNC_URL = 'https://ssp-cookie.logan.ai'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, + isBidRequestValid: isBidRequestValid, buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition @@ -67,14 +31,7 @@ export const spec = { placements: placements }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - } + consentCheck(bidderRequest, request); const len = validBidRequests.length; for (let i = 0; i < len; i++) { @@ -123,42 +80,11 @@ export const spec = { }; }, - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - + interpretResponse: interpretResponse, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, SYNC_URL); } + }; registerBidder(spec); diff --git a/modules/mediabramaBidAdapter.js b/modules/mediabramaBidAdapter.js index caf6854fe03..9722f1672ba 100644 --- a/modules/mediabramaBidAdapter.js +++ b/modules/mediabramaBidAdapter.js @@ -1,155 +1,22 @@ -import { - isFn, - isStr, - deepAccess, - getWindowTop, - triggerPixel -} from '../src/utils.js'; + import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { bidWinReport, buildBidRequests, buildUserSyncs, interpretResponse, isBidRequestValid } from '../libraries/precisoUtils/bidUtilsCommon.js'; const BIDDER_CODE = 'mediabrama'; const AD_URL = 'https://prebid.mediabrama.com/pbjs'; const SYNC_URL = 'https://prebid.mediabrama.com/sync'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - const winTop = getWindowTop(); - const location = winTop.location; - const placements = []; - - const request = { - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - host: location.host, - page: location.pathname, - placements: placements - }; - - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - schain: bid.schain || {}, - bidfloor: getBidFloor(bid) - }; - - if (typeof bid.userId !== 'undefined') { - placement.userId = bid.userId; - } - - const mediaType = bid.mediaTypes; - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.adFormat = BANNER; - } - - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - + isBidRequestValid: isBidRequestValid, + buildRequests: buildBidRequests(AD_URL), + interpretResponse: interpretResponse, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, SYNC_URL); }, - - onBidWon: (bid) => { - const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (isStr(bid.nurl) && bid.nurl !== '') { - bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); - triggerPixel(bid.nurl); - } - } + onBidWon: bidWinReport }; registerBidder(spec); diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index b4f1b665d91..6f6ab82eca2 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,205 +1,75 @@ -import { isFn, deepAccess, logInfo } from '../src/utils.js'; +import { logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - -const BIDDER_CODE = 'preciso'; -const AD_URL = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; -const URL_SYNC = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; -const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { buildRequests, interpretResponse, onBidWon } from '../libraries/precisoUtils/bidUtils.js'; +import { buildUserSyncs } from '../libraries/precisoUtils/bidUtilsCommon.js'; + +const BIDDER__CODE = 'preciso'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE }); +// export const storage2 = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE }); +const SUPPORTED_MEDIA_TYPES = [BANNER]; const GVLID = 874; -let userId = 'NA'; +let precisoId = 'NA'; +let sharedId = 'NA'; + +const endpoint = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; +let syncEndpoint = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; export const spec = { - code: BIDDER_CODE, + code: BIDDER__CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, gvlid: GVLID, isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.publisherId) && bid.params.host == 'prebid'); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - // userId = validBidRequests[0].userId.pubcid; - let winTop = window; - let location; - var offset = new Date().getTimezoneOffset(); - logInfo('timezone ' + offset); - var city = Intl.DateTimeFormat().resolvedOptions().timeZone; - logInfo('location test' + city) - - const countryCode = getCountryCodeByTimezone(city); - logInfo(`The country code for ${city} is ${countryCode}`); - - location = bidderRequest?.refererInfo ?? null; - - let request = { - id: validBidRequests[0].bidderRequestId, - - imp: validBidRequests.map(request => { - const { bidId, sizes, mediaType, ortb2 } = request - const item = { - id: bidId, - region: request.params.region, - traffic: mediaType, - bidFloor: getBidFloor(request), - ortb2: ortb2 - - } - - if (request.mediaTypes.banner) { - item.banner = { - format: (request.mediaTypes.banner.sizes || sizes).map(size => { - return { w: size[0], h: size[1] } - }), - } - } - - if (request.schain) { - item.schain = request.schain; - } - - if (request.floorData) { - item.bidFloor = request.floorData.floorMin; - } - return item - }), - auctionId: validBidRequests[0].auctionId, - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - geo: navigator.geolocation.getCurrentPosition(position => { - const { latitude, longitude } = position.coords; - return { - latitude: latitude, - longitude: longitude - } - // Show a map centered at latitude / longitude. - }) || { utcoffset: new Date().getTimezoneOffset() }, - city: city, - 'host': location?.domain ?? '', - 'page': location?.page ?? '', - 'coppa': config.getConfig('coppa') === true ? 1 : 0 - // userId: validBidRequests[0].userId - }; - - request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent; + sharedId = storage.getDataFromLocalStorage('_sharedid') || storage.getCookie('_sharedid'); + let precisoBid = true; + const preCall = 'https://ssp-usersync.mndtrk.com/getUUID?sharedId=' + sharedId; + precisoId = storage.getDataFromLocalStorage('_pre|id'); + if (Object.is(precisoId, 'NA') || Object.is(precisoId, null) || Object.is(precisoId, undefined)) { + if (!bid.precisoBid) { + precisoBid = false; + getapi(preCall); } } - return { - method: 'POST', - url: AD_URL, - data: request, - - }; + return Boolean(bid.bidId && bid.params && bid.params.publisherId && precisoBid); }, - - interpretResponse: function (serverResponse) { - const response = serverResponse.body - - const bids = [] - - response.seatbid.forEach(seat => { - seat.bid.forEach(bid => { - bids.push({ - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - ad: bid.adm, - currency: 'USD', - netRevenue: true, - ttl: 300, - meta: { - advertiserDomains: bid.adomain || [], - }, - }) - }) - }) - - return bids - }, - - getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '', gppConsent = '') => { - let syncs = []; - let { gdprApplies, consentString = '' } = gdprConsent; - - if (serverResponses.length > 0) { - logInfo('preciso bidadapter getusersync serverResponses:' + serverResponses.toString); - } - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `${URL_SYNC}id=${userId}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=4` - }); + buildRequests: buildRequests(endpoint), + interpretResponse, + onBidWon, + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + const isSpec = syncOptions.spec; + if (!Object.is(isSpec, true)) { + let syncId = storage.getCookie('_sharedid'); + syncEndpoint = syncEndpoint + 'id=' + syncId; } else { - syncs.push({ - type: 'image', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=2` - }); + syncEndpoint = syncEndpoint + 'id=NA'; } - return syncs + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint); } - }; -function getCountryCodeByTimezone(city) { - try { - const now = new Date(); - const options = { - timeZone: city, - timeZoneName: 'long', - }; - const [timeZoneName] = new Intl.DateTimeFormat('en-US', options) - .formatToParts(now) - .filter((part) => part.type === 'timeZoneName'); +registerBidder(spec); - if (timeZoneName) { - // Extract the country code from the timezone name - const parts = timeZoneName.value.split('-'); - if (parts.length >= 2) { - return parts[1]; - } - } - } catch (error) { - // Handle errors, such as an invalid timezone city - logInfo(error); - } +async function getapi(url) { + try { + // Storing response + const response = await fetch(url); - // Handle the case where the city is not found or an error occurred - return 'Unknown'; -} + // Storing data in form of JSON + var data = await response.json(); -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } + const dataMap = new Map(Object.entries(data)); + const uuidValue = dataMap.get('UUID'); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 + if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) { + storage.setDataInLocalStorage('_pre|id', uuidValue); + } + return data; + } catch (error) { + logInfo('Error in preciso precall' + error); } } - -registerBidder(spec); diff --git a/modules/redtramBidAdapter.js b/modules/redtramBidAdapter.js index 726b2d53f1c..bacc209f991 100644 --- a/modules/redtramBidAdapter.js +++ b/modules/redtramBidAdapter.js @@ -1,151 +1,21 @@ -import { - isFn, - deepAccess, - getWindowTop, - triggerNurlWithCpm -} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { bidWinReport, buildBidRequests, buildUserSyncs, interpretResponse, isBidRequestValid } from '../libraries/precisoUtils/bidUtilsCommon.js'; const BIDDER_CODE = 'redtram'; const AD_URL = 'https://prebid.redtram.com/pbjs'; const SYNC_URL = 'https://prebid.redtram.com/sync'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - const winTop = getWindowTop(); - const location = winTop.location; - const placements = []; - - const request = { - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - host: location.host, - page: location.pathname, - placements: placements - }; - - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - schain: bid.schain || {}, - bidfloor: getBidFloor(bid) - }; - - if (typeof bid.userId !== 'undefined') { - placement.userId = bid.userId; - } - - const mediaType = bid.mediaTypes; - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.adFormat = BANNER; - } - - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - + isBidRequestValid: isBidRequestValid, + buildRequests: buildBidRequests(AD_URL), + interpretResponse: interpretResponse, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, SYNC_URL); }, - - onBidWon: (bid) => { - const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - triggerNurlWithCpm(bid, cpm) - } + onBidWon: bidWinReport }; registerBidder(spec); diff --git a/test/spec/libraries/precisoUtils/bidUtilsCommon_spec.js b/test/spec/libraries/precisoUtils/bidUtilsCommon_spec.js new file mode 100644 index 00000000000..754b104d96d --- /dev/null +++ b/test/spec/libraries/precisoUtils/bidUtilsCommon_spec.js @@ -0,0 +1,267 @@ +import {expect} from 'chai'; + +import { BANNER } from '../../../../src/mediaTypes.js'; +import * as utils from '../../../../src/utils.js'; +import { interpretResponse, isBidRequestValid, buildUserSyncs, buildBidRequests, bidWinReport } from '../../../../libraries/precisoUtils/bidUtilsCommon.js'; + +const BIDDER_CODE = 'bidder'; +const TESTDOMAIN = 'test.org' +const AD_URL = `https://${TESTDOMAIN}/pbjs`; +const SYNC_URL = `https://${TESTDOMAIN}/sync`; + +describe('bidUtilsCommon', function () { + const bid = { + bidId: '23dc19818e5293', + bidder: BIDDER_CODE, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 23611, + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + const spec = { + isBidRequestValid: isBidRequestValid, + buildRequests: buildBidRequests(AD_URL), + interpretResponse, + getUserSyncs: buildUserSyncs, + onBidWon: bidWinReport + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'schain', 'bidfloor'); + expect(placement.placementId).to.equal(23611); + expect(placement.bidId).to.equal('23dc19818e5293'); + expect(placement.adFormat).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23dc19818e5293', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: {} + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23dc19818e5293'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23dc19818e5293', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + it('should do nothing on getUserSyncs', function () { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true + }, {}, SYNC_URL); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`https://${TESTDOMAIN}/sync/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) + }); + }); + + describe('on bidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('should replace nurl for banner', function () { + const nurl = 'nurl/?ap=${' + 'AUCTION_PRICE}'; + const bid = { + 'bidderCode': 'redtram', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '5691dd18ba6ab6', + 'requestId': '23dc19818e5293', + 'transactionId': '948c716b-bf64-4303-bcf4-395c2f6a9770', + 'auctionId': 'a6b7c61f-15a9-481b-8f64-e859787e9c07', + 'mediaType': 'banner', + 'source': 'client', + 'ad': "
\n", + 'cpm': 0.68, + 'nurl': nurl, + 'creativeId': 'test', + 'currency': 'USD', + 'dealId': '', + 'meta': { + 'advertiserDomains': [], + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'name': 'redtram' + } + ] + } + }, + 'netRevenue': true, + 'ttl': 120, + 'metrics': {}, + 'adapterCode': 'redtram', + 'originalCpm': 0.68, + 'originalCurrency': 'USD', + 'responseTimestamp': 1668162732297, + 'requestTimestamp': 1668162732292, + 'bidder': 'redtram', + 'adUnitCode': 'div-prebid', + 'timeToRespond': 5, + 'pbLg': '0.50', + 'pbMg': '0.60', + 'pbHg': '0.68', + 'pbAg': '0.65', + 'pbDg': '0.68', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'redtram', + 'hb_adid': '5691dd18ba6ab6', + 'hb_pb': '0.68', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 23611 + } + ] + }; + spec.onBidWon(bid); + expect(bid.nurl).to.deep.equal('nurl/?ap=0.68'); + }); + }); +}); diff --git a/test/spec/libraries/precisoUtils/bidUtils_spec.js b/test/spec/libraries/precisoUtils/bidUtils_spec.js new file mode 100644 index 00000000000..eecfea4b70e --- /dev/null +++ b/test/spec/libraries/precisoUtils/bidUtils_spec.js @@ -0,0 +1,150 @@ + +import { expect } from 'chai'; +import { buildRequests, interpretResponse } from '../../../../libraries/precisoUtils/bidUtils.js'; + +const DEFAULT_PRICE = 1 +const DEFAULT_CURRENCY = 'USD' +const DEFAULT_BANNER_WIDTH = 300 +const DEFAULT_BANNER_HEIGHT = 250 +const BIDDER_CODE = 'preciso'; +const TESTDOMAIN = 'test.org' +const bidEndPoint = `https://${TESTDOMAIN}/bid_request/openrtb`; + +describe('bidderOperations', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: BIDDER_CODE, + buyerUid: 'testuid', + mediaTypes: { + banner: { + sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] + } + }, + params: { + host: 'prebid', + sourceid: '0', + publisherId: '0', + mediaType: 'banner', + region: 'IND' + + }, + userId: { + pubcid: '12355454test' + + }, + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + } + + }; + + const spec = { + // isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(bidEndPoint), + interpretResponse, + // buildUserSyncs: buildUserSyncs(syncEndPoint) + }; + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal(`https://${TESTDOMAIN}/bid_request/openrtb`); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data.device).to.be.a('object'); + expect(data.user).to.be.a('object'); + expect(data.source).to.be.a('object'); + expect(data.site).to.be.a('object'); + }); + it('Returns data.device is undefined if no valid device object is passed', function () { + delete bid.ortb2.device; + serverRequest = spec.buildRequests([bid]); + let data = serverRequest.data; + expect(data.device).to.be.undefined; + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + bidderRequestId: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + seatbid: [ + { + bid: [ + { + id: '123', + impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + price: DEFAULT_PRICE, + adm: 'hi', + cid: 'test_cid', + crid: 'test_banner_crid', + w: DEFAULT_BANNER_WIDTH, + h: DEFAULT_BANNER_HEIGHT, + adomain: [], + } + ], + seat: BIDDER_CODE + } + ], + } + let expectedResponse = [ + { + requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + cpm: DEFAULT_PRICE, + width: DEFAULT_BANNER_WIDTH, + height: DEFAULT_BANNER_HEIGHT, + creativeId: 'test_banner_crid', + ad: 'hi', + currency: DEFAULT_CURRENCY, + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: [] }, + } + ] + let result = spec.interpretResponse({ body: response }) + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])) + }) + }); + // describe('getUserSyncs', function () { + // const syncUrl = `https://${TESTDOMAIN}/rtb/user/usersync.aspx?/iframe?pbjs=1&coppa=0`; + // const syncOptions = { + // iframeEnabled: true + // }; + // let userSync = spec.buildUserSyncs(syncOptions); + // it('Returns valid URL and type', function () { + // expect(userSync).to.be.an('array').with.lengthOf(1); + // expect(userSync[0].type).to.exist; + // expect(userSync[0].url).to.exist; + // expect(userSync).to.deep.equal([ + // { type: 'iframe', url: syncUrl } + // ]); + // }); + // }); +}); diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 78a1615a02e..60cdd43504a 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/precisoBidAdapter.js'; -import { config } from '../../../src/config.js'; +// simport { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 const DEFAULT_CURRENCY = 'USD' @@ -10,8 +10,10 @@ const BIDDER_CODE = 'preciso'; describe('PrecisoAdapter', function () { let bid = { + precisoBid: true, bidId: '23fhj33i987f', bidder: 'preciso', + buyerUid: 'testuid', mediaTypes: { banner: { sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] @@ -22,15 +24,33 @@ describe('PrecisoAdapter', function () { sourceid: '0', publisherId: '0', mediaType: 'banner', - region: 'prebid-eu' + region: 'IND' }, userId: { pubcid: '12355454test' }, - geo: 'NA', - city: 'Asia,delhi' + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + } + }; describe('isBidRequestValid', function () { @@ -59,43 +79,17 @@ describe('PrecisoAdapter', function () { }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; - // expect(data).to.be.an('object'); - - // expect(data).to.have.all.keys('bidId', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); - - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.coppa).to.be.a('number'); - expect(data.language).to.be.a('string'); - // expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - - expect(data.city).to.be.a('string'); - expect(data.geo).to.be.a('object'); - // expect(data.userId).to.be.a('string'); - // expect(data.imp).to.be.a('object'); - }); - // it('Returns empty data if no valid requests are passed', function () { - /// serverRequest = spec.buildRequests([]); - // let data = serverRequest.data; - // expect(data.imp).to.be.an('array').that.is.empty; - // }); - }); - - describe('with COPPA', function () { - beforeEach(function () { - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + expect(data).to.be.an('object'); + expect(data.device).to.be.a('object'); + expect(data.user).to.be.a('object'); + expect(data.source).to.be.a('object'); + expect(data.site).to.be.a('object'); }); - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send the Coppa "required" flag set to "1" in the request', function () { - let serverRequest = spec.buildRequests([bid]); - expect(serverRequest.data.coppa).to.equal(1); + it('Returns empty data if no valid requests are passed', function () { + delete bid.ortb2.device; + serverRequest = spec.buildRequests([bid]); + let data = serverRequest.data; + expect(data.device).to.be.undefined; }); }); @@ -147,7 +141,8 @@ describe('PrecisoAdapter', function () { describe('getUserSyncs', function () { const syncUrl = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=NA&gdpr=0&gdpr_consent=&us_privacy=&t=4'; const syncOptions = { - iframeEnabled: true + iframeEnabled: true, + spec: true }; let userSync = spec.getUserSyncs(syncOptions); it('Returns valid URL and type', function () { From c452306d7b205c4a4a1c0ee6dfbef4c5d8f81015 Mon Sep 17 00:00:00 2001 From: Andy Blackwell Date: Mon, 19 Aug 2024 07:06:50 -0500 Subject: [PATCH 0437/1097] 33across - allow aliasing (#12138) --- modules/33acrossBidAdapter.js | 4 +--- test/spec/modules/33acrossBidAdapter_spec.js | 21 -------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 60d732e35d3..ae002d79d58 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -73,9 +73,7 @@ function isBidRequestValid(bid) { } function _validateBasic(bid) { - const invalidBidderName = bid.bidder !== BIDDER_CODE && !BIDDER_ALIASES.includes(bid.bidder); - - if (invalidBidderName || !bid.params) { + if (!bid.params) { return false; } diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 35c8e31ecfe..ff05f412d58 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -527,27 +527,6 @@ describe('33acrossBidAdapter:', function () { }); }); - it('returns false for invalid bidder name values', function() { - const invalidBidderName = [ - undefined, - '33', - '33x', - 'thirtythree', - '' - ]; - - invalidBidderName.forEach((bidderName) => { - const bid = { - bidder: bidderName, - params: { - siteId: 'sample33xGUID123456789' - } - }; - - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); - it('returns true for valid guid values', function() { // NOTE: We ignore whitespace at the start and end since // in our experience these are common typos From d37434826d86b67604c341012d52c00b30469289 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Mon, 19 Aug 2024 08:07:55 -0400 Subject: [PATCH 0438/1097] Updating isBidRequestValid logic to account for aliasing of bidder names (#12136) --- modules/sharethroughBidAdapter.js | 2 +- test/spec/modules/sharethroughBidAdapter_spec.js | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index efa0bb3471b..e8038ae233d 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -18,7 +18,7 @@ export const sharethroughAdapterSpec = { code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], gvlid: 80, - isBidRequestValid: (bid) => !!bid.params.pkey && bid.bidder === BIDDER_CODE, + isBidRequestValid: (bid) => !!bid.params.pkey, buildRequests: (bidRequests, bidderRequest) => { const timeout = bidderRequest.timeout; diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 77619dee528..276860b6be6 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -38,19 +38,8 @@ describe('sharethrough adapter spec', function () { expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should return false if req has wrong bidder code', function () { - const invalidBidRequest = { - bidder: 'notSharethrough', - params: { - pkey: 'abc123', - }, - }; - expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); - }); - it('should return true if req is correct', function () { const validBidRequest = { - bidder: 'sharethrough', params: { pkey: 'abc123', }, From c4c87110654e0f867253fc4dbd5d66b5661ef07b Mon Sep 17 00:00:00 2001 From: Pavlo Kavulych <72217414+Chucky-choo@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:40:38 +0300 Subject: [PATCH 0439/1097] Digitalmatter Bid Adapter : add dichange alias (#12133) * add alias * fix alias --------- Co-authored-by: Chucky-choo --- modules/digitalMatterBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index 9676ed6e95f..e6af0a8f108 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -12,7 +12,7 @@ const ENDPOINT = 'https://prebid.di-change.live'; export const spec = { code: BIDDER_CODE, - aliases: ['digitalmatter'], + aliases: ['dichange', 'digitalmatter'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), From 0f3a8535d38c3658689cad1860c903a36b682fb7 Mon Sep 17 00:00:00 2001 From: Yuki Tsujii Date: Tue, 20 Aug 2024 02:15:19 +0900 Subject: [PATCH 0440/1097] add media consortium adapter (#11892) Co-authored-by: Maxime Lequain --- modules/mediaConsortiumBidAdapter.js | 273 ++++++++++++ modules/mediaConsortiumBidAdapter.md | 79 ++++ .../modules/mediaConsortiumBidAdapter_spec.js | 411 ++++++++++++++++++ 3 files changed, 763 insertions(+) create mode 100644 modules/mediaConsortiumBidAdapter.js create mode 100644 modules/mediaConsortiumBidAdapter.md create mode 100644 test/spec/modules/mediaConsortiumBidAdapter_spec.js diff --git a/modules/mediaConsortiumBidAdapter.js b/modules/mediaConsortiumBidAdapter.js new file mode 100644 index 00000000000..a1cd6586735 --- /dev/null +++ b/modules/mediaConsortiumBidAdapter.js @@ -0,0 +1,273 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js' +import {registerBidder} from '../src/adapters/bidderFactory.js' +import {generateUUID, isPlainObject, isArray, logWarn, deepClone} from '../src/utils.js' +import {Renderer} from '../src/Renderer.js' +import {OUTSTREAM} from '../src/video.js' +import {config} from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'mediaConsortium' + +const PROFILE_API_USAGE_CONFIG_KEY = 'useProfileApi' +const ONE_PLUS_X_ID_USAGE_CONFIG_KEY = 'readOnePlusXId' + +const SYNC_ENDPOINT = 'https://relay.hubvisor.io/v1/sync/big' +const AUCTION_ENDPOINT = 'https://relay.hubvisor.io/v1/auction/big' + +const XANDR_OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +export const OPTIMIZATIONS_STORAGE_KEY = 'media_consortium_optimizations' + +const SYNC_TYPES = { + image: 'image', + redirect: 'image', + iframe: 'iframe' +} + +const storageManager = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + version: '0.0.1', + code: BIDDER_CODE, + gvlid: 1112, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid(bid) { + return true + }, + buildRequests(bidRequests, bidderRequest) { + const useProfileApi = config.getConfig(PROFILE_API_USAGE_CONFIG_KEY) ?? false + const readOnePlusXId = config.getConfig(ONE_PLUS_X_ID_USAGE_CONFIG_KEY) ?? false + + const { + auctionId, + bids, + gdprConsent: {gdprApplies = false, consentString} = {}, + ortb2: {device, site} + } = bidderRequest + + const currentTimestamp = Date.now() + const optimizations = getOptimizationsFromLocalStorage() + + const impressions = bids.reduce((acc, bidRequest) => { + const {bidId, adUnitCode, mediaTypes} = bidRequest + const optimization = optimizations[adUnitCode] + + if (optimization) { + const {expiresAt, isEnabled} = optimization + + if (expiresAt >= currentTimestamp && !isEnabled) { + return acc + } + } + + let finalizedMediatypes = deepClone(mediaTypes) + + if (mediaTypes.video && mediaTypes.video.context !== OUTSTREAM) { + logWarn(`Filtering video request for adUnitCode ${adUnitCode} because context is not ${OUTSTREAM}`) + + if (Object.keys(finalizedMediatypes).length > 1) { + delete finalizedMediatypes.video + } else { + return acc + } + } + + return acc.concat({id: bidId, adUnitCode, mediaTypes: finalizedMediatypes}) + }, []) + + if (!impressions.length) { + return + } + + const request = { + id: auctionId ?? generateUUID(), + impressions, + device, + site, + user: { + ids: {} + }, + regulations: { + gdpr: { + applies: gdprApplies, + consentString + } + }, + timeout: 3600, + options: { + useProfileApi + } + } + + if (readOnePlusXId) { + const fpId = getFpIdFromLocalStorage() + + if (fpId) { + request.user.ids['1plusX'] = fpId + } + } + + const syncData = { + gdpr: gdprApplies, + ad_unit_codes: impressions.map(({adUnitCode}) => adUnitCode).join(',') + } + + if (consentString) { + syncData.gdpr_consent = consentString + } + + return [ + { + method: 'GET', + url: SYNC_ENDPOINT, + data: syncData + }, + { + method: 'POST', + url: AUCTION_ENDPOINT, + data: request + } + ] + }, + interpretResponse(serverResponse, params) { + if (!isValidResponse(serverResponse)) return [] + + const {body: {bids, optimizations}} = serverResponse + + if (optimizations && isArray(optimizations)) { + const currentTimestamp = Date.now() + + const optimizationsToStore = optimizations.reduce((acc, optimization) => { + const {adUnitCode, isEnabled, ttl} = optimization + + return { + ...acc, + [adUnitCode]: {isEnabled, expiresAt: currentTimestamp + ttl} + } + }, getOptimizationsFromLocalStorage()) + + storageManager.setDataInLocalStorage(OPTIMIZATIONS_STORAGE_KEY, JSON.stringify(optimizationsToStore)) + } + + return bids.map((bid) => { + const { + impressionId, + price: {cpm, currency}, + dealId, + ad: { + creative: {id, mediaType, size: {width, height}, markup} + }, + ttl = 360 + } = bid + + const formattedBid = { + requestId: impressionId, + cpm, + currency, + dealId, + ttl, + netRevenue: true, + creativeId: id, + mediaType, + width, + height, + ad: markup, + adUrl: null + } + + if (mediaType === VIDEO) { + const impressionRequest = params.data.impressions.find(({id}) => id === impressionId) + + formattedBid.vastXml = markup + + if (impressionRequest) { + formattedBid.renderer = buildXandrOutstreamRenderer(impressionId, impressionRequest.adUnitCode) + } else { + logWarn(`Could not find adUnitCode matching the impressionId ${impressionId} to setup the renderer`) + } + } + + return formattedBid + }) + }, + getUserSyncs(syncOptions, serverResponses) { + if (serverResponses.length !== 2) { + return + } + + const [sync] = serverResponses + + return sync.body?.bidders?.reduce((acc, {type, url}) => { + const syncType = SYNC_TYPES[type] + + if (!syncType || !url) { + return acc + } + + return acc.concat({type: syncType, url}) + }, []) + } +} + +registerBidder(spec) + +export function getOptimizationsFromLocalStorage() { + try { + const storedOptimizations = storageManager.getDataFromLocalStorage(OPTIMIZATIONS_STORAGE_KEY) + + return storedOptimizations ? JSON.parse(storedOptimizations) : {} + } catch (err) { + return {} + } +} + +function getFpIdFromLocalStorage() { + try { + return storageManager.getDataFromLocalStorage('ope_fpid') + } catch (err) { + return null + } +} + +function isValidResponse(response) { + return isPlainObject(response) && + isPlainObject(response.body) && + isArray(response.body.bids) +} + +function buildXandrOutstreamRenderer(bidId, adUnitCode) { + const renderer = Renderer.install({ + id: bidId, + url: XANDR_OUTSTREAM_RENDERER_URL, + loaded: false, + adUnitCode, + targetId: adUnitCode + }); + + try { + renderer.setRender(xandrOutstreamRenderer); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +function xandrOutstreamRenderer(bid) { + const {width, height, adUnitCode, vastXml} = bid + + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [width, height], + targetId: adUnitCode, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + content: vastXml, + showVolume: false, + allowFullscreen: true, + skippable: false + } + }); + }); +} diff --git a/modules/mediaConsortiumBidAdapter.md b/modules/mediaConsortiumBidAdapter.md new file mode 100644 index 00000000000..b78627077cb --- /dev/null +++ b/modules/mediaConsortiumBidAdapter.md @@ -0,0 +1,79 @@ +# Media Consortium Bid adapter + +## Overview + +``` +- Module Name: Media Consortium Bidder Adapter +- Module Type: Media Consortium Bidder Adapter +- Maintainer: mediaconsortium-develop@bi.garage.co.jp +``` + +## Description + +Module that connects to Media Consortium demand sources and supports the following media types: `banner`, `video`. + +To get access to the full feature set of the adapter you'll need to allow localstorage usage in the `bidderSettings`. + +```javascript + pbjs.bidderSettings = { + mediaConsortium: { + storageAllowed: true + } + } +``` + +## Managing 1plusX profile API usage and FPID retrieval + +You can use the `setBidderConfig` function to enable or disable 1plusX profile API usage and fpid retrieval. + +If the keys found below are not defined, their values will default to `false`. + +```javascript + pbjs.setBidderConfig({ + bidders: ['mediaConsortium'], + config: { + // Controls the 1plusX profile API usage + useProfileApi: true, + // Controls the 1plusX fpid retrieval + readOnePlusXId: true + } + }); +``` + +## Test Parameters + +```javascript + var adUnits = [ + { + code: 'div-prebid-banner', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'mediaConsortium', + params: {} + } + ] + }, + { + code: 'div-prebid-video', + mediaTypes:{ + video: { + playerSize: [ + [300, 250] + ], + context: 'outstream' + } + }, + bids:[ + { + bidder: 'mediaConsortium', + params: {} + } + ] + } + ]; +``` diff --git a/test/spec/modules/mediaConsortiumBidAdapter_spec.js b/test/spec/modules/mediaConsortiumBidAdapter_spec.js new file mode 100644 index 00000000000..6a7ac6f5741 --- /dev/null +++ b/test/spec/modules/mediaConsortiumBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import { spec, OPTIMIZATIONS_STORAGE_KEY, getOptimizationsFromLocalStorage } from 'modules/mediaConsortiumBidAdapter.js'; + +const BANNER_BID = { + adUnitCode: 'dfp_ban_atf', + bidId: '2f0d9715f60be8', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + } +} + +const VIDEO_BID = { + adUnitCode: 'video', + bidId: '2f0d9715f60be8', + mediaTypes: { + video: { + playerSize: [ + [300, 250] + ], + context: 'outstream' + } + } +} + +const VIDEO_BID_WITH_MISSING_CONTEXT = { + adUnitCode: 'video', + bidId: '2f0d9715f60be8', + mediaTypes: { + video: { + playerSize: [ + [300, 250] + ] + } + } +} + +const MULTI_MEDIATYPES_BID = { + adUnitCode: 'multi_type', + bidId: '2f0d9715f60be8', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [ + [300, 250] + ], + context: 'outstream' + } + } +} + +const MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT = { + adUnitCode: 'multi_type', + bidId: '2f0d9715f60be8', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [ + [300, 250] + ], + context: 'instream' + } + } +} + +describe('Media Consortium Bid Adapter', function () { + before(function () { + // The local storage variable is not cleaned in some other test so we need to do it ourselves here + localStorage.removeItem('ope_fpid') + }) + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mediaConsortium: { + storageAllowed: true + } + } + }) + + afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }) + + describe('buildRequests', function () { + const bidderRequest = { + auctionId: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + ortb2: { + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + } + } + }; + + it('should build a banner request', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: BANNER_BID.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: BANNER_BID.bidId, + adUnitCode: BANNER_BID.adUnitCode, + mediaTypes: BANNER_BID.mediaTypes + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const bids = [BANNER_BID] + const [syncRequest, auctionRequest] = spec.buildRequests(bids, {...bidderRequest, bids}); + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + + it('should build a video request', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: VIDEO_BID.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: VIDEO_BID.bidId, + adUnitCode: VIDEO_BID.adUnitCode, + mediaTypes: VIDEO_BID.mediaTypes + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const bids = [VIDEO_BID] + const [syncRequest, auctionRequest] = spec.buildRequests(bids, {...bidderRequest, bids}); + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + + it('should build a request with multiple mediatypes', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: MULTI_MEDIATYPES_BID.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: MULTI_MEDIATYPES_BID.bidId, + adUnitCode: MULTI_MEDIATYPES_BID.adUnitCode, + mediaTypes: MULTI_MEDIATYPES_BID.mediaTypes + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const bids = [MULTI_MEDIATYPES_BID] + const [syncRequest, auctionRequest] = spec.buildRequests(bids, {...bidderRequest, bids}); + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + + it('should not build a request if optimizations are there for the adunit code', function () { + const bids = [BANNER_BID] + const optimizations = { + [bids[0].adUnitCode]: {isEnabled: false, expiresAt: Date.now() + 600000} + } + + localStorage.setItem(OPTIMIZATIONS_STORAGE_KEY, JSON.stringify(optimizations)) + + const requests = spec.buildRequests(bids, {...bidderRequest, bids}); + + localStorage.removeItem(OPTIMIZATIONS_STORAGE_KEY) + + expect(requests).to.be.undefined + }) + + it('should exclude video requests where context is missing or not equal to outstream', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.bidId, + adUnitCode: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.adUnitCode, + mediaTypes: {banner: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.mediaTypes.banner} + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const invalidVideoBids = [VIDEO_BID_WITH_MISSING_CONTEXT] + const multiMediatypesBidWithInvalidVideo = [MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT] + + expect(spec.buildRequests(invalidVideoBids, {...bidderRequest, bids: invalidVideoBids})).to.be.undefined + + const [syncRequest, auctionRequest] = spec.buildRequests(multiMediatypesBidWithInvalidVideo, {...bidderRequest, bids: multiMediatypesBidWithInvalidVideo}) + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + }) + + describe('interpretResponse', function () { + it('should return an empty array if the response is invalid', function () { + expect(spec.interpretResponse({body: 'INVALID_BODY'}, {})).to.deep.equal([]); + }) + + it('should return a formatted bid', function () { + const serverResponse = { + body: { + id: 'requestId', + bids: [{ + impressionId: '2f0d9715f60be8', + price: { + cpm: 1, + currency: 'JPY' + }, + dealId: 'TEST_DEAL_ID', + ad: { + creative: { + id: 'CREATIVE_ID', + mediaType: 'banner', + size: {width: 320, height: 250}, + markup: '
1
' + } + }, + ttl: 3600 + }], + optimizations: [ + { + adUnitCode: 'test_ad_unit_code', + isEnabled: false, + ttl: 12000 + }, + { + adUnitCode: 'test_ad_unit_code_2', + isEnabled: true, + ttl: 12000 + } + ] + } + } + + const formattedBid = { + requestId: '2f0d9715f60be8', + cpm: 1, + currency: 'JPY', + dealId: 'TEST_DEAL_ID', + ttl: 3600, + netRevenue: true, + creativeId: 'CREATIVE_ID', + mediaType: 'banner', + width: 320, + height: 250, + ad: '
1
', + adUrl: null + } + + const formattedResponse = spec.interpretResponse(serverResponse, {}) + const storedOptimizations = getOptimizationsFromLocalStorage() + + localStorage.removeItem(OPTIMIZATIONS_STORAGE_KEY) + + expect(formattedResponse).to.deep.equal([formattedBid]); + + expect(storedOptimizations['test_ad_unit_code']).to.exist + expect(storedOptimizations['test_ad_unit_code'].isEnabled).to.equal(false) + expect(storedOptimizations['test_ad_unit_code'].expiresAt).to.be.a('number') + + expect(storedOptimizations['test_ad_unit_code_2']).to.exist + expect(storedOptimizations['test_ad_unit_code_2'].isEnabled).to.equal(true) + expect(storedOptimizations['test_ad_unit_code_2'].expiresAt).to.be.a('number') + }) + }); + + describe('getUserSyncs', function () { + it('should return an empty response if the response is invalid or missing data', function () { + expect(spec.getUserSyncs(null, [{body: 'INVALID_BODY'}])).to.be.undefined; + expect(spec.getUserSyncs(null, [{body: 'INVALID_BODY'}, {body: 'INVALID_BODY'}])).to.be.undefined; + }) + + it('should return an array of user syncs', function () { + const serverResponses = [ + { + body: { + bidders: [ + {type: 'image', url: 'https://test-url.com'}, + {type: 'redirect', url: 'https://test-url.com'}, + {type: 'iframe', url: 'https://test-url.com'} + ] + } + }, + { + body: 'BID-RESPONSE-DATA' + } + ] + + const formattedUserSyncs = [ + {type: 'image', url: 'https://test-url.com'}, + {type: 'image', url: 'https://test-url.com'}, + {type: 'iframe', url: 'https://test-url.com'} + ] + + expect(spec.getUserSyncs(null, serverResponses)).to.deep.equal(formattedUserSyncs); + }) + }); +}); From 9e2c63ec92b3f88882d04ae2b9cbbdc80f504a32 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 19 Aug 2024 18:49:38 -0700 Subject: [PATCH 0441/1097] UserID: merge EIDs with first party data (#12110) * ortbConverter: do not override EIDS provided as first party data * update tests * Allow bidder-specific FPD enrichments * Refactor eid generation, add primaryIds * EIDs as FPD * Fix tests * PBS tests, fix multiple hook registration * Fix more test cleanup * remove eidPermissions * refactor oderByPriority * refactor initializedSubmodules * fix lint * update id5 primaryIds * simplify eid source filtering * clean up PBS userId logic * Revert "Allow bidder-specific FPD enrichments" This reverts commit 2fb74525a5d527e378b48e966769283e2256a9ea. * undo bidder-specific enrichments * use startAuction instead of requestBids * add test on userIdAsEids * Fix lint --- modules/id5IdSystem.js | 1 + modules/imuIdSystem.js | 1 + modules/liveIntentIdSystem.js | 1 + modules/prebidServerBidAdapter/index.js | 17 +- .../prebidServerBidAdapter/ortbConverter.js | 30 +- modules/userId/eids.js | 56 +- modules/userId/index.js | 315 ++++++----- .../spec/modules/conversantBidAdapter_spec.js | 8 +- test/spec/modules/criteoBidAdapter_spec.js | 29 +- test/spec/modules/id5IdSystem_spec.js | 12 +- test/spec/modules/idxIdSystem_spec.js | 9 +- .../modules/improvedigitalBidAdapter_spec.js | 9 +- test/spec/modules/lmpIdSystem_spec.js | 9 +- test/spec/modules/openxBidAdapter_spec.js | 12 +- .../modules/prebidServerBidAdapter_spec.js | 54 +- .../spec/modules/pulsepointBidAdapter_spec.js | 44 +- test/spec/modules/r2b2BidAdapter_spec.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 3 +- .../modules/trafficgateBidAdapter_spec.js | 3 +- test/spec/modules/uid2IdSystem_helpers.js | 4 +- test/spec/modules/userId_spec.js | 488 ++++++++++++------ .../spec/modules/zeotapIdPlusIdSystem_spec.js | 4 +- test/spec/ortbConverter/userId_spec.js | 54 -- test/spec/unit/pbjs_api_spec.js | 2 +- 24 files changed, 651 insertions(+), 516 deletions(-) delete mode 100644 test/spec/ortbConverter/userId_spec.js diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 95e40337e79..d08efdcb232 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -237,6 +237,7 @@ export const id5IdSubmodule = { } return cacheIdObj; }, + primaryIds: ['id5id', 'trueLinkId'], eids: { 'id5id': { getValue: function (data) { diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 1242ca183ea..3e9904c526f 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -166,6 +166,7 @@ export const imuIdSubmodule = { } }; }, + primaryIds: ['imppid', 'imuid'], eids: { 'imppid': { source: 'ppid.intimatemerger.com', diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index a9708910ca7..50a8dc2aa1d 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -302,6 +302,7 @@ export const liveIntentIdSubmodule = { return { callback: result }; }, + primaryIds: ['libp'], eids: { ...UID1_EIDS, ...UID2_EIDS, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index edae21e97a7..4212ab294f5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -4,7 +4,6 @@ import { deepClone, flatten, generateUUID, - getPrebidInternal, insertUserSyncIframe, isNumber, isPlainObject, @@ -16,7 +15,7 @@ import { triggerPixel, uniques, } from '../../src/utils.js'; -import { EVENTS, REJECTION_REASON, S2S } from '../../src/constants.js'; +import {EVENTS, REJECTION_REASON, S2S} from '../../src/constants.js'; import adapterManager, {s2sActivityParams} from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; import {addPaapiConfig, isValid} from '../../src/adapters/bidderFactory.js'; @@ -37,8 +36,6 @@ const TYPE = S2S.SRC; let _syncCount = 0; let _s2sConfigs; -let eidPermissions; - /** * @typedef {Object} AdapterOptions * @summary s2sConfig parameter that adds arguments to resulting OpenRTB payload that goes to Prebid Server @@ -553,7 +550,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques .reduce(flatten, []) .filter(uniques); - const request = s2sBidRequest.metrics.measureTime('buildRequests', () => buildPBSRequest(s2sBidRequest, bidRequests, adUnits, requestedBidders, eidPermissions)); + const request = s2sBidRequest.metrics.measureTime('buildRequests', () => buildPBSRequest(s2sBidRequest, bidRequests, adUnits, requestedBidders)); const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); @@ -605,14 +602,4 @@ function shouldEmitNonbids(s2sConfig, response) { return s2sConfig?.extPrebid?.returnallbidstatus && response?.ext?.seatnonbid; } -/** - * Global setter that sets eids permissions for bidders - * This setter is to be used by userId module when included - * @param {Array} newEidPermissions - */ -function setEidPermissions(newEidPermissions) { - eidPermissions = newEidPermissions; -} -getPrebidInternal().setEidPermissions = setEidPermissions; - adapterManager.registerBidAdapter(new PrebidServer(), 'prebidServer'); diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 242c65c7dfa..01da91b57a7 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -1,17 +1,7 @@ import {ortbConverter} from '../../libraries/ortbConverter/converter.js'; -import { - deepAccess, - deepSetValue, - getBidRequest, - getDefinedParams, - isArray, - logError, - logWarn, - mergeDeep, - timestamp -} from '../../src/utils.js'; +import {deepAccess, deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; import {config} from '../../src/config.js'; -import { STATUS, S2S } from '../../src/constants.js'; +import {S2S, STATUS} from '../../src/constants.js'; import {createBid} from '../../src/bidfactory.js'; import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; import {setImpBidParams} from '../../libraries/pbsExtensions/processors/params.js'; @@ -55,7 +45,7 @@ const PBS_CONVERTER = ortbConverter({ if (!imps.length) { logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); } else { - let {s2sBidRequest, requestedBidders, eidPermissions} = context; + let {s2sBidRequest} = context; const request = buildRequest(imps, proxyBidderRequest, context); request.tmax = s2sBidRequest.s2sConfig.timeout ?? Math.min(s2sBidRequest.requestBidsTimeout * 0.75, s2sBidRequest.s2sConfig.maxTimeout ?? s2sDefaultConfig.maxTimeout); @@ -67,16 +57,6 @@ const PBS_CONVERTER = ortbConverter({ } }) - if (isArray(eidPermissions) && eidPermissions.length > 0) { - if (requestedBidders && isArray(requestedBidders)) { - eidPermissions = eidPermissions.map(p => ({ - ...p, - bidders: p.bidders.filter(bidder => requestedBidders.includes(bidder)) - })) - } - deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); - } - if (!context.transmitTids) { deepSetValue(request, 'ext.prebid.createtids', false); } @@ -253,7 +233,7 @@ const PBS_CONVERTER = ortbConverter({ }, }); -export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requestedBidders, eidPermissions) { +export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requestedBidders) { const requestTimestamp = timestamp(); const impIds = new Set(); const proxyBidRequests = []; @@ -295,7 +275,6 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste proxyBidRequests.push({ ...adUnit, adUnitCode: adUnit.code, - ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), pbsData: {impId, actualBidRequests, adUnit}, }); }); @@ -317,7 +296,6 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste s2sBidRequest, requestedBidders, actualBidderRequests: bidderRequests, - eidPermissions, nativeRequest: s2sBidRequest.s2sConfig.ortbNative, getRedactor, transmitTids: isActivityAllowed(ACTIVITY_TRANSMIT_TID, s2sParams), diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 930dd34b23d..bf425b8d9f0 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,4 +1,4 @@ -import {deepAccess, deepClone, isFn, isPlainObject, isStr} from '../../src/utils.js'; +import {deepClone, isFn, isStr} from '../../src/utils.js'; /* * @typedef {import('../modules/userId/index.js').SubmoduleContainer} SubmoduleContainer */ @@ -6,25 +6,24 @@ import {deepAccess, deepClone, isFn, isPlainObject, isStr} from '../../src/utils export const EID_CONFIG = new Map(); // this function will create an eid object for the given UserId sub-module -function createEidObject(userIdData, subModuleKey) { - const conf = EID_CONFIG.get(subModuleKey); - if (conf && userIdData) { +function createEidObject(userIdData, subModuleKey, eidConf) { + if (eidConf && userIdData) { let eid = {}; - eid.source = isFn(conf['getSource']) ? conf['getSource'](userIdData) : conf['source']; - const value = isFn(conf['getValue']) ? conf['getValue'](userIdData) : userIdData; + eid.source = isFn(eidConf['getSource']) ? eidConf['getSource'](userIdData) : eidConf['source']; + const value = isFn(eidConf['getValue']) ? eidConf['getValue'](userIdData) : userIdData; if (isStr(value)) { - const uid = { id: value, atype: conf['atype'] }; + const uid = { id: value, atype: eidConf['atype'] }; // getUidExt - if (isFn(conf['getUidExt'])) { - const uidExt = conf['getUidExt'](userIdData); + if (isFn(eidConf['getUidExt'])) { + const uidExt = eidConf['getUidExt'](userIdData); if (uidExt) { uid.ext = uidExt; } } eid.uids = [uid]; // getEidExt - if (isFn(conf['getEidExt'])) { - const eidExt = conf['getEidExt'](userIdData); + if (isFn(eidConf['getEidExt'])) { + const eidExt = eidConf['getEidExt'](userIdData); if (eidExt) { eid.ext = eidExt; } @@ -35,7 +34,7 @@ function createEidObject(userIdData, subModuleKey) { return null; } -export function createEidsArray(bidRequestUserId) { +export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { const allEids = {}; function collect(eid) { const key = JSON.stringify([eid.source?.toLowerCase(), eid.ext]); @@ -48,31 +47,24 @@ export function createEidsArray(bidRequestUserId) { Object.entries(bidRequestUserId).forEach(([name, values]) => { values = Array.isArray(values) ? values : [values]; - const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name)); + const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name, eidConfigs.get(name))); eids.filter(eid => eid != null).forEach(collect); }) return Object.values(allEids); } /** - * @param {SubmoduleContainer[]} submodules + * @param {SubmodulePriorityMap} priorityMap */ -export function buildEidPermissions(submodules) { - let eidPermissions = []; - submodules.filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length) - .forEach(i => { - Object.keys(i.idObj).forEach(key => { - const eidConf = EID_CONFIG.get(key) || {}; - if (deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && - eidConf.source) { - eidPermissions.push( - { - source: eidConf.source, - bidders: i.config.bidders - } - ); - } - }); - }); - return eidPermissions; +export function getEids(priorityMap) { + const eidConfigs = new Map(); + const idValues = {}; + Object.entries(priorityMap).forEach(([key, submodules]) => { + const submodule = submodules.find(mod => mod.idObj?.[key] != null); + if (submodule) { + idValues[key] = submodule.idObj[key]; + eidConfigs.set(key, submodule.submodule.eids?.[key]) + } + }) + return createEidsArray(idValues, eidConfigs); } diff --git a/modules/userId/index.js b/modules/userId/index.js index 31083fd1e47..f7c6e1c5433 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -126,14 +126,18 @@ * @property {(function|undefined)} callback - function that will return an id */ -import {find, includes} from '../../src/polyfill.js'; +/** + * @typedef {{[idKey: string]: SubmoduleContainer[]}} SubmodulePriorityMap + */ + +import {find} from '../../src/polyfill.js'; import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; import {EVENTS} from '../../src/constants.js'; import {module, ready as hooksReady} from '../../src/hook.js'; -import {buildEidPermissions, createEidsArray, EID_CONFIG} from './eids.js'; +import {EID_CONFIG, getEids} from './eids.js'; import { getCoreStorageManager, getStorageManager, @@ -144,7 +148,6 @@ import { deepAccess, deepSetValue, delayExecution, - getPrebidInternal, isArray, isEmpty, isFn, @@ -157,7 +160,6 @@ import { } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; import {defer, GreedyPromise} from '../../src/utils/promise.js'; -import {registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetrics.js'; import {findRootDomain} from '../../src/fpd/rootDomain.js'; import {allConsent, GDPR_GVLIDS} from '../../src/consentHandler.js'; @@ -166,6 +168,7 @@ import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_ENRICH_EIDS} from '../../src/activities/activities.js'; import {activityParams} from '../../src/activities/activityParams.js'; import {USERSYNC_DEFAULT_CONFIG} from '../../src/userSync.js'; +import {startAuction} from '../../src/prebid.js'; const MODULE_NAME = 'User ID'; const COOKIE = STORAGE_TYPE_COOKIES; @@ -182,7 +185,7 @@ let addedUserIdHook = false; /** @type {SubmoduleContainer[]} */ let submodules = []; -/** @type {SubmoduleContainer[]} */ +/** @type {PriorityMaps} */ let initializedSubmodules; /** @type {SubmoduleConfig[]} */ @@ -327,13 +330,6 @@ export function deleteStoredValue(submodule) { }); } -function setPrebidServerEidPermissions(initializedSubmodules) { - let setEidPermissions = getPrebidInternal().setEidPermissions; - if (typeof setEidPermissions === 'function' && isArray(initializedSubmodules)) { - setEidPermissions(buildEidPermissions(initializedSubmodules)); - } -} - function getValueFromCookie(submodule, storedKey) { return submodule.storageMgr.getCookie(storedKey) } @@ -387,8 +383,9 @@ function getStoredValue(submodule, key = undefined) { /** * @param {SubmoduleContainer[]} submodules * @param {function} cb - callback for after processing is done. + * @param {PriorityMaps} priorityMaps */ -function processSubmoduleCallbacks(submodules, cb, allModules) { +function processSubmoduleCallbacks(submodules, cb, priorityMaps) { cb = uidMetrics().fork().startTiming('userId.callbacks.total').stopBefore(cb); const done = delayExecution(() => { clearTimeout(timeoutID); @@ -404,7 +401,8 @@ function processSubmoduleCallbacks(submodules, cb, allModules) { } // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.submodule.decode(idObj, submodule.config); - updatePPID(getCombinedSubmoduleIds(allModules)); + priorityMaps.refresh(); + updatePPID(priorityMaps); } else { logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } @@ -422,95 +420,156 @@ function processSubmoduleCallbacks(submodules, cb, allModules) { } /** - * This function will create a combined object for all subModule Ids - * @param {SubmoduleContainer[]} submodules + * @param {SubmodulePriorityMap} priorityMap + * @returns {{}} */ -function getCombinedSubmoduleIds(submodules) { - if (!Array.isArray(submodules) || !submodules.length) { - return {}; - } - return getPrioritizedCombinedSubmoduleIds(submodules) +function getIds(priorityMap) { + return Object.fromEntries( + Object.entries(priorityMap) + .map(([key, submodules]) => [key, submodules.find(mod => mod.idObj?.[key] != null)?.idObj?.[key]]) + .filter(([_, value]) => value != null) + ) } -/** - * This function will return a submodule ID object for particular source name - * @param {SubmoduleContainer[]} submodules - * @param {string} sourceName - */ -function getSubmoduleId(submodules, sourceName) { - if (!Array.isArray(submodules) || !submodules.length) { - return {}; +function getPrimaryIds(submodule) { + if (submodule.primaryIds) return submodule.primaryIds; + const ids = Object.keys(submodule.eids ?? {}); + if (ids.length > 1) { + throw new Error(`ID submodule ${submodule.name} can provide multiple IDs, but does not specify 'primaryIds'`) } - - const prioritisedIds = getPrioritizedCombinedSubmoduleIds(submodules); - const eligibleIdName = Object.keys(prioritisedIds).find(idName => { - const config = EID_CONFIG.get(idName); - return config?.source === sourceName || (isFn(config?.getSource) && config.getSource() === sourceName); - }); - - return eligibleIdName ? {[eligibleIdName]: prioritisedIds[eligibleIdName]} : []; + return ids; } /** - * This function will create a combined object for bidder with allowed subModule Ids - * @param {SubmoduleContainer[]} submodules - * @param {string} bidder + * Given a collection of items, where each item maps to any number of IDs (getKeys) and an ID module (getIdMod), + * return a map from ID key to all items that map to that ID key, in order of priority (highest priority first). + * + * @template T + * @param {T[]} items + * @param {(item: T) => string[]} getKeys + * @param {(item: T) => Submodule} getIdMod + * @returns {{[key: string]: T[]}} */ -function getCombinedSubmoduleIdsForBidder(submodules, bidder) { - if (!Array.isArray(submodules) || !submodules.length || !bidder) { - return {}; - } - const eligibleSubmodules = submodules - .filter(i => !i.config.bidders || !isArray(i.config.bidders) || includes(i.config.bidders, bidder)) - - return getPrioritizedCombinedSubmoduleIds(eligibleSubmodules); -} - -function collectByPriority(submodules, getIds, getName) { - return Object.fromEntries(Object.entries(submodules.reduce((carry, submod) => { - const ids = getIds(submod); - ids && Object.keys(ids).forEach(key => { - const maybeCurrentIdPriority = idPriority[key]?.indexOf(getName(submod)); - const currentIdPriority = isNumber(maybeCurrentIdPriority) ? maybeCurrentIdPriority : -1; - const currentIdState = {priority: currentIdPriority, value: ids[key]}; - if (carry[key]) { - const winnerIdState = currentIdState.priority > carry[key].priority ? currentIdState : carry[key]; - carry[key] = winnerIdState; - } else { - carry[key] = currentIdState; - } - }); - return carry; - }, {})).map(([k, v]) => [k, v.value])); +function orderByPriority(items, getKeys, getIdMod) { + const tally = {}; + items.forEach(item => { + const module = getIdMod(item); + const primaryIds = getPrimaryIds(module); + getKeys(item).forEach(key => { + const keyItems = tally[key] = tally[key] ?? [] + const keyPriority = idPriority[key]?.indexOf(module.name) ?? (primaryIds.includes(key) ? 0 : -1); + const pos = keyItems.findIndex(([priority]) => priority < keyPriority); + keyItems.splice(pos === -1 ? keyItems.length : pos, 0, [keyPriority, item]) + }) + }) + return Object.fromEntries(Object.entries(tally).map(([key, items]) => [key, items.map(([_, item]) => item)])) } /** - * @param {SubmoduleContainer[]} submodules + * @typedef {Object} PriorityMaps + * @property {SubmoduleContainer[]} submodules all active submodules + * @property {SubmodulePriorityMap} global priority map for global (not bidder-specific) submodules + * @property {SubmodulePriorityMap} combined priority map for ALL submodules, disregarding bidder filters + * @property {{[bidder: string]: SubmodulePriorityMap}} bidder priority maps for each bidder's specific submodules + * @property {(submodules: SubmoduleContainer[]) => void} refresh refresh priority maps, optionally adding or updating some submodules. + * Should be called every time a submodule's ID is updated. */ -function getPrioritizedCombinedSubmoduleIds(submodules) { - return collectByPriority( - submodules.filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length), - (submod) => submod.idObj, - (submod) => submod.submodule.name - ) -} /** - * @param {AdUnit[]} adUnits - * @param {SubmoduleContainer[]} submodules + * @returns PriorityMaps */ -function addIdDataToAdUnitBids(adUnits, submodules) { +function mkPriorityMaps() { + const map = { + submodules: [], + global: {}, + bidder: {}, + combined: {}, + /** + * @param {SubmoduleContainer[]} addtlModules + */ + refresh(addtlModules = []) { + const refreshing = new Set(addtlModules.map(mod => mod.submodule)); + map.submodules = map.submodules.filter((mod) => !refreshing.has(mod.submodule)).concat(addtlModules); + update(); + } + } + function update() { + const modulesById = orderByPriority( + map.submodules, + (submod) => Object.keys(submod.idObj ?? {}), + (submod) => submod.submodule, + ) + const global = {}; + const bidder = {}; + Object.entries(modulesById) + .forEach(([key, modules]) => { + let allNonGlobal = true; + const bidderFilters = new Set(); + modules.map(mod => mod.config.bidders) + .forEach(bidders => { + if (Array.isArray(bidders) && bidders.length > 0) { + bidders.forEach(bidder => bidderFilters.add(bidder)); + } else { + allNonGlobal = false; + } + }) + if (bidderFilters.size > 0 && !allNonGlobal) { + logWarn(`userID modules ${modules.map(mod => mod.submodule.name).join(', ')} provide the same ID ('${key}'), but are configured for different bidders. ID will be skipped.`) + } else { + if (bidderFilters.size === 0) { + global[key] = modules; + } else { + bidderFilters.forEach(bidderCode => { + bidder[bidderCode] = bidder[bidderCode] ?? {}; + bidder[bidderCode][key] = modules; + }) + } + } + }); + const combined = Object.values(bidder).concat([global]).reduce((combo, map) => Object.assign(combo, map), {}); + Object.assign(map, {global, bidder, combined}); + } + return map; +} + +export function enrichEids(ortb2Fragments) { + const {global: globalFpd, bidder: bidderFpd} = ortb2Fragments; + const {global: globalMods, bidder: bidderMods} = initializedSubmodules; + const globalEids = getEids(globalMods); + if (globalEids.length > 0) { + deepSetValue(globalFpd, 'user.ext.eids', (globalFpd.user?.ext?.eids ?? []).concat(globalEids)); + } + Object.entries(bidderMods).forEach(([bidder, moduleMap]) => { + const bidderEids = getEids(moduleMap); + if (bidderEids.length > 0) { + deepSetValue( + bidderFpd, + `${bidder}.user.ext.eids`, + (bidderFpd[bidder]?.user?.ext?.eids ?? []).concat(bidderEids) + ); + } + }) + return ortb2Fragments; +} + +function addIdData({adUnits, ortb2Fragments}) { + ortb2Fragments = ortb2Fragments ?? {global: {}, bidder: {}} + enrichEids(ortb2Fragments); if ([adUnits].some(i => !Array.isArray(i) || !i.length)) { return; } + const globalIds = getIds(initializedSubmodules.global); + const globalEids = ortb2Fragments.global.user?.ext?.eids || []; adUnits.forEach(adUnit => { if (adUnit.bids && isArray(adUnit.bids)) { adUnit.bids.forEach(bid => { - const combinedSubmoduleIds = getCombinedSubmoduleIdsForBidder(submodules, bid.bidder); - if (Object.keys(combinedSubmoduleIds).length) { - // create a User ID object on the bid, - bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = createEidsArray(combinedSubmoduleIds); + const bidderIds = Object.assign({}, globalIds, getIds(initializedSubmodules.bidder[bid.bidder] ?? {})); + const bidderEids = globalEids.concat(ortb2Fragments.bidder[bid.bidder]?.user?.ext?.eids || []); + if (Object.keys(bidderIds).length > 0) { + bid.userId = bidderIds; + } + if (bidderEids.length > 0) { + bid.userIdAsEids = bidderEids; } }); } @@ -563,7 +622,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { })) .then(() => startCallbacks.promise.finally(initMetrics.startTiming('userId.callbacks.pending'))) .then(checkRefs(() => { - const modWithCb = initModules.filter(item => isFn(item.callback)); + const modWithCb = initModules.submodules.filter(item => isFn(item.callback)); if (modWithCb.length) { return new GreedyPromise((resolve) => processSubmoduleCallbacks(modWithCb, resolve, initModules)); } @@ -636,13 +695,12 @@ function getPPID(eids = getUserIdsAsEids() || []) { * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const requestBidsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { +export const startAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { GreedyPromise.race([ getIds().catch(() => null), delay(auctionDelay) ]).then(() => { - // pass available user id data to bid adapters - addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); + addIdData(reqBidsConfigObj); uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); @@ -654,7 +712,7 @@ export const requestBidsHook = timedAuctionHook('userId', function requestBidsHo * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIds() { - return getCombinedSubmoduleIds(initializedSubmodules) + return getIds(initializedSubmodules.combined) } /** @@ -662,7 +720,7 @@ function getUserIds() { * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIdsAsEids() { - return createEidsArray(getUserIds()) + return getEids(initializedSubmodules.combined) } /** @@ -671,7 +729,7 @@ function getUserIdsAsEids() { */ function getUserIdsAsEidBySource(sourceName) { - return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0]; + return getUserIdsAsEids().filter(eid => eid.source === sourceName)[0]; } /** @@ -802,7 +860,7 @@ function consentChanged(submodule) { return !storedConsent || storedConsent !== getConsentHash(); } -function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { +function populateSubmoduleId(submodule, forceRefresh) { // TODO: the ID submodule API only takes GDPR consent; it should be updated now that GDPR // is only a tiny fraction of a vast consent universe const gdprConsent = gdprDataHandler.getConsentData(); @@ -857,12 +915,12 @@ function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } } } - updatePPID(getCombinedSubmoduleIds(allSubmodules)); } -function updatePPID(userIds = getUserIds()) { - if (userIds && ppidSource) { - const ppid = getPPID(createEidsArray(userIds)); +function updatePPID(priorityMaps) { + const eids = getEids(priorityMaps.combined); + if (eids.length && ppidSource) { + const ppid = getPPID(eids); if (ppid) { if (isGptPubadsDefined()) { window.googletag.pubads().setPublisherProvidedId(ppid); @@ -877,7 +935,7 @@ function updatePPID(userIds = getUserIds()) { } } -function initSubmodules(dest, submodules, forceRefresh = false) { +function initSubmodules(priorityMaps, submodules, forceRefresh = false) { return uidMetrics().fork().measureTime('userId.init.modules', function () { if (!submodules.length) return []; // to simplify log messages from here on @@ -902,7 +960,7 @@ function initSubmodules(dest, submodules, forceRefresh = false) { const initialized = submodules.reduce((carry, submodule) => { return submoduleMetrics(submodule.submodule.name).measureTime('init', () => { try { - populateSubmoduleId(submodule, forceRefresh, submodules); + populateSubmoduleId(submodule, forceRefresh); carry.push(submodule); } catch (e) { logError(`Error in userID module '${submodule.submodule.name}':`, e); @@ -910,29 +968,12 @@ function initSubmodules(dest, submodules, forceRefresh = false) { return carry; }) }, []); - if (initialized.length) { - setPrebidServerEidPermissions(initialized); - } - initialized.forEach(updateInitializedSubmodules.bind(null, dest)); + priorityMaps.refresh(initialized); + updatePPID(priorityMaps); return initialized; }) } -function updateInitializedSubmodules(dest, submodule) { - let updated = false; - for (let i = 0; i < dest.length; i++) { - if (submodule.config.name.toLowerCase() === dest[i].config.name.toLowerCase()) { - updated = true; - dest[i] = submodule; - break; - } - } - - if (!updated) { - dest.push(submodule); - } -} - function getConfiguredStorageTypes(config) { return config?.storage?.type?.trim().split(/\s*&\s*/) || []; } @@ -1039,11 +1080,13 @@ function canUseStorage(submodule) { function updateEIDConfig(submodules) { EID_CONFIG.clear(); - Object.entries(collectByPriority( - submodules, - (mod) => mod.eids, - (mod) => mod.name - )).forEach(([id, conf]) => EID_CONFIG.set(id, conf)); + Object.entries( + orderByPriority( + submodules, + (mod) => Object.keys(mod.eids || {}), + (mod) => mod + ) + ).forEach(([key, submodules]) => EID_CONFIG.set(key, submodules[0].eids[key])) } /** @@ -1076,25 +1119,26 @@ function updateSubmodules() { }).filter(submodule => submodule !== null) .forEach((sm) => submodules.push(sm)); - if (!addedUserIdHook && submodules.length) { - // priority value 40 will load after consentManagement with a priority of 50 - getGlobal().requestBids.before(requestBidsHook, 40); - adapterManager.callDataDeletionRequest.before(requestDataDeletion); - coreGetPPID.after((next) => next(getPPID())); + if (submodules.length) { + if (!addedUserIdHook) { + startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd + adapterManager.callDataDeletionRequest.before(requestDataDeletion); + coreGetPPID.after((next) => next(getPPID())); + addedUserIdHook = true; + } logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); - addedUserIdHook = true; } } /** * This function will update the idPriority according to the provided configuration * @param {Object} idPriorityConfig - * @param {SubmoduleContainer[]} submodules + * @param {Submodule[]} submodules */ function updateIdPriority(idPriorityConfig, submodules) { if (idPriorityConfig) { const result = {}; - const aliasToName = new Map(submodules.map(s => s.submodule.aliasName ? [s.submodule.aliasName, s.submodule.name] : [])); + const aliasToName = new Map(submodules.map(s => s.aliasName ? [s.aliasName, s.name] : [])); Object.keys(idPriorityConfig).forEach(key => { const priority = isArray(idPriorityConfig[key]) ? [...idPriorityConfig[key]].reverse() : [] result[key] = priority.map(s => aliasToName.has(s) ? aliasToName.get(s) : s); @@ -1103,6 +1147,8 @@ function updateIdPriority(idPriorityConfig, submodules) { } else { idPriority = {}; } + initializedSubmodules.refresh(); + updateEIDConfig(submodules) } export function requestDataDeletion(next, ...args) { @@ -1153,8 +1199,7 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { ppidSource = undefined; submodules = []; configRegistry = []; - addedUserIdHook = false; - initializedSubmodules = []; + initializedSubmodules = mkPriorityMaps(); initIdSystem = idSystemInitializer({delay}); if (configListener != null) { configListener(); @@ -1172,7 +1217,7 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : USERSYNC_DEFAULT_CONFIG.syncDelay auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : USERSYNC_DEFAULT_CONFIG.auctionDelay; updateSubmodules(); - updateIdPriority(userSync.idPriority, submodules); + updateIdPriority(userSync.idPriority, submoduleRegistry); initIdSystem({ready: true}); } } @@ -1192,11 +1237,3 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { init(config); module('userId', attachIdSystem, { postInstallAllowed: true }); - -export function setOrtbUserExtEids(ortbRequest, bidderRequest, context) { - const eids = deepAccess(context, 'bidRequests.0.userIdAsEids'); - if (eids && Object.keys(eids).length > 0) { - deepSetValue(ortbRequest, 'user.ext.eids', eids.concat(ortbRequest.user?.ext?.eids || [])); - } -} -registerOrtbProcessor({type: REQUEST, name: 'userExtEids', fn: setOrtbUserExtEids}); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index d68383b9118..2b2d44b3b06 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -560,16 +560,10 @@ describe('Conversant adapter tests', function() { // clone bidRequests let requests = utils.deepClone(bidRequests); - const uid = {pubcid: '112233', idl_env: '334455'}; const eidArray = [{'source': 'pubcid.org', 'uids': [{'id': '112233', 'atype': 1}]}, {'source': 'liveramp.com', 'uids': [{'id': '334455', 'atype': 3}]}]; - // add pubcid to every entry - requests.forEach((unit) => { - Object.assign(unit, {userId: uid}); - Object.assign(unit, {userIdAsEids: eidArray}); - }); // construct http post payload - const payload = spec.buildRequests(requests, {}).data; + const payload = spec.buildRequests(requests, {ortb2: {user: {ext: {eids: eidArray}}}}).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ {source: 'pubcid.org', uids: [{id: '112233', atype: 1}]}, {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 079357ab4fe..00f05a2dc4d 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -758,19 +758,28 @@ describe('The Criteo bidding adapter', function () { sizes: [[728, 90]] } }, - userIdAsEids: [ - { - source: 'criteo.com', - uids: [{ - id: 'abc', - atype: 1 - }] - } - ], params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const br = { + ...bidderRequest, + ortb2: { + user: { + ext: { + eids: [ + { + source: 'criteo.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ] + } + } + } + } + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); const ortbRequest = request.data; expect(ortbRequest.user.ext.eids).to.deep.equal([ { diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 2703c6681e7..456bd92cb02 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -4,7 +4,7 @@ import { coreStorage, getConsentHash, init, - requestBidsHook, + startAuctionHook, setSubmoduleRegistry } from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; @@ -912,7 +912,7 @@ describe('ID5 ID System', function () { setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); - requestBidsHook(wrapAsyncExpects(done, () => { + startAuctionHook(wrapAsyncExpects(done, () => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); @@ -940,7 +940,7 @@ describe('ID5 ID System', function () { setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.euid`); @@ -969,7 +969,7 @@ describe('ID5 ID System', function () { setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); - requestBidsHook(wrapAsyncExpects(done, function () { + startAuctionHook(wrapAsyncExpects(done, function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); @@ -992,7 +992,7 @@ describe('ID5 ID System', function () { setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getValueConfig(ID5_STORED_ID)); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); @@ -1022,7 +1022,7 @@ describe('ID5 ID System', function () { setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(id5Config); return new Promise((resolve) => { - requestBidsHook(() => { + startAuctionHook(() => { resolve(); }, {adUnits}); }).then(() => { diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js index 0d30808cc1f..bfe9d1b1e68 100644 --- a/test/spec/modules/idxIdSystem_spec.js +++ b/test/spec/modules/idxIdSystem_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { init, startAuctionHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import 'src/prebid.js'; @@ -101,10 +101,15 @@ describe('IDx ID System', () => { afterEach(() => { sandbox.restore(); + config.resetConfig(); + }) + + after(() => { + init(config); }) it('when a stored IDx exists it is added to bids', (done) => { - requestBidsHook(() => { + startAuctionHook(() => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.idx'); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 215e0b8ac98..5dc94f544ea 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -535,7 +535,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add eids', function () { - const userIdAsEids = [ + const eids = [ { source: 'id5-sync.com', uids: [{ @@ -551,9 +551,10 @@ describe('Improve Digital Adapter Tests', function () { id: '1111' }] }]}}; - const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.userIdAsEids = userIdAsEids; - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const request = spec.buildRequests([simpleBidRequest], { + ...bidderRequestReferrer, + ortb2: {user: {ext: {eids: eids}}} + })[0]; const payload = JSON.parse(request.data); expect(payload.user).to.deep.equal(expectedUserObject); }); diff --git a/test/spec/modules/lmpIdSystem_spec.js b/test/spec/modules/lmpIdSystem_spec.js index c17df3a7ef3..28f8ba0697d 100644 --- a/test/spec/modules/lmpIdSystem_spec.js +++ b/test/spec/modules/lmpIdSystem_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { find } from 'src/polyfill.js'; import { config } from 'src/config.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { init, startAuctionHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js'; import { mockGdprConsent } from '../../helpers/consentData.js'; import 'src/prebid.js'; @@ -100,10 +100,15 @@ describe('LMPID System', () => { afterEach(() => { sandbox.restore(); + config.resetConfig(); }); + after(() => { + init(config); + }) + it('when a stored LMPID exists it is added to bids', (done) => { - requestBidsHook(() => { + startAuctionHook(() => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.lmpid'); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index dbc036c860b..faaab727b17 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -972,7 +972,7 @@ describe('OpenxRtbAdapter', function () { }); context('when there are userid providers', function () { - const userIdAsEids = [ + const eids = [ { source: 'adserver.org', uids: [{ @@ -1003,14 +1003,12 @@ describe('OpenxRtbAdapter', function () { ]; it(`should send the user id under the extended ids`, function () { - const bidRequestsWithUserId = [{ + const bidRequests = [{ bidder: 'openx', params: { unit: '11', delDomain: 'test-del-domain' }, - userId: { - }, adUnitCode: 'adunit-code', mediaTypes: { banner: { @@ -1020,12 +1018,12 @@ describe('OpenxRtbAdapter', function () { bidId: 'test-bid-id-1', bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1', - userIdAsEids: userIdAsEids }]; // enrich bid request with userId key/value - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - expect(request[0].data.user.ext.eids).to.eql(userIdAsEids); + mockBidderRequest.ortb2 = {user: {ext: {eids}}} + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.eql(eids); }); it(`when no user ids are available, it should not send any extended ids`, function () { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 46d7b313f3a..9769c2010df 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2065,34 +2065,36 @@ describe('S2S Adapter', function () { }); }); - it('when userId is defined on bids, it\'s properties should be copied to user.ext.tpid properties', function () { - let consentConfig = { s2sConfig: CONFIG }; - config.setConfig(consentConfig); - - let userIdBidRequest = utils.deepClone(BID_REQUESTS); - userIdBidRequest[0].bids[0].userId = { - criteoId: '44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU', - tdid: 'abc123', - pubcid: '1234', - parrableId: { eid: '01.1563917337.test-eid' }, - lipb: { - lipbid: 'li-xyz', - segments: ['segA', 'segB'] - }, - idl_env: '0000-1111-2222-3333', - id5id: { - uid: '11111', - ext: { - linkType: 'some-link-type' + it('should pass user.ext.eids from FPD', function () { + config.setConfig({s2sConfig: CONFIG}); + const req = { + ...REQUEST, + ortb2Fragments: { + global: { + user: { + ext: { + eids: [{id: 1}, {id: 2}] + } + } + }, + bidder: { + appnexus: { + user: { + ext: { + eids: [{id: 3}] + } + } + } } } - }; - userIdBidRequest[0].bids[0].userIdAsEids = [{id: 1}, {id: 2}]; - - adapter.callBids(REQUEST, userIdBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(server.requests[0].requestBody); - expect(typeof requestBid.user.ext.eids).is.equal('object'); - expect(requestBid.user.ext.eids).to.eql([{id: 1}, {id: 2}]); + } + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + expect(payload.user.ext.eids).to.eql([{id: 1}, {id: 2}]); + expect(payload.ext.prebid.bidderconfig).to.eql([{ + bidders: ['appnexus'], + config: {ortb2: {user: {ext: {eids: [{id: 3}]}}}} + }]); }); it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index da8b6d8473a..5b63e201e42 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -3,6 +3,9 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepClone} from '../../../src/utils'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -484,22 +487,33 @@ describe('PulsePoint Adapter Tests', function () { it('Verify common id parameters', function () { const bidRequests = deepClone(slotConfigs); - bidRequests[0].userIdAsEids = [{ - source: 'pubcid.org', - uids: [{ - id: 'userid_pubcid' - }] - }, { - source: 'adserver.org', - uids: [{ - id: 'userid_ttd', - ext: { - rtiPartner: 'TDID' + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + const br = { + ...bidderRequest, + ortb2: { + user: { + ext: { + eids + } } - }] + } } - ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); expect(request).to.be.not.null; expect(request.data).to.be.not.null; const ortbRequest = request.data; @@ -507,7 +521,7 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.user).to.not.be.undefined; expect(ortbRequest.user.ext).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.not.be.undefined; - expect(ortbRequest.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + expect(ortbRequest.user.ext.eids).to.deep.equal(eids); }); it('Verify user level first party data', function () { diff --git a/test/spec/modules/r2b2BidAdapter_spec.js b/test/spec/modules/r2b2BidAdapter_spec.js index b94b400a71d..63850b78c40 100644 --- a/test/spec/modules/r2b2BidAdapter_spec.js +++ b/test/spec/modules/r2b2BidAdapter_spec.js @@ -420,7 +420,7 @@ describe('R2B2 adapter', function () { ], }, ]; - bids[0].userIdAsEids = eidsArray; + bidderRequest.ortb2 = {user: {ext: {eids: eidsArray}}} let requests = spec.buildRequests(bids, bidderRequest); let request = requests[0]; let eids = request.data.user.ext.eids; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index a5d25da9123..0856dc2571b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -272,7 +272,7 @@ describe('the rubicon adapter', function () { }], criteoId: '1111', }; - bid.userIdAsEids = [ + const eids = [ { 'source': 'liveintent.com', 'uids': [ @@ -347,6 +347,7 @@ describe('the rubicon adapter', function () { ] } ]; + bidderRequest.ortb2 = {user: {ext: {eids}}}; return bidderRequest; } diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 7fbc566d375..192497f9c8d 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -1001,10 +1001,9 @@ describe('TrafficgateOpenxRtbAdapter', function () { bidId: 'test-bid-id-1', bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1', - userIdAsEids: userIdAsEids }]; // enrich bid request with userId key/value - + mockBidderRequest.ortb2 = {user: {ext: {eids: userIdAsEids}}} const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); expect(request[0].data.user.ext.eids).to.eql(userIdAsEids); }); diff --git a/test/spec/modules/uid2IdSystem_helpers.js b/test/spec/modules/uid2IdSystem_helpers.js index cf60b123c66..44a3b374999 100644 --- a/test/spec/modules/uid2IdSystem_helpers.js +++ b/test/spec/modules/uid2IdSystem_helpers.js @@ -1,6 +1,6 @@ import {setConsentConfig} from 'modules/consentManagementTcf.js'; import {server} from 'test/mocks/xhr.js'; -import {coreStorage, requestBidsHook} from 'modules/userId/index.js'; +import {coreStorage, startAuctionHook} from 'modules/userId/index.js'; const msIn12Hours = 60 * 60 * 12 * 1000; const expireCookieDate = 'Thu, 01 Jan 1970 00:00:01 GMT'; @@ -19,7 +19,7 @@ export const runAuction = async () => { bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] }]; return new Promise(function(resolve) { - requestBidsHook(function() { + startAuctionHook(function() { resolve(adUnits[0].bids[0]); }, {adUnits}); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 2f28df47667..3e659c867cf 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -2,12 +2,12 @@ import { attachIdSystem, auctionDelay, coreStorage, - dep, + dep, enrichEids, findRootDomain, getConsentHash, getValidSubmoduleConfigs, init, PBJS_USER_ID_OPTOUT_NAME, - requestBidsHook, + startAuctionHook, requestDataDeletion, setStoredValue, setSubmoduleRegistry, @@ -70,6 +70,7 @@ describe('User ID', function () { decode(v) { return v; }, + primaryIds: [], aliasName, eids } @@ -136,7 +137,7 @@ describe('User ID', function () { function runBidsHook(...args) { startDelay = delay(); - const result = requestBidsHook(...args, {delay: startDelay}); + const result = startAuctionHook(...args, {delay: startDelay}); return new Promise((resolve) => setTimeout(() => resolve(result))); } @@ -176,6 +177,10 @@ describe('User ID', function () { config.resetConfig(); }); + after(() => { + init(config); + }) + describe('GVL IDs', () => { beforeEach(() => { sinon.stub(GDPR_GVLIDS, 'register'); @@ -1114,7 +1119,7 @@ describe('User ID', function () { // simulate an infinite `auctionDelay`; refreshing should still allow the auction to continue // as soon as ID submodules have completed init startInit(); - requestBidsHook(() => { + startAuctionHook(() => { done(); }, {adUnits: [getAdUnitMock()]}, {delay: delay()}); getGlobal().refreshUserIds(); @@ -1126,7 +1131,7 @@ describe('User ID', function () { it('should continue the auction when init fails', (done) => { startInit(); - requestBidsHook(() => { + startAuctionHook(() => { done(); }, {adUnits: [getAdUnitMock()]}, @@ -1634,7 +1639,7 @@ describe('User ID', function () { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.mid'); expect(bid.userId.mid).to.equal('1234'); - expect(bid.userIdAsEids.length).to.equal(0);// "mid" is an un-known submodule for USER_IDS_CONFIG in eids.js + expect(bid.userIdAsEids).to.not.exist;// "mid" is an un-known submodule for USER_IDS_CONFIG in eids.js }); }); @@ -1728,13 +1733,46 @@ describe('User ID', function () { }); }); - describe('Request bids hook appends userId to bid objs in adapters', function () { + describe('Start auction hook appends userId to bid objs in adapters', function () { let adUnits; beforeEach(function () { adUnits = [getAdUnitMock()]; }); + it('should include pub-provided eids in userIdAsEids', (done) => { + init(config); + setSubmoduleRegistry([createMockIdSubmodule('mockId', {id: {mockId: 'id'}}, null, {mockId: {source: 'mockid.com', atype: 1}})]); + config.setConfig({ + userSync: { + userIds: [ + {name: 'mockId'} + ] + } + }); + startAuctionHook(({adUnits}) => { + adUnits[0].bids.forEach(bid => { + expect(bid.userIdAsEids.find(eid => eid.source === 'mockid.com')).to.exist; + const bidderEid = bid.userIdAsEids.find(eid => eid.bidder === 'pub-provided'); + expect(bidderEid != null).to.eql(bid.bidder === 'sampleBidder'); + expect(bid.userIdAsEids.find(eid => eid.id === 'pub-provided')).to.exist; + }) + done(); + }, { + adUnits, + ortb2Fragments: { + global: { + user: {ext: {eids: [{id: 'pub-provided'}]}} + }, + bidder: { + sampleBidder: { + user: {ext: {eids: [{bidder: 'pub-provided'}]}} + } + } + } + }) + }) + it('test hook from pubcommonid cookie', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); @@ -1742,7 +1780,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1767,7 +1805,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'html5'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1794,7 +1832,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1822,7 +1860,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1848,7 +1886,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1871,133 +1909,18 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcidvalue'); expect(bid.userId.pubcidvalue).to.equal('testpubcidvalue'); - expect(bid.userIdAsEids.length).to.equal(0);// "pubcidvalue" is an un-known submodule for USER_IDS_CONFIG in eids.js + expect(bid.userIdAsEids).to.not.exist; // "pubcidvalue" is an un-known submodule for USER_IDS_CONFIG in eids.js }); }); done(); }, {adUnits}); }); - it('eidPermissions fun with bidders', function (done) { - coreStorage.setCookie('pubcid', 'test222', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule]); - let eidPermissions; - getPrebidInternal().setEidPermissions = function (newEidPermissions) { - eidPermissions = newEidPermissions; - } - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'pubCommonId', - bidders: [ - 'sampleBidder' - ], - storage: { - type: 'cookie', - name: 'pubcid', - expires: 28 - } - } - ] - } - }); - - requestBidsHook(function () { - expect(eidPermissions).to.deep.equal( - [ - { - bidders: [ - 'sampleBidder' - ], - source: 'pubcid.org' - } - ] - ); - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - if (bid.bidder === 'sampleBidder') { - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('test222'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'pubcid.org', - uids: [ - { - id: 'test222', - atype: 1 - } - ] - }); - } - if (bid.bidder === 'anotherSampleBidder') { - expect(bid).to.not.have.deep.nested.property('userId.pubcid'); - expect(bid).to.not.have.property('userIdAsEids'); - } - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - getPrebidInternal().setEidPermissions = undefined; - done(); - }, {adUnits}); - }); - - it('eidPermissions fun without bidders', function (done) { - coreStorage.setCookie('pubcid', 'test222', new Date(Date.now() + 5000).toUTCString()); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule]); - let eidPermissions; - getPrebidInternal().setEidPermissions = function (newEidPermissions) { - eidPermissions = newEidPermissions; - } - config.setConfig({ - userSync: { - syncDelay: 0, - userIds: [ - { - name: 'pubCommonId', - storage: { - type: 'cookie', - name: 'pubcid', - expires: 28 - } - } - ] - } - }); - - requestBidsHook(function () { - expect(eidPermissions).to.deep.equal( - [] - ); - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('test222'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'pubcid.org', - uids: [ - { - id: 'test222', - atype: 1 - }] - }); - }); - }); - getPrebidInternal().setEidPermissions = undefined; - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - it('test hook from pubProvidedId config params', function (done) { init(config); setSubmoduleRegistry([pubProvidedIdSubmodule]); @@ -2041,7 +1964,7 @@ describe('User ID', function () { } }); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubProvidedId'); @@ -2126,7 +2049,7 @@ describe('User ID', function () { } }); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { // check PubCommonId id data was copied to bid @@ -2181,7 +2104,7 @@ describe('User ID', function () { })) } }); - requestBidsHook((req) => { + startAuctionHook((req) => { const activeIds = req.adUnits.flatMap(au => au.bids).flatMap(bid => Object.keys(bid.userId)); expect(Array.from(new Set(activeIds))).to.have.members([MOCK_IDS[1]]); done(); @@ -2514,7 +2437,7 @@ describe('User ID', function () { expect(typeof (getGlobal()).getEncryptedEidsForSource).to.equal('function'); }); - it('pbjs.getEncryptedEidsForSource should return the string without encryption if encryption is false', (done) => { + it('pbjs.getEncryptedEidsForSource should return the string without encryption if encryption is false', () => { init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ @@ -2536,43 +2459,42 @@ describe('User ID', function () { }, }); const encrypt = false; - (getGlobal()).getEncryptedEidsForSource(signalSources[0], encrypt).then((data) => { + return (getGlobal()).getEncryptedEidsForSource(signalSources[0], encrypt).then((data) => { let users = (getGlobal()).getUserIdsAsEids(); expect(data).to.equal(users[0].uids[0].id); - done(); - }).catch(done); + }) }); it('pbjs.getEncryptedEidsForSource should return prioritized id as non-encrypted string', (done) => { init(config); - setSubmoduleRegistry([ - createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), - createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), - createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}, null, { - uid2: { - source: 'uidapi.com', - getValue(data) { - return data.id - } - }, - pubcid: { - source: 'pubcid.org', - }, - lipb: { - source: 'liveintent.com', - getValue(data) { - return data.lipbid - } + const EIDS = { + uid2: { + source: 'uidapi.com', + getValue(data) { + return data.id } - }), - createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}, null, { - merkleId: { - source: 'merkleinc.com', - getValue(data) { - return data.id - } + }, + pubcid: { + source: 'pubcid.org', + }, + lipb: { + source: 'liveintent.com', + getValue(data) { + return data.lipbid } - }) + }, + merkleId: { + source: 'merkleinc.com', + getValue(data) { + return data.id + } + } + } + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}, null, EIDS), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}, null, EIDS), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}, null, EIDS), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}, null, EIDS) ]); config.setConfig({ userSync: { @@ -2717,4 +2639,246 @@ describe('User ID', function () { }); }) }); + + describe('enrichEids', () => { + let idValues; + + function mockIdSubmodule(key, ...extraKeys) { + return { + name: `${key}Module`, + decode(v) { return v }, + getId() { + return { + id: Object.fromEntries( + idValues[key]?.map(mod => [mod, key === mod ? `${key}_value` : `${mod}_value_from_${key}Module`]) + ) + } + }, + primaryIds: [key], + eids: { + [key]: { + source: `${key}.com`, + atype: 1, + }, + ...Object.fromEntries(extraKeys.map(extraKey => [extraKey, { + source: `${extraKey}.com`, + atype: 1, + getUidExt() { + return {provider: `${key}Module`} + } + }])) + } + } + } + + beforeEach(() => { + idValues = { + mockId1: ['mockId1'], + mockId2: ['mockId2', 'mockId3'], + mockId3: ['mockId1', 'mockId2', 'mockId3', 'mockId4'], + mockId4: ['mockId4'] + } + init(config); + + setSubmoduleRegistry([ + mockIdSubmodule('mockId1'), + mockIdSubmodule('mockId2', 'mockId3'), + mockIdSubmodule('mockId3', 'mockId1', 'mockId2', 'mockId4'), + mockIdSubmodule('mockId4') + ]); + }) + + function enrich({global = {}, bidder = {}} = {}) { + return getGlobal().getUserIdsAsync().then(() => { + enrichEids({global, bidder}); + return {global, bidder}; + }) + } + + function eidsFrom(nameFromModuleMapping) { + return Object.entries(nameFromModuleMapping).map(([key, module]) => { + const owner = `${key}Module` === module + const uid = { + id: owner ? `${key}_value` : `${key}_value_from_${module}`, + atype: 1, + }; + if (!owner) { + uid.ext = {provider: module} + } + return { + source: `${key}.com`, + uids: [uid] + } + }) + } + + function bidderEids(bidderMappings) { + return Object.fromEntries( + Object.entries(bidderMappings).map(([bidder, mapping]) => [bidder, {user: {ext: {eids: eidsFrom(mapping)}}}]) + ) + } + + it('should use lower-priority module if higher priority module cannot provide an id', () => { + idValues.mockId3 = [] + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'] + }, + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId3Module' }, + ] + } + }); + return enrich().then(({global}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId1Module' + })) + }); + }); + + it('should not choke if no id is available for a module', () => { + idValues.mockId1 = [] + config.setConfig({ + userSync: { + userIds: [ + { name: 'mockId1Module' }, + ] + } + }); + return enrich().then(({global}) => { + expect(global.user?.ext?.eids).to.not.exist; + }); + }); + + it('should add EIDs that are not bidder-restricted', () => { + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'], + mockId4: ['mockId4Module', 'mockId3Module'] + }, + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' }, + ] + } + }); + return enrich().then(({global}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId3Module', + mockId2: 'mockId2Module', + mockId3: 'mockId3Module', + mockId4: 'mockId4Module' + })); + }); + }); + + it('should separate bidder-restricted eids', () => { + config.setConfig({ + userSync: { + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA', 'bidderB'] }, + { name: 'mockId4Module' }, + ] + } + }); + return enrich().then(({global, bidder}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId4: 'mockId4Module' + })); + [bidder.bidderA, bidder.bidderB].forEach(bidderCfg => { + expect(bidderCfg.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId1Module' + })) + }) + }); + }) + + it('should exclude bidder-unrestricted IDs that conflict for some bidders', () => { + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId1Module', 'mockId3Module'], + }, + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA'] }, + { name: 'mockId3Module' }, + ] + } + }); + return enrich().then(({global, bidder}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId2: 'mockId3Module', + mockId3: 'mockId3Module', + mockId4: 'mockId3Module' + })); + expect(bidder).to.eql({}); + }); + }); + + it('should provide bidder-specific IDs, even when they conflict across bidders', () => { + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId1Module', 'mockId3Module'], + }, + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA'] }, + { name: 'mockId3Module', bidders: ['bidderB'] }, + ] + } + }); + return enrich().then(({global, bidder}) => { + expect(global.user?.ext?.eids).to.not.exist; + expect(bidder).to.eql(bidderEids({ + bidderA: { + mockId1: 'mockId1Module' + }, + bidderB: { + mockId1: 'mockId1Module', + mockId2: 'mockId3Module', + mockId3: 'mockId3Module', + mockId4: 'mockId3Module' + } + })); + }); + }); + + it('should not override pub-provided EIDS', () => { + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA', 'bidderB'] }, + { name: 'mockId4Module' }, + ] + } + }); + const globalEids = [{pub: 'provided'}]; + const bidderAEids = [{bidder: 'A'}] + const fpd = { + global: {user: {ext: {eids: globalEids}}}, + bidder: { + bidderA: { + user: {ext: {eids: bidderAEids}} + } + } + } + return enrich(fpd).then(({global, bidder}) => { + expect(global.user.ext.eids).to.eql(globalEids.concat(eidsFrom({ + mockId4: 'mockId4Module' + }))); + expect(bidder.bidderA.user.ext.eids).to.eql(bidderAEids.concat(eidsFrom({ + mockId1: 'mockId1Module' + }))); + expect(bidder.bidderB.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId1Module' + })); + }); + }) + }); }); diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 67e2939a97d..5b2f4e15129 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; -import {attachIdSystem, init, requestBidsHook, setSubmoduleRegistry} from 'modules/userId/index.js'; +import {attachIdSystem, init, startAuctionHook, setSubmoduleRegistry} from 'modules/userId/index.js'; import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; import * as storageManager from 'src/storageManager.js'; import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; @@ -176,7 +176,7 @@ describe('Zeotap ID System', function() { }); it('when a stored Zeotap ID exists it is added to bids', function(done) { - requestBidsHook(function() { + startAuctionHook(function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.IDP'); diff --git a/test/spec/ortbConverter/userId_spec.js b/test/spec/ortbConverter/userId_spec.js deleted file mode 100644 index 2084cb0cfae..00000000000 --- a/test/spec/ortbConverter/userId_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import {setOrtbUserExtEids} from '../../../modules/userId/index.js'; - -describe('pbjs - ortb user eids', () => { - it('sets user.ext.eids from request', () => { - const req = {}; - setOrtbUserExtEids(req, {}, { - bidRequests: [ - { - userIdAsEids: [{e: 'id'}] - } - ] - }); - expect(req.user.ext.eids).to.eql([{e: 'id'}]); - }); - - it('should not override eids from fpd', () => { - const req = { - user: { - ext: { - eids: [{existing: 'id'}] - } - } - }; - setOrtbUserExtEids(req, {}, { - bidRequests: [ - { - userIdAsEids: [{nw: 'id'}] - } - ] - }); - expect(req.user.ext.eids).to.eql([ - {nw: 'id'}, - {existing: 'id'}, - ]) - }) - - it('has no effect if requests have no eids', () => { - const req = {}; - setOrtbUserExtEids(req, {}, [{}]); - expect(req).to.eql({}); - }) - - it('has no effect if user.ext.eids is an empty array', () => { - const req = {}; - setOrtbUserExtEids(req, {}, { - bidRequests: [ - { - userIdAsEids: [] - } - ] - }); - expect(req).to.eql({}); - }); -}) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 558fb17dc42..0c450c7f1ae 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1916,7 +1916,7 @@ describe('Unit: Prebid Module', function () { }); it('passes ortb2 fragments to createAuction', () => { - const ortb2Fragments = {}; + const ortb2Fragments = {global: {}, bidder: {}}; pbjsModule.startAuction({ adUnits: [{ code: 'au', From 4d060914cd9eda1494d36b4d04217c3da91e751a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:22:53 -0400 Subject: [PATCH 0442/1097] Bump ws, @wdio/browserstack-service, @wdio/cli and @wdio/local-runner (#12148) Bumps [ws](https://github.com/websockets/ws) to 8.17.1 and updates ancestor dependencies [ws](https://github.com/websockets/ws), [@wdio/browserstack-service](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/wdio-browserstack-service), [@wdio/cli](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/wdio-cli) and [@wdio/local-runner](https://github.com/webdriverio/webdriverio/tree/HEAD/packages/wdio-local-runner). These dependencies need to be updated together. Updates `ws` from 8.13.0 to 8.17.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.13.0...8.17.1) Updates `@wdio/browserstack-service` from 8.39.0 to 9.0.5 - [Release notes](https://github.com/webdriverio/webdriverio/releases) - [Changelog](https://github.com/webdriverio/webdriverio/blob/main/CHANGELOG.md) - [Commits](https://github.com/webdriverio/webdriverio/commits/v9.0.5/packages/wdio-browserstack-service) Updates `@wdio/cli` from 8.39.0 to 9.0.5 - [Release notes](https://github.com/webdriverio/webdriverio/releases) - [Changelog](https://github.com/webdriverio/webdriverio/blob/main/CHANGELOG.md) - [Commits](https://github.com/webdriverio/webdriverio/commits/v9.0.5/packages/wdio-cli) Updates `@wdio/local-runner` from 8.39.0 to 9.0.5 - [Release notes](https://github.com/webdriverio/webdriverio/releases) - [Changelog](https://github.com/webdriverio/webdriverio/blob/main/CHANGELOG.md) - [Commits](https://github.com/webdriverio/webdriverio/commits/v9.0.5/packages/wdio-local-runner) --- updated-dependencies: - dependency-name: ws dependency-type: indirect - dependency-name: "@wdio/browserstack-service" dependency-type: direct:development - dependency-name: "@wdio/cli" dependency-type: direct:development - dependency-name: "@wdio/local-runner" dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 11718 +++++++++++++++++--------------------------- package.json | 6 +- 2 files changed, 4469 insertions(+), 7255 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a4071ec14d..7146239e05b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,10 +26,10 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@wdio/browserstack-service": "^8.29.0", - "@wdio/cli": "^8.29.0", + "@wdio/browserstack-service": "^9.0.5", + "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^8.29.0", - "@wdio/local-runner": "^8.29.0", + "@wdio/local-runner": "^9.0.5", "@wdio/mocha-framework": "^8.29.0", "@wdio/spec-reporter": "^8.29.0", "ajv": "6.12.3", @@ -1737,6 +1737,16 @@ "node": ">=6.9.0" } }, + "node_modules/@browserstack/ai-sdk-node": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@browserstack/ai-sdk-node/-/ai-sdk-node-1.5.8.tgz", + "integrity": "sha512-34snogSnvskHxUZYOX61ga1/oTlyXwneRtd7Epu2bEdSsRR1rMm8xXhO6DVrLsHPwPHz+ljAlwVwhcE2uKysxw==", + "dev": true, + "dependencies": { + "axios": "^1.6.2", + "uuid": "9.0.1" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1772,6 +1782,390 @@ "node": ">=16" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -2001,12 +2395,270 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@inquirer/checkbox": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.4.7.tgz", + "integrity": "sha512-5YwCySyV1UEgqzz34gNsC38eKxRBtlRDpJLlKcRtTjlYA/yDKuc1rfw+hjw+2WJxbAZtaDPsRl5Zk7J14SBoBw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.10.tgz", + "integrity": "sha512-TdESOKSVwf6+YWDz8GhS6nKscwzkIyakEzCLJ5Vh6O3Co2ClhCJ0A4MG909MUWfaWdpJm7DE45ii51/2Kat9tA==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.1.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-spinners": "^2.9.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@types/node": { + "version": "22.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.1.tgz", + "integrity": "sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@inquirer/core/node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/@inquirer/editor": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.1.22.tgz", + "integrity": "sha512-K1QwTu7GCK+nKOVRBp5HY9jt3DXOfPGPr6WRDrPImkcJRelG9UTx2cAtK1liXmibRrzJlTWOwqgWT3k2XnS62w==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.1.22.tgz", + "integrity": "sha512-wTZOBkzH+ItPuZ3ZPa9lynBsdMp6kQ9zbjVPYEtSBG7UulGjg2kQiAnUjgyG4SlntpTce5bOmXAPvE4sguXjpA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.5.tgz", + "integrity": "sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.2.9.tgz", + "integrity": "sha512-7Z6N+uzkWM7+xsE+3rJdhdG/+mQgejOVqspoW+w0AbSZnL6nq5tGMEVASaYVWbkoSzecABWwmludO2evU3d31g==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.0.10.tgz", + "integrity": "sha512-kWTxRF8zHjQOn2TJs+XttLioBih6bdc5CcosXIzZsrTY383PXI35DuhIllZKu7CdXFi2rz2BWPN9l0dPsvrQOA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.1.22.tgz", + "integrity": "sha512-5Fxt1L9vh3rAKqjYwqsjU4DZsEvY/2Gll+QkqR4yEpy6wvzLxdSgFhUcxfDAOtO4BEoTreWoznC0phagwLU5Kw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.2.4.tgz", + "integrity": "sha512-pb6w9pWrm7EfnYDgQObOurh2d2YH07+eDo3xQBsNAM2GRhliz6wFXGi1thKQ4bN6B0xDd6C3tBsjdr3obsCl3Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.0.7.tgz", + "integrity": "sha512-p1wpV+3gd1eST/o5N3yQpYEdFNCzSP0Klrl+5bfD3cTTz8BGG6nf4Z07aBW0xjlKIj1Rp0y3x/X4cZYi6TfcLw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.4.7.tgz", + "integrity": "sha512-JH7XqPEkBpNWp3gPCqWqY8ECbyMoFcCZANlL6pV9hf59qK6dGmkOlx1ydyhY+KZ0c5X74+W6Mtp+nm2QX0/MAQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.2.tgz", + "integrity": "sha512-w9qFkumYDCNyDZmNQjf/n6qQuvQ4dMC3BJesY4oF+yr0CxR5vxujflAVeIcS6U336uzi9GM0kAfZlLrZ9UTkpA==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -2024,7 +2676,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -2036,15 +2687,13 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -2062,7 +2711,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -2105,7 +2753,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "license": "MIT", "dependencies": { "jest-get-type": "^29.6.3" }, @@ -2130,7 +2777,6 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, - "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -2148,7 +2794,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -2164,7 +2809,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2181,7 +2825,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -2193,15 +2836,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@jest/types/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -2211,7 +2852,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2272,18 +2912,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2339,7 +2967,6 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -2440,22 +3067,28 @@ "node": ">=12" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@sinonjs/commons": { @@ -2499,30 +3132,6 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -2644,15 +3253,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, - "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -2662,7 +3269,6 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } @@ -2715,6 +3321,15 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", @@ -2745,12 +3360,17 @@ "@types/node": "*" } }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/supports-color": { "version": "8.1.3", @@ -2792,22 +3412,26 @@ "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, - "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -2816,8 +3440,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/yauzl": { "version": "2.10.3", @@ -2891,6 +3514,18 @@ "is-function": "^1.0.1" } }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vitest/snapshot": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", @@ -2967,90 +3602,135 @@ "optional": true }, "node_modules/@wdio/browserstack-service": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.39.0.tgz", - "integrity": "sha512-EEbmg9hBk0FAf9b2oHEL0w8aIscKPy3ms0/zSaLp+jDMuWHllpVQ83GCW9qHIJbKUzTNUlF031fRdPzq+GQaVg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.0.5.tgz", + "integrity": "sha512-pJNb9jJwPf+FEwAEnnUc6d9s6/QlvcZbh9NtjO23a/wr3HvXdzhlRHwzUV1RWboDpGsww5PFmtGcIo7GdDQL+g==", "dev": true, - "license": "MIT", "dependencies": { + "@browserstack/ai-sdk-node": "1.5.8", "@percy/appium-app": "^2.0.1", "@percy/selenium-webdriver": "^2.0.3", "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "8.38.0", - "@wdio/reporter": "8.39.0", - "@wdio/types": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/reporter": "9.0.4", + "@wdio/types": "9.0.4", "browserstack-local": "^1.5.1", "chalk": "^5.3.0", "csv-writer": "^1.6.0", "formdata-node": "5.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", - "got": "^12.6.1", - "uuid": "^9.0.0", - "webdriverio": "8.39.0", + "uuid": "^10.0.0", + "webdriverio": "9.0.5", "winston-transport": "^4.5.0", "yauzl": "^3.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "peerDependencies": { - "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dev": true, + "dependencies": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@wdio/browserstack-service/node_modules/@wdio/reporter": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.39.0.tgz", - "integrity": "sha512-XahXhmaA1okdwg4/ThHFSqy/41KywxhbtszPcTzyXB+9INaqFNHA1b1vvWs0mrD5+tTtKbg4caTcEHVJU4iv0w==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.0.4.tgz", + "integrity": "sha512-g55MiqToKEZ+L5quk7Ddk3m1fKgh2U2rq3zLG8bZUYU+3KFglfRC/Ld5yTso8S1tG4EDgl5HKSN5Bl8I5gncbg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "diff": "^5.0.0", "object-inspect": "^1.12.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/browserstack-service/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/browserstack-service/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, - "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@wdio/browserstack-service/node_modules/archiver": { @@ -3058,7 +3738,6 @@ "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, - "license": "MIT", "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", @@ -3077,7 +3756,6 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, - "license": "MIT", "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", @@ -3092,18 +3770,16 @@ } }, "node_modules/@wdio/browserstack-service/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true }, "node_modules/@wdio/browserstack-service/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3127,7 +3803,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -3138,7 +3813,6 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -3155,33 +3829,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, "node_modules/@wdio/browserstack-service/node_modules/compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, - "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", @@ -3198,7 +3850,6 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, - "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" @@ -3207,134 +3858,41 @@ "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", + "node_modules/@wdio/browserstack-service/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=16.0.0" } }, "node_modules/@wdio/browserstack-service/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause", "optional": true, "peer": true }, - "node_modules/@wdio/browserstack-service/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/browserstack-service/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wdio/browserstack-service/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -3342,25 +3900,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "node_modules/@wdio/browserstack-service/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "license": "ISC", "engines": { "node": ">=12" } @@ -3370,7 +3914,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3381,219 +3924,48 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/browserstack-service/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@wdio/browserstack-service/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@wdio/browserstack-service/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, - "license": "MIT", "optional": true, "peer": true, "dependencies": { - "ms": "2.1.2" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wdio/browserstack-service/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, - "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -3605,12 +3977,23 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@wdio/browserstack-service/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@wdio/browserstack-service/node_modules/serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^2.12.2" }, @@ -3626,60 +4009,22 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/browserstack-service/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/tar-fs/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=6" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/@wdio/browserstack-service/node_modules/type-fest": { @@ -3687,7 +4032,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -3695,246 +4039,104 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", + "node_modules/@wdio/browserstack-service/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "dev": true, "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "*" + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/@wdio/browserstack-service/node_modules/webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "peerDependencies": { - "devtools": "^8.14.0" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "devtools": { + "puppeteer-core": { "optional": true } } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/@wdio/browserstack-service/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, + "optional": true, + "peer": true, "engines": { - "node": ">=16.3.0" + "node": ">=10.0.0" }, "peerDependencies": { - "typescript": ">= 4.7.4" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "typescript": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { "optional": true } } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "node_modules/@wdio/browserstack-service/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "mitt": "3.0.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@wdio/browserstack-service/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" + "engines": { + "node": ">=12" } }, "node_modules/@wdio/browserstack-service/node_modules/zip-stream": { @@ -3942,7 +4144,6 @@ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, - "license": "MIT", "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", @@ -3953,80 +4154,127 @@ } }, "node_modules/@wdio/cli": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.39.0.tgz", - "integrity": "sha512-kAd+8TYjJWRN6gZaRGiSu6Yj3k4+ULRt2NWAgNtGhnr0/Rwlr3j9bjAIhXGL574VqrH+rSnrevDdeGuLcZa1xg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.0.5.tgz", + "integrity": "sha512-D/QBlodNIdxuNpUPbuhk+mLidVLT+Vsb0Q0Fd4lh57Jy8kw5nJ56ykqiI0WE1oI0i+XtyJ7iFOPUztuCjjhX3A==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.1", "@vitest/snapshot": "^1.2.1", - "@wdio/config": "8.39.0", - "@wdio/globals": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", - "cli-spinners": "^2.9.0", + "cli-spinners": "^3.0.0", "dotenv": "^16.3.1", "ejs": "^3.1.9", - "execa": "^8.0.1", + "execa": "^9.2.0", "import-meta-resolve": "^4.0.0", - "inquirer": "9.2.12", + "inquirer": "^10.1.8", "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "read-pkg-up": "10.0.0", + "read-pkg-up": "^10.0.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.39.0", + "tsx": "^4.7.2", + "webdriverio": "9.0.5", "yargs": "^17.7.2" }, "bin": { "wdio": "bin/wdio.js" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/cli/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@wdio/cli/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@wdio/cli/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/cli/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, - "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/cli/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@wdio/cli/node_modules/archiver": { @@ -4034,7 +4282,6 @@ "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, - "license": "MIT", "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", @@ -4053,7 +4300,6 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, - "license": "MIT", "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", @@ -4072,7 +4318,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -4081,18 +4326,16 @@ } }, "node_modules/@wdio/cli/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true }, "node_modules/@wdio/cli/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -4116,7 +4359,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -4127,7 +4369,6 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -4144,33 +4385,11 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, "node_modules/@wdio/cli/node_modules/compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, - "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", @@ -4187,7 +4406,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -4200,7 +4418,6 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, - "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" @@ -4209,208 +4426,97 @@ "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" - } - }, - "node_modules/@wdio/cli/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", + "node_modules/@wdio/cli/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=16.0.0" } }, "node_modules/@wdio/cli/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause", "optional": true, "peer": true }, - "node_modules/@wdio/cli/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/cli/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.1.tgz", + "integrity": "sha512-gdhefCCNy/8tpH/2+ajP9IQc14vXchNdd0weyzSJEFURhRMGncQ+zKFxwjAufIewPEJm9BPOaJnvg2UtlH2gPQ==", "dev": true, "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^5.2.0", + "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" }, "engines": { - "node": ">=16.17" + "node": "^18.19.0 || >=20.5.0" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/@wdio/cli/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/cli/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/cli/node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wdio/cli/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "node_modules/@wdio/cli/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/@wdio/cli/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@wdio/cli/node_modules/minimatch": { @@ -4418,7 +4524,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4429,36 +4534,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@wdio/cli/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@wdio/cli/node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -4474,16 +4549,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/@wdio/cli/node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4501,202 +4573,80 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "node_modules/@wdio/cli/node_modules/pretty-ms": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", + "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "2.1.2" + "parse-ms": "^4.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "node": ">=18" }, - "engines": { - "node": ">= 14" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "node_modules/@wdio/cli/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.0.2", - "debug": "4" + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/cli/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@wdio/cli/node_modules/readable-stream": { @@ -4704,7 +4654,6 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, - "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -4716,12 +4665,23 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@wdio/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@wdio/cli/node_modules/serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^2.12.2" }, @@ -4749,60 +4709,22 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/cli/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/@wdio/cli/node_modules/tar-fs/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/cli/node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/@wdio/cli/node_modules/type-fest": { @@ -4810,7 +4732,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -4818,232 +4739,59 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/@wdio/cli/node_modules/webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": ">=16.3.0" + "node": ">=18" }, "peerDependencies": { - "typescript": ">= 4.7.4" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "typescript": { + "puppeteer-core": { "optional": true } } }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@wdio/cli/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -5083,7 +4831,6 @@ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, - "license": "MIT", "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", @@ -5121,114 +4868,330 @@ } }, "node_modules/@wdio/config": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.39.0.tgz", - "integrity": "sha512-yNuGPMPibY91s936gnJCHWlStvIyDrwLwGfLC/NCdTin4F7HL4Gp5iJnHWkJFty1/DfFi8jjoIUBNLM8HEez+A==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.5.tgz", + "integrity": "sha512-+dxUU2SLXLkqQhVU/wauU1VgqEKIFubOyUb6B0ueAMpM1aolc62zhE9D9rrQYbjkPOM7nFsjuuGR5+9+zaoZ6g==", "dev": true, - "license": "MIT", "dependencies": { - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.0.0", + "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/config/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@wdio/config/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@wdio/config/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/config/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, - "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/config/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/config/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/config/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@wdio/config/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/config/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wdio/config/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/config/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@wdio/config/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/@wdio/config/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/@wdio/globals": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.39.0.tgz", - "integrity": "sha512-qZo6JjRCIOtdvba6fdSqj6b91TnWXD6rmamyud93FTqbcspnhBvr8lmgOs5wnslTKeeTTigCjpsT310b4/AyHA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.0.5.tgz", + "integrity": "sha512-ZkopKj1qEDNKuF1a87JTLfTKCBFgCHLUns5ob5D1oEmMFp0NwB89HHGBWgtuJpCUmxJAbf4rCKglVeKhB9rY7A==", "dev": true, - "license": "MIT", "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "optionalDependencies": { - "expect-webdriverio": "^4.11.2", - "webdriverio": "8.39.0" + "expect-webdriverio": "^5.0.1", + "webdriverio": "9.0.5" + } + }, + "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@wdio/globals/node_modules/@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "optional": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/globals/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "optional": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@wdio/globals/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/globals/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/globals/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@wdio/globals/node_modules/archiver": { @@ -5236,7 +5199,6 @@ "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "archiver-utils": "^5.0.2", @@ -5256,7 +5218,6 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "glob": "^10.0.0", @@ -5272,11 +5233,10 @@ } }, "node_modules/@wdio/globals/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true, - "license": "MIT", "optional": true }, "node_modules/@wdio/globals/node_modules/brace-expansion": { @@ -5284,7 +5244,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "balanced-match": "^1.0.0" @@ -5309,7 +5268,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "optional": true, "dependencies": { "base64-js": "^1.3.1", @@ -5321,31 +5279,22 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=8.0.0" } }, - "node_modules/@wdio/globals/node_modules/chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", + "node_modules/@wdio/globals/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "license": "Apache-2.0", "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, "engines": { - "node": ">=12.13.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@wdio/globals/node_modules/compress-commons": { @@ -5353,7 +5302,6 @@ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "crc-32": "^1.2.0", @@ -5371,7 +5319,6 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "crc-32": "^1.2.0", @@ -5381,134 +5328,75 @@ "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" - } - }, - "node_modules/@wdio/globals/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@wdio/globals/node_modules/devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", + "node_modules/@wdio/globals/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=16.0.0" } }, "node_modules/@wdio/globals/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause", "optional": true, "peer": true }, - "node_modules/@wdio/globals/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/globals/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/globals/node_modules/expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/globals/node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" + "node": ">=18 || >=20 || >=22" }, - "engines": { - "node": ">=6.0" + "peerDependencies": { + "@wdio/globals": "^9.0.0-alpha.350", + "@wdio/logger": "^9.0.0-alpha.350", + "webdriverio": "^9.0.0-alpha.350" }, "peerDependenciesMeta": { - "supports-color": { - "optional": true + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false } } }, - "node_modules/@wdio/globals/node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "node_modules/@wdio/globals/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "license": "MIT", "optional": true, - "peer": true + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } }, "node_modules/@wdio/globals/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -5517,25 +5405,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/globals/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "node_modules/@wdio/globals/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "license": "ISC", "optional": true, "engines": { "node": ">=12" @@ -5546,7 +5420,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -5558,226 +5431,49 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/globals/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@wdio/globals/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@wdio/globals/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@wdio/globals/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, - "license": "MIT", "optional": true, "peer": true, "dependencies": { - "ms": "2.1.2" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wdio/globals/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "abort-controller": "^3.0.0", @@ -5790,12 +5486,24 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@wdio/globals/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@wdio/globals/node_modules/serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "type-fest": "^2.12.2" @@ -5812,61 +5520,24 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/globals/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/@wdio/globals/node_modules/tar-fs/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/globals/node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=6" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/@wdio/globals/node_modules/type-fest": { @@ -5874,7 +5545,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "license": "(MIT OR CC0-1.0)", "optional": true, "engines": { "node": ">=12.20" @@ -5883,224 +5553,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/globals/node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/@wdio/globals/node_modules/webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": ">=16.3.0" + "node": ">=18" }, "peerDependencies": { - "typescript": ">= 4.7.4" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "typescript": { + "puppeteer-core": { "optional": true } } }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/@wdio/globals/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, - "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -6118,11 +5624,10 @@ } }, "node_modules/@wdio/globals/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "cliui": "^8.0.1", @@ -6142,7 +5647,6 @@ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { "archiver-utils": "^5.0.0", @@ -6154,36 +5658,61 @@ } }, "node_modules/@wdio/local-runner": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.39.0.tgz", - "integrity": "sha512-TSGJVVWqshH7IO13OKw7G/364q3FczZDEh4h6bYe+GAs91KpZrEhZanyALgjh5F3crWtlffX+GA2HUwpi8X0sA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.0.5.tgz", + "integrity": "sha512-BFZ/e7z1s2cYsix1evijydaDn0YffeIHjPsMoa9b+zhW8BoZfTEDGKblYvzRgjUDD4elXs+YRZpA6EhjcGJTxQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0", - "@wdio/logger": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/runner": "8.39.0", - "@wdio/types": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/runner": "9.0.5", + "@wdio/types": "9.0.4", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@wdio/local-runner/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/local-runner/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@wdio/logger": { @@ -6231,22 +5760,21 @@ } }, "node_modules/@wdio/protocols": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.38.0.tgz", - "integrity": "sha512-7BPi7aXwUtnXZPeWJRmnCNFjyDvGrXlBmN9D4Pi58nILkyjVRQKEY9/qv/pcdyB0cvmIvw++Kl/1Lg+RxG++UA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.4.tgz", + "integrity": "sha512-T9v8Jkp94NxLLil5J7uJ/+YHk5/7fhOggzGcN+LvuCHS6jbJFZ/11c4SUEuXw27Yqk01fFXPBbF6TwcTTOuW/Q==", "dev": true }, "node_modules/@wdio/repl": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz", - "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.4.tgz", + "integrity": "sha512-5Bc5ARjWA7t6MZNnVJ9WvO1iDsy6iOsrSDWiP7APWAdaF/SJCP3SFE2c+PdQJpJWhr2Kk0fRGuyDM+GdsmZhwg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/reporter": { @@ -6266,64 +5794,124 @@ } }, "node_modules/@wdio/runner": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.39.0.tgz", - "integrity": "sha512-M1ixrrCtuwxHVzwsOKGMWBZCteafV0ztoS9+evMWGQtj0ZEsmhjAhWR3n2nZftt24vWOs+eNLGe2p+IO9Sm9bA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.0.5.tgz", + "integrity": "sha512-qZF7k3BeQaM7pQRwIvedbfaC7xBU1xRY+wFkp44U/wvYZOOrqWiwv/Synk1iCFkOdxl/b+Gqp68dDmS9BrVDmw==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.11.28", - "@wdio/config": "8.39.0", - "@wdio/globals": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "deepmerge-ts": "^5.1.0", - "expect-webdriverio": "^4.12.0", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.0.1", "gaze": "^1.1.3", - "webdriver": "8.39.0", - "webdriverio": "8.39.0" + "webdriver": "9.0.5", + "webdriverio": "9.0.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/runner/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@wdio/runner/node_modules/@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/runner/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@wdio/runner/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/@wdio/runner/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, - "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/@wdio/runner/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@wdio/runner/node_modules/archiver": { @@ -6331,7 +5919,6 @@ "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, - "license": "MIT", "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", @@ -6350,7 +5937,6 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, - "license": "MIT", "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", @@ -6365,18 +5951,16 @@ } }, "node_modules/@wdio/runner/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true }, "node_modules/@wdio/runner/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -6400,7 +5984,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -6411,30 +5994,20 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8.0.0" } }, - "node_modules/@wdio/runner/node_modules/chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", + "node_modules/@wdio/runner/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, "engines": { - "node": ">=12.13.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@wdio/runner/node_modules/compress-commons": { @@ -6442,7 +6015,6 @@ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, - "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", @@ -6459,7 +6031,6 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, - "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" @@ -6468,134 +6039,72 @@ "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "node_modules/@wdio/runner/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@wdio/runner/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@wdio/runner/node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } + "peer": true }, - "node_modules/@wdio/runner/node_modules/devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", + "node_modules/@wdio/runner/node_modules/expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" }, "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/@wdio/runner/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true - }, - "node_modules/@wdio/runner/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" + "node": ">=18 || >=20 || >=22" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/runner/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "peerDependencies": { + "@wdio/globals": "^9.0.0-alpha.350", + "@wdio/logger": "^9.0.0-alpha.350", + "webdriverio": "^9.0.0-alpha.350" }, - "engines": { - "node": ">= 6" + "peerDependenciesMeta": { + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false + } } }, - "node_modules/@wdio/runner/node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "node_modules/@wdio/runner/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "ms": "2.1.2" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wdio/runner/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -6603,25 +6112,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "node_modules/@wdio/runner/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "license": "ISC", "engines": { "node": ">=12" } @@ -6631,7 +6126,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6642,219 +6136,72 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/runner/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@wdio/runner/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@wdio/runner/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/runner/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wdio/runner/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, - "license": "MIT", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -6866,12 +6213,23 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@wdio/runner/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@wdio/runner/node_modules/serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^2.12.2" }, @@ -6887,60 +6245,22 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/@wdio/runner/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/@wdio/runner/node_modules/tar-fs/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/runner/node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=6" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/@wdio/runner/node_modules/type-fest": { @@ -6948,7 +6268,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -6956,213 +6275,59 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, "node_modules/@wdio/runner/node_modules/webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" + "node": ">=18" }, "peerDependencies": { - "typescript": ">= 4.7.4" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "typescript": { + "puppeteer-core": { "optional": true } } }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, "node_modules/@wdio/runner/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, - "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -7180,11 +6345,10 @@ } }, "node_modules/@wdio/runner/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -7203,7 +6367,6 @@ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, - "license": "MIT", "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", @@ -7445,9 +6608,9 @@ "dev": true }, "node_modules/@zip.js/zip.js": { - "version": "2.7.45", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.45.tgz", - "integrity": "sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow==", + "version": "2.7.48", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.48.tgz", + "integrity": "sha512-J7cliimZ2snAbr0IhLx2U8BwfA1pKucahKzTpFtYq4hEgKxwvFJcIjCIVNPwQpfVab7iVP+AKmoH1gidBlyhiQ==", "dev": true, "engines": { "bun": ">=0.7.0", @@ -7466,7 +6629,6 @@ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, - "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" }, @@ -8284,6 +7446,31 @@ "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, + "node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -9322,45 +8509,6 @@ "node": ">=0.10.0" } }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -9529,6 +8677,60 @@ "node": "*" } }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -9599,15 +8801,16 @@ } }, "node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", "dev": true, - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" @@ -9624,7 +8827,6 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], - "license": "MIT", "engines": { "node": ">=8" } @@ -9691,12 +8893,12 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.1.0.tgz", + "integrity": "sha512-2MH0N34TpDAs9ROPVkZJfBWFoYfv4zfkJF14PBHY4v/qRY75SLcm4WaEKNCLScsXieosa/tY/+slJ+BDswJxHQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=18.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10548,9 +9750,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -10738,6 +9940,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "optional": true, "dependencies": { "clone": "^1.0.2" }, @@ -10750,6 +9953,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "optional": true, "engines": { "node": ">=0.8" } @@ -11547,7 +10751,6 @@ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -12181,16 +11384,17 @@ } }, "node_modules/edgedriver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.0.tgz", - "integrity": "sha512-IeJXEczG+DNYBIa9gFgVYTqrawlxmc9SUqUsWU2E98jOsO/amA7wzabKOS8Bwgr/3xWoyXCJ6yGFrbFKrilyyQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.1.tgz", + "integrity": "sha512-3Ve9cd5ziLByUdigw6zovVeWJjVs8QHVmqOB0sJ0WNeVPcwf4p18GnxMmVvlFmYRloUwf5suNuorea4QzwBIOA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@wdio/logger": "^8.28.0", - "@zip.js/zip.js": "^2.7.44", + "@wdio/logger": "^8.38.0", + "@zip.js/zip.js": "^2.7.48", "decamelize": "^6.0.0", "edge-paths": "^3.0.5", + "fast-xml-parser": "^4.4.1", "node-fetch": "^3.3.2", "which": "^4.0.0" }, @@ -12246,6 +11450,31 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -12613,6 +11842,45 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -13350,1245 +12618,260 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/event-stream/node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/expect-webdriverio": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.15.1.tgz", - "integrity": "sha512-xtBSidt7Whs1fsUC+utxVzfmkmaStXWW17b+BcMCiCltx0Yku6l7BTv1Y14DEKX8L6rttaDQobYyRtBKbi4ssg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/snapshot": "^1.2.2", - "expect": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "lodash.isequal": "^4.5.0" - }, - "engines": { - "node": ">=16 || >=18 || >=20" - }, - "optionalDependencies": { - "@wdio/globals": "^8.29.3", - "@wdio/logger": "^8.28.0", - "webdriverio": "^8.29.3" - } - }, - "node_modules/expect-webdriverio/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/expect-webdriverio/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", - "split2": "^4.2.0", - "wait-port": "^1.0.4" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/expect-webdriverio/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/expect-webdriverio/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/expect-webdriverio/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/expect-webdriverio/node_modules/chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/expect-webdriverio/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" - } - }, - "node_modules/expect-webdriverio/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expect-webdriverio/node_modules/devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/expect-webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true - }, - "node_modules/expect-webdriverio/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/expect-webdriverio/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/expect-webdriverio/node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/expect-webdriverio/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/expect-webdriverio/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/expect-webdriverio/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "optional": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/expect-webdriverio/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/expect-webdriverio/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/expect-webdriverio/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/expect-webdriverio/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/expect-webdriverio/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/expect-webdriverio/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "safe-buffer": "~5.2.0" + "d": "1", + "es5-ext": "~0.10.14" } }, - "node_modules/expect-webdriverio/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" } }, - "node_modules/expect-webdriverio/node_modules/tar-fs/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/event-stream/node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=6" } }, - "node_modules/expect-webdriverio/node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "engines": { "node": ">=6" } }, - "node_modules/expect-webdriverio/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, - "engines": { - "node": ">=12.20" + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4.8" } }, - "node_modules/expect-webdriverio/node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", + "node_modules/execa/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "optional": true, - "peer": true, "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" + "shebang-regex": "^1.0.0" }, "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "license": "Apache-2.0", - "optional": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "isexe": "^2.0.0" }, "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "which": "bin/which" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", "dev": true, - "license": "Apache-2.0", - "optional": true, "dependencies": { - "mitt": "3.0.0" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, - "peerDependencies": { - "devtools-protocol": "*" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "node-fetch": "^2.6.12" + "ms": "2.0.0" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "ms": "2.1.2" + "is-descriptor": "^0.1.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, - "license": "Apache-2.0", - "optional": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "is-extendable": "^0.1.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/expect-webdriverio/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "dev": true, - "license": "MIT", - "optional": true, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "homedir-polyfill": "^1.0.1" }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/express": { @@ -14858,6 +13141,28 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -14918,28 +13223,15 @@ } }, "node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" + "is-unicode-supported": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -15214,11 +13506,10 @@ "dev": true }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, - "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -15235,7 +13526,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC", "engines": { "node": ">=14" }, @@ -15272,15 +13562,6 @@ "node": ">= 0.12" } }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true, - "engines": { - "node": ">= 14.17" - } - }, "node_modules/formdata-node": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz", @@ -15550,7 +13831,6 @@ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", "dev": true, - "license": "MIT", "dependencies": { "globule": "^1.0.0" }, @@ -15714,6 +13994,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -15832,11 +14124,10 @@ "dev": true }, "node_modules/glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -15848,9 +14139,6 @@ "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -16181,7 +14469,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -16191,7 +14478,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -16301,7 +14587,6 @@ "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", "dev": true, - "license": "MIT", "dependencies": { "glob": "~7.1.1", "lodash": "^4.17.21", @@ -16317,7 +14602,6 @@ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -16338,7 +14622,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -16369,43 +14652,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -18666,6 +16912,31 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/htmlfy": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.2.1.tgz", + "integrity": "sha512-HoomFHQ3av1uhq+7FxJTq4Ns0clAD+tGbQNrSd0WFY3UAjjUk6G3LaWEqdgmIXYkY4pexZiyZ3ykZJhQlM0J5A==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -18747,19 +17018,6 @@ "npm": ">=1.3.7" } }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -18774,12 +17032,12 @@ } }, "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true, "engines": { - "node": ">=16.17.0" + "node": ">=18.18.0" } }, "node_modules/iconv-lite": { @@ -18826,8 +17084,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -18905,53 +17162,21 @@ } }, "node_modules/inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-10.1.8.tgz", + "integrity": "sha512-syxGpOzLyqVeZi1KDBjRTnCn5PiGWySGHP0BbqXbqsEK0ckkZk3egAepEWslUjZXj0rhkUapVXM/IpADWe4D6w==", "dev": true, "dependencies": { - "@ljharb/through": "^2.3.11", + "@inquirer/prompts": "^5.3.8", + "@inquirer/type": "^1.5.2", + "@types/mute-stream": "^0.0.4", "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", + "mute-stream": "^1.0.0", "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" + "rxjs": "^7.8.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/internal-slot": { @@ -19334,15 +17559,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -19586,12 +17802,12 @@ } }, "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -19942,17 +18158,13 @@ } }, "node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -20059,7 +18271,6 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -20075,7 +18286,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -20091,7 +18301,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20108,7 +18317,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -20120,15 +18328,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jest-diff/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -20138,7 +18344,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -20151,7 +18356,6 @@ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -20161,7 +18365,6 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -20177,7 +18380,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -20193,7 +18395,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20210,7 +18411,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -20222,15 +18422,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jest-matcher-utils/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -20240,7 +18438,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -20253,7 +18450,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -20274,7 +18470,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -20290,7 +18485,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20307,7 +18501,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -20319,15 +18512,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jest-message-util/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -20337,7 +18528,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -20351,7 +18541,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -20361,7 +18550,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -20374,7 +18562,6 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, - "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -20392,7 +18579,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -20408,7 +18594,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -20425,7 +18610,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -20437,15 +18621,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/jest-util/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -20455,7 +18637,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -20632,7 +18813,6 @@ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -21297,19 +19477,6 @@ "@babel/traverse": "^7.10.5" } }, - "node_modules/ky": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", - "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, "node_modules/last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -21386,7 +19553,6 @@ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "dev": true, - "license": "MIT", "dependencies": { "immediate": "~3.0.5" } @@ -21736,8 +19902,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.isobject": { "version": "3.0.2", @@ -21942,18 +20107,6 @@ "get-func-name": "^2.0.1" } }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -23262,18 +21415,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -23309,17 +21450,17 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, - "license": "MIT" + "optional": true, + "peer": true }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -24231,18 +22372,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -24628,139 +22757,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -24800,15 +22796,6 @@ "node": ">=0.10.0" } }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -24924,15 +22911,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, - "license": "BlueOak-1.0.0" + "dev": true }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" + "dev": true }, "node_modules/parent-module": { "version": "1.0.1", @@ -25055,6 +23040,55 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -25136,7 +23170,6 @@ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -25149,14 +23182,10 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/path-to-regexp": { "version": "0.1.7", @@ -26549,6 +24578,15 @@ "node": ">= 0.10" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -26556,21 +24594,6 @@ "deprecated": "https://github.com/lydell/resolve-url#deprecated", "dev": true }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/resq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", @@ -27735,7 +25758,6 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -27748,7 +25770,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -27946,7 +25967,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -27961,7 +25981,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -28065,7 +26084,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -28113,12 +26131,12 @@ } }, "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -28136,6 +26154,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -28626,6 +26650,15 @@ "ms": "^2.1.1" } }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -28866,6 +26899,25 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tsx": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz", + "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==", + "dev": true, + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -29141,6 +27193,15 @@ "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", "dev": true }, + "node_modules/undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -29541,6 +27602,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -30160,6 +28227,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "optional": true, "dependencies": { "defaults": "^1.0.3" } @@ -30184,64 +28252,214 @@ } }, "node_modules/webdriver": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.39.0.tgz", - "integrity": "sha512-Kc3+SfiH4ufyrIht683VT2vnJocx0pfH8rYdyPvEh1b2OYewtFTHK36k9rBDHZiBmk6jcSXs4M2xeFgOuon9Lg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.5.tgz", + "integrity": "sha512-+xkdfbmG1IZrXxiPwab450Xuh9QClOcxTJ6tnde0rzxlPxdUqZqzwuMtM+VXZybxF4yCLrJWbeT0BpwJFAz1nA==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "deepmerge-ts": "^5.1.0", - "got": "^12.6.1", - "ky": "^0.33.0", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", "ws": "^8.8.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/webdriver/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webdriver/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/webdriver/node_modules/@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "^20.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "node_modules/webdriver/node_modules/@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, - "license": "MIT", "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" + } + }, + "node_modules/webdriver/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webdriver/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webdriver/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/webdriver/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webdriver/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/webdriver/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webdriver/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webdriver/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/webdriver/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, "node_modules/webdriverio": { @@ -31099,6 +29317,39 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -31252,7 +29503,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -31270,7 +29520,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -31286,7 +29535,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -31298,15 +29546,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -31510,6 +29756,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", @@ -31580,6 +29850,17 @@ "node": ">= 6" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -32680,6 +30961,16 @@ "to-fast-properties": "^2.0.0" } }, + "@browserstack/ai-sdk-node": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@browserstack/ai-sdk-node/-/ai-sdk-node-1.5.8.tgz", + "integrity": "sha512-34snogSnvskHxUZYOX61ga1/oTlyXwneRtd7Epu2bEdSsRR1rMm8xXhO6DVrLsHPwPHz+ljAlwVwhcE2uKysxw==", + "dev": true, + "requires": { + "axios": "^1.6.2", + "uuid": "9.0.1" + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -32706,6 +30997,174 @@ "jsdoc-type-pratt-parser": "~4.0.0" } }, + "@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -32883,6 +31342,210 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@inquirer/checkbox": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.4.7.tgz", + "integrity": "sha512-5YwCySyV1UEgqzz34gNsC38eKxRBtlRDpJLlKcRtTjlYA/yDKuc1rfw+hjw+2WJxbAZtaDPsRl5Zk7J14SBoBw==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + } + }, + "@inquirer/core": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.10.tgz", + "integrity": "sha512-TdESOKSVwf6+YWDz8GhS6nKscwzkIyakEzCLJ5Vh6O3Co2ClhCJ0A4MG909MUWfaWdpJm7DE45ii51/2Kat9tA==", + "dev": true, + "requires": { + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.1.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-spinners": "^2.9.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "dependencies": { + "@types/node": { + "version": "22.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.1.tgz", + "integrity": "sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==", + "dev": true, + "requires": { + "undici-types": "~6.19.2" + } + }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + } + } + }, + "@inquirer/editor": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.1.22.tgz", + "integrity": "sha512-K1QwTu7GCK+nKOVRBp5HY9jt3DXOfPGPr6WRDrPImkcJRelG9UTx2cAtK1liXmibRrzJlTWOwqgWT3k2XnS62w==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "external-editor": "^3.1.0" + } + }, + "@inquirer/expand": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.1.22.tgz", + "integrity": "sha512-wTZOBkzH+ItPuZ3ZPa9lynBsdMp6kQ9zbjVPYEtSBG7UulGjg2kQiAnUjgyG4SlntpTce5bOmXAPvE4sguXjpA==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/figures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.5.tgz", + "integrity": "sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA==", + "dev": true + }, + "@inquirer/input": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.2.9.tgz", + "integrity": "sha512-7Z6N+uzkWM7+xsE+3rJdhdG/+mQgejOVqspoW+w0AbSZnL6nq5tGMEVASaYVWbkoSzecABWwmludO2evU3d31g==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + } + }, + "@inquirer/number": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.0.10.tgz", + "integrity": "sha512-kWTxRF8zHjQOn2TJs+XttLioBih6bdc5CcosXIzZsrTY383PXI35DuhIllZKu7CdXFi2rz2BWPN9l0dPsvrQOA==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + } + }, + "@inquirer/password": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.1.22.tgz", + "integrity": "sha512-5Fxt1L9vh3rAKqjYwqsjU4DZsEvY/2Gll+QkqR4yEpy6wvzLxdSgFhUcxfDAOtO4BEoTreWoznC0phagwLU5Kw==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2" + } + }, + "@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "requires": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + } + }, + "@inquirer/rawlist": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.2.4.tgz", + "integrity": "sha512-pb6w9pWrm7EfnYDgQObOurh2d2YH07+eDo3xQBsNAM2GRhliz6wFXGi1thKQ4bN6B0xDd6C3tBsjdr3obsCl3Q==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/search": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.0.7.tgz", + "integrity": "sha512-p1wpV+3gd1eST/o5N3yQpYEdFNCzSP0Klrl+5bfD3cTTz8BGG6nf4Z07aBW0xjlKIj1Rp0y3x/X4cZYi6TfcLw==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/select": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.4.7.tgz", + "integrity": "sha512-JH7XqPEkBpNWp3gPCqWqY8ECbyMoFcCZANlL6pV9hf59qK6dGmkOlx1ydyhY+KZ0c5X74+W6Mtp+nm2QX0/MAQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/type": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.2.tgz", + "integrity": "sha512-w9qFkumYDCNyDZmNQjf/n6qQuvQ4dMC3BJesY4oF+yr0CxR5vxujflAVeIcS6U336uzi9GM0kAfZlLrZ9UTkpA==", + "dev": true, + "requires": { + "mute-stream": "^1.0.0" + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -33079,15 +31742,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7" - } - }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -33200,16 +31854,22 @@ } } }, + "@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true }, "@sinonjs/commons": { @@ -33253,23 +31913,6 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, - "@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.1" - } - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "optional": true, - "peer": true - }, "@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -33459,6 +32102,15 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, + "@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "20.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", @@ -33489,6 +32141,12 @@ "@types/node": "*" } }, + "@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -33535,19 +32193,25 @@ "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true }, + "@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "dev": true, "requires": { "@types/node": "*" } }, "@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -33613,6 +32277,15 @@ "is-function": "^1.0.1" } }, + "@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "requires": { + "tinyrainbow": "^1.2.0" + } + }, "@vitest/snapshot": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", @@ -33686,71 +32359,108 @@ "optional": true }, "@wdio/browserstack-service": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.39.0.tgz", - "integrity": "sha512-EEbmg9hBk0FAf9b2oHEL0w8aIscKPy3ms0/zSaLp+jDMuWHllpVQ83GCW9qHIJbKUzTNUlF031fRdPzq+GQaVg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.0.5.tgz", + "integrity": "sha512-pJNb9jJwPf+FEwAEnnUc6d9s6/QlvcZbh9NtjO23a/wr3HvXdzhlRHwzUV1RWboDpGsww5PFmtGcIo7GdDQL+g==", "dev": true, "requires": { + "@browserstack/ai-sdk-node": "1.5.8", "@percy/appium-app": "^2.0.1", "@percy/selenium-webdriver": "^2.0.3", "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "8.38.0", - "@wdio/reporter": "8.39.0", - "@wdio/types": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/reporter": "9.0.4", + "@wdio/types": "9.0.4", "browserstack-local": "^1.5.1", "chalk": "^5.3.0", "csv-writer": "^1.6.0", "formdata-node": "5.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", - "got": "^12.6.1", - "uuid": "^9.0.0", - "webdriverio": "8.39.0", + "uuid": "^10.0.0", + "webdriverio": "9.0.5", "winston-transport": "^4.5.0", "yauzl": "^3.0.0" }, "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dev": true, + "requires": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/reporter": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.39.0.tgz", - "integrity": "sha512-XahXhmaA1okdwg4/ThHFSqy/41KywxhbtszPcTzyXB+9INaqFNHA1b1vvWs0mrD5+tTtKbg4caTcEHVJU4iv0w==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.0.4.tgz", + "integrity": "sha512-g55MiqToKEZ+L5quk7Ddk3m1fKgh2U2rq3zLG8bZUYU+3KFglfRC/Ld5yTso8S1tG4EDgl5HKSN5Bl8I5gncbg==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "diff": "^5.0.0", "object-inspect": "^1.12.0" } }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" } }, "archiver": { @@ -33784,9 +32494,9 @@ } }, "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, "brace-expansion": { @@ -33820,20 +32530,6 @@ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, "compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", @@ -33857,100 +32553,28 @@ "readable-stream": "^4.0.0" } }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - } + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, "optional": true, "peer": true }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@tootallnate/once": "2", - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } } }, "is-stream": { @@ -33959,18 +32583,6 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -33986,139 +32598,35 @@ "brace-expansion": "^2.0.1" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "dependencies": { - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "socks-proxy-agent": "^8.0.2" } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" } }, "readable-stream": { @@ -34134,6 +32642,12 @@ "string_decoder": "^1.3.0" } }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, "serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", @@ -34153,47 +32667,15 @@ } }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "optional": true, - "peer": true, "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } + "tar-stream": "^3.1.5" } }, "type-fest": { @@ -34202,147 +32684,60 @@ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "optional": true, - "peer": true + "uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true }, "webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": true, "requires": {} }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -34368,65 +32763,103 @@ } }, "@wdio/cli": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.39.0.tgz", - "integrity": "sha512-kAd+8TYjJWRN6gZaRGiSu6Yj3k4+ULRt2NWAgNtGhnr0/Rwlr3j9bjAIhXGL574VqrH+rSnrevDdeGuLcZa1xg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.0.5.tgz", + "integrity": "sha512-D/QBlodNIdxuNpUPbuhk+mLidVLT+Vsb0Q0Fd4lh57Jy8kw5nJ56ykqiI0WE1oI0i+XtyJ7iFOPUztuCjjhX3A==", "dev": true, "requires": { "@types/node": "^20.1.1", "@vitest/snapshot": "^1.2.1", - "@wdio/config": "8.39.0", - "@wdio/globals": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", - "cli-spinners": "^2.9.0", + "cli-spinners": "^3.0.0", "dotenv": "^16.3.1", "ejs": "^3.1.9", - "execa": "^8.0.1", + "execa": "^9.2.0", "import-meta-resolve": "^4.0.0", - "inquirer": "9.2.12", + "inquirer": "^10.1.8", "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "read-pkg-up": "10.0.0", + "read-pkg-up": "^10.0.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.39.0", + "tsx": "^4.7.2", + "webdriverio": "9.0.5", "yargs": "^17.7.2" }, "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "requires": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" } }, "archiver": { @@ -34468,9 +32901,9 @@ } }, "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, "brace-expansion": { @@ -34504,20 +32937,6 @@ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, "compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", @@ -34549,155 +32968,72 @@ "readable-stream": "^4.0.0" } }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - } + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, "optional": true, "peer": true }, "execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.1.tgz", + "integrity": "sha512-gdhefCCNy/8tpH/2+ajP9IQc14vXchNdd0weyzSJEFURhRMGncQ+zKFxwjAufIewPEJm9BPOaJnvg2UtlH2gPQ==", "dev": true, "requires": { + "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^5.2.0", + "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" } }, "get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@tootallnate/once": "2", - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } } }, "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, "minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -34707,23 +33043,6 @@ "brace-expansion": "^2.0.1" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -34733,14 +33052,11 @@ "path-key": "^4.0.0" } }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } + "parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true }, "path-key": { "version": "4.0.0", @@ -34748,137 +33064,62 @@ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true }, - "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "pretty-ms": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", + "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", "dev": true, "requires": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "dependencies": { - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "parse-ms": "^4.0.0" + } + }, + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, "optional": true, "peer": true, "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } } } @@ -34896,6 +33137,12 @@ "string_decoder": "^1.3.0" } }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, "serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", @@ -34921,47 +33168,15 @@ } }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "optional": true, - "peer": true, "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } + "tar-stream": "^3.1.5" } }, "type-fest": { @@ -34970,156 +33185,48 @@ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "optional": true, - "peer": true - }, "webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - } + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": true, "requires": {} }, "yargs": { @@ -35171,66 +33278,222 @@ } }, "@wdio/config": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.39.0.tgz", - "integrity": "sha512-yNuGPMPibY91s936gnJCHWlStvIyDrwLwGfLC/NCdTin4F7HL4Gp5iJnHWkJFty1/DfFi8jjoIUBNLM8HEez+A==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.5.tgz", + "integrity": "sha512-+dxUU2SLXLkqQhVU/wauU1VgqEKIFubOyUb6B0ueAMpM1aolc62zhE9D9rrQYbjkPOM7nFsjuuGR5+9+zaoZ6g==", "dev": true, "requires": { - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.0.0", + "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0" }, "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "requires": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "requires": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" } } } }, "@wdio/globals": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.39.0.tgz", - "integrity": "sha512-qZo6JjRCIOtdvba6fdSqj6b91TnWXD6rmamyud93FTqbcspnhBvr8lmgOs5wnslTKeeTTigCjpsT310b4/AyHA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.0.5.tgz", + "integrity": "sha512-ZkopKj1qEDNKuF1a87JTLfTKCBFgCHLUns5ob5D1oEmMFp0NwB89HHGBWgtuJpCUmxJAbf4rCKglVeKhB9rY7A==", "dev": true, "requires": { - "expect-webdriverio": "^4.11.2", - "webdriverio": "8.39.0" + "expect-webdriverio": "^5.0.1", + "webdriverio": "9.0.5" }, "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dev": true, + "optional": true, + "requires": { + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "optional": true, + "requires": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + } + }, + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "optional": true, "requires": { @@ -35238,25 +33501,35 @@ } }, "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "optional": true, "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "optional": true, + "requires": { + "debug": "^4.3.4" } }, "archiver": { @@ -35292,9 +33565,9 @@ } }, "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true, "optional": true }, @@ -35326,19 +33599,12 @@ "dev": true, "optional": true }, - "chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } + "optional": true }, "compress-commons": { "version": "6.0.2", @@ -35365,100 +33631,43 @@ "readable-stream": "^4.0.0" } }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - } + "optional": true }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, "optional": true, "peer": true }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "dev": true, "optional": true, - "peer": true + "requires": { + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "optional": true, - "peer": true, "requires": { - "@tootallnate/once": "2", - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } } }, "is-stream": { @@ -35468,18 +33677,6 @@ "dev": true, "optional": true }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -35497,146 +33694,36 @@ "brace-expansion": "^2.0.1" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "optional": true, "requires": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "dependencies": { - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "optional": true, - "requires": { - "debug": "^4.3.4" - } - }, - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true - } + "socks-proxy-agent": "^8.0.2" } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" } }, "readable-stream": { @@ -35653,6 +33740,13 @@ "string_decoder": "^1.3.0" } }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "optional": true + }, "serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", @@ -35674,47 +33768,16 @@ } }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "optional": true, - "peer": true, "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } + "tar-stream": "^3.1.5" } }, "type-fest": { @@ -35724,158 +33787,55 @@ "dev": true, "optional": true }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "optional": true, - "peer": true - }, "webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", "dev": true, "optional": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "optional": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "optional": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "optional": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "optional": true, + "peer": true, "requires": {} }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "optional": true, "requires": { @@ -35903,29 +33863,47 @@ } }, "@wdio/local-runner": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.39.0.tgz", - "integrity": "sha512-TSGJVVWqshH7IO13OKw7G/364q3FczZDEh4h6bYe+GAs91KpZrEhZanyALgjh5F3crWtlffX+GA2HUwpi8X0sA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.0.5.tgz", + "integrity": "sha512-BFZ/e7z1s2cYsix1evijydaDn0YffeIHjPsMoa9b+zhW8BoZfTEDGKblYvzRgjUDD4elXs+YRZpA6EhjcGJTxQ==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/logger": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/runner": "8.39.0", - "@wdio/types": "8.39.0", + "@wdio/logger": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/runner": "9.0.5", + "@wdio/types": "9.0.4", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" }, "dependencies": { + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "requires": { "@types/node": "^20.1.0" } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true } } }, @@ -35964,15 +33942,15 @@ } }, "@wdio/protocols": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.38.0.tgz", - "integrity": "sha512-7BPi7aXwUtnXZPeWJRmnCNFjyDvGrXlBmN9D4Pi58nILkyjVRQKEY9/qv/pcdyB0cvmIvw++Kl/1Lg+RxG++UA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.4.tgz", + "integrity": "sha512-T9v8Jkp94NxLLil5J7uJ/+YHk5/7fhOggzGcN+LvuCHS6jbJFZ/11c4SUEuXw27Yqk01fFXPBbF6TwcTTOuW/Q==", "dev": true }, "@wdio/repl": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz", - "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.4.tgz", + "integrity": "sha512-5Bc5ARjWA7t6MZNnVJ9WvO1iDsy6iOsrSDWiP7APWAdaF/SJCP3SFE2c+PdQJpJWhr2Kk0fRGuyDM+GdsmZhwg==", "dev": true, "requires": { "@types/node": "^20.1.0" @@ -35992,52 +33970,100 @@ } }, "@wdio/runner": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.39.0.tgz", - "integrity": "sha512-M1ixrrCtuwxHVzwsOKGMWBZCteafV0ztoS9+evMWGQtj0ZEsmhjAhWR3n2nZftt24vWOs+eNLGe2p+IO9Sm9bA==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.0.5.tgz", + "integrity": "sha512-qZF7k3BeQaM7pQRwIvedbfaC7xBU1xRY+wFkp44U/wvYZOOrqWiwv/Synk1iCFkOdxl/b+Gqp68dDmS9BrVDmw==", "dev": true, "requires": { "@types/node": "^20.11.28", - "@wdio/config": "8.39.0", - "@wdio/globals": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "deepmerge-ts": "^5.1.0", - "expect-webdriverio": "^4.12.0", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.0.1", "gaze": "^1.1.3", - "webdriver": "8.39.0", - "webdriverio": "8.39.0" + "webdriver": "9.0.5", + "webdriverio": "9.0.5" }, "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "requires": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "requires": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + } + }, + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" } }, "archiver": { @@ -36071,9 +34097,9 @@ } }, "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, "brace-expansion": { @@ -36101,19 +34127,11 @@ "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true }, - "chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true }, "compress-commons": { "version": "6.0.2", @@ -36138,100 +34156,40 @@ "readable-stream": "^4.0.0" } }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - } + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true }, "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, "optional": true, "peer": true }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "dev": true, - "optional": true, - "peer": true + "requires": { + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + } }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@tootallnate/once": "2", - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } } }, "is-stream": { @@ -36240,18 +34198,6 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -36267,138 +34213,54 @@ "brace-expansion": "^2.0.1" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "dependencies": { - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "socks-proxy-agent": "^8.0.2" } }, "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, "peer": true, "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, "optional": true, "peer": true, "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true } } }, @@ -36415,6 +34277,12 @@ "string_decoder": "^1.3.0" } }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, "serialize-error": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", @@ -36434,47 +34302,15 @@ } }, "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "optional": true, - "peer": true, "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } + "tar-stream": "^3.1.5" } }, "type-fest": { @@ -36483,147 +34319,54 @@ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "optional": true, - "peer": true - }, "webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": true, "requires": {} }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -36864,9 +34607,9 @@ "dev": true }, "@zip.js/zip.js": { - "version": "2.7.45", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.45.tgz", - "integrity": "sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow==", + "version": "2.7.48", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.48.tgz", + "integrity": "sha512-J7cliimZ2snAbr0IhLx2U8BwfA1pKucahKzTpFtYq4hEgKxwvFJcIjCIVNPwQpfVab7iVP+AKmoH1gidBlyhiQ==", "dev": true }, "abbrev": { @@ -37496,6 +35239,30 @@ "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, + "axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -38365,35 +36132,6 @@ "unset-value": "^1.0.0" } }, - "cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true - }, - "cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - } - } - }, "call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -38508,6 +36246,50 @@ "get-func-name": "^2.0.2" } }, + "cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dev": true, + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -38557,14 +36339,16 @@ "dev": true }, "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", "dev": true, "optional": true, "peer": true, "requires": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" } }, "ci-info": { @@ -38622,9 +36406,9 @@ } }, "cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.1.0.tgz", + "integrity": "sha512-2MH0N34TpDAs9ROPVkZJfBWFoYfv4zfkJF14PBHY4v/qRY75SLcm4WaEKNCLScsXieosa/tY/+slJ+BDswJxHQ==", "dev": true }, "cli-width": { @@ -39284,9 +37068,9 @@ "dev": true }, "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "requires": { "ms": "2.1.2" } @@ -39424,6 +37208,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "optional": true, "requires": { "clone": "^1.0.2" }, @@ -39432,7 +37217,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true + "dev": true, + "optional": true } } }, @@ -40485,15 +38271,16 @@ } }, "edgedriver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.0.tgz", - "integrity": "sha512-IeJXEczG+DNYBIa9gFgVYTqrawlxmc9SUqUsWU2E98jOsO/amA7wzabKOS8Bwgr/3xWoyXCJ6yGFrbFKrilyyQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.1.tgz", + "integrity": "sha512-3Ve9cd5ziLByUdigw6zovVeWJjVs8QHVmqOB0sJ0WNeVPcwf4p18GnxMmVvlFmYRloUwf5suNuorea4QzwBIOA==", "dev": true, "requires": { - "@wdio/logger": "^8.28.0", - "@zip.js/zip.js": "^2.7.44", + "@wdio/logger": "^8.38.0", + "@zip.js/zip.js": "^2.7.48", "decamelize": "^6.0.0", "edge-paths": "^3.0.5", + "fast-xml-parser": "^4.4.1", "node-fetch": "^3.3.2", "which": "^4.0.0" } @@ -40534,6 +38321,27 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "dev": true, + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -40840,6 +38648,38 @@ "es6-symbol": "^3.1.1" } }, + "esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -41459,835 +39299,145 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "expect-webdriverio": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.15.1.tgz", - "integrity": "sha512-xtBSidt7Whs1fsUC+utxVzfmkmaStXWW17b+BcMCiCltx0Yku6l7BTv1Y14DEKX8L6rttaDQobYyRtBKbi4ssg==", - "dev": true, - "requires": { - "@vitest/snapshot": "^1.2.2", - "@wdio/globals": "^8.29.3", - "@wdio/logger": "^8.28.0", - "expect": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "lodash.isequal": "^4.5.0", - "webdriverio": "^8.29.3" - }, - "dependencies": { - "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "^20.1.0" - } - }, - "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", - "split2": "^4.2.0", - "wait-port": "^1.0.4" - } - }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "optional": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "optional": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "optional": true - }, - "chrome-launcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.2.tgz", - "integrity": "sha512-YclTJey34KUm5jB1aEJCq807bSievi7Nb/TU4Gu504fUYi3jw3KCIaH6L7nFWQhdEgH3V+wCh+kKD1P5cXnfxw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "optional": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, - "optional": true, "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "shebang-regex": "^1.0.0" } }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "optional": true, - "peer": true, "requires": { - "node-fetch": "^2.6.11" + "isexe": "^2.0.0" } - }, + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "optional": true, - "peer": true, "requires": { "ms": "2.0.0" } }, - "devtools": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.39.0.tgz", - "integrity": "sha512-QNbvNTNQMlU5gZqbmqzF92vfMOP/Eaa8KcvRj87M0jbn3dfwOeBC7WiECPFQ0MAfmynfarK7G7Ec+TfbAAEyNQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - } - }, - "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } + "is-descriptor": "^0.1.0" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "optional": true - }, - "lighthouse-logger": { + "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, - "optional": true, - "peer": true, "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "is-extendable": "^0.1.0" } }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, - "optional": true, "requires": { - "brace-expansion": "^2.0.1" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" } }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, - "dependencies": { - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "optional": true, - "requires": { - "debug": "^4.3.4" - } - }, - "debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true - } - } - }, - "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true, - "peer": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "optional": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } - } - }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "optional": true - }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "optional": true, - "peer": true - }, - "webdriverio": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.39.0.tgz", - "integrity": "sha512-pDpGu0V+TL1LkXPode67m3s+IPto4TcmcOzMpzFgu2oeLMBornoLN3yQSFR1fjZd1gK4UfnG3lJ4poTGOfbWfw==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1302984", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.39.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "optional": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.1302984", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", - "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", - "dev": true, - "optional": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - } - } - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "optional": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - } - } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "optional": true, - "requires": {} - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "optional": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } + "dev": true } } }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, "express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -42518,6 +39668,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "dev": true, + "requires": { + "strnum": "^1.0.5" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -42561,21 +39720,12 @@ } }, "figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "requires": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - } + "is-unicode-supported": "^2.0.0" } }, "file-entry-cache": { @@ -42794,9 +39944,9 @@ "dev": true }, "foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -42834,12 +39984,6 @@ "mime-types": "^2.1.12" } }, - "form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true - }, "formdata-node": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz", @@ -43170,6 +40314,15 @@ "get-intrinsic": "^1.2.4" } }, + "get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -43275,9 +40428,9 @@ "dev": true }, "glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "requires": { "foreground-child": "^3.1.0", @@ -43702,33 +40855,6 @@ "get-intrinsic": "^1.1.3" } }, - "got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - } - } - }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -45520,6 +42646,24 @@ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", "dev": true }, + "htmlfy": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.2.1.tgz", + "integrity": "sha512-HoomFHQ3av1uhq+7FxJTq4Ns0clAD+tGbQNrSd0WFY3UAjjUk6G3LaWEqdgmIXYkY4pexZiyZ3ykZJhQlM0J5A==", + "dev": true + }, + "htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -45587,16 +42731,6 @@ "sshpk": "^1.7.0" } }, - "http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -45608,9 +42742,9 @@ } }, "human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true }, "iconv-lite": { @@ -45697,43 +42831,18 @@ "dev": true }, "inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-10.1.8.tgz", + "integrity": "sha512-syxGpOzLyqVeZi1KDBjRTnCn5PiGWySGHP0BbqXbqsEK0ckkZk3egAepEWslUjZXj0rhkUapVXM/IpADWe4D6w==", "dev": true, "requires": { - "@ljharb/through": "^2.3.11", + "@inquirer/prompts": "^5.3.8", + "@inquirer/type": "^1.5.2", + "@types/mute-stream": "^0.0.4", "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", + "mute-stream": "^1.0.0", "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } + "rxjs": "^7.8.1" } }, "internal-slot": { @@ -45992,12 +43101,6 @@ "is-extglob": "^2.1.1" } }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, "is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -46157,9 +43260,9 @@ } }, "is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", "dev": true }, "is-utf8": { @@ -46418,9 +43521,9 @@ } }, "jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", @@ -47436,12 +44539,6 @@ "@babel/traverse": "^7.10.5" } }, - "ky": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", - "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", - "dev": true - }, "last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -47983,12 +45080,6 @@ "get-func-name": "^2.0.1" } }, - "lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -48894,12 +45985,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true - }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -48931,10 +46016,12 @@ "dev": true }, "mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "optional": true, + "peer": true }, "mixin-deep": { "version": "1.3.2", @@ -49611,12 +46698,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true - }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -49903,99 +46984,6 @@ "word-wrap": "^1.2.5" } }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -50026,12 +47014,6 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, - "p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -50220,6 +47202,47 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } + } + }, + "parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "requires": { + "parse5": "^7.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -50286,9 +47309,9 @@ }, "dependencies": { "lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true } } @@ -51351,21 +48374,18 @@ "value-or-function": "^3.0.0" } }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", "dev": true }, - "responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "requires": { - "lowercase-keys": "^3.0.0" - } - }, "resq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", @@ -52596,9 +49616,9 @@ "dev": true }, "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true }, "strip-json-comments": { @@ -52607,6 +49627,12 @@ "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -53003,6 +50029,12 @@ } } }, + "tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true + }, "tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -53195,6 +50227,17 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "tsx": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz", + "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==", + "dev": true, + "requires": { + "esbuild": "~0.23.0", + "fsevents": "~2.3.3", + "get-tsconfig": "^4.7.5" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -53398,6 +50441,12 @@ "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", "dev": true }, + "undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "dev": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -53706,6 +50755,12 @@ "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==", "dev": true }, + "urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -54205,6 +51260,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "optional": true, "requires": { "defaults": "^1.0.3" } @@ -54222,52 +51278,164 @@ "dev": true }, "webdriver": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.39.0.tgz", - "integrity": "sha512-Kc3+SfiH4ufyrIht683VT2vnJocx0pfH8rYdyPvEh1b2OYewtFTHK36k9rBDHZiBmk6jcSXs4M2xeFgOuon9Lg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.5.tgz", + "integrity": "sha512-+xkdfbmG1IZrXxiPwab450Xuh9QClOcxTJ6tnde0rzxlPxdUqZqzwuMtM+VXZybxF4yCLrJWbeT0BpwJFAz1nA==", "dev": true, "requires": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.39.0", - "@wdio/logger": "8.38.0", - "@wdio/protocols": "8.38.0", - "@wdio/types": "8.39.0", - "@wdio/utils": "8.39.0", - "deepmerge-ts": "^5.1.0", - "got": "^12.6.1", - "ky": "^0.33.0", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", "ws": "^8.8.0" }, "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "requires": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, "@wdio/types": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.39.0.tgz", - "integrity": "sha512-86lcYROTapOJuFd9ouomFDfzDnv3Kn+jE0RmqfvN9frZAeLVJ5IKjX9M6HjplsyTZhjGO1uCaehmzx+HJus33Q==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.39.0.tgz", - "integrity": "sha512-jY+n6jlGeK+9Tx8T659PKLwMQTGpLW5H78CSEWgZLbjbVSr2LfGR8Lx0CRktNXxAtqEVZPj16Pi74OtAhvhE6Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "requires": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.38.0", - "@wdio/types": "8.39.0", + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.5.0", - "geckodriver": "^4.3.1", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", "split2": "^4.2.0", - "wait-port": "^1.0.4" + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "requires": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" } } } @@ -54890,6 +52058,32 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -55186,6 +52380,18 @@ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true }, + "yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true + }, + "yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true + }, "zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", @@ -55242,6 +52448,14 @@ } } }, + "zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "optional": true, + "peer": true + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 3a7189fca59..17ca7dd6a40 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,10 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@wdio/browserstack-service": "^8.29.0", - "@wdio/cli": "^8.29.0", + "@wdio/browserstack-service": "^9.0.5", + "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^8.29.0", - "@wdio/local-runner": "^8.29.0", + "@wdio/local-runner": "^9.0.5", "@wdio/mocha-framework": "^8.29.0", "@wdio/spec-reporter": "^8.29.0", "ajv": "6.12.3", From 825fc6e23204db294b42341a9748f6a110bd2bf8 Mon Sep 17 00:00:00 2001 From: eszponder <155961428+eszponder@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:54:34 +0200 Subject: [PATCH 0443/1097] Smartadserver Bid Adapter : add DSA support (#12141) * add support of dsa * restore topics * DSA fix for UT --- modules/smartadserverBidAdapter.js | 22 ++++++- .../modules/smartadserverBidAdapter_spec.js | 58 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index a90f99da2f7..840ba8a82aa 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,4 +1,14 @@ -import { deepAccess, deepClone, isArrayOfNums, isFn, isInteger, isPlainObject, logError } from '../src/utils.js'; +import { + deepAccess, + deepClone, + isArray, + isArrayOfNums, + isEmpty, + isFn, + isInteger, + isPlainObject, + logError +} from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -204,6 +214,11 @@ export const spec = { payload.gpid = gpid; } + const dsa = deepAccess(bid, 'ortb2.regs.ext.dsa'); + if (dsa) { + payload.dsa = dsa; + } + if (bidderRequest) { if (bidderRequest.gdprConsent) { payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; @@ -285,7 +300,10 @@ export const spec = { netRevenue: response.isNetCpm, ttl: response.ttl, dspPixels: response.dspPixels, - meta: { advertiserDomains: response.adomain ? response.adomain : [] } + meta: { + ...isArray(response.adomain) && !isEmpty(response.adomain) ? { advertiserDomains: response.adomain } : {}, + ...!isEmpty(response.dsa) ? { dsa: response.dsa } : {} + } }; if (bidRequest.mediaType === VIDEO) { diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 2f06331f616..08b9f616551 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -465,6 +465,32 @@ describe('Smart bid adapter tests', function () { }); }); + it('Verify metadata', function () { + const adomain = ['advertiser-domain.com']; + const dsa = { + dsarequired: 1, + pubrender: 0, + datatopub: 1, + transparency: [{ + domain: 'smartadserver.com', + dsaparams: [1, 2] + }] + }; + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse({ + body: { + ...BID_RESPONSE.body, + adomain, + dsa, + } + }, request[0]); + + expect(bids[0].cpm).to.equal(12); + expect(bids[0]).to.have.property('meta'); + expect(bids[0].meta).to.have.property('advertiserDomains').and.to.deep.equal(adomain); + expect(bids[0].meta).to.have.property('dsa').and.to.deep.equal(dsa); + }); + describe('gdpr tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); @@ -1512,6 +1538,38 @@ describe('Smart bid adapter tests', function () { }); }); + describe('Digital Services Act (DSA)', function () { + it('should include dsa if ortb2.regs.ext.dsa available', function () { + const dsa = { + dsarequired: 1, + pubrender: 0, + datatopub: 1, + transparency: [ + { + domain: 'ok.domain.com', + dsaparams: [1, 2] + }, + { + domain: 'ko.domain.com', + dsaparams: [1, '3'] + } + ] + }; + + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + bidRequests[0].ortb2 = { + regs: { + ext: { dsa } + } + }; + + const request = spec.buildRequests(bidRequests); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('dsa').and.to.deep.equal(dsa); + }); + }); + describe('#getValuableProperty method', function () { it('should return an object when calling with a number value', () => { const obj = spec.getValuableProperty('prop', 3); From 8a544cadf9e88540c202938eb2715becd2fb1323 Mon Sep 17 00:00:00 2001 From: danijel-ristic <168181386+danijel-ristic@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:00:41 +0200 Subject: [PATCH 0444/1097] Change consent default param name (#12154) Co-authored-by: Danijel Ristic --- modules/targetVideoAdServerVideo.js | 2 +- test/spec/modules/targetVideoAdServerVideo_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/targetVideoAdServerVideo.js b/modules/targetVideoAdServerVideo.js index e54aa1ab141..335fbcda730 100644 --- a/modules/targetVideoAdServerVideo.js +++ b/modules/targetVideoAdServerVideo.js @@ -28,7 +28,7 @@ export function buildVideoUrl(options) { mute: '[vpmute]', page_url: '[page_url]', cachebuster: '[timestamp]', - consent: '[consent]', + gdpr_consent: '[consent]', } const adUnit = options.adUnit; diff --git a/test/spec/modules/targetVideoAdServerVideo_spec.js b/test/spec/modules/targetVideoAdServerVideo_spec.js index a254d76f2c9..c4c2e34b73f 100644 --- a/test/spec/modules/targetVideoAdServerVideo_spec.js +++ b/test/spec/modules/targetVideoAdServerVideo_spec.js @@ -125,7 +125,7 @@ describe('TargetVideo Ad Server Video', function() { expect(url).to.include('mute=[vpmute]'); expect(url).to.include('page_url=[page_url]'); expect(url).to.include('cachebuster=[timestamp]'); - expect(url).to.include('consent=[consent]'); + expect(url).to.include('gdpr_consent=[consent]'); getWinningBidsStub.restore(); getAllTargetingDataStub.restore(); From 1231b4cb4d98c20e7b6f571eb616743632257f06 Mon Sep 17 00:00:00 2001 From: freedomadnetworkdev Date: Wed, 21 Aug 2024 09:56:41 -0400 Subject: [PATCH 0445/1097] Freedom Ad Network Bid Adapter: initial release (#12153) * Create fanAdapter.md * Create fanAdapter.js * Create fanAdapter_spec.js * Update fanAdapter_spec.js * Update fanAdapter.js * Update fanAdapter.js * Update fanAdapter_spec.js * Update fanAdapter.js * Update fanAdapter_spec.js * Update fanAdapter.js * Update fanAdapter_spec.js * Update fanAdapter.js * Update fanAdapter_spec.js * Update fanAdapter.js * Update fanAdapter.js --- modules/fanAdapter.js | 176 +++++++++++++++ modules/fanAdapter.md | 40 ++++ test/spec/modules/fanAdapter_spec.js | 315 +++++++++++++++++++++++++++ 3 files changed, 531 insertions(+) create mode 100644 modules/fanAdapter.js create mode 100644 modules/fanAdapter.md create mode 100644 test/spec/modules/fanAdapter_spec.js diff --git a/modules/fanAdapter.js b/modules/fanAdapter.js new file mode 100644 index 00000000000..cdcc8d19889 --- /dev/null +++ b/modules/fanAdapter.js @@ -0,0 +1,176 @@ +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + +const BIDDER_CODE = 'freedomadnetwork'; +const BASE_URL = 'https://srv.freedomadnetwork.com'; + +/** + * Build OpenRTB request from bidRequest and bidderRequest + * + * @param {BidRequest} bid + * @param {BidderRequest} bidderRequest + * @returns {Request} + */ +function buildBidRequest(bid, bidderRequest) { + const payload = { + id: bid.bidId, + tmax: bidderRequest.timeout, + placements: [bid.params.placementId], + at: 1, + user: {} + } + + const gdprConsent = utils.deepAccess(bidderRequest, 'gdprConsent'); + if (!!gdprConsent && gdprConsent.gdprApplies) { + payload.user.gdpr = 1; + payload.user.consent = gdprConsent.consentString; + } + + const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + payload.user.usp = uspConsent; + } + + return { + method: 'POST', + url: BASE_URL + '/pb/req', + data: JSON.stringify(payload), + options: { + contentType: 'application/json', + withCredentials: false, + customHeaders: { + 'Accept-Language': 'en;q=10', + }, + }, + originalBidRequest: bid + } +} + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (!bid) { + utils.logWarn(BIDDER_CODE, 'Invalid bid', bid); + + return false; + } + + if (!bid.params) { + utils.logWarn(BIDDER_CODE, 'bid.params is required'); + + return false; + } + + if (!bid.params.placementId) { + utils.logWarn(BIDDER_CODE, 'bid.params.placementId is required'); + + return false; + } + + var banner = utils.deepAccess(bid, 'mediaTypes.banner'); + if (banner === undefined) { + return false; + } + + return true; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => buildBidRequest(bid, bidderRequest)); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + let bidResponses = []; + + if (!serverBody) { + return bidResponses; + } + + serverBody.forEach((response) => { + const bidResponse = { + requestId: response.id, + bidid: response.bidid, + impid: response.impid, + userId: response.userId, + cpm: response.cpm, + currency: response.currency, + width: response.width, + height: response.height, + ad: response.payload, + ttl: response.ttl, + creativeId: response.crid, + netRevenue: response.netRevenue, + trackers: response.trackers, + meta: { + mediaType: response.mediaType, + advertiserDomains: response.domains, + } + }; + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function (bid) { + if (!bid) { + return; + } + + const payload = { + id: bid.bidid, + impid: bid.impid, + t: bid.cpm, + u: bid.userId, + } + + ajax(BASE_URL + '/pb/imp', null, JSON.stringify(payload), { + method: 'POST', + customHeaders: { + 'Accept-Language': 'en;q=10', + }, + }); + + if (bid.trackers && bid.trackers.length > 0) { + for (var i = 0; i < bid.trackers.length; i++) { + if (bid.trackers[i].type == 0) { + utils.triggerPixel(bid.trackers[i].url); + } + } + } + }, + onSetTargeting: function(bid) {}, + onBidderError: function(error) { + utils.logError(`${BIDDER_CODE} bidder error`, error); + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + return syncs; + }, + onTimeout: function(timeoutData) {}, + supportedMediaTypes: [BANNER, NATIVE] +} + +registerBidder(spec); diff --git a/modules/fanAdapter.md b/modules/fanAdapter.md new file mode 100644 index 00000000000..caa18ca8552 --- /dev/null +++ b/modules/fanAdapter.md @@ -0,0 +1,40 @@ +# Freedom Ad Network Bidder Adapter + +# Overview + +``` +Module Name: Freedom Ad Network Bidder Adapter +Module Type: Bidder Adapter +Maintainer: info@freedomadnetwork.com +``` + +## Description + +Module that connects to FAN's demand sources. + +## Bid Parameters + +| Name | Scope | Type | Description | Example | +|---------------|----------|--------------------|-----------------------------------------|-------------------------------------------------| +| `placementId` | required | String | The Placement Id provided by FAN. | `e6203f1e-bd6d-4f42-9895-d1a19cdb83c8` | + +## Example + +### Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'freedomadnetwork', + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + }] +}]; +``` diff --git a/test/spec/modules/fanAdapter_spec.js b/test/spec/modules/fanAdapter_spec.js new file mode 100644 index 00000000000..010ba91339d --- /dev/null +++ b/test/spec/modules/fanAdapter_spec.js @@ -0,0 +1,315 @@ +import * as ajax from 'src/ajax.js'; +import { expect } from 'chai'; +import { spec } from 'modules/fanAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from 'src/mediaTypes.js'; + +describe('Freedom Ad Network Bid Adapter', function () { + describe('Test isBidRequestValid', function () { + it('undefined bid should return false', function () { + expect(spec.isBidRequestValid()).to.be.false; + }); + + it('null bid should return false', function () { + expect(spec.isBidRequestValid(null)).to.be.false; + }); + + it('bid.params should be set', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('bid.params.placementId should be set', function () { + expect(spec.isBidRequestValid({ + params: { foo: 'bar' } + })).to.be.false; + }); + + it('valid bid should return true', function () { + expect(spec.isBidRequestValid({ + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + })).to.be.true; + }); + }); + + describe('Test buildRequests', function () { + const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: Date.now(), + bidderCode: 'myBidderCode', + bidderRequestId: '15246a574e859f', + refererInfo: { + page: 'http://example.com', + stack: ['http://example.com'] + }, + gdprConsent: { + gdprApplies: true, + consentString: 'IwuyYwpjmnsauyYasIUWwe' + }, + uspConsent: 'Oush3@jmUw82has', + timeout: 3000 + }; + + it('build request object', function () { + const bidRequests = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1776', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + }, + { + adUnitCode: 'test-native', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1777', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: { + title: { + required: true, + len: 20, + }, + image: { + required: true, + sizes: [300, 250], + aspect_ratios: [{ + ratio_width: 1, + ratio_height: 1 + }] + }, + icon: { + required: true, + sizes: [60, 60], + aspect_ratios: [{ + ratio_width: 1, + ratio_height: 1 + }] + }, + sponsoredBy: { + required: true, + len: 20 + }, + body: { + required: true, + len: 140 + }, + cta: { + required: true, + len: 20, + } + } + }, + params: { + placementId: '3f50a79e-5582-4e5c-b1f4-9dcc1c82cece' + } + }, + { + adUnitCode: 'test-native2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1778', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: { + title: {}, + image: {}, + icon: {}, + sponsoredBy: {}, + body: {}, + cta: {} + } + }, + params: { + placementId: '2015defc-19db-4cf6-926d-d2d0d32122fa', + } + }, + { + adUnitCode: 'test-native3', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1779', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: {}, + }, + params: { + placementId: '8064026a-9932-45ae-b804-03491302ad88' + } + } + ]; + + let reqs; + + expect(function () { + reqs = spec.buildRequests(bidRequests, bidderRequest); + }).to.not.throw(); + + expect(reqs).to.be.an('array').that.have.lengthOf(bidRequests.length); + + for (let i = 0, len = reqs.length; i < len; i++) { + const req = reqs[i]; + const bidRequest = bidRequests[i]; + + expect(req.method).to.equal('POST'); + expect(req.url).to.equal('https://srv.freedomadnetwork.com/pb/req'); + + expect(req.options).to.be.an('object'); + expect(req.options.contentType).to.contain('application/json'); + expect(req.options.customHeaders).to.be.an('object'); + + expect(req.originalBidRequest).to.equal(bidRequest); + + var data = JSON.parse(req.data); + expect(data.id).to.equal(bidRequest.bidId); + expect(data.placements[0]).to.equal(bidRequest.params.placementId); + } + }); + }); + + describe('Test adapter request', function () { + const adapter = newBidder(spec); + + it('adapter.callBids exists and is a function', function () { + expect(adapter.callBids).to.be.a('function'); + }); + }); + + describe('Test response interpretResponse', function () { + it('Test main interpretResponse', function () { + const serverResponse = { + body: [{ + id: '8064026a1776', + bidid: '78e10bd4-aa67-40a6-b282-0f2697251eb3', + impid: '88faf7e7-bef8-43a5-9ef3-73db10c2af6b', + userId: '944c9c880be09af1e90da1f883538607', + cpm: 17.76, + currency: 'USD', + width: 300, + height: 250, + ttl: 60, + netRevenue: false, + crid: '03f3ed6f-1a9e-4276-8ad7-0dc5efae289e', + payload: '', + trackers: [], + mediaType: 'native', + domains: ['foo.com'], + }] + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1776', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.requestId).to.equal(bid.id); + expect(bidResponse.bidid).to.equal(bid.bidid); + expect(bidResponse.impid).to.equal(bid.impid); + expect(bidResponse.userId).to.equal(bid.userId); + expect(bidResponse.cpm).to.equal(bid.cpm); + expect(bidResponse.currency).to.equal(bid.currency); + expect(bidResponse.width).to.equal(bid.width); + expect(bidResponse.height).to.equal(bid.height); + expect(bidResponse.ad).to.equal(bid.payload); + expect(bidResponse.ttl).to.equal(bid.ttl); + expect(bidResponse.creativeId).to.equal(bid.crid); + expect(bidResponse.netRevenue).to.equal(bid.netRevenue); + expect(bidResponse.trackers).to.equal(bid.trackers); + expect(bidResponse.meta.mediaType).to.equal(bid.mediaType); + expect(bidResponse.meta.advertiserDomains).to.equal(bid.domains); + }); + + it('Test empty server response', function () { + const bidResponses = spec.interpretResponse({}, {}); + + expect(bidResponses).to.be.an('array').that.is.empty; + }); + + it('Test empty bid response', function () { + const bidResponses = spec.interpretResponse({ body: [] }, {}); + + expect(bidResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('Test getUserSyncs', function () { + it('getUserSyncs should return empty', function () { + const serverResponse = {}; + const syncOptions = {} + const userSyncPixels = spec.getUserSyncs(syncOptions, [serverResponse]) + expect(userSyncPixels).to.have.lengthOf(0); + }); + }); + + describe('Test onTimeout', function () { + it('onTimeout should not throw', function () { + expect(spec.onTimeout()).to.not.throw; + }); + }); + + describe('Test onBidWon', function () { + let sandbox, ajaxStub; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + ajaxStub = sandbox.stub(ajax, 'ajax'); + }); + + afterEach(function () { + sandbox.restore(); + ajaxStub.restore(); + }); + + const bid = { + bidid: '78e10bd4-aa67-40a6-b282-0f2697251eb3', + impid: '88faf7e7-bef8-43a5-9ef3-73db10c2af6b', + cpm: 17.76, + trackers: ['foo.com'], + } + + it('onBidWon empty bid should not throw', function () { + expect(spec.onBidWon({})).to.not.throw; + expect(ajaxStub.calledOnce).to.equal(true); + }); + + it('onBidWon valid bid should not throw', function () { + expect(spec.onBidWon(bid)).to.not.throw; + expect(ajaxStub.calledOnce).to.equal(true); + }); + }); + + describe('Test onSetTargeting', function () { + it('onSetTargeting should not throw', function () { + expect(spec.onSetTargeting()).to.not.throw; + }); + }); +}); From 8fe3e53887d69cd9e1dbe8567dcf0cd9f808f2c1 Mon Sep 17 00:00:00 2001 From: OctaviaS20 <151752110+OctaviaS20@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:05:00 +0300 Subject: [PATCH 0446/1097] deleted bidder name check (#12157) Co-authored-by: Octavia Suceava --- modules/connatixBidAdapter.js | 7 ++----- test/spec/modules/connatixBidAdapter_spec.js | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 802ad855e27..de0bddc3b97 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -75,20 +75,17 @@ export const spec = { const bidId = deepAccess(bid, 'bidId'); const mediaTypes = deepAccess(bid, 'mediaTypes', {}); const params = deepAccess(bid, 'params', {}); - const bidder = deepAccess(bid, 'bidder'); const hasBidId = Boolean(bidId); - const isValidBidder = (bidder === BIDDER_CODE); const hasMediaTypes = Boolean(mediaTypes) && (Boolean(mediaTypes[BANNER]) || Boolean(mediaTypes[VIDEO])); const isValidBanner = validateBanner(mediaTypes); const isValidVideo = validateVideo(mediaTypes); const hasRequiredBidParams = Boolean(params.placementId); - const isValid = isValidBidder && hasBidId && hasMediaTypes && isValidBanner && isValidVideo && hasRequiredBidParams; + const isValid = hasBidId && hasMediaTypes && isValidBanner && isValidVideo && hasRequiredBidParams; if (!isValid) { logError( - `Invalid bid request: - isValidBidder: ${isValidBidder}, + `Invalid bid request: hasBidId: ${hasBidId}, hasMediaTypes: ${hasMediaTypes}, isValidBanner: ${isValidBanner}, diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 1bf04ed9db8..ceb91a46bee 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -52,10 +52,6 @@ describe('connatixBidAdapter', function () { it('Should return true if all required fileds are present', function () { expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('Should return false if bidder does not correspond', function () { - bid.bidder = 'abc'; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); it('Should return false if bidId is missing', function () { delete bid.bidId; expect(spec.isBidRequestValid(bid)).to.be.false; From 95c8118583eb0737409e41c8f436f90bc134df32 Mon Sep 17 00:00:00 2001 From: OctaviaS20 <151752110+OctaviaS20@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:12:56 +0300 Subject: [PATCH 0447/1097] Connatix Bid Adapter : support eids (#12142) * add eids on request * change naming --------- Co-authored-by: Darian Avasan Co-authored-by: Octavia Suceava --- modules/connatixBidAdapter.js | 15 ++++++++- test/spec/modules/connatixBidAdapter_spec.js | 32 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index de0bddc3b97..925ddebe099 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -7,7 +7,8 @@ import { isFn, logError, isArray, - formatQS + formatQS, + deepSetValue } from '../src/utils.js'; import { @@ -61,6 +62,16 @@ export function validateVideo(mediaTypes) { return video.context !== ADPOD; } +/** + * Get ids from Prebid User ID Modules and add them to the payload + */ +function _handleEids(payload, validBidRequests) { + let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(payload, 'userIdList', bidUserIdAsEids); + } +} + export const spec = { code: BIDDER_CODE, gvlid: 143, @@ -127,6 +138,8 @@ export const spec = { bidRequests, }; + _handleEids(requestPayload, validBidRequests); + return { method: 'POST', url: AD_URL, diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index ceb91a46bee..d2e8ea63cbe 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -352,6 +352,38 @@ describe('connatixBidAdapter', function () { }); }); + describe('userIdAsEids', function() { + let validBidRequests; + + this.beforeEach(function () { + bid = mockBidRequest(); + validBidRequests = [bid]; + }) + + it('Connatix adapter reads EIDs from Prebid user models and adds it to Request', function() { + validBidRequests[0].userIdAsEids = [{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID_FROM_USER_ID_MODULE', + 'atype': 1, + 'ext': { + 'stype': 'ppuid', + 'rtiPartner': 'TDID' + } + }] + }, + { + 'source': 'pubserver.org', + 'uids': [{ + 'id': 'TDID_FROM_USER_ID_MODULE', + 'atype': 1 + }] + }]; + let serverRequest = spec.buildRequests(validBidRequests, {}); + expect(serverRequest.data.userIdList).to.deep.equal(validBidRequests[0].userIdAsEids); + }); + }); + describe('getBidFloor', function () { this.beforeEach(function () { bid = mockBidRequest(); From 23ac00226a03931014cac7597c7f19eb40d4ccc3 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:15:57 -0400 Subject: [PATCH 0448/1097] PrebidServer Bid Adapter : update to use gloablly defined alias or s2sConfig defined alias (#12159) * update to use gloablly defined alias or s2sConfig defined alias * Add test case --------- Co-authored-by: Demetrio Girardi --- modules/prebidServerBidAdapter/index.js | 3 +- .../modules/prebidServerBidAdapter_spec.js | 137 ++++++++++-------- 2 files changed, 79 insertions(+), 61 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 4212ab294f5..629ac41dd14 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -460,8 +460,9 @@ export function PrebidServer() { if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { + const s2sAliases = (s2sBidRequest.s2sConfig.extPrebid && s2sBidRequest.s2sConfig.extPrebid.aliases) ?? {}; let syncBidders = s2sBidRequest.s2sConfig.bidders - .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) + .map(bidder => adapterManager.aliasRegistry[bidder] || s2sAliases[bidder] || bidder) .filter((bidder, index, array) => (array.indexOf(bidder) === index)); queueSync(syncBidders, gdprConsent, uspConsent, gppConsent, s2sBidRequest.s2sConfig); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9769c2010df..0ddc1174314 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3925,77 +3925,94 @@ describe('S2S Adapter', function () { expect(typeof config.getConfig('s2sConfig').syncUrlModifier.appnexus).to.equal('function') }); - it('should set correct bidder names to bidders property when using an alias for that bidder', function () { - const s2sConfig = utils.deepClone(CONFIG); - - // Add syncEndpoint so that the request goes to the User Sync endpoint - // Modify the bidders property to include an alias for Rubicon adapter - s2sConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; - s2sConfig.bidders = ['appnexus', 'rubicon-alias']; - - const s2sBidRequest = utils.deepClone(REQUEST); - s2sBidRequest.s2sConfig = s2sConfig; - - // Add another bidder, `rubicon-alias` - s2sBidRequest.ad_units[0].bids.push({ - bidder: 'rubicon-alias', - params: { - accoundId: 14062, - siteId: 70608, - zoneId: 498816 + Object.entries({ + 'an alias'() { + adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); + }, + 'a server side alias'(s2sConfig) { + s2sConfig.extPrebid = { + aliases: { + 'rubicon-alias': 'rubicon' + } } - }); - - // create an alias for the Rubicon Bid Adapter - adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); + } + }).forEach(([t, setupAlias]) => { + describe(`when using ${t}`, () => { + afterEach(() => { + delete adapterManager.aliasRegistry['rubicon-alias']; + }); + it(`should set correct bidder names to bidders property`, function () { + const s2sConfig = utils.deepClone(CONFIG); + + // Add syncEndpoint so that the request goes to the User Sync endpoint + // Modify the bidders property to include an alias for Rubicon adapter + s2sConfig.syncEndpoint = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + s2sConfig.bidders = ['appnexus', 'rubicon-alias']; + + setupAlias(s2sConfig); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + // Add another bidder, `rubicon-alias` + s2sBidRequest.ad_units[0].bids.push({ + bidder: 'rubicon-alias', + params: { + accoundId: 14062, + siteId: 70608, + zoneId: 498816 + } + }); - const bidRequest = utils.deepClone(BID_REQUESTS); - bidRequest.push({ - 'bidderCode': 'rubicon-alias', - 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4', - 'bidderRequestId': '4b1a4f9c3e4546', - 'tid': 'd7fa8342-ae22-4ca1-b237-331169350f84', - 'bids': [ - { - 'bidder': 'rubicon-alias', - 'params': { - 'accountId': 14062, - 'siteId': 70608, - 'zoneId': 498816 - }, - 'bid_id': '2a9523915411c3', - 'mediaTypes': { - 'banner': { + const bidRequest = utils.deepClone(BID_REQUESTS); + bidRequest.push({ + 'bidderCode': 'rubicon-alias', + 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4', + 'bidderRequestId': '4b1a4f9c3e4546', + 'tid': 'd7fa8342-ae22-4ca1-b237-331169350f84', + 'bids': [ + { + 'bidder': 'rubicon-alias', + 'params': { + 'accountId': 14062, + 'siteId': 70608, + 'zoneId': 498816 + }, + 'bid_id': '2a9523915411c3', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '78ddc106-b7d8-45d1-bd29-86993098e53d', 'sizes': [ [ 300, 250 ] - ] + ], + 'bidId': '2a9523915411c3', + 'bidderRequestId': '4b1a4f9c3e4546', + 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4' } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '78ddc106-b7d8-45d1-bd29-86993098e53d', - 'sizes': [ - [ - 300, - 250 - ] ], - 'bidId': '2a9523915411c3', - 'bidderRequestId': '4b1a4f9c3e4546', - 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4' - } - ], - 'auctionStart': 1569234122602, - 'timeout': 1000, - 'src': 's2s' - }); + 'auctionStart': 1569234122602, + 'timeout': 1000, + 'src': 's2s' + }); - adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); + }); + }); }); it('should add cooperative sync flag to cookie_sync request if property is present', function () { From 84d323814528665de008c55ba90dd895ee89f6ea Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:16:04 +0530 Subject: [PATCH 0449/1097] PubMatic Bid Adapter MD : update configuration document for user sync (#12163) * Updated configuration document for user sync * Updated configuration document for user sync --------- Co-authored-by: pm-azhar-mulla --- modules/pubmaticBidAdapter.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index e472b30a916..6fe84d81350 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -189,7 +189,12 @@ PubMatic recommends the UserSync configuration below. Without it, the PubMatic pbjs.setConfig({ userSync: { iframeEnabled: true, - enabledBidders: ['pubmatic'], + filterSettings: { + iframe: { + bidders: '*', // '*' represents all bidders + filter: 'include' + } + }, syncDelay: 6000 }}); From 9f8829e31c290cf61d68a2bcccae6bc78ef8e125 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 22 Aug 2024 13:58:59 -0400 Subject: [PATCH 0450/1097] Paapi tools: add constants for importing (#12160) * Create buyerOrigins.js * Update buyerOrigins.js * Update buyerOrigins.js * Update ringieraxelspringerBidAdapter.js * Update buyerOrigins.js * Update buyerOrigins.js * Update buyerOrigins.js * Update buyerOrigins.js * Update buyerOrigins.js * Update ringieraxelspringerBidAdapter.js * Update buyerOrigins.js * Update buyerOrigins.js --- libraries/paapiTools/buyerOrigins.js | 35 ++++++++++++++++++++++++ modules/ringieraxelspringerBidAdapter.js | 7 +++-- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 libraries/paapiTools/buyerOrigins.js diff --git a/libraries/paapiTools/buyerOrigins.js b/libraries/paapiTools/buyerOrigins.js new file mode 100644 index 00000000000..ace9b7da073 --- /dev/null +++ b/libraries/paapiTools/buyerOrigins.js @@ -0,0 +1,35 @@ +/* + This list is several known buyer origins for PAAPI auctions. + Bidders should add anyone they like to it. + It is not intended to be comphensive nor maintained by the Core team. + Rather, Bid adapters should simply append additional constants whenever + the need arises in their adapter. + + The goal is to reduce expression of common constants over many + bid adapters attempting to define interestGroupBuyers + in advance of network traffic. + + Bidders should consider updating their interstGroupBuyer list + with server communication for auctions initiated after the first bid response. + + Known buyers without current importers are commented out. If you need one, uncomment it. +*/ + +export const BO_CSR_ONET = 'https://csr.onet.pl'; +// export const BO_DOUBLECLICK_GOOGLEADS = 'https://googleads.g.doubleclick.net'; +// export const BO_DOUBLECLICK_TD = 'https://td.doubleclick.net'; +// export const BO_RTBHOUSE = 'https://f.creativecdn.com'; +// export const BO_CRITEO_US = 'https://fledge.us.criteo.com'; +// export const BO_CRITEO_EU = 'https://fledge.eu.criteo.com'; +// export const BO_CRITEO_AS = 'https://fledge.as.criteo.com'; +// export const BO_CRITEO_GRID_MERCURY = 'https://grid-mercury.criteo.com'; +// export const BO_CRITEO_BIDSWITCH_TRADR = 'https://tradr.bsw-sb.criteo.com'; +// export const BO_CRITEO_BIDSWITCH_SANDBOX = 'https://dsp-paapi-sandbox.bsw-ig.criteo.com'; +// export const BO_APPSPOT = 'https://fledge-buyer-testing-1.uc.r.appspot.com'; +// export const BO_OPTABLE = 'https://ads.optable.co'; +// export const BO_ADROLL = 'https://x.adroll.com'; +// export const BO_ADFORM = 'https://a2.adform.net'; +// export const BO_RETARGETLY = 'https://cookieless-campaign.prd-00.retargetly.com'; +// export const BO_AUDIGENT = 'https://proton.ad.gt'; +// export const BO_YAHOO = 'https://pa.ybp.yahoo.com'; +// export const BO_DOTOMI = 'https://usadmm.dotomi.com'; diff --git a/modules/ringieraxelspringerBidAdapter.js b/modules/ringieraxelspringerBidAdapter.js index 1fd6e327b9b..14033c1247f 100644 --- a/modules/ringieraxelspringerBidAdapter.js +++ b/modules/ringieraxelspringerBidAdapter.js @@ -7,6 +7,7 @@ import { } from '../src/utils.js'; import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; +import { BO_CSR_ONET } from '../libraries/paapiTools/buyerOrigins.js'; const BIDDER_CODE = 'ringieraxelspringer'; const VERSION = '1.0'; @@ -314,9 +315,9 @@ const parseAuctionConfigs = (serverResponse, bidRequest) => { auctionConfigs.push({ 'bidId': bid.bidId, 'config': { - 'seller': 'https://csr.onet.pl', - 'decisionLogicUrl': `https://csr.onet.pl/${encodeURIComponent(bid.params.network)}/v1/protected-audience-api/decision-logic.js`, - 'interestGroupBuyers': ['https://csr.onet.pl'], + 'seller': BO_CSR_ONET, + 'decisionLogicUrl': `${BO_CSR_ONET}/${encodeURIComponent(bid.params.network)}/v1/protected-audience-api/decision-logic.js`, + 'interestGroupBuyers': [ BO_CSR_ONET ], 'auctionSignals': { 'params': bid.params, 'sizes': bid.sizes, From 54230fb463e7d8e22a47e3aa2d69c986e9a2bd05 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Thu, 22 Aug 2024 15:21:07 -0400 Subject: [PATCH 0451/1097] Sharethrough Bid Adapter: support battr property in bid requests (#12162) * BATTR, include in bid request assembly * Updating bid adapter and unit-test files to account for `battr` possibly being an attribute a publisher specified in their ad-unit setup. * Sharethrough bid adapter: also check ortb2Imp for battr * Updating bid adapter logic to look for `battr` in `ortb2Imp.banner` in addition to `mediaTypes.banner`. * Updating unit tests to verify that `battr` from `mediaTypes.banner` is preferred over the prop from `ortb2Imp.banner` --- modules/sharethroughBidAdapter.js | 3 + .../modules/sharethroughBidAdapter_spec.js | 58 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index e8038ae233d..622759f875a 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -157,6 +157,7 @@ export const sharethroughAdapterSpec = { plcmt: videoRequest.plcmt ? videoRequest.plcmt : null, }; + if (videoRequest.battr) impression.video.battr = videoRequest.battr; if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery; if (videoRequest.companiontype) impression.video.companiontype = videoRequest.companiontype; if (videoRequest.companionad) impression.video.companionad = videoRequest.companionad; @@ -166,6 +167,8 @@ export const sharethroughAdapterSpec = { topframe: inIframe() ? 0 : 1, format: bidReq.sizes.map((size) => ({ w: +size[0], h: +size[1] })), }; + const battr = deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr') + if (battr) impression.banner.battr = battr } return { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 276860b6be6..14cabdc153e 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -4,6 +4,7 @@ import * as sinon from 'sinon'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config'; import * as utils from 'src/utils'; +import { deepSetValue } from '../../../src/utils'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); @@ -74,6 +75,7 @@ describe('sharethrough adapter spec', function () { mediaTypes: { banner: { pos: 1, + battr: [6, 7], }, }, ortb2Imp: { @@ -227,6 +229,7 @@ describe('sharethrough adapter spec', function () { skipmin: 10, skipafter: 20, delivery: 1, + battr: [13, 14], companiontype: 'companion type', companionad: 'companion ad', context: 'instream', @@ -548,8 +551,58 @@ describe('sharethrough adapter spec', function () { ]); }); + it('should correctly harvest battr values for banner if present in mediaTypes.banner of impression and battr is not defined in ortb2Imp.banner', () => { + // assemble + const EXPECTED_BATTR_VALUES = [6, 7]; + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + + // assert + expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); + }); + + it('should not include battr values for banner if NOT present in mediaTypes.banner of impression and battr is not defined in ortb2Imp.banner', () => { + // assemble + delete bidRequests[0].mediaTypes.banner.battr; + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + + // assert + expect(builtRequest.data.imp[0].banner.battr).to.be.undefined; + }); + + it('should prefer battr values from mediaTypes.banner over ortb2Imp.banner', () => { + // assemble + deepSetValue(bidRequests[0], 'ortb2Imp.banner.battr', [1, 2, 3]); + const EXPECTED_BATTR_VALUES = [6, 7]; // values from mediaTypes.banner + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + + // assert + expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); + }); + + it('should use battr values from ortb2Imp.banner if mediaTypes.banner.battr is not present', () => { + // assemble + delete bidRequests[0].mediaTypes.banner.battr; + const EXPECTED_BATTR_VALUES = [1, 2, 3]; + deepSetValue(bidRequests[0], 'ortb2Imp.banner.battr', EXPECTED_BATTR_VALUES); + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + + // assert + expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); + }); + it('should default to pos 0 if not provided', () => { - delete bidRequests[0].mediaTypes; + delete bidRequests[0].mediaTypes.banner.pos; const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; const bannerImp = builtRequest.data.imp[0].banner; @@ -579,6 +632,7 @@ describe('sharethrough adapter spec', function () { expect(videoImp.skipafter).to.equal(20); expect(videoImp.placement).to.equal(1); expect(videoImp.delivery).to.equal(1); + expect(videoImp.battr).to.deep.equal([13, 14]); expect(videoImp.companiontype).to.equal('companion type'); expect(videoImp.companionad).to.equal('companion ad'); }); @@ -599,6 +653,7 @@ describe('sharethrough adapter spec', function () { delete bidRequests[1].mediaTypes.video.skipafter; delete bidRequests[1].mediaTypes.video.placement; delete bidRequests[1].mediaTypes.video.delivery; + delete bidRequests[1].mediaTypes.video.battr; delete bidRequests[1].mediaTypes.video.companiontype; delete bidRequests[1].mediaTypes.video.companionad; @@ -621,6 +676,7 @@ describe('sharethrough adapter spec', function () { expect(videoImp.skipafter).to.equal(0); expect(videoImp.placement).to.equal(1); expect(videoImp.delivery).to.be.undefined; + expect(videoImp.battr).to.be.undefined; expect(videoImp.companiontype).to.be.undefined; expect(videoImp.companionad).to.be.undefined; }); From 5bb13cfc421d04d955d3286a61a4980225ecb0ec Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 22 Aug 2024 20:36:12 +0000 Subject: [PATCH 0452/1097] Prebid 9.11.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7146239e05b..a2a2461415a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.11.0-pre", + "version": "9.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.11.0-pre", + "version": "9.11.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 17ca7dd6a40..0ab9b9810d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.11.0-pre", + "version": "9.11.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 3b86b5a5127d9a59329ba95079dd7e127a599329 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 22 Aug 2024 20:36:13 +0000 Subject: [PATCH 0453/1097] Increment version to 9.12.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a2a2461415a..d2c5d20b1f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.11.0", + "version": "9.12.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.11.0", + "version": "9.12.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 0ab9b9810d2..2734420071b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.11.0", + "version": "9.12.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From cd6681cc9d5917a67a9df45450d78afe3d6cbba6 Mon Sep 17 00:00:00 2001 From: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:56:40 +0200 Subject: [PATCH 0454/1097] ConnectAd Bid Adapter: Sync endpoint Update (#11650) * ConnectAd Adapter Update PreBid Version 9 fix Image Sync PreBid Client Transform DSA GPP PreBid Client Timeout SellerDefinedAudience Seller Defined Context Sua IAB Cat in Repsonse Global Placement ID (gpid) * Update connectadBidAdapter.js --------- Co-authored-by: Patrick McCann Co-authored-by: Patrick McCann --- modules/connectadBidAdapter.js | 164 ++++----- test/spec/modules/connectadBidAdapter_spec.js | 330 ++++++++++++++++-- 2 files changed, 380 insertions(+), 114 deletions(-) diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 5b892a6df22..8424c9d57bf 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -1,8 +1,9 @@ -import { deepSetValue, logWarn } from '../src/utils.js'; +import { deepAccess, deepSetValue, mergeDeep, logWarn, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; + const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; @@ -30,25 +31,39 @@ export const spec = { return ret; } + const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); + const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); + const data = Object.assign({ placements: [], time: Date.now(), - user: {}, - // TODO: does the fallback to window.location make sense? - url: bidderRequest.refererInfo?.page || window.location.href, + url: bidderRequest.refererInfo?.page, referrer: bidderRequest.refererInfo?.ref, - // TODO: please do not send internal data structures over the network - referrer_info: bidderRequest.refererInfo?.legacy, screensize: getScreenSize(), dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: navigator.language, ua: navigator.userAgent, - pversion: '$prebid.version$' + pversion: '$prebid.version$', + cur: 'USD', + user: {}, + regs: {}, + source: {}, + site: {}, + sda: sellerDefinedAudience, + sdc: sellerDefinedContext, + }); + + const ortb2Params = bidderRequest?.ortb2 || {}; + ['site', 'user', 'device', 'bcat', 'badv', 'regs'].forEach(entry => { + const ortb2Param = ortb2Params[entry]; + if (ortb2Param) { + mergeDeep(data, { [entry]: ortb2Param }); + } }); // coppa compliance if (config.getConfig('coppa') === true) { - deepSetValue(data, 'user.coppa', 1); + deepSetValue(data, 'regs.coppa', 1); } // adding schain object @@ -71,24 +86,50 @@ export const spec = { deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); } + // GPP Support + if (bidderRequest?.gppConsent?.gppString) { + deepSetValue(data, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(data, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(data, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(data, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + + // DSA Support + if (bidderRequest?.ortb2?.regs?.ext?.dsa) { + deepSetValue(data, 'regs.ext.dsa', bidderRequest.ortb2.regs.ext.dsa); + } + // EIDS Support if (validBidRequests[0].userIdAsEids) { deepSetValue(data, 'user.ext.eids', validBidRequests[0].userIdAsEids); } - validBidRequests.map(bid => { + const tid = deepAccess(bidderRequest, 'ortb2.source.tid') + if (tid) { + deepSetValue(data, 'source.tid', tid) + } + data.tmax = bidderRequest.timeout; + } + + validBidRequests.map(bid => { const placement = Object.assign({ - // TODO: fix transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 - id: bid.transactionId, + id: generateUUID(), divName: bid.bidId, + tagId: bid.adUnitCode, pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0], sizes: bid.mediaTypes.banner.sizes, - adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes), bidfloor: getBidFloor(bid), siteId: bid.params.siteId, - networkId: bid.params.networkId + networkId: bid.params.networkId, + tid: bid.ortb2Imp?.ext?.tid }); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + placement.gpid = gpid; + } + if (placement.networkId && placement.siteId) { data.placements.push(placement); } @@ -126,12 +167,22 @@ export const spec = { bid.width = decision.width; bid.height = decision.height; bid.dealid = decision.dealid || null; - bid.meta = { advertiserDomains: decision && decision.adomain ? decision.adomain : [] }; + bid.meta = { + advertiserDomains: decision && decision.adomain ? decision.adomain : [] + }; bid.ad = retrieveAd(decision); bid.currency = 'USD'; bid.creativeId = decision.adId; bid.ttl = 360; bid.netRevenue = true; + + if (decision.dsa) { + bid.meta = Object.assign({}, bid.meta, { dsa: decision.dsa }) + } + if (decision.category) { + bid.meta = Object.assign({}, bid.meta, { primaryCatId: decision.category }) + } + bidResponses.push(bid); } } @@ -140,8 +191,15 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - let syncEndpoint = 'https://cdn.connectad.io/connectmyusers.php?'; + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncEndpoint; + + if (pixelType == 'iframe') { + syncEndpoint = 'https://sync.connectad.io/iFrameSyncer?'; + } else { + syncEndpoint = 'https://sync.connectad.io/ImageSyncer?'; + } if (gdprConsent) { syncEndpoint = tryAppendQueryString(syncEndpoint, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); @@ -155,73 +213,26 @@ export const spec = { syncEndpoint = tryAppendQueryString(syncEndpoint, 'us_privacy', uspConsent); } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gpp', gppConsent.gppString); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gpp_sid', gppConsent?.applicableSections?.join(',')); + } + if (config.getConfig('coppa') === true) { syncEndpoint = tryAppendQueryString(syncEndpoint, 'coppa', 1); } - if (syncOptions.iframeEnabled) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { return [{ - type: 'iframe', + type: pixelType, url: syncEndpoint }]; } else { - logWarn('Bidder ConnectAd: Please activate iFrame Sync'); + logWarn('Bidder ConnectAd: No User-Matching allowed'); } } }; -const sizeMap = [ - null, - '120x90', - '200x200', - '468x60', - '728x90', - '300x250', - '160x600', - '120x600', - '300x100', - '180x150', - '336x280', - '240x400', - '234x60', - '88x31', - '120x60', - '120x240', - '125x125', - '220x250', - '250x250', - '250x90', - '0x0', - '200x90', - '300x50', - '320x50', - '320x480', - '185x185', - '620x45', - '300x125', - '800x250', - '980x120', - '980x150', - '320x150', - '300x300', - '200x600', - '320x500', - '320x320' -]; - -sizeMap[77] = '970x90'; -sizeMap[123] = '970x250'; -sizeMap[43] = '300x600'; -sizeMap[286] = '970x66'; -sizeMap[3230] = '970x280'; -sizeMap[429] = '486x60'; -sizeMap[374] = '700x500'; -sizeMap[934] = '300x1050'; -sizeMap[1578] = '320x100'; -sizeMap[331] = '320x250'; -sizeMap[3301] = '320x267'; -sizeMap[2730] = '728x250'; - function getBidFloor(bidRequest) { let floorInfo = {}; @@ -238,17 +249,6 @@ function getBidFloor(bidRequest) { return floor; } -function getSize(sizes) { - const result = []; - sizes.forEach(function(size) { - const index = sizeMap.indexOf(size[0] + 'x' + size[1]); - if (index >= 0) { - result.push(index); - } - }); - return result; -} - function retrieveAd(decision) { return decision.contents && decision.contents[0] && decision.contents[0].body; } diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index d8dfcb0ce98..f067d801c5e 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/connectadBidAdapter.js'; import { config } from 'src/config.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import assert from 'assert'; describe('ConnectAd Adapter', function () { let bidRequests; @@ -26,7 +27,13 @@ describe('ConnectAd Adapter', function () { bidId: '2f95c00074b931', auctionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', bidderRequestId: '1c56ad30b9b8ca8', - transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df' + transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', + ortb2Imp: { + ext: { + tid: '601bda1a-01a9-4de9-b8f3-649d3bdd0d8f', + gpid: '/12345/homepage-leftnav' + } + }, } ]; @@ -69,6 +76,10 @@ describe('ConnectAd Adapter', function () { } }); + afterEach(function () { + config.resetConfig(); + }); + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -193,30 +204,28 @@ describe('ConnectAd Adapter', function () { }); it('should build a request if Consent but no gdprApplies', function () { - let bidderRequest = { + let localbidderRequest = { timeout: 3000, gdprConsent: { gdprApplies: false, consentString: 'consentDataString', }, } - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, localbidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.placements[0].adTypes).to.be.an('array'); expect(requestparse.placements[0].siteId).to.equal(123456); expect(requestparse.user.ext.consent).to.equal('consentDataString'); }); it('should build a request if gdprConsent empty', function () { - let bidderRequest = { + let localbidderRequest = { timeout: 3000, gdprConsent: {} } - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, localbidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.placements[0].adTypes).to.be.an('array'); expect(requestparse.placements[0].siteId).to.equal(123456); }); @@ -239,7 +248,7 @@ describe('ConnectAd Adapter', function () { it('should not include schain when not provided', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.source).to.not.exist; + expect(requestparse).to.not.have.property('source.ext.schain'); }); it('should submit coppa if set in config', function () { @@ -248,7 +257,17 @@ describe('ConnectAd Adapter', function () { .returns(true); const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.user.coppa).to.equal(1); + expect(requestparse.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should not set coppa when coppa is not provided or is set to false', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(false); + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + assert.equal(requestparse.regs.coppa, undefined); config.getConfig.restore(); }); @@ -259,27 +278,96 @@ describe('ConnectAd Adapter', function () { expect(requestparse.user.ext.eids[0].uids[0].id).to.equal('123456'); }); - it('should add referer info', function () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequ = { - refererInfo: { - page: 'https://connectad.io/page.html', - legacy: { - referer: 'https://connectad.io/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'https://connectad.io/page.html', - 'https://connectad.io/iframe1.html', - 'https://connectad.io/iframe2.html' - ] + it('should include DSA signals', function () { + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'domain1.com', + dsaparams: [1] + }, + { + domain: 'domain2.com', + dsaparams: [1, 2] + } + ] + }; + + let bidRequest = { + ortb2: { + regs: { + ext: { + dsa + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + assert.deepEqual(data.regs.ext.dsa, dsa); + }); + + it('should pass auction level tid', function() { + const bidRequest = Object.assign([], bidRequests); + + const localBidderRequest = { + ...bidderRequest, + ortb2: { + source: { + tid: '9XSL9B79XM' } } } - const request = spec.buildRequests([bidRequest], bidderRequ); + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.source?.tid).to.equal('9XSL9B79XM') + }); + + it('should pass gpid', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].gpid).to.equal('/12345/homepage-leftnav'); + }); + + it('should pass impression level tid', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].tid).to.equal('601bda1a-01a9-4de9-b8f3-649d3bdd0d8f'); + }); + + it('should pass first party data', function() { + const bidRequest = Object.assign([], bidRequests); + + const localBidderRequest = { + ...bidderRequest, + ortb2: { + bcat: ['IAB1', 'IAB2-1'], + badv: ['xyz.com', 'zyx.com'], + site: { ext: { data: 'some site data' } }, + device: { ext: { data: 'some device data' } }, + user: { ext: { data: 'some user data' } }, + regs: { ext: { data: 'some regs data' } } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(localBidderRequest.ortb2.bcat); + expect(data.badv).to.deep.equal(localBidderRequest.ortb2.badv); + expect(data.site).to.nested.include({'ext.data': 'some site data'}); + expect(data.device).to.nested.include({'ext.data': 'some device data'}); + expect(data.user).to.nested.include({'ext.data': 'some user data'}); + expect(data.regs).to.nested.include({'ext.data': 'some regs data'}); + }); + + it('should accept tmax from global config if not set by requestBids method', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.referrer_info).to.exist; + expect(requestparse.tmax).to.deep.equal(3000); }); it('should populate schain', function () { @@ -313,6 +401,76 @@ describe('ConnectAd Adapter', function () { }); }); + describe('GPP Implementation', function() { + it('should check with GPP Consent', function () { + let bidRequest = { + gppConsent: { + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'fullGppData': { + 'sectionId': 3, + 'gppVersion': 1, + 'sectionList': [ + 5, + 7 + ], + 'applicableSections': [ + 5 + ], + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'pingData': { + 'cmpStatus': 'loaded', + 'gppVersion': '1.0', + 'cmpDisplayStatus': 'visible', + 'supportedAPIs': [ + 'tcfca', + 'usnat', + 'usca', + 'usva', + 'usco', + 'usut', + 'usct' + ], + 'cmpId': 31 + }, + 'eventName': 'sectionChange' + }, + 'applicableSections': [ + 5 + ], + 'apiVersion': 1 + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + + it('should check without GPP Consent', function () { + let bidRequest = {}; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal(undefined); + }); + + it('should check with GPP Consent read from OpenRTB2', function () { + let bidRequest = { + ortb2: { + regs: { + 'gpp': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'gpp_sid': [ + 5 + ] + } + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + }); + describe('bid responses', function () { it('should return complete bid response with adomain', function () { const ADOMAINS = ['connectad.io']; @@ -348,6 +506,53 @@ describe('ConnectAd Adapter', function () { expect(bids[0].meta.advertiserDomains).to.deep.equal(ADOMAINS); }); + it('should process meta response object', function () { + const ADOMAINS = ['connectad.io']; + const dsa = { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'dsp1domain.com', + dsaparams: [1, 2] + }], + adrender: 1 + }; + + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + adomain: ['connectad.io'], + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '250', + width: '300', + dsa: dsa, + category: 'IAB123', + pricing: { + clearPrice: 11.899999999999999 + } + } + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(11.899999999999999); + expect(bids[0].width).to.equal('300'); + expect(bids[0].height).to.equal('250'); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].meta.advertiserDomains).to.deep.equal(ADOMAINS); + expect(bids[0].meta.dsa).to.equal(dsa); + expect(bids[0].meta.primaryCatId).to.equal('IAB123'); + }); + it('should return complete bid response with empty adomain', function () { const ADOMAINS = []; @@ -471,22 +676,59 @@ describe('ConnectAd Adapter', function () { }); }); + describe('GPP Sync', function() { + it('should concatenate gppString and applicableSections values in the returned image url', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5] }; + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'image', + url: `https://sync.connectad.io/ImageSyncer?gpp=DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN&gpp_sid=5&` + }]); + }); + + it('should concatenate gppString and applicableSections values in the returned iFrame url', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5, 6] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', + url: `https://sync.connectad.io/iFrameSyncer?gpp=DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN&gpp_sid=5%2C6&` + }]); + }); + + it('should return url without Gpp consent if gppConsent is undefined', () => { + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, undefined); + expect(result).to.deep.equal([{ + type: 'iframe', + url: `https://sync.connectad.io/iFrameSyncer?` + }]); + }); + + it('should return iFrame url without Gpp consent if gppConsent.gppString is undefined', () => { + const gppConsent = { applicableSections: ['5'] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', + url: `https://sync.connectad.io/iFrameSyncer?` + }]); + }); + }); + describe('getUserSyncs', () => { let testParams = [ { name: 'iframe/no gdpr or ccpa', - arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null], + arguments: [{ iframeEnabled: true, pixelEnabled: false }, {}, null], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?'] + pixels: ['https://sync.connectad.io/iFrameSyncer?'] } }, { name: 'iframe/gdpr', - arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + arguments: [{ iframeEnabled: true, pixelEnabled: false }, {}, {gdprApplies: true, consentString: '234234'}], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?gdpr=1&gdpr_consent=234234&'] + pixels: ['https://sync.connectad.io/iFrameSyncer?gdpr=1&gdpr_consent=234234&'] } }, { @@ -494,15 +736,39 @@ describe('ConnectAd Adapter', function () { arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null, 'YN12'], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?us_privacy=YN12&'] + pixels: ['https://sync.connectad.io/iFrameSyncer?us_privacy=YN12&'] } }, { name: 'iframe/ccpa & gdpr', - arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + arguments: [{ iframeEnabled: true, pixelEnabled: false }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + expect: { + type: 'iframe', + pixels: ['https://sync.connectad.io/iFrameSyncer?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + } + }, + { + name: 'image/ccpa & gdpr', + arguments: [{ iframeEnabled: false, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + expect: { + type: 'image', + pixels: ['https://sync.connectad.io/ImageSyncer?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + } + }, + { + name: 'image/gdpr', + arguments: [{ iframeEnabled: false, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + expect: { + type: 'image', + pixels: ['https://sync.connectad.io/ImageSyncer?gdpr=1&gdpr_consent=234234&'] + } + }, + { + name: 'should prioritize iframe over image for user sync', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + pixels: ['https://sync.connectad.io/iFrameSyncer?'] } } ]; From 3c30238be9709a37a38ca78263243e0a931d793e Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Fri, 23 Aug 2024 15:02:13 +0100 Subject: [PATCH 0455/1097] Sovrn bid adapter add ortb2 device (#11784) * Sovrn Bid Adapter: Add full ORTB2 device data to request payload * Sovrn Bid Adapter: Add test to verify presence of ORTB2 device data in request --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/sovrnBidAdapter.js | 11 +++++++++- test/spec/modules/sovrnBidAdapter_spec.js | 25 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 53f6fb2f40d..4fa3ebc9b40 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -6,7 +6,10 @@ import { logError, deepAccess, isInteger, - logWarn, getBidIdParameter, isEmptyStr + logWarn, + getBidIdParameter, + isEmptyStr, + mergeDeep } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { @@ -198,6 +201,12 @@ export const spec = { deepSetValue(sovrnBidReq, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); } + // if present, merge device object from ortb2 into `sovrnBidReq.device` + if (bidderRequest?.ortb2?.device) { + sovrnBidReq.device = sovrnBidReq.device || {}; + mergeDeep(sovrnBidReq.device, bidderRequest.ortb2.device); + } + if (eids) { deepSetValue(sovrnBidReq, 'user.ext.eids', eids) if (criteoId) { diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 2d6af1f964f..c684f8691aa 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -421,6 +421,31 @@ describe('sovrnBidAdapter', function() { expect(regs.gpp_sid).to.include(8) }) + it('should add ORTB2 device data to the request', function () { + const bidderRequest = { + ...baseBidderRequest, + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }; + + const request = spec.buildRequests([baseBidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(bidderRequest.ortb2.device); + }); + it('should not send gpp info when gppConsent is not defined', function () { const bidderRequest = { ...baseBidderRequest, From 99e16eb3990a32cd1c58fa265513744d973801a4 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 23 Aug 2024 10:51:14 -0400 Subject: [PATCH 0456/1097] Update connectadBidAdapter.js (#12170) --- modules/connectadBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 8424c9d57bf..6789c937afb 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -110,9 +110,8 @@ export const spec = { deepSetValue(data, 'source.tid', tid) } data.tmax = bidderRequest.timeout; - } - validBidRequests.map(bid => { + validBidRequests.map(bid => { const placement = Object.assign({ id: generateUUID(), divName: bid.bidId, From 52414693c34f74e62bc98503afc7f3f89c9faa6d Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Sat, 24 Aug 2024 02:49:20 +0300 Subject: [PATCH 0457/1097] IntentIq Analytics Adapter: add pcid value to the payload (#12169) * Add pcid value to the payload * Update intentIqIdSystem.js --------- Co-authored-by: Patrick McCann --- modules/intentIqAnalyticsAdapter.js | 6 ++++-- modules/intentIqIdSystem.js | 6 +++++- test/spec/modules/intentIqAnalyticsAdapter_spec.js | 13 ++++++++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 10ce8097bf1..ae3f252b412 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -16,7 +16,7 @@ export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); const FIRST_PARTY_KEY = '_iiq_fdata'; const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -const JSVERSION = 0.1 +const JSVERSION = 0.2 const PARAMS_NAMES = { abTestGroup: 'abGroup', @@ -50,7 +50,8 @@ const PARAMS_NAMES = { referrer: 'vrref', isInBrowserBlacklist: 'inbbl', prebidVersion: 'pbjsver', - partnerId: 'partnerId' + partnerId: 'partnerId', + firstPartyId: 'pcid' }; let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { @@ -157,6 +158,7 @@ export function preparePayload(data) { result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup == 'A'; result[PARAMS_NAMES.agentId] = REPORTER_ID; + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) fillPrebidEventData(data, result); diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 434caae7ffb..90b470ae919 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -32,7 +32,7 @@ export const OPT_OUT = 'O'; export const BLACK_LIST = 'L'; export const CLIENT_HINTS_KEY = '_iiq_ch'; export const EMPTY = 'EMPTY' -export const VERSION = 0.1 +export const VERSION = 0.2 const encoderCH = { brands: 0, @@ -223,6 +223,10 @@ export function detectBrowserFromUserAgent(userAgent) { return 'unknown'; } +/** + * @typedef {Object} NavigatorUAData + * @property {Array<{brand: string, version: string}>} brands - The list of brands associated with the user agent. + */ /** * Detects the browser from the NavigatorUAData object diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 9a204dde31b..3b1b25543fc 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -12,6 +12,7 @@ import { REPORTER_ID, getReferrer, preparePayload } from '../../../modules/inten const partner = 10; const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; +const version = 0.2; const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); @@ -111,7 +112,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); - expect(request.url).to.contain('&jsver=0.1&vrref=http://localhost:9876/'); + expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); expect(request.url).to.contain('&payload='); expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); }); @@ -128,23 +129,25 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); - expect(request.url).to.contain('&jsver=0.1&vrref=http://localhost:9876/'); + expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); expect(request.url).to.contain('iiqid=testpcid'); }); it('should handle BID_WON event with default group configuration', function () { localStorage.setItem(FIRST_PARTY_KEY, defaultData); + const defaultDataObj = JSON.parse(defaultData) events.emit(EVENTS.BID_WON, wonRequest); expect(server.requests.length).to.be.above(0); const request = server.requests[0]; - const data = preparePayload(wonRequest); - const base64String = btoa(JSON.stringify(data)); + const dataToSend = preparePayload(wonRequest); + const base64String = btoa(JSON.stringify(dataToSend)); const payload = `[%22${base64String}%22]`; expect(request.url).to.equal( - `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344&agid=${REPORTER_ID}&jsver=0.1&vrref=${getReferrer()}&source=pbjs&payload=${payload}` + `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}` ); + expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) }); it('should not send request if manualReport is true', function () { From 769e7fecb052b572dcab507f17f9c9c56180d18e Mon Sep 17 00:00:00 2001 From: Takamasa-Murano Date: Sat, 24 Aug 2024 08:52:23 +0900 Subject: [PATCH 0458/1097] ssp_geniee Bid Adapter : initial release (#12131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 作り直し * modify about geparams/gecuparams * modified to reflect the points raised in the comments * modify about currency * remove temporary function * modify about md file * modify to reslove warings * modify to reslove warings * modify to reslove warings --------- Co-authored-by: Murano Takamasa --- modules/ssp_genieeBidAdapter.js | 437 ++++++++++++++++++ modules/ssp_genieeBidAdapter.md | 40 ++ .../spec/modules/ssp_genieeBidAdapter_spec.js | 421 +++++++++++++++++ 3 files changed, 898 insertions(+) create mode 100644 modules/ssp_genieeBidAdapter.js create mode 100644 modules/ssp_genieeBidAdapter.md create mode 100644 test/spec/modules/ssp_genieeBidAdapter_spec.js diff --git a/modules/ssp_genieeBidAdapter.js b/modules/ssp_genieeBidAdapter.js new file mode 100644 index 00000000000..dc4cd13d4a1 --- /dev/null +++ b/modules/ssp_genieeBidAdapter.js @@ -0,0 +1,437 @@ +/* eslint-disable camelcase */ +import * as utils from '../src/utils.js'; +import { isPlainObject } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; +import { highEntropySUAAccessor } from '../src/fpd/sua.js'; +import { config } from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + +const BIDDER_CODE = 'ssp_geniee'; +export const BANNER_ENDPOINT = 'https://aladdin.genieesspv.jp/yie/ld/api/ad_call/v2'; +// export const ENDPOINT_USERSYNC = ''; +const SUPPORTED_MEDIA_TYPES = [ BANNER ]; +const DEFAULT_CURRENCY = 'JPY'; +const ALLOWED_CURRENCIES = ['USD', 'JPY']; +const NET_REVENUE = true; +const MODULE_NAME = `ssp_geniee`; +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}) + +/** + * List of keys for geparams (parameters we use) + * key: full name of the parameter + * value: shortened name used in geparams + */ +const GEPARAMS_KEY = { + /** + * location.href whose protocol is not http + */ + LOCATION: 'loc', + /** + * document.referrer whose protocol is not http + */ + REFERRER: 'ref', + /** + * URL parameter to be linked to clicks + */ + GENIEE_CT0: 'ct0', + /** + * zipcode + */ + ZIP: 'zip', + /** + * country + */ + COUNTRY: 'country', + /** + * city + */ + CITY: 'city', + /** + * longitude + */ + LONGITUDE: 'long', + /** + * lattitude + */ + LATITUDE: 'lati', + /** + * for customised parameters + */ + CUSTOM: 'custom', + /** + * advertising identifier for iOS + */ + IDENTIFIER_FOR_ADVERTISERS: 'idfa', + /** + * tracked Ad restrictions for iOS + */ + LIMIT_AD_TRACKING: 'lat', + /** + * bundle ID of iOS applications? + */ + BUNDLE: 'bundle', +}; + +/** + * List of keys for gecuparams (parameters we use) + * key: full name of the parameter + * value: shortened name used in geparams + */ +const GECUPARAMS_KEY = { + /** + * version no of gecuparams + */ + VERSION: 'ver', + /** + * minor version no of gecuparams + */ + MINOR_VERSION: 'minor', + /** + * encrypted value of LTSV format + */ + VALUE: 'value', +}; + +/** + * executing encodeURIComponent including single quotation + * @param {string} str + * @returns + */ +function encodeURIComponentIncludeSingleQuotation(str) { + return encodeURIComponent(str).replace(/'/g, '%27'); +} + +/** + * Checking "params" has a value for the key "key" and it is not undefined, null, or an empty string + * To support IE in the same way, we cannot use the ?? operator + * @param {Object} params + * @param {string} key + * @returns {boolean} + */ +function hasParamsNotBlankString(params, key) { + return ( + key in params && + typeof params[key] !== 'undefined' && + params[key] != null && + params[key] != '' + ); +} + +/** + * making request data be used commonly banner and native + * @see https://docs.prebid.org/dev-docs/bidder-adaptor.html#location-and-referrers + */ +function makeCommonRequestData(bid, geparameter, refererInfo) { + const data = { + zoneid: bid.params.zoneId, + cb: Math.floor(Math.random() * 99999999999), + charset: document.charset || document.characterSet || '', + loc: refererInfo?.page || refererInfo?.location || refererInfo?.topmostLocation || refererInfo?.legacy.referer || encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.LOCATION]) || '', + ct0: geparameter[GEPARAMS_KEY.GENIEE_CT0] !== 'undefined' + ? encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.GENIEE_CT0]) + : '', + referer: refererInfo?.ref || encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.REFERRER]) || '', + topframe: window.parent == window.self ? 1 : 0, + cur: bid.params.hasOwnProperty('currency') ? bid.params.currency : DEFAULT_CURRENCY, + requestid: bid.bidId, + ua: navigator.userAgent, + tpaf: 1, + cks: 1, + ib: 0, + }; + + try { + if (window.self.toString() !== '[object Window]' || window.parent.toString() !== '[object Window]') { + data.err = '1'; + } + } catch (e) {} + + if (GEPARAMS_KEY.IDENTIFIER_FOR_ADVERTISERS in geparameter) { + data.idfa = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.IDENTIFIER_FOR_ADVERTISERS]); + } + if (GEPARAMS_KEY.LIMIT_AD_TRACKING in geparameter) { + data.adtk = geparameter[GEPARAMS_KEY.LIMIT_AD_TRACKING] ? '0' : '1'; + } + // makeScreenSizeForQueryParameter + if (typeof screen !== 'undefined') { + const screenWidth = screen.width; + const screenHeight = screen.height; + if (screenWidth > screenHeight) { + data.sw = screenHeight; + data.sh = screenWidth; + } else { + data.sw = screenWidth; + data.sh = screenHeight; + } + } + // makeBannerJskQuery + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.ZIP)) { + data.zip = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.ZIP]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.COUNTRY)) { + data.country = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.COUNTRY]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.CITY)) { + data.city = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.CITY]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.LONGITUDE)) { + data.long = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.LONGITUDE]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.LATITUDE)) { + data.lati = encodeURIComponentIncludeSingleQuotation( + geparameter[GEPARAMS_KEY.LATITUDE] + ); + } + if (GEPARAMS_KEY.CUSTOM in geparameter && isPlainObject(geparameter[GEPARAMS_KEY.CUSTOM])) { + for (const c in geparameter[GEPARAMS_KEY.CUSTOM]) { + if (hasParamsNotBlankString(geparameter[GEPARAMS_KEY.CUSTOM], c)) { + data[encodeURIComponentIncludeSingleQuotation('custom_' + c)] = + encodeURIComponentIncludeSingleQuotation( + geparameter[GEPARAMS_KEY.CUSTOM][c] + ); + } + } + } + const gecuparameter = window.gecuparams || {}; + if (isPlainObject(gecuparameter)) { + if (hasParamsNotBlankString(gecuparameter, GECUPARAMS_KEY.VERSION)) { + data.gc_ver = encodeURIComponentIncludeSingleQuotation(gecuparameter[GECUPARAMS_KEY.VERSION]); + } + if (hasParamsNotBlankString(gecuparameter, GECUPARAMS_KEY.MINOR_VERSION)) { + data.gc_minor = encodeURIComponentIncludeSingleQuotation(gecuparameter[GECUPARAMS_KEY.MINOR_VERSION]); + } + if (hasParamsNotBlankString(gecuparameter, GECUPARAMS_KEY.VALUE)) { + data.gc_value = encodeURIComponentIncludeSingleQuotation(gecuparameter[GECUPARAMS_KEY.VALUE]); + } + } + + // imuid + const imuidQuery = getImuidAsQueryParameter(); + if (imuidQuery) data.extuid = imuidQuery; + + // makeUAQuery + // To avoid double encoding, not using encodeURIComponent here + const ua = JSON.parse(getUserAgent()); + if (ua && ua.fullVersionList) { + const fullVersionList = ua.fullVersionList.reduce((acc, cur) => { + let str = acc; + if (str) str += ','; + str += '"' + cur.brand + '";v="' + cur.version + '"'; + return str; + }, ''); + data.ucfvl = fullVersionList; + } + if (ua && ua.platform) data.ucp = '"' + ua.platform + '"'; + if (ua && ua.architecture) data.ucarch = '"' + ua.architecture + '"'; + if (ua && ua.platformVersion) data.ucpv = '"' + ua.platformVersion + '"'; + if (ua && ua.bitness) data.ucbit = '"' + ua.bitness + '"'; + data.ucmbl = '?' + (ua && ua.mobile ? '1' : '0'); + if (ua && ua.model) data.ucmdl = '"' + ua.model + '"'; + + return data; +} + +/** + * making request data for banner + */ +function makeBannerRequestData(bid, geparameter, refererInfo) { + const data = makeCommonRequestData(bid, geparameter, refererInfo); + + // this query is not used in nad endpoint but used in ad_call endpoint + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.BUNDLE)) { + data.apid = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.BUNDLE]); + } + + return data; +} + +/** + * making bid response be used commonly banner and native + */ +function makeCommonBidResponse(bid, width, height) { + return { + requestId: bid.requestid, + cpm: bid.price, + creativeId: bid.creativeId, + currency: bid.cur, + netRevenue: NET_REVENUE, + ttl: 700, + width: width, // width of the ad iframe + height: height, // height of the ad iframe + }; +} + +/** + * making bid response for banner + */ +function makeBannerBidResponse(bid, request) { + const bidResponse = makeCommonBidResponse(bid, bid.width, bid.height); + const loc = encodeURIComponentIncludeSingleQuotation( + window.top === window.self ? location.href : window.top.document.referrer + ); + const beacon = !bid.ib + ? '' + : ` +
+ +
`; + bidResponse.ad = makeBidResponseAd( + beacon + '
' + makeChangeHeightEventMarkup(request) + decodeURIComponent(bid.adm) + '
' + ); + bidResponse.mediaType = BANNER; + + return bidResponse; +} + +/** + * making change height event markup for af iframe. About passback ad, it is possible that ad image is cut off. To handle this, we add this event to change height after ad is loaded. + */ +function makeChangeHeightEventMarkup(request) { + return ( + '' + ); +} + +/** + * making bid response ad. This is also the value to be used by document.write in renderAd function. + * @param {string} innerHTML + * @returns + */ +function makeBidResponseAd(innerHTML) { + return '' + innerHTML + ''; +} + +/** + * add imuid script tag + */ +function appendImuidScript() { + const scriptEl = document.createElement('script'); + scriptEl.src = '//dmp.im-apps.net/scripts/im-uid-hook.js?cid=3929'; + scriptEl.async = true; + document.body.appendChild(scriptEl); +} + +/** + * return imuid strings as query parameters + */ +function getImuidAsQueryParameter() { + const imuid = storage.getCookie('_im_uid.3929'); + return imuid ? 'im:' + imuid : ''; // To avoid double encoding, not using encodeURIComponent here +} + +function getUserAgent() { + return storage.getDataFromLocalStorage('key') || null; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + /** + * Determines whether or not the given bid request is valid. + * @param {BidRequest} bidRequest The bid request params to validate. + * @return boolean True if this is a valid bid request, and false otherwise. + */ + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params.zoneId) return false; + const currencyType = config.getConfig('currency.adServerCurrency'); + if (typeof currencyType === 'string' && ALLOWED_CURRENCIES.indexOf(currencyType) === -1) { + utils.logError('Invalid currency type, we support only JPY and USD!'); + return false; + } + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {Array} validBidRequests an array of bid requests + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const serverRequests = []; + + const HIGH_ENTROPY_HINTS = [ + 'architecture', + 'model', + 'mobile', + 'platform', + 'bitness', + 'platformVersion', + 'fullVersionList', + ]; + + const uaData = window.navigator?.userAgentData; + if (uaData && uaData.getHighEntropyValues) { + const getHighEntropySUA = highEntropySUAAccessor(uaData); + getHighEntropySUA(HIGH_ENTROPY_HINTS).then((ua) => { + if (ua) { + storage.setDataInLocalStorage('ua', JSON.stringify(ua)); + } + }); + } + + validBidRequests.forEach((bid) => { + // const isNative = bid.mediaTypes?.native; + const geparameter = window.geparams || {}; + + serverRequests.push({ + method: 'GET', + url: BANNER_ENDPOINT, + data: makeBannerRequestData(bid, geparameter, bidderRequest?.refererInfo), + bid: bid, + }); + }); + + return serverRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidderRequest A matched bid request for this response. + * @return Array An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidderRequest) { + const bidResponses = []; + + if (!serverResponse || !serverResponse.body) { + return bidResponses; + } + + appendImuidScript(); + + const zoneId = bidderRequest.bid.params.zoneId; + let successBid; + successBid = serverResponse.body || {}; + + if (successBid.hasOwnProperty(zoneId)) { + const bid = successBid[zoneId]; + bidResponses.push(makeBannerBidResponse(bid, bidderRequest)); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + // if we need user sync, we add this part after preparing the endpoint + /* if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: ENDPOINT_USERSYNC + }); + } */ + + return syncs; + }, + onTimeout: function (timeoutData) {}, + onBidWon: function (bid) {}, + onSetTargeting: function (bid) {}, +}; + +registerBidder(spec); diff --git a/modules/ssp_genieeBidAdapter.md b/modules/ssp_genieeBidAdapter.md new file mode 100644 index 00000000000..7c2dc27511e --- /dev/null +++ b/modules/ssp_genieeBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Geniee Bid Adapter +Module Type: Bidder Adapter +Maintainer: supply-carpet@geniee.co.jp +``` + +# Description +This is [Geniee](https://geniee.co.jp) Bidder Adapter for Prebid.js. +(This is Geniee *SSP* Bidder Adapter. The another adapter named "Geniee Bid Adapter" is Geniee *DSP* Bidder Adapter.) + +Please contact us before using the adapter. + +We will provide ads when satisfy the following conditions: + +- There are a certain number bid requests by zone +- The request is a Banner ad +- Payment is possible in Japanese yen or US dollars +- The request is not for GDPR or COPPA users + +Thus, even if the following test, it will be no bids if the request does not reach a certain requests. + +# Test Parameters + +```js +var adUnits = [ + { + code: 'banner-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]], + } + }, + bids: [ + {} + ] + }, +]; +``` diff --git a/test/spec/modules/ssp_genieeBidAdapter_spec.js b/test/spec/modules/ssp_genieeBidAdapter_spec.js new file mode 100644 index 00000000000..b0bad74d59b --- /dev/null +++ b/test/spec/modules/ssp_genieeBidAdapter_spec.js @@ -0,0 +1,421 @@ +import { expect } from 'chai'; +import { + spec, + BANNER_ENDPOINT, +} from 'modules/ssp_genieeBidAdapter.js'; +import { config } from '../../../src/config.js'; + +describe('ssp_genieeBidAdapter', function () { + const ZONE_ID = 1234567; + const AD_UNIT_CODE = 'adunit-code'; + const BANNER_BID = { + bidder: spec.code, + params: { + zoneId: ZONE_ID, + invalidImpBeacon: false, + }, + adUnitCode: AD_UNIT_CODE, + sizes: [[300, 250]], + bidId: 'bidId12345', + bidderRequestId: 'bidderRequestId12345', + auctionId: 'auctionId12345', + }; + + function getGeparamsDefinedBid(bid, params) { + const newBid = { ...bid }; + newBid.params.geparams = params; + return newBid; + } + + function hasParamsNotBlankStringTestGeparams(param, query) { + it(`should set the ${query} query to geparams.${param} when geparams.${param} is neither undefined nor null nor a blank string`, function () { + window.geparams[param] = undefined; + let request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.geparams[param] = null; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.geparams[param] = ''; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + const value = 'hoge'; + window.geparams[param] = value; + request = spec.buildRequests([BANNER_BID]); + expect(JSON.stringify(request[0].data)).to.have.string(`"${query}":"${value}"`); + }); + } + + function hasParamsNotBlankStringTestGecuparams(param, query) { + it(`should set the ${query} query to gecuparams.${param} when gecuparams.${param} is neither undefined nor null nor a blank string`, function () { + window.gecuparams = {}; + window.gecuparams[param] = undefined; + let request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.gecuparams[param] = null; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.gecuparams[param] = ''; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + const value = 'hoge'; + window.gecuparams[param] = value; + request = spec.buildRequests([BANNER_BID]); + expect(JSON.stringify(request[0].data)).to.have.string(`"${query}":"${value}"`); + }); + } + + beforeEach(function () { + document.documentElement.innerHTML = ''; + const adTagParent = document.createElement('div'); + adTagParent.id = AD_UNIT_CODE; + document.body.appendChild(adTagParent); + }); + + describe('isBidRequestValid', function () { + it('should return true when params.zoneId exists and params.currency does not exist', function () { + expect(spec.isBidRequestValid(BANNER_BID)).to.be.true; + }); + + it('should return true when params.zoneId and params.currency exist and params.currency is JPY or USD', function () { + config.setConfig({ currency: { adServerCurrency: 'JPY' } }); + expect( + spec.isBidRequestValid({ + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }) + ).to.be.true; + config.setConfig({ currency: { adServerCurrency: 'USD' } }); + expect( + spec.isBidRequestValid({ + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }) + ).to.be.true; + }); + + it('should return false when params.zoneId does not exist', function () { + expect(spec.isBidRequestValid({ ...BANNER_BID, params: {} })).to.be.false; + }); + + it('should return false when params.zoneId and params.currency exist and params.currency is neither JPY nor USD', function () { + config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + expect( + spec.isBidRequestValid({ + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }) + ).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should changes the endpoint with banner ads or naive ads', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].url).to.equal(BANNER_ENDPOINT); + }); + + it('should return a ServerRequest where the bid is a bid for validBidRequests', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].bid).to.equal(BANNER_BID); + }); + + describe('QueryStringParameters', function () { + it('should sets the value of the zoneid query to bid.params.zoneId', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.zoneid)).to.have.string( + `${BANNER_BID.params.zoneId}` + ); + }); + + it('should sets the values for loc and referer queries when bidderRequest.refererInfo.referer has a value', function () { + const referer = 'https://example.com/'; + const request = spec.buildRequests([BANNER_BID], { + refererInfo: { legacy: { referer: referer }, ref: referer }, + }); + expect(String(request[0].data.loc)).to.have.string( + `${referer}` + ); + expect(String(request[0].data.referer)).to.have.string( + `${referer}` + ); + }); + + it('should makes the values of loc query and referer query geparams value when bidderRequest.refererInfo.referer is a falsy value', function () { + const loc = 'https://www.google.com/'; + const referer = 'https://example.com/'; + window.geparams = { + loc: 'https://www.google.com/', + ref: 'https://example.com/', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { loc: loc, ref: referer }), + ]); + expect(String(request[0].data.loc)).to.have.string( + `${encodeURIComponent(loc)}` + ); + expect(String(request[0].data.referer)).to.have.string( + `${encodeURIComponent(referer)}` + ); + }); + + it('should sets the value of the ct0 query to geparams.ct0', function () { + const ct0 = 'hoge'; + window.geparams = { + ct0: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { ct0: ct0 }), + ]); + expect(String(request[0].data.ct0)).to.have.string(`${ct0}`); + }); + + it('should replaces currency with JPY if there is no currency provided', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.cur)).to.have.string('JPY'); + }); + + it('should makes currency the value of params.currency when params.currency exists', function () { + const request = spec.buildRequests([ + { + ...BANNER_BID, + params: { ...BANNER_BID.params, currency: 'JPY' }, + }, + { + ...BANNER_BID, + params: { ...BANNER_BID.params, currency: 'USD' }, + }, + ]); + expect(String(request[0].data.cur)).to.have.string('JPY'); + expect(String(request[1].data.cur)).to.have.string('USD'); + }); + + it('should makes invalidImpBeacon the value of params.invalidImpBeacon when params.invalidImpBeacon exists (in current version, this parameter is not necessary and ib is always `0`)', function () { + const request = spec.buildRequests([ + { + ...BANNER_BID, + params: { ...BANNER_BID.params, invalidImpBeacon: true }, + }, + { + ...BANNER_BID, + params: { ...BANNER_BID.params, invalidImpBeacon: false }, + }, + { + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }, + ]); + expect(String(request[0].data.ib)).to.have.string('0'); + expect(String(request[1].data.ib)).to.have.string('0'); + expect(String(request[2].data.ib)).to.have.string('0'); + }); + + it('should not sets the value of the adtk query when geparams.lat does not exist', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('adtk'); + }); + + it('should sets the value of the adtk query to 0 when geparams.lat is truthy value', function () { + window.geparams = { + lat: 1, + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { lat: 1 }), + ]); + expect(String(request[0].data.adtk)).to.have.string('0'); + }); + + it('should sets the value of the adtk query to 1 when geparams.lat is falsy value', function () { + window.geparams = { + lat: 0, + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { lat: 0 }), + ]); + expect(String(request[0].data.adtk)).to.have.string('1'); + }); + + it('should sets the value of the idfa query to geparams.idfa', function () { + const idfa = 'hoge'; + window.geparams = { + idfa: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { idfa: idfa }), + ]); + expect(String(request[0].data.idfa)).to.have.string(`${idfa}`); + }); + + it('should set the sw query to screen.height and the sh query to screen.width when screen.width is greater than screen.height', function () { + const width = 1440; + const height = 900; + const stub = sinon.stub(window, 'screen').get(function () { + return { width: width, height: height }; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.sw)).to.have.string(`${height}`); + expect(String(request[0].data.sh)).to.have.string(`${width}`); + stub.restore(); + }); + + it('should set the sw query to screen.width and the sh query to screen.height when screen.width is not greater than screen.height', function () { + const width = 411; + const height = 731; + const stub = sinon.stub(window, 'screen').get(function () { + return { width: width, height: height }; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.sw)).to.have.string(`${width}`); + expect(String(request[0].data.sh)).to.have.string(`${height}`); + stub.restore(); + }); + + hasParamsNotBlankStringTestGeparams('zip', 'zip'); + hasParamsNotBlankStringTestGeparams('country', 'country'); + hasParamsNotBlankStringTestGeparams('city', 'city'); + hasParamsNotBlankStringTestGeparams('long', 'long'); + hasParamsNotBlankStringTestGeparams('lati', 'lati'); + + it('should set the custom query to geparams.custom', function () { + const params = { + custom: { + c1: undefined, + c2: null, + c3: '', + c4: 'hoge', + }, + }; + window.geparams = { + custom: { + c1: undefined, + c2: null, + c3: '', + c4: 'hoge', + }, + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, params), + ]); + expect(request[0].data).to.not.have.property('custom_c1'); + expect(request[0].data).to.not.have.property('custom_c2'); + expect(request[0].data).to.not.have.property('custom_c3'); + expect(request[0].data.custom_c4).to.have.string( + `${params.custom.c4}` + ); + }); + + hasParamsNotBlankStringTestGecuparams('ver', 'gc_ver'); + hasParamsNotBlankStringTestGecuparams('minor', 'gc_minor'); + hasParamsNotBlankStringTestGecuparams('value', 'gc_value'); + + it('should sets the value of the gfuid query to geparams.gfuid', function () { + const gfuid = 'hoge'; + window.geparams = { + gfuid: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { gfuid: gfuid }), + ]); + expect(request[0].data).to.not.have.property('gfuid'); + }); + + it('should sets the value of the adt query to geparams.adt', function () { + const adt = 'hoge'; + window.geparams = { + adt: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { adt: adt }), + ]); + expect(request[0].data).to.not.have.property('adt'); + }); + + it('should adds a query for naive ads and no query for banner ads', function () { + // const query = '&tkf=1&ad_track=1&apiv=1.1.0'; + const query_apiv = '1.1.0'; + const query_tkf = '1'; + const query_ad_track = '1'; + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.apiv)).to.not.have.string(query_apiv); + expect(String(request[0].data.tkf)).to.not.have.string(query_tkf); + expect(String(request[0].data.ad_track)).to.not.have.string(query_ad_track); + }); + + it('should sets the value of the apid query to geparams.bundle when media type is banner', function () { + const bundle = 'hoge'; + window.geparams = { + bundle: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { bundle: bundle }), + ]); + expect(String(request[0].data.apid)).to.have.string(`${bundle}`); + }); + + it('should not include the extuid query when it does not contain the imuid cookie', function () { + const stub = sinon.stub(document, 'cookie').get(function () { + return ''; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('extuid'); + stub.restore(); + }); + + it('should include an extuid query when it contains an imuid cookie', function () { + const imuid = 'b.a4ad1d3eeb51e600'; + const stub = sinon.stub(document, 'cookie').get(function () { + return `_im_uid.3929=${imuid}`; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.extuid)).to.have.string( + `${`im:${imuid}`}` + ); + stub.restore(); + }); + }); + }); + + describe('interpretResponse', function () { + const response = {}; + response[ZONE_ID] = { + creativeId: '', + cur: 'JPY', + price: 0.092, + width: 300, + height: 250, + requestid: '2e42361a6172bf', + adm: '', + }; + const expected = { + requestId: response[ZONE_ID].requestid, + cpm: response[ZONE_ID].price, + creativeId: response[ZONE_ID].creativeId, + netRevenue: true, + currency: 'JPY', + ttl: 700, + width: response[ZONE_ID].width, + height: response[ZONE_ID].height, + }; + + it('should sets the response correctly when it comes to banner ads', function () { + const expectedBanner = { + ...expected, + ad: + '
' + + response[ZONE_ID].adm + + '
', + mediaType: 'banner', + }; + const request = spec.buildRequests([BANNER_BID])[0]; + const result = spec.interpretResponse({ body: response }, request); + expect(result[0]).to.have.deep.equal(expectedBanner); + }); + }); +}); From 87e198f833d8f5689dc1dd6d5706abcddfb9cdff Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Sun, 25 Aug 2024 18:51:53 +0300 Subject: [PATCH 0459/1097] Add content language extraction in bidderUtils (#12172) This update adds a new constant `contentLang` that extracts the content language from the bidder request or defaults to the document's language. The `contentLang` constant is then included in the bid request data, ensuring the correct language information is passed along. --- libraries/vidazooUtils/bidderUtils.js | 3 +++ test/spec/modules/illuminBidAdapter_spec.js | 7 +++++++ test/spec/modules/kueezRtbBidAdapter_spec.js | 7 +++++++ test/spec/modules/shinezRtbBidAdapter_spec.js | 7 +++++++ test/spec/modules/tagorasBidAdapter_spec.js | 7 +++++++ test/spec/modules/twistDigitalBidAdapter_spec.js | 4 ++++ test/spec/modules/vidazooBidAdapter_spec.js | 4 ++++ 7 files changed, 39 insertions(+) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 4b9f2fe37d0..9b1473b0c28 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -246,6 +246,8 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); + const contentLang = deepAccess(bidderRequest, 'ortb2.site.content.language') || document.documentElement.lang; + if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ currency: 'USD', @@ -278,6 +280,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder gpid: gpid, cat: cat, contentData, + contentLang, userData: userData, pagecat: pagecat, transactionId: ortb2Imp?.ext?.tid, diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 3e809862b95..0e6e98c6f04 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -107,6 +107,11 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] @@ -331,6 +336,7 @@ describe('IlluminBidAdapter', function () { gpid: '0123456789', cat: [], contentData: [], + contentLang: 'en', isStorageAllowed: true, pagecat: [], userData: [] @@ -397,6 +403,7 @@ describe('IlluminBidAdapter', function () { 'ext.param2': 'dolorsitamet', cat: [], contentData: [], + contentLang: 'en', isStorageAllowed: true, pagecat: [], userData: [] diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index dd3ea4bfb2b..188fe005990 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -104,6 +104,11 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] @@ -327,6 +332,7 @@ describe('KueezRtbBidAdapter', function () { }, gpid: '', cat: [], + contentLang: 'en', contentData: [], isStorageAllowed: true, pagecat: [], @@ -393,6 +399,7 @@ describe('KueezRtbBidAdapter', function () { 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', cat: [], + contentLang: 'en', contentData: [], isStorageAllowed: true, pagecat: [], diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 2e4c35cb065..329a683938c 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -109,6 +109,11 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] @@ -332,6 +337,7 @@ describe('ShinezRtbBidAdapter', function () { }, gpid: '0123456789', cat: [], + contentLang: 'en', contentData: [], isStorageAllowed: true, pagecat: [], @@ -398,6 +404,7 @@ describe('ShinezRtbBidAdapter', function () { 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', cat: [], + contentLang: 'en', contentData: [], isStorageAllowed: true, pagecat: [], diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index d68088affa1..53d71a1de78 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -107,6 +107,11 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] @@ -329,6 +334,7 @@ describe('TagorasBidAdapter', function () { }, gpid: '', cat: [], + contentLang: 'en', contentData: [], isStorageAllowed: true, pagecat: [], @@ -394,6 +400,7 @@ describe('TagorasBidAdapter', function () { 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', cat: [], + contentLang: 'en', contentData: [], isStorageAllowed: true, pagecat: [], diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 674414bb526..a114b582083 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -111,6 +111,7 @@ const BIDDER_REQUEST = { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'], 'content': { + 'language': 'en', 'data': [{ 'name': 'example.com', 'ext': { @@ -337,6 +338,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + contentLang: 'en', contentData: [{ 'name': 'example.com', 'ext': { @@ -438,6 +440,7 @@ describe('TwistDigitalBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', contentData: [{ 'name': 'example.com', 'ext': { @@ -524,6 +527,7 @@ describe('TwistDigitalBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', contentData: [{ 'name': 'example.com', 'ext': { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 0f4a476ada3..2ca1c80ea47 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -115,6 +115,7 @@ const BIDDER_REQUEST = { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'], 'content': { + 'language': 'en', 'data': [{ 'name': 'example.com', 'ext': { @@ -342,6 +343,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + contentLang: 'en', contentData: [{ 'name': 'example.com', 'ext': { @@ -450,6 +452,7 @@ describe('VidazooBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', contentData: [{ 'name': 'example.com', 'ext': { @@ -541,6 +544,7 @@ describe('VidazooBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', contentData: [{ 'name': 'example.com', 'ext': { From 8932942c96c675ca70c21745b982f77dabc0c7c4 Mon Sep 17 00:00:00 2001 From: Prebid-Team Date: Wed, 28 Aug 2024 19:28:25 +0530 Subject: [PATCH 0460/1097] Incrx Bid Adapter : add incrementX banner and vast (#12115) * IncrementX VAST Adapter * Add incrxBidAdapter.md file for banner & video * Resolved issue:Bidder incrementx is missing required params --- modules/incrxBidAdapter.js | 96 ++++++++++-- modules/incrxBidAdapter.md | 45 ++++++ test/spec/modules/incrxBidAdapter_spec.js | 179 +++++++++++++--------- 3 files changed, 234 insertions(+), 86 deletions(-) create mode 100644 modules/incrxBidAdapter.md diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 9b939aff11b..92059f18149 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -1,7 +1,8 @@ import { parseSizesInput, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js' - +import { BANNER, VIDEO } from '../src/mediaTypes.js' +import { OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -15,7 +16,7 @@ const CREATIVE_TTL = 300; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -26,7 +27,9 @@ export const spec = { isBidRequestValid: function (bid) { return !!(bid.params.placementId); }, - + hasTypeVideo(bid) { + return typeof bid.mediaTypes !== 'undefined' && typeof bid.mediaTypes.video !== 'undefined'; + }, /** * Make a server request from the list of BidRequests. * @@ -37,17 +40,29 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); - + let mdType = 0; + if (bidRequest.mediaTypes[BANNER]) { + mdType = 1; + } else { + mdType = 2; + } const requestParams = { _vzPlacementId: bidRequest.params.placementId, sizes: sizes, _slotBidId: bidRequest.bidId, - // TODO: is 'page' the right value here? _rqsrc: bidderRequest.refererInfo.page, + mChannel: mdType }; - - const payload = { - q: encodeURI(JSON.stringify(requestParams)) + let payload; + if (mdType === 1) { // BANNER + payload = { + q: encodeURI(JSON.stringify(requestParams)) + }; + } else { // VIDEO or other types + payload = { + q: encodeURI(JSON.stringify(requestParams)), + bidderRequestData: encodeURI(JSON.stringify(bidderRequest)) + }; } return { @@ -64,30 +79,81 @@ export const spec = { * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse) { + interpretResponse: function (serverResponse, bidderRequest) { const response = serverResponse.body; const bids = []; if (isEmpty(response)) { return bids; } + let decodedBidderRequestData; + if (typeof bidderRequest.data.bidderRequestData === 'string') { + decodedBidderRequestData = JSON.parse(decodeURI(bidderRequest.data.bidderRequestData)); + } else { + decodedBidderRequestData = bidderRequest.data.bidderRequestData; + } const responseBid = { requestId: response.slotBidId, - cpm: response.cpm, + cpm: response.cpm > 0 ? response.cpm : 0, currency: response.currency || DEFAULT_CURRENCY, adType: response.adType || '1', settings: response.settings, - width: response.adWidth, - height: response.adHeight, + width: response.adWidth || 300, + height: response.adHeight || 250, ttl: CREATIVE_TTL, creativeId: response.creativeId || 0, netRevenue: response.netRevenue || false, + mediaType: response.mediaType || BANNER, meta: { - mediaType: response.mediaType || BANNER, + mediaType: response.mediaType, advertiserDomains: response.advertiserDomains || [] }, - ad: response.ad + }; + if (response.mediaType === BANNER) { + responseBid.ad = response.ad || ''; + } else if (response.mediaType === VIDEO) { + let context, adUnitCode; + for (let i = 0; i < decodedBidderRequestData.bids.length; i++) { + const item = decodedBidderRequestData.bids[i]; + if (item.bidId === response.slotBidId) { + context = item.mediaTypes.video.context; + adUnitCode = item.adUnitCode; + break; + } + } + if (context === OUTSTREAM) { + responseBid.vastXml = response.ad || ''; + if (response.rUrl) { + responseBid.renderer = createRenderer({ ...response, adUnitCode }); + } + } + } bids.push(responseBid); + function createRenderer(bid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: bid.slotBidId, + url: bid.rUrl, + config: rendererOptions, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(({ renderer, width, height, vastXml, adUnitCode }) => { + renderer.push(() => { + window.onetag.Player.init({ + ...bid, + width, + height, + vastXml, + nodeId: adUnitCode, + config: renderer.getConfig() + }); + }); + }); + } catch (e) { + } + return renderer; + } return bids; } diff --git a/modules/incrxBidAdapter.md b/modules/incrxBidAdapter.md new file mode 100644 index 00000000000..b37d1e6b566 --- /dev/null +++ b/modules/incrxBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: IncrementX Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid-team@vertoz.com +``` + +# Description + +IncrementX Bid Adapter supports banner and video at present. + +# Test Parameters +``` + var adUnits = [ + { + code: "banner-space", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: "incrementx", + params: { + placementId: "your_placementId" // required, + } + }] + }, { + code: 'video-outstream-space', + mediaTypes: { + video: { + context: "outstream", + playerSize: [640,480] + } + }, + bids: [{ + bidder: "incrementx", + params: { + placementId: "your_placement_id" // required, + } + }] + }]; + +``` diff --git a/test/spec/modules/incrxBidAdapter_spec.js b/test/spec/modules/incrxBidAdapter_spec.js index 3fb4ffe2cd3..72234c17845 100644 --- a/test/spec/modules/incrxBidAdapter_spec.js +++ b/test/spec/modules/incrxBidAdapter_spec.js @@ -1,14 +1,12 @@ import { expect } from 'chai'; import { spec } from 'modules/incrxBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; -describe('IncrementX', function () { - const METHOD = 'POST'; - const URL = 'https://hb.incrementxserv.com/vzhbidder/bid'; - - const bidRequest = { - bidder: 'IncrementX', +describe('incrementx', function () { + const bannerBidRequest = { + bidder: 'incrementx', params: { - placementId: 'PNX-HB-F796830VCF3C4B' + placementId: 'IX-HB-12345' }, mediaTypes: { banner: { @@ -19,86 +17,125 @@ describe('IncrementX', function () { [300, 250], [300, 600] ], - bidId: 'bid-id-123456', - adUnitCode: 'ad-unit-code-1', - bidderRequestId: 'bidder-request-id-123456', - auctionId: 'auction-id-123456', - transactionId: 'transaction-id-123456' + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidId: '2faedf3e89d123', + bidderRequestId: '1c78fb49cc71c6', + auctionId: 'b4f81e8e36232', + transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fb' + }; + const videoBidRequest = { + bidder: 'incrementx', + params: { + placementId: 'IX-HB-12346' + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: ['640x480'] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-1', + bidId: '2faedf3e89d124', + bidderRequestId: '1c78fb49cc71c7', + auctionId: 'b4f81e8e36233', + transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fc' }; - describe('isBidRequestValid', function () { - it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + it('should return true when required params are found', function () { + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(true); + expect(spec.isBidRequestValid(videoBidRequest)).to.equal(true); }); }); describe('buildRequests', function () { - let bidderRequest = { + const bidderRequest = { refererInfo: { - page: 'https://www.test.com', - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: [ - 'https://www.test.com' - ], - canonicalUrl: null + page: 'https://someurl.com' } }; - - it('should build correct POST request for banner bid', function () { - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - expect(request).to.be.an('object'); - expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(URL); - - const payload = JSON.parse(decodeURI(request.data.q)); - expect(payload).to.be.an('object'); - expect(payload._vzPlacementId).to.be.a('string'); - expect(payload.sizes).to.be.an('array'); - expect(payload._slotBidId).to.be.a('string'); - expect(payload._rqsrc).to.be.a('string'); + it('should build banner request', function () { + const requests = spec.buildRequests([bannerBidRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('https://hb.incrementxserv.com/vzhbidder/bid'); + const data = JSON.parse(decodeURI(requests[0].data.q)); + expect(data._vzPlacementId).to.equal('IX-HB-12345'); + expect(data.sizes).to.to.a('array'); + expect(data.mChannel).to.equal(1); + }); + it('should build video request', function () { + const requests = spec.buildRequests([videoBidRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('https://hb.incrementxserv.com/vzhbidder/bid'); + const data = JSON.parse(decodeURI(requests[0].data.q)); + expect(data._vzPlacementId).to.equal('IX-HB-12346'); + expect(data.sizes).to.be.a('array'); + expect(data.mChannel).to.equal(2); }); }); - describe('interpretResponse', function () { - let serverResponse = { + const bannerServerResponse = { body: { - vzhPlacementId: 'PNX-HB-F796830VCF3C4B', - bid: 'BID-XXXX-XXXX', - adWidth: '300', - adHeight: '250', - cpm: '0.7', - ad: '

Ad from IncrementX

', - slotBidId: 'bid-id-123456', - adType: '1', - settings: '1,2', - nurl: 'htt://nurl.com', - statusText: 'Success' + slotBidId: '2faedf3e89d123', + cpm: 0.5, + adWidth: 300, + adHeight: 250, + ad: '
Banner Ad
', + mediaType: BANNER, + netRevenue: true, + currency: 'USD', + advertiserDomains: ['example.com'] } }; - - let expectedResponse = [{ - requestId: 'bid-id-123456', - cpm: '0.7', - currency: 'USD', - adType: '1', - settings: '1,2', - netRevenue: false, - width: '300', - height: '250', - creativeId: 0, - ttl: 300, - ad: '

Ad from IncrementX

', - meta: { - mediaType: 'banner', - advertiserDomains: [] + const videoServerResponse = { + body: { + slotBidId: '2faedf3e89d124', + cpm: 1.0, + adWidth: 640, + adHeight: 480, + ad: 'Test VAST', + mediaType: VIDEO, + netRevenue: true, + currency: 'USD', + rUrl: 'https://example.com/vast.xml', + advertiserDomains: ['example.com'] } - }]; - - it('should correctly interpret valid banner response', function () { - let result = spec.interpretResponse(serverResponse); - expect(result).to.deep.equal(expectedResponse); + }; + const bidderRequest = { + refererInfo: { + page: 'https://someurl.com' + }, + data: { + bidderRequestData: { + bids: [videoBidRequest] + } + } + }; + it('should handle banner response', function () { + const bidResponses = spec.interpretResponse(bannerServerResponse, bidderRequest); + expect(bidResponses).to.have.lengthOf(1); + const bid = bidResponses[0]; + expect(bid.requestId).to.equal('2faedf3e89d123'); + expect(bid.cpm).to.equal(0.5); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.equal('
Banner Ad
'); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + }); + it('should handle video response', function () { + const bidResponses = spec.interpretResponse(videoServerResponse, bidderRequest); + expect(bidResponses).to.have.lengthOf(1); + const bid = bidResponses[0]; + expect(bid.requestId).to.equal('2faedf3e89d124'); + expect(bid.cpm).to.equal(1.0); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.vastXml).to.equal('Test VAST'); + expect(bid.renderer).to.exist; + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); }); }); }); From 06ead4c7d1afc13a5a00727d95fd09206a4e8c47 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 28 Aug 2024 08:09:55 -0700 Subject: [PATCH 0461/1097] Debugging module: fix bug where mocked bidders always time out with auctions (#12177) --- modules/debugging/debugging.js | 6 ++-- test/spec/modules/debugging_mod_spec.js | 40 ++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js index ced8f7bf4a0..803d7ee5cd7 100644 --- a/modules/debugging/debugging.js +++ b/modules/debugging/debugging.js @@ -105,11 +105,13 @@ export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest ({bids, bidRequest} = interceptBids({ bids, bidRequest, - addBid: cbs.onBid, - addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, ...config}), + addBid: wrapCallback(cbs.onBid), + addPaapiConfig: wrapCallback((config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, ...config})), done })); if (bids.length === 0) { + // eslint-disable-next-line no-unused-expressions + cbs.onResponse?.({}); // trigger onResponse so that the bidder may be marked as "timely" if necessary done(); } else { next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index cd7e642699a..438c70fae64 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -354,17 +354,30 @@ describe('Debugging config', () => { }); describe('bidderBidInterceptor', () => { - let next, interceptBids, onCompletion, interceptResult, done, addBid; + let next, interceptBids, onCompletion, interceptResult, done, addBid, wrapCallback, addPaapiConfig, wrapped; - function interceptorArgs({spec = {}, bids = [], bidRequest = {}, ajax = {}, wrapCallback = {}, cbs = {}} = {}) { + function interceptorArgs({spec = {}, bids = [], bidRequest = {}, ajax = {}, cbs = {}} = {}) { return [next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, Object.assign({onCompletion}, cbs)]; } beforeEach(() => { next = sinon.spy(); + wrapped = false; + wrapCallback = sinon.stub().callsFake(cb => { + if (cb == null) return cb; + return function () { + wrapped = true; + try { + return cb.apply(this, arguments) + } finally { + wrapped = false; + } + } + }); interceptBids = sinon.stub().callsFake((opts) => { done = opts.done; addBid = opts.addBid; + addPaapiConfig = opts.addPaapiConfig; return interceptResult; }); onCompletion = sinon.spy(); @@ -372,13 +385,26 @@ describe('bidderBidInterceptor', () => { }); it('should pass to interceptBid an addBid that triggers onBid', () => { - const onBid = sinon.spy(); + const onBid = sinon.stub().callsFake(() => { + expect(wrapped).to.be.true; + }); bidderBidInterceptor(...interceptorArgs({cbs: {onBid}})); - const bid = {}; + const bid = { + bidder: 'bidder' + }; addBid(bid); expect(onBid.calledWith(sinon.match.same(bid))).to.be.true; }); + it('should pass addPaapiConfig that triggers onPaapi', () => { + const onPaapi = sinon.stub().callsFake(() => { + expect(wrapped).to.be.true; + }); + bidderBidInterceptor(...interceptorArgs({cbs: {onPaapi}})); + addPaapiConfig({paapi: 'config'}, {bidId: 'bidId'}); + sinon.assert.calledWith(onPaapi, {paapi: 'config', bidId: 'bidId'}) + }) + describe('with no remaining bids', () => { it('should pass a done callback that triggers onCompletion', () => { bidderBidInterceptor(...interceptorArgs()); @@ -387,6 +413,12 @@ describe('bidderBidInterceptor', () => { expect(onCompletion.calledOnce).to.be.true; }); + it('should call onResponse', () => { + const onResponse = sinon.stub(); + bidderBidInterceptor(...interceptorArgs({cbs: {onResponse}})); + sinon.assert.called(onResponse); + }) + it('should not call next()', () => { bidderBidInterceptor(...interceptorArgs()); expect(next.called).to.be.false; From dd35fd839328b694a061c16fc837a25a501747b8 Mon Sep 17 00:00:00 2001 From: Edge226Ads <144908224+Edge226Ads@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:44:15 +0300 Subject: [PATCH 0462/1097] Edge226 Bid Adapter : updates to use the teqblaze library (#12178) * New adapter Edge226 * upd to teqblaze style --- modules/edge226BidAdapter.js | 180 +------------------- test/spec/modules/edge226BidAdapter_spec.js | 134 +++++++++++++-- 2 files changed, 122 insertions(+), 192 deletions(-) diff --git a/modules/edge226BidAdapter.js b/modules/edge226BidAdapter.js index f0b91183a3e..ae235f02f64 100644 --- a/modules/edge226BidAdapter.js +++ b/modules/edge226BidAdapter.js @@ -1,189 +1,17 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'edge226'; const AD_URL = 'https://ssp.dauup.com/pbjs'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.plcmt = mediaTypes[VIDEO].plcmt; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/test/spec/modules/edge226BidAdapter_spec.js b/test/spec/modules/edge226BidAdapter_spec.js index 4819d8d4a4e..2162b844930 100644 --- a/test/spec/modules/edge226BidAdapter_spec.js +++ b/test/spec/modules/edge226BidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/edge226BidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'edge226' +const bidder = 'edge226'; describe('Edge226BidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('Edge226BidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('Edge226BidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('Edge226BidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('Edge226BidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -129,7 +153,7 @@ describe('Edge226BidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +169,56 @@ describe('Edge226BidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +244,10 @@ describe('Edge226BidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +262,38 @@ describe('Edge226BidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { From 76e74a773d11822cd9f7f132c71903b67a42549e Mon Sep 17 00:00:00 2001 From: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com> Date: Wed, 28 Aug 2024 22:44:15 +0300 Subject: [PATCH 0463/1097] Playdigo Bid Adapter : add GVLID (#12179) * init adapter * add gpp support * upd * add userSync * add gvl id --- modules/playdigoBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/playdigoBidAdapter.js b/modules/playdigoBidAdapter.js index e41e86c4c47..5d65a91e1a7 100644 --- a/modules/playdigoBidAdapter.js +++ b/modules/playdigoBidAdapter.js @@ -3,11 +3,13 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'playdigo'; +const GVLID = 1302; const AD_URL = 'https://server.playdigo.com/pbjs'; const SYNC_URL = 'https://cs.playdigo.com'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(), From 14b3b355194d00d9c85d13790bad79a88e9d2631 Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Thu, 29 Aug 2024 14:25:41 +0200 Subject: [PATCH 0464/1097] null check config.rate and config.defaultRate (#12175) --- modules/currency.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/currency.js b/modules/currency.js index aa464b81f9a..8ac2b8cbead 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -62,13 +62,13 @@ export let responseReady = defer(); export function setConfig(config) { ratesURL = DEFAULT_CURRENCY_RATE_URL; - if (typeof config.rates === 'object') { + if (config.rates !== null && typeof config.rates === 'object') { currencyRates.conversions = config.rates; currencyRatesLoaded = true; needToCallForCurrencyFile = false; // don't call if rates are already specified } - if (typeof config.defaultRates === 'object') { + if (config.defaultRates !== null && typeof config.defaultRates === 'object') { defaultRates = config.defaultRates; // set up the default rates to be used if the rate file doesn't get loaded in time From acdeb2770c825b0f489e19424e558d2710172d78 Mon Sep 17 00:00:00 2001 From: Helly Alpern <157289260+hellyalpern@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:42:31 +0300 Subject: [PATCH 0465/1097] Add COPPA compliance check in bid request data (#12190) - Extract COPPA flag from bidder request using deepAccess. - Ensure COPPA flag is included in the request payload for compliance. --- libraries/vidazooUtils/bidderUtils.js | 3 ++- test/spec/modules/illuminBidAdapter_spec.js | 9 ++++++--- test/spec/modules/kueezRtbBidAdapter_spec.js | 9 ++++++--- test/spec/modules/shinezRtbBidAdapter_spec.js | 9 ++++++--- test/spec/modules/tagorasBidAdapter_spec.js | 9 ++++++--- test/spec/modules/twistDigitalBidAdapter_spec.js | 6 +++++- test/spec/modules/vidazooBidAdapter_spec.js | 6 +++++- 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 9b1473b0c28..fbb4300cb24 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -245,8 +245,8 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); - const contentLang = deepAccess(bidderRequest, 'ortb2.site.content.language') || document.documentElement.lang; + const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa', 0); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ @@ -281,6 +281,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder cat: cat, contentData, contentLang, + coppa, userData: userData, pagecat: pagecat, transactionId: ortb2Imp?.ext?.tid, diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 0e6e98c6f04..3cd79c7468d 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -114,7 +114,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -339,7 +340,8 @@ describe('IlluminBidAdapter', function () { contentLang: 'en', isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); @@ -406,7 +408,8 @@ describe('IlluminBidAdapter', function () { contentLang: 'en', isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 188fe005990..2df5e299aeb 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -111,7 +111,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -336,7 +337,8 @@ describe('KueezRtbBidAdapter', function () { contentData: [], isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); @@ -403,7 +405,8 @@ describe('KueezRtbBidAdapter', function () { contentData: [], isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 329a683938c..ebd2e987491 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -116,7 +116,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -341,7 +342,8 @@ describe('ShinezRtbBidAdapter', function () { contentData: [], isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); @@ -408,7 +410,8 @@ describe('ShinezRtbBidAdapter', function () { contentData: [], isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 53d71a1de78..6ebe2896ffc 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -114,7 +114,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -338,7 +339,8 @@ describe('TagorasBidAdapter', function () { contentData: [], isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); @@ -404,7 +406,8 @@ describe('TagorasBidAdapter', function () { contentData: [], isStorageAllowed: true, pagecat: [], - userData: [] + userData: [], + coppa: 0 } }); }); diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index a114b582083..c15a6d1d909 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -126,7 +126,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -339,6 +340,7 @@ describe('TwistDigitalBidAdapter', function () { 'architecture': '' }, contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -441,6 +443,7 @@ describe('TwistDigitalBidAdapter', function () { cat: ['IAB2'], pagecat: ['IAB2-2'], contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -528,6 +531,7 @@ describe('TwistDigitalBidAdapter', function () { cat: ['IAB2'], pagecat: ['IAB2-2'], contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 2ca1c80ea47..14a49b21cdc 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -130,7 +130,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -344,6 +345,7 @@ describe('VidazooBidAdapter', function () { 'architecture': '' }, contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -453,6 +455,7 @@ describe('VidazooBidAdapter', function () { cat: ['IAB2'], pagecat: ['IAB2-2'], contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -545,6 +548,7 @@ describe('VidazooBidAdapter', function () { cat: ['IAB2'], pagecat: ['IAB2-2'], contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { From 7911ddd3b16dc86890d00cbf0d5c58b5d0663768 Mon Sep 17 00:00:00 2001 From: GodefroiRoussel Date: Thu, 29 Aug 2024 17:59:10 +0200 Subject: [PATCH 0466/1097] Adagio Analytics Adapter: add bidders code (#12188) * AdagioAnalyticsAdapter: add "bdrs_code" to beacon * AdagioAnalyticsAdapter: add comment and rename variable --------- Co-authored-by: Godefroi Roussel --- modules/adagioAnalyticsAdapter.js | 10 ++-- .../modules/adagioAnalyticsAdapter_spec.js | 47 +++++++++++++++---- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 033809e7f1b..ed9f6562c34 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -214,13 +214,16 @@ function handlerAuctionInit(event) { bannerSize => bannerSize ).sort(); - const sortedBidderCodes = bidders.sort() + const sortedBidderNames = bidders.sort(); const bidSrcMapper = (bidder) => { + // bidderCode in the context of the bidderRequest is the name given to the bidder in the adunit. + // It is not always the "true" bidder code, it can also be its alias const request = event.bidderRequests.find(br => br.bidderCode === bidder) return request ? request.bids[0].src : null } - const biddersSrc = sortedBidderCodes.map(bidSrcMapper).join(','); + const biddersSrc = sortedBidderNames.map(bidSrcMapper).join(','); + const biddersCode = sortedBidderNames.map(bidder => adapterManager.resolveAlias(bidder)).join(','); // if adagio was involved in the auction we identified it with rtdUid, if not use the prebid auctionId const auctionId = rtdUid || prebidAuctionId; @@ -238,7 +241,7 @@ function handlerAuctionInit(event) { url_dmn: w.location.hostname, mts: mediaTypesKeys.join(','), ban_szs: bannerSizes.join(','), - bdrs: sortedBidderCodes.join(','), + bdrs: sortedBidderNames.join(','), pgtyp: deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.pagetype', null), plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.placement', null), t_n: adgRtdSession.testName || null, @@ -246,6 +249,7 @@ function handlerAuctionInit(event) { s_id: adgRtdSession.id || null, s_new: adgRtdSession.new || null, bdrs_src: biddersSrc, + bdrs_code: biddersCode, }; if (adagioBidRequest && adagioBidRequest.bids) { diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 2c1f259ea35..7cb0e6c58be 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -244,6 +244,11 @@ const AUCTION_INIT_ANOTHER = { 'params': { ...PARAMS_ADG }, + }, { + 'bidder': 'anotherWithAlias', + 'params': { + 'publisherId': '1001' + }, }, ], 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'ortb2Imp': { @@ -340,7 +345,25 @@ const AUCTION_INIT_ANOTHER = { 'auctionId': AUCTION_ID, 'src': 'client', 'bidRequestsCount': 1 - } + }, { + 'bidder': 'anotherWithAlias', + 'params': { + 'publisherId': '1001', + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + }, ], 'timeout': 3000, 'refererInfo': { @@ -682,10 +705,12 @@ describe('adagio analytics adapter', () => { site: 'test-com', } }); + adapterManager.aliasRegistry['anotherWithAlias'] = 'another'; }); afterEach(() => { adagioAnalyticsAdapter.disableAnalytics(); + delete adapterManager.aliasRegistry['anotherWithAlias']; }); it('builds and sends auction data', () => { @@ -715,7 +740,8 @@ describe('adagio analytics adapter', () => { expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); - expect(search.bdrs).to.equal('adagio,another,nobid'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); expect(search.adg_mts).to.equal('ban'); } @@ -736,8 +762,8 @@ describe('adagio analytics adapter', () => { expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); - expect(search.bdrs_bid).to.equal('1,1,0'); - expect(search.bdrs_cpm).to.equal('1.42,2.052,'); + expect(search.bdrs_bid).to.equal('1,1,0,0'); + expect(search.bdrs_cpm).to.equal('1.42,2.052,,'); } { @@ -796,6 +822,7 @@ describe('adagio analytics adapter', () => { expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another'); + expect(search.bdrs_code).to.equal('adagio,another'); expect(search.adg_mts).to.equal('ban'); expect(search.t_n).to.equal('test'); expect(search.t_v).to.equal('version'); @@ -819,6 +846,7 @@ describe('adagio analytics adapter', () => { expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x480'); expect(search.bdrs).to.equal('another'); + expect(search.bdrs_code).to.equal('another'); expect(search.adg_mts).to.not.exist; } @@ -839,7 +867,8 @@ describe('adagio analytics adapter', () => { expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); - expect(search.bdrs).to.equal('adagio,another,nobid'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); expect(search.adg_mts).to.equal('ban'); } @@ -864,8 +893,8 @@ describe('adagio analytics adapter', () => { expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); - expect(search.bdrs_bid).to.equal('0,0,0'); - expect(search.bdrs_cpm).to.equal(',,'); + expect(search.bdrs_bid).to.equal('0,0,0,0'); + expect(search.bdrs_cpm).to.equal(',,,'); } { @@ -939,8 +968,8 @@ describe('adagio analytics adapter', () => { expect(search.v).to.equal('2'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); - expect(search.bdrs_bid).to.equal('1,1,0'); - expect(search.bdrs_cpm).to.equal('1.42,,'); + expect(search.bdrs_bid).to.equal('1,1,0,0'); + expect(search.bdrs_cpm).to.equal('1.42,,,'); } }); }); From 541888a67181e75c191391038d6767b3c3ebaa80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Millet?= Date: Thu, 29 Aug 2024 19:51:27 +0200 Subject: [PATCH 0467/1097] Dailymotion bid adapter: add publisher restrictions in consent enforcement (#12185) ## Type of change - [x] Updated bidder adapter ## Description of change The previous version of the consent enforcement was only looking at the consent given by the user. This change now also looks at the publisher restrictions to disable cookie sending when the user gave consent but the publisher disallows some consents. --- modules/dailymotionBidAdapter.js | 24 +- .../modules/dailymotionBidAdapter_spec.js | 692 +++++++++++++++++- 2 files changed, 707 insertions(+), 9 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index eccdbbbe982..e454f9b8c7d 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -4,6 +4,8 @@ import { deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; import { userSync } from '../src/userSync.js'; +const DAILYMOTION_VENDOR_ID = 573; + /** * Get video metadata from bid request * @@ -110,7 +112,7 @@ function isUserSyncEnabled() { export const spec = { code: 'dailymotion', - gvlid: 573, + gvlid: DAILYMOTION_VENDOR_ID, supportedMediaTypes: [VIDEO], /** @@ -157,13 +159,21 @@ export const spec = { deepAccess(bidderRequest, 'gdprConsent.vendorData.hasGlobalConsent') === true || ( // Vendor consent - deepAccess(bidderRequest, 'gdprConsent.vendorData.vendor.consents.573') === true && - // Purposes - [1, 3, 4].every(v => deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true) && - // Flexible purposes + deepAccess(bidderRequest, `gdprConsent.vendorData.vendor.consents.${DAILYMOTION_VENDOR_ID}`) === true && + + // Purposes with legal basis "consent". These are not flexible, so if publisher requires legitimate interest (2) it cancels them + [1, 3, 4].every(v => + deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) !== 0 && + deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) !== 2 && + deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true + ) && + + // Purposes with legal basis "legitimate interest" (default) or "consent" (when specified as such by publisher) [2, 7, 9, 10].every(v => - deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true || - deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.legitimateInterests.${v}`) === true + deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) !== 0 && + (deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) === 1 + ? deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true + : deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.legitimateInterests.${v}`) === true) ) ); diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 750bf340261..3eddb17db54 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -394,7 +394,7 @@ describe('dailymotionBidAdapterTests', () => { expect(request.options.withCredentials).to.eql(true); }); - it('validates buildRequests with detailed consent, no legitimate interest', () => { + it('validates buildRequests with detailed consent without legitimate interest', () => { const bidRequestData = [{ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId: 123456, @@ -497,7 +497,7 @@ describe('dailymotionBidAdapterTests', () => { () => spec.buildRequests(bidRequestData, bidderRequestData), ); - expect(request.options.withCredentials).to.eql(true); + expect(request.options.withCredentials).to.eql(false); }); it('validates buildRequests with detailed consent, with legitimate interest', () => { @@ -608,6 +608,694 @@ describe('dailymotionBidAdapterTests', () => { expect(request.options.withCredentials).to.eql(true); }); + it('validates buildRequests with detailed consent and legitimate interest but publisher forces consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 2: { 573: 1 }, + 7: { 573: 1 }, + 9: { 573: 1 }, + 10: { 573: 1 }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent, no legitimate interest and publisher forces consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 2: { 573: 1 }, + 7: { 573: 1 }, + 9: { 573: 1 }, + 10: { 573: 1 }, + }, + }, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent but publisher full restriction on purpose 1', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 1: { + 573: 0, + }, + }, + }, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent but publisher restriction 2 on consent purpose 1', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 1: { + 573: 2, + }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent, legitimate interest and publisher restriction on purpose 1', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 1: { + 573: 1, + }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent and legitimate interest but publisher restriction on legitimate interest 2', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 2: { + 573: 2, + }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + it('validates buildRequests with insufficient consent', () => { const bidRequestData = [{ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', From ac5cc64644f6371b014cf656f2af78ef2f257c58 Mon Sep 17 00:00:00 2001 From: qt-io <104574052+qt-io@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:38:09 +0300 Subject: [PATCH 0468/1097] QT bid adapter: add gvlid (#12189) * New Adapter: QT * changed coppa retrieving * add GVLID --------- Co-authored-by: qt-io --- modules/qtBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/qtBidAdapter.js b/modules/qtBidAdapter.js index f9f8b9b9efe..cbfd3586fe5 100644 --- a/modules/qtBidAdapter.js +++ b/modules/qtBidAdapter.js @@ -3,11 +3,13 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'qt'; +const GVLID = 1331; const AD_URL = 'https://endpoint1.qt.io/pbjs'; const SYNC_URL = 'https://cs.qt.io'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(), From d690c86a2a652af409de2d69f005cb9f78304dab Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 29 Aug 2024 23:05:22 +0200 Subject: [PATCH 0469/1097] AdagioAnalyticsAdapter: fix `rtdUid` getter (#12187) --- modules/adagioAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index ed9f6562c34..c64535f74b5 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -186,7 +186,7 @@ function handlerAuctionInit(event) { // Check if Adagio is on the bid requests. const adagioBidRequest = event.bidderRequests.find(bidRequest => isAdagio(bidRequest.bidderCode)); - const rtdUid = deepAccess(adagioBidRequest, 'ortb2.site.ext.data.adg_rtd.uid'); + const rtdUid = deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.adg_rtd.uid'); cache.addPrebidAuctionIdRef(prebidAuctionId, rtdUid); cache.auctions[prebidAuctionId] = {}; From b7efc5ac0c7d07dc45e30aa98dc76e7139b017b2 Mon Sep 17 00:00:00 2001 From: Phaneendra Hegde Date: Fri, 30 Aug 2024 02:36:05 +0530 Subject: [PATCH 0470/1097] PubxaiAnalyticsAdapter Update: Added an extra field in the auction payload. (#12181) * send BidRejected Events to capture floored bids * fix tests * send pubx_id as query param * added extraData in analytics adapter to be sent in beacon data * added extraData in analytics adapter to be sent in beacon data * moved data read to session storage * bumped version * moving all data to localStorage again * updated test cases for pubxaiAA.js --------- Co-authored-by: tej656 Co-authored-by: Tej <139129627+tej656@users.noreply.github.com> Co-authored-by: NikhilX --- modules/pubxaiAnalyticsAdapter.js | 5 ++++- test/spec/modules/pubxaiAnalyticsAdapter_spec.js | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index f8ec72cde75..cd93b9bd3f6 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -18,7 +18,7 @@ let initOptions; const emptyUrl = ''; const analyticsType = 'endpoint'; const adapterCode = 'pubxai'; -const pubxaiAnalyticsVersion = 'v2.0.0'; +const pubxaiAnalyticsVersion = 'v2.1.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; @@ -77,6 +77,7 @@ export const auctionCache = new Proxy( consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + extraData: JSON.parse(storage.getDataFromLocalStorage('pubx:extraData')) || {}, initOptions: { ...initOptions, auctionId: name, // back-compat @@ -248,6 +249,7 @@ const prepareSend = (auctionId) => { 'userDetail', 'consentDetail', 'pmacDetail', + 'extraData', 'initOptions', ], eventType: 'win', @@ -263,6 +265,7 @@ const prepareSend = (auctionId) => { 'userDetail', 'consentDetail', 'pmacDetail', + 'extraData', 'initOptions', ], eventType: 'auction', diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index abc52b00439..6b2fbef6c06 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -599,6 +599,7 @@ describe('pubxai analytics adapter', () => { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, pmacDetail: {}, + extraData: {}, initOptions: { ...initOptions, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -693,6 +694,7 @@ describe('pubxai analytics adapter', () => { consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), }, pmacDetail: {}, + extraData: {}, initOptions: { ...initOptions, auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -763,7 +765,7 @@ describe('pubxai analytics adapter', () => { ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v2.0.0', + pubxaiAnalyticsVersion: 'v2.1.0', prebidVersion: '$prebid.version$', pubxId: pubxId, }); @@ -807,7 +809,7 @@ describe('pubxai analytics adapter', () => { // Step 8: check that the meta information in the call is correct expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v2.0.0', + pubxaiAnalyticsVersion: 'v2.1.0', prebidVersion: '$prebid.version$', pubxId: pubxId, }); @@ -933,7 +935,7 @@ describe('pubxai analytics adapter', () => { ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v2.0.0', + pubxaiAnalyticsVersion: 'v2.1.0', prebidVersion: '$prebid.version$', pubxId: pubxId, }); @@ -1050,7 +1052,7 @@ describe('pubxai analytics adapter', () => { ); expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ auctionTimestamp: '1616654312804', - pubxaiAnalyticsVersion: 'v2.0.0', + pubxaiAnalyticsVersion: 'v2.1.0', prebidVersion: '$prebid.version$', pubxId: pubxId, }); From 360c3eaf94baf6af0336abc59829baa3d66a3a5d Mon Sep 17 00:00:00 2001 From: Alexis BRENON Date: Fri, 30 Aug 2024 11:26:09 +0200 Subject: [PATCH 0471/1097] Greenbids Analytics : send cpm on any valid bid (#12174) * fix(greenbids,analytics): send cpm on any valid bid (#5) * fix(greenbids,analytics): cpm not passed * bump version number * fix unit tests --- modules/greenbidsAnalyticsAdapter.js | 28 ++++++++++++++++++- .../modules/greenbidsAnalyticsAdapter_spec.js | 8 ++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index 156ae0332a2..99ce89ee4d1 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -4,9 +4,17 @@ import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName} from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + +/** + * @typedef {object} Message Payload message sent to the Greenbids API + */ + const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.3.1'; +export const ANALYTICS_VERSION = '2.3.2'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; @@ -97,6 +105,11 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER contentType: 'application/json' }); }, + /** + * + * @param {string} auctionId + * @returns {Message} + */ createCommonMessage(auctionId) { const cachedAuction = this.getCachedAuction(auctionId); return { @@ -111,14 +124,27 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER adUnits: [], }; }, + /** + * @param {Bid} bid + * @param {BIDDER_STATUS} status + */ serializeBidResponse(bid, status) { return { bidder: bid.bidder, isTimeout: (status === BIDDER_STATUS.TIMEOUT), hasBid: (status === BIDDER_STATUS.BID), params: (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {}, + ...(status === BIDDER_STATUS.BID ? { + cpm: bid.cpm, + currency: bid.currency + } : {}), }; }, + /** + * @param {*} message Greenbids API payload + * @param {Bid} bid Bid to add to the payload + * @param {BIDDER_STATUS} status Bidding status + */ addBidResponseToMessage(message, bid, status) { const adUnitCode = bid.adUnitCode.toLowerCase(); const adUnitIndex = message.adUnits.findIndex((adUnit) => { diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index d5362c9ed19..7eeaa7a6c67 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -280,13 +280,17 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { bidder: 'greenbids', isTimeout: false, hasBid: true, - params: {} + params: {}, + cpm: 0.1, + currency: 'USD', }, { bidder: 'greenbidsx', isTimeout: false, hasBid: true, - params: {'placement ID': 12784} + params: { 'placement ID': 12784 }, + cpm: 0.08, + currency: 'USD', } ] }, From f0b45e8111348ee735e7ab4ca4a47eb4a6bbfe00 Mon Sep 17 00:00:00 2001 From: Daniel Maxim Date: Tue, 3 Sep 2024 12:00:22 +0300 Subject: [PATCH 0472/1097] Invibes Bid Adapter: added us consent support (#12183) * Invibes Bid Adapter: added us consent support * Invibes Bid Adapter: switched to LF * Invibes Bid Adapter: fixed pr comment * Invibes Bid Adapter: Fixed unit tests --- modules/invibesBidAdapter.js | 41 ++++++++++++++------- test/spec/modules/invibesBidAdapter_spec.js | 23 ++++++++++++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index a69311834b2..35c1a12ff5e 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -14,7 +14,7 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 12, + PREBID_VERSION: 13, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], @@ -140,7 +140,7 @@ function buildRequest(bidRequests, bidderRequest) { _userId = _userId || bidRequest.userId; }); - invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); + invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent, bidderRequest.uspConsent); invibes.visitId = invibes.visitId || generateRandomId(); @@ -176,6 +176,7 @@ function buildRequest(bidRequests, bidderRequest) { li: invibes.legitimateInterests.toString(), tc: invibes.gdpr_consent, + uspc: bidderRequest.uspConsent, isLocalStorageEnabled: storage.hasLocalStorage(), preventPageViewEvent: preventPageViewEvent, isPlacementRefresh: isPlacementRefresh, @@ -498,7 +499,7 @@ function renderCreative(bidModel) { } function readFromLocalStorage(key) { - if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) { + if ((invibes.GdprModuleInstalled || invibes.UspModuleInstalled) && (!invibes.optIn || !invibes.purposes[0])) { return; } @@ -592,20 +593,15 @@ function buildSyncUrl() { return syncUrl; } -function readGdprConsent(gdprConsent) { +function readGdprConsent(gdprConsent, usConsent) { + invibes.GdprModuleInstalled = false; + invibes.UspModuleInstalled = false; if (gdprConsent && gdprConsent.vendorData) { invibes.GdprModuleInstalled = true; invibes.gdpr_consent = getVendorConsentData(gdprConsent.vendorData); if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) { - var index; - for (index = 0; index < invibes.purposes.length; ++index) { - invibes.purposes[index] = true; - } - - for (index = 0; index < invibes.legitimateInterests.length; ++index) { - invibes.legitimateInterests[index] = true; - } + setAllPurposesAndLegitimateInterests(true); return 2; } @@ -635,12 +631,29 @@ function readGdprConsent(gdprConsent) { } return 2; + } else if (usConsent && usConsent.length > 2) { + invibes.UspModuleInstalled = true; + if (usConsent[2] == 'N') { + setAllPurposesAndLegitimateInterests(true); + return 2; + } } - invibes.GdprModuleInstalled = false; + setAllPurposesAndLegitimateInterests(false); return 0; } +function setAllPurposesAndLegitimateInterests(value) { + var index; + for (index = 0; index < invibes.purposes.length; ++index) { + invibes.purposes[index] = value; + } + + for (index = 0; index < invibes.legitimateInterests.length; ++index) { + invibes.legitimateInterests[index] = value; + } +} + function tryCopyValueToArray(value, target, length) { if (value instanceof Array) { for (let i = 0; i < length && i < value.length; i++) { @@ -751,7 +764,7 @@ invibes.getCookie = function (name) { return; } - if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) { + if ((invibes.GdprModuleInstalled || invibes.UspModuleInstalled) && (!invibes.optIn || !invibes.purposes[0])) { return; } diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index df789a7cca0..e12376b2a37 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -1511,5 +1511,28 @@ describe('invibesBidAdapter:', function () { let response = spec.getUserSyncs({iframeEnabled: false}); expect(response).to.equal(undefined); }); + + it('uses uspConsent when no gdprConsent', function () { + let bidderRequest = { + uspConsent: '1YNY', + refererInfo: { + page: 'https://randomWeb.com?someFakePara=fakeValue&secondParam=secondValue' + } + }; + + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(top.window.invibes.optIn).to.equal(2); + expect(top.window.invibes.GdprModuleInstalled).to.be.false; + expect(top.window.invibes.UspModuleInstalled).to.be.true; + var index; + for (index = 0; index < top.window.invibes.purposes.length; ++index) { + expect(top.window.invibes.purposes[index]).to.be.true; + } + for (index = 0; index < top.window.invibes.legitimateInterests.length; ++index) { + expect(top.window.invibes.legitimateInterests[index]).to.be.true; + } + expect(request.data.tc).to.not.exist; + expect(request.data.uspc).to.equal(bidderRequest.uspConsent); + }); }); }); From fe193616f6f5a633311fadcba3aaa1c0d42f6164 Mon Sep 17 00:00:00 2001 From: Eyvaz <62054743+eyvazahmadzada@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:25:46 +0200 Subject: [PATCH 0473/1097] IntentIQ Analytics Adapter: browser blacklist (#12119) * add browser blacklist to analytics * remove log * fix browser blacklist lowercase * fix browserblocklist lower case issue, add unit tests * remove log * remove unnecessary tests, improve testing logic * remove code duplication * export detectbrowser * description fix * move detect browser util functions to library * fix unit test * update tests * update tests * fix test error --- .../detectBrowserUtils/detectBrowserUtils.js | 72 ++++++++++++++++++ modules/intentIqAnalyticsAdapter.js | 11 ++- modules/intentIqIdSystem.js | 76 +------------------ .../modules/intentIqAnalyticsAdapter_spec.js | 36 +++++++++ test/spec/modules/intentIqIdSystem_spec.js | 3 +- 5 files changed, 121 insertions(+), 77 deletions(-) create mode 100644 libraries/detectBrowserUtils/detectBrowserUtils.js diff --git a/libraries/detectBrowserUtils/detectBrowserUtils.js b/libraries/detectBrowserUtils/detectBrowserUtils.js new file mode 100644 index 00000000000..0606388a346 --- /dev/null +++ b/libraries/detectBrowserUtils/detectBrowserUtils.js @@ -0,0 +1,72 @@ +import { logError } from '../../src/utils.js'; + +/** + * Detects the browser using either userAgent or userAgentData + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowser() { + try { + if (navigator.userAgent) { + return detectBrowserFromUserAgent(navigator.userAgent); + } else if (navigator.userAgentData) { + return detectBrowserFromUserAgentData(navigator.userAgentData); + } + } catch (error) { + logError('Error detecting browser:', error); + } + return 'unknown'; +} + +/** + * Detects the browser from the user agent string + * @param {string} userAgent - The user agent string from the browser + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowserFromUserAgent(userAgent) { + const browserRegexPatterns = { + opera: /Opera|OPR/, + edge: /Edg/, + chrome: /Chrome|CriOS/, + safari: /Safari/, + firefox: /Firefox/, + ie: /MSIE|Trident/, + }; + + // Check for Chrome first to avoid confusion with Safari + if (browserRegexPatterns.chrome.test(userAgent)) { + return 'chrome'; + } + + // Now we can safely check for Safari + if (browserRegexPatterns.safari.test(userAgent) && !browserRegexPatterns.chrome.test(userAgent)) { + return 'safari'; + } + + // Check other browsers + for (const browser in browserRegexPatterns) { + if (browserRegexPatterns[browser].test(userAgent)) { + return browser; + } + } + + return 'unknown'; +} + +/** + * Detects the browser from the NavigatorUAData object + * @param {NavigatorUAData} userAgentData - The user agent data object from the browser + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowserFromUserAgentData(userAgentData) { + const brandNames = userAgentData.brands.map(brand => brand.brand); + + if (brandNames.includes('Microsoft Edge')) { + return 'edge'; + } else if (brandNames.includes('Opera')) { + return 'opera'; + } else if (brandNames.some(brand => brand === 'Chromium' || brand === 'Google Chrome')) { + return 'chrome'; + } + + return 'unknown'; +} diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index ae3f252b412..fbb74bee9c8 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -6,6 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; import { config } from '../src/config.js'; import { EVENTS } from '../src/constants.js'; import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; +import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; const MODULE_NAME = 'iiqAnalytics' const analyticsType = 'endpoint'; @@ -113,6 +114,8 @@ function initLsValues() { iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; } + + iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : ''; } } @@ -134,6 +137,13 @@ function initReadLsIds() { function bidWon(args) { if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { initLsValues(); } + + const currentBrowserLowerCase = detectBrowser(); + if (iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList?.includes(currentBrowserLowerCase)) { + logError('IIQ ANALYTICS -> Browser is in blacklist!'); + return; + } + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { initReadLsIds(); } if (!iiqAnalyticsAnalyticsAdapter.initOptions.manualReport) { ajax(constructFullUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); @@ -227,7 +237,6 @@ iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapte iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function }; - adapterManager.registerAnalyticsAdapter({ adapter: iiqAnalyticsAnalyticsAdapter, code: MODULE_NAME diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 90b470ae919..2702aa9848d 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -13,6 +13,7 @@ import { MODULE_TYPE_UID } from '../src/activities/modules.js'; import { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; import AES from 'crypto-js/aes.js'; import Utf8 from 'crypto-js/enc-utf8.js'; +import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -172,81 +173,6 @@ export function handleGPPData(data = {}) { return JSON.stringify(data); } -/** - * Detects the browser using either userAgent or userAgentData - * @return {string} The name of the detected browser or 'unknown' if unable to detect - */ -function detectBrowser() { - try { - if (navigator.userAgent) { - return detectBrowserFromUserAgent(navigator.userAgent); - } else if (navigator.userAgentData) { - return detectBrowserFromUserAgentData(navigator.userAgentData); - } - } catch (error) { - logError('Error detecting browser:', error); - } - return 'unknown'; -} - -/** - * Detects the browser from the user agent string - * @param {string} userAgent - The user agent string from the browser - * @return {string} The name of the detected browser or 'unknown' if unable to detect - */ -export function detectBrowserFromUserAgent(userAgent) { - const browserRegexPatterns = { - opera: /Opera|OPR/, - edge: /Edg/, - chrome: /Chrome|CriOS/, - safari: /Safari/, - firefox: /Firefox/, - ie: /MSIE|Trident/, - }; - - // Check for Chrome first to avoid confusion with Safari - if (browserRegexPatterns.chrome.test(userAgent)) { - return 'chrome'; - } - - // Now we can safely check for Safari - if (browserRegexPatterns.safari.test(userAgent) && !browserRegexPatterns.chrome.test(userAgent)) { - return 'safari'; - } - - // Check other browsers - for (const browser in browserRegexPatterns) { - if (browserRegexPatterns[browser].test(userAgent)) { - return browser; - } - } - - return 'unknown'; -} -/** - * @typedef {Object} NavigatorUAData - * @property {Array<{brand: string, version: string}>} brands - The list of brands associated with the user agent. - */ - -/** - * Detects the browser from the NavigatorUAData object - * @param {NavigatorUAData} userAgentData - The user agent data object from the browser - * @return {string} The name of the detected browser or 'unknown' if unable to detect - */ -export function detectBrowserFromUserAgentData(userAgentData) { - const brandNames = userAgentData.brands.map(brand => brand.brand); - - if (brandNames.includes('Microsoft Edge')) { - return 'edge'; - } else if (brandNames.includes('Opera')) { - return 'opera'; - } else if (brandNames.some(brand => brand === 'Chromium' || brand === 'Google Chrome')) { - return 'chrome'; - } - - return 'unknown'; -} - /** * Processes raw client hints data into a structured format. * @param {object} clientHints - Raw client hints data diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 3b1b25543fc..25421ae0e69 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js'; import * as utils from 'src/utils.js'; +import * as detectBrowserUtils from '../../../libraries/detectBrowserUtils/detectBrowserUtils'; import { server } from 'test/mocks/xhr.js'; import { config } from 'src/config.js'; import { EVENTS } from 'src/constants.js'; @@ -67,6 +68,7 @@ let wonRequest = { describe('IntentIQ tests all', function () { let logErrorStub; + let detectBrowserStub; beforeEach(function () { logErrorStub = sinon.stub(utils, 'logError'); @@ -94,6 +96,7 @@ describe('IntentIQ tests all', function () { afterEach(function () { logErrorStub.restore(); + if (detectBrowserStub) detectBrowserStub.restore(); config.getConfig.restore(); events.getEvents.restore(); iiqAnalyticsAnalyticsAdapter.disableAnalytics(); @@ -172,4 +175,37 @@ describe('IntentIQ tests all', function () { expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('B'); expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; }); + + it('should not send request if the browser is in blacklist (chrome)', function () { + const USERID_CONFIG_BROWSER = [...USERID_CONFIG]; + USERID_CONFIG_BROWSER[0].params.browserBlackList = 'ChrOmE'; + + config.getConfig.restore(); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG_BROWSER); + detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('chrome'); + + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.equal(0); + }); + + it('should send request if the browser is not in blacklist (safari)', function () { + const USERID_CONFIG_BROWSER = [...USERID_CONFIG]; + USERID_CONFIG_BROWSER[0].params.browserBlackList = 'chrome,firefox'; + + config.getConfig.restore(); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG_BROWSER); + detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('safari'); + + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain(`https://reports.intentiq.com/report?pid=${partner}&mct=1`); + expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain('&payload='); + expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + }); }); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index c5dabfbeed3..596185bedbd 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,9 +2,10 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, decryptData, detectBrowserFromUserAgent, detectBrowserFromUserAgentData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; +import { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; import { clearAllCookies } from '../../helpers/cookies'; +import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/detectBrowserUtils/detectBrowserUtils'; const partner = 10; const pai = '11'; From 25667cd6607da75619f03fb6040734060d9a0af4 Mon Sep 17 00:00:00 2001 From: mp4symitri Date: Tue, 3 Sep 2024 17:58:37 +0530 Subject: [PATCH 0474/1097] symitriDapRtdProvider : Hash user identity before using it (#12129) * symitriDapRtdModule-enable-secure-ad-receipt * WIP - adding unit tests * Add unit test * Added user identity hashing. Removed Publisher Page scrapping Removed onBidWonListener logic Changed documentation Updated related tests * Added comments * Updated comment * Dont rely on entropy script to execute code, use async sha256 hash function * updating tests * be more definsive around window.crypto use and test more reliable * check for crypto.subtle in test --------- Co-authored-by: Jeff Palladino Co-authored-by: Manan --- .../gpt/symitridap_segments_example.html | 132 ++++++++++++++++++ .../creative-renderer-display/renderer.js | 2 +- .../creative-renderer-native/renderer.js | 2 +- modules/symitriDapRtdProvider.js | 81 +++++++---- modules/symitriDapRtdProvider.md | 107 +++++++++----- .../modules/symitriDapRtdProvider_spec.js | 51 ++++++- 6 files changed, 309 insertions(+), 66 deletions(-) create mode 100644 integrationExamples/gpt/symitridap_segments_example.html diff --git a/integrationExamples/gpt/symitridap_segments_example.html b/integrationExamples/gpt/symitridap_segments_example.html new file mode 100644 index 00000000000..8ec7958dd0d --- /dev/null +++ b/integrationExamples/gpt/symitridap_segments_example.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
Segments Sent to Bidding Adapter
+
+ + diff --git a/libraries/creative-renderer-display/renderer.js b/libraries/creative-renderer-display/renderer.js index 72f3658fe79..146afab46ae 100644 --- a/libraries/creative-renderer-display/renderer.js +++ b/libraries/creative-renderer-display/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "!function(){\"use strict\";window.render=function({ad:d,adUrl:i,width:n,height:e},{mkFrame:o},r){if(!d&&!i)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{const t=r.document,s={width:n,height:e};i&&!d?s.src=i:s.srcdoc=d,t.body.appendChild(o(t,s))}}}();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";window.render=function({ad:d,adUrl:e,width:i,height:r},{mkFrame:n},o){if(!d&&!e)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{const s=o.document,t={width:i,height:r};e&&!d?t.src=e:t.srcdoc=d,s.body.appendChild(n(s,t))}}})();" \ No newline at end of file diff --git a/libraries/creative-renderer-native/renderer.js b/libraries/creative-renderer-native/renderer.js index 57d86fc8ce3..d7d85cdd7ba 100644 --- a/libraries/creative-renderer-native/renderer.js +++ b/libraries/creative-renderer-native/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "!function(){\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}}();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}})();" \ No newline at end of file diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 1921bbddf4c..aed583cbdfa 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -70,32 +70,41 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { */ 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.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { - setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy 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); + + // Attempt to load entroy script if no entropy object exist and entropy config settings are present. + // Else + if (!entropyDict && rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { + setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); } else { - if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { - loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); } else { - reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapEntropyUrl 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); }); + + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); + } else { + logMessage('No dapEntropyUrl is specified.'); + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + } } function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { @@ -131,18 +140,17 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { /** * Module init - * @param {Object} provider + * @param {Object} config * @param {Object} userConsent * @return {boolean} */ - function init(provider, userConsent) { + function init(config, userConsent) { if (dapUtils.checkConsent(userConsent) === false) { return false; } return true; } - /** @type {RtdSubmodule} */ const rtdSubmodule = { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, @@ -159,7 +167,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, segtax: rtdConfig.params.segtax, - identity: {type: rtdConfig.params.identityType} + identity: {type: rtdConfig.params.identityType, value: rtdConfig.params.identityValue}, }; let refreshMembership = true; let token = dapUtils.dapGetTokenFromLocalStorage(); @@ -526,6 +534,16 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return [ config, false ]; }, + addIdentifier: async function(identity, apiParams) { + if (window.crypto && window.crypto.subtle && typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { + const hashBuffer = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + apiParams.identity = hashHex.toUpperCase(); + } + return apiParams + }, + /** * SYNOPSIS * @@ -594,9 +612,16 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { 'type': identity.type, }; - if (typeof (identity.identity) != typeof (undefined)) { - apiParams.identity = identity.identity; + if (identity.type === 'hid') { + this.addIdentifier(identity, apiParams).then((apiParams) => { + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); + }); + } else { + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); } + }, + + callTokenize(config, identity, apiParams, onDone, onSuccess, onError) { if (typeof (identity.attributes) != typeof (undefined)) { apiParams.attributes = identity.attributes; } @@ -621,7 +646,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return; } - let customHeaders = {'Content-Type': 'application/json'}; + let customHeaders = { 'Content-Type': 'application/json' }; let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); if (dapSSID) { customHeaders[headerPrefix + '-DAP-SS-ID'] = dapSSID; diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index 654f7b25bfd..f44c83f9dfb 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -1,49 +1,88 @@ -### Overview +--- +layout: page_v2 +title: Symitri DAP Real Time Data Provider Module +display_name: Symitri DAP Real Time Data Provider Module +description: Symitri DAP Real Time Data Provider Module +page_type: module +module_type: rtd +module_code : symitriDapRtdProvider +enable_download : true +vendor_specific: true +sidebarType : 1 +--- - Symitri DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the SAID to the bid-stream. +# Symitri DAP Real Time Data Provider Module -### Integration +{:.no_toc} - 1) Build the symitriDapRTD module into the Prebid.js package with: +* TOC +{:toc} - ``` - gulp build --modules=symitriDapRtdProvider,... - ``` +The Symitri Data Activation Platform (DAP) is a privacy-first system that protects end-user privacy by only allowing them to be targeted as part of a larger cohort. Symitri DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the Secure Ad ID(SAID) to the bid-stream. SAID is a JWT/JWE which carries with it the cohorts and only a side-car or trusted server in the demand-side platform is allowed to see its contents. - 2) Use `setConfig` to instruct Prebid.js to initilaize the symitriDapRtdProvider module, as specified below. +## Publisher Usage + +1. Build the symitriDapRTD module into the Prebid.js package with: + + ```bash + gulp build --modules=symitriDapRtdProvider,... + ``` + +2. Use `setConfig` to instruct Prebid.js to initilaize the symitriDapRtdProvider module, as specified below. ### Configuration +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 2000, + dataProviders: [ + { + name: "symitriDap", + waitForIt: true, + params: { + apiHostname: '', + apiVersion: "x1", + apiAuthToken: '', + domain: 'your-domain.com', + identityType: 'hid'| ... | 'dap-signature:1.0.0', + identityValue: '', + segtax: 501, + dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', + dapEntropyTimeout: 1500 + } + } + ] + } +}); ``` - pbjs.setConfig({ - realTimeData: { - auctionDelay: 2000, - dataProviders: [ - { - name: "dap", - waitForIt: true, - params: { - apiHostname: '', - apiVersion: "x1", - domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', - segtax: 504, - dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', - dapEntropyTimeout: 1500 // Maximum time for dapentropy to run - } - } - ] - } - }); - ``` - -Please reach out to your Symitri account representative(Prebid@symitri.com) to get provisioned on the DAP platform. +Please reach out to your Symitri account representative() to get provisioned on the DAP platform. + +**Config Syntax details:** + +{: .table .table-bordered .table-striped } +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Symitri Dap Rtd module name | 'symitriDap' always| +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| apiHostname | String | Hostname provided by Symitri | Please reach out to your Symitri account representative() for this value| +| apiVersion | String | This holds the API version | It should be "x1" always | +| apiAuthToken | String | Symitri API AuthToken | Please reach out to your Symitri account representative() for this value | +| domain | String | The domain name of your webpage | | +| identityType | String | Something like this 'hid', ... 'dap-signature:1.0.0' | | +| identityValue | String | This is optional field to pass user hid. Will be used only if identityType is hid | | +| segtax | Integer | The taxonomy for Symitri | The value should be 501 | +| dapEntropyUrl | String | URL to dap entropy script | Optional if the script is directly included on the webpage. Contact your Symitri account rep for more details | +| dapEntropyTimeout | Integer | Maximum time allotted for the entropy calculation to happen | | ### Testing + To view an example of available segments returned by dap: + +```bash +gulp serve --modules=rtdModule,symitriDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter ``` -‘gulp serve --modules=rtdModule,symitriDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter’ -``` + and then point your browser at: -"http://localhost:9999/integrationExamples/gpt/symitridap_segments_example.html" +"" diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 79ebfd707c7..793d8deafbe 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -3,12 +3,16 @@ import { dapUtils, generateRealTimeData, symitriDapRtdSubmodule, + onBidWonListener, storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP } from 'modules/symitriDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; import {hook} from '../../../src/hook.js'; +import { EVENTS } from 'src/constants.js'; const responseHeader = {'Content-Type': 'application/json'}; +let events = require('src/events'); + describe('symitriDapRtdProvider', function() { const testReqBidsConfigObj = { adUnits: [ @@ -37,11 +41,12 @@ describe('symitriDapRtdProvider', function() { }; const cmoduleConfig = { - 'name': 'dap', + 'name': 'symitriDap', 'waitForIt': true, 'params': { 'apiHostname': 'prebid.dap.akadns.net', 'apiVersion': 'x1', + 'apiAuthToken': 'Token 1234', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', 'segtax': 503 @@ -49,7 +54,7 @@ describe('symitriDapRtdProvider', function() { } const emoduleConfig = { - 'name': 'dap', + 'name': 'symitriDap', 'waitForIt': true, 'params': { 'apiHostname': 'prebid.dap.akadns.net', @@ -597,4 +602,46 @@ describe('symitriDapRtdProvider', function() { expect(symitriDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); + + describe('Test identifier is added properly to apiParams', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('passed identifier is handled', async function () { + const test_identity = 'test_identity_1234'; + let identity = { + value: test_identity + }; + let apiParams = { + 'type': identity.type, + }; + + if (window.crypto && window.crypto.subtle) { + let hid = await dapUtils.addIdentifier(identity, apiParams).then(); + expect(hid['identity']).is.equal('843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'); + } else { + expect(window.crypto.subtle).is.undefined + } + }); + + it('passed undefined identifier is handled', async function () { + const test_identity = undefined; + let identity = { + identity: test_identity + } + let apiParams = { + 'type': identity.type, + }; + + let hid = await dapUtils.addIdentifier(identity, apiParams); + expect(hid.identity).is.undefined; + }); + }); }); From 56b098516d7119b70211b7b33526a0f09248ba6f Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:17:17 +0300 Subject: [PATCH 0475/1097] Jsdoc Lint: fix types in modules userId (#12196) --- modules/userId/eids.js | 5 +-- modules/userId/index.js | 72 ++++++++++++++++----------------------- modules/yandexIdSystem.js | 17 ++++----- 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/modules/userId/eids.js b/modules/userId/eids.js index bf425b8d9f0..57341773184 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,6 +1,7 @@ import {deepClone, isFn, isStr} from '../../src/utils.js'; -/* - * @typedef {import('../modules/userId/index.js').SubmoduleContainer} SubmoduleContainer + +/** + * @typedef {import('./index.js').SubmodulePriorityMap} SubmodulePriorityMap */ export const EID_CONFIG = new Map(); diff --git a/modules/userId/index.js b/modules/userId/index.js index f7c6e1c5433..2ac23011417 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -4,63 +4,47 @@ */ /** - * @interface Submodule + * @typedef Submodule + * @property {string} name - used to link submodule with config + * @property {decode} decode + * @property {getId} getId + * @property {Object} eids + * @property {number} [gvlid] - vendor ID + * @property {extendId} [extendId] + * @property {function} [domainOverride] - use a predefined domain override for cookies or provide your own + * @property {function(): string} [findRootDomain] - returns the root domain */ /** - * @function - * @summary performs action to obtain id and return a value in the callback's response argument. - * If IdResponse#id is defined, then it will be written to the current active storage. - * If IdResponse#callback is defined, then it'll called at the end of auction. - * It's permissible to return neither, one, or both fields. - * @name Submodule#getId + * Performs action to obtain id and return a value in the callback's response argument. + * If IdResponse#id is defined, then it will be written to the current active storage. + * If IdResponse#callback is defined, then it'll called at the end of auction. + * It's permissible to return neither, one, or both fields. + * @callback getId * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData - * @param {(Object|undefined)} cacheIdObj - * @return {(IdResponse|undefined)} A response object that contains id and/or callback. + * @param {Object|undefined} cacheIdObj + * @returns {IdResponse|undefined} A response object that contains id and/or callback. */ /** - * @function - * @summary Similar to Submodule#getId, this optional method returns response to for id that exists already. - * If IdResponse#id is defined, then it will be written to the current active storage even if it exists already. - * If IdResponse#callback is defined, then it'll called at the end of auction. - * It's permissible to return neither, one, or both fields. - * @name Submodule#extendId + * Similar to `getId`, this optional method returns response to for id that exists already. + * If IdResponse#id is defined, then it will be written to the current active storage even if it exists already. + * If IdResponse#callback is defined, then it'll called at the end of auction. + * It's permissible to return neither, one, or both fields. + * @callback extendId * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @param {Object} storedId - existing id, if any - * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. + * @returns {IdResponse|function(callback:function)} A response object that contains id and/or callback. */ /** - * @function - * @summary decode a stored value for passing to bid requests - * @name Submodule#decode + * Decode a stored value for passing to bid requests + * @callback decode * @param {Object|string} value * @param {SubmoduleConfig|undefined} config - * @return {(Object|undefined)} - */ - -/** - * @property - * @summary used to link submodule with config - * @name Submodule#name - * @type {string} - */ - -/** - * @property - * @summary use a predefined domain override for cookies or provide your own - * @name Submodule#domainOverride - * @type {(undefined|function)} - */ - -/** - * @function - * @summary Returns the root domain - * @name Submodule#findRootDomain - * @returns {string} + * @returns {Object|undefined} */ /** @@ -69,6 +53,7 @@ * @property {(SubmoduleStorage|undefined)} storage - browser storage config * @property {(SubmoduleParams|undefined)} params - params config for use by the submodule.getId function * @property {(Object|undefined)} value - if not empty, this value is added to bid requests for access in adapters + * @property {string[]} [enabledStorageTypes] */ /** @@ -111,6 +96,7 @@ * @property {(Object|undefined)} idObj - cache decoded id value (this is copied to every adUnit bid) * @property {(function|undefined)} callback - holds reference to submodule.getId() result if it returned a function. Will be set to undefined after callback executes * @property {StorageManager} storageMgr + * @property {string[]} [enabledStorageTypes] */ /** @@ -122,8 +108,8 @@ /** * @typedef {Object} IdResponse - * @property {(Object|undefined)} id - id data - * @property {(function|undefined)} callback - function that will return an id + * @property {Object} [id] - id data + * @property {function} [callback] - function that will return an id */ /** diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js index 24b1f611ccd..e482a9645a4 100644 --- a/modules/yandexIdSystem.js +++ b/modules/yandexIdSystem.js @@ -6,6 +6,11 @@ // @ts-check +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + */ + import { MODULE_TYPE_UID } from '../src/activities/modules.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -26,11 +31,8 @@ export const PREBID_STORAGE = getStorageManager({ bidderCode: undefined }); +/** @type {Submodule} */ export const yandexIdSubmodule = { - /** - * Used to link submodule with config. - * @type {string} - */ name: BIDDER_CODE, /** * Decodes the stored id value for passing to bid requests. @@ -41,11 +43,6 @@ export const yandexIdSubmodule = { return { [YANDEX_ID_KEY]: value }; }, - /** - * @param {import('./userId/index.js').SubmoduleConfig} submoduleConfig - * @param {unknown} [_consentData] - * @param {string} [storedId] Id that was saved by the core previously. - */ getId(submoduleConfig, _consentData, storedId) { if (checkConfigHasErrorsAndReport(submoduleConfig)) { return; @@ -70,7 +67,7 @@ export const yandexIdSubmodule = { }; /** - * @param {import('./userId/index.js').SubmoduleConfig} submoduleConfig + * @param {SubmoduleConfig} submoduleConfig * @returns {boolean} `true` - when there are errors, `false` - otherwise. */ function checkConfigHasErrorsAndReport(submoduleConfig) { From 9f1bf51e6aefc2581c604b7af9227e18f74dbf40 Mon Sep 17 00:00:00 2001 From: Siminko Vlad <85431371+siminkovladyslav@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:24:34 +0200 Subject: [PATCH 0476/1097] OMS Bid Adapter: add user syncs, test coverage and update documentation (#12137) * OMS Adapter: add user syncs, test coverage and update documentation * remove code duplication in getUserSyncs func for oms, admatic, rubicon and dianomi adapters --- libraries/userSyncUtils/userSyncUtils.js | 24 +++++++++ modules/admaticBidAdapter.js | 22 +------- modules/dianomiBidAdapter.js | 14 +----- modules/omsBidAdapter.js | 22 ++++++-- modules/omsBidAdapter.md | 2 +- modules/rubiconBidAdapter.js | 22 +------- test/spec/modules/omsBidAdapter_spec.js | 64 ++++++++++++++++++++++-- 7 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 libraries/userSyncUtils/userSyncUtils.js diff --git a/libraries/userSyncUtils/userSyncUtils.js b/libraries/userSyncUtils/userSyncUtils.js new file mode 100644 index 00000000000..db8c77d307b --- /dev/null +++ b/libraries/userSyncUtils/userSyncUtils.js @@ -0,0 +1,24 @@ +export function getUserSyncParams(gdprConsent, uspConsent, gppConsent) { + let params = {}; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; + } + } + + if (uspConsent) { + params['us_privacy'] = encodeURIComponent(uspConsent); + } + + if (gppConsent?.gppString) { + params['gpp'] = gppConsent.gppString; + params['gpp_sid'] = gppConsent.applicableSections?.toString(); + } + + return params; +} diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 34a6ec788bd..1b8b8752235 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -3,6 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -159,26 +160,7 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = {}; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params['gdpr'] = Number(gdprConsent.gdprApplies); - } - if (typeof gdprConsent.consentString === 'string') { - params['gdpr_consent'] = gdprConsent.consentString; - } - } - - if (uspConsent) { - params['us_privacy'] = encodeURIComponent(uspConsent); - } - - if (gppConsent?.gppString) { - params['gpp'] = gppConsent.gppString; - params['gpp_sid'] = gppConsent.applicableSections?.toString(); - } - + let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); params = Object.keys(params).length ? `?${formatQS(params)}` : ''; hasSynced = true; diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js index f4f81f20f87..3ae8b4d6b80 100644 --- a/modules/dianomiBidAdapter.js +++ b/modules/dianomiBidAdapter.js @@ -15,6 +15,7 @@ import { import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; const { getConfig } = config; @@ -302,19 +303,8 @@ export const spec = { .filter(Boolean); }, getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - const params = {}; - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params['gdpr'] = Number(gdprConsent.gdprApplies); - } - if (typeof gdprConsent.consentString === 'string') { - params['gdpr_consent'] = gdprConsent.consentString; - } - } + const params = getUserSyncParams(gdprConsent, uspConsent); - if (uspConsent) { - params['us_privacy'] = encodeURIComponent(uspConsent); - } if (syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint return { diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 2fec1c1d7a9..901b9a138d5 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -10,15 +10,18 @@ import { isPlainObject, getBidIdParameter, getUniqueIdentifierStr, + formatQS, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {percentInView} from '../libraries/percentInView/percentInView.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; const BIDDER_CODE = 'oms'; const URL = 'https://rt.marphezis.com/hb'; -const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid'; +const USER_SYNC_URL_IFRAME = 'https://rt.marphezis.com/sync?dpid=0'; export const spec = { code: BIDDER_CODE, @@ -179,9 +182,20 @@ function interpretResponse(serverResponse) { return response; } -// Don't do user sync for now -function getUserSyncs(syncOptions, responses, gdprConsent) { - return []; +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + + if (syncOptions.iframeEnabled) { + let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); + params = Object.keys(params).length ? `&${formatQS(params)}` : ''; + + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + params, + }); + } + + return syncs; } function onBidderError(errorData) { diff --git a/modules/omsBidAdapter.md b/modules/omsBidAdapter.md index f1e2d459eca..0a6b9cac82c 100644 --- a/modules/omsBidAdapter.md +++ b/modules/omsBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: OMS Bid Adapter Module Type: Bidder Adapter -Maintainer: alexandruc@onlinemediasolutions.com +Maintainer: devsupport@onlinemediasolutions.com ``` # Description diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 457c52d0750..0a27a1116a4 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -22,6 +22,7 @@ import { _each } from '../src/utils.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -744,26 +745,7 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = {}; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params['gdpr'] = Number(gdprConsent.gdprApplies); - } - if (typeof gdprConsent.consentString === 'string') { - params['gdpr_consent'] = gdprConsent.consentString; - } - } - - if (uspConsent) { - params['us_privacy'] = encodeURIComponent(uspConsent); - } - - if (gppConsent?.gppString) { - params['gpp'] = gppConsent.gppString; - params['gpp_sid'] = gppConsent.applicableSections?.toString(); - } - + let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); params = Object.keys(params).length ? `?${formatQS(params)}` : ''; hasSynced = true; diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index 18b878acac3..dc2d8ffebb6 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -410,11 +410,67 @@ describe('omsBidAdapter', function () { }); describe('getUserSyncs ', () => { - let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + const syncOptions = { iframeEnabled: true }; + const userSyncUrlIframe = 'https://rt.marphezis.com/sync?dpid=0'; - it('should not return', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); - expect(returnStatement).to.be.empty; + it('returns empty syncs arr when syncOptions.iframeEnabled is false', () => { + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.be.empty; + }); + + it('returns syncs arr when syncOptions.iframeEnabled is true', () => { + expect(spec.getUserSyncs(syncOptions, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', + url: userSyncUrlIframe + }]); + }); + + it('should pass gdpr param when gdprConsent.gdprApplies type is boolean', () => { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: true }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}&gdpr=1` + }]); + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: false }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}&gdpr=0` + }]); + }); + + it('should pass gdpr_consent param when gdprConsent.consentString type is string', () => { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: false, consentString: 'test' }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}&gdpr=0&gdpr_consent=test` + }]); + }); + + it('should pass no params when gdprConsent.consentString and gdprConsent.gdprApplies types dont match', () => { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: 'true', consentString: 1 }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}` + }]); + }); + + it('should pass us_privacy param when uspConsent is defined', function () { + expect(spec.getUserSyncs(syncOptions, {}, undefined, 'test')).to.deep.equal([{ + type: 'iframe', url: `${userSyncUrlIframe}&us_privacy=test` + }]); + }); + + it('should pass gpp and gpp_sid params when gppConsent.gppString is defined', function () { + expect(spec.getUserSyncs(syncOptions, {}, {}, undefined, { + gppString: 'test', + applicableSections: [1, 2] + })).to.deep.equal([{ + type: 'iframe', url: `${userSyncUrlIframe}&gpp=test&gpp_sid=1,2` + }]); + }); + + it('should pass all params correctly', function () { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: false, consentString: 'test' }, 'test', { + gppString: 'test', + applicableSections: [] + })).to.deep.equal([{ + type: 'iframe', url: `${userSyncUrlIframe}&gdpr=0&gdpr_consent=test&us_privacy=test&gpp=test&gpp_sid=` + }]); }); }); }); From b4f6df60eb71d73494557b2a9fc7165d2c503de1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:50:21 -0400 Subject: [PATCH 0477/1097] Bump webpack from 5.92.0 to 5.94.0 (#12195) Bumps [webpack](https://github.com/webpack/webpack) from 5.92.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.92.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 50 +++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2c5d20b1f5..2dfd1d24563 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3190,16 +3190,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -11525,9 +11515,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -28946,12 +28936,11 @@ "dev": true }, "node_modules/webpack": { - "version": "5.92.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz", - "integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -28960,7 +28949,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -31971,16 +31960,6 @@ "@types/json-schema": "*" } }, - "@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -38384,9 +38363,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -51800,12 +51779,11 @@ "dev": true }, "webpack": { - "version": "5.92.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.0.tgz", - "integrity": "sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "requires": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -51814,7 +51792,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", From a8b7e9871c26f31893b4a3f93d182c2abff63f3f Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Tue, 3 Sep 2024 20:04:29 +0300 Subject: [PATCH 0478/1097] AdMatic Bid Adapter : default currency removed (#12198) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 22 +++++++++++---------- test/spec/modules/admaticBidAdapter_spec.js | 13 ++++++++---- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 1b8b8752235..a5ebe91f98a 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -12,7 +12,7 @@ import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; */ export const OPENRTB = { - NATIVE: { + N: { IMAGE_TYPE: { ICON: 1, MAIN: 3, @@ -74,7 +74,6 @@ export const spec = { const ortb = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); - const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; const bidderName = validBidRequests[0].bidder; const payload = { @@ -89,7 +88,6 @@ export const spec = { }, imp: bids, ext: { - cur: currency, bidder: bidderName }, schain: {}, @@ -104,6 +102,10 @@ export const spec = { tmax: parseInt(tmax) }; + if (config.getConfig('currency.adServerCurrency')) { + payload.ext.cur = config.getConfig('currency.adServerCurrency'); + } + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ''; @@ -189,7 +191,7 @@ export const spec = { cpm: bid.price, width: bid.width, height: bid.height, - currency: body.cur || 'TRY', + currency: body.cur, netRevenue: true, creativeId: bid.creative_id, meta: { @@ -414,30 +416,30 @@ function interpretNativeAd(adm) { }; native.assets.forEach(asset => { switch (asset.id) { - case OPENRTB.NATIVE.ASSET_ID.TITLE: + case OPENRTB.N.ASSET_ID.TITLE: result.title = asset.title.text; break; - case OPENRTB.NATIVE.ASSET_ID.IMAGE: + case OPENRTB.N.ASSET_ID.IMAGE: result.image = { url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; break; - case OPENRTB.NATIVE.ASSET_ID.ICON: + case OPENRTB.N.ASSET_ID.ICON: result.icon = { url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; break; - case OPENRTB.NATIVE.ASSET_ID.BODY: + case OPENRTB.N.ASSET_ID.BODY: result.body = asset.data.value; break; - case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + case OPENRTB.N.ASSET_ID.SPONSORED: result.sponsoredBy = asset.data.value; break; - case OPENRTB.NATIVE.ASSET_ID.CTA: + case OPENRTB.N.ASSET_ID.CTA: result.cta = asset.data.value; break; } diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 8730f2c1a0d..50700a7b4e1 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -802,6 +802,7 @@ describe('admaticBidAdapter', () => { 'price': 0.01, 'type': 'banner', 'bidder': 'admatic', + 'currency': 'TRY', 'mime_type': { 'name': 'backfill', 'force': false @@ -816,6 +817,7 @@ describe('admaticBidAdapter', () => { 'width': 300, 'height': 250, 'price': 0.01, + 'currency': 'TRY', 'type': 'video', 'mime_type': { 'name': 'backfill', @@ -832,6 +834,7 @@ describe('admaticBidAdapter', () => { 'width': 1, 'height': 1, 'price': 0.01, + 'currency': 'TRY', 'type': 'native', 'mime_type': { 'name': 'backfill', @@ -843,6 +846,7 @@ describe('admaticBidAdapter', () => { 'iurl': 'https://www.admatic.com.tr' } ], + 'cur': 'TRY', 'queryId': 'cdnbh24rlv0hhkpfpln0', 'status': true }}; @@ -853,9 +857,9 @@ describe('admaticBidAdapter', () => { cpm: 0.01, width: 300, height: 250, - currency: 'TRY', mediaType: 'banner', netRevenue: true, + currency: 'TRY', ad: '
', creativeId: '374', meta: { @@ -873,9 +877,9 @@ describe('admaticBidAdapter', () => { cpm: 0.01, width: 300, height: 250, - currency: 'TRY', mediaType: 'video', netRevenue: true, + currency: 'TRY', vastXml: '', creativeId: '3741', meta: { @@ -893,9 +897,9 @@ describe('admaticBidAdapter', () => { cpm: 0.01, width: 1, height: 1, - currency: 'TRY', mediaType: 'native', netRevenue: true, + currency: 'TRY', native: { 'clickUrl': 'https://www.admatic.com.tr', 'impressionTrackers': ['https://www.admatic.com.tr'], @@ -1144,7 +1148,8 @@ describe('admaticBidAdapter', () => { let bids = { body: { data: [], 'queryId': 'cdnbh24rlv0hhkpfpln0', - 'status': true + 'status': true, + 'cur': 'TRY' }}; let result = spec.interpretResponse(bids, {data: request}); From fe190c336ea1438671a718c88bbe35f5e4c64d5a Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:08:12 +0200 Subject: [PATCH 0479/1097] RTB House Bid Adapter: paapi response interpreter uses additional config params (#12197) * RTB House Bid Adapter: paapi response interpreter uses additional config params * RTB House Bid Adapter: fix lint errors --- modules/rtbhouseBidAdapter.js | 39 ++++++---- test/spec/modules/rtbhouseBidAdapter_spec.js | 81 +++++++++++++++++++- 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 7e2a7da3b61..4f8c8c1c550 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -115,10 +115,11 @@ export const spec = { let computedEndpointUrl = ENDPOINT_URL; if (bidderRequest.paapi?.enabled) { - const fledgeConfig = config.getConfig('fledgeConfig') || { + const fromConfig = config.getConfig('paapiConfig') || config.getConfig('fledgeConfig') || { sellerTimeout: 500 }; + const fledgeConfig = { seller: FLEDGE_SELLER_URL, decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, - sellerTimeout: 500 + ...fromConfig }; mergeDeep(request, { ext: { fledge_config: fledgeConfig } }); computedEndpointUrl = FLEDGE_ENDPOINT_URL; @@ -165,7 +166,6 @@ export const spec = { interpretResponse: function (serverResponse, originalRequest) { let bids; - const fledgeInterestGroupBuyers = config.getConfig('fledgeConfig.interestGroupBuyers') || []; const responseBody = serverResponse.body; let fledgeAuctionConfigs = null; @@ -173,24 +173,35 @@ export const spec = { // we have fledge response // mimic the original response ([{},...]) bids = this.interpretOrtbResponse({ body: responseBody.seatbid[0]?.bid }, originalRequest); - - const seller = responseBody.ext.seller; - const decisionLogicUrl = responseBody.ext.decisionLogicUrl; - const sellerTimeout = 'sellerTimeout' in responseBody.ext ? { sellerTimeout: responseBody.ext.sellerTimeout } : {}; + const paapiAdapterConfig = config.getConfig('paapiConfig') || config.getConfig('fledgeConfig') || {}; + const fledgeInterestGroupBuyers = paapiAdapterConfig.interestGroupBuyers || []; + // values from the response.ext are the most important + const { + decisionLogicUrl = paapiAdapterConfig.decisionLogicUrl || paapiAdapterConfig.decisionLogicURL || + FLEDGE_DECISION_LOGIC_URL, + seller = paapiAdapterConfig.seller || FLEDGE_SELLER_URL, + sellerTimeout = 500 + } = responseBody.ext; + + const fledgeConfig = { + seller, + decisionLogicUrl, + decisionLogicURL: decisionLogicUrl, + sellerTimeout + }; + // fledgeConfig settings are more important; other paapiAdapterConfig settings are facultative + mergeDeep(fledgeConfig, paapiAdapterConfig, fledgeConfig); responseBody.ext.igbid.forEach((igbid) => { - const perBuyerSignals = {}; + const perBuyerSignals = {...fledgeConfig.perBuyerSignals}; // may come from paapiAdapterConfig igbid.igbuyer.forEach(buyerItem => { perBuyerSignals[buyerItem.igdomain] = buyerItem.buyersignal }); fledgeAuctionConfigs = fledgeAuctionConfigs || {}; - fledgeAuctionConfigs[igbid.impid] = mergeDeep( + fledgeAuctionConfigs[igbid.impid] = mergeDeep({}, fledgeConfig, { - seller, - decisionLogicUrl, - interestGroupBuyers: [...fledgeInterestGroupBuyers, ...Object.keys(perBuyerSignals)], + interestGroupBuyers: [...new Set([...fledgeInterestGroupBuyers, ...Object.keys(perBuyerSignals)])], perBuyerSignals, - }, - sellerTimeout + } ); }); } else { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index cc303dc2f96..f3f9ce8616f 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -460,7 +460,7 @@ describe('RTBHouseAdapter', () => { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: true }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: { enabled: true } }); expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids'); expect(request.method).to.equal('POST'); }); @@ -470,7 +470,7 @@ describe('RTBHouseAdapter', () => { delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: false }); - const request = spec.buildRequests(bidRequest, {...bidderRequest, paapi: {enabled: true}}); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -480,7 +480,7 @@ describe('RTBHouseAdapter', () => { expect(data.ext.fledge_config.sellerTimeout).to.equal(500); }); - it('sets a fledgeConfig object values when available from config', function () { + it('sets request.ext.fledge_config object values when available from fledgeConfig', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; @@ -490,7 +490,7 @@ describe('RTBHouseAdapter', () => { decisionLogicUrl: 'https://sellers.domain/decision.url' } }); - const request = spec.buildRequests(bidRequest, {...bidderRequest, paapi: {enabled: true}}); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -500,6 +500,49 @@ describe('RTBHouseAdapter', () => { expect(data.ext.fledge_config.sellerTimeout).to.not.exist; }); + it('sets request.ext.fledge_config object values when available from paapiConfig', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + + config.setConfig({ + paapiConfig: { + seller: 'https://sellers.domain', + decisionLogicUrl: 'https://sellers.domain/decision.url' + } + }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); + const data = JSON.parse(request.data); + expect(data.ext).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.contain.keys('seller', 'decisionLogicUrl'); + expect(data.ext.fledge_config.seller).to.equal('https://sellers.domain'); + expect(data.ext.fledge_config.decisionLogicUrl).to.equal('https://sellers.domain/decision.url'); + expect(data.ext.fledge_config.sellerTimeout).to.not.exist; + }); + + it('sets request.ext.fledge_config object values when available from paapiConfig rather than from fledgeConfig if both exist', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + + config.setConfig({ + paapiConfig: { + seller: 'https://paapiconfig.sellers.domain', + decisionLogicUrl: 'https://paapiconfig.sellers.domain/decision.url' + }, + fledgeConfig: { + seller: 'https://fledgeconfig.sellers.domain', + decisionLogicUrl: 'https://fledgeconfig.sellers.domain/decision.url' + } + }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); + const data = JSON.parse(request.data); + expect(data.ext).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.contain.keys('seller', 'decisionLogicUrl'); + expect(data.ext.fledge_config.seller).to.equal('https://paapiconfig.sellers.domain'); + expect(data.ext.fledge_config.decisionLogicUrl).to.equal('https://paapiconfig.sellers.domain/decision.url'); + }); + it('when FLEDGE is disabled, should not send imp.ext.ae', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; @@ -788,6 +831,36 @@ describe('RTBHouseAdapter', () => { }); }); + context('when the response contains FLEDGE auction config and bid request has additional signals in paapiConfig', function () { + let bidderRequest; + config.setConfig({ + paapiConfig: { + interestGroupBuyers: ['https://buyer1.com'], + perBuyerSignals: { + 'https://buyer1.com': { signal: 1 } + }, + customSignal: 1 + } + }); + let response = spec.interpretResponse({body: fledgeResponse}, {bidderRequest}); + + it('should have 2 buyers in interestGroupBuyers', function () { + expect(response.paapi[0].config.interestGroupBuyers.length).to.equal(2); + expect(response.paapi[0].config.interestGroupBuyers).to.have.members(['https://buyer1.com', 'https://buyer-domain.com']); + }); + + it('should have 2 perBuyerSignals with proper values', function () { + expect(response.paapi[0].config.perBuyerSignals).to.contain.keys('https://buyer1.com', 'https://buyer-domain.com'); + expect(response.paapi[0].config.perBuyerSignals['https://buyer1.com']).to.deep.equal({ signal: 1 }); + expect(response.paapi[0].config.perBuyerSignals['https://buyer-domain.com']).to.deep.equal({}); + }); + + it('should contain any custom signal passed via paapiConfig', function () { + expect(response.paapi[0].config).to.contain.keys('customSignal'); + expect(response.paapi[0].config.customSignal).to.equal(1); + }); + }); + context('when the response contains DSA object', function () { it('should get correct bid response', function () { const dsa = { From c2fdee7979fd17ea5400e7271d844a9feaa4f4dd Mon Sep 17 00:00:00 2001 From: Andrius Versockas Date: Wed, 4 Sep 2024 16:06:14 +0300 Subject: [PATCH 0480/1097] Eskimi Bid Adapter: Endpoint adjustments and cookie-sync endpoint (#12201) Co-authored-by: Andrius Versockas --- modules/eskimiBidAdapter.js | 173 ++++++++++++++++++--- modules/eskimiBidAdapter.md | 33 +++- test/spec/modules/eskimiBidAdapter_spec.js | 14 +- 3 files changed, 193 insertions(+), 27 deletions(-) diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index 02568c01014..dd3f634462d 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -2,15 +2,16 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; -import {getBidIdParameter} from '../src/utils.js'; +import {getBidIdParameter, logInfo, mergeDeep} from '../src/utils.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; /** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests */ const BIDDER_CODE = 'eskimi'; -const ENDPOINT = 'https://sspback.eskimi.com/bid-request' - const DEFAULT_BID_TTL = 30; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; @@ -36,24 +37,39 @@ const VIDEO_ORTB_PARAMS = [ const BANNER_ORTB_PARAMS = [ 'battr' -] +]; + +const REGION_SUBDOMAIN_SUFFIX = { + EU: '', + US: '-us-e', + APAC: '-asia' +}; export const spec = { code: BIDDER_CODE, + aliases: ['eskimi'], gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, + getUserSyncs, /** * Register bidder specific code, which will execute if a bid from this bidder won the auction * @param {Bid} bid The bid that won the auction */ onBidWon: function (bid) { + logInfo('Bid won: ', bid); if (bid.burl) { utils.triggerPixel(bid.burl); } - } + }, + onTimeout: function (timeoutData) { + logInfo('Timeout: ', timeoutData); + }, + onBidderError: function ({error, bidderRequest}) { + logInfo('Error: ', error, bidderRequest); + }, } registerBidder(spec); @@ -79,7 +95,24 @@ const CONVERTER = ortbConverter({ } return imp; - } + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + pv: '$prebid.version$' + } + }) + const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } + if (bid.params.test) { + req.test = 1 + } + return req; + }, }); function isBidRequestValid(bidRequest) { @@ -101,9 +134,17 @@ function isValidVideoRequest(bidRequest) { return utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); } -function buildRequests(validBids, bidderRequest) { - let videoBids = validBids.filter(bid => isVideoBid(bid)); - let bannerBids = validBids.filter(bid => isBannerBid(bid)); +/** + * Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. + * Make a server request from the list of BidRequests. + * + * @param {*} validBidRequests + * @param {*} bidderRequest + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests(validBidRequests, bidderRequest) { + let videoBids = validBidRequests.filter(bid => isVideoBid(bid)); + let bannerBids = validBidRequests.filter(bid => isBannerBid(bid)); let requests = []; bannerBids.forEach(bid => { @@ -118,14 +159,14 @@ function buildRequests(validBids, bidderRequest) { } function interpretResponse(response, request) { - return CONVERTER.fromORTB({ request: request.data, response: response.body }).bids; + return CONVERTER.fromORTB({request: request.data, response: response.body}).bids; } function buildVideoImp(bidRequest, imp) { const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {}); const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {}); - const videoParams = { ...videoAdUnitParams, ...videoBidderParams }; + const videoParams = {...videoAdUnitParams, ...videoBidderParams}; const videoSizes = (videoAdUnitParams && videoAdUnitParams.playerSize) || []; @@ -144,14 +185,14 @@ function buildVideoImp(bidRequest, imp) { imp.video.plcmt = imp.video.plcmt || 4; } - return { ...imp }; + return {...imp}; } function buildBannerImp(bidRequest, imp) { const bannerAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}`, {}); const bannerBidderParams = utils.deepAccess(bidRequest, `params.${BANNER}`, {}); - const bannerParams = { ...bannerAdUnitParams, ...bannerBidderParams }; + const bannerParams = {...bannerAdUnitParams, ...bannerBidderParams}; let sizes = bidRequest.mediaTypes.banner.sizes; @@ -166,15 +207,15 @@ function buildBannerImp(bidRequest, imp) { } }); - return { ...imp }; + return {...imp}; } function createRequest(bidRequests, bidderRequest, mediaType) { - const data = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + const data = CONVERTER.toORTB({bidRequests, bidderRequest, context: {mediaType}}) const bid = bidRequests.find((b) => b.params.placementId) if (!data.site) data.site = {} - data.site.ext = { placementId: bid.params.placementId } + data.site.ext = {placementId: bid.params.placementId} if (bidderRequest.gdprConsent) { if (!data.user) data.user = {}; @@ -191,9 +232,9 @@ function createRequest(bidRequests, bidderRequest, mediaType) { return { method: 'POST', - url: ENDPOINT, + url: getBidRequestUrlByRegion(), data: data, - options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} } } @@ -204,3 +245,99 @@ function isVideoBid(bid) { function isBannerBid(bid) { return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); } + +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @param gppConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && hasSyncConsent(gdprConsent, uspConsent, gppConsent)) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let query = []; + let syncUrl = getUserSyncUrlByRegion(); + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); + query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + // CCPA + if (uspConsent) { + query.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + // GPP Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + query.push('gpp=' + encodeURIComponent(gppConsent.gppString)); + query.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); + } + return [{ + type: pixelType, + url: `${syncUrl}${query.length > 0 ? '?' + query.join('&') : ''}` + }]; + } +} + +function hasSyncConsent(gdprConsent, uspConsent, gppConsent) { + return hasPurpose1Consent(gdprConsent) && hasUspConsent(uspConsent) && hasGppConsent(gppConsent); +} + +function hasUspConsent(uspConsent) { + return typeof uspConsent !== 'string' || !(uspConsent[0] === '1' && uspConsent[2] === 'Y'); +} + +function hasGppConsent(gppConsent) { + return ( + !(gppConsent && Array.isArray(gppConsent.applicableSections)) || + gppConsent.applicableSections.every((section) => typeof section === 'number' && section <= 5) + ); +} + +/** + * Get Bid Request endpoint url by region + * @return {string} + */ +function getBidRequestUrlByRegion() { + return `https://ittr${getRegionSubdomainSuffix()}.eskimi.com/prebidjs`; +} + +/** + * Get User Sync endpoint url by region + * @return {string} + */ +function getUserSyncUrlByRegion() { + return `https://ittpx${getRegionSubdomainSuffix()}.eskimi.com/sync?sp_id=137`; +} + +/** + * Get subdomain URL suffix by region + * @return {string} + */ +function getRegionSubdomainSuffix() { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + + switch (region) { + case 'Europe': + case 'Africa': + case 'Atlantic': + case 'Arctic': + return REGION_SUBDOMAIN_SUFFIX['EU']; + case 'Asia': + case 'Australia': + case 'Antarctica': + case 'Pacific': + case 'Indian': + return REGION_SUBDOMAIN_SUFFIX['APAC']; + case 'America': + return REGION_SUBDOMAIN_SUFFIX['US']; + default: + return REGION_SUBDOMAIN_SUFFIX['EU']; + } + } catch (err) { + return REGION_SUBDOMAIN_SUFFIX['EU']; + } +} diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md index b3494a217eb..30db251b7a8 100644 --- a/modules/eskimiBidAdapter.md +++ b/modules/eskimiBidAdapter.md @@ -48,6 +48,35 @@ Banner and video formats are supported. Where: -* placementId - Placement ID of the ad unit (required) -* bcat, badv, bapp, battr - ORTB blocking parameters as specified by OpenRTB 2.5 +* `placementId` - Placement ID of the ad unit (required) +* `bcat`, `badv`, `bapp`, `battr` - ORTB blocking parameters as specified by OpenRTB 2.5 +# ## Configuration + +Eskimi recommends the UserSync configuration below. Without it, the Eskimi adapter will not able to perform user syncs, which lowers match rate and reduces monetization. + +```javascript +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: ['eskimi'], + filter: 'include' + } + }, + syncDelay: 6000 + }}); +``` + +### Bidder Settings + +The Eskimi bid adapter uses browser local storage. Since Prebid.js 7.x, the access to it must be explicitly set. + +```js +// https://docs.prebid.org/dev-docs/publisher-api-reference/bidderSettings.html +pbjs.bidderSettings = { + eskimi: { + storageAllowed: true + } +} +``` diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js index c00a22a5aac..a452f115767 100644 --- a/test/spec/modules/eskimiBidAdapter_spec.js +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -1,5 +1,5 @@ -import { expect } from 'chai'; -import { spec } from 'modules/eskimiBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/eskimiBidAdapter.js'; import * as utils from 'src/utils'; const BANNER_BID = { @@ -165,8 +165,8 @@ describe('Eskimi bid adapter', function () { it('should properly forward ORTB blocking params', function () { let bid = utils.deepClone(BANNER_BID); bid = utils.mergeDeep(bid, { - params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'] }, - mediaTypes: { banner: { battr: [1] } } + params: {bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example']}, + mediaTypes: {banner: {battr: [1]}} }); let [request] = spec.buildRequests([bid], BIDDER_REQUEST); @@ -253,7 +253,7 @@ describe('Eskimi bid adapter', function () { const [request] = spec.buildRequests([bid], BIDDER_REQUEST); const response = utils.deepClone(BANNER_BID_RESPONSE); - const bids = spec.interpretResponse({ body: response }, request); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.be.an('array').that.is.not.empty; expect(bids[0].mediaType).to.equal('banner'); @@ -274,7 +274,7 @@ describe('Eskimi bid adapter', function () { const bid = utils.deepClone(BANNER_BID); let request = spec.buildRequests([bid], BIDDER_REQUEST)[0]; - const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, { 'body': {} }); + const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, {'body': {}}); const bids = spec.interpretResponse(EMPTY_RESP, request); expect(bids).to.be.empty; }); @@ -285,7 +285,7 @@ describe('Eskimi bid adapter', function () { const bid = utils.deepClone(VIDEO_BID); const [request] = spec.buildRequests([bid], BIDDER_REQUEST); - const bids = spec.interpretResponse({ body: VIDEO_BID_RESPONSE }, request); + const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request); expect(bids).to.be.an('array').that.is.not.empty; expect(bids[0].mediaType).to.equal('video'); From 213caa72cb0d1f44f4c6b2492af0e26bd72d5f96 Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:07:21 +0300 Subject: [PATCH 0481/1097] Rubicon Bid Adapter tests: migrate querystring to URLSearchParams (#12194) * Migrate queryString to URLSearchParams for rubiconBidAdapter_spec * Update package.json --------- Co-authored-by: Patrick McCann --- package.json | 1 - test/spec/modules/rubiconBidAdapter_spec.js | 394 ++++++++++---------- 2 files changed, 197 insertions(+), 198 deletions(-) diff --git a/package.json b/package.json index 2734420071b..66265afdafa 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,6 @@ "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", - "querystring": "^0.2.1", "resolve-from": "^5.0.0", "sinon": "^4.5.0", "through2": "^4.0.2", diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 0856dc2571b..2ce16ab8101 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -8,7 +8,6 @@ import { resetRubiConf, converter } from 'modules/rubiconBidAdapter.js'; -import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {find} from 'src/polyfill.js'; @@ -516,7 +515,7 @@ describe('the rubicon adapter', function () { duplicate.bids[0].params.floor = 0.01; let [request] = spec.buildRequests(duplicate.bids, duplicate); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -548,9 +547,9 @@ describe('the rubicon adapter', function () { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(data[key]).to.match(value); + expect(data.get(key)).to.match(value); } else { - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); } }); }); @@ -570,38 +569,38 @@ describe('the rubicon adapter', function () { }) ).to.be.true; - let data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + let data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // not an object should work and not send getFloorResponse = undefined; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // make it respond with a non USD floor should not send it getFloorResponse = {currency: 'EUR', floor: 1.0}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // make it respond with a non USD floor should not send it getFloorResponse = {currency: 'EUR'}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // make it respond with USD floor and string floor getFloorResponse = {currency: 'USD', floor: '1.23'}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.equal('1.23'); + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.equal('1.23'); // make it respond with USD floor and num floor getFloorResponse = {currency: 'USD', floor: 1.23}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.equal('1.23'); + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.equal('1.23'); }); it('should send rp_maxbids to AE if rubicon multibid config exists', function () { @@ -609,9 +608,9 @@ describe('the rubicon adapter', function () { multibidRequest.bidLimit = 5; let [request] = spec.buildRequests(multibidRequest.bids, multibidRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['rp_maxbids']).to.equal('5'); + expect(data.get('rp_maxbids')).to.equal('5'); }); it('should not send p_pos to AE if not params.position specified', function () { @@ -619,10 +618,10 @@ describe('the rubicon adapter', function () { delete noposRequest.bids[0].params.position; let [request] = spec.buildRequests(noposRequest.bids, noposRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal(null); }); it('should not send p_pos to AE if not mediaTypes.banner.pos is invalid', function () { @@ -635,10 +634,10 @@ describe('the rubicon adapter', function () { delete bidRequest.bids[0].params.position; let [request] = spec.buildRequests(bidRequest.bids, bidRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal(null); }); it('should send p_pos to AE if mediaTypes.banner.pos is valid', function () { @@ -651,10 +650,10 @@ describe('the rubicon adapter', function () { delete bidRequest.bids[0].params.position; let [request] = spec.buildRequests(bidRequest.bids, bidRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal('atf'); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal('atf'); }); it('should not send p_pos to AE if not params.position is invalid', function () { @@ -662,10 +661,10 @@ describe('the rubicon adapter', function () { badposRequest.bids[0].params.position = 'bad'; let [request] = spec.buildRequests(badposRequest.bids, badposRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal(null); }); it('should correctly send p_pos in sra fashion', function() { @@ -694,9 +693,9 @@ describe('the rubicon adapter', function () { sraPosRequest.bids.push(bidCopy3); let [request] = spec.buildRequests(sraPosRequest.bids, sraPosRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['p_pos']).to.equal('atf;;btf;;'); + expect(data.get('p_pos')).to.equal('atf;;btf;;'); }); it('should correctly send cdep signal when requested', () => { @@ -704,9 +703,9 @@ describe('the rubicon adapter', function () { badposRequest.bids[0].ortb2 = {device: {ext: {cdep: 3}}}; let [request] = spec.buildRequests(badposRequest.bids, badposRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['o_cdep']).to.equal('3'); + expect(data.get('o_cdep')).to.equal('3'); }); it('ad engine query params should be ordered correctly', function () { @@ -742,15 +741,15 @@ describe('the rubicon adapter', function () { 'tg_i.rating': '5-star', 'tg_i.prodtype': 'tech,mobile', 'rf': 'localhost', - 'p_geo.latitude': undefined, - 'p_geo.longitude': undefined + 'p_geo.latitude': null, + 'p_geo.longitude': null }; sandbox.stub(Math, 'random').callsFake(() => 0.1); delete bidderRequest.bids[0].params.latLong; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -758,15 +757,15 @@ describe('the rubicon adapter', function () { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(data[key]).to.match(value); + expect(data.get(key)).to.match(value); } else { - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); } }); bidderRequest.bids[0].params.latLong = []; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); + data = new URLSearchParams(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -774,9 +773,9 @@ describe('the rubicon adapter', function () { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(data[key]).to.match(value); + expect(data.get(key)).to.match(value); } else { - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); } }); }); @@ -796,24 +795,24 @@ describe('the rubicon adapter', function () { delete bidderRequest.bids[0].params.referrer; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.exist; - expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); + expect(new URLSearchParams(request.data).get('rf')).to.exist; + expect(new URLSearchParams(request.data).get('rf')).to.equal('https://www.prebid.org'); }); it('page_url should use params.referrer, bidderRequest.refererInfo in that order', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.equal('localhost'); + expect(new URLSearchParams(request.data).get('rf')).to.equal('localhost'); delete bidderRequest.bids[0].params.referrer; let refererInfo = {page: 'https://www.prebid.org'}; bidderRequest = Object.assign({refererInfo}, bidderRequest); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); + expect(new URLSearchParams(request.data).get('rf')).to.equal('https://www.prebid.org'); bidderRequest.refererInfo.page = 'http://www.prebid.org'; bidderRequest.bids[0].params.secure = true; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); + expect(new URLSearchParams(request.data).get('rf')).to.equal('https://www.prebid.org'); }); it('should use rubicon sizes if present (including non-mappable sizes)', function () { @@ -821,10 +820,10 @@ describe('the rubicon adapter', function () { sizesBidderRequest.bids[0].params.sizes = [55, 57, 59, 801]; let [request] = spec.buildRequests(sizesBidderRequest.bids, sizesBidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['size_id']).to.equal('55'); - expect(data['alt_size_ids']).to.equal('57,59,801'); + expect(data.get('size_id')).to.equal('55'); + expect(data.get('alt_size_ids')).to.equal('57,59,801'); }); it('should not validate bid request if no valid sizes', function () { @@ -850,48 +849,48 @@ describe('the rubicon adapter', function () { floorBidderRequest.bids[0].params.floor = 2; let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['rp_floor']).to.equal('2'); + expect(data.get('rp_floor')).to.equal('2'); }); describe('GDPR consent config', function () { it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', function () { const bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gdpr']).to.equal('1'); - expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(data.get('gdpr')).to.equal('1'); + expect(data.get('gdpr_consent')).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); }); it('should send only "gdpr_consent", when gdprConsent defines only consentString', function () { const bidderRequest = createGdprBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - expect(data['gdpr']).to.equal(undefined); + expect(data.get('gdpr_consent')).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(data.get('gdpr')).to.equal(null); }); it('should not send GDPR params if gdprConsent is not defined', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gdpr']).to.equal(undefined); - expect(data['gdpr_consent']).to.equal(undefined); + expect(data.get('gdpr')).to.equal(null); + expect(data.get('gdpr_consent')).to.equal(null); }); it('should set "gdpr" value as 1 or 0, using "gdprApplies" value of either true/false', function () { let bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - expect(data['gdpr']).to.equal('1'); + let data = new URLSearchParams(request.data); + expect(data.get('gdpr')).to.equal('1'); bidderRequest = createGdprBidderRequest(false); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data['gdpr']).to.equal('0'); + data = new URLSearchParams(request.data); + expect(data.get('gdpr')).to.equal('0'); }); }); @@ -899,16 +898,16 @@ describe('the rubicon adapter', function () { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { addUspToBidderRequest(bidderRequest); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['us_privacy']).to.equal('1NYN'); + expect(data.get('us_privacy')).to.equal('1NYN'); }); it('should not send us_privacy if bidderRequest has no uspConsent value', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['us_privacy']).to.equal(undefined); + expect(data.get('us_privacy')).to.equal(null); }); }); @@ -919,19 +918,19 @@ describe('the rubicon adapter', function () { applicableSections: 2 }; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); delete bidderRequest.gppConsent; - expect(data['gpp']).to.equal('consent'); - expect(data['gpp_sid']).to.equal('2'); + expect(data.get('gpp')).to.equal('consent'); + expect(data.get('gpp_sid')).to.equal('2'); }); it('should not send gpp information if bidderRequest does not have a value for gppConsent', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gpp']).to.equal(undefined); - expect(data['gpp_sid']).to.equal(undefined); + expect(data.get('gpp')).to.equal(null); + expect(data.get('gpp_sid')).to.equal(null); }); }); @@ -954,13 +953,14 @@ describe('the rubicon adapter', function () { // get the built request let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // make sure that no tg_v or tg_i keys are present in the request - let matchingExp = RegExp('^tg_(i|v)\..*$') - Object.keys(data).forEach(key => { + let matchingExp = RegExp('^tg_(i|v)\..*$'); + // Display the keys + for (const key of data.keys()) { expect(key).to.not.match(matchingExp); - }); + } }); it('should contain valid params when some are undefined', function () { @@ -986,17 +986,17 @@ describe('the rubicon adapter', function () { // get the built request let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // make sure none of the undefined keys are in query undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); + expect(data.get(key)).to.equal(null); }); // make sure the expected and defined ones do show up still Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); }); }); @@ -1080,12 +1080,12 @@ describe('the rubicon adapter', function () { // get the built request let [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // make sure that tg_v, tg_i, and kw values are correct Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; - expect(data[key]).to.deep.equal(value); + expect(data.get(key)).to.deep.equal(value); }); }); }); @@ -1164,13 +1164,13 @@ describe('the rubicon adapter', function () { expect(bidRequestItem.params.siteId).to.equal(array[0].params.siteId); }); - const data = parseQuery(item.data); + const data = new URLSearchParams(item.data); Object.keys(expectedQuery).forEach(key => { - expect(data).to.have.property(key); + expect(data.get(key)).to.be.exist; // extract semicolon delineated values - const params = data[key].split(';'); + const params = data.get(key).split(';'); // skip value test for site and zone ids if (key !== 'site_id' && key !== 'zone_id') { @@ -1219,10 +1219,10 @@ describe('the rubicon adapter', function () { // check that slots param value matches expect(serverRequests[0].data.indexOf('&slots=10&') !== -1).to.equal(true); // check that zone_id has 10 values (since all zone_ids are unique all should exist in get param) - data = parseQuery(serverRequests[0].data); - expect(data).to.be.a('object'); - expect(data).to.have.property('zone_id'); - expect(data.zone_id.split(';')).to.have.lengthOf(10); + data = new URLSearchParams(serverRequests[0].data); + expect(typeof data).to.equal('object'); + expect(data.get('zone_id')).to.be.exist; + expect(data.get('zone_id').split(';')).to.have.lengthOf(10); // TEST '100' BIDS, add 90 to the previously added 10 for (let i = 0; i < 90; i++) { @@ -1260,10 +1260,10 @@ describe('the rubicon adapter', function () { expect(serverRequests).that.is.an('array').of.length(1); // get the built query - let data = parseQuery(serverRequests[0].data); + let data = new URLSearchParams(serverRequests[0].data); // num slots should be 4 - expect(data.slots).to.equal('4'); + expect(data.get('slots')).to.equal('4'); }); it('should not group bid requests if singleRequest does not equal true', function () { @@ -1350,10 +1350,10 @@ describe('the rubicon adapter', function () { } ]; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['tpid_tdid']).to.equal('abcd-efgh-ijkl-mnop-1234'); - expect(data['eid_adserver.org']).to.equal('abcd-efgh-ijkl-mnop-1234'); + expect(data.get('tpid_tdid')).to.equal('abcd-efgh-ijkl-mnop-1234'); + expect(data.get('eid_adserver.org')).to.equal('abcd-efgh-ijkl-mnop-1234'); }); describe('LiveIntent support', function () { @@ -1383,11 +1383,11 @@ describe('the rubicon adapter', function () { } ]; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['tpid_liveintent.com']).to.equal('0000-1111-2222-3333'); - expect(data['eid_liveintent.com']).to.equal('0000-1111-2222-3333'); - expect(data['tg_v.LIseg']).to.equal('segA,segB'); + expect(data.get('tpid_liveintent.com')).to.equal('0000-1111-2222-3333'); + expect(data.get('eid_liveintent.com')).to.equal('0000-1111-2222-3333'); + expect(data.get('tg_v.LIseg')).to.equal('segA,segB'); }); it('should send tg_v.LIseg when userIdAsEids contains liveintentId with ext.segments as array', function () { @@ -1441,9 +1441,9 @@ describe('the rubicon adapter', function () { } ] let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['x_liverampidl']).to.equal('1111-2222-3333-4444'); + expect(data.get('x_liverampidl')).to.equal('1111-2222-3333-4444'); }); }); @@ -1465,9 +1465,9 @@ describe('the rubicon adapter', function () { } ] let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_pubcid.org']).to.equal('1111^1'); + expect(data.get('eid_pubcid.org')).to.equal('1111^1'); }); }); @@ -1489,9 +1489,9 @@ describe('the rubicon adapter', function () { } ] let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_criteo.com']).to.equal('1111^1'); + expect(data.get('eid_criteo.com')).to.equal('1111^1'); }); }); @@ -1536,9 +1536,9 @@ describe('the rubicon adapter', function () { } ]; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['ppuid']).to.equal('11111'); + expect(data.get('ppuid')).to.equal('11111'); }); }); @@ -1568,9 +1568,9 @@ describe('the rubicon adapter', function () { } ]; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_id5-sync.com']).to.equal('11111^1^22222'); + expect(data.get('eid_id5-sync.com')).to.equal('11111^1^22222'); }); }); @@ -1586,9 +1586,9 @@ describe('the rubicon adapter', function () { }] }] let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_catchall']).to.equal('11111^2'); + expect(data.get('eid_catchall')).to.equal('11111^2'); }); it('should send rubiconproject special case', function () { @@ -1602,9 +1602,9 @@ describe('the rubicon adapter', function () { }] }] let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_rubiconproject.com']).to.equal('some-cool-id'); + expect(data.get('eid_rubiconproject.com')).to.equal('some-cool-id'); }); }); @@ -1616,9 +1616,9 @@ describe('the rubicon adapter', function () { pubcid: '1111' }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['ppuid']).to.equal('123'); + expect(data.get('ppuid')).to.equal('123'); }); }); }); @@ -1633,20 +1633,20 @@ describe('the rubicon adapter', function () { it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.pbadslot’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot’')).to.be.null; }); it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.pbadslot’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot’')).to.be.null; }); it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { @@ -1659,10 +1659,10 @@ describe('the rubicon adapter', function () { }; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.pbadslot'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot')).to.be.null; }); it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { @@ -1675,11 +1675,11 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.pbadslot'); - expect(data['tg_i.pbadslot']).to.equal('abc'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot')).to.be.exist; + expect(data.get('tg_i.pbadslot')).to.equal('abc'); }); it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { @@ -1692,11 +1692,11 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.pbadslot'); - expect(data['tg_i.pbadslot']).to.equal('/a/b/c'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot')).to.be.exist; + expect(data.get('tg_i.pbadslot')).to.equal('/a/b/c'); }); it('should send gpid as p_gpid if valid', function () { @@ -1707,11 +1707,11 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('p_gpid'); - expect(data['p_gpid']).to.equal('/1233/sports&div1'); + expect(typeof data).to.equal('object'); + expect(data.get('p_gpid')).to.be.exist; + expect(data.get('p_gpid')).to.equal('/1233/sports&div1'); }); describe('Pass DSA signals', function() { @@ -1751,9 +1751,9 @@ describe('the rubicon adapter', function () { const expectedTransparency = 'testdomain.com~1'; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({ ...b, ortb2: ortb2Clone })), bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data['dsatransparency']).to.equal(expectedTransparency); + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) it('should send dsaparams if \"ortb2.regs.ext.dsa.transparancy[0].params\"', function() { const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); @@ -1765,9 +1765,9 @@ describe('the rubicon adapter', function () { const expectedTransparency = 'testdomain.com~1'; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data['dsatransparency']).to.equal(expectedTransparency); + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) it('should pass an empty transparency param if \"ortb2.regs.ext.dsa.transparency[0].params\" is empty', function() { const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); @@ -1778,8 +1778,8 @@ describe('the rubicon adapter', function () { }]; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); - const data = parseQuery(request.data); - expect(data['dsatransparency']).to.be.undefined + const data = new URLSearchParams(request.data); + expect(data.get('dsatransparency')).to.be.null }) it('should send an empty transparency if \"ortb2.regs.ext.dsa.transparency[0].domain\" is empty', function() { const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); @@ -1790,36 +1790,36 @@ describe('the rubicon adapter', function () { }]; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data['dsatransparency']).to.be.undefined + expect(data.get('dsatransparency')).to.be.null }) it('should send dsa signals if \"ortb2.regs.ext.dsa\"', function() { const expectedTransparency = 'testdomain.com~1~~testdomain2.com~1_2' const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest) - const data = parseQuery(request.data); - - expect(data).to.be.an('Object'); - expect(data).to.have.property('dsarequired'); - expect(data).to.have.property('dsapubrender'); - expect(data).to.have.property('dsadatatopubs'); - expect(data).to.have.property('dsatransparency'); - - expect(data['dsarequired']).to.equal(ortb2.regs.ext.dsa.dsarequired.toString()); - expect(data['dsapubrender']).to.equal(ortb2.regs.ext.dsa.pubrender.toString()); - expect(data['dsadatatopubs']).to.equal(ortb2.regs.ext.dsa.datatopub.toString()); - expect(data['dsatransparency']).to.equal(expectedTransparency) + const data = new URLSearchParams(request.data); + + expect(typeof data).to.equal('object'); + expect(data.get('dsarequired')).to.be.exist; + expect(data.get('dsapubrender')).to.be.exist; + expect(data.get('dsadatatopubs')).to.be.exist; + expect(data.get('dsatransparency')).to.be.exist; + + expect(data.get('dsarequired')).to.equal(ortb2.regs.ext.dsa.dsarequired.toString()); + expect(data.get('dsapubrender')).to.equal(ortb2.regs.ext.dsa.pubrender.toString()); + expect(data.get('dsadatatopubs')).to.equal(ortb2.regs.ext.dsa.datatopub.toString()); + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) it('should return one transparency param', function() { const expectedTransparency = 'testdomain.com~1'; const ortb2Clone = deepClone(ortb2); ortb2Clone.regs.ext.dsa.transparency.pop() const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest) - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('dsatransparency'); - expect(data['dsatransparency']).to.equal(expectedTransparency); + expect(typeof data).to.equal('object'); + expect(data.get('dsatransparency')).to.be.exist; + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) }) @@ -1838,12 +1838,12 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data['p_gpid']).to.equal('/1233/sports&div1'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.pbadslot']).to.equal('pb_slot'); + expect(typeof data).to.equal('object'); + expect(data.get('p_gpid')).to.equal('/1233/sports&div1'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.be.null; + expect(data.get('tg_i.pbadslot')).to.equal('pb_slot'); }); }); @@ -1857,20 +1857,20 @@ describe('the rubicon adapter', function () { it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code’')).to.be.null; }); it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code’')).to.be.null; }); it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { @@ -1885,10 +1885,10 @@ describe('the rubicon adapter', function () { }; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.be.null; }); it('should send NOT \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string but not gam', function () { @@ -1904,10 +1904,10 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.but.null; }); it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string and name is gam', function () { @@ -1923,11 +1923,11 @@ describe('the rubicon adapter', function () { }; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('/a/b/c'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.be.exist; + expect(data.get('tg_i.dfp_ad_unit_code')).to.equal('/a/b/c'); }); }); @@ -1996,11 +1996,11 @@ describe('the rubicon adapter', function () { // Build Fastlane call let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // Loop through expected values and if they do not match push an error const errors = Object.entries(expectedValues).reduce((accum, [key, val]) => { - if (data[key] !== val) accum.push(`${key} - expect: ${val} - got: ${data[key]}`) + if (data.get(key) !== val) accum.push(`${key} - expect: ${val} - got: ${data[key]}`) return accum; }, []); @@ -2030,24 +2030,24 @@ describe('the rubicon adapter', function () { // Build Fastlane request let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // should show new names - expect(data.m_ch_model).to.equal('Suface Duo'); - expect(data.m_ch_mobile).to.equal('?1'); + expect(data.get('m_ch_model')).to.equal('Suface Duo'); + expect(data.get('m_ch_mobile')).to.equal('?1'); // should still send platform - expect(data.m_ch_platform).to.equal('macOS'); + expect(data.get('m_ch_platform')).to.equal('macOS'); // platform version not sent - expect(data).to.not.haveOwnProperty('m_ch_platform_ver'); + expect(data.get('m_ch_platform_ver')).to.be.null; // both ua and full_ver not sent because browsers not array - expect(data).to.not.haveOwnProperty('m_ch_ua'); - expect(data).to.not.haveOwnProperty('m_ch_full_ver'); + expect(data.get('m_ch_ua')).to.be.null; + expect(data.get('m_ch_full_ver')).to.be.null; // arch not sent - expect(data).to.not.haveOwnProperty('m_ch_arch'); + expect(data.get('m_ch_arch')).to.be.null; }); }); }); @@ -4226,7 +4226,7 @@ describe('the rubicon adapter', function () { it('should use the integration type provided in the config instead of the default', () => { config.setConfig({rubicon: {int_type: 'testType'}}); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).tk_flint).to.equal('testType_v$prebid.version$'); + expect(new URLSearchParams(request.data).get('tk_flint')).to.equal('testType_v$prebid.version$'); }); }); }); @@ -4449,7 +4449,7 @@ describe('the rubicon adapter', function () { it('should properly serialize schain object with correct delimiters', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); const numNodes = schainConfig.nodes.length; - const schain = parseQuery(results[0].data).rp_schain; + const schain = new URLSearchParams(results[0].data).get('rp_schain'); // each node serialization should start with an ! expect(schain.match(/!/g).length).to.equal(numNodes); @@ -4460,21 +4460,21 @@ describe('the rubicon adapter', function () { it('should send the proper version for the schain', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); - const schain = parseQuery(results[0].data).rp_schain.split('!'); + const schain = new URLSearchParams(results[0].data).get('rp_schain').split('!'); const version = schain.shift().split(',')[0]; expect(version).to.equal(bidRequests.bids[0].schain.ver); }); it('should send the correct value for complete in schain', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); - const schain = parseQuery(results[0].data).rp_schain.split('!'); + const schain = new URLSearchParams(results[0].data).get('rp_schain').split('!'); const complete = schain.shift().split(',')[1]; expect(complete).to.equal(String(bidRequests.bids[0].schain.complete)); }); it('should send available params in the right order', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); - const schain = parseQuery(results[0].data).rp_schain.split('!'); + const schain = new URLSearchParams(results[0].data).get('rp_schain').split('!'); schain.shift(); schain.forEach((serializeNode, nodeIndex) => { From 9c16cfb2cc1b50e01fa1c24f4466c75d94347a85 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Wed, 4 Sep 2024 15:33:27 +0200 Subject: [PATCH 0482/1097] Core: Truncating IPs using geo activity (#12107) * 11395 Truncating IPs using geo activity * refactor * returning null for invalid ip --------- Co-authored-by: Marcin Komorski --- src/activities/redactor.js | 19 +++++++++ src/utils/ipUtils.js | 52 ++++++++++++++++++++++++ test/spec/activities/redactor_spec.js | 18 +++++++++ test/spec/unit/utils/ipUtils_spec.js | 58 +++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/utils/ipUtils.js create mode 100644 test/spec/unit/utils/ipUtils_spec.js diff --git a/src/activities/redactor.js b/src/activities/redactor.js index 694a96b2b14..65d14722ce5 100644 --- a/src/activities/redactor.js +++ b/src/activities/redactor.js @@ -7,6 +7,7 @@ import { ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD } from './activities.js'; +import { scrubIPv4, scrubIPv6 } from '../utils/ipUtils.js'; export const ORTB_UFPD_PATHS = [ 'data', @@ -21,6 +22,8 @@ export const ORTB_UFPD_PATHS = [ ].map(f => `user.${f}`).concat('device.ext.cdep'); export const ORTB_EIDS_PATHS = ['user.eids', 'user.ext.eids']; export const ORTB_GEO_PATHS = ['user.geo.lat', 'user.geo.lon', 'device.geo.lat', 'device.geo.lon']; +export const ORTB_IPV4_PATHS = ['device.ip'] +export const ORTB_IPV6_PATHS = ['device.ipv6'] /** * @typedef TransformationRuleDef @@ -157,6 +160,22 @@ export function ortb2TransmitRules(isAllowed = isActivityAllowed) { return Math.round((val + Number.EPSILON) * 100) / 100; } }, + { + name: ACTIVITY_TRANSMIT_PRECISE_GEO, + paths: ORTB_IPV4_PATHS, + applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_PRECISE_GEO, isAllowed), + get(val) { + return scrubIPv4(val); + } + }, + { + name: ACTIVITY_TRANSMIT_PRECISE_GEO, + paths: ORTB_IPV6_PATHS, + applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_PRECISE_GEO, isAllowed), + get(val) { + return scrubIPv6(val); + } + }, { name: ACTIVITY_TRANSMIT_TID, paths: ['source.tid'], diff --git a/src/utils/ipUtils.js b/src/utils/ipUtils.js new file mode 100644 index 00000000000..600c0e75087 --- /dev/null +++ b/src/utils/ipUtils.js @@ -0,0 +1,52 @@ +export function scrubIPv4(ip) { + if (!ip) { + return null; + } + + const ones = 24; + + let ipParts = ip.split('.').map(Number) + + if (ipParts.length != 4) { + return null; + } + + let mask = []; + for (let i = 0; i < 4; i++) { + let n = Math.max(0, Math.min(8, ones - (i * 8))); + mask.push((0xff << (8 - n)) & 0xff); + } + + let maskedIP = ipParts.map((part, i) => part & mask[i]); + + return maskedIP.join('.'); +} + +export function scrubIPv6(ip) { + if (!ip) { + return null; + } + + const ones = 64; + + let ipParts = ip.split(':').map(part => parseInt(part, 16)); + + ipParts = ipParts.map(part => isNaN(part) ? 0 : part); + while (ipParts.length < 8) { + ipParts.push(0); + } + + if (ipParts.length != 8) { + return null; + } + + let mask = []; + for (let i = 0; i < 8; i++) { + let n = Math.max(0, Math.min(16, ones - (i * 16))); + mask.push((0xffff << (16 - n)) & 0xffff); + } + + let maskedIP = ipParts.map((part, i) => part & mask[i]); + + return maskedIP.map(part => part.toString(16)).join(':'); +} diff --git a/test/spec/activities/redactor_spec.js b/test/spec/activities/redactor_spec.js index f54b2dcfb95..84d98a958a0 100644 --- a/test/spec/activities/redactor_spec.js +++ b/test/spec/activities/redactor_spec.js @@ -1,6 +1,8 @@ import { objectTransformer, ORTB_EIDS_PATHS, ORTB_GEO_PATHS, + ORTB_IPV4_PATHS, + ORTB_IPV6_PATHS, ORTB_UFPD_PATHS, redactorFactory, redactRule } from '../../../src/activities/redactor.js'; @@ -299,6 +301,22 @@ describe('redactor', () => { expect(deepAccess(ortb2, path)).to.eql(allowed ? 1.2345 : 1.23); }) }) + ORTB_IPV4_PATHS.forEach(path => { + it(`should ${allowed ? 'NOT ' : ''} round down ${path}`, () => { + const ortb2 = {}; + deepSetValue(ortb2, path, '192.168.1.1'); + redactor.ortb2(ortb2); + expect(deepAccess(ortb2, path)).to.eql(allowed ? '192.168.1.1' : '192.168.1.0'); + }) + }) + ORTB_IPV6_PATHS.forEach(path => { + it(`should ${allowed ? 'NOT ' : ''} round down ${path}`, () => { + const ortb2 = {}; + deepSetValue(ortb2, path, '2001:0000:130F:0000:0000:09C0:876A:130B'); + redactor.ortb2(ortb2); + expect(deepAccess(ortb2, path)).to.eql(allowed ? '2001:0000:130F:0000:0000:09C0:876A:130B' : '2001:0:130f:0:0:0:0:0'); + }) + }) }); }); }) diff --git a/test/spec/unit/utils/ipUtils_spec.js b/test/spec/unit/utils/ipUtils_spec.js new file mode 100644 index 00000000000..8cd82a8c4fe --- /dev/null +++ b/test/spec/unit/utils/ipUtils_spec.js @@ -0,0 +1,58 @@ +import { scrubIPv4, scrubIPv6 } from '../../../../src/utils/ipUtils' + +describe('ipUtils', () => { + describe('ipv4', () => { + it('should mask ip v4', () => { + let input = '192.168.1.1'; + let output = scrubIPv4(input); + expect(output).to.deep.equal('192.168.1.0'); + input = '192.168.255.255'; + output = scrubIPv4(input); + expect(output).to.deep.equal('192.168.255.0'); + }); + + it('should return null for null input', () => { + let input = null; + let output = scrubIPv4(input); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = '192.130.2'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = '2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + }); + + describe('ipv6', () => { + it('should mask ip v6', () => { + let input = '2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF'; + let output = scrubIPv6(input); + expect(output).to.deep.equal('2001:db8:3333:4444:0:0:0:0'); + }); + + it('should return null for null input', () => { + let input = null; + let output = scrubIPv6(input); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = '2001:db8:3333:4444:CCCC:DDDD:EEEE'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = 'invalid'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + }); +}) From 675cf35ed790a51d683d248caa642b31579e6c7f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 5 Sep 2024 05:46:10 -0700 Subject: [PATCH 0483/1097] Core: fix bug where custom priceGranularity does not work with setBidderConfig (#12103) * Core: fix bug where custom priceGranularity does not work with setBidderConfig * allow setting customPriceBucket directly --- src/config.js | 276 ++++++++++++++++++++------------------- test/spec/config_spec.js | 46 +++++-- 2 files changed, 178 insertions(+), 144 deletions(-) diff --git a/src/config.js b/src/config.js index e9c37a8d329..53f53f99529 100644 --- a/src/config.js +++ b/src/config.js @@ -60,89 +60,159 @@ const GRANULARITY_OPTIONS = { const ALL_TOPICS = '*'; -export function newConfig() { - let listeners = []; - let defaults; - let config; - let bidderConfig; - let currBidder = null; - - function resetConfig() { - defaults = {}; - - function getProp(name) { - return props[name].val; - } +function attachProperties(config, useDefaultValues = true) { + const values = useDefaultValues ? { + priceGranularity: GRANULARITY_OPTIONS.MEDIUM, + customPriceBucket: {}, + mediaTypePriceGranularity: {}, + bidderSequence: DEFAULT_BIDDER_SEQUENCE, + auctionOptions: {} + } : {} + + function getProp(name) { + return values[name]; + } - function setProp(name, val) { - props[name].val = val; + function setProp(name, val) { + if (!values.hasOwnProperty(name)) { + Object.defineProperty(config, name, {enumerable: true}); } + values[name] = val; + } - const props = { - publisherDomain: { - set(val) { - if (val != null) { - logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') + const props = { + publisherDomain: { + set(val) { + if (val != null) { + logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') + } + setProp('publisherDomain', val); + } + }, + priceGranularity: { + set(val) { + if (validatePriceGranularity(val)) { + if (typeof val === 'string') { + setProp('priceGranularity', (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM); + } else if (isPlainObject(val)) { + setProp('customPriceBucket', val); + setProp('priceGranularity', GRANULARITY_OPTIONS.CUSTOM) + logMessage('Using custom price granularity'); } - setProp('publisherDomain', val); } - }, - priceGranularity: { - val: GRANULARITY_OPTIONS.MEDIUM, - set(val) { - if (validatePriceGranularity(val)) { + } + }, + customPriceBucket: {}, + mediaTypePriceGranularity: { + set(val) { + val != null && setProp('mediaTypePriceGranularity', Object.keys(val).reduce((aggregate, item) => { + if (validatePriceGranularity(val[item])) { if (typeof val === 'string') { - setProp('priceGranularity', (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM); + aggregate[item] = (hasGranularity(val[item])) ? val[item] : getProp('priceGranularity'); } else if (isPlainObject(val)) { - setProp('customPriceBucket', val); - setProp('priceGranularity', GRANULARITY_OPTIONS.CUSTOM) - logMessage('Using custom price granularity'); + aggregate[item] = val[item]; + logMessage(`Using custom price granularity for ${item}`); } + } else { + logWarn(`Invalid price granularity for media type: ${item}`); } + return aggregate; + }, {})); + } + }, + bidderSequence: { + set(val) { + if (VALID_ORDERS[val]) { + setProp('bidderSequence', val); + } else { + logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); } - }, - customPriceBucket: { - val: {}, - set() {} - }, - mediaTypePriceGranularity: { - val: {}, - set(val) { - val != null && setProp('mediaTypePriceGranularity', Object.keys(val).reduce((aggregate, item) => { - if (validatePriceGranularity(val[item])) { - if (typeof val === 'string') { - aggregate[item] = (hasGranularity(val[item])) ? val[item] : getProp('priceGranularity'); - } else if (isPlainObject(val)) { - aggregate[item] = val[item]; - logMessage(`Using custom price granularity for ${item}`); - } - } else { - logWarn(`Invalid price granularity for media type: ${item}`); - } - return aggregate; - }, {})); + } + }, + auctionOptions: { + set(val) { + if (validateauctionOptions(val)) { + setProp('auctionOptions', val); } - }, - bidderSequence: { - val: DEFAULT_BIDDER_SEQUENCE, - set(val) { - if (VALID_ORDERS[val]) { - setProp('bidderSequence', val); - } else { - logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); - } + } + } + } + + Object.defineProperties(config, Object.fromEntries( + Object.entries(props) + .map(([k, def]) => [k, Object.assign({ + get: getProp.bind(null, k), + set: setProp.bind(null, k), + enumerable: values.hasOwnProperty(k), + configurable: !values.hasOwnProperty(k) + }, def)]) + )); + + return config; + + function hasGranularity(val) { + return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]); + } + + function validatePriceGranularity(val) { + if (!val) { + logError('Prebid Error: no value passed to `setPriceGranularity()`'); + return false; + } + if (typeof val === 'string') { + if (!hasGranularity(val)) { + logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); + } + } else if (isPlainObject(val)) { + if (!isValidPriceConfig(val)) { + logError('Invalid custom price value passed to `setPriceGranularity()`'); + return false; + } + } + return true; + } + + function validateauctionOptions(val) { + if (!isPlainObject(val)) { + logWarn('Auction Options must be an object') + return false + } + + for (let k of Object.keys(val)) { + if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') { + logWarn(`Auction Options given an incorrect param: ${k}`) + return false + } + if (k === 'secondaryBidders') { + if (!isArray(val[k])) { + logWarn(`Auction Options ${k} must be of type Array`); + return false + } else if (!val[k].every(isStr)) { + logWarn(`Auction Options ${k} must be only string`); + return false } - }, - auctionOptions: { - val: {}, - set(val) { - if (validateauctionOptions(val)) { - setProp('auctionOptions', val); - } + } else if (k === 'suppressStaleRender') { + if (!isBoolean(val[k])) { + logWarn(`Auction Options ${k} must be of type boolean`); + return false; } } } - let newConfig = { + return true; + } +} + +export function newConfig() { + let listeners = []; + let defaults; + let config; + let bidderConfig; + let currBidder = null; + + function resetConfig() { + defaults = {}; + + let newConfig = attachProperties({ // `debug` is equivalent to legacy `pbjs.logging` property debug: DEFAULT_DEBUG, bidderTimeout: DEFAULT_BIDDER_TIMEOUT, @@ -165,16 +235,7 @@ export function newConfig() { userSync: { topics: DEFAULT_IFRAMES_CONFIG } - }; - - Object.defineProperties(newConfig, - Object.fromEntries(Object.entries(props) - .map(([k, def]) => [k, Object.assign({ - get: getProp.bind(null, k), - set: setProp.bind(null, k), - enumerable: true, - }, def)])) - ); + }); if (config) { callSubscribers( @@ -190,57 +251,6 @@ export function newConfig() { config = newConfig; bidderConfig = {}; - - function hasGranularity(val) { - return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]); - } - - function validatePriceGranularity(val) { - if (!val) { - logError('Prebid Error: no value passed to `setPriceGranularity()`'); - return false; - } - if (typeof val === 'string') { - if (!hasGranularity(val)) { - logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); - } - } else if (isPlainObject(val)) { - if (!isValidPriceConfig(val)) { - logError('Invalid custom price value passed to `setPriceGranularity()`'); - return false; - } - } - return true; - } - - function validateauctionOptions(val) { - if (!isPlainObject(val)) { - logWarn('Auction Options must be an object') - return false - } - - for (let k of Object.keys(val)) { - if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') { - logWarn(`Auction Options given an incorrect param: ${k}`) - return false - } - if (k === 'secondaryBidders') { - if (!isArray(val[k])) { - logWarn(`Auction Options ${k} must be of type Array`); - return false - } else if (!val[k].every(isStr)) { - logWarn(`Auction Options ${k} must be only string`); - return false - } - } else if (k === 'suppressStaleRender') { - if (!isBoolean(val[k])) { - logWarn(`Auction Options ${k} must be of type boolean`); - return false; - } - } - } - return true; - } } /** @@ -451,14 +461,14 @@ export function newConfig() { check(config); config.bidders.forEach(bidder => { if (!bidderConfig[bidder]) { - bidderConfig[bidder] = {}; + bidderConfig[bidder] = attachProperties({}, false); } Object.keys(config.config).forEach(topic => { let option = config.config[topic]; - - if (isPlainObject(option)) { + const currentConfig = bidderConfig[bidder][topic]; + if (isPlainObject(option) && (currentConfig == null || isPlainObject(currentConfig))) { const func = mergeFlag ? mergeDeep : Object.assign; - bidderConfig[bidder][topic] = func({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][topic] = func({}, currentConfig || {}, option); } else { bidderConfig[bidder][topic] = option; } diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index d7f6b9de6c0..b54f207e6a9 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -252,19 +252,43 @@ describe('config API', function () { expect(configResult.native).to.be.equal('high'); }); - it('sets priceGranularity and customPriceBucket', function () { - const goodConfig = { - 'buckets': [{ - 'max': 3, - 'increment': 0.01, - 'cap': true - }] - }; - setConfig({ priceGranularity: goodConfig }); - expect(getConfig('priceGranularity')).to.be.equal('custom'); - expect(getConfig('customPriceBucket')).to.equal(goodConfig); + Object.entries({ + 'using setConfig': { + setter: () => config.setConfig, + getter: () => config.getConfig + }, + 'using setBidderConfig': { + setter: () => (config) => setBidderConfig({bidders: ['mockBidder'], config}), + getter: () => (option) => config.runWithBidder('mockBidder', () => config.getConfig(option)) + } + }).forEach(([t, {getter, setter}]) => { + describe(t, () => { + let getConfig, setConfig; + beforeEach(() => { + getConfig = getter(); + setConfig = setter(); + }); + it('sets priceGranularity and customPriceBucket', function () { + const goodConfig = { + 'buckets': [{ + 'max': 3, + 'increment': 0.01, + 'cap': true + }] + }; + setConfig({ priceGranularity: goodConfig }); + expect(getConfig('priceGranularity')).to.be.equal('custom'); + expect(getConfig('customPriceBucket')).to.eql(goodConfig); + }); + }); }); + it('does not force defaults for bidder config', () => { + config.setConfig({bidderSequence: 'fixed'}); + config.setBidderConfig({bidders: ['mockBidder'], config: {other: 'config'}}) + expect(config.runWithBidder('mockBidder', () => config.getConfig('bidderSequence'))).to.eql('fixed'); + }) + it('sets deviceAccess', function () { // When the deviceAccess flag config option is not set, cookies may be read and set expect(getConfig('deviceAccess')).to.be.equal(true); From c82ce3180cb9d9fc59351f9b81e92a7b57a0cde5 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 5 Sep 2024 07:51:25 -0700 Subject: [PATCH 0484/1097] Fix if dot is in adUnitCode (#12206) --- src/adUnits.js | 8 +++----- test/spec/unit/adUnits_spec.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/adUnits.js b/src/adUnits.js index b0c728fd945..413fc6a7c28 100644 --- a/src/adUnits.js +++ b/src/adUnits.js @@ -1,5 +1,3 @@ -import { deepAccess } from './utils.js'; - let adUnits = {}; export function reset() { adUnits = {} @@ -54,7 +52,7 @@ export function incrementBidderWinsCounter(adunit, bidderCode) { * @returns {number} current adunit count */ export function getRequestsCounter(adunit) { - return deepAccess(adUnits, `${adunit}.requestsCounter`) || 0; + return adUnits?.[adunit]?.requestsCounter || 0; } /** @@ -64,7 +62,7 @@ export function getRequestsCounter(adunit) { * @returns {number} current adunit bidder requests count */ export function getBidderRequestsCounter(adunit, bidder) { - return deepAccess(adUnits, `${adunit}.bidders.${bidder}.requestsCounter`) || 0; + return adUnits?.[adunit]?.bidders?.[bidder]?.requestsCounter || 0; } /** @@ -74,5 +72,5 @@ export function getBidderRequestsCounter(adunit, bidder) { * @returns {number} current adunit bidder requests count */ export function getBidderWinsCounter(adunit, bidder) { - return deepAccess(adUnits, `${adunit}.bidders.${bidder}.winsCounter`) || 0; + return adUnits?.[adunit]?.bidders?.[bidder]?.winsCounter || 0; } diff --git a/test/spec/unit/adUnits_spec.js b/test/spec/unit/adUnits_spec.js index 1b26f7d5601..01c5bc49f3f 100644 --- a/test/spec/unit/adUnits_spec.js +++ b/test/spec/unit/adUnits_spec.js @@ -22,6 +22,11 @@ describe('Adunit Counter', function () { adunitCounter.incrementRequestsCounter(ADUNIT_ID_2); expect(adunitCounter.getRequestsCounter(ADUNIT_ID_2)).to.be.equal(1); }); + it('increments and checks requests counter if adUnit has a dots in it', function () { + const adCode = 'adunit.1' + adunitCounter.incrementRequestsCounter(adCode); + expect(adunitCounter.getRequestsCounter(adCode)).to.be.equal(1); + }); it('increments and checks requests counter of adunit 1 for bidder 1', function () { adunitCounter.incrementBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1); expect(adunitCounter.getBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1)).to.be.equal(1); @@ -34,6 +39,11 @@ describe('Adunit Counter', function () { adunitCounter.incrementBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1); expect(adunitCounter.getBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1)).to.be.equal(2); }); + it('increments and checks bidder requests counter if adUnit has a dots in it', function () { + const adCode = 'adunit.1' + adunitCounter.incrementBidderRequestsCounter(adCode, BIDDER_ID_2); + expect(adunitCounter.getBidderRequestsCounter(adCode, BIDDER_ID_2)).to.be.equal(1); + }); it('increments and checks wins counter of adunit 1 for bidder 1', function () { adunitCounter.incrementBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_1); expect(adunitCounter.getBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_1)).to.be.equal(1); @@ -46,4 +56,9 @@ describe('Adunit Counter', function () { adunitCounter.incrementBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_2); expect(adunitCounter.getBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_2)).to.be.equal(1); }); + it('increments and checks wins counter if adUnit has a dots in it', function () { + const adCode = 'adunit.1' + adunitCounter.incrementBidderWinsCounter(adCode, BIDDER_ID_2); + expect(adunitCounter.getBidderWinsCounter(adCode, BIDDER_ID_2)).to.be.equal(1); + }); }); From 034bd70efe8c246b82ca9409f2aaa0d90ae820db Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 5 Sep 2024 19:58:42 +0000 Subject: [PATCH 0485/1097] Prebid 9.12.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2dfd1d24563..fba7b3c0c6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.12.0-pre", + "version": "9.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.12.0-pre", + "version": "9.12.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 66265afdafa..2eb93c5b552 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.12.0-pre", + "version": "9.12.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From d9676cefd3e0a97d9979aeced7096ba1f480dfb9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 5 Sep 2024 19:58:42 +0000 Subject: [PATCH 0486/1097] Increment version to 9.13.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fba7b3c0c6d..b3f6c750465 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.12.0", + "version": "9.13.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.12.0", + "version": "9.13.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 2eb93c5b552..3c68cc0fb39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.12.0", + "version": "9.13.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From a6fc47ed11a94492fb9a5cac064cbc33d84614d9 Mon Sep 17 00:00:00 2001 From: Eyvaz <62054743+eyvazahmadzada@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:34:07 +0200 Subject: [PATCH 0487/1097] Intentiq Analytics: Referrer Info Update (#12155) * improve referrer for more accurate reporting * add unit tests --- modules/intentIqAnalyticsAdapter.js | 13 +++++-- .../modules/intentIqAnalyticsAdapter_spec.js | 36 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index fbb74bee9c8..3f05d91550f 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { logInfo, logError } from '../src/utils.js'; +import { logInfo, logError, getWindowSelf, getWindowTop, getWindowLocation } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; @@ -229,7 +229,16 @@ function constructFullUrl(data) { } export function getReferrer() { - return document.referrer; + try { + if (getWindowSelf() === getWindowTop()) { + return getWindowLocation().href; + } else { + return getWindowTop().location.href; + } + } catch (error) { + logError(`Error accessing location: ${error}`); + return ''; + } } iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics; diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 25421ae0e69..6c9a0fb9e79 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -68,6 +68,9 @@ let wonRequest = { describe('IntentIQ tests all', function () { let logErrorStub; + let getWindowSelfStub; + let getWindowTopStub; + let getWindowLocationStub; let detectBrowserStub; beforeEach(function () { @@ -96,6 +99,9 @@ describe('IntentIQ tests all', function () { afterEach(function () { logErrorStub.restore(); + if (getWindowSelfStub) getWindowSelfStub.restore(); + if (getWindowTopStub) getWindowTopStub.restore(); + if (getWindowLocationStub) getWindowLocationStub.restore(); if (detectBrowserStub) detectBrowserStub.restore(); config.getConfig.restore(); events.getEvents.restore(); @@ -176,6 +182,36 @@ describe('IntentIQ tests all', function () { expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; }); + it('should return window.location.href when window.self === window.top', function () { + // Stub helper functions + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(window); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns(window); + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); + + const referrer = getReferrer(); + expect(referrer).to.equal('http://localhost:9876/'); + }); + + it('should return window.top.location.href when window.self !== window.top and access is successful', function () { + // Stub helper functions to simulate iframe + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns({}); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns({ location: { href: 'http://example.com/' } }); + + const referrer = getReferrer(); + expect(referrer).to.equal('http://example.com/'); + }); + + it('should return an empty string and log an error when accessing window.top.location.href throws an error', function () { + // Stub helper functions to simulate error + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns({}); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').throws(new Error('Access denied')); + + const referrer = getReferrer(); + expect(referrer).to.equal(''); + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.contain('Error accessing location: Error: Access denied'); + }); + it('should not send request if the browser is in blacklist (chrome)', function () { const USERID_CONFIG_BROWSER = [...USERID_CONFIG]; USERID_CONFIG_BROWSER[0].params.browserBlackList = 'ChrOmE'; From 1d094e95c6feef394366c5332dfd94134a252b37 Mon Sep 17 00:00:00 2001 From: Komal Kumari <169047654+pm-komal-kumari@users.noreply.github.com> Date: Fri, 6 Sep 2024 06:32:50 +0530 Subject: [PATCH 0488/1097] Remove duplicate event call for actionDebug event (#12193) Co-authored-by: Komal Kumari --- src/prebid.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index 0eaccf5a8a6..04e1bedf2f9 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -194,7 +194,6 @@ function validateAdUnitPos(adUnit, mediaType) { let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`; logWarn(warning); - events.emit(EVENTS.AUCTION_DEBUG, { type: 'WARNING', arguments: warning }); delete adUnit.mediaTypes[mediaType].pos; } From 48916ae2f4399fb353db72867db413c881d51cd6 Mon Sep 17 00:00:00 2001 From: Zach Bowman Date: Fri, 6 Sep 2024 10:25:08 -0400 Subject: [PATCH 0489/1097] Yahoo Ads Bid Adapter: Fix to only set bid response renderer for video. (#12139) --- modules/yahooAdsBidAdapter.js | 2 +- test/spec/modules/yahooAdsBidAdapter_spec.js | 70 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/modules/yahooAdsBidAdapter.js b/modules/yahooAdsBidAdapter.js index 49efb7c20cb..5776f42cd6e 100644 --- a/modules/yahooAdsBidAdapter.js +++ b/modules/yahooAdsBidAdapter.js @@ -666,7 +666,7 @@ export const spec = { }; } - if (deepAccess(bidderRequest, 'mediaTypes.video.context') === 'outstream' && !bidderRequest.renderer) { + if (deepAccess(bidderRequest, 'mediaTypes.video.context') === 'outstream' && !bidderRequest.renderer && bidResponse.mediaType === VIDEO) { bidResponse.renderer = createRenderer(bidderRequest, bidResponse) || undefined; } diff --git a/test/spec/modules/yahooAdsBidAdapter_spec.js b/test/spec/modules/yahooAdsBidAdapter_spec.js index aaa7e2da8ee..b8be46c1903 100644 --- a/test/spec/modules/yahooAdsBidAdapter_spec.js +++ b/test/spec/modules/yahooAdsBidAdapter_spec.js @@ -3,6 +3,7 @@ import { config } from 'src/config.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { spec } from 'modules/yahooAdsBidAdapter.js'; import {createEidsArray} from '../../../modules/userId/eids'; +import {deepAccess} from '../../../src/utils'; const DEFAULT_BID_ID = '84ab500420319d'; const DEFAULT_BID_DCN = '2093845709823475'; @@ -1628,5 +1629,74 @@ describe('Yahoo Advertising Bid Adapter:', () => { expect(response[0].bidderCode).to.be.undefined; }); }); + + describe('Renderer:', () => { + it('should create and set renderer when bidder request context is outstream, bidder request renderer is falsy, and bid response mediaType is VIDEO', () => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + bidderRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.equal('outstream'); + expect(bidderRequest.renderer).to.be.undefined; + expect(response[0].mediaType).to.equal('video'); + expect(response[0].renderer).to.not.be.undefined; + }); + + it('should not create and set renderer when bidder request renderer is falsy and bid response mediaType is VIDEO, but bidder request context is not outstream,', () => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.not.equal('outstream'); + expect(bidderRequest.renderer).to.be.undefined; + expect(response[0].mediaType).to.equal('video'); + expect(response[0].renderer).to.be.undefined; + }); + + it('should not create and set renderer when bidder request context is outstream and bid response mediaType is VIDEO, but bidder request renderer is not falsy', () => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + bidderRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + bidderRequest.renderer = 'not falsy'; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.equal('outstream'); + expect(bidderRequest.renderer).to.not.be.undefined; + expect(response[0].mediaType).to.equal('video'); + expect(response[0].renderer).to.be.undefined; + }); + + it('should not create and set renderer when bidder request context is outstream and bidder request renderer is falsy, but bid response mediaType is not VIDEO', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.equal('outstream'); + expect(bidderRequest.renderer).to.be.undefined; + expect(response[0].mediaType).to.not.equal('video'); + expect(response[0].renderer).to.be.undefined; + }); + }); }); }); From 5b189786032a61adc54571dbdf214ad49d5fac46 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Mon, 9 Sep 2024 02:50:36 -0600 Subject: [PATCH 0490/1097] PBS Adapter: Add PBS_ANALYTICS Event (#12044) * Update index.js * Update constants.js * Update prebidServerBidAdapter_spec.js * Fix function name and make it better * Linting issue * Capitalize B --- modules/prebidServerBidAdapter/index.js | 29 ++++++++++++++-- src/constants.js | 1 + .../modules/prebidServerBidAdapter_spec.js | 33 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 629ac41dd14..33ddb9847fa 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -473,7 +473,8 @@ export function PrebidServer() { if (isValid) { bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest)); } - if (shouldEmitNonbids(s2sBidRequest.s2sConfig, response)) { + const { seatNonBidData, atagData } = getAnalyticsFlags(s2sBidRequest.s2sConfig, response) + if (seatNonBidData) { events.emit(EVENTS.SEAT_NON_BID, { seatnonbid: response.ext.seatnonbid, auctionId: bidRequests[0].auctionId, @@ -482,6 +483,18 @@ export function PrebidServer() { adapterMetrics }); } + // pbs analytics event + if (seatNonBidData || atagData) { + const data = { + seatnonbid: seatNonBidData, + atag: atagData, + auctionId: bidRequests[0].auctionId, + requestedBidders, + response, + adapterMetrics + } + events.emit(EVENTS.PBS_ANALYTICS, data); + } done(false); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, @@ -599,8 +612,18 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques } }, 'processPBSRequest'); -function shouldEmitNonbids(s2sConfig, response) { - return s2sConfig?.extPrebid?.returnallbidstatus && response?.ext?.seatnonbid; +function getAnalyticsFlags(s2sConfig, response) { + return { + atagData: getAtagData(response), + seatNonBidData: getNonBidData(s2sConfig, response) + } +} +function getNonBidData(s2sConfig, response) { + return s2sConfig?.extPrebid?.returnallbidstatus ? response?.ext?.seatnonbid : undefined; +} + +function getAtagData(response) { + return response?.ext?.prebid?.analytics?.tags; } adapterManager.registerBidAdapter(new PrebidServer(), 'prebidServer'); diff --git a/src/constants.js b/src/constants.js index bb20f1b66b9..095ee648b7e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -43,6 +43,7 @@ export const EVENTS = { BILLABLE_EVENT: 'billableEvent', BID_ACCEPTED: 'bidAccepted', RUN_PAAPI_AUCTION: 'paapiRunAuction', + PBS_ANALYTICS: 'pbsAnalytics', PAAPI_BID: 'paapiBid', PAAPI_NO_BID: 'paapiNoBid', PAAPI_ERROR: 'paapiError', diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 0ddc1174314..d8e52a9beec 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3157,6 +3157,39 @@ describe('S2S Adapter', function () { expect(event[1].response).to.deep.equal(responding); }); + it('emits the PBS_ANALYTICS event and captures seatnonbid responses', function () { + const original = CONFIG; + CONFIG.extPrebid = { returnallbidstatus: true }; + const nonbidResponse = {...RESPONSE_OPENRTB, ext: {seatnonbid: [{}]}}; + config.setConfig({ CONFIG }); + CONFIG = original; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const responding = deepClone(nonbidResponse); + Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) + server.requests[0].respond(200, {}, JSON.stringify(responding)); + const event = events.emit.thirdCall.args; + expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); + expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); + expect(event[1].requestedBidders).to.deep.equal(['appnexus']); + expect(event[1].response).to.deep.equal(responding); + }); + + it('emits the PBS_ANALYTICS event and captures atag responses', function () { + const original = CONFIG; + CONFIG.extPrebid = { returnallbidstatus: true }; + const atagResponse = {...RESPONSE_OPENRTB, ext: {prebid: {analytics: {tags: ['data']}}}}; + config.setConfig({ CONFIG }); + CONFIG = original; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const responding = deepClone(atagResponse); + Object.assign(responding.ext.prebid.analytics.tags, ['stuff']) + server.requests[0].respond(200, {}, JSON.stringify(responding)); + const event = events.emit.secondCall.args; + expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); + expect(event[1].atag[0]).to.deep.equal('stuff'); + expect(event[1].response).to.deep.equal(responding); + }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30 From 78fa92eb1030ebe68e1163268e43d7cdaa70a16d Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:12:09 +0200 Subject: [PATCH 0491/1097] Tappx Bid Adapter : fix multiple format sizes (#12209) * 10207_include setConfig function and intruccions in readme file * Update tappxBidAdapter.md * Update tappxBidAdapter.js * multiple formats --------- Co-authored-by: jgarciaorad --- modules/tappxBidAdapter.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 0b618b3c124..28965cf8ad0 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -328,11 +328,8 @@ function buildOneRequest(validBidRequests, bidderRequest) { banner.api = api; - let format = {}; - format[0] = {}; - format[0].w = w; - format[0].h = h; - banner.format = format; + const formatArr = bannerMediaType.sizes.map(size => ({w: size[0], h: size[1]})) + banner.format = Object.assign({}, formatArr); imp.banner = banner; } From ff822b82f4d762be3d1507fb4ec3457e4ebc164f Mon Sep 17 00:00:00 2001 From: Evan Luo <47800038+MrAAAgent@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:09:27 -0400 Subject: [PATCH 0492/1097] feat: include all context api response fields in ortb2site.ext.data object (#12210) --- modules/mobianRtdProvider.js | 11 +++++- test/spec/modules/mobianRtdProvider_spec.js | 42 ++++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 6499c3519f5..8f08b21e14a 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -45,18 +45,27 @@ function getBidRequestData(bidReqConfig, callback, config) { const contentCategories = results.mobianContentCategories || []; const sentiment = results.mobianSentiment || 'unknown'; const emotions = results.mobianEmotions || []; + const themes = results.mobianThemes || []; + const tones = results.mobianTones || []; + const genres = results.mobianGenres || []; const risk = { risk: mobianRisk, contentCategories: contentCategories, sentiment: sentiment, - emotions: emotions + emotions: emotions, + themes: themes, + tones: tones, + genres: genres, }; deepSetValue(ortb2Site.ext, 'data.mobianRisk', mobianRisk); deepSetValue(ortb2Site.ext, 'data.mobianContentCategories', contentCategories); deepSetValue(ortb2Site.ext, 'data.mobianSentiment', sentiment); deepSetValue(ortb2Site.ext, 'data.mobianEmotions', emotions); + deepSetValue(ortb2Site.ext, 'data.mobianThemes', themes); + deepSetValue(ortb2Site.ext, 'data.mobianTones', tones); + deepSetValue(ortb2Site.ext, 'data.mobianGenres', genres); resolve(risk); callback(); diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index 278d8fdef92..cbe04b9f893 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -36,7 +36,10 @@ describe('Mobian RTD Submodule', function () { mobianRisk: 'low', mobianSentiment: 'positive', mobianContentCategories: [], - mobianEmotions: ['joy'] + mobianEmotions: ['joy'], + mobianThemes: [], + mobianTones: [], + mobianGenres: [] } })); }); @@ -46,13 +49,19 @@ describe('Mobian RTD Submodule', function () { risk: 'low', contentCategories: [], sentiment: 'positive', - emotions: ['joy'] + emotions: ['joy'], + themes: [], + tones: [], + genres: [] }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'low', mobianContentCategories: [], mobianSentiment: 'positive', - mobianEmotions: ['joy'] + mobianEmotions: ['joy'], + mobianThemes: [], + mobianTones: [], + mobianGenres: [] }); }); }); @@ -68,7 +77,10 @@ describe('Mobian RTD Submodule', function () { mobianRisk: 'medium', mobianSentiment: 'negative', mobianContentCategories: ['arms', 'crime'], - mobianEmotions: ['anger', 'fear'] + mobianEmotions: ['anger', 'fear'], + mobianThemes: ['conflict', 'international relations'], + mobianTones: ['factual', 'serious'], + mobianGenres: ['news', 'political_analysis'] } })); }); @@ -78,13 +90,19 @@ describe('Mobian RTD Submodule', function () { risk: 'medium', contentCategories: ['arms', 'crime'], sentiment: 'negative', - emotions: ['anger', 'fear'] + emotions: ['anger', 'fear'], + themes: ['conflict', 'international relations'], + tones: ['factual', 'serious'], + genres: ['news', 'political_analysis'] }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'medium', mobianContentCategories: ['arms', 'crime'], mobianSentiment: 'negative', - mobianEmotions: ['anger', 'fear'] + mobianEmotions: ['anger', 'fear'], + mobianThemes: ['conflict', 'international relations'], + mobianTones: ['factual', 'serious'], + mobianGenres: ['news', 'political_analysis'] }); }); }); @@ -103,7 +121,7 @@ describe('Mobian RTD Submodule', function () { return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { expect(result).to.deep.equal({}); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.not.have.any.keys( - 'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions' + 'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions', 'mobianThemes', 'mobianTones', 'mobianGenres' ); }); }); @@ -149,13 +167,19 @@ describe('Mobian RTD Submodule', function () { risk: 'high', contentCategories: [], sentiment: 'unknown', - emotions: [] + emotions: [], + themes: [], + tones: [], + genres: [], }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'high', mobianContentCategories: [], mobianSentiment: 'unknown', - mobianEmotions: [] + mobianEmotions: [], + mobianThemes: [], + mobianTones: [], + mobianGenres: [] }); }); }); From f728178202e2fba09fc528ac3c1fba33dae322be Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:09:32 +0530 Subject: [PATCH 0493/1097] Check for valid data before adding to tracker (#12212) Co-authored-by: pm-azhar-mulla --- modules/pubmaticAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index e54db34d401..2fec213a612 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -499,7 +499,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&origbidid=' + enc(winningBid?.bidResponse?.partnerImpId || winningBid?.bidResponse?.prebidBidId || winningBid.bidId); pixelURL += '&di=' + enc(winningBid?.bidResponse?.dealId || OPEN_AUCTION_DEAL_ID); - pixelURL += '&pb=' + enc(pg); + pg && (pixelURL += '&pb=' + enc(pg)); pixelURL += '&plt=' + enc(getDevicePlatform()); pixelURL += '&psz=' + enc((winningBid?.bidResponse?.dimensions?.width || '0') + 'x' + From 1d2b583eb1e9689ef83506291daef353e9bc6d2d Mon Sep 17 00:00:00 2001 From: onlsol <48312668+onlsol@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:41:03 +0400 Subject: [PATCH 0494/1097] Refactor: Consolidate shared adapter methods into dspxUtils, reduce redundant code (#12140) * Refactor: Consolidate shared adapter methods into dspxUtils, reduce redundant code * Update bidderUtils.js * Update bidderUtils.js --------- Co-authored-by: avj Co-authored-by: Patrick McCann --- libraries/dspxUtils/bidderUtils.js | 390 ++++++++++++++++++++ modules/dspxBidAdapter.js | 443 ++--------------------- modules/stvBidAdapter.js | 217 +---------- test/spec/modules/dspxBidAdapter_spec.js | 6 +- test/spec/modules/stvBidAdapter_spec.js | 6 +- 5 files changed, 453 insertions(+), 609 deletions(-) create mode 100644 libraries/dspxUtils/bidderUtils.js diff --git a/libraries/dspxUtils/bidderUtils.js b/libraries/dspxUtils/bidderUtils.js new file mode 100644 index 00000000000..f19e2bfc29a --- /dev/null +++ b/libraries/dspxUtils/bidderUtils.js @@ -0,0 +1,390 @@ +import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import {deepAccess, isArray, isEmptyStr, isFn, logError} from '../../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ +/** + * Adds userIds to payload + * + * @param bidRequest + * @param payload + */ +export function fillUsersIds(bidRequest, payload) { + if (bidRequest.hasOwnProperty('userId')) { + let didMapping = { + did_netid: 'userId.netId', + did_id5: 'userId.id5id.uid', + did_id5_linktype: 'userId.id5id.ext.linkType', + did_uid2: 'userId.uid2', + did_sharedid: 'userId.sharedid', + did_pubcid: 'userId.pubcid', + did_uqid: 'userId.utiq', + did_cruid: 'userId.criteoid', + did_euid: 'userId.euid', + // did_tdid: 'unifiedId', + did_tdid: 'userId.tdid', + did_ppuid: function() { + let path = 'userId.pubProvidedId'; + let value = deepAccess(bidRequest, path); + if (isArray(value)) { + for (const rec of value) { + if (rec.uids && rec.uids.length > 0) { + for (let i = 0; i < rec.uids.length; i++) { + if ('id' in rec.uids[i] && deepAccess(rec.uids[i], 'ext.stype') === 'ppuid') { + return (rec.uids[i].atype ?? '') + ':' + rec.source + ':' + rec.uids[i].id; + } + } + } + } + } + return undefined; + }, + did_cpubcid: 'crumbs.pubcid' + }; + for (let paramName in didMapping) { + let path = didMapping[paramName]; + + // handle function + if (typeof path == 'function') { + let value = path(paramName); + if (value) { + payload[paramName] = value; + } + continue; + } + // direct access + let value = deepAccess(bidRequest, path); + if (typeof value == 'string' || typeof value == 'number') { + payload[paramName] = value; + } else if (typeof value == 'object') { + // trying to find string ID value + if (typeof deepAccess(bidRequest, path + '.id') == 'string') { + payload[paramName] = deepAccess(bidRequest, path + '.id'); + } else { + if (Object.keys(value).length > 0) { + logError(`WARNING: fillUserIds had to use first key in user object to get value for bid.userId key: ${path}.`); + payload[paramName] = value[Object.keys(value)[0]]; + } + } + } + } + } +} + +export function appendToUrl(url, what) { + if (!what) { + return url; + } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; +} + +export function objectToQueryString(obj, prefix) { + let str = []; + let p; + for (p in obj) { + if (obj.hasOwnProperty(p)) { + let k = prefix ? prefix + '[' + p + ']' : p; + let v = obj[p]; + str.push((v !== null && typeof v === 'object') + ? objectToQueryString(v, k) + : encodeURIComponent(k) + '=' + encodeURIComponent(v)); + } + } + return str.filter(n => n).join('&'); +} + +/** + * Check if it's a banner bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a banner bid + */ +export function isBannerRequest(bid) { + return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); +} + +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ +export function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +/** + * Get video sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} + */ +export function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +/** + * Get video context + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} + */ +export function getVideoContext(bid) { + return deepAccess(bid, 'mediaTypes.video.context') || 'unknown'; +} + +/** + * Get banner sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} True if it's a video bid + */ +export function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +/** + * Parse size + * @param size + * @returns {object} sizeObj + */ +export function parseSize(size) { + let sizeObj = {} + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + return sizeObj; +} + +/** + * Parse sizes + * @param sizes + * @returns {{width: number , height: number }[]} + */ +export function parseSizes(sizes) { + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parseSize(size)); + } + return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) +} + +/** + * Get MediaInfo object for server request + * + * @param mediaTypesInfo + * @returns {*} + */ +export function convertMediaInfoForRequest(mediaTypesInfo) { + let requestData = {}; + Object.keys(mediaTypesInfo).forEach(mediaType => { + requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { + return size.width + 'x' + size.height; + }).join(','); + }); + return requestData; +} + +/** + * Get media types info + * + * @param bid + */ +export function getMediaTypesInfo(bid) { + let mediaTypesInfo = {}; + + if (bid.mediaTypes) { + Object.keys(bid.mediaTypes).forEach(mediaType => { + if (mediaType === BANNER) { + mediaTypesInfo[mediaType] = getBannerSizes(bid); + } + if (mediaType === VIDEO) { + mediaTypesInfo[mediaType] = getVideoSizes(bid); + } + }); + } else { + mediaTypesInfo[BANNER] = getBannerSizes(bid); + } + return mediaTypesInfo; +} + +/** + * Get Bid Floor + * @param bid + * @returns {number|*} + */ +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +/** + * Convert site.content to string + * @param content + */ +export function siteContentToString(content) { + if (!content) { + return ''; + } + let stringKeys = ['id', 'title', 'series', 'season', 'artist', 'genre', 'isrc', 'url', 'keywords']; + let intKeys = ['episode', 'context', 'livestream']; + let arrKeys = ['cat']; + let retArr = []; + arrKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && Array.isArray(val)) { + retArr.push(k + ':' + val.join('|')); + } + }); + intKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && typeof val === 'number') { + retArr.push(k + ':' + val); + } + }); + stringKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && typeof val === 'string') { + retArr.push(k + ':' + encodeURIComponent(val)); + } + }); + return retArr.join(','); +} + +/** + * Assigns multiple values to the specified keys on an object if the values are not undefined. + * @param {Object} target - The object to which the values will be assigned. + * @param {Object} values - An object containing key-value pairs to be assigned. + */ +export function assignDefinedValues(target, values) { + for (const key in values) { + if (values[key] !== undefined) { + target[key] = values[key]; + } + } +} + +/** + * Extracts user segments/topics from the bid request object + * @param {Object} bid - The bid request object + * @returns {{segclass: *, segtax: *, segments: *}|undefined} - User segments/topics or undefined if not found + */ +export function extractUserSegments(bid) { + const userData = deepAccess(bid, 'ortb2.user.data') || []; + for (const dataObj of userData) { + if (dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0) { + const segments = dataObj.segment + .filter(seg => seg.id && !isEmptyStr(seg.id) && isFinite(seg.id)) + .map(seg => Number(seg.id)); + if (segments.length > 0) { + return { + segtax: deepAccess(dataObj, 'ext.segtax'), + segclass: deepAccess(dataObj, 'ext.segclass'), + segments: segments.join(',') + }; + } + } + } + return undefined; +} + +export function handleSyncUrls(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + const syncs = []; + let gdprParams = ''; + if (gdprConsent) { + if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (serverResponses.length > 0 && serverResponses[0].body.userSync) { + if (syncOptions.iframeEnabled) { + serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ + type: 'iframe', + url: appendToUrl(url, gdprParams) + })); + } + if (syncOptions.pixelEnabled) { + serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ + type: 'image', + url: appendToUrl(url, gdprParams) + })); + } + } + return syncs; +} + +export function interpretResponse(serverResponse, bidRequest, rendererFunc) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.crid || 0; + const cpm = response.cpm / 1000000 || 0; + if (cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'EUR'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const bidResponse = { + requestId: response.bid_id, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + type: response.type, + ttl: 60, + meta: { + advertiserDomains: response.adomain || [] + } + }; + + if (response.vastUrl) { + bidResponse.vastUrl = response.vastUrl; + bidResponse.mediaType = 'video'; + } + if (response.vastXml) { + bidResponse.vastXml = response.vastXml; + bidResponse.mediaType = 'video'; + } + if (response.renderer) { + bidResponse.renderer = rendererFunc(bidRequest, response); + } + + if (response.videoCacheKey) { + bidResponse.videoCacheKey = response.videoCacheKey; + } + + if (response.adTag) { + bidResponse.ad = response.adTag; + } + + if (response.bid_appendix) { + Object.keys(response.bid_appendix).forEach(fieldName => { + bidResponse[fieldName] = response.bid_appendix[fieldName]; + }); + } + + bidResponses.push(bidResponse); + } + return bidResponses; +} diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index b2610610cf2..acb5fb64d81 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,9 +1,22 @@ -import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn, isEmptyStr, isArray} from '../src/utils.js'; - +import {deepAccess, logMessage, getBidIdParameter, logError, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; import {includes} from '../src/polyfill.js'; +import { + fillUsersIds, + handleSyncUrls, + objectToQueryString, + isBannerRequest, + getVideoContext, + convertMediaInfoForRequest, + getMediaTypesInfo, + getBidFloor, + siteContentToString, + assignDefinedValues, + extractUserSegments, + interpretResponse +} from '../libraries/dspxUtils/bidderUtils.js'; +import {Renderer} from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -153,341 +166,11 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest) { logMessage('DSPx: serverResponse', serverResponse); logMessage('DSPx: bidRequest', bidRequest); - const bidResponses = []; - const response = serverResponse.body; - const crid = response.crid || 0; - const cpm = response.cpm / 1000000 || 0; - if (cpm !== 0 && crid !== 0) { - const dealId = response.dealid || ''; - const currency = response.currency || 'EUR'; - const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; - const bidResponse = { - requestId: response.bid_id, - cpm: cpm, - width: response.width, - height: response.height, - creativeId: crid, - dealId: dealId, - currency: currency, - netRevenue: netRevenue, - type: response.type, - ttl: 60, - meta: { - advertiserDomains: response.adomain || [] - } - }; - - if (response.vastUrl) { - bidResponse.vastUrl = response.vastUrl; - bidResponse.mediaType = 'video'; - } - if (response.vastXml) { - bidResponse.vastXml = response.vastXml; - bidResponse.mediaType = 'video'; - } - if (response.renderer) { - bidResponse.renderer = newRenderer(bidRequest, response); - } - - if (response.videoCacheKey) { - bidResponse.videoCacheKey = response.videoCacheKey; - } - - if (response.adTag) { - bidResponse.ad = response.adTag; - } - - if (response.bid_appendix) { - Object.keys(response.bid_appendix).forEach(fieldName => { - bidResponse[fieldName] = response.bid_appendix[fieldName]; - }); - } - - bidResponses.push(bidResponse); - } - return bidResponses; + return interpretResponse(serverResponse, bidRequest, (bidRequest, response) => newRenderer(bidRequest, response)); }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (!serverResponses || serverResponses.length === 0) { - return []; - } - - const syncs = [] - - let gdprParams = ''; - if (gdprConsent) { - if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - gdprParams = `gdpr_consent=${gdprConsent.consentString}`; - } - } - - if (serverResponses.length > 0 && serverResponses[0].body.userSync) { - if (syncOptions.iframeEnabled) { - serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ - type: 'iframe', - url: appendToUrl(url, gdprParams) - })); - } - if (syncOptions.pixelEnabled) { - serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ - type: 'image', - url: appendToUrl(url, gdprParams) - })); - } - } - return syncs; - } -} - -/** - * Adds userIds to payload - * - * @param bidRequest - * @param payload - */ -function fillUsersIds(bidRequest, payload) { - if (bidRequest.hasOwnProperty('userId')) { - let didMapping = { - did_netid: 'userId.netId', - did_id5: 'userId.id5id.uid', - did_id5_linktype: 'userId.id5id.ext.linkType', - did_uid2: 'userId.uid2', - did_sharedid: 'userId.sharedid', - did_pubcid: 'userId.pubcid', - did_uqid: 'userId.utiq', - did_cruid: 'userId.criteoid', - did_euid: 'userId.euid', - // did_tdid: 'unifiedId', - did_tdid: 'userId.tdid', - did_ppuid: function() { - let path = 'userId.pubProvidedId'; - let value = deepAccess(bidRequest, path); - if (isArray(value)) { - for (const rec of value) { - if (rec.uids && rec.uids.length > 0) { - for (let i = 0; i < rec.uids.length; i++) { - if ('id' in rec.uids[i] && deepAccess(rec.uids[i], 'ext.stype') === 'ppuid') { - return (rec.uids[i].atype ?? '') + ':' + rec.source + ':' + rec.uids[i].id; - } - } - } - } - } - return undefined; - }, - did_cpubcid: 'crumbs.pubcid' - }; - for (let paramName in didMapping) { - let path = didMapping[paramName]; - - // handle function - if (typeof path == 'function') { - let value = path(paramName); - if (value) { - payload[paramName] = value; - } - continue; - } - // direct access - let value = deepAccess(bidRequest, path); - if (typeof value == 'string' || typeof value == 'number') { - payload[paramName] = value; - } else if (typeof value == 'object') { - // trying to find string ID value - if (typeof deepAccess(bidRequest, path + '.id') == 'string') { - payload[paramName] = deepAccess(bidRequest, path + '.id'); - } else { - if (Object.keys(value).length > 0) { - logError(`DSPx: WARNING: fillUserIds had to use first key in user object to get value for bid.userId key: ${path}.`); - payload[paramName] = value[Object.keys(value)[0]]; - } - } - } - } - } -} - -function appendToUrl(url, what) { - if (!what) { - return url; - } - return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; -} - -function objectToQueryString(obj, prefix) { - let str = []; - let p; - for (p in obj) { - if (obj.hasOwnProperty(p)) { - let k = prefix ? prefix + '[' + p + ']' : p; - let v = obj[p]; - str.push((v !== null && typeof v === 'object') - ? objectToQueryString(v, k) - : encodeURIComponent(k) + '=' + encodeURIComponent(v)); - } - } - return str.filter(n => n).join('&'); -} - -/** - * Check if it's a banner bid request - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a banner bid - */ -function isBannerRequest(bid) { - return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); -} - -/** - * Check if it's a video bid request - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a video bid - */ -function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); -} - -/** - * Get video sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} - */ -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -/** - * Get video context - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} - */ -function getVideoContext(bid) { - return deepAccess(bid, 'mediaTypes.video.context') || 'unknown'; -} - -/** - * Get banner sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid - */ -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -/** - * Parse size - * @param size - * @returns {object} sizeObj - */ -function parseSize(size) { - let sizeObj = {} - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - return sizeObj; -} - -/** - * Parse sizes - * @param sizes - * @returns {{width: number , height: number }[]} - */ -function parseSizes(sizes) { - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parseSize(size)); - } - return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) -} - -/** - * Get MediaInfo object for server request - * - * @param mediaTypesInfo - * @returns {*} - */ -function convertMediaInfoForRequest(mediaTypesInfo) { - let requestData = {}; - Object.keys(mediaTypesInfo).forEach(mediaType => { - requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { - return size.width + 'x' + size.height; - }).join(','); - }); - return requestData; -} - -/** - * Get media types info - * - * @param bid - */ -function getMediaTypesInfo(bid) { - let mediaTypesInfo = {}; - - if (bid.mediaTypes) { - Object.keys(bid.mediaTypes).forEach(mediaType => { - if (mediaType === BANNER) { - mediaTypesInfo[mediaType] = getBannerSizes(bid); - } - if (mediaType === VIDEO) { - mediaTypesInfo[mediaType] = getVideoSizes(bid); - } - }); - } else { - mediaTypesInfo[BANNER] = getBannerSizes(bid); - } - return mediaTypesInfo; -} - -/** - * Get Bid Floor - * @param bid - * @returns {number|*} - */ -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'EUR', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - -/** - * Create a new renderer - * - * @param bidRequest - * @param response - * @returns {Renderer} - */ -function newRenderer(bidRequest, response) { - logMessage('DSPx: newRenderer', bidRequest, response); - const renderer = Renderer.install({ - id: response.renderer.id || response.bid_id, - url: (bidRequest.params && bidRequest.params.rendererUrl) || response.renderer.url, - config: response.renderer.options || deepAccess(bidRequest, 'renderer.options'), - loaded: false - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); + return handleSyncUrls(syncOptions, serverResponses, gdprConsent); } - return renderer; } /** @@ -496,7 +179,7 @@ function newRenderer(bidRequest, response) { * @param bid */ function outstreamRender(bid) { - logMessage('DSPx: outstreamRender bid:', bid); + logMessage('[DSPx][outstreamRender] bid:', bid); const embedCode = createOutstreamEmbedCode(bid); try { const inIframe = getBidIdParameter('iframe', bid.renderer.config); @@ -507,7 +190,7 @@ function outstreamRender(bid) { if (typeof window.dspxRender === 'function') { window.dspxRender(bid); } else { - logError('[dspx][renderer] Error: dspxRender function is not found'); + logError('[DSPx][outstreamRender] Error: dspxRender function is not found'); } return; } @@ -518,13 +201,13 @@ function outstreamRender(bid) { if (typeof window.dspxRender === 'function') { window.dspxRender(bid); } else { - logError('[dspx][renderer] Error: dspxRender function is not found'); + logError('[DSPx][outstreamRender] Error: dspxRender function is not found'); } } else if (slot) { - logError('[dspx][renderer] Error: slot not found'); + logError('[DSPx][outstreamRender] Error: slot not found'); } } catch (err) { - logError('[dspx][renderer] Error:' + err.message) + logError('[DSPx][outstreamRender] Error:' + err.message) } } @@ -561,73 +244,27 @@ function createOutstreamEmbedCode(bid) { } /** - * Convert site.content to string - * @param content + * Create a new renderer + * + * @param bidRequest + * @param response + * @returns {Renderer} */ -function siteContentToString(content) { - if (!content) { - return ''; - } - let stringKeys = ['id', 'title', 'series', 'season', 'artist', 'genre', 'isrc', 'url', 'keywords']; - let intKeys = ['episode', 'context', 'livestream']; - let arrKeys = ['cat']; - let retArr = []; - arrKeys.forEach(k => { - let val = deepAccess(content, k); - if (val && Array.isArray(val)) { - retArr.push(k + ':' + val.join('|')); - } - }); - intKeys.forEach(k => { - let val = deepAccess(content, k); - if (val && typeof val === 'number') { - retArr.push(k + ':' + val); - } - }); - stringKeys.forEach(k => { - let val = deepAccess(content, k); - if (val && typeof val === 'string') { - retArr.push(k + ':' + encodeURIComponent(val)); - } +function newRenderer(bidRequest, response) { + logMessage('[DSPx] newRenderer', bidRequest, response); + const renderer = Renderer.install({ + id: response.renderer.id || response.bid_id, + url: (bidRequest.params && bidRequest.params.rendererUrl) || response.renderer.url, + config: response.renderer.options || deepAccess(bidRequest, 'renderer.options'), + loaded: false }); - return retArr.join(','); -} - -/** - * Assigns multiple values to the specified keys on an object if the values are not undefined. - * @param {Object} target - The object to which the values will be assigned. - * @param {Object} values - An object containing key-value pairs to be assigned. - */ -function assignDefinedValues(target, values) { - for (const key in values) { - if (values[key] !== undefined) { - target[key] = values[key]; - } - } -} -/** - * Extracts user segments/topics from the bid request object - * @param {Object} bid - The bid request object - * @returns {{segclass: *, segtax: *, segments: *}|undefined} - User segments/topics or undefined if not found - */ -function extractUserSegments(bid) { - const userData = deepAccess(bid, 'ortb2.user.data') || []; - for (const dataObj of userData) { - if (dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0) { - const segments = dataObj.segment - .filter(seg => seg.id && !isEmptyStr(seg.id) && isFinite(seg.id)) - .map(seg => Number(seg.id)); - if (segments.length > 0) { - return { - segtax: deepAccess(dataObj, 'ext.segtax'), - segclass: deepAccess(dataObj, 'ext.segclass'), - segments: segments.join(',') - }; - } - } + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('[DSPx]Prebid Error calling setRender on renderer', err); } - return undefined; + return renderer; } registerBidder(spec); diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index 5cffc5853b5..ef8b815b5f9 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -1,7 +1,16 @@ -import {deepAccess} from '../src/utils.js'; +import {deepAccess, logMessage} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; +import { + handleSyncUrls, + isBannerRequest, + isVideoRequest, + convertMediaInfoForRequest, + getMediaTypesInfo, + getBidFloor, + interpretResponse +} from '../libraries/dspxUtils/bidderUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -121,88 +130,21 @@ export const spec = { return { method: 'GET', url: endpoint, - data: objectToQueryString(payload), + data: stvObjectToQueryString(payload), }; }); }, interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - const response = serverResponse.body; - const crid = response.crid || 0; - const cpm = response.cpm / 1000000 || 0; - if (cpm !== 0 && crid !== 0) { - const dealId = response.dealid || ''; - const currency = response.currency || 'EUR'; - const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; - const bidResponse = { - requestId: response.bid_id, - cpm: cpm, - width: response.width, - height: response.height, - creativeId: crid, - dealId: dealId, - currency: currency, - netRevenue: netRevenue, - ttl: 60, - meta: { - advertiserDomains: response.adomain || [] - } - }; - if (response.vastXml) { - bidResponse.vastXml = response.vastXml; - bidResponse.mediaType = 'video'; - } else { - bidResponse.ad = response.adTag; - } - - bidResponses.push(bidResponse); - } - return bidResponses; + logMessage('STV: serverResponse', serverResponse); + logMessage('STV: bidRequest', bidRequest); + return interpretResponse(serverResponse, bidRequest, (bidRequest, response) => null); // we don't use any renderer }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (!serverResponses || serverResponses.length === 0) { - return []; - } - - const syncs = [] - - let gdprParams = ''; - if (gdprConsent) { - if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - gdprParams = `gdpr_consent=${gdprConsent.consentString}`; - } - } - - if (serverResponses.length > 0 && serverResponses[0].body !== undefined && - serverResponses[0].body.userSync !== undefined && serverResponses[0].body.userSync.iframeUrl !== undefined && - serverResponses[0].body.userSync.iframeUrl.length > 0) { - if (syncOptions.iframeEnabled) { - serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ - type: 'iframe', - url: appendToUrl(url, gdprParams) - })); - } - if (syncOptions.pixelEnabled) { - serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ - type: 'image', - url: appendToUrl(url, gdprParams) - })); - } - } - return syncs; + return handleSyncUrls(syncOptions, serverResponses, gdprConsent); } } -function appendToUrl(url, what) { - if (!what) { - return url; - } - return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; -} - -function objectToQueryString(obj, prefix) { +function stvObjectToQueryString(obj, prefix) { let str = []; let p; for (p in obj) { @@ -210,7 +152,7 @@ function objectToQueryString(obj, prefix) { let k = prefix ? prefix + '[' + p + ']' : p; let v = obj[p]; str.push((v !== null && typeof v === 'object') - ? objectToQueryString(v, k) + ? stvObjectToQueryString(v, k) : (k == 'schain' || k == 'uids' ? k + '=' + v : encodeURIComponent(k) + '=' + encodeURIComponent(v))); } } @@ -291,129 +233,4 @@ function serializeUids(bidRequest) { return uids.join(','); } -/** - * Check if it's a banner bid request - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a banner bid - */ -function isBannerRequest(bid) { - return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); -} - -/** - * Check if it's a video bid request - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a video bid - */ -function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); -} - -/** - * Get video sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid - */ -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -/** - * Get banner sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid - */ -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -/** - * Parse size - * @param sizes - * @returns {width: number, h: height} - */ -function parseSize(size) { - let sizeObj = {} - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - return sizeObj; -} - -/** - * Parse sizes - * @param sizes - * @returns {{width: number , height: number }[]} - */ -function parseSizes(sizes) { - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parseSize(size)); - } - return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) -} - -/** - * Get MediaInfo object for server request - * - * @param mediaTypesInfo - * @returns {*} - */ -function convertMediaInfoForRequest(mediaTypesInfo) { - let requestData = {}; - Object.keys(mediaTypesInfo).forEach(mediaType => { - requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { - return size.width + 'x' + size.height; - }).join(','); - }); - return requestData; -} - -/** - * Get Bid Floor - * @param bid - * @returns {number|*} - */ -function getBidFloor(bid) { - if (typeof bid.getFloor !== 'function') { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'EUR', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - -/** - * Get media types info - * - * @param bid - */ -function getMediaTypesInfo(bid) { - let mediaTypesInfo = {}; - - if (bid.mediaTypes) { - Object.keys(bid.mediaTypes).forEach(mediaType => { - if (mediaType === BANNER) { - mediaTypesInfo[mediaType] = getBannerSizes(bid); - } - if (mediaType === VIDEO) { - mediaTypesInfo[mediaType] = getVideoSizes(bid); - } - }); - } else { - mediaTypesInfo[BANNER] = getBannerSizes(bid); - } - return mediaTypesInfo; -} - registerBidder(spec); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 8f02e51f131..ad7bc827837 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -608,7 +608,7 @@ describe('dspxAdapter', function () { } }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[0])); expect(result[0].meta.advertiserDomains.length).to.equal(1); expect(result[0].meta.advertiserDomains[0]).to.equal(expectedResponse[0].meta.advertiserDomains[0]); }); @@ -628,7 +628,7 @@ describe('dspxAdapter', function () { } }]; let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[1])); expect(result[0].meta.advertiserDomains.length).to.equal(0); }); @@ -647,7 +647,7 @@ describe('dspxAdapter', function () { } }]; let result = spec.interpretResponse(serverVideoResponseVastUrl, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[2])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[2])); expect(result[0].meta.advertiserDomains.length).to.equal(0); }); diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js index 099d8d33b02..9dc311562ba 100644 --- a/test/spec/modules/stvBidAdapter_spec.js +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -277,7 +277,7 @@ describe('stvAdapter', function() { 'width': '300', 'height': '250', 'type': 'sspHTML', - 'tag': '', + 'adTag': '', 'requestId': '220ed41385952a', 'currency': 'EUR', 'ttl': 60, @@ -338,7 +338,7 @@ describe('stvAdapter', function() { } }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[0])); expect(result[0].meta.advertiserDomains.length).to.equal(1); expect(result[0].meta.advertiserDomains[0]).to.equal(expectedResponse[0].meta.advertiserDomains[0]); }); @@ -358,7 +358,7 @@ describe('stvAdapter', function() { } }]; let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[1])); expect(result[0].meta.advertiserDomains.length).to.equal(0); }); From 123d8aa4c98efefd913b58e16b4ffbc162b11a4f Mon Sep 17 00:00:00 2001 From: mp4symitri Date: Mon, 9 Sep 2024 22:05:46 +0530 Subject: [PATCH 0495/1097] Symetri RTD module: OnBidResponse method added (#12214) * OnBidResponse listener added to add pixel for deals matching to the user deals stored in local storage * Adding support for simpleId, compositeId & hashedId * Solved Linter errors. Made some changes to reduce integration errors * Rolled back the default case as it was generating test errors. * Testing onBidResponseEvent * Pass pixel URL as module config parameter * Added extra attributes to Pixel URL. Documentation Updated. * Bidder Name & Code both added * Fixed Tests Fixed Linter Errors Updated Example --------- Co-authored-by: Manan Co-authored-by: Jeff Palladino <1226357+jpalladino84@users.noreply.github.com> --- .../gpt/symitridap_segments_example.html | 3 +- modules/symitriDapRtdProvider.js | 30 +++++++++++++++++-- modules/symitriDapRtdProvider.md | 15 ++++++++-- .../modules/symitriDapRtdProvider_spec.js | 16 +++++++++- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/integrationExamples/gpt/symitridap_segments_example.html b/integrationExamples/gpt/symitridap_segments_example.html index 8ec7958dd0d..1f5a654cfdb 100644 --- a/integrationExamples/gpt/symitridap_segments_example.html +++ b/integrationExamples/gpt/symitridap_segments_example.html @@ -78,7 +78,8 @@ apiVersion: "x1", domain: "prebid.org", identityValue: "test@invalid.com", - identityType: "hid" + identityType: "simpleid", + pixelUrl: 'https://www.test.com/pixel' } } ] diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index aed583cbdfa..aeb42226470 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -151,13 +151,30 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return true; } + function onBidResponse(bidResponse, config, userConsent) { + if (bidResponse.dealId && typeof (bidResponse.dealId) != typeof (undefined)) { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); // Get Membership details from Local Storage + let deals = membership.deals; // Get list of Deals the user is mapped to + deals.forEach((deal) => { + deal = JSON.parse(deal); + if (bidResponse.dealId == deal.id) { // Check if the bid response deal Id matches to the deals mapped to the user + let token = dapUtils.dapGetTokenFromLocalStorage(); + let url = config.params.pixelUrl + '?token=' + token + '&ad_id=' + bidResponse.adId + '&bidder=' + bidResponse.bidder + '&bidder_code=' + bidResponse.bidderCode + '&cpm=' + bidResponse.cpm + '&creative_id=' + bidResponse.creativeId + '&deal_id=' + bidResponse.dealId + '&media_type=' + bidResponse.mediaType + '&response_timestamp=' + bidResponse.responseTimestamp; + bidResponse.ad = `${bidResponse.ad}`, meta: { - advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a', + advertiserDomains: [(matchedBid.advertiser) ? matchedBid.advertiser : 'n/a'], }, }; diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 225186dd326..25d8f2baec0 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -682,7 +682,7 @@ describe('yieldlabBidAdapter', () => { expect(result[0].netRevenue).to.equal(false); expect(result[0].ttl).to.equal(300); expect(result[0].referrer).to.equal(''); - expect(result[0].meta.advertiserDomains).to.equal('yieldlab'); + expect(result[0].meta.advertiserDomains).to.deep.equal(['yieldlab']); expect(result[0].ad).to.include(' - - -
- - - - `; - - return adcode; -} - const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -714,21 +644,22 @@ const spec = { interpretResponse(serverResponse, request) { const { bidderRequest } = request; - const response = serverResponse.body; + const { body: response = {} } = serverResponse; + const { seatbid: responseSeat, ext: responseExt = {} } = response; + const { paapi: fledgeAuctionConfigs = [] } = responseExt; const bids = []; let site = JSON.parse(request.data).site; // get page and referer data from request site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) pageView.sn = site.sn; // store site_name (for syncing and notifications) - let seat; - if (response.seatbid !== undefined) { + if (responseSeat !== undefined) { /* Match response to request, by comparing bid id's 'bidid-' prefix indicates oneCode (parameterless) request and response */ - response.seatbid.forEach(seatbid => { - seat = seatbid.seat; - seatbid.bid.forEach(serverBid => { + responseSeat.forEach(seatbid => { + const { seat, bid } = seatbid; + bid.forEach(serverBid => { // get data from bid response const { adomain, crid = `mcad_${bidderRequest.auctionId}_${site.slot}`, impid, exp = 300, ext = {}, price, w, h } = serverBid; @@ -744,7 +675,7 @@ const spec = { const { bidId } = bidRequest || {}; // get ext data from bid - const { siteid = site.id, slotid = site.slot, pubid, adlabel, cache: creativeCache, vurls = [], dsa } = ext; + const { siteid = site.id, slotid = site.slot, pubid, adlabel, cache: creativeCache, vurls = [], dsa, platform = 'wpartner', pricepl } = ext; // update site data site = { @@ -775,8 +706,9 @@ const spec = { meta: { advertiserDomains: adomain, networkName: seat, - pricepl: ext && ext.pricepl, + pricepl, dsa, + platform, }, netRevenue: true, vurls, @@ -790,6 +722,8 @@ const spec = { bid.vastXml = serverBid.adm; bid.vastContent = serverBid.adm; bid.vastUrl = creativeCache; + + logInfo(`Bid ${bid.creativeId} is a video ad`); } else if (isNativeAd(serverBid)) { // native bid.mediaType = 'native'; @@ -803,10 +737,18 @@ const spec = { logWarn('Could not parse native data', serverBid.adm); bid.cpm = 0; } - } else { - // banner ad (default) + logInfo(`Bid ${bid.creativeId} as a native ad`); + } else if (isHTML(serverBid)) { + // banner ad (preformatted) bid.mediaType = 'banner'; - bid.ad = renderCreative(site, response.id, serverBid, seat, bidderRequest); + logInfo(`Bid ${bid.creativeId} as a preformatted banner`); + bid.ad = serverBid.adm; + } else { + // unsupported bid format - send notification and set CPM to zero + const payload = getNotificationPayload(bid); + payload.event = 'parseError'; + sendNotification(payload); + bid.cpm = 0; } if (bid.cpm > 0) { @@ -820,15 +762,14 @@ const spec = { }); } - return bids; + return fledgeAuctionConfigs.length ? { bids, fledgeAuctionConfigs } : bids; }, - getUserSyncs(syncOptions, serverResponses, gdprConsent) { + getUserSyncs(syncOptions) { let mySyncs = []; - // TODO: the check on CMP api version does not seem to make sense here. It means "always run the usersync unless an old (v1) CMP was detected". No attention is paid to the consent choices. - if (syncOptions.iframeEnabled && consentApiVersion != 1) { + if (syncOptions.iframeEnabled) { mySyncs.push({ type: 'iframe', - url: `${SYNC_URL}?tcf=${consentApiVersion}&pvid=${pageView.id}&sn=${pageView.sn}`, + url: `${SYNC_URL}?tcf=2&pvid=${pageView.id}&sn=${pageView.sn}`, }); }; return mySyncs; @@ -843,6 +784,15 @@ const spec = { } }, + onBidderError(errorData) { + const payload = getNotificationPayload(errorData); + if (payload) { + payload.event = 'parseError'; + sendNotification(payload); + return payload; + } + }, + onBidViewable(bid) { const payload = getNotificationPayload(bid); if (payload) { @@ -852,6 +802,15 @@ const spec = { } }, + onBidBillable(bid) { + const payload = getNotificationPayload(bid); + if (payload) { + payload.event = 'bidBillable'; + sendNotification(payload); + return payload; + } + }, + onBidWon(bid) { const payload = getNotificationPayload(bid); if (payload) { @@ -860,6 +819,7 @@ const spec = { return payload; } }, + }; registerBidder(spec); diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 2f5fe104eb1..623faab5f1e 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -303,7 +303,7 @@ describe('SSPBC adapter', function () { 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', - 'adm': 'AD CODE 1', + 'adm': 'AD_CODE1', 'adomain': ['adomain.pl'], 'cid': 'BZ4gAg21T5nNtxlUCDSW', 'crid': 'lxHWkB7OnZeso3QiN1N4', @@ -319,7 +319,7 @@ describe('SSPBC adapter', function () { 'siteid': '8816', 'slotid': '005', 'price': 2, - 'adm': 'AD CODE 2', + 'adm': 'AD_CODE2', 'cid': '57744', 'crid': '858252', 'w': 300, @@ -343,7 +343,64 @@ describe('SSPBC adapter', function () { 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', - 'adm': 'AD CODE 1', + 'adm': 'AD_CODE', + 'adomain': ['adomain.pl'], + 'cid': 'BZ4gAg21T5nNtxlUCDSW', + 'crid': 'lxHWkB7OnZeso3QiN1N4', + 'w': 728, + 'h': 90, + }], + 'seat': 'dsp1', + 'group': 0 + }], + 'cur': 'PLN' + } + }; + + const serverResponsePaapi = { + 'body': { + 'id': bidderRequestId, + 'seatbid': [{ + 'bid': [{ + 'id': '3347324c-6889-46d2-a800-ae78a5214c06', + 'impid': '003', + 'siteid': '8816', + 'slotid': '003', + 'price': 1, + 'adid': 'lxHWkB7OnZeso3QiN1N4', + 'nurl': '', + 'adm': 'AD_CODE', + 'adomain': ['adomain.pl'], + 'cid': 'BZ4gAg21T5nNtxlUCDSW', + 'crid': 'lxHWkB7OnZeso3QiN1N4', + 'w': 728, + 'h': 90, + }], + 'seat': 'dsp1', + 'group': 0 + }], + 'cur': 'PLN', + 'ext': { + 'paapi': [ + { 'config_data': 'config value' }, + ] + }, + } + }; + + const serverResponseIncorrect = { + 'body': { + 'id': bidderRequestId, + 'seatbid': [{ + 'bid': [{ + 'id': '3347324c-6889-46d2-a800-ae78a5214c06', + 'impid': '003', + 'siteid': '8816', + 'slotid': '003', + 'price': 1, + 'adid': 'lxHWkB7OnZeso3QiN1N4', + 'nurl': '', + 'adm': 'THIS_IS_NOT_AN_AD', 'adomain': ['adomain.pl'], 'cid': 'BZ4gAg21T5nNtxlUCDSW', 'crid': 'lxHWkB7OnZeso3QiN1N4', @@ -366,7 +423,7 @@ describe('SSPBC adapter', function () { 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', - 'adm': 'AD CODE 1', + 'adm': 'AD_CODE', 'adomain': ['adomain.pl'], 'cid': 'BZ4gAg21T5nNtxlUCDSW', 'crid': 'lxHWkB7OnZeso3QiN1N4', @@ -458,6 +515,8 @@ describe('SSPBC adapter', function () { serverResponse, serverResponseOneCode, serverResponseSingle, + serverResponseIncorrect, + serverResponsePaapi, serverResponseVideo, serverResponseNative, emptyResponse @@ -593,7 +652,7 @@ describe('SSPBC adapter', function () { }); describe('interpretResponse', function () { - const { bid_OneCode, bid_video, bid_native, bids, emptyResponse, serverResponse, serverResponseOneCode, serverResponseSingle, serverResponseVideo, serverResponseNative, bidRequest, bidRequestOneCode, bidRequestSingle, bidRequestVideo, bidRequestNative } = prepareTestData(); + const { bid_OneCode, bid_video, bid_native, bids, emptyResponse, serverResponse, serverResponseOneCode, serverResponseSingle, serverResponseIncorrect, serverResponsePaapi, serverResponseVideo, serverResponseNative, bidRequest, bidRequestOneCode, bidRequestSingle, bidRequestVideo, bidRequestNative } = prepareTestData(); const request = spec.buildRequests(bids, bidRequest); const requestSingle = spec.buildRequests([bids[0]], bidRequestSingle); const requestOneCode = spec.buildRequests([bid_OneCode], bidRequestOneCode); @@ -632,15 +691,11 @@ describe('SSPBC adapter', function () { expect(resultPartial.length).to.equal(1); }); - it('banner ad code should contain required variables', function () { + it('should not alter HTML from response', function () { let resultSingle = spec.interpretResponse(serverResponseSingle, requestSingle); let adcode = resultSingle[0].ad; - expect(adcode).to.be.a('string'); - expect(adcode).to.contain('window.rekid'); - expect(adcode).to.contain('window.mcad'); - expect(adcode).to.contain('window.tcString'); - expect(adcode).to.contain('window.page'); - expect(adcode).to.contain('window.requestPVID'); + + expect(adcode).to.be.equal(serverResponseSingle.body.seatbid[0].bid[0].adm); }); it('should create a correct video bid', function () { @@ -666,6 +721,19 @@ describe('SSPBC adapter', function () { expect(nativeBid).to.have.keys('cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls'); expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers', 'clickTrackers'); }); + + it('should reject responses that are not HTML, VATS/VPAID or native', function () { + let resultIncorrect = spec.interpretResponse(serverResponseIncorrect, requestSingle); + + expect(resultIncorrect.length).to.equal(0); + }); + + it('should response with fledge auction configs', function () { + const { bids, fledgeAuctionConfigs } = spec.interpretResponse(serverResponsePaapi, requestSingle); + + expect(bids.length).to.equal(1); + expect(fledgeAuctionConfigs.length).to.equal(1); + }); }); describe('getUserSyncs', function () { @@ -703,6 +771,25 @@ describe('SSPBC adapter', function () { }); }); + describe('onBidBillable', function () { + it('should generate no notification if bid is undefined', function () { + let notificationPayload = spec.onBidBillable(); + expect(notificationPayload).to.be.undefined; + }); + + it('should generate notification with event name and request/adUnit data, if correct bid is provided. Should also contain site/slot data as arrays.', function () { + const { bids } = prepareTestData(); + let bid = bids[0]; + + let notificationPayload = spec.onBidBillable(bid); + expect(notificationPayload).to.have.property('event').that.equals('bidBillable'); + expect(notificationPayload).to.have.property('requestId').that.equals(bid.bidderRequestId); + expect(notificationPayload).to.have.property('tagid').that.deep.equals([bid.adUnitCode]); + expect(notificationPayload).to.have.property('siteId').that.is.an('array'); + expect(notificationPayload).to.have.property('slotId').that.is.an('array'); + }); + }); + describe('onTimeout', function () { it('should generate no notification if timeout data is undefined / has no bids', function () { let notificationPayloadUndefined = spec.onTimeout(); From c7768055078b82ef911cb0343fafd1f122f6c01e Mon Sep 17 00:00:00 2001 From: Pavlo Kyrylenko Date: Wed, 18 Sep 2024 18:14:06 +0300 Subject: [PATCH 0517/1097] bidders list updated (#12253) Co-authored-by: Pavlo --- modules/anonymisedRtdProvider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/anonymisedRtdProvider.md b/modules/anonymisedRtdProvider.md index 936e5fc7437..526c75d7fb7 100644 --- a/modules/anonymisedRtdProvider.md +++ b/modules/anonymisedRtdProvider.md @@ -24,7 +24,7 @@ Anonymised’s Real-time Data Provider automatically obtains segment IDs from th waitForIt: true, params: { cohortStorageKey: "cohort_ids", - bidders: ["smartadserver", "appnexus"], + bidders: ["appnexus", "onetag", "pubmatic", "smartadserver", ...], segtax: 1000 } } From b2831c5c025aba1f879ff0f7a2be1618546a4350 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Thu, 19 Sep 2024 23:10:14 +1000 Subject: [PATCH 0518/1097] Add canonical into ad request to Adnuntius ad server (#12255) --- modules/adnuntiusBidAdapter.js | 9 ++++++++- test/spec/modules/adnuntiusBidAdapter_spec.js | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index c9275e6bd0b..d017b6a8398 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -275,7 +275,14 @@ export const spec = { networks[network] = networks[network] || {}; networks[network].adUnits = networks[network].adUnits || []; - if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.page; + + const refererInfo = bidderRequest && bidderRequest.refererInfo ? bidderRequest.refererInfo : {}; + if (refererInfo.page) { + networks[network].context = bidderRequest.refererInfo.page; + } + if (refererInfo.canonicalUrl) { + networks[network].canonical = bidderRequest.refererInfo.canonicalUrl; + } const payloadRelatedData = storageTool.getPayloadRelatedData(bid.params.network); if (Object.keys(payloadRelatedData).length > 0) { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index cef63486420..d4802ffd4c0 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -475,7 +475,12 @@ describe('adnuntiusBidAdapter', function () { return 'overridden-value'; }); - const request = spec.buildRequests(bidderRequests, {}); + const request = spec.buildRequests(bidderRequests, { + refererInfo: { + canonicalUrl: 'https://canonical.com/page.html', + page: 'https://canonical.com/something-else.html' + } + }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); const bid = request[0].bid[0] @@ -483,7 +488,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL.replace('format=prebid', 'format=prebid&so=overridden-value')); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}]}'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"context":"https://canonical.com/something-else.html","canonical":"https://canonical.com/page.html"}'); }); it('Test requests with no local storage', function () { From 6d2e37ab8f76f87d3ebdb17a229df2332358209c Mon Sep 17 00:00:00 2001 From: Eyvaz <62054743+eyvazahmadzada@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:44:02 +0200 Subject: [PATCH 0519/1097] IntentIQ Analytics Adapter: fix fpid issue (#12254) * fix fpid issue * fix return issue * fix partner id check return --- modules/intentIqAnalyticsAdapter.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 3f05d91550f..76615730dd5 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -97,7 +97,6 @@ function readData(key) { function initLsValues() { if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; - iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData(FIRST_PARTY_KEY)); let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId'); if (iiqArr && iiqArr.length > 0) iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; if (!iiqArr) iiqArr = []; @@ -112,18 +111,19 @@ function initLsValues() { if (iiqArr && iiqArr.length > 0) { if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) { iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; - iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; } - iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : ''; } } function initReadLsIds() { - if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner == -1) return; try { iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; - let iData = readData(FIRST_PARTY_DATA_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner) + iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData(FIRST_PARTY_KEY)); + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { + iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; + } + let iData = readData(FIRST_PARTY_DATA_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); if (iData) { iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; let pData = JSON.parse(iData); @@ -138,6 +138,8 @@ function initReadLsIds() { function bidWon(args) { if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { initLsValues(); } + if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner == -1) return; + const currentBrowserLowerCase = detectBrowser(); if (iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList?.includes(currentBrowserLowerCase)) { logError('IIQ ANALYTICS -> Browser is in blacklist!'); From 24516d31921dcc51ff76f50ab59b029830d53a51 Mon Sep 17 00:00:00 2001 From: Mikhail Malkov Date: Thu, 19 Sep 2024 17:53:56 +0300 Subject: [PATCH 0520/1097] NextMillennium Bid Adapter : sending a request with several imp objects (#12244) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * NextMillennium: Sending a request with several imp objects. --- modules/nextMillenniumBidAdapter.js | 169 +++++----- .../modules/nextMillenniumBidAdapter_spec.js | 313 ++++++++++++------ 2 files changed, 283 insertions(+), 199 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 5e9be67c6bb..9dbf2e93dc6 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -13,7 +13,6 @@ import { } from '../src/utils.js'; import {getAd} from '../libraries/targetVideoUtils/bidderUtils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import { EVENTS } from '../src/constants.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -21,7 +20,8 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '3.1.0'; +const NM_VERSION = '4.0.0'; +const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; @@ -81,65 +81,46 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const requests = []; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; + const site = getSiteObj(); + const device = getDeviceObj(); - _each(validBidRequests, (bid) => { - window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; - const id = getPlacementId(bid); - const auctionId = bid.auctionId; - const bidId = bid.bidId; - - const site = getSiteObj(); - const device = getDeviceObj(); - const {cur, mediaTypes} = getCurrency(bid); + const postBody = { + id: bidderRequest?.bidderRequestId, + ext: { + next_mil_imps: [], + }, - const postBody = { - id: bidderRequest?.bidderRequestId, - cur, - ext: { - prebid: { - storedrequest: { - id, - }, - }, + device, + site, + imp: [], + }; - nextMillennium: { - nm_version: NM_VERSION, - pbjs_version: getGlobal()?.version || undefined, - refresh_count: window.nmmRefreshCounts[bid.adUnitCode]++, - elOffsets: getBoundingClient(bid), - scrollTop: window.pageYOffset || document.documentElement.scrollTop, - }, - }, + setConsentStrings(postBody, bidderRequest); + setOrtb2Parameters(postBody, bidderRequest?.ortb2); - device, - site, - imp: [], - }; + const urlParameters = parseUrl(getWindowTop().location.href).search; + const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; + setEids(postBody, validBidRequests); + _each(validBidRequests, (bid, i) => { + window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; + const id = getPlacementId(bid); + const {cur, mediaTypes} = getCurrency(bid); + if (i === 0) postBody.cur = cur; postBody.imp.push(getImp(bid, id, mediaTypes)); - setConsentStrings(postBody, bidderRequest); - setOrtb2Parameters(postBody, bidderRequest?.ortb2); - setEids(postBody, bid); - - const urlParameters = parseUrl(getWindowTop().location.href).search; - const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; - const params = bid.params; - - requests.push({ - method: 'POST', - url: isTest ? TEST_ENDPOINT : ENDPOINT, - data: JSON.stringify(postBody), - options: { - contentType: 'text/plain', - withCredentials: true, - }, + postBody.ext.next_mil_imps.push(getExtNextMilImp(bid)); + }); - bidId, - params, - auctionId, - }); + this.getUrlPixelMetric(EVENTS.BID_REQUESTED, validBidRequests); - this.getUrlPixelMetric(EVENTS.BID_REQUESTED, bid); + requests.push({ + method: 'POST', + url: isTest ? TEST_ENDPOINT : ENDPOINT, + data: JSON.stringify(postBody), + options: { + contentType: 'text/plain', + withCredentials: true, + }, }); return requests; @@ -149,16 +130,15 @@ export const spec = { const response = serverResponse.body; const bidResponses = []; + const bids = []; _each(response.seatbid, (resp) => { _each(resp.bid, (bid) => { const requestId = bidRequest.bidId; - const params = bidRequest.params; const {ad, adUrl, vastUrl, vastXml} = getAd(bid); const bidResponse = { requestId, - params, cpm: bid.price, width: bid.w, height: bid.h, @@ -182,11 +162,13 @@ export const spec = { }; bidResponses.push(bidResponse); - - this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bid); }); + + bids.push(resp.bid); }); + this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bids.flat()); + return bidResponses; }, @@ -226,22 +208,26 @@ export const spec = { triggerPixel(url); }, - _getUrlPixelMetric(eventName, bid) { - const bidder = bid.bidder || bid.bidderCode; + _getUrlPixelMetric(eventName, bids) { + if (!Array.isArray(bids)) bids = [bids]; + + const bidder = bids[0]?.bidder || bids[0]?.bidderCode; if (bidder != BIDDER_CODE) return; - let params; - if (bid.params) { - params = Array.isArray(bid.params) ? bid.params : [bid.params]; - } else { - if (Array.isArray(bid.bids)) params = bid.bids.map(bidI => bidI.params); - }; + let params = []; + _each(bids, bid => { + if (bid.params) { + params.push(bid.params); + } else { + if (Array.isArray(bid.bids)) params.push(bid.bids.map(bidI => bidI.params)); + }; + }); if (!params.length) return; const placementIdsArray = []; const groupIdsArray = []; - params.forEach(paramsI => { + params.flat().forEach(paramsI => { if (paramsI.group_id) { groupIdsArray.push(paramsI.group_id); } else { @@ -252,9 +238,7 @@ export const spec = { const placementIds = (placementIdsArray.length && `&placements=${placementIdsArray.join(';')}`) || ''; const groupIds = (groupIdsArray.length && `&groups=${groupIdsArray.join(';')}`) || ''; - if (!(groupIds || placementIds)) { - return; - }; + if (!(groupIds || placementIds)) return; const url = `${REPORT_ENDPOINT}?event=${eventName}&bidder=${bidder}&source=pbjs${groupIds}${placementIds}`; @@ -268,6 +252,21 @@ export const spec = { }, }; +function getExtNextMilImp(bid) { + if (typeof window?.nmmRefreshCounts[bid.adUnitCode] === 'number') ++window.nmmRefreshCounts[bid.adUnitCode]; + const nextMilImp = { + impId: bid.adUnitCode, + nextMillennium: { + nm_version: NM_VERSION, + pbjs_version: PBJS_VERSION, + refresh_count: window?.nmmRefreshCounts[bid.adUnitCode] || 0, + scrollTop: window.pageYOffset || document.documentElement.scrollTop, + }, + }; + + return nextMilImp; +} + export function getImp(bid, id, mediaTypes) { const {banner, video} = mediaTypes; const imp = { @@ -362,10 +361,16 @@ export function setOrtb2Parameters(postBody, ortb2 = {}) { } } -export function setEids(postBody, bid) { - if (!isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; +export function setEids(postBody = {}, bids = []) { + let isFind = false; + _each(bids, bid => { + if (isFind || !isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; - deepSetValue(postBody, 'user.eids', bid.userIdAsEids); + if (bid.userIdAsEids.length) { + deepSetValue(postBody, 'user.eids', bid.userIdAsEids); + isFind = true; + }; + }); } export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gppConsent = {}, type = '') { @@ -411,21 +416,7 @@ function getCurrency(bid = {}) { return {cur, mediaTypes}; } -function getAdEl(bid) { - // best way I could think of to get El, is by matching adUnitCode to google slots... - const slot = window.googletag && window.googletag.pubads && window.googletag.pubads().getSlots().find(slot => slot.getAdUnitPath() === bid.adUnitCode); - const slotElementId = slot && slot.getSlotElementId(); - if (!slotElementId) return null; - return document.querySelector('#' + slotElementId); -} - -function getBoundingClient(bid) { - const el = getAdEl(bid); - if (!el) return {}; - return el.getBoundingClientRect(); -} - -function getPlacementId(bid) { +export function getPlacementId(bid) { const groupId = getBidIdParameter('group_id', bid.params); const placementId = getBidIdParameter('placement_id', bid.params); if (!groupId) return placementId; @@ -433,8 +424,8 @@ function getPlacementId(bid) { let windowTop = getTopWindow(window); let sizes = []; if (bid.mediaTypes) { - if (bid.mediaTypes.banner) sizes = bid.mediaTypes.banner.sizes; - if (bid.mediaTypes.video) sizes = [bid.mediaTypes.video.playerSize]; + if (bid.mediaTypes.banner) sizes = [...bid.mediaTypes.banner.sizes]; + if (bid.mediaTypes.video) sizes.push(bid.mediaTypes.video.playerSize); }; const host = (windowTop && windowTop.location && windowTop.location.host) || ''; diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index ff58671b17b..01b09320055 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -15,6 +15,7 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - banner', data: { id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-banner-1', @@ -42,6 +43,7 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - video', data: { id: '234', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}}, adUnitCode: 'test-video-1', @@ -74,6 +76,7 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - mediaTypes.video is empty', data: { id: '234', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {w: 640, h: 480}}, adUnitCode: 'test-video-2', @@ -98,8 +101,8 @@ describe('nextMillenniumBidAdapterTests', () => { for (let {title, data, expected} of dataTests) { it(title, () => { - const {bid, id, mediaTypes} = data; - const imp = getImp(bid, id, mediaTypes); + const {bid, id, mediaTypes, postBody} = data; + const imp = getImp(bid, id, mediaTypes, postBody); expect(imp).to.deep.equal(expected); }); } @@ -485,9 +488,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: undefined, - }, + }], }, expected: {}, @@ -497,9 +500,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids - array is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: [], - }, + }], }, expected: {}, @@ -509,19 +512,34 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is', data: { postBody: {}, - bid: { - userIdAsEids: [ - { - source: '33across.com', - uids: [{id: 'some-random-id-value', atype: 1}], - }, + bids: [ + { + userIdAsEids: [], + }, - { - source: 'utiq.com', - uids: [{id: 'some-random-id-value', atype: 1}], - }, - ], - }, + { + userIdAsEids: [ + { + source: '33across.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + + { + source: 'utiq.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + + { + userIdAsEids: [ + { + source: 'test.test', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + ], }, expected: { @@ -544,8 +562,8 @@ describe('nextMillenniumBidAdapterTests', () => { for (let { title, data, expected } of dataTests) { it(title, () => { - const { postBody, bid } = data; - setEids(postBody, bid); + const { postBody, bids } = data; + setEids(postBody, bids); expect(postBody).to.deep.equal(expected); }); } @@ -606,110 +624,144 @@ describe('nextMillenniumBidAdapterTests', () => { } }; - const bidRequestDataGI = [ - { - adUnitCode: 'test-banner-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - banner: { - sizes: [[300, 250]] + const bidRequestDataGI = getBidRequestDataGI(); + function getBidRequestDataGI(adUnitCodes = ['test-banner-gi', 'test-banner-gi', 'test-video-gi']) { + return [ + { + adUnitCode: adUnitCodes[0], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + + sizes: [[300, 250]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - sizes: [[300, 250]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, + { + adUnitCode: adUnitCodes[1], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, - { - adUnitCode: 'test-banner-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 300]] + sizes: [[300, 250], [300, 300]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - sizes: [[300, 250], [300, 300]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, + { + adUnitCode: adUnitCodes[2], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + video: { + playerSize: [640, 480], + } + }, - { - adUnitCode: 'test-video-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - video: { - playerSize: [640, 480], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, - ]; + ]; + } it('validate_generated_params', function() { const request = spec.buildRequests(bidRequestData, {bidderRequestId: 'mock-uuid'}); - expect(request[0].bidId).to.equal('bid1234'); expect(JSON.parse(request[0].data).id).to.exist; }); - it('use parameters group_id', function() { + it('check parameters group_id or placement_id', function() { for (let test of bidRequestDataGI) { const request = spec.buildRequests([test]); const requestData = JSON.parse(request[0].data); - const storeRequestId = requestData.ext.prebid.storedrequest.id; - const templateRE = /^g[1-9]\d*;(?:[1-9]\d*x[1-9]\d*\|)*[1-9]\d*x[1-9]\d*;/; - expect(templateRE.test(storeRequestId)).to.be.true; + const storeRequestId = (requestData.imp[0].ext.prebid.storedrequest.id || ''); + expect(storeRequestId.length).to.be.not.equal(0); + + const srId = storeRequestId.split(';'); + const isGroupId = (/^g[1-9]\d*/).test(srId[0]); + if (isGroupId) { + expect(srId.length).to.be.equal(3); + expect((/^g[1-9]\d*/).test(srId[0])).to.be.true; + const sizes = srId[1].split('|'); + for (let size of sizes) { + if (!(/^[1-9]\d*[xX,][1-9]\d*$/).test(size)) { + expect(storeRequestId).to.be.equal(''); + } + + expect((/^[1-9]\d*[xX,][1-9]\d*$/).test(size)).to.be.true; + } + } else { + expect(srId.length).to.be.equal(1); + expect((/^[1-9]\d*/).test(srId[0])).to.be.true; + }; }; }); - it('Check if refresh_count param is incremented', function() { - const request = spec.buildRequests(bidRequestData); - expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(1); + describe('Check ext.next_mil_imps', function() { + const expectedNextMilImps = [ + { + impId: 'nmi-test-0', + nextMillennium: {refresh_count: 1}, + }, + + { + impId: 'nmi-test-1', + nextMillennium: {refresh_count: 1}, + }, + + { + impId: 'nmi-test-2', + nextMillennium: {refresh_count: 1}, + }, + ]; + + const dataForRequest = getBidRequestDataGI(expectedNextMilImps.map(el => el.impId)); + for (let j = 0; j < 2; j++) { + const request = spec.buildRequests(dataForRequest); + const bidRequest = JSON.parse(request[0].data); + for (let i = 0; i < bidRequest.ext.next_mil_imps.length; i++) { + it(`test - ${j * i + 1}`, () => { + const nextMilImp = bidRequest.ext.next_mil_imps[i]; + expect(nextMilImp.impId).to.deep.equal(expectedNextMilImps[i].impId); + expect(nextMilImp.nextMillennium.refresh_count).to.deep.equal(expectedNextMilImps[i].nextMillennium.refresh_count + j); + }) + }; + }; }); it('Check if domain was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).site.domain).to.exist - }) - - it('Check if elOffsets was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).ext.nextMillennium.elOffsets).to.be.an('object') - }) + const request = spec.buildRequests(bidRequestData); + expect(JSON.parse(request[0].data).site.domain).to.exist; + }); it('Check if imp object was added', function() { const request = spec.buildRequests(bidRequestData) expect(JSON.parse(request[0].data).imp).to.be.an('array') }); - it('Check if imp prebid stored id is correct', function() { - const request = spec.buildRequests(bidRequestData) - const requestData = JSON.parse(request[0].data); - const storedReqId = requestData.ext.prebid.storedrequest.id; - expect(requestData.imp[0].ext.prebid.storedrequest.id).to.equal(storedReqId) - }); - it('validate_response_params', function() { let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); expect(bids).to.have.lengthOf(1); @@ -806,11 +858,12 @@ describe('nextMillenniumBidAdapterTests', () => { expect(bid.currency).to.equal('USD'); }); - it('Check function of getting URL for sending statistics data', function() { + describe('function spec._getUrlPixelMetric', function() { const dataForTests = [ { + title: 'Check function of getting URL for sending statistics data - 1', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'appnexus', bids: [{bidder: 'appnexus', params: {}}], }, @@ -819,8 +872,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 2', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'appnexus', bids: [{bidder: 'appnexus', params: {placement_id: '807'}}], }, @@ -829,8 +883,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 3', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [{bidder: 'nextMillennium', params: {placement_id: '807'}}], }, @@ -839,8 +894,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 4', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [ {bidder: 'nextMillennium', params: {placement_id: '807'}}, @@ -852,8 +908,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 5', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [{bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}], }, @@ -862,8 +919,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 6', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [ {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, @@ -876,8 +934,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 7', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'appnexus', }, @@ -885,8 +944,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 8', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'nextMillennium', params: {placement_id: '807'}, }, @@ -895,8 +955,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 9', eventName: 'noBid', - bid: { + bids: { bidder: 'appnexus', }, @@ -904,8 +965,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 10', eventName: 'noBid', - bid: { + bids: { bidder: 'nextMillennium', params: {placement_id: '807'}, }, @@ -914,8 +976,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 11', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'appnexus', }, @@ -923,19 +986,49 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 12', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'nextMillennium', params: {placement_id: '807'}, }, expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidTimeout&bidder=nextMillennium&source=pbjs&placements=807', }, + + { + title: 'Check function of getting URL for sending statistics data - 13', + eventName: 'bidRequested', + bids: [ + { + bidderCode: 'nextMillennium', + bids: [ + {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, + {bidder: 'nextMillennium', params: {group_id: '456'}}, + {bidder: 'nextMillennium', params: {placement_id: '222'}}, + ], + }, + + { + bidderCode: 'nextMillennium', + params: {group_id: '7777'}, + }, + + { + bidderCode: 'nextMillennium', + params: {placement_id: '8888'}, + }, + ], + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&groups=123;456;7777&placements=222;8888', + }, ]; - for (let {eventName, bid, expected} of dataForTests) { - const url = spec._getUrlPixelMetric(eventName, bid); - expect(url).to.equal(expected); + for (let {title, eventName, bids, expected} of dataForTests) { + it(title, () => { + const url = spec._getUrlPixelMetric(eventName, bids); + expect(url).to.equal(expected); + }); }; - }) + }); }); From 38ecf2a4d5c3a18074d4d5d373ca289b86ffe382 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 19 Sep 2024 08:10:29 -0700 Subject: [PATCH 0521/1097] consentManagementGpp: pause auctions when user is reviewing / updating consent preferences (#12224) --- modules/consentManagementGpp.js | 19 +++- .../spec/modules/consentManagementGpp_spec.js | 92 +++++++++++-------- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 013650de20a..2d6c6583257 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -10,7 +10,7 @@ import {gppDataHandler} from '../src/adapterManager.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; -import {GreedyPromise} from '../src/utils/promise.js'; +import {GreedyPromise, defer} from '../src/utils/promise.js'; import {buildActivityParams} from '../src/activities/params.js'; import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; @@ -72,7 +72,7 @@ export class GPPClient { constructor(cmp) { this.cmp = cmp; - [this.#resolve, this.#reject] = [0, 1].map(slot => (result) => { + [this.#resolve, this.#reject] = ['resolve', 'reject'].map(slot => (result) => { while (this.#pending.length) { this.#pending.pop()[slot](result); } @@ -103,6 +103,15 @@ export class GPPClient { } else if (this.isCMPReady(event?.pingData || {}) && ['sectionChange', 'signalStatus'].includes(event?.eventName)) { this.#resolve(this.updateConsent(event.pingData)); } + // NOTE: according to https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md, + // > [signalStatus] Event is called whenever the display status of the CMP changes (e.g. the CMP shows the consent layer). + // + // however, from real world testing, at least some CMPs only trigger 'cmpDisplayStatus' + // other CMPs may do something else yet; here we just look for 'signalStatus: not ready' on any event + // to decide if consent data is likely to change + if (consentData != null && event?.pingData != null && !this.isCMPReady(event.pingData)) { + consentData = null; + } } }); } @@ -136,9 +145,9 @@ export class GPPClient { * @returns {Promise<{}>} */ nextUpdate() { - return new GreedyPromise((resolve, reject) => { - this.#pending.push([resolve, reject]); - }); + const def = defer(); + this.#pending.push(def); + return def.promise; } /** diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js index fb27bf4818c..25114ac6602 100644 --- a/test/spec/modules/consentManagementGpp_spec.js +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -10,8 +10,6 @@ import {gppDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; import 'src/prebid.js'; -import {MODE_CALLBACK, MODE_MIXED} from '../../../libraries/cmp/cmpClient.js'; -import {GreedyPromise} from '../../../src/utils/promise.js'; let expect = require('chai').expect; @@ -348,8 +346,8 @@ describe('consentManagementGpp', function () { sinon.assert.match(gppDataHandler.getConsentData(), gppData2); }); }); - }) - }) + }); + }); }); }); @@ -496,56 +494,76 @@ describe('consentManagementGpp', function () { }); }); - describe('already known consentData:', function () { - let cmpStub = sinon.stub(); - - function mockCMP(pingData) { - return function (command, callback) { + describe('on CMP sectionChange events', () => { + let pingData, triggerCMPEvent; + beforeEach(() => { + pingData = { + applicableSections: [7], + gppString: 'xyz', + }; + triggerCMPEvent = null; + window.__gpp = sinon.stub().callsFake(function (command, callback) { switch (command) { case 'addEventListener': // eslint-disable-next-line standard/no-callback-literal - callback({eventName: 'sectionChange', pingData}) + triggerCMPEvent = (event, payload = {}) => callback({eventName: event, pingData: {...pingData, ...payload}}) break; case 'ping': callback(pingData) break; + default: + throw new Error('unexpected __gpp invocation') } - } - } - - beforeEach(function () { - didHookReturn = false; - window.__gpp = function () {}; + }); + setConsentConfig(goodConfig); }); - afterEach(function () { - config.resetConfig(); - cmpStub.restore(); + afterEach(() => { delete window.__gpp; resetConsentData(); }); - it('should bypass CMP and simply use previously stored consentData', function () { - let testConsentData = { - applicableSections: [7], - gppString: 'xyz', - }; - - cmpStub = sinon.stub(window, '__gpp').callsFake(mockCMP({...testConsentData, signalStatus: 'ready'})); - setConsentConfig(goodConfig); - requestBidsHook(() => {}, {}); - cmpStub.reset(); - + function startHook() { + let hookRan = false; requestBidsHook(() => { - didHookReturn = true; + hookRan = true; }, {}); - let consent = gppDataHandler.getConsentData(); + return () => new Promise((resolve) => setTimeout(resolve(hookRan), 5)); + } - expect(didHookReturn).to.be.true; - expect(consent.gppString).to.equal(testConsentData.gppString); - expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); - sinon.assert.notCalled(cmpStub); + it('should wait for signalStatus: ready', async () => { + const didHookRun = startHook(); + expect(await didHookRun()).to.be.false; + triggerCMPEvent('sectionChange', {signalStatus: 'not ready'}); + expect(await didHookRun()).to.be.false; + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + expect(await didHookRun()).to.be.true; + expect(gppDataHandler.getConsentData().gppString).to.eql('xyz'); }); - }); + + it('should re-use GPP data once ready', async () => { + let didHookRun = startHook(); + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await didHookRun(); + window.__gpp.reset(); + didHookRun = startHook(); + expect(await didHookRun()).to.be.true; + sinon.assert.notCalled(window.__gpp); + }); + + it('after signalStatus: ready, should wait again for signalStatus: ready', async () => { + let didHookRun = startHook(); + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await didHookRun(); + for (let run of ['first', 'second']) { + triggerCMPEvent('cmpDisplayStatus', {signalStatus: 'not ready'}); + didHookRun = startHook(); + expect(await didHookRun()).to.be.false; + triggerCMPEvent('sectionChange', {signalStatus: 'ready', gppString: run}); + expect(await didHookRun()).to.be.true; + expect(gppDataHandler.getConsentData().gppString).to.eql(run); + } + }); + }) }); }); From 9a06e306153f93ec1dd289630d1c1f4608de8d97 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 19 Sep 2024 20:44:01 +0200 Subject: [PATCH 0522/1097] Core: Adding useBaseGvlid to aliasBidAdapter (#12247) * Adding useBaseGvlid to aliasBidAdapter * refactor --------- Co-authored-by: Marcin Komorski --- src/adapterManager.js | 6 ++++-- test/spec/unit/core/adapterManager_spec.js | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/adapterManager.js b/src/adapterManager.js index a571ba62ddc..63ecfc280be 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -553,11 +553,13 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { newAdapter = new bidAdapter.constructor(); newAdapter.setBidderCode(alias); } else { + const { useBaseGvlid = false } = options || {}; let spec = bidAdapter.getSpec(); - let gvlid = options && options.gvlid; - if (spec.gvlid != null && gvlid == null) { + const gvlid = useBaseGvlid ? spec.gvlid : options?.gvlid; + if (gvlid == null && spec.gvlid != null) { logWarn(`Alias '${alias}' will NOT re-use the GVL ID of the original adapter ('${spec.code}', gvlid: ${spec.gvlid}). Functionality that requires TCF consent may not work as expected.`) } + let skipPbsAliasing = options && options.skipPbsAliasing; newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid, skipPbsAliasing })); _aliasRegistry[alias] = bidderCode; diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index a68634501ac..3974cfde68f 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1630,6 +1630,15 @@ describe('adapterManager tests', function () { expect(adapterManager.videoAdapters).to.include(alias); } }); + + it('should use gvlid of original adapter when option set', () => { + const gvlid = 'origvlid'; + let thisSpec = Object.assign(spec, { gvlid }); + registerBidder(thisSpec); + const alias = 'bidderWithGvlid'; + adapterManager.aliasBidAdapter(CODE, alias, {useBaseGvlid: true}); + expect(adapterManager.bidderRegistry[alias].getSpec()?.gvlid).to.deep.eql(gvlid); + }) }); describe('special case for s2s-only bidders', function () { From 16abe421bc3a8194c1f20777c34cc35bb8d9a544 Mon Sep 17 00:00:00 2001 From: yagovelazquezfreestar <143502852+yagovelazquezfreestar@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:00:52 -0300 Subject: [PATCH 0523/1097] fix: identity link throwing unhandled promises (#12249) --- modules/identityLinkIdSystem.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 82aa2303e1c..a9d88110308 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -132,6 +132,8 @@ function getEnvelope(url, callback, configParams) { utils.logInfo('identityLink: A 3P retrieval is attempted!'); setEnvelopeSource(false); ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + } else { + callback() } } From 3f9ef255f46f755a8def8606e247ad698958d7fb Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 19 Sep 2024 20:50:16 +0000 Subject: [PATCH 0524/1097] Prebid 9.14.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4246bbc5fd9..396c2e25e50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.14.0-pre", + "version": "9.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.14.0-pre", + "version": "9.14.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index e06fb2f53ef..0d4acacf09d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.14.0-pre", + "version": "9.14.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 8b1d121b3ee9b6f55f600b423d897c67cab97c59 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 19 Sep 2024 20:50:16 +0000 Subject: [PATCH 0525/1097] Increment version to 9.15.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 396c2e25e50..5fcb84d7454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.14.0", + "version": "9.15.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.14.0", + "version": "9.15.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.24.6", diff --git a/package.json b/package.json index 0d4acacf09d..03f09a0f161 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.14.0", + "version": "9.15.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 3bbf6144c31490d23aa103530ff89e9713fe39d7 Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Sun, 22 Sep 2024 20:21:13 +0300 Subject: [PATCH 0526/1097] Yandex Id System: refactoring (#12219) --- modules/userId/index.js | 6 +-- modules/yandexBidAdapter.js | 8 +++- modules/yandexIdSystem.js | 60 +++++++++++++++--------- src/storageManager.js | 6 ++- test/spec/modules/yandexIdSystem_spec.js | 4 +- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 2ac23011417..b274afda397 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -22,8 +22,8 @@ * It's permissible to return neither, one, or both fields. * @callback getId * @param {SubmoduleConfig} config - * @param {ConsentData|undefined} consentData - * @param {Object|undefined} cacheIdObj + * @param {ConsentData|undefined} [consentData] + * @param {Object|undefined} [cacheIdObj] * @returns {IdResponse|undefined} A response object that contains id and/or callback. */ @@ -43,7 +43,7 @@ * Decode a stored value for passing to bid requests * @callback decode * @param {Object|string} value - * @param {SubmoduleConfig|undefined} config + * @param {SubmoduleConfig|undefined} [config] * @returns {Object|undefined} */ diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index e694dbcfe03..97bd793d633 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -2,7 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel } from '../src/utils.js'; +import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel, logInfo } from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -181,7 +181,7 @@ export const spec = { } const queryParamsString = formatQS(queryParams); - return { + const request = { method: 'POST', url: BIDDER_URL + `/${pageId}?${queryParamsString}`, data, @@ -190,6 +190,10 @@ export const spec = { }, bidRequest, }; + + logInfo('ServerRequest', request); + + return request; }); }, diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js index 3aa90b0f490..e21642abf0b 100644 --- a/modules/yandexIdSystem.js +++ b/modules/yandexIdSystem.js @@ -13,7 +13,7 @@ import { MODULE_TYPE_UID } from '../src/activities/modules.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import { getStorageManager, STORAGE_TYPE_COOKIES } from '../src/storageManager.js'; import { logError, logInfo } from '../src/utils.js'; // .com suffix is just a convention for naming the bidder eids @@ -23,7 +23,7 @@ export const YANDEX_ID_KEY = 'yandexId'; export const YANDEX_EXT_COOKIE_NAMES = ['_ym_fa']; export const BIDDER_CODE = 'yandex'; export const YANDEX_USER_ID_KEY = '_ym_uid'; -export const YANDEX_COOKIE_STORAGE_TYPE = 'cookie'; +export const YANDEX_STORAGE_TYPE = STORAGE_TYPE_COOKIES; export const YANDEX_MIN_EXPIRE_DAYS = 30; export const PREBID_STORAGE = getStorageManager({ @@ -40,7 +40,7 @@ export const yandexIdSubmodule = { * @param {string} value */ decode(value) { - logInfo('decoded value yandexId', value); + logInfo(`Decoded ${YANDEX_ID_KEY}`, value); return { [YANDEX_ID_KEY]: value }; }, @@ -50,18 +50,24 @@ export const yandexIdSubmodule = { } if (storedId) { + logInfo('Got storedId', storedId); return { id: storedId }; } return { - id: new YandexUidGenerator().generateUid(), + id: new YandexIdGenerator().generate(), }; }, eids: { [YANDEX_ID_KEY]: { source: BIDDER_EID_KEY, + /** + * Agent Type 1 means that it is an ID + * which is tied to a specific web browser or device (cookie-based, probabilistic, or other). + * @see https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/main/AdCOM%20v1.0%20FINAL.md#list--agent-types- + */ atype: 1, getUidExt() { if (PREBID_STORAGE.cookiesAreEnabled()) { @@ -85,22 +91,22 @@ function checkConfigHasErrorsAndReport(submoduleConfig) { const READABLE_MODULE_NAME = 'Yandex ID module'; if (submoduleConfig.storage == null) { - logError(`Misconfigured ${READABLE_MODULE_NAME}. "storage" is required.`) + logError(`Misconfigured ${READABLE_MODULE_NAME}. "storage" is required.`); return true; } - if (submoduleConfig.storage?.name !== YANDEX_USER_ID_KEY) { - logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is required to be "${YANDEX_USER_ID_KEY}"`); + if (submoduleConfig.storage.name !== YANDEX_USER_ID_KEY) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is expected to be "${YANDEX_USER_ID_KEY}", actual is "${submoduleConfig.storage.name}"`); error = true; } - if (submoduleConfig.storage?.type !== YANDEX_COOKIE_STORAGE_TYPE) { - logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is required to be "${YANDEX_COOKIE_STORAGE_TYPE}"`); + if (submoduleConfig.storage.type !== YANDEX_STORAGE_TYPE) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is expected to be "${YANDEX_STORAGE_TYPE}", actual is "${submoduleConfig.storage.type}"`); error = true; } - if ((submoduleConfig.storage?.expires ?? 0) < YANDEX_MIN_EXPIRE_DAYS) { - logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.expires" is required to be not less than "${YANDEX_MIN_EXPIRE_DAYS}"`); + if ((submoduleConfig.storage.expires ?? 0) < YANDEX_MIN_EXPIRE_DAYS) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.expires" is expected not to be less than "${YANDEX_MIN_EXPIRE_DAYS}", actual is "${submoduleConfig.storage.expires}"`); error = true; } @@ -111,7 +117,22 @@ function checkConfigHasErrorsAndReport(submoduleConfig) { * Yandex-specific generator for uid. Needs to be compatible with Yandex Metrica tag. * @see https://github.com/yandex/metrica-tag/blob/main/src/utils/uid/uid.ts#L51 */ -class YandexUidGenerator { +class YandexIdGenerator { + generate() { + const yandexId = [ + this._getCurrentSecTimestamp(), + this._getRandomInteger(1000000, 999999999), + ].join(''); + + logInfo(`Generated ${YANDEX_ID_KEY}`, yandexId); + + return yandexId; + } + + _getCurrentSecTimestamp() { + return Math.round(Date.now() / 1000); + } + /** * @param {number} min * @param {number} max @@ -119,20 +140,13 @@ class YandexUidGenerator { _getRandomInteger(min, max) { const generateRandom = this._getRandomGenerator(); + /** + * Needs to be compatible with Yandex Metrica `getRandom` function. + * @see https://github.com/yandex/metrica-tag/blob/main/src/utils/number/random.ts#L12 + */ return Math.floor(generateRandom() * (max - min)) + min; } - _getCurrentSecTimestamp() { - return Math.round(Date.now() / 1000); - } - - generateUid() { - return [ - this._getCurrentSecTimestamp(), - this._getRandomInteger(1000000, 999999999), - ].join(''); - } - _getRandomGenerator() { if (window.crypto) { return () => { diff --git a/src/storageManager.js b/src/storageManager.js index 493c44f056e..0c0d29dbee4 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -58,6 +58,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is * If not specified, defaults to the host portion of the current document location. * If a domain is specified, subdomains are always included. * Domain must match the domain of the JavaScript origin. Setting cookies to foreign domains will be silently ignored. + * @param {function} [done] */ const setCookie = function (key, value, expires, sameSite, domain, done) { let cb = function (result) { @@ -75,6 +76,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is /** * @param {string} name + * @param {function} [done] * @returns {(string|null)} */ const getCookie = function(name, done) { @@ -89,6 +91,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is }; /** + * @param {function} [done] * @returns {boolean} */ const cookiesAreEnabled = function (done) { @@ -170,7 +173,8 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is * Returns all cookie values from the jar whose names contain the `keyLike` * Needs to exist in `utils.js` as it follows the StorageHandler interface defined in live-connect-js. If that module were to be removed, this function can go as well. * @param {string} keyLike - * @return {[]} + * @param {function} [done] + * @returns {string[]} */ const findSimilarCookies = function(keyLike, done) { let cb = function (result) { diff --git a/test/spec/modules/yandexIdSystem_spec.js b/test/spec/modules/yandexIdSystem_spec.js index 519b6c90759..593ce0841d4 100644 --- a/test/spec/modules/yandexIdSystem_spec.js +++ b/test/spec/modules/yandexIdSystem_spec.js @@ -6,7 +6,7 @@ import { YANDEX_EXT_COOKIE_NAMES, BIDDER_CODE, YANDEX_USER_ID_KEY, - YANDEX_COOKIE_STORAGE_TYPE, + YANDEX_STORAGE_TYPE, YANDEX_MIN_EXPIRE_DAYS, PREBID_STORAGE, yandexIdSubmodule, @@ -30,7 +30,7 @@ const CORRECT_SUBMODULE_CONFIG = { storage: { expires: YANDEX_MIN_EXPIRE_DAYS, name: YANDEX_USER_ID_KEY, - type: YANDEX_COOKIE_STORAGE_TYPE, + type: YANDEX_STORAGE_TYPE, refreshInSeconds: undefined, }, params: undefined, From 20c189885eb594e0fddeff7298e037e2d97d247d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Baudisch?= Date: Sun, 22 Sep 2024 19:25:39 +0200 Subject: [PATCH 0527/1097] Dependency updates to reduce vulnerability (#12259) * packages update to remove vulnerabilities * wdio.shared.conf.js: update mocha compiler to new babel-register module --- package-lock.json | 8770 ++++++++++++++----------------------------- package.json | 12 +- wdio.shared.conf.js | 2 +- 3 files changed, 2798 insertions(+), 5986 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5fcb84d7454..b1dbe33501e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "9.15.0-pre", "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.24.6", + "@babel/core": "^7.25.2", "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.18.9", @@ -26,6 +26,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", + "@babel/register": "^7.24.6", "@wdio/browserstack-service": "^9.0.5", "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^8.29.0", @@ -36,7 +37,6 @@ "assert": "^2.0.0", "babel-loader": "^8.0.5", "babel-plugin-istanbul": "^6.1.1", - "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -45,7 +45,7 @@ "es5-shim": "^4.5.14", "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.20.2", + "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsdoc": "^48.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", @@ -89,7 +89,7 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", - "mocha": "^10.0.0", + "mocha": "^10.7.3", "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", @@ -100,9 +100,9 @@ "url-parse": "^1.0.5", "video.js": "^7.17.0", "videojs-contrib-ads": "^6.9.0", - "videojs-ima": "^1.11.0", + "videojs-ima": "^2.3.0", "videojs-playlist": "^5.0.0", - "webdriverio": "^7.6.1", + "webdriverio": "^9.0.9", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.0", @@ -141,28 +141,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -196,11 +196,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -233,13 +233,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -359,15 +359,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -463,9 +462,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } @@ -479,9 +478,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "engines": { "node": ">=6.9.0" } @@ -501,12 +500,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -527,9 +526,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1674,6 +1676,147 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/register": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", @@ -1691,31 +1834,28 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1724,11 +1864,11 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -3066,6 +3206,12 @@ "node": ">=12" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -3120,9 +3266,9 @@ } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "node_modules/@socket.io/component-emitter": { @@ -3137,24 +3283,6 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -3213,16 +3341,6 @@ "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "node_modules/@types/hast": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", @@ -3232,12 +3350,6 @@ "@types/unist": "^2" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3274,15 +3386,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -3292,12 +3395,6 @@ "@types/unist": "^2" } }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "node_modules/@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -3340,15 +3437,6 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", @@ -3373,12 +3461,6 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true }, - "node_modules/@types/ua-parser-js": { - "version": "0.7.39", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", - "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", - "dev": true - }, "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -3722,48 +3804,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "node_modules/@wdio/browserstack-service/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3773,39 +3813,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@wdio/browserstack-service/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -3818,35 +3825,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/browserstack-service/node_modules/deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -3877,18 +3855,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/browserstack-service/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -3950,22 +3916,6 @@ "node": ">=18" } }, - "node_modules/@wdio/browserstack-service/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@wdio/browserstack-service/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -3978,30 +3928,6 @@ "node": ">=10" } }, - "node_modules/@wdio/browserstack-service/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/@wdio/browserstack-service/node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -4016,18 +3942,6 @@ "bare-path": "^2.1.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/browserstack-service/node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -4128,20 +4042,6 @@ "node": ">=12" } }, - "node_modules/@wdio/browserstack-service/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/cli": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.0.5.tgz", @@ -4266,60 +4166,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/archiver-utils/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "node_modules/@wdio/cli/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -4329,39 +4175,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/cli/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@wdio/cli/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@wdio/cli/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -4374,47 +4187,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/compress-commons/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/cli/node_modules/deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -4433,9 +4205,9 @@ "peer": true }, "node_modules/@wdio/cli/node_modules/execa": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.1.tgz", - "integrity": "sha512-gdhefCCNy/8tpH/2+ajP9IQc14vXchNdd0weyzSJEFURhRMGncQ+zKFxwjAufIewPEJm9BPOaJnvg2UtlH2gPQ==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.0.tgz", + "integrity": "sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==", "dev": true, "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", @@ -4445,7 +4217,7 @@ "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", - "npm-run-path": "^5.2.0", + "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", @@ -4524,15 +4296,16 @@ } }, "node_modules/@wdio/cli/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "dependencies": { - "path-key": "^4.0.0" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4638,22 +4411,6 @@ "node": ">=18" } }, - "node_modules/@wdio/cli/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@wdio/cli/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -4666,21 +4423,6 @@ "node": ">=10" } }, - "node_modules/@wdio/cli/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/cli/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4693,15 +4435,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/@wdio/cli/node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -4716,18 +4449,6 @@ "bare-path": "^2.1.0" } }, - "node_modules/@wdio/cli/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/cli/node_modules/webdriverio": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", @@ -4815,20 +4536,6 @@ "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/concise-reporter": { "version": "8.38.2", "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.38.2.tgz", @@ -5183,51 +4890,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "optional": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "optional": true, - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "optional": true - }, "node_modules/@wdio/globals/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -5238,41 +4900,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/globals/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@wdio/globals/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@wdio/globals/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -5286,37 +4913,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/globals/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "optional": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/globals/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "optional": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/globals/node_modules/deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -5381,19 +4977,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/globals/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -5458,23 +5041,6 @@ "node": ">=18" } }, - "node_modules/@wdio/globals/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@wdio/globals/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -5488,32 +5054,6 @@ "node": ">=10" } }, - "node_modules/@wdio/globals/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "optional": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/globals/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/@wdio/globals/node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -5529,19 +5069,6 @@ "bare-path": "^2.1.0" } }, - "node_modules/@wdio/globals/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/globals/node_modules/webdriverio": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", @@ -5631,21 +5158,6 @@ "node": ">=12" } }, - "node_modules/@wdio/globals/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "optional": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/local-runner": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.0.5.tgz", @@ -5903,48 +5415,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "node_modules/@wdio/runner/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -5954,39 +5424,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/runner/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@wdio/runner/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@wdio/runner/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -5999,35 +5436,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/runner/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/runner/node_modules/deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -6089,18 +5497,6 @@ "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/runner/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -6186,22 +5582,6 @@ "node": ">=18" } }, - "node_modules/@wdio/runner/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@wdio/runner/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -6214,30 +5594,6 @@ "node": ">=10" } }, - "node_modules/@wdio/runner/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/runner/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/@wdio/runner/node_modules/tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -6252,18 +5608,6 @@ "bare-path": "^2.1.0" } }, - "node_modules/@wdio/runner/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/runner/node_modules/webdriverio": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", @@ -6351,20 +5695,6 @@ "node": ">=12" } }, - "node_modules/@wdio/runner/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/spec-reporter": { "version": "8.38.2", "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.38.2.tgz", @@ -6840,99 +6170,164 @@ } }, "node_modules/archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "dependencies": { - "archiver-utils": "^2.1.0", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "dependencies": { - "glob": "^7.1.4", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", + "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, - "node_modules/archiver-utils/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "engines": { - "node": "*" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/archiver/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, "engines": { - "node": ">= 6" + "node": ">=8.0.0" } }, - "node_modules/archiver/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/archy": { @@ -7162,6 +6557,15 @@ "node": ">=0.10.0" } }, + "node_modules/array-sort/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", @@ -7466,170 +6870,6 @@ "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true - }, - "node_modules/babel-code-frame/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "node_modules/babel-core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/babel-core/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-core/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-core/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "dependencies": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "node_modules/babel-generator/node_modules/jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, "node_modules/babel-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", @@ -7649,15 +6889,6 @@ "webpack": ">=2" } }, - "node_modules/babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -7710,137 +6941,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==", - "dev": true, - "dependencies": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "node_modules/babel-register/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "node_modules/babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-traverse/node_modules/globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-traverse/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "node_modules/babel-types/node_modules/to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true, - "bin": { - "babylon": "bin/babylon.js" - } - }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -8098,31 +7198,6 @@ "file-uri-to-path": "1.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -8141,9 +7216,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -8153,7 +7228,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -8744,42 +7819,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chrome-launcher/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -8996,25 +8035,30 @@ "node": ">= 0.10" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "dependencies": { - "mimic-response": "^1.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6" } }, - "node_modules/clone-response/node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, "node_modules/clone-stats": { @@ -9154,32 +8198,80 @@ } }, "node_modules/compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/compress-commons/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/compress-commons/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/concat-map": { @@ -9455,59 +8547,65 @@ } }, "node_modules/crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "node-fetch": "2.6.7" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/crc32-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "safe-buffer": "~5.2.0" } }, "node_modules/cross-spawn": { @@ -9808,33 +8906,6 @@ "node": ">=0.10" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -9885,15 +8956,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deepmerge-ts": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", @@ -9915,6 +8977,15 @@ "node": ">=0.10.0" } }, + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-resolution": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", @@ -9947,15 +9018,6 @@ "node": ">=0.8" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -10113,18 +9175,6 @@ "node": ">=0.10.0" } }, - "node_modules/detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", - "dev": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -10134,591 +9184,13 @@ "node": ">=0.10.0" } }, - "node_modules/devtools": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.35.0.tgz", - "integrity": "sha512-7HMZMcJSCK/PaBCWVs4n4ZhtBNdUQj10iPwXvj/JDkqPreEXN/XW9GJAoMuLPFmCEKfxe+LrIbgs8ocGJ6rp/A==", - "dev": true, - "dependencies": { - "@types/node": "^18.0.0", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/devtools-protocol": { "version": "0.0.1260888", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz", "integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==", - "dev": true - }, - "node_modules/devtools/node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/devtools/node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/devtools/node_modules/@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/devtools/node_modules/@types/which": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", - "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", - "dev": true - }, - "node_modules/devtools/node_modules/@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", - "dev": true, - "dependencies": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/devtools/node_modules/@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/devtools/node_modules/@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/devtools/node_modules/@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", - "dev": true, - "dependencies": { - "@types/node": "^18.0.0", - "got": "^11.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "^4.6.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/devtools/node_modules/@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", - "dev": true, - "dependencies": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/devtools/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/devtools/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/devtools/node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/devtools/node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/devtools/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/devtools/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/devtools/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.948846", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", - "integrity": "sha512-5fGyt9xmMqUl2VI7+rnUkKCiAQIpLns8sfQtTENy5L70ktbNw0Z3TFJ1JoFNYdx/jffz4YXU45VF75wKZD7sZQ==", - "dev": true - }, - "node_modules/devtools/node_modules/edge-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", - "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", - "dev": true, - "dependencies": { - "@types/which": "^1.3.2", - "which": "^2.0.2" - } - }, - "node_modules/devtools/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/devtools/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/devtools/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/devtools/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/devtools/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/devtools/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/devtools/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/devtools/node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/devtools/node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/puppeteer-core": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.1.3.tgz", - "integrity": "sha512-96pzvVBzq5lUGt3L/QrIH3mxn3NfZylHeusNhq06xBAHPI0Upc0SC/9u7tXjL0oRnmcExeVRJivr1lj7Ah/yDQ==", - "dev": true, - "dependencies": { - "debug": "4.3.2", - "devtools-protocol": "0.0.948846", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.7", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.2.3" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/devtools/node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/devtools/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/devtools/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/devtools/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } + "optional": true, + "peer": true }, "node_modules/di": { "version": "0.0.1", @@ -11435,6 +9907,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, "engines": { "node": ">= 0.8" } @@ -12077,9 +10550,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -12122,26 +10595,27 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", "tsconfig-paths": "^3.15.0" }, @@ -12864,36 +11338,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -12912,11 +11386,66 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -13288,12 +11817,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -13312,6 +11841,14 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -13362,6 +11899,173 @@ "node": ">= 0.10" } }, + "node_modules/findup-sync/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/findup-sync/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fined": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", @@ -13610,12 +12314,6 @@ "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-extra": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", @@ -14254,6 +12952,15 @@ "node": ">=0.10.0" } }, + "node_modules/glob-watcher/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/glob-watcher/node_modules/binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -14288,7 +12995,6 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", "dev": true, "dependencies": { "anymatch": "^2.0.0", @@ -14338,7 +13044,7 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", "dev": true, "hasInstallScript": true, "optional": true, @@ -14414,7 +13120,7 @@ "node": ">=0.10.0" } }, - "node_modules/glob-watcher/node_modules/kind-of": { + "node_modules/glob-watcher/node_modules/is-number/node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", @@ -14426,6 +13132,67 @@ "node": ">=0.10.0" } }, + "node_modules/glob-watcher/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/micromatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/glob-watcher/node_modules/readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -14657,7 +13424,6 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, - "license": "MIT", "dependencies": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -15039,117 +13805,6 @@ "node": ">=6" } }, - "node_modules/gulp-connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/gulp-connect/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/gulp-connect/node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/gulp-connect/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true, - "bin": { - "mime": "cli.js" - } - }, - "node_modules/gulp-connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/gulp-connect/node_modules/send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-connect/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/gulp-eslint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", @@ -16839,19 +15494,6 @@ "node": ">=12.0.0" } }, - "node_modules/home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -16926,12 +15568,6 @@ "entities": "^4.5.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -17191,15 +15827,6 @@ "node": ">= 0.10" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -17383,11 +16010,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -17494,18 +16124,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -17791,9 +16409,9 @@ } }, "node_modules/is-unicode-supported": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", - "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "engines": { "node": ">=18" @@ -18512,19 +17130,6 @@ "node": ">=8" } }, - "node_modules/jest-message-util/node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/jest-message-util/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -19431,9 +18036,9 @@ } }, "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -19577,31 +18182,6 @@ "node": ">=0.10.0" } }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "dev": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "node_modules/lines-and-columns": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", @@ -19836,18 +18416,6 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, "node_modules/lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -19857,12 +18425,6 @@ "lodash._root": "^3.0.0" } }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -19893,18 +18455,6 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, - "node_modules/lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -20075,18 +18625,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -20160,15 +18698,6 @@ "node": ">=0.10.0" } }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -20206,12 +18735,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true - }, "node_modules/matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -20227,6 +18750,106 @@ "node": ">= 0.10.0" } }, + "node_modules/matchdep/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/matchdep/node_modules/findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", @@ -20242,6 +18865,12 @@ "node": ">= 0.10" } }, + "node_modules/matchdep/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "node_modules/matchdep/node_modules/is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", @@ -20254,6 +18883,67 @@ "node": ">=0.10.0" } }, + "node_modules/matchdep/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mdast-util-definitions": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", @@ -20593,9 +19283,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -21175,193 +19868,16 @@ ] }, "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6" } }, "node_modules/mime": { @@ -21483,31 +19999,31 @@ "dev": true }, "node_modules/mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -21517,15 +20033,6 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mocha/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -21584,33 +20091,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -21640,38 +20120,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mocha/node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -21785,9 +20233,9 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -21907,9 +20355,9 @@ } }, "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "engines": { "node": ">=10" @@ -22131,15 +20579,6 @@ "node": ">=0.10.0" } }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -22229,9 +20668,9 @@ } }, "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dev": true, "dependencies": { "isarray": "0.0.1" @@ -22755,15 +21194,6 @@ "readable-stream": "^2.0.1" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -22794,15 +21224,6 @@ "node": ">=4" } }, - "node_modules/p-iteration": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz", - "integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -23177,9 +21598,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "1.1.0", @@ -23290,6 +21711,15 @@ "node": ">=0.10.0" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/pkcs7": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", @@ -23451,15 +21881,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -23649,115 +22070,6 @@ "node": ">=6" } }, - "node_modules/puppeteer-core": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", - "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", - "dev": true, - "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", - "dev": true - }, - "node_modules/puppeteer-core/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/puppeteer-core/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/puppeteer-core/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -23779,11 +22091,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -23810,18 +22122,6 @@ "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "dev": true }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -24374,18 +22674,6 @@ "node": ">=0.10" } }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -24517,12 +22805,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "node_modules/resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -24855,23 +23137,24 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", + "depd": "~1.1.2", + "destroy": "~1.0.4", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" }, "engines": { "node": ">= 0.8.0" @@ -24881,62 +23164,120 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { "ms": "2.0.0" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true }, "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, "bin": { "mime": "cli.js" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" }, "engines": { - "node": ">=4" + "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "node_modules/send/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } }, "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "type-fest": "^2.12.2" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -25021,19 +23362,87 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -25130,6 +23539,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -25181,7 +23602,6 @@ "deprecated": "16.1.1", "dev": true, "hasInstallScript": true, - "license": "BSD-3-Clause", "dependencies": { "@sinonjs/formatio": "^2.0.0", "diff": "^3.1.0", @@ -25215,15 +23635,6 @@ "node": ">= 10" } }, - "node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/slashes": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", @@ -25558,15 +23969,6 @@ "decode-uri-component": "^0.2.0" } }, - "node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, "node_modules/source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", @@ -26428,15 +24830,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/terser/node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -26790,12 +25183,6 @@ "node": ">=0.8" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -26815,15 +25202,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -27223,6 +25601,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -27566,21 +25956,6 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/url/node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", @@ -27905,23 +26280,21 @@ "dev": true }, "node_modules/videojs-ima": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-1.11.0.tgz", - "integrity": "sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.3.0.tgz", + "integrity": "sha512-8r0BZGT+WCTO6PePyKZHikV79Ojqh4yLMx4+DmPyXeRcKUVsQ7Va0R7Ok8GRcA8Zy3l1PM6jzLrD/W1rwKhZ8g==", "dev": true, "dependencies": { "@hapi/cryptiles": "^5.1.0", - "can-autoplay": "^3.0.0", + "can-autoplay": "^3.0.2", "extend": ">=3.0.2", - "lodash": ">=4.17.19", - "lodash.template": ">=4.5.0", - "videojs-contrib-ads": "^6.6.5" + "videojs-contrib-ads": "^6.9.0" }, "engines": { "node": ">=0.8.0" }, "peerDependencies": { - "video.js": "^5.19.2 || ^6 || ^7" + "video.js": "^5.19.2 || ^6 || ^7 || ^8" } }, "node_modules/videojs-playlist": { @@ -28442,177 +26815,170 @@ } }, "node_modules/webdriverio": { - "version": "7.36.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.36.0.tgz", - "integrity": "sha512-OTYmKBF7eFKBX39ojUIEzw7AlE1ZRJiFoMTtEQaPMuPzZCP2jUBq6Ey38nuZrYXLkXn3/le9a14pNnKSM0n56w==", - "dev": true, - "dependencies": { - "@types/aria-query": "^5.0.0", - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/repl": "7.33.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "archiver": "^5.0.0", - "aria-query": "^5.2.1", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.9.tgz", + "integrity": "sha512-IwvKzhcJ9NjOL55xwj27uTTKkfxsg77dmAfqoKFSP5dQ70JzU+NgxiALEjjWQDybtt1yGIkHk7wjjxjboMU1uw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.35.0", - "devtools-protocol": "^0.0.1260888", - "fs-extra": "^11.1.1", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^6.0.4", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.33.0" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.8" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/webdriverio/node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" + "node": ">=18.20.0" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "peerDependencies": { + "puppeteer-core": "^22.3.0" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } } }, - "node_modules/webdriverio/node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.0" + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=10" - } - }, - "node_modules/webdriverio/node_modules/@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" + "node": ">=18" } }, "node_modules/webdriverio/node_modules/@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.8.tgz", + "integrity": "sha512-37L+hd+A1Nyehd/pgfTrLC6w+Ngbu0CIoFh9Vv6v8Cgu5Hih0TLofvlg+J1BNbcTd5eQ2tFKZBDeFMhQaIiTpg==", "dev": true, "dependencies": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.20.0" } }, "node_modules/webdriverio/node_modules/@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.8.tgz", + "integrity": "sha512-uIyYIDBwLczmsp9JE5hN3ME8Xg+9WNBfSNXD69ICHrY9WPTzFf94UeTuavK7kwSKF3ro2eJbmNZItYOfnoovnw==", "dev": true, "dependencies": { - "chalk": "^4.0.0", + "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.20.0" } }, "node_modules/webdriverio/node_modules/@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.8.tgz", + "integrity": "sha512-xRH54byFf623/w/KW62xkf/C2mGyigSfMm+UT3tNEAd5ZA9X2VAWQWQBPzdcrsck7Fxk4zlQX8Kb34RSs7Cy4Q==", + "dev": true }, "node_modules/webdriverio/node_modules/@wdio/repl": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz", - "integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", + "integrity": "sha512-3iubjl4JX5zD21aFxZwQghqC3lgu+mSs8c3NaiYYNCC+IT5cI/8QuKlgh9s59bu+N3gG988jqMJeCYlKuUv/iw==", "dev": true, "dependencies": { - "@wdio/utils": "7.33.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.20.0" } }, "node_modules/webdriverio/node_modules/@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.8.tgz", + "integrity": "sha512-pmz2iRWddTanrv8JC7v3wUGm17KRv2WyyJhQfklMSANn9V1ep6pw1RJG2WJnKq4NojMvH1nVv1sMZxXrYPhpYw==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "got": "^11.8.1" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "^4.6.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18.20.0" } }, "node_modules/webdriverio/node_modules/@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.8.tgz", + "integrity": "sha512-p3EgOdkhCvMxJFd3WTtSChqYFQu2mz69/5tOsljDaL+4QYwnRR7O8M9wFsL3/9XMVcHdnC4Ija2VRxQ/lb+hHQ==", "dev": true, "dependencies": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/webdriverio/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "debug": "^4.3.4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14" } }, "node_modules/webdriverio/node_modules/brace-expansion": { @@ -28624,306 +26990,147 @@ "balanced-match": "^1.0.0" } }, - "node_modules/webdriverio/node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/webdriverio/node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/webdriverio/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/webdriverio/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webdriverio/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/webdriverio/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/webdriverio/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=14.14" + "node": ">=16.0.0" } }, - "node_modules/webdriverio/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/webdriverio/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/webdriverio/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webdriverio/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/webdriverio/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/webdriverio/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/webdriverio/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/webdriverio/node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webdriverio/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/webdriverio/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/webdriverio/node_modules/ky": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", - "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", - "dev": true, - "engines": { - "node": ">=12" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, - "node_modules/webdriverio/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "node_modules/webdriverio/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/webdriverio/node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webdriverio/node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" } }, - "node_modules/webdriverio/node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webdriverio/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/webdriverio/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/webdriverio/node_modules/webdriver": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.8.tgz", + "integrity": "sha512-UnV0ANriSTUgypGk0pz8lApeQuHt+72WEDQG5hFwkkSvggtKLyWdT7+PQkNoXvDajTmiLIqUOq8XPI/Pm71rtw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "deepmerge-ts": "^7.0.3", + "ws": "^8.8.0" }, "engines": { - "node": ">=8" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/webdriver": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz", - "integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==", + "node_modules/webdriverio/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "got": "^11.0.2", - "ky": "0.30.0", - "lodash.merge": "^4.6.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, "node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -29328,16 +27535,6 @@ "node": ">=18" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", @@ -29456,9 +27653,9 @@ "dev": true }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "node_modules/wrap-ansi": { @@ -29759,73 +27956,66 @@ } }, "node_modules/zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "dependencies": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, - "node_modules/zip-stream/node_modules/archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "glob": "^7.2.3", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/zip-stream/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/zip-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "safe-buffer": "~5.2.0" } }, "node_modules/zod": { @@ -29876,25 +28066,25 @@ } }, "@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==" + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==" }, "@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -29914,11 +28104,11 @@ } }, "@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "requires": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -29942,13 +28132,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "requires": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" } @@ -30035,15 +28225,14 @@ } }, "@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "requires": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" } }, "@babel/helper-optimise-call-expression": { @@ -30106,9 +28295,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==" + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==" }, "@babel/helper-validator-identifier": { "version": "7.24.7", @@ -30116,9 +28305,9 @@ "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==" + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==" }, "@babel/helper-wrap-function": { "version": "7.24.7", @@ -30132,12 +28321,12 @@ } }, "@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "requires": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" } }, "@babel/highlight": { @@ -30152,9 +28341,12 @@ } }, "@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==" + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "requires": { + "@babel/types": "^7.25.6" + } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "version": "7.24.7", @@ -30889,6 +29081,113 @@ "esutils": "^2.0.2" } }, + "@babel/register": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, "@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", @@ -30903,38 +29202,35 @@ } }, "@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "requires": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" } }, "@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "requires": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "requires": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } @@ -31832,6 +30128,12 @@ } } }, + "@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -31880,9 +30182,9 @@ } }, "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "@socket.io/component-emitter": { @@ -31897,24 +30199,6 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true }, - "@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -31973,16 +30257,6 @@ "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "requires": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "@types/hast": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", @@ -31992,12 +30266,6 @@ "@types/unist": "^2" } }, - "@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -32034,15 +30302,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -32052,12 +30311,6 @@ "@types/unist": "^2" } }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -32100,15 +30353,6 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, - "@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/sinonjs__fake-timers": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", @@ -32133,12 +30377,6 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true }, - "@types/ua-parser-js": { - "version": "0.7.39", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", - "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", - "dev": true - }, "@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -32431,42 +30669,6 @@ "debug": "^4.3.4" } }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -32476,51 +30678,12 @@ "balanced-match": "^1.0.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, "deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -32545,12 +30708,6 @@ "debug": "4" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -32597,43 +30754,12 @@ "ws": "^8.18.0" } }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, "semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -32646,12 +30772,6 @@ "tar-stream": "^3.1.5" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true - }, "uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -32716,17 +30836,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, @@ -32830,50 +30939,6 @@ "debug": "^4.3.4" } }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - } - } - }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -32883,59 +30948,12 @@ "balanced-match": "^1.0.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - } - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, "deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -32951,9 +30969,9 @@ "peer": true }, "execa": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.1.tgz", - "integrity": "sha512-gdhefCCNy/8tpH/2+ajP9IQc14vXchNdd0weyzSJEFURhRMGncQ+zKFxwjAufIewPEJm9BPOaJnvg2UtlH2gPQ==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.0.tgz", + "integrity": "sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==", "dev": true, "requires": { "@sindresorhus/merge-streams": "^4.0.0", @@ -32963,7 +30981,7 @@ "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", - "npm-run-path": "^5.2.0", + "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", @@ -33012,12 +31030,13 @@ } }, "npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "requires": { - "path-key": "^4.0.0" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" } }, "parse-ms": { @@ -33092,49 +31111,18 @@ } } }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, "semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "requires": { - "type-fest": "^2.12.2" - } - }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -33147,12 +31135,6 @@ "tar-stream": "^3.1.5" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true - }, "webdriverio": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", @@ -33211,17 +31193,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, @@ -33500,45 +31471,6 @@ "debug": "^4.3.4" } }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "optional": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "optional": true - }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -33549,24 +31481,6 @@ "balanced-match": "^1.0.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "optional": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "optional": true - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -33574,31 +31488,6 @@ "dev": true, "optional": true }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "optional": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "optional": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, "deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -33638,13 +31527,6 @@ "debug": "4" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "optional": true - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -33694,20 +31576,6 @@ "ws": "^8.18.0" } }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, "semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -33715,26 +31583,6 @@ "dev": true, "optional": true }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "optional": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -33748,13 +31596,6 @@ "tar-stream": "^3.1.5" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "optional": true - }, "webdriverio": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", @@ -33815,18 +31656,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, @@ -34034,42 +31863,6 @@ "debug": "^4.3.4" } }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -34079,51 +31872,12 @@ "balanced-match": "^1.0.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, "deepmerge-ts": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", @@ -34160,12 +31914,6 @@ "debug": "4" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -34232,43 +31980,12 @@ } } }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, "semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "tar-fs": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", @@ -34281,12 +31998,6 @@ "tar-stream": "^3.1.5" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true - }, "webdriverio": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", @@ -34345,17 +32056,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, @@ -34751,82 +32451,117 @@ } }, "archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "requires": { - "archiver-utils": "^2.1.0", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" }, "dependencies": { "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "safe-buffer": "~5.2.0" } } } }, "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "requires": { - "glob": "^7.1.4", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", + "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" + "readable-stream": "^4.0.0" }, "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" } } } @@ -35005,6 +32740,14 @@ "default-compare": "^1.0.0", "get-value": "^2.0.6", "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "array-uniq": { @@ -35237,155 +32980,6 @@ "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, - "dependencies": { - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", - "dev": true - } - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, "babel-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", @@ -35398,15 +32992,6 @@ "schema-utils": "^2.6.5" } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -35447,132 +33032,6 @@ "@babel/helper-define-polyfill-provider": "^0.6.2" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -35774,30 +33233,6 @@ "file-uri-to-path": "1.0.0" } }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -35840,9 +33275,9 @@ } }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -35852,7 +33287,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -36274,32 +33709,6 @@ "readdirp": "~3.6.0" } }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "dev": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - } - } - }, "chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -36454,20 +33863,25 @@ "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", "dev": true }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "dependencies": { - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } } } }, @@ -36580,26 +33994,54 @@ "dev": true }, "compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" } } } @@ -36815,44 +34257,45 @@ "dev": true }, "crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } - } - } - }, - "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "requires": { - "node-fetch": "2.6.7" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "whatwg-url": "^5.0.0" + "safe-buffer": "~5.2.0" } } } @@ -37086,23 +34529,6 @@ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true - } - } - }, "deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -37144,12 +34570,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, "deepmerge-ts": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", @@ -37163,6 +34583,14 @@ "dev": true, "requires": { "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "default-resolution": { @@ -37190,12 +34618,6 @@ } } }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true - }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -37299,435 +34721,19 @@ "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", "dev": true }, - "devtools": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.35.0.tgz", - "integrity": "sha512-7HMZMcJSCK/PaBCWVs4n4ZhtBNdUQj10iPwXvj/JDkqPreEXN/XW9GJAoMuLPFmCEKfxe+LrIbgs8ocGJ6rp/A==", - "dev": true, - "requires": { - "@types/node": "^18.0.0", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^9.0.0" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - }, - "@types/which": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", - "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", - "dev": true - }, - "@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", - "dev": true, - "requires": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" - } - }, - "@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true - }, - "@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", - "dev": true, - "requires": { - "@types/node": "^18.0.0", - "got": "^11.8.1" - } - }, - "@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", - "dev": true, - "requires": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.948846", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", - "integrity": "sha512-5fGyt9xmMqUl2VI7+rnUkKCiAQIpLns8sfQtTENy5L70ktbNw0Z3TFJ1JoFNYdx/jffz4YXU45VF75wKZD7sZQ==", - "dev": true - }, - "edge-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", - "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", - "dev": true, - "requires": { - "@types/which": "^1.3.2", - "which": "^2.0.2" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true - }, - "puppeteer-core": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.1.3.tgz", - "integrity": "sha512-96pzvVBzq5lUGt3L/QrIH3mxn3NfZylHeusNhq06xBAHPI0Upc0SC/9u7tXjL0oRnmcExeVRJivr1lj7Ah/yDQ==", - "dev": true, - "requires": { - "debug": "4.3.2", - "devtools-protocol": "0.0.948846", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.7", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.2.3" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} - } - } - }, "devtools-protocol": { "version": "0.0.1260888", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz", "integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "di": { "version": "0.0.1", @@ -38287,7 +35293,8 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true }, "encoding-sniffer": { "version": "0.2.0", @@ -38913,9 +35920,9 @@ } }, "eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", "dev": true, "requires": { "debug": "^3.2.7" @@ -38943,26 +35950,27 @@ } }, "eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, "requires": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", "tsconfig-paths": "^3.15.0" }, @@ -39407,36 +36415,36 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -39452,10 +36460,52 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } } } }, @@ -39751,12 +36801,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -39772,6 +36822,11 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -39810,6 +36865,145 @@ "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "fined": { @@ -39996,12 +37190,6 @@ "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "fs-extra": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", @@ -40533,6 +37721,12 @@ } } }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -40658,15 +37852,68 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "isobject": "^3.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } } }, "readdirp": { @@ -41151,99 +38398,6 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true } } }, @@ -42566,16 +39720,6 @@ "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", "dev": true }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -42632,12 +39776,6 @@ "entities": "^4.5.0" } }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -42830,15 +39968,6 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -42959,11 +40088,11 @@ "dev": true }, "is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "requires": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" } }, "is-data-descriptor": { @@ -43033,12 +40162,6 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -43228,9 +40351,9 @@ } }, "is-unicode-supported": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", - "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true }, "is-utf8": { @@ -43756,16 +40879,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -44481,9 +41594,9 @@ } }, "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "kleur": { @@ -44596,33 +41709,6 @@ } } }, - "lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, "lines-and-columns": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", @@ -44815,18 +41901,6 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -44836,12 +41910,6 @@ "lodash._root": "^3.0.0" } }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -44872,18 +41940,6 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, - "lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -45030,15 +42086,6 @@ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "dev": true }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -45101,14 +42148,6 @@ "dev": true, "requires": { "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } } }, "map-cache": { @@ -45138,12 +42177,6 @@ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", "dev": true }, - "marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true - }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -45156,6 +42189,86 @@ "stack-trace": "0.0.10" }, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, "findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", @@ -45168,6 +42281,12 @@ "resolve-dir": "^1.0.1" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", @@ -45176,6 +42295,57 @@ "requires": { "is-extglob": "^2.1.0" } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, @@ -45443,9 +42613,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -45784,148 +42954,13 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "braces": "^3.0.3", + "picomatch": "^2.3.1" } }, "mime": { @@ -46017,39 +43052,33 @@ "dev": true }, "mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -46095,22 +43124,6 @@ } } }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -46137,29 +43150,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -46230,9 +43220,9 @@ } }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -46313,9 +43303,9 @@ } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, "yocto-queue": { @@ -46479,12 +43469,6 @@ "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true } } }, @@ -46568,9 +43552,9 @@ } }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dev": true, "requires": { "isarray": "0.0.1" @@ -46961,12 +43945,6 @@ "readable-stream": "^2.0.1" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -46988,12 +43966,6 @@ "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true }, - "p-iteration": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz", - "integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -47285,9 +44257,9 @@ } }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "path-type": { "version": "1.1.0", @@ -47373,6 +44345,12 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, "pkcs7": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", @@ -47474,12 +44452,6 @@ "parse-ms": "^2.1.0" } }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -47639,86 +44611,6 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, - "puppeteer-core": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", - "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", - "dev": true, - "requires": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", - "dev": true - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "requires": {} - } - } - }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -47732,11 +44624,11 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "query-selector-shadow-dom": { @@ -47757,12 +44649,6 @@ "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "dev": true }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -48192,15 +45078,6 @@ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -48305,12 +45182,6 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -48565,73 +45436,121 @@ } }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, "requires": { "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", + "depd": "~1.1.2", + "destroy": "~1.0.4", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" }, "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true }, "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true } } }, "serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "requires": { - "type-fest": "^0.20.2" + "type-fest": "^2.12.2" }, "dependencies": { "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true } } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -48706,14 +45625,73 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + } + } + } } }, "set-blocking": { @@ -48796,6 +45774,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -48862,12 +45849,6 @@ "totalist": "^3.0.0" } }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true - }, "slashes": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", @@ -49141,15 +46122,6 @@ "decode-uri-component": "^0.2.0" } }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, "source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", @@ -49835,15 +46807,6 @@ "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } } } }, @@ -50119,12 +47082,6 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -50137,12 +47094,6 @@ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "dev": true }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "dev": true - }, "triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -50439,6 +47390,12 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" }, + "unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true + }, "unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -50689,15 +47646,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true - }, - "qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.6" - } } } }, @@ -50973,17 +47921,15 @@ "dev": true }, "videojs-ima": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-1.11.0.tgz", - "integrity": "sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.3.0.tgz", + "integrity": "sha512-8r0BZGT+WCTO6PePyKZHikV79Ojqh4yLMx4+DmPyXeRcKUVsQ7Va0R7Ok8GRcA8Zy3l1PM6jzLrD/W1rwKhZ8g==", "dev": true, "requires": { "@hapi/cryptiles": "^5.1.0", - "can-autoplay": "^3.0.0", + "can-autoplay": "^3.0.2", "extend": ">=3.0.2", - "lodash": ">=4.17.19", - "lodash.template": ">=4.5.0", - "videojs-contrib-ads": "^6.6.5" + "videojs-contrib-ads": "^6.9.0" } }, "videojs-playlist": { @@ -51402,365 +48348,252 @@ } } }, - "webdriverio": { - "version": "7.36.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.36.0.tgz", - "integrity": "sha512-OTYmKBF7eFKBX39ojUIEzw7AlE1ZRJiFoMTtEQaPMuPzZCP2jUBq6Ey38nuZrYXLkXn3/le9a14pNnKSM0n56w==", - "dev": true, - "requires": { - "@types/aria-query": "^5.0.0", - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/repl": "7.33.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "archiver": "^5.0.0", - "aria-query": "^5.2.1", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.35.0", - "devtools-protocol": "^0.0.1260888", - "fs-extra": "^11.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^6.0.4", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.33.0" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - }, - "@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", - "dev": true, - "requires": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" - } - }, - "@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true - }, - "@wdio/repl": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz", - "integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==", - "dev": true, - "requires": { - "@wdio/utils": "7.33.0" - } - }, - "@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", - "dev": true, - "requires": { - "@types/node": "^18.0.0", - "got": "^11.8.1" - } - }, - "@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", - "dev": true, - "requires": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "ky": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", - "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", - "dev": true - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "webdriver": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz", - "integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==", - "dev": true, - "requires": { - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "got": "^11.0.2", - "ky": "0.30.0", - "lodash.merge": "^4.6.1" - } - } - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, + "webdriverio": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.9.tgz", + "integrity": "sha512-IwvKzhcJ9NjOL55xwj27uTTKkfxsg77dmAfqoKFSP5dQ70JzU+NgxiALEjjWQDybtt1yGIkHk7wjjxjboMU1uw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.8" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", + "dev": true, + "requires": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + } + }, + "@wdio/config": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.8.tgz", + "integrity": "sha512-37L+hd+A1Nyehd/pgfTrLC6w+Ngbu0CIoFh9Vv6v8Cgu5Hih0TLofvlg+J1BNbcTd5eQ2tFKZBDeFMhQaIiTpg==", + "dev": true, + "requires": { + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + } + }, + "@wdio/logger": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.8.tgz", + "integrity": "sha512-uIyYIDBwLczmsp9JE5hN3ME8Xg+9WNBfSNXD69ICHrY9WPTzFf94UeTuavK7kwSKF3ro2eJbmNZItYOfnoovnw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/protocols": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.8.tgz", + "integrity": "sha512-xRH54byFf623/w/KW62xkf/C2mGyigSfMm+UT3tNEAd5ZA9X2VAWQWQBPzdcrsck7Fxk4zlQX8Kb34RSs7Cy4Q==", + "dev": true + }, + "@wdio/repl": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", + "integrity": "sha512-3iubjl4JX5zD21aFxZwQghqC3lgu+mSs8c3NaiYYNCC+IT5cI/8QuKlgh9s59bu+N3gG988jqMJeCYlKuUv/iw==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/types": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.8.tgz", + "integrity": "sha512-pmz2iRWddTanrv8JC7v3wUGm17KRv2WyyJhQfklMSANn9V1ep6pw1RJG2WJnKq4NojMvH1nVv1sMZxXrYPhpYw==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.8.tgz", + "integrity": "sha512-p3EgOdkhCvMxJFd3WTtSChqYFQu2mz69/5tOsljDaL+4QYwnRR7O8M9wFsL3/9XMVcHdnC4Ija2VRxQ/lb+hHQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } + }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true + }, + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "requires": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "webdriver": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.8.tgz", + "integrity": "sha512-UnV0ANriSTUgypGk0pz8lApeQuHt+72WEDQG5hFwkkSvggtKLyWdT7+PQkNoXvDajTmiLIqUOq8XPI/Pm71rtw==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "deepmerge-ts": "^7.0.3", + "ws": "^8.8.0" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } + }, "webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -52045,16 +48878,6 @@ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", @@ -52145,9 +48968,9 @@ "dev": true }, "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "wrap-ansi": { @@ -52354,57 +49177,46 @@ "dev": true }, "zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "requires": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "dependencies": { - "archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "glob": "^7.2.3", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "safe-buffer": "~5.2.0" } } } diff --git a/package.json b/package.json index 03f09a0f161..54a68be1c45 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", + "@babel/register": "^7.24.6", "@wdio/browserstack-service": "^9.0.5", "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^8.29.0", @@ -56,7 +57,6 @@ "assert": "^2.0.0", "babel-loader": "^8.0.5", "babel-plugin-istanbul": "^6.1.1", - "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -65,7 +65,7 @@ "es5-shim": "^4.5.14", "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.20.2", + "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsdoc": "^48.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", @@ -109,7 +109,7 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", - "mocha": "^10.0.0", + "mocha": "^10.7.3", "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", @@ -120,9 +120,9 @@ "url-parse": "^1.0.5", "video.js": "^7.17.0", "videojs-contrib-ads": "^6.9.0", - "videojs-ima": "^1.11.0", + "videojs-ima": "^2.3.0", "videojs-playlist": "^5.0.0", - "webdriverio": "^7.6.1", + "webdriverio": "^9.0.9", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.0", @@ -130,7 +130,7 @@ "yargs": "^1.3.1" }, "dependencies": { - "@babel/core": "^7.24.6", + "@babel/core": "^7.25.2", "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.18.9", diff --git a/wdio.shared.conf.js b/wdio.shared.conf.js index 34e1ee9c675..08b46eaab91 100644 --- a/wdio.shared.conf.js +++ b/wdio.shared.conf.js @@ -16,7 +16,7 @@ exports.config = { mochaOpts: { ui: 'bdd', timeout: 60000, - compilers: ['js:babel-register'], + compilers: ['js:@babel/register'], }, // if you see error, update this to spec reporter and logLevel above to get detailed report. reporters: ['spec'] From 1536afcf381abe64935f94883d24f457ab1cf7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Baudisch?= Date: Sun, 22 Sep 2024 19:29:39 +0200 Subject: [PATCH 0528/1097] linter.yml: also install dependencies for PR branch (#12262) --- .github/workflows/linter.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 034e0eddee7..03ef6478f1c 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -38,6 +38,9 @@ jobs: - name: Check out PR run: git checkout ${{ github.event.pull_request.head.sha }} + - name: Install dependencies + run: npm ci + - name: Run linter on PR run: npx eslint --no-inline-config --format json $(cat __changed_files.txt | xargs stat --printf '%n\n' 2> /dev/null) > __pr.json || true From 08049783c746a45428807ec6c55d9efaacac0113 Mon Sep 17 00:00:00 2001 From: Phaneendra Hegde Date: Mon, 23 Sep 2024 17:49:35 +0530 Subject: [PATCH 0529/1097] pubxaiAnalyticsAdapter : collect rejected and nobid cases' data in a better way. (#12263) * send BidRejected Events to capture floored bids * fix tests * send pubx_id as query param * added extraData in analytics adapter to be sent in beacon data * added extraData in analytics adapter to be sent in beacon data * moved data read to session storage * bumped version * moving all data to localStorage again * updated test cases for pubxaiAA.js * fixing the missing logging of invalid bids --------- Co-authored-by: tej656 Co-authored-by: Tej <139129627+tej656@users.noreply.github.com> Co-authored-by: NikhilX Co-authored-by: Nathan Oliver --- modules/pubxaiAnalyticsAdapter.js | 2 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 107 +++++++++++++++++- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index cd93b9bd3f6..b2f9af247f6 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -188,7 +188,7 @@ const track = ({ eventType, args }) => { timestamp: args.timestamp, }); if ( - auctionCache[args.auctionId].bids.every((bid) => bid.bidType === 3) + auctionCache[args.auctionId].bids.every((bid) => [1, 3].includes(bid.bidType)) ) { prepareSend(args.auctionId); } diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 6b2fbef6c06..05b32dae434 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -22,8 +22,8 @@ const readBlobSafariCompat = (blob) => { describe('pubxai analytics adapter', () => { beforeEach(() => { - getGlobal().refreshUserIds() sinon.stub(events, 'getEvents').returns([]); + getGlobal().refreshUserIds?.() }); afterEach(() => { @@ -776,15 +776,66 @@ describe('pubxai analytics adapter', () => { } }); - it('auction with no bids', async () => { + it('auction data with only rejected bids', async () => { // Step 1: Send auction init event events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); - // Step 3: Send bid time out event - events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + // Step 3: Send bid rejected (afaict the only expected reason would be a bid being too low) + events.emit(EVENTS.BID_REJECTED, prebidEvent['bidResponse']); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 4: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 5: Send auction end event + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 6: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 8: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + + // Step 9: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + { + ...expectedAfterBid, + bids: [{ + ...expectedAfterBid.bids[0], + bidType: 1 + }] + } + ]); + }); + + it('auction data with only timed out bids', async () => { + // Step 1: Send auction init event + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid rejected (afaict the only expected reason would be a bid being too low) + events.emit(EVENTS.BID_TIMEOUT, [prebidEvent['bidResponse']]); // Simulate "navigate away" behaviour document.dispatchEvent(new Event('visibilitychange')); @@ -816,6 +867,54 @@ describe('pubxai analytics adapter', () => { // Step 9: check that the data sent in the request is correct expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + { + ...expectedAfterBid, + bids: [{ + ...expectedAfterBid.bids[0], + bidType: 3 + }] + } + ]); + }); + + it('auction with no bids', async () => { + // Step 1: Send auction init event + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 3: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 4: Send auction end event + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 5: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 6: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 7: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + + // Step 8: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ { ...expectedAfterBid, From a4822db3bf4a4420c887a8f25e02c02cb0547fe8 Mon Sep 17 00:00:00 2001 From: Jonas SPRENGER Date: Mon, 23 Sep 2024 18:20:14 +0200 Subject: [PATCH 0530/1097] AdagioAnalyticsAdapter: add adg-pba aTag to beacon (#14103) (#12264) --- modules/adagioAnalyticsAdapter.js | 32 ++++++++- .../modules/adagioAnalyticsAdapter_spec.js | 70 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index c64535f74b5..2f015f07c31 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -3,7 +3,7 @@ */ import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; -import { deepAccess, logError, logInfo, logWarn } from '../src/utils.js'; +import { deepAccess, logError, logInfo, logWarn, isPlainObject } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -378,6 +378,33 @@ function handlerAdRender(event, isSuccess) { sendNewBeacon(auctionId, adUnitCode); }; +/** + * handlerPbsAnalytics add to the cache data coming from Adagio PBS AdResponse. + * The data is retrieved from an AnalyticsTag (set by a custom PBS module named `adg-pba`), + * located in the AdResponse at `response.ext.prebid.analytics.tags[].pba`. + */ +function handlerPbsAnalytics(event) { + const pbaByAdUnit = event.atag.find(e => { + return e.module === 'adg-pba' + })?.pba; + + if (!pbaByAdUnit) { + return; + } + + const adUnitCodes = cache.getAllAdUnitCodes(event.auctionId); + + adUnitCodes.forEach(adUnitCode => { + const pba = pbaByAdUnit[adUnitCode] + + if (isPlainObject(pba)) { + cache.updateAuction(event.auctionId, adUnitCode, { + ...addKeyPrefix(pba, 'e_') + }); + } + }) +} + /** * END HANDLERS */ @@ -405,6 +432,9 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { case EVENTS.AD_RENDER_FAILED: handlerAdRender(args, eventType === EVENTS.AD_RENDER_SUCCEEDED); break; + case EVENTS.PBS_ANALYTICS: + handlerPbsAnalytics(args); + break; } } catch (error) { logError('Error on Adagio Analytics Adapter', error); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 7cb0e6c58be..fd899a1c864 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -612,6 +612,22 @@ const AUCTION_END_ANOTHER_NOBID = Object.assign({}, AUCTION_INIT_ANOTHER, { bidsReceived: [] }); +const PBS_ANALYTICS_ANOTHER = { + atag: [ + { + stage: 'auction-response', + module: 'adg-pba', + pba: { + '/19968336/header-bid-tag-1': { + st_id: '53', + splt_cs_id: '731' + } + } + } + ], + auctionId: AUCTION_ID, +} + const MOCK = { SET_TARGETING: { [BID_ADAGIO.adUnitCode]: BID_ADAGIO.adserverTargeting, @@ -972,5 +988,59 @@ describe('adagio analytics adapter', () => { expect(search.bdrs_cpm).to.equal('1.42,,,'); } }); + + it('set adg-pbs aTags in beacon', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.PBS_ANALYTICS, PBS_ANALYTICS_ANOTHER); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + + expect(server.requests.length).to.equal(4, 'requests count'); + + // server.requests[0] -> AUCTION_INIT - AdUnit header-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[0].url); + + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.v).to.equal('1'); + + expect(search.e_st_id).to.be.undefined; + expect(search.e_splt_cs_id).to.be.undefined; + } + + // server.requests[1] -> AUCTION_INIT - AdUnit footer-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[1].url); + + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.v).to.equal('1'); + + expect(search.e_st_id).to.be.undefined; + expect(search.e_splt_cs_id).to.be.undefined; + } + + // server.requests[2] -> AUCTION_END - AdUnit header-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[2].url); + + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.v).to.equal('2'); + + // The adg-pbs aTags fields are set in the beacon ! + expect(search.e_st_id).to.equal('53'); + expect(search.e_splt_cs_id).to.equal('731'); + } + + // server.requests[3] -> AUCTION_END - AdUnit footer-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[3].url); + + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.v).to.equal('2'); + + expect(search.e_st_id).to.be.undefined; + expect(search.e_splt_cs_id).to.be.undefined; + } + }); }); }); From d693107d02eaec2779951b04756c31b8205a8349 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 23 Sep 2024 14:19:56 -0700 Subject: [PATCH 0531/1097] userId: fix unhandled rejection from refreshUserIds (#12246) * userId: fix unhandled rejection from refreshUserIds * apply same treatment to getEncryptedEidsForSource --- modules/userId/index.js | 40 ++++++++++++++++++-------------- test/spec/modules/userId_spec.js | 24 ++++++++++--------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index b274afda397..d0299427603 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -723,7 +723,7 @@ function getUserIdsAsEidBySource(sourceName) { * Sample use case is exposing this function to ESP */ function getEncryptedEidsForSource(source, encrypt, customFunction) { - return initIdSystem().then(() => { + return retryOnCancel().then(() => { let eidsSignals = {}; if (isFn(customFunction)) { @@ -782,6 +782,23 @@ function registerSignalSources() { } } +function retryOnCancel(initParams) { + return initIdSystem(initParams).then( + () => getUserIds(), + (e) => { + if (e === INIT_CANCELED) { + // there's a pending refresh - because GreedyPromise runs this synchronously, we are now in the middle + // of canceling the previous init, before the refresh logic has had a chance to run. + // Use a "normal" Promise to clear the stack and let it complete (or this will just recurse infinitely) + return Promise.resolve().then(getUserIdsAsync) + } else { + logError('Error initializing userId', e) + return GreedyPromise.reject(e) + } + } + ); +} + /** * Force (re)initialization of ID submodules. * @@ -793,12 +810,12 @@ function registerSignalSources() { * @param callback? called when the refresh is complete */ function refreshUserIds({submoduleNames} = {}, callback) { - return initIdSystem({refresh: true, submoduleNames}) - .then(() => { + return retryOnCancel({refresh: true, submoduleNames}) + .then((userIds) => { if (callback && isFn(callback)) { callback(); } - return getUserIds(); + return userIds; }); } @@ -814,20 +831,7 @@ function refreshUserIds({submoduleNames} = {}, callback) { */ function getUserIdsAsync() { - return initIdSystem().then( - () => getUserIds(), - (e) => { - if (e === INIT_CANCELED) { - // there's a pending refresh - because GreedyPromise runs this synchronously, we are now in the middle - // of canceling the previous init, before the refresh logic has had a chance to run. - // Use a "normal" Promise to clear the stack and let it complete (or this will just recurse infinitely) - return Promise.resolve().then(getUserIdsAsync) - } else { - logError('Error initializing userId', e) - return GreedyPromise.reject(e) - } - } - ); + return retryOnCancel(); } export function getConsentHash() { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 3e659c867cf..02c70507a07 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1102,18 +1102,20 @@ describe('User ID', function () { }); }); - it('should still resolve promises returned by getUserIdsAsync', () => { - startInit(); - let result = null; - getGlobal().getUserIdsAsync().then((val) => { result = val; }); - return clearStack().then(() => { - expect(result).to.equal(null); // auction has not ended, callback should not have been called - mockIdCallback.callsFake((cb) => cb(MOCK_ID)); - return getGlobal().refreshUserIds().then(clearStack); - }).then(() => { - expect(result).to.deep.equal(getGlobal().getUserIds()) // auction still not over, but refresh was explicitly forced + ['refreshUserIds', 'getUserIdsAsync'].forEach(method => { + it(`should still resolve promises returned by ${method}`, () => { + startInit(); + let result = null; + getGlobal()[method]().then((val) => { result = val; }); + return clearStack().then(() => { + expect(result).to.equal(null); // auction has not ended, callback should not have been called + mockIdCallback.callsFake((cb) => cb(MOCK_ID)); + return getGlobal().refreshUserIds().then(clearStack); + }).then(() => { + expect(result).to.deep.equal(getGlobal().getUserIds()) // auction still not over, but refresh was explicitly forced + }); }); - }); + }) it('should not stop auctions', (done) => { // simulate an infinite `auctionDelay`; refreshing should still allow the auction to continue From fa9c7e646839ca4822deba9aea2cfec35d0cd003 Mon Sep 17 00:00:00 2001 From: digital-matter Date: Tue, 24 Sep 2024 15:23:54 +0300 Subject: [PATCH 0532/1097] Digital Matter Bid Adapter : overhaul adapter (#12203) * Digital Matter Bid Adapter: Refactor Adapter * Digital Matter Bid Adapter: add aliases --- modules/digitalMatterBidAdapter.js | 213 ++++++- modules/digitalMatterBidAdapter.md | 68 +- .../modules/digitalMatterBidAdapter_spec.js | 593 ++++++------------ 3 files changed, 427 insertions(+), 447 deletions(-) diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index e6af0a8f108..c6663434d1e 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -1,23 +1,208 @@ -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {deepAccess, deepSetValue, getDNT, inIframe, logWarn, parseSizesInput} from '../src/utils.js'; +import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { - buildRequests, - getUserSyncs, - interpretResponse, - isBidRequestValid -} from '../libraries/xeUtils/bidderUtils.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; -const BIDDER_CODE = 'digitalmatter'; -const ENDPOINT = 'https://prebid.di-change.live'; +const BIDDER_CODE = 'digitalMatter'; +const ENDPOINT_URL = 'https://adx.digitalmatter.services/' export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER], aliases: ['dichange', 'digitalmatter'], - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), - interpretResponse, - getUserSyncs + bidParameters: ['accountId', 'siteId'], + isBidRequestValid: function (bid) { + if (typeof bid.params !== 'object') { + return false; + } + if (!hasBannerMediaType(bid)) { + logWarn('Invalid bid request: missing required mediaType - banner'); + return false; + } + + return !!(bid.params.accountId && bid.params.siteId) + }, + buildRequests: function (validBidRequests, bidderRequest) { + const common = bidderRequest.ortb2 || {}; + const site = common.site; + const tid = common?.source?.tid; + const {user} = common || {}; + + if (!site.page) { + site.page = bidderRequest.refererInfo.page; + } + + const device = getDevice(common.device); + const schain = getByKey(validBidRequests, 'schain'); + const eids = getByKey(validBidRequests, 'userIdAsEids'); + const currency = config.getConfig('currency') + const cur = currency && [currency]; + + const imp = validBidRequests.map((bid, id) => { + const {accountId, siteId} = bid.params; + const bannerParams = deepAccess(bid, 'mediaTypes.banner'); + const position = deepAccess(bid, 'mediaTypes.banner.pos') ?? 0; + + return { + id: bid.adUnitCode, + bidId: bid.bidId, + accountId: accountId, + adUnitCode: bid.adUnitCode, + siteId: siteId, + banner: { + pos: position, + topframe: inIframe() ? 0 : 1, + format: bannerParams.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + sizes: parseSizesInput(bannerParams.sizes), + }; + }); + + const ext = { + prebid: { + targeting: { + includewinners: true, + includebidderkeys: false + } + } + }; + + const payload = { + id: bidderRequest.bidderRequestId, + tid, + site, + device, + user, + cur, + imp, + test: config.getConfig('debug') ? 1 : 0, + tmax: bidderRequest.timeout, + start: bidderRequest.auctionStart, + ext + }; + + if (schain) { + deepSetValue(payload, 'source.ext.schain', schain); + } + + if (eids) { + deepSetValue(payload, 'user.ext.eids', eids); + } + + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL + 'openrtb2/auction', + data: payloadString, + }; + }, + interpretResponse: function (serverResponse) { + const body = serverResponse.body || serverResponse; + const {cur} = body; + const bids = []; + + if (body && body.bids && Array.isArray(body.bids)) { + body.bids.forEach(bidItem => { + const bid = { + requestId: bidItem.bidid, + adomain: bidItem.adomain, + cpm: bidItem.cpm, + currency: cur, + netRevenue: true, + ttl: bidItem.ttl || 300, + creativeId: bidItem.creativeid, + width: bidItem.width, + height: bidItem.height, + dealId: bidItem.dealid, + ad: bidItem.ad, + meta: bidItem.meta, + }; + + bids.push(bid); + }); + } + + return bids + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if (usersSynced) { + return []; + } + + const userSyncs = []; + + function checkGppStatus(gppConsent) { + if (gppConsent && Array.isArray(gppConsent.applicableSections)) { + return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); + } + return true; + } + + if (hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { + responses.forEach(response => { + if (response.body.ext && response.body.ext.usersync) { + try { + const userSync = response.body.ext.usersync; + + userSync.forEach((element) => { + let url = element.url; + let type = element.type; + + if (url) { + if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + userSyncs.push({type: 'image', url: url}); + } else if (type === 'iframe' && syncOptions.iframeEnabled) { + userSyncs.push({type: 'iframe', url: url}); + } + } + }) + } catch (e) { + // + } + } + }); + } + + return userSyncs; + } +} + +let usersSynced = false; + +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +function getDevice(data) { + let dnt = data.dnt; + if (!dnt) { + dnt = getDNT() ? 1 : 0; + } + return { + w: data.w || window.innerWidth, + h: data.h || window.innerHeight, + ua: data.ua || navigator.userAgent, + dnt: dnt, + language: data.language || navigator.language, + } +} + +function getByKey(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } } registerBidder(spec); diff --git a/modules/digitalMatterBidAdapter.md b/modules/digitalMatterBidAdapter.md index 1b8426497e9..19c0ecd631a 100644 --- a/modules/digitalMatterBidAdapter.md +++ b/modules/digitalMatterBidAdapter.md @@ -1,50 +1,38 @@ # Overview ``` -Module Name: Digital Matter Bidder Adapter -Module Type: Digital Matter Bidder Adapter -Maintainer: di-change@digitalmatter.ai +Module Name: Digital Matter Bid Adapter +Module Type: Digital Matter Bid Adapter +Maintainer: prebid@digitalmatter.ai ``` -# Test Parameters -``` +# Description + +Module that connects to Digital Matter demand sources + +# Banner Test Parameters + +```js var adUnits = [ - { - code: 'test-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [ - { - bidder: 'digitalmatter', - params: { - env: 'digitalmatter', - pid: '40', - ext: {} - } - } + { + code: "test-banner", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] ] + } }, - { - code: 'test-video', - sizes: [ [ 640, 480 ] ], - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream', - skipppable: true - } - }, - bids: [{ - bidder: 'digitalmatter', - params: { - env: 'digitalmatter', - pid: '40', - ext: {} - } - }] - } + bids: [ + { + bidder: "digitalMatter", + params: { + accountId: "1_demo_1", // string, required + siteId: "1-demo-1" // string, required + } + } + ] + } ]; ``` diff --git a/test/spec/modules/digitalMatterBidAdapter_spec.js b/test/spec/modules/digitalMatterBidAdapter_spec.js index deb31a9da02..87056588cd9 100644 --- a/test/spec/modules/digitalMatterBidAdapter_spec.js +++ b/test/spec/modules/digitalMatterBidAdapter_spec.js @@ -1,89 +1,65 @@ -import {expect} from 'chai'; -import {config} from 'src/config.js'; -import {spec} from 'modules/digitalMatterBidAdapter.js'; -import {deepClone} from 'src/utils'; -import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; - -const ENDPOINT = 'https://prebid.di-change.live'; - -const defaultRequest = { - adUnitCode: 'test', - bidId: '1', - requestId: 'qwerty', - ortb2: { - source: { - tid: 'auctionId' +import {assert, expect} from 'chai'; +import {spec} from 'modules/digitalMatterBidAdapter'; +import {config} from '../../../src/config'; +import {deepClone} from '../../../src/utils'; + +const bid = { + 'adUnitCode': 'adUnitCode', + 'bidId': 'bidId', + 'bidder': 'digitalMatter', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] } }, - ortb2Imp: { - ext: { - tid: 'tr1', - } - }, - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 200] - ] - } - }, - bidder: 'digitalmatter', - params: { - env: 'digitalmatter', - pid: '40', - ext: {} - }, - bidRequestsCount: 1 -}; - -const defaultRequestVideo = deepClone(defaultRequest); -defaultRequestVideo.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'instream', - skipppable: true + 'params': { + 'accountId': '1_demo_1', + 'siteId': '1-demo-1' } }; - -const videoBidderRequest = { - bidderCode: 'digitalmatter', - bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] -}; - -const displayBidderRequest = { - bidderCode: 'digitalmatter', - bids: [{bidId: 'qwerty'}] +const bidderRequest = { + ortb2: { + source: { + tid: 'tid-string' + }, + regs: { + ext: { + gdpr: 1 + } + }, + site: { + domain: 'publisher.domain.com', + publisher: { + domain: 'publisher.domain.com' + }, + page: 'https://publisher.domain.com/test.html' + }, + device: { + w: 100, + h: 100, + dnt: 0, + ua: navigator.userAgent, + language: 'en' + } + } }; -describe('digitalmatterBidAdapter', () => { +describe('Digital Matter BidAdapter', function () { describe('isBidRequestValid', function () { - it('should return false when request params is missing', function () { - const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); - }); - - it('should return false when required env param is missing', function () { - const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params.env; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + it('should return true when all required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required pid param is missing', function () { - const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params.pid; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + it('should return false when required params are not passed', function () { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); - it('should return false when video.playerSize is missing', function () { - const invalidRequest = deepClone(defaultRequestVideo); - delete invalidRequest.mediaTypes.video.playerSize; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); - }); - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + it('should return false when media type banner is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes.banner; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -91,368 +67,199 @@ describe('digitalmatterBidAdapter', () => { beforeEach(function () { config.resetConfig(); }); - it('should send request with correct structure', function () { - const request = spec.buildRequests([defaultRequest], {}); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT + '/bid'); - expect(request.options).to.have.property('contentType').and.to.equal('application/json'); - expect(request).to.have.property('data'); + let request = spec.buildRequests([bid], bidderRequest); + + assert.equal(request.method, 'POST'); + assert.equal(request.url, 'https://adx.digitalmatter.services/openrtb2/auction'); + assert.equal(request.options, undefined); + assert.ok(request.data); }); - it('should build basic request structure', function () { - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); - expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); - expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); - expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); - expect(request).to.have.property('bc').and.to.equal(1); - expect(request).to.have.property('floor').and.to.equal(null); - expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); - expect(request).to.have.property('gdprApplies').and.to.equal(0); - expect(request).to.have.property('consentString').and.to.equal(''); - expect(request).to.have.property('userEids').and.to.deep.equal([]); - expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); - expect(request).to.have.property('ext').and.to.deep.equal({}); - expect(request).to.have.property('env').and.to.deep.equal({ - env: 'digitalmatter', - pid: '40' - }); - expect(request).to.have.property('device').and.to.deep.equal({ - ua: navigator.userAgent, - lang: navigator.language + it('should have default request structure', function () { + let keys = 'tid,site,device,imp,test,ext'.split(','); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('should send info about device', function () { + config.setConfig({ + device: {w: 1920, h: 1080} }); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); }); - it('should build request with schain', function () { - const schainRequest = deepClone(defaultRequest); - schainRequest.schain = { - validation: 'strict', - config: { - ver: '1.0' - } - }; - const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; - expect(request).to.have.property('schain').and.to.deep.equal({ - validation: 'strict', - config: { - ver: '1.0' - } + it('should send info about the site', function () { + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.deepEqual(request.site, { + domain: 'publisher.domain.com', + publisher: { + domain: 'publisher.domain.com' + }, + page: 'https://publisher.domain.com/test.html' }); }); - it('should build request with location', function () { - const bidderRequest = { - refererInfo: { - page: 'page', - location: 'location', - domain: 'domain', - ref: 'ref', - isAmp: false - } - }; - const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; - expect(request).to.have.property('location'); - const location = request.location; - expect(location).to.have.property('page').and.to.equal('page'); - expect(location).to.have.property('location').and.to.equal('location'); - expect(location).to.have.property('domain').and.to.equal('domain'); - expect(location).to.have.property('ref').and.to.equal('ref'); - expect(location).to.have.property('isAmp').and.to.equal(false); + it('should send currency if defined', function () { + config.setConfig({currency: {adServerCurrency: 'EUR'}}); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.deepEqual(request.cur, [{adServerCurrency: 'EUR'}]); }); - it('should build request with ortb2 info', function () { - const ortb2Request = deepClone(defaultRequest); - ortb2Request.ortb2 = { - site: { - name: 'name' + it('should pass supply chain object', function () { + let validBidRequests = { + ...bid, + schain: { + validation: 'strict', + config: { + ver: '1.0' + } } }; - const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; - expect(request).to.have.property('ortb2').and.to.deep.equal({ - site: { - name: 'name' + + let request = JSON.parse(spec.buildRequests([validBidRequests], bidderRequest).data); + assert.deepEqual(request.source.ext.schain, { + validation: 'strict', + config: { + ver: '1.0' } }); }); - it('should build request with ortb2Imp info', function () { - const ortb2ImpRequest = deepClone(defaultRequest); - ortb2ImpRequest.ortb2Imp = { - ext: { - data: { - pbadslot: 'home1', - adUnitSpecificAttribute: '1' + it('should pass extended ids if exists', function () { + let validBidRequests = { + ...bid, + userIdAsEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + } + ] } - } + ] }; - const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; - expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ - ext: { - data: { - pbadslot: 'home1', - adUnitSpecificAttribute: '1' - } - } - }); - }); - it('should build request with valid bidfloor', function () { - const bfRequest = deepClone(defaultRequest); - bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); - const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; - expect(request).to.have.property('floor').and.to.equal(5); + let request = JSON.parse(spec.buildRequests([validBidRequests], bidderRequest).data); + + assert.deepEqual(request.user.ext.eids, validBidRequests.userIdAsEids); }); - it('should build request with gdpr consent data if applies', function () { - const bidderRequest = { + it('should pass gdpr consent data if gdprApplies', function () { + let consentedBidderRequest = { + ...bidderRequest, gdprConsent: { gdprApplies: true, - consentString: 'qwerty' + consentString: 'consentDataString' } }; - const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; - expect(request).to.have.property('gdprApplies').and.equals(1); - expect(request).to.have.property('consentString').and.equals('qwerty'); - }); - it('should build request with usp consent data if applies', function () { - const bidderRequest = { - uspConsent: '1YA-' - }; - const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; - expect(request).to.have.property('usPrivacy').and.equals('1YA-'); - }); - - it('should build request with extended ids', function () { - const idRequest = deepClone(defaultRequest); - idRequest.userIdAsEids = [ - {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} - ]; - const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; - expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); - }); - - it('should build request with video', function () { - const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; - expect(request).to.have.property('video').and.to.deep.equal({ - playerSize: [640, 480], - context: 'instream', - skipppable: true - }); - expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + let request = JSON.parse(spec.buildRequests([bid], consentedBidderRequest).data); + assert.equal(request.user.ext.consent, consentedBidderRequest.gdprConsent.consentString); + assert.equal(request.regs.ext.gdpr, consentedBidderRequest.gdprConsent.gdprApplies); + assert.equal(typeof request.regs.ext.gdpr, 'number'); }); }); describe('interpretResponse', function () { - it('should return empty bids', function () { - const serverResponse = { - body: { - data: null - } - }; - - const invalidResponse = spec.interpretResponse(serverResponse, {}); - expect(invalidResponse).to.be.an('array').that.is.empty; + it('should return empty array if no body in response', function () { + assert.ok(spec.interpretResponse([])); }); - it('should interpret valid response', function () { - const serverResponse = { - body: { - data: [{ - requestId: 'qwerty', - cpm: 1, - currency: 'USD', - width: 300, - height: 250, - ttl: 600, - meta: { - advertiserDomains: ['digitalmatter'] - }, - ext: { - pixels: [ - ['iframe', 'surl1'], - ['image', 'surl2'], - ] - } - }] + it('should return array with bids if response not empty', function () { + const firstResponse = { + id: 'id_1', + impid: 'impId_1', + bidid: 'bidId_1', + adunitcode: 'adUnitCode_1', + cpm: 0.10, + ad: '

ad', + adomain: [ + 'advertiser.org' + ], + width: 970, + height: 250, + creativeid: 'creativeId_1', + meta: { + advertiserDomains: [ + 'advertiser.org' + ] } }; - - const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); - const bid = validResponse[0]; - expect(validResponse).to.be.an('array').that.is.not.empty; - expect(bid.requestId).to.equal('qwerty'); - expect(bid.cpm).to.equal(1); - expect(bid.currency).to.equal('USD'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.ttl).to.equal(600); - expect(bid.meta).to.deep.equal({advertiserDomains: ['digitalmatter']}); - }); - - it('should interpret valid banner response', function () { - const serverResponse = { - body: { - data: [{ - requestId: 'qwerty', - cpm: 1, - currency: 'USD', - width: 300, - height: 250, - ttl: 600, - mediaType: 'banner', - creativeId: 'demo-banner', - ad: 'ad', - meta: {} - }] + const secondResponse = { + 'id': 'id_2', + 'impid': 'impId_2', + 'bidid': 'bidId_2', + 'adunitcode': 'adUnitCode_2', + 'cpm': 0.11, + 'ad': '

ad', + 'adomain': [ + 'advertiser.org' + ], + 'width': 970, + 'height': 250, + 'creativeid': 'creativeId_2', + 'meta': { + 'advertiserDomains': [ + 'advertiser.org' + ] } }; + const currency = 'EUR'; - const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); - const bid = validResponseBanner[0]; - expect(validResponseBanner).to.be.an('array').that.is.not.empty; - expect(bid.mediaType).to.equal('banner'); - expect(bid.creativeId).to.equal('demo-banner'); - expect(bid.ad).to.equal('ad'); - }); - - it('should interpret valid video response', function () { - const serverResponse = { + const bids = spec.interpretResponse({ body: { - data: [{ - requestId: 'qwerty', - cpm: 1, - currency: 'USD', - width: 600, - height: 480, - ttl: 600, - mediaType: 'video', - creativeId: 'demo-video', - ad: 'vast-xml', - meta: {} - }] + id: 'randomId', + cur: currency, + bids: [ + firstResponse, + secondResponse + ] } - }; + }); - const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); - const bid = validResponseBanner[0]; - expect(validResponseBanner).to.be.an('array').that.is.not.empty; - expect(bid.mediaType).to.equal('video'); - expect(bid.creativeId).to.equal('demo-video'); - expect(bid.ad).to.equal('vast-xml'); + assert.ok(bids); + assert.deepEqual(bids[0].requestId, firstResponse.bidid); + assert.deepEqual(bids[0].cpm, firstResponse.cpm); + assert.deepEqual(bids[0].creativeId, firstResponse.creativeid); + assert.deepEqual(bids[0].ttl, 300); + assert.deepEqual(bids[0].netRevenue, true); + assert.deepEqual(bids[0].currency, currency); + assert.deepEqual(bids[0].width, firstResponse.width); + assert.deepEqual(bids[0].height, firstResponse.height); + assert.deepEqual(bids[0].dealId, undefined); + assert.deepEqual(bids[0].meta.advertiserDomains, [ 'advertiser.org' ]); + + assert.deepEqual(bids[1].requestId, secondResponse.bidid); + assert.deepEqual(bids[1].cpm, secondResponse.cpm); + assert.deepEqual(bids[1].creativeId, secondResponse.creativeid); + assert.deepEqual(bids[1].ttl, 300); + assert.deepEqual(bids[1].netRevenue, true); + assert.deepEqual(bids[1].currency, currency); + assert.deepEqual(bids[1].width, secondResponse.width); + assert.deepEqual(bids[1].height, secondResponse.height); + assert.deepEqual(bids[1].dealId, undefined); + assert.deepEqual(bids[1].meta.advertiserDomains, [ 'advertiser.org' ]); }); }); describe('getUserSyncs', function () { - it('shoukd handle no params', function () { - const opts = spec.getUserSyncs({}, []); - expect(opts).to.be.an('array').that.is.empty; - }); - - it('should return empty if sync is not allowed', function () { - const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); - expect(opts).to.be.an('array').that.is.empty; - }); - - it('should allow iframe sync', function () { - const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ - body: { - data: [{ - requestId: 'qwerty', - ext: { - pixels: [ - ['iframe', 'surl1?a=b'], - ['image', 'surl2?a=b'], - ] - } - }] - } - }]); - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('iframe'); - expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); - }); - - it('should allow pixel sync', function () { - const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ - body: { - data: [{ - requestId: 'qwerty', - ext: { - pixels: [ - ['iframe', 'surl1?a=b'], - ['image', 'surl2?a=b'], - ] - } - }] - } - }]); - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('image'); - expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); - }); - - it('should allow pixel sync and parse consent params', function () { - const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ - body: { - data: [{ - requestId: 'qwerty', - ext: { - pixels: [ - ['iframe', 'surl1?a=b'], - ['image', 'surl2?a=b'], - ] - } - }] - } - }], { - gdprApplies: 1, - consentString: '1YA-' - }); - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('image'); - expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); - }); - }); - - describe('getBidFloor', function () { - it('should return null when getFloor is not a function', () => { - const bid = {getFloor: 2}; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return null when getFloor doesnt return an object', () => { - const bid = {getFloor: () => 2}; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return null when floor is not a number', () => { - const bid = { - getFloor: () => ({floor: 'string', currency: 'USD'}) - }; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return null when currency is not USD', () => { - const bid = { - getFloor: () => ({floor: 5, currency: 'EUR'}) - }; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return floor value when everything is correct', () => { - const bid = { - getFloor: () => ({floor: 5, currency: 'USD'}) - }; - const result = getBidFloor(bid); - expect(result).to.equal(5); + it('handle empty array (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); }); }); -}) +}); From 5135da200ef1d4b9bc6d9b3fd295c8ff06e09884 Mon Sep 17 00:00:00 2001 From: Rares Mihai Preda <54801398+rares-mihai-preda@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:42:11 +0300 Subject: [PATCH 0533/1097] Connatix Bid Adapter: Support Events (#12199) * POC test * fix tests + a bug in log * removed appnexus newly added events and logs * use constants for events * prepared POC for PrebidJS official * code review * refactor * set viewability container to hba to determine if we cannot detect vieweability or the publisher does not sent us the viewability container id * set context and added two clear TODOs with what it remains to be considered done * Connatix Bid Adapter: Support Events * rename constant name * refactor * adapt the previous solution to the new one suggested by PrebidJs team * unit tests * test * fix unit tests * more unit tests * added bid id on bid won event * conficts * fix find a bid by alias and include tests --- modules/connatixBidAdapter.js | 117 +++++++--- test/spec/modules/connatixBidAdapter_spec.js | 225 ++++++++++++++++++- 2 files changed, 310 insertions(+), 32 deletions(-) diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 7a110a59107..6e453e2caa7 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -1,19 +1,23 @@ +import adapterManager from '../src/adapterManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { percentInView } from '../libraries/percentInView/percentInView.js'; +import { config } from '../src/config.js'; + +import { ajax } from '../src/ajax.js'; import { deepAccess, - isFn, - logError, - isArray, + deepSetValue, formatQS, getWindowTop, + isArray, + isFn, isNumber, isStr, - deepSetValue + logError } from '../src/utils.js'; import { @@ -23,10 +27,15 @@ import { } from '../src/mediaTypes.js'; const BIDDER_CODE = 'connatix'; + const AD_URL = 'https://capi.connatix.com/rtb/hba'; const DEFAULT_MAX_TTL = '3600'; const DEFAULT_CURRENCY = 'USD'; +const EVENTS_URL = 'https://capi.connatix.com/tr/am'; + +let context = {}; + /* * Get the bid floor value from the bid object, either using the getFloor function or by accessing the 'params.bidfloor' property. * If the bid floor cannot be determined, return 0 as a fallback value. @@ -137,6 +146,32 @@ export function detectViewability(bid) { return null; } +export function _getBidRequests(validBidRequests) { + return validBidRequests.map(bid => { + const { + bidId, + mediaTypes, + params, + sizes, + } = bid; + const { placementId, viewabilityContainerIdentifier } = params; + let detectedViewabilityPercentage = detectViewability(bid); + if (isNumber(detectedViewabilityPercentage)) { + detectedViewabilityPercentage = detectedViewabilityPercentage / 100; + } + return { + bidId, + mediaTypes, + sizes, + placementId, + floor: getBidFloor(bid), + hasViewabilityContainerId: Boolean(viewabilityContainerIdentifier), + declaredViewabilityPercentage: bid.params.viewabilityPercentage ?? null, + detectedViewabilityPercentage, + }; + }); +} + /** * Get ids from Prebid User ID Modules and add them to the payload */ @@ -189,29 +224,7 @@ export const spec = { * Return an object containing the request method, url, and the constructed payload. */ buildRequests: (validBidRequests = [], bidderRequest = {}) => { - const bidRequests = validBidRequests.map(bid => { - const { - bidId, - mediaTypes, - params, - sizes, - } = bid; - - let detectedViewabilityPercentage = detectViewability(bid); - if (isNumber(detectedViewabilityPercentage)) { - detectedViewabilityPercentage = detectedViewabilityPercentage / 100; - } - - return { - bidId, - mediaTypes, - sizes, - detectedViewabilityPercentage, - declaredViewabilityPercentage: bid.params.viewabilityPercentage ?? null, - placementId: params.placementId, - floor: getBidFloor(bid), - }; - }); + const bidRequests = _getBidRequests(validBidRequests); const requestPayload = { ortb2: bidderRequest.ortb2, @@ -224,10 +237,12 @@ export const spec = { _handleEids(requestPayload, validBidRequests); + context = requestPayload; + return { method: 'POST', url: AD_URL, - data: requestPayload + data: context }; }, @@ -301,7 +316,51 @@ export const spec = { type: 'iframe', url }]; - } + }, + + isConnatix: (aliasName) => { + if (!aliasName) { + return false; + } + + const originalBidderName = adapterManager.aliasRegistry[aliasName] || aliasName; + return originalBidderName === BIDDER_CODE; + }, + + /** + * Register bidder specific code, which will execute if the server response time is greater than auction timeout + */ + onTimeout: (timeoutData) => { + const connatixBidRequestTimeout = timeoutData.find(bidderRequest => spec.isConnatix(bidderRequest.bidder)); + + // Log only it is a timeout for Connatix + // Otherwise it is not relevant for us + if (!connatixBidRequestTimeout) { + return; + } + const requestTimeout = connatixBidRequestTimeout.timeout; + const timeout = isNumber(requestTimeout) ? requestTimeout : config.getConfig('bidderTimeout'); + spec.triggerEvent({type: 'Timeout', timeout, context}); + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + */ + onBidWon(bidWinData) { + if (bidWinData == null) { + return; + } + const {bidder, cpm, requestId, bidId, adUnitCode, timeToRespond, auctionId} = bidWinData; + + spec.triggerEvent({type: 'BidWon', bestBidBidder: bidder, bestBidPrice: cpm, requestId, bidId, adUnitCode, timeToRespond, auctionId, context}); + }, + + triggerEvent(data) { + ajax(EVENTS_URL, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); + }, }; registerBidder(spec); diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index e9718136052..5c01e23b027 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -1,16 +1,21 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { - spec, + _getBidRequests, + _canSelectViewabilityContainer as connatixCanSelectViewabilityContainer, detectViewability as connatixDetectViewability, getBidFloor as connatixGetBidFloor, _getMinSize as connatixGetMinSize, - _isViewabilityMeasurable as connatixIsViewabilityMeasurable, - _canSelectViewabilityContainer as connatixCanSelectViewabilityContainer, _getViewability as connatixGetViewability, + _isViewabilityMeasurable as connatixIsViewabilityMeasurable, + spec } from '../../../modules/connatixBidAdapter.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as ajax from '../../../src/ajax.js'; import { ADPOD, BANNER, VIDEO } from '../../../src/mediaTypes.js'; +const BIDDER_CODE = 'connatix'; + describe('connatixBidAdapter', function () { let bid; @@ -327,6 +332,181 @@ describe('connatixBidAdapter', function () { }); }); + describe('_getBidRequests', function () { + let bid; + + // Mock a bid request similar to the one already used in connatixBidAdapter tests + function mockBidRequest() { + const mediaTypes = { + banner: { + sizes: [16, 9], + } + }; + return { + bidId: 'testing', + bidder: 'connatix', + params: { + placementId: '30e91414-545c-4f45-a950-0bec9308ff22', + viewabilityContainerIdentifier: 'viewabilityId', + }, + mediaTypes, + sizes: [300, 250] + }; + } + + it('should map valid bid requests and include the expected fields', function () { + bid = mockBidRequest(); + + const result = _getBidRequests([bid]); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.have.property('bidId', bid.bidId); + expect(result[0]).to.have.property('mediaTypes', bid.mediaTypes); + expect(result[0]).to.have.property('sizes', bid.sizes); + expect(result[0]).to.have.property('placementId', bid.params.placementId); + expect(result[0]).to.have.property('hasViewabilityContainerId', true); + }); + + it('should set hasViewabilityContainerId to false when viewabilityContainerIdentifier is absent', function () { + bid = mockBidRequest(); + delete bid.params.viewabilityContainerIdentifier; + + const result = _getBidRequests([bid]); + + expect(result[0]).to.have.property('hasViewabilityContainerId', false); + }); + + it('should call getBidFloor for each bid and return the correct floor value', function () { + bid = mockBidRequest(); + const floorValue = 5; + + // Mock getFloor method on bid + bid.getFloor = function() { + return { floor: floorValue }; + }; + + const result = _getBidRequests([bid]); + + expect(result[0]).to.have.property('floor', floorValue); + }); + + it('should return floor as 0 if getBidFloor throws an error', function () { + bid = mockBidRequest(); + + // Mock getFloor method to throw an error + bid.getFloor = function() { + throw new Error('error'); + }; + + const result = _getBidRequests([bid]); + + expect(result[0]).to.have.property('floor', 0); + }); + }); + + describe('onTimeout', function () { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'triggerEvent') + }) + + afterEach(() => { + ajaxStub.restore() + }); + + it('call event if bidder is connatix', () => { + const result = spec.onTimeout([{ + bidder: 'connatix', + timeout: 500, + }]); + expect(ajaxStub.calledOnce).to.equal(true); + + const data = ajaxStub.firstCall.args[0]; + expect(data.type).to.equal('Timeout'); + expect(data.timeout).to.equal(500); + }); + + it('timeout event is not triggered if bidder is not connatix', () => { + const result = spec.onTimeout([{ + bidder: 'otherBidder', + timeout: 500, + }]); + expect(ajaxStub.notCalled).to.equal(true); + }); + }); + + describe('onBidWon', function () { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'triggerEvent'); + }); + + afterEach(() => { + ajaxStub.restore(); + }); + + it('calls triggerEvent with correct data when bidWinData is provided', () => { + const bidWinData = { + bidder: 'connatix', + cpm: 2.5, + requestId: 'abc123', + bidId: 'dasdas-dsawda-dwaddw-dwdwd', + adUnitCode: 'adunit_1', + timeToRespond: 300, + auctionId: 'auction_456', + }; + + spec.onBidWon(bidWinData); + expect(ajaxStub.calledOnce).to.equal(true); + + const eventData = ajaxStub.firstCall.args[0]; + expect(eventData.type).to.equal('BidWon'); + expect(eventData.bestBidBidder).to.equal('connatix'); + expect(eventData.bestBidPrice).to.equal(2.5); + expect(eventData.requestId).to.equal('abc123'); + expect(eventData.bidId).to.equal('dasdas-dsawda-dwaddw-dwdwd'); + expect(eventData.adUnitCode).to.equal('adunit_1'); + expect(eventData.timeToRespond).to.equal(300); + expect(eventData.auctionId).to.equal('auction_456'); + }); + + it('does not call triggerEvent if bidWinData is null', () => { + spec.onBidWon(null); + expect(ajaxStub.notCalled).to.equal(true); + }); + + it('does not call triggerEvent if bidWinData is undefined', () => { + spec.onBidWon(undefined); + expect(ajaxStub.notCalled).to.equal(true); + }); + }); + + describe('triggerEvent', function () { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(() => { + ajaxStub.restore(); + }); + + it('should call ajax with the correct parameters', () => { + const data = { type: 'BidWon', bestBidBidder: 'bidder1', bestBidPrice: 1.5, requestId: 'req123', adUnitCode: 'ad123', timeToRespond: 250, auctionId: 'auc123', context: {} }; + spec.triggerEvent(data); + + expect(ajaxStub.calledOnce).to.equal(true); + const [url, _, payload, options] = ajaxStub.firstCall.args; + expect(url).to.equal('https://capi.connatix.com/tr/am'); + expect(payload).to.equal(JSON.stringify(data)); + expect(options.method).to.equal('POST'); + expect(options.withCredentials).to.equal(false); + }); + }); + describe('isBidRequestValid', function () { this.beforeEach(function () { bid = mockBidRequest(); @@ -667,6 +847,45 @@ describe('connatixBidAdapter', function () { }); }); + describe('isConnatix', function () { + let aliasRegistryStub; + + beforeEach(() => { + aliasRegistryStub = sinon.stub(adapterManager, 'aliasRegistry').value({}); + }); + + afterEach(() => { + aliasRegistryStub.restore(); + }); + + it('should return false if aliasName is undefined or null', () => { + expect(spec.isConnatix(undefined)).to.be.false; + expect(spec.isConnatix(null)).to.be.false; + }); + + it('should return true if aliasName matches BIDDER_CODE', () => { + const aliasName = BIDDER_CODE; + expect(spec.isConnatix(aliasName)).to.be.true; + }); + + it('should return true if aliasName is mapped to BIDDER_CODE in aliasRegistry', () => { + const aliasName = 'connatixAlias'; + aliasRegistryStub.value({ 'connatixAlias': BIDDER_CODE }); + expect(spec.isConnatix(aliasName)).to.be.true; + }); + + it('should return false if aliasName does not match BIDDER_CODE', () => { + const aliasName = 'otherBidder'; + expect(spec.isConnatix(aliasName)).to.be.false; + }); + + it('should return false if aliasName is mapped to a different bidder in aliasRegistry', () => { + const aliasName = 'someOtherAlias'; + aliasRegistryStub.value({ 'someOtherAlias': 'otherBidder' }); + expect(spec.isConnatix(aliasName)).to.be.false; + }); + }); + describe('getBidFloor', function () { this.beforeEach(function () { bid = mockBidRequest(); From 4675958d761569581ffbe70ff31e10f0eda07f21 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 24 Sep 2024 12:09:14 -0700 Subject: [PATCH 0534/1097] Core: deferred rendering (#11914) * mark bids with deferRendering / deferBilling * refactor internals of triggerBilling * deferred rendering * trigger deferred render from triggerBilling * Fix dfp gdpr parameter dupes --- libraries/dfpUtils/dfpUtils.js | 16 ++- modules/bidViewability.js | 19 +-- modules/dfpAdServerVideo.js | 8 +- modules/dfpAdpod.js | 9 +- .../prebidServerBidAdapter/ortbConverter.js | 5 +- modules/topLevelPaapi.js | 1 - src/adRendering.js | 81 ++++++++--- src/adapterManager.js | 27 +++- src/adapters/bidderFactory.js | 2 + src/auction.js | 7 +- src/auctionManager.js | 5 +- src/prebid.js | 52 +++---- src/secureCreatives.js | 10 +- src/utils.js | 8 +- test/spec/auctionmanager_spec.js | 39 +++++- test/spec/modules/bidViewability_spec.js | 26 ++-- .../modules/prebidServerBidAdapter_spec.js | 12 ++ test/spec/modules/topLevelPaapi_spec.js | 1 - test/spec/unit/adRendering_spec.js | 98 +++++++++++-- test/spec/unit/core/adapterManager_spec.js | 59 +++++++- test/spec/unit/core/bidderFactory_spec.js | 129 +++++++++++++----- test/spec/unit/pbjs_api_spec.js | 77 +++-------- test/spec/unit/secureCreatives_spec.js | 3 - 23 files changed, 451 insertions(+), 243 deletions(-) diff --git a/libraries/dfpUtils/dfpUtils.js b/libraries/dfpUtils/dfpUtils.js index d7df13824c7..4b957eb4999 100644 --- a/libraries/dfpUtils/dfpUtils.js +++ b/libraries/dfpUtils/dfpUtils.js @@ -1,3 +1,5 @@ +import {gdprDataHandler} from '../../src/consentHandler.js'; + /** Safe defaults which work on pretty much all video calls. */ export const DEFAULT_DFP_PARAMS = { env: 'vp', @@ -12,9 +14,13 @@ export const DFP_ENDPOINT = { pathname: '/gampad/ads' } -export const setGdprConsent = (gdprConsent, queryParams) => { - if (!gdprConsent) { return; } - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } +export function gdprParams() { + const gdprConsent = gdprDataHandler.getConsentData(); + const params = {}; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { params.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { params.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { params.addtl_consent = gdprConsent.addtlConsent; } + } + return params; } diff --git a/modules/bidViewability.js b/modules/bidViewability.js index c1aab83b7e6..3bfcd93174a 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -4,11 +4,12 @@ import {config} from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; +import {EVENTS} from '../src/constants.js'; import {isFn, logWarn, triggerPixel} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; +import adapterManager, {gppDataHandler, uspDataHandler} from '../src/adapterManager.js'; import {find} from '../src/polyfill.js'; +import {gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; const MODULE_NAME = 'bidViewability'; const CONFIG_ENABLED = 'enabled'; @@ -32,14 +33,7 @@ export let getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => { export let fireViewabilityPixels = (globalModuleConfig, bid) => { if (globalModuleConfig[CONFIG_FIRE_PIXELS] === true && bid.hasOwnProperty(BID_VURL_ARRAY)) { - let queryParams = {}; - - const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } - if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } - if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } - } + let queryParams = gdprParams(); const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } @@ -67,7 +61,6 @@ export let logWinningBidNotFound = (slot) => { export let impressionViewableHandler = (globalModuleConfig, slot, event) => { let respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot); - let respectiveDeferredAdUnit = getGlobal().adUnits.find(adUnit => adUnit.deferBilling && respectiveBid.adUnitCode === adUnit.code); if (respectiveBid === null) { logWinningBidNotFound(slot); @@ -77,8 +70,8 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { // trigger respective bidder's onBidViewable handler adapterManager.callBidViewableBidder(respectiveBid.adapterCode || respectiveBid.bidder, respectiveBid); - if (respectiveDeferredAdUnit) { - adapterManager.callBidBillableBidder(respectiveBid); + if (respectiveBid.deferBilling) { + adapterManager.triggerBilling(respectiveBid); } // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 367520870e3..3e1a716d8e7 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,10 +2,8 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import { DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent } from '../libraries/dfpUtils/dfpUtils.js'; import { getSignals } from '../libraries/gptUtils/gptUtils.js'; import { registerVideoSupport } from '../src/adServerManager.js'; -import { gdprDataHandler } from '../src/adapterManager.js'; import { getPPID } from '../src/adserver.js'; import { auctionManager } from '../src/auctionManager.js'; import { config } from '../src/config.js'; @@ -24,6 +22,7 @@ import { parseSizesInput, parseUrl } from '../src/utils.js'; +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; /** * @typedef {Object} DfpVideoParams * @@ -108,13 +107,12 @@ export function buildDfpVideoUrl(options) { urlComponents.search, derivedParams, options.params, - { cust_params: encodedCustomParams } + { cust_params: encodedCustomParams }, + gdprParams() ); const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } - const gdprConsent = gdprDataHandler.getConsentData(); - setGdprConsent(gdprConsent, queryParams); if (!queryParams.ppid) { const ppid = getPPID(); diff --git a/modules/dfpAdpod.js b/modules/dfpAdpod.js index 1675954459c..d443e770d87 100644 --- a/modules/dfpAdpod.js +++ b/modules/dfpAdpod.js @@ -1,8 +1,7 @@ import {submodule} from '../src/hook.js'; import {buildUrl, deepAccess, formatQS, logError, parseSizesInput} from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; -import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, setGdprConsent} from '../libraries/dfpUtils/dfpUtils.js'; -import {gdprDataHandler} from '../src/consentHandler.js'; +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; import {registerVideoSupport} from '../src/adServerManager.js'; export const adpodUtils = {}; @@ -75,12 +74,10 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { DEFAULT_DFP_PARAMS, derivedParams, params, - { cust_params: encodedCustomParams } + { cust_params: encodedCustomParams }, + gdprParams(), ); - const gdprConsent = gdprDataHandler.getConsentData(); - setGdprConsent(gdprConsent, queryParams); - const masterTag = buildUrl({ ...DFP_ENDPOINT, search: queryParams diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 01da91b57a7..31ea363c9df 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -102,7 +102,10 @@ const PBS_CONVERTER = ortbConverter({ transactionId: context.adUnit.transactionId, adUnitId: context.adUnit.adUnitId, auctionId: context.bidderRequest.auctionId, - }), bidResponse), + }), bidResponse, { + deferRendering: !!context.adUnit.deferBilling, + deferBilling: !!context.adUnit.deferBilling + }), adUnit: context.adUnit.code }; }, diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js index 040c0125b3a..86164668a90 100644 --- a/modules/topLevelPaapi.js +++ b/modules/topLevelPaapi.js @@ -96,7 +96,6 @@ export function getRenderingDataHook(next, bid, options) { export function markWinningBidHook(next, bid) { if (isPaapiBid(bid)) { - bid.status = BID_STATUS.RENDERED; emit(EVENTS.BID_WON, bid); next.bail(); } else { diff --git a/src/adRendering.js b/src/adRendering.js index e4acfd2d69a..6f268ca1aec 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -19,6 +19,7 @@ import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; import {GreedyPromise} from './utils/promise.js'; import adapterManager from './adapterManager.js'; +import {useMetrics} from './utils/perfMetrics.js'; const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; @@ -167,32 +168,70 @@ doRender.before(function (next, args) { }, 100) export function handleRender({renderFn, resizeFn, adId, options, bidResponse, doc}) { + deferRendering(bidResponse, () => { + if (bidResponse == null) { + emitAdRenderFail({ + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + message: `Cannot find ad '${adId}'`, + id: adId + }); + return; + } + if (bidResponse.status === BID_STATUS.RENDERED) { + logWarn(`Ad id ${adId} has been rendered before`); + events.emit(STALE_RENDER, bidResponse); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + return; + } + } + try { + doRender({renderFn, resizeFn, bidResponse, options, doc}); + } catch (e) { + emitAdRenderFail({ + reason: AD_RENDER_FAILED_REASON.EXCEPTION, + message: e.message, + id: adId, + bid: bidResponse + }); + } + }) +} + +export function markBidAsRendered(bidResponse) { + const metrics = useMetrics(bidResponse.metrics); + metrics.checkpoint('bidRender'); + metrics.timeBetween('bidWon', 'bidRender', 'render.deferred'); + metrics.timeBetween('auctionEnd', 'bidRender', 'render.pending'); + metrics.timeBetween('requestBids', 'bidRender', 'render.e2e'); + bidResponse.status = BID_STATUS.RENDERED; +} + +const DEFERRED_RENDER = new WeakMap(); +const WINNERS = new WeakSet(); + +export function deferRendering(bidResponse, renderFn) { if (bidResponse == null) { - emitAdRenderFail({ - reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - message: `Cannot find ad '${adId}'`, - id: adId - }); + // if the bid is missing, let renderFn deal with it now + renderFn(); return; } - if (bidResponse.status === BID_STATUS.RENDERED) { - logWarn(`Ad id ${adId} has been rendered before`); - events.emit(STALE_RENDER, bidResponse); - if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { - return; - } + DEFERRED_RENDER.set(bidResponse, renderFn); + if (!bidResponse.deferRendering) { + renderIfDeferred(bidResponse); } - try { - doRender({renderFn, resizeFn, bidResponse, options, doc}); - } catch (e) { - emitAdRenderFail({ - reason: AD_RENDER_FAILED_REASON.EXCEPTION, - message: e.message, - id: adId, - bid: bidResponse - }); + if (!WINNERS.has(bidResponse)) { + WINNERS.add(bidResponse); + markWinningBid(bidResponse); + } +} + +export function renderIfDeferred(bidResponse) { + const renderFn = DEFERRED_RENDER.get(bidResponse); + if (renderFn) { + renderFn(); + markBidAsRendered(bidResponse); + DEFERRED_RENDER.delete(bidResponse); } - markWinningBid(bidResponse); } export function renderAdDirect(doc, adId, options) { diff --git a/src/adapterManager.js b/src/adapterManager.js index 63ecfc280be..c39ef039af3 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -10,6 +10,7 @@ import { getUniqueIdentifierStr, getUserConfiguredParams, groupBy, + internal, isArray, isPlainObject, isValidMediaTypes, @@ -30,13 +31,15 @@ import {find, includes} from './polyfill.js'; import { getBidderRequestsCounter, getBidderWinsCounter, - getRequestsCounter, incrementBidderRequestsCounter, - incrementBidderWinsCounter, incrementRequestsCounter + getRequestsCounter, + incrementBidderRequestsCounter, + incrementBidderWinsCounter, + incrementRequestsCounter } from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js'; import * as events from './events.js'; -import { EVENTS, S2S } from './constants.js'; +import {EVENTS, S2S} from './constants.js'; import {useMetrics} from './utils/perfMetrics.js'; import {auctionManager} from './auctionManager.js'; import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, MODULE_TYPE_PREBID} from './activities/modules.js'; @@ -134,6 +137,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics} bidRequestsCount: getRequestsCounter(adUnit.code), bidderRequestsCount: getBidderRequestsCounter(adUnit.code, bid.bidder), bidderWinsCount: getBidderWinsCounter(adUnit.code, bid.bidder), + deferBilling: !!adUnit.deferBilling })); return bids; }, []) @@ -644,7 +648,7 @@ function invokeBidderMethod(bidder, method, spec, fn, ...params) { } function tryCallBidderMethod(bidder, method, param) { - if (param?.src !== S2S.SRC) { + if (param?.source !== S2S.SRC) { const target = getBidderMethod(bidder, method); if (target != null) { invokeBidderMethod(bidder, method, ...target, param); @@ -673,9 +677,18 @@ adapterManager.callBidWonBidder = function(bidder, bid, adUnits) { tryCallBidderMethod(bidder, 'onBidWon', bid); }; -adapterManager.callBidBillableBidder = function(bid) { - tryCallBidderMethod(bid.bidder, 'onBidBillable', bid); -}; +adapterManager.triggerBilling = (() => { + const BILLED = new WeakSet(); + return (bid) => { + if (!BILLED.has(bid)) { + BILLED.add(bid); + if (bid.source === S2S.SRC && bid.burl) { + internal.triggerPixel(bid.burl); + } + tryCallBidderMethod(bid.bidder, 'onBidBillable', bid); + } + } +})(); adapterManager.callSetTargetingBidder = function(bidder, bid) { tryCallBidderMethod(bidder, 'onSetTargeting', bid); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index ac26883ae99..4826e5fd17a 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -323,6 +323,8 @@ export function newBidder(spec) { bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); + bid.deferBilling = bidRequest.deferBilling; + bid.deferRendering = bid.deferBilling && (bid.deferRendering ?? typeof spec.onBidBillable !== 'function'); const prebidBid = Object.assign(createBid(STATUS.GOOD, bidRequest), bid, pick(bidRequest, TIDS)); addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { diff --git a/src/auction.js b/src/auction.js index b422ffa7333..a8411b3fb25 100644 --- a/src/auction.js +++ b/src/auction.js @@ -66,7 +66,6 @@ */ import { - callBurl, deepAccess, generateUUID, getValue, @@ -370,11 +369,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } function addWinningBid(winningBid) { - const winningAd = adUnits.find(adUnit => adUnit.adUnitId === winningBid.adUnitId); _winningBids = _winningBids.concat(winningBid); - callBurl(winningBid); adapterManager.callBidWonBidder(winningBid.adapterCode || winningBid.bidder, winningBid, adUnits); - if (winningAd && !winningAd.deferBilling) adapterManager.callBidBillableBidder(winningBid); + if (!winningBid.deferBilling) { + adapterManager.triggerBilling(winningBid) + } } function setBidTargeting(bid) { diff --git a/src/auctionManager.js b/src/auctionManager.js index a1ab1a859da..27a2c2beaf2 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -73,11 +73,10 @@ export function newAuctionManager() { auctionManager.addWinningBid = function(bid) { const metrics = useMetrics(bid.metrics); metrics.checkpoint('bidWon'); - metrics.timeBetween('auctionEnd', 'bidWon', 'render.pending'); - metrics.timeBetween('requestBids', 'bidWon', 'render.e2e'); + metrics.timeBetween('auctionEnd', 'bidWon', 'adserver.pending'); + metrics.timeBetween('requestBids', 'bidWon', 'adserver.e2e'); const auction = getAuction(bid.auctionId); if (auction) { - bid.status = BID_STATUS.RENDERED; auction.addWinningBid(bid); } else { logWarn(`Auction not found when adding winning bid`); diff --git a/src/prebid.js b/src/prebid.js index 04e1bedf2f9..18a9127a793 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -39,7 +39,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; -import {insertLocatorFrame, renderAdDirect} from './adRendering.js'; +import {insertLocatorFrame, markBidAsRendered, renderAdDirect, renderIfDeferred} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; import {fillVideoDefaults, validateOrtbVideoFields} from './video.js'; @@ -886,32 +886,22 @@ if (FEATURES.VIDEO) { * * @alias module:pbjs.markWinningBidAsUsed */ - pbjsInstance.markWinningBidAsUsed = function (markBidRequest) { - const bids = fetchReceivedBids(markBidRequest, 'Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); - + pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode}) { + let bids; + if (adUnitCode && adId == null) { + bids = targeting.getWinningBids(adUnitCode); + } else if (adId) { + bids = auctionManager.getBidsReceived().filter(bid => bid.adId === adId) + } else { + logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); + } if (bids.length > 0) { auctionManager.addWinningBid(bids[0]); + markBidAsRendered(bids[0]) } } } -const fetchReceivedBids = (bidRequest, warningMessage) => { - let bids = []; - - if (bidRequest.adUnitCode && bidRequest.adId) { - bids = auctionManager.getBidsReceived() - .filter(bid => bid.adId === bidRequest.adId && bid.adUnitCode === bidRequest.adUnitCode); - } else if (bidRequest.adUnitCode) { - bids = targeting.getWinningBids(bidRequest.adUnitCode); - } else if (bidRequest.adId) { - bids = auctionManager.getBidsReceived().filter(bid => bid.adId === bidRequest.adId); - } else { - logWarn(warningMessage); - } - - return bids; -}; - /** * Get Prebid config options * @param {Object} options @@ -993,19 +983,13 @@ pbjsInstance.processQueue = function () { /** * @alias module:pbjs.triggerBilling */ -pbjsInstance.triggerBilling = (winningBid) => { - const bids = fetchReceivedBids(winningBid, 'Improper use of triggerBilling. It requires a bid with at least an adUnitCode or an adId to function.'); - const triggerBillingBid = bids.find(bid => bid.requestId === winningBid.requestId) || bids[0]; - - if (bids.length > 0 && triggerBillingBid) { - try { - adapterManager.callBidBillableBidder(triggerBillingBid); - } catch (e) { - logError('Error when triggering billing :', e); - } - } else { - logWarn('The bid provided to triggerBilling did not match any bids received.'); - } +pbjsInstance.triggerBilling = ({adId, adUnitCode}) => { + auctionManager.getAllWinningBids() + .filter((bid) => bid.adId === adId || (adId == null && bid.adUnitCode === adUnitCode)) + .forEach((bid) => { + adapterManager.triggerBilling(bid); + renderIfDeferred(bid); + }); }; export default pbjsInstance; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 630d4986c3e..a69585d28e8 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -7,7 +7,7 @@ import {getAllAssetsMessage, getAssetMessage} from './native.js'; import {BID_STATUS, MESSAGES} from './constants.js'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; import {find, includes} from './polyfill.js'; -import {getBidToRender, handleCreativeEvent, handleNativeMessage, handleRender, markWinningBid} from './adRendering.js'; +import {deferRendering, getBidToRender, handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; @@ -103,16 +103,12 @@ function handleNativeRequest(reply, data, adObject) { return; } - if (adObject.status !== BID_STATUS.RENDERED) { - markWinningBid(adObject); - } - switch (data.action) { case 'assetRequest': - reply(getAssetMessage(data, adObject)); + deferRendering(adObject, () => reply(getAssetMessage(data, adObject))); break; case 'allAssetRequest': - reply(getAllAssetsMessage(data, adObject)); + deferRendering(adObject, () => reply(getAllAssetsMessage(data, adObject))); break; default: handleNativeMessage(data, adObject, {resizeFn: getResizer(data.adId, adObject)}) diff --git a/src/utils.js b/src/utils.js index 64880b4a462..b30702af1a0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ import {config} from './config.js'; import {klona} from 'klona/json'; import {includes} from './polyfill.js'; -import { EVENTS, S2S } from './constants.js'; +import {EVENTS} from './constants.js'; import {GreedyPromise} from './utils/promise.js'; import {getGlobal} from './prebidGlobal.js'; import { default as deepAccess } from 'dlv/index.js'; @@ -474,12 +474,6 @@ export function triggerPixel(url, done, timeout) { img.src = url; } -export function callBurl({ source, burl }) { - if (source === S2S.SRC && burl) { - internal.triggerPixel(burl); - } -} - /** * Inserts an empty iframe with the specified `html`, primarily used for tracking purposes * (though could be for other purposes) diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index ab00ac86d98..3f9d1cba5f8 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -5,7 +5,7 @@ import { adjustBids, getMediaTypeGranularity, getPriceByGranularity, - addBidResponse, resetAuctionState, responsesReady + addBidResponse, resetAuctionState, responsesReady, newAuction } from 'src/auction.js'; import { EVENTS, TARGETING_KEYS, S2S } from 'src/constants.js'; import * as auctionModule from 'src/auction.js'; @@ -28,6 +28,7 @@ import '../../modules/currency.js' import { setConfig as setCurrencyConfig } from '../../modules/currency.js'; import { REJECTION_REASON } from '../../src/constants.js'; import { setDocumentHidden } from './unit/utils/focusTimeout_spec.js'; +import {sandbox} from 'sinon'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -1738,6 +1739,42 @@ describe('auctionmanager.js', function () { }); }); + describe('addWinningBid', () => { + let auction, bid, adUnits, sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(adapterManager, 'callBidWonBidder'); + sandbox.stub(adapterManager, 'triggerBilling') + adUnits = [{code: 'au1'}, {code: 'au2'}] + auction = newAuction({adUnits}); + bid = { + bidder: 'mock-bidder' + }; + }) + afterEach(() => { + sandbox.restore(); + }); + + it('should call bidWon', () => { + auction.addWinningBid(bid); + sinon.assert.calledWith(adapterManager.callBidWonBidder, bid.bidder, bid, adUnits); + }); + + [undefined, false].forEach(deferBilling => { + it(`should call onBidBillable if deferBilling = ${deferBilling}`, () => { + bid.deferBilling = deferBilling; + auction.addWinningBid(bid); + sinon.assert.calledWith(adapterManager.triggerBilling, bid); + }); + }) + + it('should NOT call onBidBillable if deferBilling = true', () => { + bid.deferBilling = true; + auction.addWinningBid(bid); + sinon.assert.notCalled(adapterManager.triggerBilling); + }) + }) + function mockAuction(getBidRequests, start = 1) { return { getBidRequests: getBidRequests, diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js index 1df9aecf73a..5dbf7d84c3c 100644 --- a/test/spec/modules/bidViewability_spec.js +++ b/test/spec/modules/bidViewability_spec.js @@ -245,7 +245,7 @@ describe('#bidViewability', function() { let logWinningBidNotFoundSpy; let callBidViewableBidderSpy; let winningBidsArray; - let callBidBillableBidderSpy; + let triggerBillingSpy; let adUnits = [ { 'code': 'abc123', @@ -262,7 +262,7 @@ describe('#bidViewability', function() { triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); eventsEmitSpy = sandbox.spy(events, ['emit']); callBidViewableBidderSpy = sandbox.spy(adapterManager, ['callBidViewableBidder']); - callBidBillableBidderSpy = sandbox.spy(adapterManager, ['callBidBillableBidder']); + triggerBillingSpy = sandbox.spy(adapterManager, ['triggerBilling']); // mocking winningBidsArray winningBidsArray = []; sandbox.stub(prebidGlobal, 'getGlobal').returns({ @@ -307,22 +307,16 @@ describe('#bidViewability', function() { expect(eventsEmitSpy.callCount).to.equal(0); }); - it('should call the callBidBillableBidder function if the viewable bid is associated with an ad unit with deferBilling set to true', function() { + it('should call the triggerBilling function if the viewable bid has deferBilling set to true', function() { let moduleConfig = {}; - const deferredBillingAdUnit = { - 'code': '/harshad/Jan/2021/', - 'deferBilling': true, - 'bids': [ - { - 'bidder': 'pubmatic' - } - ] - }; - adUnits.push(deferredBillingAdUnit); - winningBidsArray.push(PBJS_WINNING_BID); + const bid = { + ...PBJS_WINNING_BID, + deferBilling: true + } + winningBidsArray.push(bid); bidViewability.impressionViewableHandler(moduleConfig, GPT_SLOT, null); - expect(callBidBillableBidderSpy.callCount).to.equal(1); - sinon.assert.calledWith(callBidBillableBidderSpy, PBJS_WINNING_BID); + expect(triggerBillingSpy.callCount).to.equal(1); + sinon.assert.calledWith(triggerBillingSpy, bid); }); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index d8e52a9beec..dcd03a8882b 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3510,6 +3510,18 @@ describe('S2S Adapter', function () { expect(response).to.have.property('adapterCode', 'appnexus2'); }); + it('should set deferBilling and deferRendering to true when request has deferBilling = true', () => { + config.setConfig({ CONFIG }); + const req = deepClone(REQUEST); + req.ad_units.forEach(au => au.deferBilling = true); + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); + sinon.assert.match(addBidResponse.firstCall.args[1], { + deferBilling: true, + deferRendering: true + }); + }); + describe('on sync requested with no cookie', () => { let cfg, req, csRes; diff --git a/test/spec/modules/topLevelPaapi_spec.js b/test/spec/modules/topLevelPaapi_spec.js index e2cad9593e9..d3c7966b235 100644 --- a/test/spec/modules/topLevelPaapi_spec.js +++ b/test/spec/modules/topLevelPaapi_spec.js @@ -489,7 +489,6 @@ describe('topLevelPaapi', () => { markWinningBidHook(next, bid); sinon.assert.notCalled(next); sinon.assert.called(next.bail); - expect(bid.status).to.eql(BID_STATUS.RENDERED); sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bid); }); it('ignores non-paapi bids', () => { diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index 971e92224e9..c3bebf45350 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -1,11 +1,14 @@ import * as events from 'src/events.js'; import * as utils from 'src/utils.js'; import { - doRender, emitAdRenderSucceeded, getBidToRender, + deferRendering, + doRender, + getBidToRender, + emitAdRenderSucceeded, getRenderingData, handleCreativeEvent, handleNativeMessage, - handleRender + handleRender, markWinningBid, renderIfDeferred } from '../../../src/adRendering.js'; import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; import {expect} from 'chai/index.mjs'; @@ -164,6 +167,91 @@ describe('adRendering', () => { }) }); + describe('deferRendering', () => { + let fn, markWin; + function markWinHook(next, bidResponse) { + markWin(bidResponse); + } + before(() => { + markWinningBid.before(markWinHook); + }) + after(() => { + markWinningBid.getHooks({hook: markWinHook}).remove(); + }) + beforeEach(() => { + fn = sinon.stub(); + markWin = sinon.stub(); + }); + + [null, undefined].forEach((bidResponse) => { + it(`should run fn immediately if bidResponse is ${bidResponse}`, () => { + deferRendering(bidResponse, fn); + sinon.assert.called(fn); + }); + }); + + [undefined, false].forEach(defer => { + describe(`when bid has deferRendering = ${defer}`, () => { + if (defer != null) { + beforeEach(() => { bidResponse.deferRendering = defer }) + } + it('should run fn and mark bid as rendered', () => { + deferRendering(bidResponse, fn); + sinon.assert.called(fn); + expect(bidResponse.status).to.equal(BID_STATUS.RENDERED); + }); + }); + }); + + describe('when bid is marked for deferred rendering', () => { + beforeEach(() => { + bidResponse.deferRendering = true; + }); + it('should not run fn and not mark bid as rendered', () => { + deferRendering(bidResponse, fn); + sinon.assert.notCalled(fn); + expect(bidResponse.status).to.not.equal(BID_STATUS.RENDERED); + }); + + it('should render on subsequent call to renderIfDeferred', () => { + deferRendering(bidResponse, fn); + renderIfDeferred(bidResponse); + sinon.assert.called(fn); + expect(bidResponse.status).to.eql(BID_STATUS.RENDERED); + }); + + it('should not render again if renderIfDeferred is called multiple times', () => { + deferRendering(bidResponse, fn); + renderIfDeferred(bidResponse); + renderIfDeferred(bidResponse); + sinon.assert.calledOnce(fn); + }); + }); + + it('should run fn if bid is not marked for deferral', () => { + deferRendering(bidResponse, fn); + }); + [true, false].forEach(defer => { + it(`should mark bid as winning (deferRendering = ${defer})`, () => { + bidResponse.deferRendering = defer; + deferRendering(bidResponse, fn); + sinon.assert.calledWith(markWin, bidResponse); + }); + + it('should not mark a winner twice', () => { + bidResponse.deferRendering = defer; + deferRendering(bidResponse, fn); + deferRendering(bidResponse, fn); + sinon.assert.calledOnce(markWin); + }) + }) + }); + describe('renderIfDeferred', () => { + it('should not choke on unmarked bids', () => { + renderIfDeferred(bidResponse); + expect(bidResponse.status).to.not.equal(BID_STATUS.RENDERED); + }) + }); describe('handleRender', () => { let doRenderStub function doRenderHook(next, ...args) { @@ -210,12 +298,6 @@ describe('adRendering', () => { sinon.assert.notCalled(doRenderStub); }) }); - - it('should mark bid as won and emit BID_WON', () => { - handleRender({renderFn, bidResponse}); - sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bidResponse); - sinon.assert.calledWith(auctionManager.addWinningBid, bidResponse); - }) }) }) diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 3974cfde68f..852c84263e9 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -82,6 +82,7 @@ describe('adapterManager tests', function () { let orgPrebidServerAdapter; let orgRubiconAdapter; let orgBadBidderAdapter; + let sandbox; before(function () { orgAppnexusAdapter = adapterManager.bidderRegistry['appnexus']; orgAdequantAdapter = adapterManager.bidderRegistry['adequant']; @@ -99,8 +100,12 @@ describe('adapterManager tests', function () { config.setConfig({s2sConfig: { enabled: false }}); }); + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); afterEach(() => { s2sTesting.clientTestBidders.clear(); + sandbox.restore(); }); describe('callBids', function () { @@ -392,12 +397,52 @@ describe('adapterManager tests', function () { }); it('should NOT call onBidWon when the bid is S2S', () => { - bids[0].src = S2S.SRC + bids[0].source = S2S.SRC adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); sinon.assert.notCalled(criteoSpec.onBidWon); }) }); + describe('triggerBilling', () => { + beforeEach(() => { + criteoSpec.onBidBillable = sinon.spy(); + sandbox.stub(utils.internal, 'triggerPixel'); + }); + describe('on client bids', () => { + it('should call bidder\'s onBidBillable, and ignore burl', () => { + adapterManager.triggerBilling(bids[0]); + sinon.assert.called(criteoSpec.onBidBillable); + sinon.assert.notCalled(utils.internal.triggerPixel) + }); + it('should not call again on second trigger', () => { + adapterManager.triggerBilling(bids[0]); + adapterManager.triggerBilling(bids[0]); + sinon.assert.calledOnce(criteoSpec.onBidBillable); + }); + }) + describe('on s2s bids', () => { + beforeEach(() => { + bids[0].source = S2S.SRC; + }); + it('should call burl and not onBidBillable', () => { + bids[0].burl = 'burl'; + adapterManager.triggerBilling(bids[0]); + sinon.assert.notCalled(criteoSpec.onBidBillable); + sinon.assert.calledWith(utils.internal.triggerPixel, 'burl'); + }); + it('should not call burl if not present', () => { + adapterManager.triggerBilling(bids[0]); + sinon.assert.notCalled(utils.internal.triggerPixel); + }); + it('should not call burl again on second triggerBilling', () => { + bids[0].burl = 'burl'; + adapterManager.triggerBilling(bids[0]); + adapterManager.triggerBilling(bids[0]); + sinon.assert.calledOnce(utils.internal.triggerPixel) + }); + }); + }) + describe('onSetTargeting', function () { beforeEach(() => { criteoSpec.onSetTargeting = sinon.stub() @@ -409,7 +454,7 @@ describe('adapterManager tests', function () { }); it('should NOT call onSetTargeting when bid is S2S', () => { - bids[0].src = S2S.SRC; + bids[0].source = S2S.SRC; adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); sinon.assert.notCalled(criteoSpec.onSetTargeting); }) @@ -423,7 +468,7 @@ describe('adapterManager tests', function () { sinon.assert.called(criteoSpec.onBidViewable); }); it('should NOT call onBidViewable when bid is S2S', () => { - bids[0].src = S2S.SRC; + bids[0].source = S2S.SRC; adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); sinon.assert.notCalled(criteoSpec.onBidViewable); }) @@ -1745,6 +1790,14 @@ describe('adapterManager tests', function () { expect(sizes1).not.to.deep.equal(sizes2); }); + it('should transfer deferBilling from ad unit', () => { + adUnits[0].deferBilling = true; + const requests = makeBidRequests(); + requests.flatMap(req => req.bids).forEach(bidRequest => { + expect(bidRequest.deferBilling).to.equal(bidRequest.adUnitCode === adUnits[0].code); + }) + }) + it('should set and increment bidRequestsCounter', () => { const [au1, au2] = adUnits; makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 56668759db6..1aab16a5e46 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -639,48 +639,103 @@ describe('bidderFactory', () => { expect(doneStub.calledOnce).to.equal(true); }); - it('should only add bids for valid adUnit code into the auction, even if the bidder doesn\'t bid on all of them', function () { - const bidder = newBidder(spec); + describe('when interpretResponse returns a bid', () => { + let bid, bidderRequest; + beforeEach(() => { + bid = { + creativeId: 'creative-id', + requestId: '1', + ad: 'ad-url.com', + cpm: 0.5, + height: 200, + width: 300, + adUnitCode: 'mock/placement', + currency: 'USD', + netRevenue: true, + ttl: 300, + bidderCode: 'sampleBidder', + sampleBidder: {advertiserId: '12345', networkId: '111222'} + } + bidderRequest = utils.deepClone(MOCK_BIDS_REQUEST); + bidderRequest.bids[0].bidder = 'sampleBidder'; + }) - const bid = { - creativeId: 'creative-id', - requestId: '1', - ad: 'ad-url.com', - cpm: 0.5, - height: 200, - width: 300, - adUnitCode: 'mock/placement', - currency: 'USD', - netRevenue: true, - ttl: 300, - bidderCode: 'sampleBidder', - sampleBidder: {advertiserId: '12345', networkId: '111222'} - }; - const bidderRequest = Object.assign({}, MOCK_BIDS_REQUEST); - bidderRequest.bids[0].bidder = 'sampleBidder'; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + function getAuctionBid() { + const bidder = newBidder(spec); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); + spec.interpretResponse.returns(bid); + bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + return addBidResponseStub.firstCall.args[1]; + } + + function setDeferredBilling(deferredBilling = true) { + bidderRequest.bids.forEach(bid => { bid.deferBilling = deferredBilling }); + } + + it('should only add bids for valid adUnit code into the auction, even if the bidder doesn\'t bid on all of them', function () { + const auctionBid = getAuctionBid(); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + // checking the fields added by our code + expect(auctionBid.originalCpm).to.equal(bid.cpm); + expect(auctionBid.originalCurrency).to.equal(bid.currency); + expect(doneStub.calledOnce).to.equal(true); + expect(logErrorSpy.callCount).to.equal(0); + expect(auctionBid.meta).to.exist; + expect(auctionBid.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); }); - spec.getUserSyncs.returns([]); - spec.interpretResponse.returns(bid); + describe('if request has deferBilling = true', () => { + beforeEach(() => setDeferredBilling(true)); - bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should set response.deferBilling = true, regardless of what the adapter says', () => { + bid.deferBilling = false; + expect(getAuctionBid().deferBilling).to.be.true; + }); + [ + { + shouldDefer: true + }, + { + deferRendering: false, + shouldDefer: false + }, + { + onBidBillable: true, + shouldDefer: false, + }, + { + onBidBillable: true, + deferRendering: true, + shouldDefer: true + } + ].forEach(({onBidBillable, deferRendering, shouldDefer}) => { + it(`sets response deferRendering = ${shouldDefer} when adapter ${onBidBillable ? 'supports' : 'does not support'} onBidBillable, and sayd deferRender = ${deferRendering}`, () => { + if (onBidBillable) { + spec.onBidBillable = sinon.stub(); + } + bid.deferRendering = deferRendering; + expect(getAuctionBid().deferRendering).to.equal(shouldDefer); + }); + }) + }); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - let bidObject = addBidResponseStub.firstCall.args[1]; - // checking the fields added by our code - expect(bidObject.originalCpm).to.equal(bid.cpm); - expect(bidObject.originalCurrency).to.equal(bid.currency); - expect(doneStub.calledOnce).to.equal(true); - expect(logErrorSpy.callCount).to.equal(0); - expect(bidObject.meta).to.exist; - expect(bidObject.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); - }); + describe('if request has deferBilling = false', () => { + beforeEach(() => setDeferredBilling(false)); + [true, false].forEach(deferredRender => { + it(`should set deferRendering = false when adapter says deferRendering = ${deferredRender}`, () => { + bid.deferRendering = deferredRender; + expect(getAuctionBid().deferRendering).to.be.false; + }); + }); + }); + }) it('should call spec.getUserSyncs() with the response', function () { const bidder = newBidder(spec); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0c450c7f1ae..5a08447ce95 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1413,18 +1413,14 @@ describe('Unit: Prebid Module', function () { spyAddWinningBid.resetHistory(); onWonEvent.resetHistory(); onStaleEvent.resetHistory(); - - // Second render should have a warning but still added to winning bids + doc.write.resetHistory(); return renderAd(doc, bidId); }).then(() => { + // Second render should have a warning but still be rendered sinon.assert.calledWith(spyLogMessage, message); sinon.assert.calledWith(spyLogWarn, warning); - - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - - sinon.assert.calledWith(onWonEvent, adResponse); sinon.assert.calledWith(onStaleEvent, adResponse); + sinon.assert.called(doc.write); // Clean up $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); @@ -3711,68 +3707,29 @@ describe('Unit: Prebid Module', function () { auctionManagerStub.returns(bidsReceived) let bids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids(); - expect(bids.length).to.equal(1); + expect(bids.length).to.equal(1); sandbox expect(bids[0].adId).to.equal('adid-1'); }); }); describe('deferred billing', function () { - const sandbox = sinon.createSandbox(); - - let adUnits = [ - { - code: 'adUnit-code-1', - mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - adUnitId: '1234567890', - bids: [ - { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1' } - ] - }, - { - code: 'adUnit-code-2', - deferBilling: true, - mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - adUnitId: '0987654321', - bids: [ - { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2' } - ] - } - ]; - - let winningBid1 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', adUnitId: '1234567890', adId: 'abcdefg' } - let winningBid2 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2', adUnitId: '0987654321' } - let adUnitCodes = ['adUnit-code-1', 'adUnit-code-2']; - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 2000}); + let bid; beforeEach(function () { - sandbox.spy(adapterManager, 'callBidWonBidder'); - sandbox.spy(adapterManager, 'callBidBillableBidder'); - sandbox.stub(auctionManager, 'getBidsReceived').returns([winningBid1]); - }); - - afterEach(function () { - sandbox.resetHistory(); - sandbox.restore(); + bid = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', adUnitId: '1234567890', adId: 'abcdefg' }; + sandbox.spy(adapterManager, 'triggerBilling'); + sandbox.stub(auctionManager, 'getAllWinningBids').returns([bid]); }); - it('should by default invoke callBidWonBidder and callBidBillableBidder', function () { - auction.addWinningBid(winningBid1); - sinon.assert.calledOnce(adapterManager.callBidWonBidder); - sinon.assert.calledOnce(adapterManager.callBidBillableBidder); - }); - - it('should only invoke callBidWonBidder and NOT callBidBillableBidder if deferBilling is present and true within the winning adUnit object', function () { - auction.addWinningBid(winningBid2); - sinon.assert.calledOnce(adapterManager.callBidWonBidder); - sinon.assert.notCalled(adapterManager.callBidBillableBidder); - }); - - it('should invoke callBidBillableBidder when pbjs.triggerBilling is invoked', function () { - $$PREBID_GLOBAL$$.triggerBilling(winningBid1); - sinon.assert.calledOnce(auctionManager.getBidsReceived); - sinon.assert.notCalled(adapterManager.callBidWonBidder); - sinon.assert.calledOnce(adapterManager.callBidBillableBidder); - }); + Object.entries({ + 'bid': () => bid, + 'adUnitCode': () => ({adUnitCode: bid.adUnitCode}) + }).forEach(([t, val]) => { + it(`should trigger billing when invoked with ${t}`, () => { + $$PREBID_GLOBAL$$.triggerBilling(val()); + sinon.assert.calledWith(adapterManager.triggerBilling, bid); + }) + }) }); describe('clearAllAuctions', () => { diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 95301302dab..64213073eee 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -209,11 +209,8 @@ describe('secureCreatives', () => { return receive(ev); }).then(() => { sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); sinon.assert.calledOnce(adResponse.renderer.render); sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); }); }); From 77b974db5dbe196fc27718614719534a8cf849e9 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Tue, 24 Sep 2024 21:50:18 +0200 Subject: [PATCH 0535/1097] Various modules: Send beacon wrapping fix (#12236) * #11838 send beacon fix * Update cwireBidAdapter.js * Update 33acrossAnalyticsAdapter.js --------- Co-authored-by: Marcin Komorski Co-authored-by: Patrick McCann --- modules/33acrossAnalyticsAdapter.js | 5 ++--- modules/cwireBidAdapter.js | 9 +++------ modules/sirdataRtdProvider.js | 9 ++------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js index 1de986cd28e..ec2e669be1a 100644 --- a/modules/33acrossAnalyticsAdapter.js +++ b/modules/33acrossAnalyticsAdapter.js @@ -5,6 +5,7 @@ import adapterManager, { coppaDataHandler, gdprDataHandler, gppDataHandler, uspD * @typedef {typeof import('../src/constants.js').EVENTS} EVENTS */ import { EVENTS } from '../src/constants.js'; +import { sendBeacon } from '../src/ajax.js'; /** @typedef {'pending'|'available'|'targetingSet'|'rendered'|'timeout'|'rejected'|'noBid'|'error'} BidStatus */ /** @@ -629,9 +630,7 @@ function setCachedBidStatus(auctionId, bidId, status) { * @param {string} endpoint URL */ function sendReport(report, endpoint) { - // TODO FIX THIS RULES VIOLATION - // eslint-disable-next-line - if (navigator.sendBeacon(endpoint, JSON.stringify(report))) { + if (sendBeacon(endpoint, JSON.stringify(report))) { log.info(`Analytics report sent to ${endpoint}`, report); return; diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index 6fbe401bfde..16370c6e911 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -3,6 +3,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {BANNER} from '../src/mediaTypes.js'; import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; +import { sendBeacon } from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -228,9 +229,7 @@ export const spec = { bid: bid } } - // TODO FIX THIS RULES VIOLATION - // eslint-disable-next-line prebid/no-member - navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, onBidderError: function (error, bidderRequest) { @@ -242,9 +241,7 @@ export const spec = { bidderRequest: bidderRequest } } - // TODO FIX THIS RULES VIOLATION - // eslint-disable-next-line prebid/no-member - navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 507d8d982f2..129d708cf8f 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -8,7 +8,7 @@ * @requires module:modules/realTimeData */ import adapterManager from '../src/adapterManager.js'; -import { ajax } from '../src/ajax.js'; +import { ajax, sendBeacon } from '../src/ajax.js'; import { deepAccess, checkCookieSupport, deepSetValue, hasDeviceAccess, inIframe, isEmpty, logError, logInfo, mergeDeep @@ -217,12 +217,7 @@ export function postContentForSemanticAnalysis(postContentToken, actualUrl) { if (payload && payload.length > 300 && payload.length < 300000) { const url = `https://contextual.sirdata.io/api/v1/push/contextual?post_content_token=${postContentToken}&url=${encodeURIComponent(actualUrl)}`; - // Use the Beacon API if supported to send the payload - if ('sendBeacon' in navigator) { - // TODO FIX RULES VIOLATION - // eslint-disable-next-line prebid/no-member - navigator.sendBeacon(url, payload); - } else { + if (!sendBeacon(url, payload)) { // Fallback to using AJAX if Beacon API is not supported ajax(url, {}, payload, { contentType: 'text/plain', From 2b5a7675cb3dc140851f802a9b2977e50a7c726e Mon Sep 17 00:00:00 2001 From: Rupesh Lakhani <35333377+AskRupert-DM@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:29:42 +0100 Subject: [PATCH 0536/1097] Ozone Project Bid Adapter: Support for auctionId and transactionId when a publisher opts in (#12267) * Update ozoneBidAdapter_spec.js * Update ozoneBidAdapter.js * Update ozoneBidAdapter_spec.js * Update ozoneBidAdapter_spec.js * Update ozoneBidAdapter.js updated device object values to come from ortb.device --- modules/ozoneBidAdapter.js | 49 +- test/spec/modules/ozoneBidAdapter_spec.js | 734 ++++++++++++++++++++++ 2 files changed, 761 insertions(+), 22 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 7a4a5a9717c..d3ba7fc0792 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -22,7 +22,7 @@ const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.3'; +const OZONEVERSION = '2.9.4'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], @@ -227,9 +227,13 @@ export const spec = { const getParams = this.getGetParametersAsObject(); const wlOztestmodeKey = whitelabelPrefix + 'testmode'; const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads - ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; + ozoneRequest.device = bidderRequest?.ortb2?.device || {}; let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string let schain = null; + var auctionId = deepAccess(validBidRequests, '0.ortb2.source.tid'); + if (auctionId === '0') { + auctionId = null; + } let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id @@ -329,6 +333,13 @@ export const spec = { if (gpid) { deepSetValue(obj, 'ext.gpid', gpid); } + let transactionId = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.tid'); + if (transactionId) { + obj.ext[whitelabelBidder].transactionId = transactionId; // this is the transactionId PER adUnit, common across bidders for this unit + } + if (auctionId) { + obj.ext[whitelabelBidder].auctionId = auctionId; // we were sent a valid auctionId to use - this will also be used as the root id value for the request + } if (fledgeEnabled) { // fledge is enabled at some config level - pbjs.setBidderConfig or pbjs.setConfig const auctionEnvironment = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.ae'); // this will be set for one of 3 reasons; adunit, setBidderConfig, setConfig if (isInteger(auctionEnvironment)) { @@ -407,22 +418,16 @@ export const spec = { } extObj[whitelabelBidder].cookieDeprecationLabel = deepAccess(bidderRequest, 'ortb2.device.ext.cdep', 'none'); logInfo('cookieDeprecationLabel from bidderRequest object = ' + extObj[whitelabelBidder].cookieDeprecationLabel); - let ozUuid = generateUUID(); let batchRequestsVal = this.getBatchRequests(); // false|numeric if (typeof batchRequestsVal === 'number') { logInfo('going to batch the requests'); let arrRet = []; // return an array of objects containing data describing max 10 bids for (let i = 0; i < tosendtags.length; i += batchRequestsVal) { - if (bidderRequest.auctionId) { - logInfo('Found bidderRequest.auctionId - will pass these values through & not generate our own id'); - ozoneRequest.id = bidderRequest.auctionId; - ozoneRequest.auctionId = bidderRequest.auctionId; - deepSetValue(ozoneRequest, 'source.tid', deepAccess(bidderRequest, 'ortb2.source.tid')); - } else { - logInfo('Did not find bidderRequest.auctionId - will generate our own id'); - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) - } + ozoneRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + if (auctionId) { + deepSetValue(ozoneRequest, 'source.tid', auctionId); + } ozoneRequest.imp = tosendtags.slice(i, i + batchRequestsVal); ozoneRequest.ext = extObj; if (ozoneRequest.imp.length > 0) { @@ -440,18 +445,13 @@ export const spec = { logInfo('requests will not be batched.'); if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); - if (bidderRequest.auctionId) { - logInfo('Found bidderRequest.auctionId - will pass these values through & not generate our own id'); - ozoneRequest.id = bidderRequest.auctionId; - ozoneRequest.auctionId = bidderRequest.auctionId; - deepSetValue(ozoneRequest, 'source.tid', deepAccess(bidderRequest, 'ortb2.source.tid')); - } else { - logInfo('Did not find bidderRequest.auctionId - will generate our own id'); - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) - } + ozoneRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + if (auctionId) { + deepSetValue(ozoneRequest, 'source.tid', auctionId); + } var ret = { method: 'POST', url: this.getAuctionUrl(), @@ -470,6 +470,9 @@ export const spec = { ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); + if (auctionId) { + deepSetValue(ozoneRequestSingle, 'source.tid', auctionId); + } logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', @@ -891,7 +894,9 @@ export const spec = { params: bid.params, price: bid.price, transactionId: bid.transactionId, - ttl: bid.ttl + ttl: bid.ttl, + ortb2: deepAccess(bid, 'ortb2'), + ortb2Imp: deepAccess(bid, 'ortb2Imp'), }; if (bid.hasOwnProperty('floorData')) { logObj.floorData = bid.floorData; diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index b48943da266..f504af91df5 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -63,6 +63,699 @@ var validBidRequestsMulti = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsWithAuctionIdTransactionId = [{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1dda', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}]; +var valid6BidRequestsWithAuctionIdTransactionId = [{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1dda', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu2', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddb', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu3', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddc', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu4', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddd', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu5', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1dde', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu6', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddf', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}]; var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -2445,6 +3138,46 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.imp[0].ext.ae).to.equal(1); }); + it('Single request: should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'singleRequest': true}}); + specMock.loadWhitelabelData(validBidRequestsWithAuctionIdTransactionId[0]); + const request = specMock.buildRequests(validBidRequestsWithAuctionIdTransactionId, validBidderRequest); // I don't look in the bidderRequest for this - there's no point + expect(request).to.be.an('Object'); + const payload = JSON.parse(request.data); + expect(payload.source.tid).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.auctionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.transactionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); + config.resetConfig(); + }); + it('non-Single request: should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'singleRequest': false}}); + specMock.loadWhitelabelData(validBidRequestsWithAuctionIdTransactionId[0]); + const request = specMock.buildRequests(validBidRequestsWithAuctionIdTransactionId, validBidderRequest); // I don't look in the bidderRequest for this - there's no point + expect(request).to.be.an('Array'); + const payload = JSON.parse(request[0].data); + expect(payload.source.tid).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.auctionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.transactionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); + config.resetConfig(); + }); + it('Batch request (flat array of single requests): should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'batchRequests': 3}}); + specMock.loadWhitelabelData(valid6BidRequestsWithAuctionIdTransactionId[0]); + const request = specMock.buildRequests(valid6BidRequestsWithAuctionIdTransactionId, validBidderRequest); // I don't look in the bidderRequest for this - there's no point + expect(request).to.be.an('Array'); + expect(request).to.have.lengthOf(2); + const payload = JSON.parse(request[0].data); + expect(payload.source.tid).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.auctionId).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.transactionId).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); + config.resetConfig(); + }); }); describe('interpretResponse', function () { beforeEach(function () { @@ -2942,6 +3675,7 @@ describe('ozone Adapter', function () { let testKey2 = 'ozone.singleRequest'; let markbidder_config2 = specMock.getWhitelabelConfigItem(testKey2); expect(markbidder_config2).to.equal('markbidder-singlerequest-value'); + config.resetConfig(); }); }); describe('setBidMediaTypeIfNotExist', function() { From 44b2a46325f9cb7d8f43cab6e2c956733a420397 Mon Sep 17 00:00:00 2001 From: Atharva Jangada Date: Wed, 25 Sep 2024 19:04:29 +0530 Subject: [PATCH 0537/1097] DeepIntent Bid Adapter: add bid floor support (#12266) * feat: added bid floor support in DI adapter * refactor: add common ortb video params to deepintentUtils file * refactor: replace common ortb video params for deepintent and sovrn bid adapters * refactor: add formatResponse function to deepintentUtils file * refactor: replace common formatResponse function for deepintent and relevatehealth bid adapters * feat: bid floor should be passed if it is set to 0 explicitly * fix: add a new line at the end of deepintentUtils file --------- Co-authored-by: Atharva Jangada --- libraries/deepintentUtils/index.js | 42 +++++++ modules/deepintentBidAdapter.js | 65 ++++------ modules/deepintentBidAdapter.md | 116 +++++++++--------- modules/relevatehealthBidAdapter.js | 19 +-- modules/sovrnBidAdapter.js | 22 +--- .../spec/modules/deepintentBidAdapter_spec.js | 16 +++ 6 files changed, 145 insertions(+), 135 deletions(-) create mode 100644 libraries/deepintentUtils/index.js diff --git a/libraries/deepintentUtils/index.js b/libraries/deepintentUtils/index.js new file mode 100644 index 00000000000..5abe2d1d061 --- /dev/null +++ b/libraries/deepintentUtils/index.js @@ -0,0 +1,42 @@ +import { isInteger } from '../../src/utils.js'; + +export const COMMON_ORTB_VIDEO_PARAMS = { + 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), + 'minduration': (value) => isInteger(value), + 'maxduration': (value) => isInteger(value), + 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), + 'w': (value) => isInteger(value), + 'h': (value) => isInteger(value), + 'startdelay': (value) => isInteger(value), + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), + 'maxextended': (value) => isInteger(value), + 'minbitrate': (value) => isInteger(value), + 'maxbitrate': (value) => isInteger(value), + 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) +}; + +export function formatResponse(bid) { + return { + requestId: bid && bid.impid ? bid.impid : undefined, + cpm: bid && bid.price ? bid.price : 0.0, + width: bid && bid.w ? bid.w : 0, + height: bid && bid.h ? bid.h : 0, + ad: bid && bid.adm ? bid.adm : '', + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, + creativeId: bid && bid.crid ? bid.crid : undefined, + netRevenue: false, + currency: bid && bid.cur ? bid.cur : 'USD', + ttl: 300, + dealId: bid && bid.dealId ? bid.dealId : undefined + } +} diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index f0c76ae557b..9c67eb02fa9 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -1,35 +1,17 @@ -import { generateUUID, deepSetValue, deepAccess, isArray, isInteger, logError, logWarn } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { generateUUID, deepSetValue, deepAccess, isArray, isFn, isPlainObject, logError, logWarn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { COMMON_ORTB_VIDEO_PARAMS, formatResponse } from '../libraries/deepintentUtils/index.js'; const BIDDER_CODE = 'deepintent'; const GVL_ID = 541; const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; const USER_SYNC_URL = 'https://cdn.deepintent.com/syncpixel.html'; const DI_M_V = '1.0.0'; export const ORTB_VIDEO_PARAMS = { - 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), - 'minduration': (value) => isInteger(value), - 'maxduration': (value) => isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), - 'w': (value) => isInteger(value), - 'h': (value) => isInteger(value), - 'startdelay': (value) => isInteger(value), + ...COMMON_ORTB_VIDEO_PARAMS, 'plcmt': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), - 'linearity': (value) => [1, 2].indexOf(value) !== -1, - 'skip': (value) => [0, 1].indexOf(value) !== -1, - 'skipmin': (value) => isInteger(value), - 'skipafter': (value) => isInteger(value), - 'sequence': (value) => isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), - 'maxextended': (value) => isInteger(value), - 'minbitrate': (value) => isInteger(value), - 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), - 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, - 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) }; export const spec = { code: BIDDER_CODE, @@ -155,29 +137,13 @@ function clean(obj) { } } -function formatResponse(bid) { - return { - requestId: bid && bid.impid ? bid.impid : undefined, - cpm: bid && bid.price ? bid.price : 0.0, - width: bid && bid.w ? bid.w : 0, - height: bid && bid.h ? bid.h : 0, - ad: bid && bid.adm ? bid.adm : '', - meta: { - advertiserDomains: bid && bid.adomain ? bid.adomain : [] - }, - creativeId: bid && bid.crid ? bid.crid : undefined, - netRevenue: false, - currency: bid && bid.cur ? bid.cur : 'USD', - ttl: 300, - dealId: bid && bid.dealId ? bid.dealId : undefined - } -} - function buildImpression(bid) { let impression = {}; + const floor = getFloor(bid); impression = { id: bid.bidId, tagid: bid.params.tagId || '', + ...(!isNaN(floor) && { bidfloor: floor }), secure: window.location.protocol === 'https:' ? 1 : 0, displaymanager: 'di_prebid', displaymanagerver: DI_M_V, @@ -192,6 +158,23 @@ function buildImpression(bid) { return impression; } +function getFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return bidRequest.params?.bidfloor; + } + + let floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + function _buildVideo(bid) { const videoObj = {}; const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {}); diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md index 0f017402d1d..73f1918085e 100644 --- a/modules/deepintentBidAdapter.md +++ b/modules/deepintentBidAdapter.md @@ -15,64 +15,70 @@ Module that connects to Deepintent's demand sources. # Banner Test Request ``` var adUnits = [ - { - code: 'di_adUnit1', - mediaTypes: { - banner: { - sizes: [[300, 250]], // a display size, only first one will be picked up since multiple ad sizes are not supported yet - } - } - bids: [ - { - bidder: 'deepintent', - params: { - tagId: '1300', // Required parameter - w: 300, // Width and Height here will override sizes in mediatype - h: 250, - pos: 1, - custom: { // Custom parameters in form of key value pairs - user_min_age: 18 - } - } - } - ] - } - ]; + { + code: 'di_adUnit1', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size, only first one will be picked up since multiple ad sizes are not supported yet + }, + }, + bids: [ + { + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter + bidfloor: 1.5, // optional + w: 300, // Width and Height here will override sizes in mediatype + h: 250, + pos: 1, + custom: { + // Custom parameters in form of key value pairs + user_min_age: 18, + }, + }, + }, + ], + }, + ]; ``` # Sample Video Ad Unit ``` -var adVideoAdUnits = [ -{ - code: 'test-div-video', - mediaTypes: { - video: { - playerSize: [640, 480], // required - context: 'instream' //required - } - }, - bids: [{ - bidder: 'deepintent', - params: { - tagId: '1300', // Required parameter // required - video: { - mimes: ['video/mp4','video/x-flv'], // required - skippable: true, // optional - minduration: 5, // optional - maxduration: 30, // optional - startdelay: 5, // optional - playbackmethod: [1,3], // optional - api: [ 1, 2 ], // optional - protocols: [ 2, 3 ], // optional - battr: [ 13, 14 ], // optional - linearity: 1, // optional - plcmt: 2, // optional - minbitrate: 10, // optional - maxbitrate: 10 // optional - } - } - }] -}] + var adVideoAdUnits = [ + { + code: 'test-div-video', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream', //required + }, + }, + bids: [ + { + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter // required + bidfloor: 1.5, // optional + video: { + mimes: ['video/mp4', 'video/x-flv'], // required + skippable: true, // optional + minduration: 5, // optional + maxduration: 30, // optional + startdelay: 5, // optional + playbackmethod: [1, 3], // optional + api: [1, 2], // optional + protocols: [2, 3], // optional + battr: [13, 14], // optional + linearity: 1, // optional + plcmt: 2, // optional + minbitrate: 10, // optional + maxbitrate: 10, // optional + }, + }, + }, + ], + }, + ]; ``` ###Recommended User Sync Configuration @@ -84,6 +90,4 @@ pbjs.setConfig({ enabledBidders: ['deepintent'], syncDelay: 3000 }}); - - ``` diff --git a/modules/relevatehealthBidAdapter.js b/modules/relevatehealthBidAdapter.js index e010f3d8fcd..560dbdeac3e 100644 --- a/modules/relevatehealthBidAdapter.js +++ b/modules/relevatehealthBidAdapter.js @@ -1,3 +1,4 @@ +import { formatResponse } from '../libraries/deepintentUtils/index.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -119,24 +120,6 @@ function getSite(bidderRequest) { } return site; } -// Function to format response -function formatResponse(bid) { - return { - requestId: bid && bid.impid ? bid.impid : undefined, - cpm: bid && bid.price ? bid.price : 0.0, - width: bid && bid.w ? bid.w : 0, - height: bid && bid.h ? bid.h : 0, - ad: bid && bid.adm ? bid.adm : '', - meta: { - advertiserDomains: bid && bid.adomain ? bid.adomain : [] - }, - creativeId: bid && bid.crid ? bid.crid : undefined, - netRevenue: false, - currency: bid && bid.cur ? bid.cur : 'USD', - ttl: 300, - dealId: bid && bid.dealId ? bid.dealId : undefined - }; -} // Function to build the user object function buildUser(bid) { if (bid && bid.params) { diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 4fa3ebc9b40..5478f9aa6e5 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -17,36 +17,18 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js' +import { COMMON_ORTB_VIDEO_PARAMS } from '../libraries/deepintentUtils/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ const ORTB_VIDEO_PARAMS = { - 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), - 'minduration': (value) => isInteger(value), - 'maxduration': (value) => isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), - 'w': (value) => isInteger(value), - 'h': (value) => isInteger(value), - 'startdelay': (value) => isInteger(value), + ...COMMON_ORTB_VIDEO_PARAMS, 'placement': (value) => isInteger(value) && value >= 1 && value <= 5, 'plcmt': (value) => isInteger(value) && value >= 1 && value <= 4, - 'linearity': (value) => [1, 2].indexOf(value) !== -1, - 'skip': (value) => [0, 1].indexOf(value) !== -1, - 'skipmin': (value) => isInteger(value), - 'skipafter': (value) => isInteger(value), - 'sequence': (value) => isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), - 'maxextended': (value) => isInteger(value), - 'minbitrate': (value) => isInteger(value), - 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), - 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, 'delivery': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 3), 'pos': (value) => isInteger(value) && value >= 1 && value <= 7, - 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) } const REQUIRED_VIDEO_PARAMS = { diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index 644e9255789..b64c29d6800 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -252,6 +252,22 @@ describe('Deepintent adapter', function () { expect(data.imp[0].displaymanager).to.equal('di_prebid'); expect(data.imp[0].displaymanagerver).to.equal('1.0.0'); }); + it('bid request check: bidfloor check', function() { + const requestClone = utils.deepClone(request); + let bRequest = spec.buildRequests(requestClone); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].bidfloor).to.not.exist; + + requestClone[0].params.bidfloor = 0; + bRequest = spec.buildRequests(requestClone); + data = JSON.parse(bRequest.data); + expect(data.imp[0].bidfloor).to.equal(0); + + requestClone[0].params.bidfloor = 1.2; + bRequest = spec.buildRequests(requestClone); + data = JSON.parse(bRequest.data); + expect(data.imp[0].bidfloor).to.equal(1.2); + }); it('bid request check: user object check', function () { let bRequest = spec.buildRequests(request); let data = JSON.parse(bRequest.data); From ceb8c45c4cf4317b1772a15cd45b19b9db66bfad Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Fri, 27 Sep 2024 08:43:16 -0400 Subject: [PATCH 0538/1097] appnexus bid adapter - initial support for pixel userSync (#12271) --- modules/appnexusBidAdapter.js | 16 ++- .../spec/modules/anPspParamsConverter_spec.js | 1 - test/spec/modules/appnexusBidAdapter_spec.js | 119 ++++++++++-------- 3 files changed, 79 insertions(+), 57 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 4935158d21c..0049d3a529c 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -452,17 +452,21 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { - function checkGppStatus(gppConsent) { - // user sync suppression for adapters is handled in activity controls and not needed in adapters - return true; - } - - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' }]; } + + if (syncOptions.pixelEnabled) { + // first attempt using static list + const imgList = ['https://px.ads.linkedin.com/setuid?partner=appNexus']; + return imgList.map(url => ({ + type: 'image', + url + })); + } } }; diff --git a/test/spec/modules/anPspParamsConverter_spec.js b/test/spec/modules/anPspParamsConverter_spec.js index 0d01d0e78a9..a96730088b8 100644 --- a/test/spec/modules/anPspParamsConverter_spec.js +++ b/test/spec/modules/anPspParamsConverter_spec.js @@ -51,7 +51,6 @@ describe('anPspParamsConverter', function () { const testBidderRequests = deepClone(bidderRequests); - debugger; //eslint-disable-line convertAnParams(function () { didHookRun = true; }, testBidderRequests); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 7fca35249b8..114aa8f2a13 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -2186,54 +2186,73 @@ describe('AppNexusAdapter', function () { }); }); - // describe('transformBidParams', function () { - // let gcStub; - // let adUnit = { bids: [{ bidder: 'appnexus' }] }; ; - - // before(function () { - // gcStub = sinon.stub(config, 'getConfig'); - // }); - - // after(function () { - // gcStub.restore(); - // }); - - // it('convert keywords param differently for psp endpoint with single s2sConfig', function () { - // gcStub.withArgs('s2sConfig').returns({ - // bidders: ['appnexus'], - // endpoint: { - // p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' - // } - // }); - - // const oldParams = { - // keywords: { - // genre: ['rock', 'pop'], - // pets: 'dog' - // } - // }; - - // const newParams = spec.transformBidParams(oldParams, true, adUnit); - // expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); - // }); - - // it('convert keywords param differently for psp endpoint with array s2sConfig', function () { - // gcStub.withArgs('s2sConfig').returns([{ - // bidders: ['appnexus'], - // endpoint: { - // p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' - // } - // }]); - - // const oldParams = { - // keywords: { - // genre: ['rock', 'pop'], - // pets: 'dog' - // } - // }; - - // const newParams = spec.transformBidParams(oldParams, true, adUnit); - // expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); - // }); - // }); + describe('getUserSyncs', function() { + let syncOptions, gdprConsent; + + beforeEach(() => { + gdprConsent = { + gdprApplies: true, + consentString: 'CPJl4C8PJl4C8OoAAAENAwCMAP_AAH_AAAAAAPgAAAAIAPgAAAAIAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', + vendorData: { + purpose: { + consents: { + '1': true + } + } + } + } + }); + + describe('pixel', function () { + beforeEach(() => { + syncOptions = { pixelEnabled: true }; + }); + + it('pixelEnabled on', function () { + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.have.length(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://px.ads.linkedin.com/setuid?partner=appNexus'); + }); + + it('pixelEnabled off', function () { + syncOptions.pixelEnabled = false; + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.be.undefined; + }); + }); + + describe('iframe', function () { + beforeEach(() => { + syncOptions = { iframeEnabled: true }; + }); + + it('iframeEnabled on with gdpr purpose 1 on', function () { + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.have.length(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal('https://acdn.adnxs.com/dmp/async_usersync.html'); + }); + + it('iframeEnabled on with gdpr purpose1 off', function () { + gdprConsent.vendorData.purpose.consents['1'] = false + + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.be.undefined; + }); + + it('iframeEnabled on without gdpr', function () { + const result = spec.getUserSyncs(syncOptions, [], null, null); + expect(result).to.have.length(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal('https://acdn.adnxs.com/dmp/async_usersync.html'); + }); + + it('iframeEnabled off', function () { + syncOptions.iframeEnabled = false; + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.be.undefined; + }); + }); + }); }); From d253486da6c9ba3fc2d2b5b7ed80ef2e278ff1ea Mon Sep 17 00:00:00 2001 From: Jaro Vanderheijden Date: Fri, 27 Sep 2024 15:25:07 +0200 Subject: [PATCH 0539/1097] Appnexus Bid Adapter: Add support for custom Native fields (#12272) --- modules/appnexusBidAdapter.js | 108 ++++++- test/spec/modules/appnexusBidAdapter_spec.js | 294 ++++++++++++++++--- 2 files changed, 364 insertions(+), 38 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 0049d3a529c..cf503dfe006 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -694,19 +694,123 @@ function newBid(serverBid, rtbBid, bidderRequest) { javascriptTrackers: jsTrackers }; if (nativeAd.main_img) { - bid['native'].image = { + bid[NATIVE].image = { url: nativeAd.main_img.url, height: nativeAd.main_img.height, width: nativeAd.main_img.width, }; } if (nativeAd.icon) { - bid['native'].icon = { + bid[NATIVE].icon = { url: nativeAd.icon.url, height: nativeAd.icon.height, width: nativeAd.icon.width, }; } + + // Custom fields + bid[NATIVE].ext = { + customImage1: nativeAd.image1 && { + url: nativeAd.image1.url, + height: nativeAd.image1.height, + width: nativeAd.image1.width, + }, + customImage2: nativeAd.image2 && { + url: nativeAd.image2.url, + height: nativeAd.image2.height, + width: nativeAd.image2.width, + }, + customImage3: nativeAd.image3 && { + url: nativeAd.image3.url, + height: nativeAd.image3.height, + width: nativeAd.image3.width, + }, + customImage4: nativeAd.image4 && { + url: nativeAd.image4.url, + height: nativeAd.image4.height, + width: nativeAd.image4.width, + }, + customImage5: nativeAd.image5 && { + url: nativeAd.image5.url, + height: nativeAd.image5.height, + width: nativeAd.image5.width, + }, + customIcon1: nativeAd.icon1 && { + url: nativeAd.icon1.url, + height: nativeAd.icon1.height, + width: nativeAd.icon1.width, + }, + customIcon2: nativeAd.icon2 && { + url: nativeAd.icon2.url, + height: nativeAd.icon2.height, + width: nativeAd.icon2.width, + }, + customIcon3: nativeAd.icon3 && { + url: nativeAd.icon3.url, + height: nativeAd.icon3.height, + width: nativeAd.icon3.width, + }, + customIcon4: nativeAd.icon4 && { + url: nativeAd.icon4.url, + height: nativeAd.icon4.height, + width: nativeAd.icon4.width, + }, + customIcon5: nativeAd.icon5 && { + url: nativeAd.icon5.url, + height: nativeAd.icon5.height, + width: nativeAd.icon5.width, + }, + customSocialIcon1: nativeAd.socialicon1 && { + url: nativeAd.socialicon1.url, + height: nativeAd.socialicon1.height, + width: nativeAd.socialicon1.width, + }, + customSocialIcon2: nativeAd.socialicon2 && { + url: nativeAd.socialicon2.url, + height: nativeAd.socialicon2.height, + width: nativeAd.socialicon2.width, + }, + customSocialIcon3: nativeAd.socialicon3 && { + url: nativeAd.socialicon3.url, + height: nativeAd.socialicon3.height, + width: nativeAd.socialicon3.width, + }, + customSocialIcon4: nativeAd.socialicon4 && { + url: nativeAd.socialicon4.url, + height: nativeAd.socialicon4.height, + width: nativeAd.socialicon4.width, + }, + customSocialIcon5: nativeAd.socialicon5 && { + url: nativeAd.socialicon5.url, + height: nativeAd.socialicon5.height, + width: nativeAd.socialicon5.width, + }, + customTitle1: nativeAd.title1, + customTitle2: nativeAd.title2, + customTitle3: nativeAd.title3, + customTitle4: nativeAd.title4, + customTitle5: nativeAd.title5, + customBody1: nativeAd.body1, + customBody2: nativeAd.body2, + customBody3: nativeAd.body3, + customBody4: nativeAd.body4, + customBody5: nativeAd.body5, + customCta1: nativeAd.ctatext1, + customCta2: nativeAd.ctatext2, + customCta3: nativeAd.ctatext3, + customCta4: nativeAd.ctatext4, + customCta5: nativeAd.ctatext5, + customDisplayUrl1: nativeAd.displayurl1, + customDisplayUrl2: nativeAd.displayurl2, + customDisplayUrl3: nativeAd.displayurl3, + customDisplayUrl4: nativeAd.displayurl4, + customDisplayUrl5: nativeAd.displayurl5, + customSocialUrl1: nativeAd.socialurl1, + customSocialUrl2: nativeAd.socialurl2, + customSocialUrl3: nativeAd.socialurl3, + customSocialUrl4: nativeAd.socialurl4, + customSocialUrl5: nativeAd.socialurl5 + }; } else { Object.assign(bid, { width: rtbBid.rtb.banner.width, diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 114aa8f2a13..05c0982c80e 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -2031,45 +2031,47 @@ describe('AppNexusAdapter', function () { } if (FEATURES.NATIVE) { + const BASE_NATIVE = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '', + 'video': { + 'content': '' + } + }; + it('handles native responses', function () { let response1 = deepClone(response); response1.tags[0].ads[0].ad_type = 'native'; - response1.tags[0].ads[0].rtb.native = { - 'title': 'Native Creative', - 'desc': 'Cool description great stuff', - 'desc2': 'Additional body text', - 'ctatext': 'Do it', - 'sponsored': 'AppNexus', - 'icon': { - 'width': 0, - 'height': 0, - 'url': 'https://cdn.adnxs.com/icon.png' - }, - 'main_img': { - 'width': 2352, - 'height': 1516, - 'url': 'https://cdn.adnxs.com/img.png' - }, - 'link': { - 'url': 'https://www.appnexus.com', - 'fallback_url': '', - 'click_trackers': ['https://nym1-ib.adnxs.com/click'] - }, - 'impression_trackers': ['https://example.com'], - 'rating': '5', - 'displayurl': 'https://AppNexus.com/?url=display_url', - 'likes': '38908320', - 'downloads': '874983', - 'price': '9.99', - 'saleprice': 'FREE', - 'phone': '1234567890', - 'address': '28 W 23rd St, New York, NY 10010', - 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '', - 'video': { - 'content': '' - } - }; + response1.tags[0].ads[0].rtb.native = BASE_NATIVE; let bidderRequest = { bids: [{ bidId: '3db3773286ee59', @@ -2080,10 +2082,230 @@ describe('AppNexusAdapter', function () { let result = spec.interpretResponse({ body: response1 }, { bidderRequest }); expect(result[0].native.title).to.equal('Native Creative'); expect(result[0].native.body).to.equal('Cool description great stuff'); + expect(result[0].native.body2).to.equal('Additional body text'); expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); expect(result[0].native.video.content).to.equal(''); }); + + it('handles custom native fields as ext', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + ...BASE_NATIVE, + 'title1': 'Custom Title 1', + 'title2': 'Custom Title 2', + 'title3': 'Custom Title 3', + 'title4': 'Custom Title 4', + 'title5': 'Custom Title 5', + // Not to be confused with Prebid's base native body & body2 + 'body1': 'Custom Body 1', + 'body2': 'Custom Body 2', + 'body3': 'Custom Body 3', + 'body4': 'Custom Body 4', + 'body5': 'Custom Body 5', + 'image1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_1.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_2.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_3.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_4.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_5.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'icon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialurl1': 'https://www.xandr.com/platform/monetize/#socialUrl1', + 'socialurl2': 'https://www.xandr.com/platform/monetize/#socialUrl2', + 'socialurl3': 'https://www.xandr.com/platform/monetize/#socialUrl3', + 'socialurl4': 'https://www.xandr.com/platform/monetize/#socialUrl4', + 'socialurl5': 'https://www.xandr.com/platform/monetize/#socialUrl5', + 'displayurl1': 'https://www.xandr.com/platform/monetize/#displayUrl1', + 'displayurl2': 'https://www.xandr.com/platform/monetize/#displayUrl2', + 'displayurl3': 'https://www.xandr.com/platform/monetize/#displayUrl3', + 'displayurl4': 'https://www.xandr.com/platform/monetize/#displayUrl4', + 'displayurl5': 'https://www.xandr.com/platform/monetize/#displayUrl5', + 'ctatext1': 'Custom CTA 1', + 'ctatext2': 'Custom CTA 2', + 'ctatext3': 'Custom CTA 3', + 'ctatext4': 'Custom CTA 4', + 'ctatext5': 'Custom CTA 5', + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, { bidderRequest }); + expect(result[0].native.ext).to.deep.equal({ + 'customTitle1': 'Custom Title 1', + 'customTitle2': 'Custom Title 2', + 'customTitle3': 'Custom Title 3', + 'customTitle4': 'Custom Title 4', + 'customTitle5': 'Custom Title 5', + 'customBody1': 'Custom Body 1', + 'customBody2': 'Custom Body 2', + 'customBody3': 'Custom Body 3', + 'customBody4': 'Custom Body 4', + 'customBody5': 'Custom Body 5', + 'customImage1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_1.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_2.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_3.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_4.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_5.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customIcon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialUrl1': 'https://www.xandr.com/platform/monetize/#socialUrl1', + 'customSocialUrl2': 'https://www.xandr.com/platform/monetize/#socialUrl2', + 'customSocialUrl3': 'https://www.xandr.com/platform/monetize/#socialUrl3', + 'customSocialUrl4': 'https://www.xandr.com/platform/monetize/#socialUrl4', + 'customSocialUrl5': 'https://www.xandr.com/platform/monetize/#socialUrl5', + 'customDisplayUrl1': 'https://www.xandr.com/platform/monetize/#displayUrl1', + 'customDisplayUrl2': 'https://www.xandr.com/platform/monetize/#displayUrl2', + 'customDisplayUrl3': 'https://www.xandr.com/platform/monetize/#displayUrl3', + 'customDisplayUrl4': 'https://www.xandr.com/platform/monetize/#displayUrl4', + 'customDisplayUrl5': 'https://www.xandr.com/platform/monetize/#displayUrl5', + 'customCta1': 'Custom CTA 1', + 'customCta2': 'Custom CTA 2', + 'customCta3': 'Custom CTA 3', + 'customCta4': 'Custom CTA 4', + 'customCta5': 'Custom CTA 5', + }); + }); } if (FEATURES.VIDEO) { From 3f4aebf7a637fb647afa67bc80f9d2bab63c1902 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 27 Sep 2024 14:04:43 +0000 Subject: [PATCH 0540/1097] Prebid 9.15.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1dbe33501e..bec66b537d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.15.0-pre", + "version": "9.15.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.15.0-pre", + "version": "9.15.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 54a68be1c45..747fa46660a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.15.0-pre", + "version": "9.15.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 41443adf64a95e45fc6e52a5ba7360369f6930fc Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 27 Sep 2024 14:04:44 +0000 Subject: [PATCH 0541/1097] Increment version to 9.16.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bec66b537d7..7370a7f8896 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.15.0", + "version": "9.16.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.15.0", + "version": "9.16.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 747fa46660a..5ab0a6ae5ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.15.0", + "version": "9.16.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From d50290c48e80ee219def52f5f174230568b9ca4f Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Fri, 27 Sep 2024 15:46:36 +0100 Subject: [PATCH 0542/1097] Teqblaze Library: Add ORTB2 device data to request payload (#12073) * Teqblaze Library: Add ORTB2 device data to request payload * Teqblaze Library: Modify tests for Teqblaze-dependent bidders --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- libraries/teqblazeUtils/bidderUtils.js | 4 ++++ .../teqblazeUtils/bidderUtils_spec.js | 22 +++++++++++++++++++ test/spec/modules/acuityadsBidAdapter_spec.js | 1 + test/spec/modules/admanBidAdapter_spec.js | 1 + test/spec/modules/adprimeBidAdapter_spec.js | 1 + test/spec/modules/axisBidAdapter_spec.js | 1 + .../modules/beyondmediaBidAdapter_spec.js | 1 + test/spec/modules/boldwinBidAdapter_spec.js | 1 + test/spec/modules/compassBidAdapter_spec.js | 1 + .../modules/contentexchangeBidAdapter_spec.js | 1 + .../spec/modules/copper6sspBidAdapter_spec.js | 1 + .../spec/modules/e_volutionBidAdapter_spec.js | 1 + test/spec/modules/edge226BidAdapter_spec.js | 4 +++- test/spec/modules/emtvBidAdapter_spec.js | 1 + test/spec/modules/globalsunBidAdapter_spec.js | 1 + test/spec/modules/iqzoneBidAdapter_spec.js | 1 + test/spec/modules/kiviadsBidAdapter_spec.js | 1 + .../spec/modules/krushmediaBidAdapter_spec.js | 1 + test/spec/modules/loyalBidAdapter_spec.js | 1 + .../modules/lunamediahbBidAdapter_spec.js | 1 + .../modules/mathildeadsBidAdapter_spec.js | 1 + test/spec/modules/mgidXBidAdapter_spec.js | 1 + test/spec/modules/mobfoxpbBidAdapter_spec.js | 1 + test/spec/modules/orakiBidAdapter_spec.js | 1 + test/spec/modules/pgamsspBidAdapter_spec.js | 1 + test/spec/modules/playdigoBidAdapter_spec.js | 1 + test/spec/modules/pubCircleBidAdapter_spec.js | 1 + test/spec/modules/pubriseBidAdapter_spec.js | 1 + test/spec/modules/qtBidAdapter_spec.js | 1 + .../modules/visiblemeasuresBidAdapter_spec.js | 1 + 30 files changed, 56 insertions(+), 1 deletion(-) diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index 96195eed70d..6186e526eb8 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -169,6 +169,10 @@ export const buildRequestsBase = (config) => { request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; } + if (bidderRequest?.ortb2?.device) { + request.device = bidderRequest.ortb2.device; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; diff --git a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js index 650ff668070..ba8b986163b 100644 --- a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -142,6 +142,7 @@ describe('TeqBlazeBidderUtils', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -268,6 +269,27 @@ describe('TeqBlazeBidderUtils', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + + it('Handles ORTB2 device data', function () { + const ortb2Device = { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }; + const _bidderRequest = JSON.parse(JSON.stringify(bidderRequest)); + _bidderRequest.ortb2.device = ortb2Device; + const _request = spec.buildRequests(bids, _bidderRequest); + + expect(_request.data.device).to.deep.equal(ortb2Device); + }); }); describe('gpp consent', function () { diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index 526bbf6fd54..ecc40025c95 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('AcuityAdsBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index cca5d57db0f..ae3b935619e 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('AdmanBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 71aeccb2975..4199145e80a 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('AdprimeBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/axisBidAdapter_spec.js b/test/spec/modules/axisBidAdapter_spec.js index c1e6adef0c6..2db6c907851 100644 --- a/test/spec/modules/axisBidAdapter_spec.js +++ b/test/spec/modules/axisBidAdapter_spec.js @@ -144,6 +144,7 @@ describe('AxisBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 6bc071eb780..9f31294dc54 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('AndBeyondMediaBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 2a2cd070d4c..820938dcec2 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('BoldwinBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 98c79fb22e2..d0cecc2272f 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('CompassBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 7c7d3d4ef2a..913c9072dd5 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('ContentexchangeBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js index cc4cb832450..a97ef3ecbe5 100644 --- a/test/spec/modules/copper6sspBidAdapter_spec.js +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -137,6 +137,7 @@ describe('Copper6SSPBidAdapter', function () { expect(data).to.have.all.keys( 'deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index 2eee3b4356e..4777b73aa3c 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('EvolutionTechBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/edge226BidAdapter_spec.js b/test/spec/modules/edge226BidAdapter_spec.js index 2162b844930..e9e1c34b9cd 100644 --- a/test/spec/modules/edge226BidAdapter_spec.js +++ b/test/spec/modules/edge226BidAdapter_spec.js @@ -134,7 +134,9 @@ describe('Edge226BidAdapter', function () { it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', + expect(data).to.have.all.keys( + 'device', + 'deviceWidth', 'deviceHeight', 'language', 'secure', diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js index 4468cb64f0c..ce81dc15ad4 100644 --- a/test/spec/modules/emtvBidAdapter_spec.js +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -138,6 +138,7 @@ describe('EMTVBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 0920ca5bd78..0651b05894f 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('GlobalsunBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index 8f622cc39ed..5e9b69d7a44 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('IQZoneBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index 618648a0c07..e2d23d06822 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -138,6 +138,7 @@ describe('KiviAdsBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 452eb517eb8..516b587edff 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('KrushmediabBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/loyalBidAdapter_spec.js b/test/spec/modules/loyalBidAdapter_spec.js index f125eef4c5c..fbfcdcd0742 100644 --- a/test/spec/modules/loyalBidAdapter_spec.js +++ b/test/spec/modules/loyalBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('LoyalBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index f2653269c7b..8ae3220504d 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('LunamediaHBBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index e55e7175f4b..cdd1ffbc4bd 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('MathildeAdsBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 4e603ef8448..9b39b307dc4 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -148,6 +148,7 @@ describe('MGIDXBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index de289f8a5e5..1bf1ec12bc4 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('MobfoxHBBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js index 47a7bf8779d..9a7c777212b 100644 --- a/test/spec/modules/orakiBidAdapter_spec.js +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -133,6 +133,7 @@ describe('OrakiBidAdapter', function () { expect(data).to.have.all.keys( 'deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index a90eadba8b0..bdb837b70a4 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('PGAMBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js index 62ae2796596..591892beb8c 100644 --- a/test/spec/modules/playdigoBidAdapter_spec.js +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('PlaydigoBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index aa880c206fa..083031101f1 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('PubCircleBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js index 6374c4f1b7f..e6dc710382c 100644 --- a/test/spec/modules/pubriseBidAdapter_spec.js +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -135,6 +135,7 @@ describe('PubriseBidAdapter', function () { let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.all.keys( + 'device', 'deviceWidth', 'deviceHeight', 'language', diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js index 8bd8730c7a9..f1c1ca61664 100644 --- a/test/spec/modules/qtBidAdapter_spec.js +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -136,6 +136,7 @@ describe('QTBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index 1e0fd6f64d2..55f2a74ce77 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -138,6 +138,7 @@ describe('VisibleMeasuresBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', From c3569c4390c674e7995f59e4e8d8bd8c735a422a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Millet?= Date: Fri, 27 Sep 2024 16:54:34 +0200 Subject: [PATCH 0543/1097] Dailymotion bid adapter: Process both ORTB2 sources of category (#12279) ## Type of change - [x] Updated bidder adapter ## Description of change This fixes a naming issue and correctly reports categories from the main ORTB2 site or app objects along with the one coming from the content object. --- modules/dailymotionBidAdapter.js | 10 ++++++---- test/spec/modules/dailymotionBidAdapter_spec.js | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 04baaebafb9..791fbccda5f 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -17,10 +17,11 @@ function getVideoMetadata(bidRequest, bidderRequest) { // As per oRTB 2.5 spec, "A bid request must not contain both an App and a Site object." // See section 3.2.14 + const siteOrAppObj = deepAccess(bidderRequest, 'ortb2.site') + ? deepAccess(bidderRequest, 'ortb2.site') + : deepAccess(bidderRequest, 'ortb2.app'); // Content object is either from Object: Site or Object: App - const contentObj = deepAccess(bidderRequest, 'ortb2.site') - ? deepAccess(bidderRequest, 'ortb2.site.content') - : deepAccess(bidderRequest, 'ortb2.app.content'); + const contentObj = deepAccess(siteOrAppObj, 'content') const parsedContentData = { // Store as object keys to ensure uniqueness @@ -70,7 +71,8 @@ function getVideoMetadata(bidRequest, bidderRequest) { ? videoParams.isCreatedForKids : null, context: { - siteOrAppCat: deepAccess(contentObj, 'cat', []), + siteOrAppCat: deepAccess(siteOrAppObj, 'cat', []), + siteOrAppContentCat: deepAccess(contentObj, 'cat', []), videoViewsInSession: ( typeof videoParams.videoViewsInSession === 'number' && videoParams.videoViewsInSession >= 0 diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index e0f572c06ba..2a276a06b15 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -211,6 +211,7 @@ describe('dailymotionBidAdapterTests', () => { isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, context: { siteOrAppCat: [], + siteOrAppContentCat: [], videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, autoplay: bidRequestData[0].params.video.autoplay, playerName: bidRequestData[0].params.video.playerName, @@ -1551,6 +1552,7 @@ describe('dailymotionBidAdapterTests', () => { isCreatedForKids: null, context: { siteOrAppCat: [], + siteOrAppContentCat: [], videoViewsInSession: null, autoplay: null, playerName: 'dailymotion', @@ -1605,6 +1607,7 @@ describe('dailymotionBidAdapterTests', () => { coppa: 0, }, site: { + cat: ['IAB-1'], content: { id: '54321', language: 'FR', @@ -1729,7 +1732,8 @@ describe('dailymotionBidAdapterTests', () => { livestream: !!bidderRequestData.ortb2.site.content.livestream, isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, context: { - siteOrAppCat: bidderRequestData.ortb2.site.content.cat, + siteOrAppCat: bidderRequestData.ortb2.site.cat, + siteOrAppContentCat: bidderRequestData.ortb2.site.content.cat, videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, autoplay: bidRequestData[0].params.video.autoplay, playerName: bidRequestData[0].params.video.playerName, @@ -1821,6 +1825,7 @@ describe('dailymotionBidAdapterTests', () => { isCreatedForKids: null, context: { siteOrAppCat: [], + siteOrAppContentCat: [], videoViewsInSession: null, autoplay: null, playerName: '', From 4991e6ec3b6ed39125b762c0d33022e0962a1140 Mon Sep 17 00:00:00 2001 From: Philip Watson Date: Sat, 28 Sep 2024 03:00:06 +1200 Subject: [PATCH 0544/1097] StroeerCore Bid Adapter: add special format parameters to bid request (#12276) --- modules/stroeerCoreBidAdapter.js | 1 + .../modules/stroeerCoreBidAdapter_spec.js | 47 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index 35f40953b1f..072b72a6724 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -216,6 +216,7 @@ const mapToPayloadBaseBid = (bidRequest) => ({ bid: bidRequest.bidId, sid: bidRequest.params.sid, viz: elementInView(bidRequest.adUnitCode), + sfp: bidRequest.params.sfp, }); const mapToPayloadBannerBid = (bidRequest) => { diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 66e2b575b8b..3cd9626885d 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -533,6 +533,7 @@ describe('stroeerCore bid adapter', function () { 'siz': [[300, 600], [160, 60]], 'fp': undefined }, + 'sfp': undefined, }, { 'sid': 'ABC=', @@ -541,7 +542,8 @@ describe('stroeerCore bid adapter', function () { 'siz': [[100, 200], [300, 500]], 'fp': undefined }, - 'viz': undefined + 'viz': undefined, + 'sfp': undefined, } ]; @@ -555,7 +557,8 @@ describe('stroeerCore bid adapter', function () { 'siz': [640, 480], 'mim': ['video/mp4', 'video/quicktime'], 'fp': undefined - } + }, + 'sfp': undefined, } ]; @@ -597,7 +600,8 @@ describe('stroeerCore bid adapter', function () { 'ban': { 'siz': [[100, 200], [300, 500]], 'fp': undefined - } + }, + 'sfp': undefined, } ]; @@ -611,14 +615,14 @@ describe('stroeerCore bid adapter', function () { 'siz': [640, 480], 'mim': ['video/mp4', 'video/quicktime'], 'fp': undefined - } + }, + 'sfp': undefined, } ]; assert.deepEqual(serverRequestInfo.data.bids, [...expectedBannerBids, ...expectedVideoBids]); }); }); - describe('optional fields', () => { it('should skip viz field when unable to determine visibility of placement', () => { placementElements.length = 0; @@ -898,6 +902,39 @@ describe('stroeerCore bid adapter', function () { assert.deepEqual(sentOrtb2, ortb2); }); + + it('should add the special format parameters', () => { + const bidReq = buildBidderRequest(); + + const sfp0 = { + 'field1': { + 'abc': '123', + } + }; + + const sfp1 = { + 'field3': 'xyz' + }; + + bidReq.bids[0].params.sfp = utils.deepClone(sfp0); + bidReq.bids[1].params.sfp = utils.deepClone(sfp1); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.deepEqual(serverRequestInfo.data.bids[0].sfp, sfp0); + assert.deepEqual(serverRequestInfo.data.bids[1].sfp, sfp1); + }); + + it('should add the special format parameters even when it is an empty object', () => { + const bidReq = buildBidderRequest(); + + bidReq.bids[0].params.sfp = {}; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.deepEqual(serverRequestInfo.data.bids[0].sfp, {}); + assert.isUndefined(serverRequestInfo.data.bids[1].sfp); + }); }); }); }); From 447a3c4a16b2848ef8a60a01b74c5ad5de10e3ca Mon Sep 17 00:00:00 2001 From: Pranav Sheth <57259342+pranavsheth@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:10:47 +0530 Subject: [PATCH 0545/1097] New Bidder: dexerto (#12182) * New Bidder: dexerto * added page property in site object. * made requested changes * handled response when no bid. --- libraries/audUtils/bidderUtils.js | 139 ++++++++++++++ modules/dexertoBidAdapter.js | 31 +++ modules/dexertoBidAdapter.md | 38 ++++ test/spec/modules/dexertoBidAdapter_spec.js | 199 ++++++++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 libraries/audUtils/bidderUtils.js create mode 100644 modules/dexertoBidAdapter.js create mode 100644 modules/dexertoBidAdapter.md create mode 100644 test/spec/modules/dexertoBidAdapter_spec.js diff --git a/libraries/audUtils/bidderUtils.js b/libraries/audUtils/bidderUtils.js new file mode 100644 index 00000000000..6f4f2bdc6e0 --- /dev/null +++ b/libraries/audUtils/bidderUtils.js @@ -0,0 +1,139 @@ +import { + deepAccess, + deepSetValue, + generateUUID, + logError +} from '../../src/utils.js'; + +// Function to get Request +export const getBannerRequest = (bidRequests, bidderRequest, ENDPOINT) => { + let request = []; + // Loop for each bid request + bidRequests.forEach(bidReq => { + let guid = generateUUID(); + const req = { + id: guid, + imp: [getImpDetails(bidReq)], + placementId: bidReq.params.placement_id, + site: getSiteDetails(bidderRequest), + user: getUserDetails(bidReq) + }; + // Fetch GPP Consent from bidderRequest + if (bidderRequest && bidderRequest.gppConsent && bidderRequest.gppConsent.gppString) { + deepSetValue(req, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(req, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.gpp) { + deepSetValue(req, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(req, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + // Fetch coppa compliance from bidderRequest + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.coppa) { + deepSetValue(req, 'regs.coppa', 1); + } + // Fetch uspConsent from bidderRequest + if (bidderRequest?.uspConsent) { + deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + request.push(req); + }); + // Return the array of request + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(request), + options: { + contentType: 'application/json', + } + }; +} +// Function to get Response +export const getBannerResponse = (bidResponse, mediaType) => { + let responseArray = []; + if (bidResponse) { + try { + let bidResp = deepAccess(bidResponse, 'body.seatbid', []); + if (bidResp && bidResp[0] && bidResp[0].bid) { + bidResp[0].bid.forEach(bidReq => { + let response = {}; + response.requestId = bidReq.impid; + response.cpm = bidReq.price; + response.width = bidReq.w; + response.height = bidReq.h; + response.ad = bidReq.adm; + response.meta = { + advertiserDomains: bidReq.adomain + }; + response.creativeId = bidReq.crid; + response.netRevenue = false; + response.currency = 'USD'; + response.ttl = 300; + response.dealId = bidReq.dealId; + response.mediaType = mediaType; + responseArray.push(response); + }); + } + } catch (e) { + logError(e); + } + } + return responseArray; +} +// Function to get imp +const getImpDetails = (bidReq) => { + let imp = {}; + if (bidReq) { + imp.id = bidReq.bidId; + imp.bidfloor = getFloorPrice(bidReq); + imp.banner = getBannerDetails(bidReq); + } + return imp; +} +// Function to get banner object +const getBannerDetails = (bidReq) => { + let response = {}; + if (bidReq.mediaTypes.banner) { + // Fetch width and height from MediaTypes object, if not provided in bidReq params + if (bidReq.mediaTypes.banner.sizes && !bidReq.params.height && !bidReq.params.width) { + let sizes = bidReq.mediaTypes.banner.sizes; + if (sizes.length > 0) { + response.h = sizes[0][1]; + response.w = sizes[0][0]; + } + } else { + response.h = bidReq.params.height; + response.w = bidReq.params.width; + } + } + return response; +} +// Function to get floor price +const getFloorPrice = (bidReq) => { + let bidfloor = deepAccess(bidReq, 'params.bid_floor', 0); + return bidfloor; +} +// Function to get site object +const getSiteDetails = (bidderRequest) => { + let page = ''; + let name = ''; + if (bidderRequest && bidderRequest.refererInfo) { + page = bidderRequest.refererInfo.page; + name = bidderRequest.refererInfo.domain; + } + return {page: page, name: name}; +} +// Function to build the user object +const getUserDetails = (bidReq) => { + let user = {}; + if (bidReq && bidReq.ortb2 && bidReq.ortb2.user) { + user.id = bidReq.ortb2.user.id ? bidReq.ortb2.user.id : ''; + user.buyeruid = bidReq.ortb2.user.buyeruid ? bidReq.ortb2.user.buyeruid : ''; + user.keywords = bidReq.ortb2.user.keywords ? bidReq.ortb2.user.keywords : ''; + user.customdata = bidReq.ortb2.user.customdata ? bidReq.ortb2.user.customdata : ''; + } else { + user.id = ''; + user.buyeruid = ''; + user.keywords = ''; + user.customdata = ''; + } + return user; +} diff --git a/modules/dexertoBidAdapter.js b/modules/dexertoBidAdapter.js new file mode 100644 index 00000000000..af06341e9e6 --- /dev/null +++ b/modules/dexertoBidAdapter.js @@ -0,0 +1,31 @@ +import { + BANNER +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const ENDPOINT_URL = 'https://rtb.dexerto.media/hb/dexerto'; +// Export const spec +export const spec = { + code: 'dexerto', + supportedMediaTypes: BANNER, + // Determines whether or not the given bid request is valid + isBidRequestValid: (bid) => { + return !!(bid.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: (bidRequests, bidderRequest) => { + return getBannerRequest(bidRequests, bidderRequest, ENDPOINT_URL); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: (bidResponse, bidRequest) => { + return getBannerResponse(bidResponse, BANNER); + } +} + +registerBidder(spec); diff --git a/modules/dexertoBidAdapter.md b/modules/dexertoBidAdapter.md new file mode 100644 index 00000000000..ad4d7bb42eb --- /dev/null +++ b/modules/dexertoBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Dexerto Bidder Adapter +Module Type: Bidder Adapter +Maintainer: niels.claes@dexerto.com +``` + +# Description + +Dexerto currently supports the BANNER type ads through prebid js + +Module that connects to dexerto's demand sources. + +# Banner Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'dexerto', + params: { + placement_id: 110003, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/dexertoBidAdapter_spec.js b/test/spec/modules/dexertoBidAdapter_spec.js new file mode 100644 index 00000000000..419fcbc9dbe --- /dev/null +++ b/test/spec/modules/dexertoBidAdapter_spec.js @@ -0,0 +1,199 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/dexertoBidAdapter'; +import * as utils from '../../../src/utils.js'; + +describe('dexerto adapter', function () { + let request; + let bannerResponse, invalidResponse; + + beforeEach(function () { + request = [ + { + bidder: 'dexerto', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placement_id: 110003, + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + } + ]; + bannerResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 0.5, + 'adid': '3424', + 'adm': "

", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.audiencelogy.com/1_3424_1.jpg', + 'cid': '410011', + 'crid': '410011', + 'w': 300, + 'h': 250, + 'cat': ['IAB1-15'] + }], + 'seat': 'audiencelogy', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1256' + } + }; + invalidResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 0.5, + 'adid': '3424', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.audiencelogy.com/1_3424_1.jpg', + 'cid': '410011', + 'crid': '410011', + 'w': 300, + 'h': 250, + 'cat': ['IAB1-15'] + }], + 'seat': 'audiencelogy', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1256' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'dexerto', + params: { + placement_id: 110003 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'dexerto', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(request); + expect(_Request.url).to.equal('https://rtb.dexerto.media/hb/dexerto'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(request[0].bidId); + expect(data[0].placementId).to.equal(110003); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(request, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate response ', function () { + it('Validate bid response : valid bid response', function () { + let bResponse = spec.interpretResponse(bannerResponse, request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(request); + let response = spec.interpretResponse(invalidResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); From bdab819083de91f2ffd8e53554bac0441c3508a7 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:04:54 +0200 Subject: [PATCH 0546/1097] LiveIntent User ID Module: Eliminating live-connect NPM Dependency (#12167) * CM-1260 Implement LiveIntent Prebid User Id Module Based On The Hub * add tests and rebase to the correct master * add fpid handling * fix naming * Revert superfluous changes * Update test/spec/modules/liveIntentIdHubSystem_spec.js Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> * Fix eids generation * comment * only keep collect and rename * wrong name * rename and fix import * Clean up after merge * Lint * Lint more * Put back module loading * fix import --------- Co-authored-by: Peixun Zhang Co-authored-by: PeiZ <74068135+peixunzhang@users.noreply.github.com> --- libraries/liveIntentId/externalIdSystem.js | 173 +++++++ libraries/liveIntentId/idSystem.js | 227 ++++++++++ libraries/liveIntentId/shared.js | 202 +++++++++ modules/liveIntentIdSystem.js | 423 +----------------- .../liveIntentExternalIdSystem_spec.js | 419 +++++++++++++++++ .../modules/liveIntentIdMinimalSystem_spec.js | 2 +- test/spec/modules/liveIntentIdSystem_spec.js | 2 +- test/spec/modules/userId_spec.js | 2 +- 8 files changed, 1034 insertions(+), 416 deletions(-) create mode 100644 libraries/liveIntentId/externalIdSystem.js create mode 100644 libraries/liveIntentId/idSystem.js create mode 100644 libraries/liveIntentId/shared.js create mode 100644 test/spec/modules/liveIntentExternalIdSystem_spec.js diff --git a/libraries/liveIntentId/externalIdSystem.js b/libraries/liveIntentId/externalIdSystem.js new file mode 100644 index 00000000000..5db94b90a44 --- /dev/null +++ b/libraries/liveIntentId/externalIdSystem.js @@ -0,0 +1,173 @@ +import { logError } from '../../src/utils.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../src/adapterManager.js'; +import { submodule } from '../../src/hook.js'; +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, parseRequestedAttributes, composeIdObject, eids, GVLID, PRIMARY_IDS } from './shared.js' + +// Reference to the client for the liQHub. +let cachedClientRef + +/** + * This function is used in tests. + */ +export function resetSubmodule() { + cachedClientRef = undefined +} + +window.liQHub = window.liQHub ?? [] + +function initializeClient(configParams) { + // Only initialize once. + if (cachedClientRef != null) return cachedClientRef + + const clientRef = {} + + const clientDetails = { name: 'prebid', version: '$prebid.version$' } + + const collectConfig = configParams.liCollectConfig ?? {}; + + let integration + if (collectConfig.appId != null) { + integration = { type: 'application', appId: collectConfig.appId, publisherId: configParams.publisherId } + } else if (configParams.distributorId != null && configParams.publisherId == null) { + integration = { type: 'distributor', distributorId: configParams.distributorId } + } else { + integration = { type: 'custom', publisherId: configParams.publisherId, distributorId: configParams.distributorId } + } + + const partnerCookies = new Set(configParams.identifiersToResolve ?? []); + + const collectSettings = { timeout: collectConfig.ajaxTimeout ?? DEFAULT_AJAX_TIMEOUT } + + let identityPartner + if (collectConfig.appId == null && configParams.distributorId != null) { + identityPartner = configParams.distributorId + } else if (configParams.partner != null) { + identityPartner = configParams.partner + } else { + identityPartner = 'prebid' + } + + const resolveSettings = { + identityPartner, + timeout: configParams.ajaxTimeout ?? DEFAULT_AJAX_TIMEOUT + } + + let idCookieSettings + if (configParams.fpid != null) { + const fpidConfig = configParams.fpid + let source + if (fpidConfig.strategy === 'html5') { + source = 'local_storage' + } else { + source = fpidConfig.strategy + } + idCookieSettings = { idCookieSettings: { type: 'provided', source, key: fpidConfig.name } }; + } else { + idCookieSettings = {} + } + + function loadConsent() { + const consent = {} + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString != null) { + consent.usPrivacy = { consentString: usPrivacyString } + } + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent != null) { + consent.gdpr = gdprConsent + } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent != null) { + consent.gpp = { consentString: gppConsent.gppString, applicableSections: gppConsent.applicableSections } + } + + return consent + } + const consent = loadConsent() + + window.liQHub.push({ + type: 'register_client', + clientRef, + clientDetails, + integration, + consent, + partnerCookies, + collectSettings, + ...idCookieSettings, + resolveSettings + }) + + if (configParams.emailHash != null) { + window.liQHub.push({ type: 'collect', clientRef, sourceEvent: { hash: configParams.emailHash } }) + } + + cachedClientRef = clientRef + return clientRef +} + +/** + * Create requestedAttributes array to pass to LiveConnect. + * @function + * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } + * @returns {Array} + */ + +function resolve(configParams, clientRef, callback) { + function onFailure(error) { + logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); + callback(); + } + + const onSuccess = [{ type: 'callback', callback }] + + window.liQHub.push({ + type: 'resolve', + clientRef, + requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides), + onFailure, + onSuccess + }) +} + +/** + * @typedef {import('../../modules/userId/index.js').Submodule} Submodule + */ + +/** @type {Submodule} */ +export const liveIntentExternalIdSubmodule = { + /** + * Used to link submodule with config. + * @type {string} + */ + name: MODULE_NAME, + gvlid: GVLID, + + /** + * Decode the stored id value for passing to bid requests. + * @function + */ + decode(value, config) { + const configParams = config?.params ?? {}; + + // Ensure client is initialized and we fired at least one collect request. + initializeClient(configParams) + + return composeIdObject(value); + }, + + /** + * Performs action to obtain id and return a value in the callback's response argument. + * @function + */ + getId(config) { + const configParams = config?.params ?? {}; + + const clientRef = initializeClient(configParams) + + return { callback: function(cb) { resolve(configParams, clientRef, cb); } }; + }, + primaryIds: PRIMARY_IDS, + eids +}; + +submodule('userId', liveIntentExternalIdSubmodule); diff --git a/libraries/liveIntentId/idSystem.js b/libraries/liveIntentId/idSystem.js new file mode 100644 index 00000000000..973dd9ba2eb --- /dev/null +++ b/libraries/liveIntentId/idSystem.js @@ -0,0 +1,227 @@ +/** + * This module adds LiveIntentId to the User ID module. + * The {@link module:modules/userId} module is required. + * @module modules/idSystem + * @requires module:modules/userId + */ +import { triggerPixel, logError } from '../../src/utils.js'; +import { ajaxBuilder } from '../../src/ajax.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../src/adapterManager.js'; +import { submodule } from '../../src/hook.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports +import { getStorageManager } from '../../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../../src/activities/modules.js'; +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, DEFAULT_DELAY, GVLID, PRIMARY_IDS, parseRequestedAttributes } from './shared.js' + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const EVENTS_TOPIC = 'pre_lips'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); +const calls = { + ajaxGet: (url, onSuccess, onError, timeout) => { + ajaxBuilder(timeout)( + url, + { + success: onSuccess, + error: onError + }, + undefined, + { + method: 'GET', + withCredentials: true + } + ) + }, + pixelGet: (url, onload) => triggerPixel(url, onload) +} + +let eventFired = false; +let liveConnect = null; + +/** + * This function is used in tests. + */ +export function reset() { + if (window && window.liQ_instances) { + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)); + window.liQ_instances = []; + } + liveIntentIdSubmodule.setModuleMode(null); + eventFired = false; + liveConnect = null; +} + +/** + * This function is used in tests. + */ +export function setEventFiredFlag() { + eventFired = true; +} + +function parseLiveIntentCollectorConfig(collectConfig) { + const config = {}; + collectConfig = collectConfig || {}; + collectConfig.appId && (config.appId = collectConfig.appId); + collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); + collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); + collectConfig.collectorUrl && (config.collectorUrl = collectConfig.collectorUrl); + config.ajaxTimeout = collectConfig.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; + return config; +} + +/** + * Create requestedAttributes array to pass to LiveConnect. + * @function + * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } + * @returns {Array} + */ + +function initializeLiveConnect(configParams) { + if (liveConnect) { + return liveConnect; + } + + configParams = configParams || {}; + const fpidConfig = configParams.fpid || {}; + + const publisherId = configParams.publisherId || 'any'; + const identityResolutionConfig = { + publisherId: publisherId, + requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) + }; + if (configParams.url) { + identityResolutionConfig.url = configParams.url; + }; + + identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; + + const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); + + if (!liveConnectConfig.appId && configParams.distributorId) { + liveConnectConfig.distributorId = configParams.distributorId; + identityResolutionConfig.source = configParams.distributorId; + } else { + identityResolutionConfig.source = configParams.partner || 'prebid'; + } + + liveConnectConfig.wrapperName = 'prebid'; + liveConnectConfig.trackerVersion = '$prebid.version$'; + liveConnectConfig.identityResolutionConfig = identityResolutionConfig; + liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; + liveConnectConfig.fireEventDelay = configParams.fireEventDelay; + + liveConnectConfig.idCookie = {}; + liveConnectConfig.idCookie.name = fpidConfig.name; + liveConnectConfig.idCookie.strategy = fpidConfig.strategy == 'html5' ? 'localStorage' : fpidConfig.strategy; + + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + liveConnectConfig.usPrivacyString = usPrivacyString; + } + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; + liveConnectConfig.gdprConsent = gdprConsent.consentString; + } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + liveConnectConfig.gppString = gppConsent.gppString; + liveConnectConfig.gppApplicableSections = gppConsent.applicableSections; + } + // The second param is the storage object, LS & Cookie manipulation uses PBJS. + // The third param is the ajax and pixel object, the AJAX and pixel use PBJS. + liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); + if (configParams.emailHash) { + liveConnect.push({ hash: configParams.emailHash }); + } + return liveConnect; +} + +function tryFireEvent() { + if (!eventFired && liveConnect) { + const eventDelay = liveConnect.config.fireEventDelay || DEFAULT_DELAY; + setTimeout(() => { + const instances = window.liQ_instances; + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)); + if (!eventFired && liveConnect) { + liveConnect.fire(); + } + }, eventDelay); + } +} + +/** @type {Submodule} */ +export const liveIntentIdSubmodule = { + moduleMode: '$$LIVE_INTENT_MODULE_MODE$$', + /** + * Used to link submodule with config. + * @type {string} + */ + name: MODULE_NAME, + gvlid: GVLID, + setModuleMode(mode) { + this.moduleMode = mode; + }, + getInitializer() { + return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode); + }, + + /** + * Decode the stored id value for passing to bid requests. + * Note that lipb object is a wrapper for everything, and + * internally it could contain more data other than `lipbid` + * (e.g. `segments`) depending on the `partner` and `publisherId` + * params. + * @function + * @param {{unifiedId:string}} value + * @param {SubmoduleConfig|undefined} config + * @returns {{lipb:Object}} + */ + decode(value, config) { + const configParams = (config && config.params) || {}; + + if (!liveConnect) { + initializeLiveConnect(configParams); + } + tryFireEvent(); + + return composeIdObject(value); + }, + + /** + * Performs action to obtain id and return a value in the callback's response argument. + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId(config) { + const configParams = (config && config.params) || {}; + const liveConnect = initializeLiveConnect(configParams); + if (!liveConnect) { + return; + } + tryFireEvent(); + const result = function(callback) { + liveConnect.resolve( + response => { + callback(response); + }, + error => { + logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); + callback(); + } + ) + } + + return { callback: result }; + }, + primaryIds: PRIMARY_IDS, + eids +}; + +submodule('userId', liveIntentIdSubmodule); diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js new file mode 100644 index 00000000000..66b0c8611ab --- /dev/null +++ b/libraries/liveIntentId/shared.js @@ -0,0 +1,202 @@ +import {UID1_EIDS} from '../uid1Eids/uid1Eids.js'; +import {UID2_EIDS} from '../uid2Eids/uid2Eids.js'; +import { getRefererInfo } from '../../src/refererDetection.js'; +import { coppaDataHandler } from '../../src/adapterManager.js'; + +export const PRIMARY_IDS = ['libp']; +export const GVLID = 148; +export const DEFAULT_AJAX_TIMEOUT = 5000; +export const MODULE_NAME = 'liveIntentId'; +export const LI_PROVIDER_DOMAIN = 'liveintent.com'; +export const DEFAULT_REQUESTED_ATTRIBUTES = { 'nonId': true }; + +export function parseRequestedAttributes(overrides) { + function renameAttribute(attribute) { + if (attribute === 'fpid') { + return 'idCookie'; + } else { + return attribute; + }; + } + function createParameterArray(config) { + return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [renameAttribute(k)] : []); + } + if (typeof overrides === 'object') { + return createParameterArray({...DEFAULT_REQUESTED_ATTRIBUTES, ...overrides}); + } else { + return createParameterArray(DEFAULT_REQUESTED_ATTRIBUTES); + } +} + +export function composeIdObject(value) { + const result = {}; + + // old versions stored lipbid in unifiedId. Ensure that we can still read the data. + const lipbid = value.nonId || value.unifiedId + if (lipbid) { + const lipb = { ...value, lipbid }; + delete lipb.unifiedId; + result.lipb = lipb; + } + + // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. + // As adapters are applied in lexicographical order, we will always + // be overwritten by the 'proper' uid2 module if it is present. + if (value.uid2) { + result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.bidswitch) { + result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.medianet) { + result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.magnite) { + result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.index) { + result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.openx) { + result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.pubmatic) { + result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.sovrn) { + result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.idCookie) { + if (!coppaDataHandler.getCoppa()) { + result.lipb = { ...result.lipb, fpid: value.idCookie }; + result.fpid = { 'id': value.idCookie }; + } + delete result.lipb.idCookie; + } + + if (value.thetradedesk) { + result.lipb = {...result.lipb, tdid: value.thetradedesk} + result.tdid = { 'id': value.thetradedesk, ext: { rtiPartner: 'TDID', provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } + delete result.lipb.thetradedesk + } + + return result +} + +export const eids = { + ...UID1_EIDS, + ...UID2_EIDS, + 'lipb': { + getValue: function(data) { + return data.lipbid; + }, + source: 'liveintent.com', + atype: 3, + getEidExt: function(data) { + if (Array.isArray(data.segments) && data.segments.length) { + return { + segments: data.segments + }; + } + } + }, + 'bidswitch': { + source: 'bidswitch.net', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'medianet': { + source: 'media.net', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'magnite': { + source: 'rubiconproject.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'index': { + source: 'liveintent.indexexchange.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'openx': { + source: 'openx.net', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'pubmatic': { + source: 'pubmatic.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'sovrn': { + source: 'liveintent.sovrn.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'fpid': { + source: 'fpid.liveintent.com', + atype: 1, + getValue: function(data) { + return data.id; + } + } +} diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 50a8dc2aa1d..2ccbb911478 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -1,417 +1,14 @@ -/** - * This module adds LiveIntentId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/liveIntentIdSystem - * @requires module:modules/userId - */ -import { triggerPixel, logError } from '../src/utils.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/prebid'; // eslint-disable-line prebid/validate-imports -import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -import { UID2_EIDS } from '../libraries/uid2Eids/uid2Eids.js'; -import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; -import { getRefererInfo } from '../src/refererDetection.js'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse - */ -const GVLID = 148; -const DEFAULT_AJAX_TIMEOUT = 5000; -const EVENTS_TOPIC = 'pre_lips'; -const MODULE_NAME = 'liveIntentId'; -const LI_PROVIDER_DOMAIN = 'liveintent.com'; -export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); -const defaultRequestedAttributes = {'nonId': true}; -const calls = { - ajaxGet: (url, onSuccess, onError, timeout) => { - ajaxBuilder(timeout)( - url, - { - success: onSuccess, - error: onError - }, - undefined, - { - method: 'GET', - withCredentials: true - } - ) - }, - pixelGet: (url, onload) => triggerPixel(url, onload) -} - -let eventFired = false; -let liveConnect = null; - -/** - * This function is used in tests - */ -export function reset() { - if (window && window.liQ_instances) { - window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)); - window.liQ_instances = []; - } - liveIntentIdSubmodule.setModuleMode(null); - eventFired = false; - liveConnect = null; -} - -/** - * This function is also used in tests - */ -export function setEventFiredFlag() { - eventFired = true; -} - -function parseLiveIntentCollectorConfig(collectConfig) { - const config = {}; - collectConfig = collectConfig || {}; - collectConfig.appId && (config.appId = collectConfig.appId); - collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); - collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); - collectConfig.collectorUrl && (config.collectorUrl = collectConfig.collectorUrl); - config.ajaxTimeout = collectConfig.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; - return config; -} - -/** - * Create requestedAttributes array to pass to liveconnect - * @function - * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } - * @returns {Array} - */ -function parseRequestedAttributes(overrides) { - function renameAttribute(attribute) { - if (attribute === 'fpid') { - return 'idCookie'; - } else { - return attribute; - }; - } - function createParameterArray(config) { - return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [renameAttribute(k)] : []); - } - if (typeof overrides === 'object') { - return createParameterArray({...defaultRequestedAttributes, ...overrides}); +function loadModule() { + // Load appropriate module based on the build flag. Constant folding ensures + // that the other one will not be included in the bundle. + // eslint-disable-next-line no-constant-condition + if ('$$LIVE_INTENT_MODULE_MODE$$' === 'external') { + // eslint-disable-next-line no-restricted-globals + return require('../libraries/liveIntentId/externalIdSystem.js') } else { - return createParameterArray(defaultRequestedAttributes); - } -} - -function initializeLiveConnect(configParams) { - if (liveConnect) { - return liveConnect; - } - - configParams = configParams || {}; - const fpidConfig = configParams.fpid || {}; - - const publisherId = configParams.publisherId || 'any'; - const identityResolutionConfig = { - publisherId: publisherId, - requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) - }; - if (configParams.url) { - identityResolutionConfig.url = configParams.url; - }; - - identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; - - const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); - - if (!liveConnectConfig.appId && configParams.distributorId) { - liveConnectConfig.distributorId = configParams.distributorId; - identityResolutionConfig.source = configParams.distributorId; - } else { - identityResolutionConfig.source = configParams.partner || 'prebid'; - } - - liveConnectConfig.wrapperName = 'prebid'; - liveConnectConfig.trackerVersion = '$prebid.version$'; - liveConnectConfig.identityResolutionConfig = identityResolutionConfig; - liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; - liveConnectConfig.fireEventDelay = configParams.fireEventDelay; - - liveConnectConfig.idCookie = {}; - liveConnectConfig.idCookie.name = fpidConfig.name; - liveConnectConfig.idCookie.strategy = fpidConfig.strategy == 'html5' ? 'localStorage' : fpidConfig.strategy; - - const usPrivacyString = uspDataHandler.getConsentData(); - if (usPrivacyString) { - liveConnectConfig.usPrivacyString = usPrivacyString; - } - const gdprConsent = gdprDataHandler.getConsentData(); - if (gdprConsent) { - liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; - liveConnectConfig.gdprConsent = gdprConsent.consentString; - } - const gppConsent = gppDataHandler.getConsentData(); - if (gppConsent) { - liveConnectConfig.gppString = gppConsent.gppString; - liveConnectConfig.gppApplicableSections = gppConsent.applicableSections; - } - // The second param is the storage object, LS & Cookie manipulation uses PBJS - // The third param is the ajax and pixel object, the ajax and pixel use PBJS - liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); - if (configParams.emailHash) { - liveConnect.push({ hash: configParams.emailHash }); - } - return liveConnect; -} - -function tryFireEvent() { - if (!eventFired && liveConnect) { - const eventDelay = liveConnect.config.fireEventDelay || 500; - setTimeout(() => { - const instances = window.liQ_instances; - instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)); - if (!eventFired && liveConnect) { - liveConnect.fire(); - } - }, eventDelay); + // eslint-disable-next-line no-restricted-globals + return require('../libraries/liveIntentId/idSystem.js') } } -/** @type {Submodule} */ -export const liveIntentIdSubmodule = { - moduleMode: '$$LIVE_INTENT_MODULE_MODE$$', - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - gvlid: GVLID, - setModuleMode(mode) { - this.moduleMode = mode; - }, - getInitializer() { - return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode); - }, - - /** - * decode the stored id value for passing to bid requests. Note that lipb object is a wrapper for everything, and - * internally it could contain more data other than `lipbid`(e.g. `segments`) depending on the `partner` and - * `publisherId` params. - * @function - * @param {{unifiedId:string}} value - * @param {SubmoduleConfig|undefined} config - * @returns {{lipb:Object}} - */ - decode(value, config) { - const configParams = (config && config.params) || {}; - function composeIdObject(value) { - const result = {}; - - // old versions stored lipbid in unifiedId. Ensure that we can still read the data. - const lipbid = value.nonId || value.unifiedId; - if (lipbid) { - const lipb = { ...value, lipbid }; - delete lipb.unifiedId; - result.lipb = lipb; - } - - // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. - // As adapters are applied in lexicographical order, we will always - // be overwritten by the 'proper' uid2 module if it is present. - if (value.uid2) { - result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.bidswitch) { - result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.medianet) { - result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.magnite) { - result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.index) { - result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.openx) { - result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.pubmatic) { - result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.sovrn) { - result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } }; - } - - if (value.idCookie) { - if (!coppaDataHandler.getCoppa()) { - result.lipb = { ...result.lipb, fpid: value.idCookie }; - result.fpid = { 'id': value.idCookie }; - } - delete result.lipb.idCookie; - } - - if (value.thetradedesk) { - result.lipb = {...result.lipb, tdid: value.thetradedesk} - result.tdid = { 'id': value.thetradedesk, ext: { rtiPartner: 'TDID', provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } - delete result.lipb.thetradedesk - } - - return result - } - - if (!liveConnect) { - initializeLiveConnect(configParams); - } - tryFireEvent(); - - return composeIdObject(value); - }, - - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @returns {IdResponse|undefined} - */ - getId(config) { - const configParams = (config && config.params) || {}; - const liveConnect = initializeLiveConnect(configParams); - if (!liveConnect) { - return; - } - tryFireEvent(); - const result = function(callback) { - liveConnect.resolve( - response => { - callback(response); - }, - error => { - logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); - callback(); - } - ) - } - - return { callback: result }; - }, - primaryIds: ['libp'], - eids: { - ...UID1_EIDS, - ...UID2_EIDS, - 'lipb': { - getValue: function(data) { - return data.lipbid; - }, - source: 'liveintent.com', - atype: 3, - getEidExt: function(data) { - if (Array.isArray(data.segments) && data.segments.length) { - return { - segments: data.segments - }; - } - } - }, - 'bidswitch': { - source: 'bidswitch.net', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'medianet': { - source: 'media.net', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'magnite': { - source: 'rubiconproject.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'index': { - source: 'liveintent.indexexchange.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'openx': { - source: 'openx.net', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'pubmatic': { - source: 'pubmatic.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'sovrn': { - source: 'liveintent.sovrn.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'fpid': { - source: 'fpid.liveintent.com', - atype: 1, - getValue: function(data) { - return data.id; - } - } - } -}; - -submodule('userId', liveIntentIdSubmodule); +export const liveIntentIdSubmodule = loadModule() diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js new file mode 100644 index 00000000000..1b5eeffecd2 --- /dev/null +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -0,0 +1,419 @@ +import { liveIntentExternalIdSubmodule, resetSubmodule } from 'libraries/liveIntentId/externalIdSystem.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; +import * as refererDetection from '../../../src/refererDetection.js'; +const DEFAULT_AJAX_TIMEOUT = 5000 +const PUBLISHER_ID = '89899'; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; + +describe('LiveIntentExternalId', function() { + let uspConsentDataStub; + let gdprConsentDataStub; + let gppConsentDataStub; + let coppaConsentDataStub; + let refererInfoStub; + + beforeEach(function() { + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + coppaConsentDataStub = sinon.stub(coppaDataHandler, 'getCoppa'); + refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + }); + + afterEach(function() { + uspConsentDataStub.restore(); + gdprConsentDataStub.restore(); + gppConsentDataStub.restore(); + coppaConsentDataStub.restore(); + refererInfoStub.restore(); + window.liQHub = []; // reset + resetSubmodule(); + }); + + it('should use appId in integration when both appId and distributorId are provided', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + distributorId: 'did-1111', + liCollectConfig: { + appId: 'a-0001' + }, + emailHash: '123' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { appId: 'a-0001', publisherId: '89899', type: 'application' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { hash: '123' }, + type: 'collect' + }]) + }); + + it('should fire an event and resolve when getId and include the privacy settings into the resolution request', function () { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: true, + consentString: 'consentDataString' + }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1, 2] + }) + liveIntentExternalIdSubmodule.getId(defaultConfigParams).callback(() => {}); + + const expectedConsent = { gdpr: { consentString: 'consentDataString', gdprApplies: true }, gpp: { applicableSections: [1, 2], consentString: 'gppConsentDataString' }, usPrivacy: { consentString: '1YNY' } } + + expect(window.liQHub).to.have.length(2) + + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: expectedConsent, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId' ], + type: 'resolve' + }) + }); + + it('should fire an event when getId and a hash is provided', function() { + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + emailHash: '58131bc547fb87af94cebdaf3102321f' + }}).callback(() => {}); + + expect(window.liQHub).to.have.length(3) + + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + expect(window.liQHub[1]).to.eql({ + clientRef: {}, + sourceEvent: { hash: '58131bc547fb87af94cebdaf3102321f' }, + type: 'collect' + }) + + const resolveCommand = window.liQHub[2] + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId' ], + type: 'resolve' + }) + }); + + it('should have the same data after call decode when appId, disrtributorId and sourceEvent is absent', function() { + liveIntentExternalIdSubmodule.decode({}, { + params: { + ...defaultConfigParams.params, + distributorId: undefined + } + }); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: undefined, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }]) + }); + + it('should have the same data after call decode when appId and sourceEvent is present', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + liCollectConfig: { + appId: 'a-0001' + }, + emailHash: '123' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { appId: 'a-0001', publisherId: '89899', type: 'application' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { hash: '123' }, + type: 'collect' + }]) + }); + + it('should have the same data after call decode when distributorId and sourceEvent is present', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + distributorId: 'did-1111', + emailHash: '123' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: 'did-1111', publisherId: defaultConfigParams.params.publisherId, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'did-1111', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { hash: '123' }, + type: 'collect' + }]) + }); + + it('should have the same data when decode with privacy settings', function() { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: false, + consentString: 'consentDataString' + }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1] + }) + liveIntentExternalIdSubmodule.decode({}, defaultConfigParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: { gdpr: { consentString: 'consentDataString', gdprApplies: false }, gpp: { applicableSections: [1], consentString: 'gppConsentDataString' }, usPrivacy: { consentString: '1YNY' } }, + integration: { distributorId: defaultConfigParams.params.distributorId, publisherId: defaultConfigParams.params.publisherId, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }]) + }); + + it('should not fire event again when it is already fired', function() { + liveIntentExternalIdSubmodule.decode({}, defaultConfigParams); + liveIntentExternalIdSubmodule.decode({}, defaultConfigParams); + + expect(window.liQHub).to.have.length(1) // instead of 2 + }); + + it('should not return a decoded identifier when the unifiedId is not present in the value', function() { + const result = liveIntentExternalIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); + expect(result).to.be.eql({}); + }); + + it('should decode a unifiedId to lipbId and remove it', function() { + const result = liveIntentExternalIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'data'}}); + }); + + it('should decode a nonId to lipbId', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); + }); + + it('should resolve extra attributes', function() { + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } + } }).callback(() => {}); + + expect(window.liQHub).to.have.length(2) + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId', 'foo' ], + type: 'resolve' + }) + }); + + it('should decode a uid2 to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode values with uid2 but no nonId', function() { + const result = liveIntentExternalIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); + expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a bidswitch id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a medianet id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a sovrn id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a magnite id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an index id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an openx id to a separate object when present', function () { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an pubmatic id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a thetradedesk id to a separate object when present', function() { + const provider = 'liveintent.com' + refererInfoStub.returns({domain: provider}) + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); + }); + + it('should allow disabling nonId resolution', function() { + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } + } }).callback(() => {}); + + expect(window.liQHub).to.have.length(2) + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'uid2' ], + type: 'resolve' + }) + }); + + it('should decode a idCookie as fpid if it exists and coppa is false', function() { + coppaConsentDataStub.returns(false) + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', idCookie: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'fpid': 'bar'}, 'fpid': {'id': 'bar'}}); + }); + + it('should not decode a idCookie as fpid if it exists and coppa is true', function() { + coppaConsentDataStub.returns(true) + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', idCookie: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo'}}) + }); + + it('should resolve fpid from cookie', function() { + const cookieName = 'testcookie' + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + fpid: { 'strategy': 'cookie', 'name': cookieName }, + requestedAttributesOverrides: { 'fpid': true } } + }).callback(() => {}); + + expect(window.liQHub).to.have.length(2) + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: PUBLISHER_ID, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + idCookieSettings: { type: 'provided', key: 'testcookie', source: 'cookie' }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId', 'idCookie' ], + type: 'resolve' + }) + }); +}); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index e280d9108a0..1c9c19d51d7 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -1,7 +1,7 @@ import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; -import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from '../../../libraries/liveIntentId/idSystem.js'; import * as refererDetection from '../../../src/refererDetection.js'; const PUBLISHER_ID = '89899'; diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index dae19d0f578..a630c73eb2b 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -1,4 +1,4 @@ -import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'libraries/liveIntentId/idSystem.js'; import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 02c70507a07..6ebe533e260 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -22,7 +22,7 @@ import * as events from 'src/events.js'; import {EVENTS} from 'src/constants.js'; import {getGlobal} from 'src/prebidGlobal.js'; import {resetConsentData, } from 'modules/consentManagementTcf.js'; -import {setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from 'modules/liveIntentIdSystem.js'; +import {setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from '../../../libraries/liveIntentId/idSystem.js'; import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import * as mockGpt from '../integration/faker/googletag.js'; From 0b882c2a7d39dfffc31920ec2ead2b9a2307bb99 Mon Sep 17 00:00:00 2001 From: Komal Kumari <169047654+pm-komal-kumari@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:57:22 +0530 Subject: [PATCH 0547/1097] Timeout RTD Issue Fix (#12274) Co-authored-by: Komal Kumari --- modules/rtdModule/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 18736c6b0ec..2ebc334b29e 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -318,10 +318,8 @@ export const setBidRequestsData = timedAuctionHook('rtd', function setBidRequest relevantSubModules.forEach(sm => { const fpdGuard = guardOrtb2Fragments(reqBidsConfigObj.ortb2Fragments || {}, activityParams(MODULE_TYPE_RTD, sm.name)); verifiers.push(fpdGuard.verify); - sm.getBidRequestData({ - ...reqBidsConfigObj, - ortb2Fragments: fpdGuard.obj - }, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) + reqBidsConfigObj.ortb2Fragments = fpdGuard.obj; + sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) }); function onGetBidRequestDataCallback() { From d807678b87bb7a4e6b26274154d5ea88bdb8b8e1 Mon Sep 17 00:00:00 2001 From: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:12:42 -0400 Subject: [PATCH 0548/1097] Prebid Core: refactor to targeting file for readability and efficiency (#12273) * refactor to targeting file for readability and efficiency js doc fix * fix update update fix again --------- Co-authored-by: Michael Moschovas --- src/targeting.js | 395 +++++++++++++------------- src/utils.js | 4 + test/fixtures/fixtures.js | 6 +- test/spec/unit/core/targeting_spec.js | 24 +- test/spec/unit/pbjs_api_spec.js | 15 +- test/spec/utils_spec.js | 2 +- 6 files changed, 239 insertions(+), 207 deletions(-) diff --git a/src/targeting.js b/src/targeting.js index 9a2ea5d66fa..aecc29787c7 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -29,6 +29,7 @@ import { logInfo, logMessage, logWarn, + sortByHighestCpm, timestamp, uniques, } from './utils.js'; @@ -67,7 +68,7 @@ export function isBidUsable(bid) { // If two bids are found for same adUnitCode, we will use the highest one to take part in auction // This can happen in case of concurrent auctions // If adUnitBidLimit is set above 0 return top N number of bids -export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { +export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, winReducer, adUnitBidLimit = 0, hasModified = false, winSorter = sortByHighestCpm) { if (!hasModified) { const bids = []; const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); @@ -76,13 +77,14 @@ export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, // filter top bid for each bucket by bidder Object.keys(buckets).forEach(bucketKey => { let bucketBids = []; - let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); + let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode') + Object.keys(bidsByBidder).forEach(key => { bucketBids.push(bidsByBidder[key].reduce(winReducer)) }); // if adUnitBidLimit is set, pass top N number bids - if (adUnitBidLimit > 0) { + if (adUnitBidLimit) { bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); bids.push(...bucketBids.slice(0, adUnitBidLimit)); } else { + bucketBids = bucketBids.sort(winSorter) bids.push(...bucketBids); } }); @@ -149,6 +151,17 @@ export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots }, Object.fromEntries(adUnitCodes.map(au => [au, []]))); } +/** + * Clears targeting for bids + */ +function clearTargeting(slot) { + pbTargetingKeys.forEach(key => { + if (slot.getTargeting(key)) { + slot.clearTargeting(key) + } + }) +} + /** * @typedef {Object.} targeting * @property {string} targeting_key @@ -169,12 +182,10 @@ export function newTargeting(auctionManager) { targeting.resetPresetTargeting = function(adUnitCode, customSlotMatching) { if (isGptPubadsDefined()) { const adUnitCodes = getAdUnitCodes(adUnitCode); - let unsetKeys = pbTargetingKeys.reduce((reducer, key) => { - reducer[key] = null; - return reducer; - }, {}); Object.values(getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching)).forEach((slots) => { - slots.forEach(slot => slot.updateTargetingFromMap(unsetKeys)) + slots.forEach(slot => { + clearTargeting(slot) + }) }) } }; @@ -196,39 +207,35 @@ export function newTargeting(auctionManager) { }); }; - /** - * checks if bid has targeting set and belongs based on matching ad unit codes - * @return {boolean} true or false - */ - function bidShouldBeAddedToTargeting(bid, adUnitCodes) { - return bid.adserverTargeting && adUnitCodes && - ((isArray(adUnitCodes) && includes(adUnitCodes, bid.adUnitCode)) || - (typeof adUnitCodes === 'string' && bid.adUnitCode === adUnitCodes)); - }; + function addBidToTargeting(bids, bidderLevelTargetingEnabled = false, deals = false) { + if (!bidderLevelTargetingEnabled) return []; - /** - * Returns targeting for any bids which have deals if alwaysIncludeDeals === true - */ - function getDealBids(adUnitCodes, bidsReceived) { - if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { - const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); - - // we only want the top bid from bidders who have multiple entries per ad unit code - const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm); - - // populate targeting keys for the remaining bids if they have a dealId - return bids.map(bid => { - if (bid.dealId && bidShouldBeAddedToTargeting(bid, adUnitCodes)) { - return { - [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( - key => typeof bid.adserverTargeting[key] !== 'undefined') - ) - }; + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); + const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); + + const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys + ? allowSendAllBidsTargetingKeys.map((key) => TARGETING_KEYS[key]) + : standardKeys; + + return bids.reduce((result, bid) => { + if ((!deals || bid.dealId)) { + const targetingValue = getTargetingMap(bid, standardKeys.filter( + key => typeof bid.adserverTargeting[key] !== 'undefined' && + (deals || allowedSendAllBidTargeting.indexOf(key) !== -1))); + + if (targetingValue) { + result.push({[bid.adUnitCode]: targetingValue}) } - }).filter(bid => bid); // removes empty elements in array - } - return []; - }; + } + return result; + }, []); + } + + function getBidderTargeting(bids) { + const alwaysIncludeDeals = config.getConfig('targetingControls.alwaysIncludeDeals'); + const bidderLevelTargetingEnabled = config.getConfig('enableSendAllBids') || alwaysIncludeDeals; + return addBidToTargeting(bids, bidderLevelTargetingEnabled, alwaysIncludeDeals); + } /** * Returns filtered ad server targeting for custom and allowed keys. @@ -277,26 +284,15 @@ export function newTargeting(auctionManager) { * @param {string=} adUnitCode * @return {Object.} targeting */ - targeting.getAllTargeting = function(adUnitCode, bidsReceived = getBidsReceived()) { + targeting.getAllTargeting = function(adUnitCode, bidLimit, bidsReceived, winReducer = getHighestCpm, winSorter = sortByHighestCpm) { + bidsReceived ||= getBidsReceived(winReducer, winSorter); const adUnitCodes = getAdUnitCodes(adUnitCode); - - // Get targeting for the winning bid. Add targeting for any bids that have - // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. - var targeting = getWinningBidTargeting(adUnitCodes, bidsReceived) - .concat(getCustomBidTargeting(adUnitCodes, bidsReceived)) - .concat(config.getConfig('enableSendAllBids') ? getBidLandscapeTargeting(adUnitCodes, bidsReceived) : getDealBids(adUnitCodes, bidsReceived)) - .concat(getAdUnitTargeting(adUnitCodes)); - - // store a reference of the targeting keys - targeting.map(adUnitCode => { - Object.keys(adUnitCode).map(key => { - adUnitCode[key].map(targetKey => { - if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) { - pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys); - } - }); - }); - }); + const sendAllBids = config.getConfig('enableSendAllBids'); + const bidLimitConfigValue = config.getConfig('sendBidsControl.bidLimit'); + const adUnitBidLimit = (sendAllBids && (bidLimit || bidLimitConfigValue)) || 0; + const { customKeysByUnit, filteredBids } = getfilteredBidsAndCustomKeys(adUnitCodes, bidsReceived); + const bidsSorted = getHighestCpmBidsFromBidPool(filteredBids, winReducer, adUnitBidLimit, undefined, winSorter); + let targeting = getTargetingLevels(bidsSorted, customKeysByUnit); const defaultKeys = Object.keys(Object.assign({}, DEFAULT_TARGETING_KEYS, NATIVE_KEYS)); let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS); @@ -332,6 +328,63 @@ export function newTargeting(auctionManager) { return targeting; }; + function updatePBTargetingKeys(adUnitCode) { + (Object.keys(adUnitCode)).forEach(key => { + adUnitCode[key].forEach(targetKey => { + const targetKeys = Object.keys(targetKey); + if (pbTargetingKeys.indexOf(targetKeys[0]) === -1) { + pbTargetingKeys = targetKeys.concat(pbTargetingKeys); + } + }); + }); + } + + function getTargetingLevels(bidsSorted, customKeysByUnit) { + const targeting = getWinningBidTargeting(bidsSorted) + .concat(getCustomBidTargeting(bidsSorted, customKeysByUnit)) + .concat(getBidderTargeting(bidsSorted)) + .concat(getAdUnitTargeting()); + + targeting.forEach(adUnitCode => { + updatePBTargetingKeys(adUnitCode); + }); + + return targeting; + } + + function getfilteredBidsAndCustomKeys(adUnitCodes, bidsReceived) { + const filteredBids = []; + const customKeysByUnit = {}; + const alwaysIncludeDeals = config.getConfig('targetingControls.alwaysIncludeDeals'); + + bidsReceived.forEach(bid => { + const adUnitIsEligible = includes(adUnitCodes, bid.adUnitCode); + const cpmAllowed = bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true ? bid.cpm >= 0 : bid.cpm > 0; + const isPreferredDeal = alwaysIncludeDeals && bid.dealId; + + if (adUnitIsEligible && (isPreferredDeal || cpmAllowed)) { + filteredBids.push(bid); + Object.keys(bid.adserverTargeting) + .filter(getCustomKeys()) + .forEach(key => { + const truncKey = key.substring(0, MAX_DFP_KEYLENGTH); + const data = customKeysByUnit[bid.adUnitCode] || {}; + const value = [bid.adserverTargeting[key]]; + + if (data[truncKey]) { + data[truncKey] = data[truncKey].concat(value).filter(uniques); + } else { + data[truncKey] = value; + } + + customKeysByUnit[bid.adUnitCode] = data; + }) + } + }); + + return {filteredBids, customKeysByUnit}; + } + // warn about conflicting configuration config.getConfig('targetingControls', function (config) { if (deepAccess(config, CFG_ALLOW_TARGETING_KEYS) != null && deepAccess(config, CFG_ADD_TARGETING_KEYS) != null) { @@ -423,27 +476,30 @@ export function newTargeting(auctionManager) { }; }).reduce((p, c) => Object.assign(c, p), {}) }; - }).reduce(function (accumulator, targeting) { + }) + + targetingObj = targetingObj.reduce(function (accumulator, targeting) { var key = Object.keys(targeting)[0]; accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); return accumulator; }, {}); + return targetingObj; } targeting.setTargetingForGPT = hook('sync', function (adUnit, customSlotMatching) { - // get our ad unit codes + // get our ad unit codes let targetingSet = targeting.getAllTargeting(adUnit); let resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null])); Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => { slots.forEach(slot => { - // now set new targeting keys + // now set new targeting keys Object.keys(targetingSet[targetId]).forEach(key => { let value = targetingSet[targetId][key]; if (typeof value === 'string' && value.indexOf(',') !== -1) { - // due to the check the array will be formed only if string has ',' else plain string will be assigned as value + // due to the check the array will be formed only if string has ',' else plain string will be assigned as value value = value.split(','); } targetingSet[targetId][key] = value; @@ -485,50 +541,54 @@ export function newTargeting(auctionManager) { return auctionManager.getAdUnitCodes() || []; } - function getBidsReceived() { - let bidsReceived = auctionManager.getBidsReceived(); - - if (!config.getConfig('useBidCache')) { - // don't use bid cache (i.e. filter out bids not in the latest auction) - bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId) - } else { - // if custom bid cache filter function exists, run for each bid from - // previous auctions. If it returns true, include bid in bid pool + function getBidsReceived(winReducer = getOldestHighestCpmBid, winSorter = undefined) { + let bidsReceived = auctionManager.getBidsReceived().reduce((bids, bid) => { + const bidCacheEnabled = config.getConfig('useBidCache'); const filterFunction = config.getConfig('bidCacheFilterFunction'); - if (typeof filterFunction === 'function') { - bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId || !!filterFunction(bid)) - } - } - - bidsReceived = bidsReceived - .filter(bid => deepAccess(bid, 'video.context') !== ADPOD) - .filter(isBidUsable); + const isBidFromLastAuction = latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId; + const filterFunctionResult = bidCacheEnabled && !isBidFromLastAuction && typeof filterFunction === 'function' ? !!filterFunction(bid) : true; + const cacheFilter = bidCacheEnabled || isBidFromLastAuction; + const bidFilter = cacheFilter && filterFunctionResult; - bidsReceived - .forEach(bid => { + if (bidFilter && deepAccess(bid, 'video.context') !== ADPOD && isBidUsable(bid)) { bid.latestTargetedAuctionId = latestAuctionForAdUnit[bid.adUnitCode]; - return bid; - }); + bids.push(bid) + } + + return bids; + }, []); - return getHighestCpmBidsFromBidPool(bidsReceived, getOldestHighestCpmBid); + return getHighestCpmBidsFromBidPool(bidsReceived, winReducer, undefined, undefined, undefined, winSorter); } /** * Returns top bids for a given adUnit or set of adUnits. * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes - * @param {Array} [bidsReceived=getBidsReceived()] - The received bids, defaulting to the result of getBidsReceived(). + * @param {(Array|undefined)} bids - The received bids, defaulting to the result of getBidsReceived(). + * @param {function(Array): Array} [winReducer = getHighestCpm] - reducer method + * @param {function(Array): Array} [winSorter = sortByHighestCpm] - sorter method * @return {Array} - An array of winning bids. */ - targeting.getWinningBids = function(adUnitCode, bidsReceived = getBidsReceived()) { + targeting.getWinningBids = function(adUnitCode, bids, winReducer = getHighestCpm, winSorter = sortByHighestCpm) { + const usedCodes = []; + const bidsReceived = bids || getBidsReceived(winReducer, winSorter); const adUnitCodes = getAdUnitCodes(adUnitCode); + return bidsReceived - .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .filter(bid => (bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true) ? bid.cpm >= 0 : bid.cpm > 0) - .map(bid => bid.adUnitCode) - .filter(uniques) - .map(adUnitCode => bidsReceived - .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) - .reduce(getHighestCpm)); + .reduce((result, bid) => { + const code = bid.adUnitCode; + const cpmEligible = bidderSettings.get(code, 'allowZeroCpmBids') === true ? bid.cpm >= 0 : bid.cpm > 0; + const isPreferredDeal = config.getConfig('targetingControls.alwaysIncludeDeals') && bid.dealId; + const eligible = includes(adUnitCodes, code) && + !includes(usedCodes, code) && + (isPreferredDeal || cpmEligible) + if (eligible) { + result.push(bid); + usedCodes.push(code); + } + + return result; + }, []); }; /** @@ -565,11 +625,20 @@ export function newTargeting(auctionManager) { /** * Get targeting key value pairs for winning bid. - * @param {string[]} adUnitCodes code array - * @return {targetingArray} winning bids targeting + * @param {Array} bidsReceived code array + * @return {targetingArray} winning bids targeting */ - function getWinningBidTargeting(adUnitCodes, bidsReceived) { - let winners = targeting.getWinningBids(adUnitCodes, bidsReceived); + function getWinningBidTargeting(bidsReceived) { + let usedAdUnitCodes = []; + let winners = bidsReceived + .reduce((bids, bid) => { + if (!includes(usedAdUnitCodes, bid.adUnitCode)) { + bids.push(bid); + usedAdUnitCodes.push(bid.adUnitCode); + } + return bids; + }, []); + let standardKeys = getStandardKeys(); winners = winners.map(winner => { @@ -601,44 +670,6 @@ export function newTargeting(auctionManager) { .concat(TARGETING_KEYS_ARR).filter(uniques); // standard keys defined in the library. } - /** - * Merge custom adserverTargeting with same key name for same adUnitCode. - * e.g: Appnexus defining custom keyvalue pair foo:bar and Rubicon defining custom keyvalue pair foo:baz will be merged to foo: ['bar','baz'] - * - * @param {Object[]} acc Accumulator for reducer. It will store updated bidResponse objects - * @param {Object} bid BidResponse - * @param {number} index current index - * @param {Array} arr original array - */ - function mergeAdServerTargeting(acc, bid, index, arr) { - function concatTargetingValue(key) { - return function(currentBidElement) { - if (!isArray(currentBidElement.adserverTargeting[key])) { - currentBidElement.adserverTargeting[key] = [currentBidElement.adserverTargeting[key]]; - } - currentBidElement.adserverTargeting[key] = currentBidElement.adserverTargeting[key].concat(bid.adserverTargeting[key]).filter(uniques); - delete bid.adserverTargeting[key]; - } - } - - function hasSameAdunitCodeAndKey(key) { - return function(currentBidElement) { - return currentBidElement.adUnitCode === bid.adUnitCode && currentBidElement.adserverTargeting[key] - } - } - - Object.keys(bid.adserverTargeting) - .filter(getCustomKeys()) - .forEach(key => { - if (acc.length) { - acc.filter(hasSameAdunitCodeAndKey(key)) - .forEach(concatTargetingValue(key)); - } - }); - acc.push(bid); - return acc; - } - function getCustomKeys() { let standardKeys = getStandardKeys(); if (FEATURES.NATIVE) { @@ -649,71 +680,42 @@ export function newTargeting(auctionManager) { } } - function truncateCustomKeys(bid) { - return { - [bid.adUnitCode]: Object.keys(bid.adserverTargeting) - // Get only the non-standard keys of the losing bids, since we - // don't want to override the standard keys of the winning bid. - .filter(getCustomKeys()) - .map(key => { - return { - [key.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] - }; - }) - } - } - /** * Get custom targeting key value pairs for bids. - * @param {string[]} adUnitCodes code array - * @return {targetingArray} bids with custom targeting defined in bidderSettings + * @param {Array} bidsSorted code array + * @param {Object} customKeysByUnit code array + * @return {targetingArray} bids with custom targeting defined in bidderSettings */ - function getCustomBidTargeting(adUnitCodes, bidsReceived) { - return bidsReceived - .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .map(bid => Object.assign({}, bid)) - .reduce(mergeAdServerTargeting, []) - .map(truncateCustomKeys) - .filter(bid => bid); // removes empty elements in array; - } - - /** - * Get targeting key value pairs for non-winning bids. - * @param {string[]} adUnitCodes code array - * @return {targetingArray} all non-winning bids targeting - */ - function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { - const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); - const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); - const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); - const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); + function getCustomBidTargeting(bidsSorted, customKeysByUnit) { + return bidsSorted + .reduce((acc, bid) => { + const newBid = Object.assign({}, bid); + const customKeysForUnit = customKeysByUnit[newBid.adUnitCode]; + const targeting = []; + + if (customKeysForUnit) { + Object.keys(customKeysForUnit).forEach(key => { + if (key && customKeysForUnit[key]) targeting.push({[key]: customKeysForUnit[key]}); + }) + } - const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys - ? allowSendAllBidsTargetingKeys.map((key) => TARGETING_KEYS[key]) - : standardKeys; + acc.push({[newBid.adUnitCode]: targeting}); - // populate targeting keys for the remaining bids - return bids.map(bid => { - if (bidShouldBeAddedToTargeting(bid, adUnitCodes)) { - return { - [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( - key => typeof bid.adserverTargeting[key] !== 'undefined' && - allowedSendAllBidTargeting.indexOf(key) !== -1) - ) - }; - } - }).filter(bid => bid); // removes empty elements in array + return acc; + }, []); } function getTargetingMap(bid, keys) { - return keys.map(key => { - return { - [`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] - }; - }); + return keys.reduce((targeting, key) => { + const value = bid.adserverTargeting[key]; + if (value) { + targeting.push({[`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]]}) + } + return targeting; + }, []); } - function getAdUnitTargeting(adUnitCodes) { + function getAdUnitTargeting() { function getTargetingObj(adUnit) { return deepAccess(adUnit, JSON_MAPPING.ADSERVER_TARGETING); } @@ -730,10 +732,13 @@ export function newTargeting(auctionManager) { } return auctionManager.getAdUnits() - .filter(adUnit => includes(adUnitCodes, adUnit.code) && getTargetingObj(adUnit)) - .map(adUnit => { - return {[adUnit.code]: getTargetingValues(adUnit)} - }); + .filter(adUnit => getTargetingObj(adUnit)) + .reduce((result, adUnit) => { + const targetingValues = getTargetingValues(adUnit); + + if (targetingValues)result.push({[adUnit.code]: targetingValues}); + return result; + }, []); } targeting.isApntagDefined = function() { diff --git a/src/utils.js b/src/utils.js index b30702af1a0..015b1142d47 100644 --- a/src/utils.js +++ b/src/utils.js @@ -606,6 +606,10 @@ export function isApnGetTagDefined() { } } +export const sortByHighestCpm = (a, b) => { + return b.cpm - a.cpm; +} + /** * Fisher–Yates shuffle * http://stackoverflow.com/a/6274398 diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index fb6cfe036e5..94513821ce1 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -966,7 +966,7 @@ export function getBidResponsesFromAPI() { export function getAdServerTargeting() { return { '/19968336/header-bid-tag-0': convertTargetingsFromOldToNew({ - 'foobar': '0x0,300x250,300x600', + 'foobar': '300x250,300x600,0x0', 'hb_size': '300x250', 'hb_pb': '10.00', 'hb_adid': '233bcbee889d46d', @@ -1035,7 +1035,7 @@ export function getTargetingKeys() { ], [ 'foobar', - ['0x0', '300x250', '300x600'] + ['300x250', '300x600', '0x0'] ] ]; } @@ -1062,7 +1062,7 @@ export function getTargetingKeysBidLandscape() { ], [ 'foobar', - ['0x0', '300x250', '300x600'] + ['300x250', '300x600', '0x0'] ], [ TARGETING_KEYS.BIDDER + '_triplelift', diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index f6cfeededd3..f3d0c6df442 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -319,7 +319,7 @@ describe('targeting tests', function () { let bidsReceived; beforeEach(function () { - bidsReceived = [bid1, bid2, bid3]; + bidsReceived = [bid1, bid2, bid3].map(deepClone); amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { return bidsReceived; @@ -398,7 +398,22 @@ describe('targeting tests', function () { bidsReceived.push(bid4); }); + after(function() { + config.setConfig({ + targetingControls: { + alwaysIncludeDeals: false + } + }); + enableSendAllBids = false; + }) + it('returns targeting with both hb_deal and hb_deal_{bidder_code}', function () { + config.setConfig({ + targetingControls: { + alwaysIncludeDeals: true + } + }); + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); // We should add both keys rather than one or the other @@ -543,8 +558,8 @@ describe('targeting tests', function () { }); after(function() { - bidsReceived = [bid1, bid2, bid3]; $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; + enableSendAllBids = false; }) it('targeting should not include a 0 cpm by default', function() { @@ -560,6 +575,8 @@ describe('targeting tests', function () { } }; + enableSendAllBids = true; + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_pb', 'hb_bidder', 'hb_adid', 'hb_bidder_appnexus', 'hb_adid_appnexus', 'hb_pb_appnexus'); expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.0') @@ -815,6 +832,7 @@ describe('targeting tests', function () { hb_pb: '3.0', hb_adid: '111111', hb_bidder: 'pubmatic', + foobar: '300x250' }; bid5.bidder = bid5.bidderCode = 'pubmatic'; bid5.cpm = 3.0; // winning bid! @@ -899,6 +917,7 @@ describe('targeting tests', function () { let bidExpiryStub; beforeEach(function () { + enableSendAllBids = false; amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { return []; }); @@ -921,6 +940,7 @@ describe('targeting tests', function () { let bidExpiryStub; let auctionManagerStub; beforeEach(function () { + enableSendAllBids = false; bidExpiryStub = sandbox.stub(filters, 'isBidNotExpired').returns(true); auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 5a08447ce95..69e608e0d65 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -294,9 +294,12 @@ describe('Unit: Prebid Module', function () { it('should return targeting info as a string', function () { const adUnitCode = config.adUnitCodes[0]; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); - var expected = 'foobar=0x0%2C300x250%2C300x600&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; + var expectedResults = [`foobar=300x250%2C300x600%2C0x0`, `${TARGETING_KEYS.SIZE}=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}=10.00`, `${TARGETING_KEYS.AD_ID}=233bcbee889d46d`, `${TARGETING_KEYS.BIDDER}=appnexus`, `${TARGETING_KEYS.SIZE}_triplelift=0x0`, `${TARGETING_KEYS.PRICE_BUCKET}_triplelift=10.00`, `${TARGETING_KEYS.AD_ID}_triplelift=222bb26f9e8bd`, `${TARGETING_KEYS.BIDDER}_triplelift=triplelift`, `${TARGETING_KEYS.SIZE}_appnexus=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_appnexus=10.00`, `${TARGETING_KEYS.AD_ID}_appnexus=233bcbee889d46d`, `${TARGETING_KEYS.BIDDER}_appnexus=appnexus`, `${TARGETING_KEYS.SIZE}_pagescience=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_pagescience=10.00`, `${TARGETING_KEYS.AD_ID}_pagescience=25bedd4813632d7`, `${TARGETING_KEYS.BIDDER}_pagescienc=pagescience`, `${TARGETING_KEYS.SIZE}_brightcom=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_brightcom=10.00`, `${TARGETING_KEYS.AD_ID}_brightcom=26e0795ab963896`, `${TARGETING_KEYS.BIDDER}_brightcom=brightcom`, `${TARGETING_KEYS.SIZE}_brealtime=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_brealtime=10.00`, `${TARGETING_KEYS.AD_ID}_brealtime=275bd666f5a5a5d`, `${TARGETING_KEYS.BIDDER}_brealtime=brealtime`, `${TARGETING_KEYS.SIZE}_pubmatic=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_pubmatic=10.00`, `${TARGETING_KEYS.AD_ID}_pubmatic=28f4039c636b6a7`, `${TARGETING_KEYS.BIDDER}_pubmatic=pubmatic`, `${TARGETING_KEYS.SIZE}_rubicon=300x600`, `${TARGETING_KEYS.PRICE_BUCKET}_rubicon=10.00`, `${TARGETING_KEYS.AD_ID}_rubicon=29019e2ab586a5a`, `${TARGETING_KEYS.BIDDER}_rubicon=rubicon`]; var result = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr(adUnitCode); - assert.equal(expected, result, 'returns expected string of ad targeting info'); + + expectedResults.forEach(expected => { + expect(result).to.include(expected); + }) }); it('should log message if adunitCode param is falsey', function () { @@ -339,7 +342,7 @@ describe('Unit: Prebid Module', function () { var targeting = $$PREBID_GLOBAL$$.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']); var expected = { '/19968336/header-bid-tag-0': { - foobar: '0x0,300x250,300x600', + foobar: '300x250,300x600,0x0', [TARGETING_KEYS.SIZE]: '300x250', [TARGETING_KEYS.PRICE_BUCKET]: '10.00', [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', @@ -477,8 +480,8 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { - foobar: '0x0,300x250,300x600', - custom_ad_id: '222bb26f9e8bd,233bcbee889d46d,25bedd4813632d7,26e0795ab963896,275bd666f5a5a5d,28f4039c636b6a7,29019e2ab586a5a' + foobar: '300x250,300x600,0x0', + custom_ad_id: '233bcbee889d46d,28f4039c636b6a7,29019e2ab586a5a,25bedd4813632d7,275bd666f5a5a5d,26e0795ab963896,222bb26f9e8bd' }, '/19968336/header-bid-tag1': { foobar: '728x90', @@ -2856,7 +2859,7 @@ describe('Unit: Prebid Module', function () { let result = targeting.getAllTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']); // $$PREBID_GLOBAL$$.getAdserverTargeting(); let expected = { '/19968336/header-bid-tag-0': { - 'foobar': '0x0,300x250,300x600', + 'foobar': '300x250,300x600,0x0', [TARGETING_KEYS.SIZE]: '300x250', [TARGETING_KEYS.PRICE_BUCKET]: '10.00', [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index a6df236ee46..1b4cc3764f6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -112,7 +112,7 @@ describe('Utils', function () { var obj = getAdServerTargeting(); var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); - var expected = 'foobar=0x0%2C300x250%2C300x600&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; + var expected = 'foobar=300x250%2C300x600%2C0x0&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; assert.equal(output, expected); }); From fd79aaa5bab9c2daa297c96cc49dea0dad50bfb0 Mon Sep 17 00:00:00 2001 From: SystemGlitch Date: Tue, 1 Oct 2024 20:26:44 +0200 Subject: [PATCH 0549/1097] AdagioAnalyticsAdapter: add bdrs_timeout, adsrv, adsrv_empty (#12281) --- libraries/gptUtils/gptUtils.js | 64 +++++++ modules/adagioAnalyticsAdapter.js | 72 +++++++- .../modules/adagioAnalyticsAdapter_spec.js | 159 ++++++++++++++++++ 3 files changed, 293 insertions(+), 2 deletions(-) diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js index 25c1de03538..68ce29ef168 100644 --- a/libraries/gptUtils/gptUtils.js +++ b/libraries/gptUtils/gptUtils.js @@ -57,3 +57,67 @@ export function getSegments(fpd, sections, segtax) { .filter(ob => ob) .filter(uniques) } + +/** + * Add an event listener on the given GAM event. + * If GPT Pubads isn't defined, window.googletag is set to a new object. + * @param {String} event + * @param {Function} callback + */ +export function subscribeToGamEvent(event, callback) { + const register = () => window.googletag.pubads().addEventListener(event, callback); + if (isGptPubadsDefined()) { + register(); + return; + } + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(register); +} + +/** + * @typedef {Object} Slot + * @property {function(String): (String|null)} get + * @property {function(): String} getAdUnitPath + * @property {function(): String[]} getAttributeKeys + * @property {function(): String[]} getCategoryExclusions + * @property {function(String): String} getSlotElementId + * @property {function(): String[]} getTargeting + * @property {function(): String[]} getTargetingKeys + * @see {@link https://developers.google.com/publisher-tag/reference#googletag.Slot GPT official docs} + */ + +/** + * @typedef {Object} SlotRenderEndedEvent + * @property {(String|null)} advertiserId + * @property {(String|null)} campaignId + * @property {(String[]|null)} companyIds + * @property {(Number|null)} creativeId + * @property {(Number|null)} creativeTemplateId + * @property {(Boolean)} isBackfill + * @property {(Boolean)} isEmpty + * @property {(Number[]|null)} labelIds + * @property {(Number|null)} lineItemId + * @property {(String)} serviceName + * @property {(string|Number[]|null)} size + * @property {(Slot)} slot + * @property {(Boolean)} slotContentChanged + * @property {(Number|null)} sourceAgnosticCreativeId + * @property {(Number|null)} sourceAgnosticLineItemId + * @property {(Number[]|null)} yieldGroupIds + * @see {@link https://developers.google.com/publisher-tag/reference#googletag.events.SlotRenderEndedEvent GPT official docs} + */ + +/** + * @callback SlotRenderEndedEventCallback + * @param {SlotRenderEndedEvent} event + * @returns {void} + */ + +/** + * Add an event listener on the GAM event 'slotRenderEnded'. + * @param {SlotRenderEndedEventCallback} callback + */ +export function subscribeToGamSlotRenderEndedEvent(callback) { + subscribeToGamEvent('slotRenderEnded', callback) +} diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 2f015f07c31..452e521c680 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -10,6 +10,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { subscribeToGamSlotRenderEndedEvent, SlotRenderEndedEvent } from '../libraries/gptUtils/gptUtils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -24,7 +25,8 @@ const ADAGIO_CODE = 'adagio'; export const _internal = { getAdagioNs: function() { return _ADAGIO; - } + }, + gamSlotCallback }; const cache = { @@ -52,6 +54,18 @@ const cache = { }, getAdagioAuctionId(auctionId) { return this.auctionIdReferences[auctionId]; + }, + + // Map adunitcode with prebid auction ID + auctionByAdunit: {}, + getAuctionIdByAdunit(adUnitPath, adSlotElementId) { + if (cache.auctionByAdunit[adUnitPath]) { + return { auctionId: cache.auctionByAdunit[adUnitPath], adUnitCode: adUnitPath } + } + if (cache.auctionByAdunit[adSlotElementId]) { + return { auctionId: cache.auctionByAdunit[adSlotElementId], adUnitCode: adSlotElementId } + } + return { auctionId: null, adUnitCode: null } } }; const enc = window.encodeURIComponent; @@ -269,6 +283,7 @@ function handlerAuctionInit(event) { } cache.auctions[prebidAuctionId][adUnitCode] = qp; + cache.auctionByAdunit[adUnitCode] = prebidAuctionId; sendNewBeacon(prebidAuctionId, adUnitCode); }); }; @@ -316,6 +331,10 @@ function handlerAuctionEnd(event) { const perfNavigation = performance.getEntriesByType('navigation')[0]; + const auction = cache.getAuction(auctionId, adUnitCode); + const bdrs = auction.bdrs.split(','); + const bdrsTimeout = auction.bdrs_timeout || []; + cache.updateAuction(auctionId, adUnitCode, { bdrs_bid: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidResponseMapper).join(','), bdrs_cpm: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidCpmMapper).join(','), @@ -323,6 +342,7 @@ function handlerAuctionEnd(event) { dom_i: Math.round(perfNavigation['domInteractive']) || null, dom_c: Math.round(perfNavigation['domComplete']) || null, loa_e: Math.round(perfNavigation['loadEventEnd']) || null, + bdrs_timeout: bdrs.map(b => bdrsTimeout.includes(b) ? '1' : '0').join(','), }); sendNewBeacon(auctionId, adUnitCode); @@ -378,6 +398,23 @@ function handlerAdRender(event, isSuccess) { sendNewBeacon(auctionId, adUnitCode); }; +function handlerBidTimeout(args) { + args.forEach(event => { + const auction = cache.getAuction(event.auctionId, event.adUnitCode); + if (!auction) { + logWarn(`bid timeout on auction ${event.auctionId}, with adunitCode ${event.adUnitCode}: could not retrieve auction from cache`); + return; + } + + // an array of bidder names is first created + // in AUCTION_END handler, this array is sorted + // and transformed in a comma-separated list. + const bdrsTimeout = auction.bdrs_timeout || []; + bdrsTimeout.push(event.bidder); + auction.bdrs_timeout = bdrsTimeout; + }); +}; + /** * handlerPbsAnalytics add to the cache data coming from Adagio PBS AdResponse. * The data is retrieved from an AnalyticsTag (set by a custom PBS module named `adg-pba`), @@ -409,10 +446,36 @@ function handlerPbsAnalytics(event) { * END HANDLERS */ +/** + * @param {SlotRenderEndedEvent} event + * @returns {void} + */ +function gamSlotCallback(event) { + const { auctionId, adUnitCode } = cache.getAuctionIdByAdunit(event.slot.getAdUnitPath(), event.slot.getSlotElementId()); + if (!auctionId) { + const slotName = `${event.slot.getAdUnitPath()} - ${event.slot.getSlotElementId()}`; + logWarn('Could not find configured ad unit matching GAM render of slot: ' + slotName); + return; + } + + cache.updateAuction(auctionId, adUnitCode, { + adsrv: 'gam', + adsrv_empty: event.isEmpty + }); + + // This event can be triggered after AUCTION_END + // To make sure the data is sent, we must send a new beacon version. + const auction = cache.getAuction(auctionId, adUnitCode) + if (auction?.loa_e !== undefined) { + // loa_e = loadEventEnd + // It means the AUCTION_END has already been sent. + sendNewBeacon(auctionId, adUnitCode); + } +} + let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { track: function(event) { const { eventType, args } = event; - try { switch (eventType) { case EVENTS.AUCTION_INIT: @@ -435,6 +498,9 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { case EVENTS.PBS_ANALYTICS: handlerPbsAnalytics(args); break; + case EVENTS.BID_TIMEOUT: + handlerBidTimeout(args); + break; } } catch (error) { logError('Error on Adagio Analytics Adapter', error); @@ -478,6 +544,8 @@ adagioAdapter.enableAnalytics = config => { adagioAdapter.options.site = undefined; } adagioAdapter.originEnableAnalytics(config); + + subscribeToGamSlotRenderEndedEvent(gamSlotCallback) } adapterManager.registerAnalyticsAdapter({ diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index fd899a1c864..329aa980caf 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -641,6 +641,20 @@ const MOCK = { adagio: BID_ADAGIO, another: BID_ANOTHER }, + BID_TIMEOUT: { + another: [ + { + auctionId: AUCTION_ID, + adUnitCode: '/19968336/header-bid-tag-1', + bidder: 'another', + }, + { + auctionId: AUCTION_ID, + adUnitCode: '/19968336/footer-bid-tag-1', + bidder: 'another', + }, + ] + }, AUCTION_END: { another: AUCTION_END_ANOTHER, another_nobid: AUCTION_END_ANOTHER_NOBID @@ -758,6 +772,7 @@ describe('adagio analytics adapter', () => { expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.equal('ban'); } @@ -780,6 +795,7 @@ describe('adagio analytics adapter', () => { expect(search.e_pba_test).to.equal('true'); expect(search.bdrs_bid).to.equal('1,1,0,0'); expect(search.bdrs_cpm).to.equal('1.42,2.052,,'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } { @@ -790,6 +806,7 @@ describe('adagio analytics adapter', () => { expect(search.v).to.equal('2'); expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.bdrs_timeout).to.equal('0'); } { @@ -805,6 +822,7 @@ describe('adagio analytics adapter', () => { expect(search.win_ban_sz).to.equal('728x90'); expect(search.win_net_cpm).to.equal('2.052'); expect(search.win_og_cpm).to.equal('2.592'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } }); @@ -839,6 +857,7 @@ describe('adagio analytics adapter', () => { expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another'); expect(search.bdrs_code).to.equal('adagio,another'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.equal('ban'); expect(search.t_n).to.equal('test'); expect(search.t_v).to.equal('version'); @@ -863,6 +882,7 @@ describe('adagio analytics adapter', () => { expect(search.ban_szs).to.equal('640x480'); expect(search.bdrs).to.equal('another'); expect(search.bdrs_code).to.equal('another'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.not.exist; } @@ -885,6 +905,7 @@ describe('adagio analytics adapter', () => { expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.equal('ban'); } @@ -897,6 +918,7 @@ describe('adagio analytics adapter', () => { expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.bdrs_timeout).to.not.exist; } { @@ -911,6 +933,7 @@ describe('adagio analytics adapter', () => { expect(search.e_pba_test).to.equal('true'); expect(search.bdrs_bid).to.equal('0,0,0,0'); expect(search.bdrs_cpm).to.equal(',,,'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } { @@ -922,6 +945,7 @@ describe('adagio analytics adapter', () => { expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); expect(search.rndr).to.not.exist; + expect(search.bdrs_timeout).to.equal('0'); } { @@ -939,6 +963,7 @@ describe('adagio analytics adapter', () => { expect(search.win_net_cpm).to.equal('1.42'); expect(search.win_og_cpm).to.equal('1.42'); expect(search.rndr).to.not.exist; + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } { @@ -956,6 +981,7 @@ describe('adagio analytics adapter', () => { expect(search.win_net_cpm).to.equal('1.42'); expect(search.win_og_cpm).to.equal('1.42'); expect(search.rndr).to.equal('0'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } }); @@ -1042,5 +1068,138 @@ describe('adagio analytics adapter', () => { expect(search.e_splt_cs_id).to.be.undefined; } }); + + it('builds and sends auction data with a bid timeout', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + + expect(server.requests.length).to.equal(4, 'requests count'); + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.bdrs_timeout).to.not.exist; + } + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.bdrs_timeout).to.not.exist; + } + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_timeout).to.equal('0,1,0,0'); + } + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.bdrs).to.equal('another'); + expect(search.bdrs_timeout).to.equal('1'); + } + }); + + it('builds and sends auction data with GAM slot callback', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + _internal.gamSlotCallback({ + slot: { + getAdUnitPath() { + return '/19968336/header-bid-tag-1' + }, + getSlotElementId() { + return '/19968336/header-bid-tag-1' + } + }, + isEmpty: true, + }); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + + expect(server.requests.length).to.equal(4, 'requests count'); + { + const { search } = utils.parseUrl(server.requests[0].url); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[1].url); + expect(search.v).to.equal('1'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[2].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.equal('gam'); + expect(search.adsrv_empty).to.equal('true'); + } + { + const { search } = utils.parseUrl(server.requests[3].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + }); + + it('builds and sends auction data with GAM slot callback after auction ended', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + _internal.gamSlotCallback({ + slot: { + getAdUnitPath() { + return '/19968336/header-bid-tag-1' + }, + getSlotElementId() { + return '/19968336/header-bid-tag-1' + } + }, + isEmpty: true, + }); + + expect(server.requests.length).to.equal(5, 'requests count'); + { + const { search } = utils.parseUrl(server.requests[0].url); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[1].url); + expect(search.v).to.equal('1'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[2].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[3].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[4].url); + expect(search.v).to.equal('3'); + expect(search.adsrv).to.equal('gam'); + expect(search.adsrv_empty).to.equal('true'); + } + }); }); }); From 30f0d80ed74f89b9a6c55b081a4a6a7a2b0bc550 Mon Sep 17 00:00:00 2001 From: sangarbe Date: Tue, 1 Oct 2024 20:27:22 +0200 Subject: [PATCH 0550/1097] Seedtag Bid Adapter: reads and sends bidFloor when available (#12277) * reads and sends bidFloor when available * fixes seedtag adapter jsdoc --- modules/seedtagBidAdapter.js | 30 ++++++++++++++++++--- test/spec/modules/seedtagBidAdapter_spec.js | 16 +++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 4d5fb913581..7a87f3a45bc 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -10,6 +10,8 @@ import { _map, isArray, triggerPixel } from '../src/utils.js'; * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + * @typedef {import('../src/adapters/bidderFactory.js').bidderRequest} bidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid */ const BIDDER_CODE = 'seedtag'; @@ -38,6 +40,22 @@ const deviceConnection = { UNKNOWN: 'unknown', }; +export const BIDFLOOR_CURRENCY = 'USD' + +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: BIDFLOOR_CURRENCY, + mediaType: '*', + size: '*' + }); + } + + return floorInfo.floor; +} + const getConnectionType = () => { const connection = navigator.connection || @@ -133,6 +151,11 @@ function buildBidRequest(validBidRequest) { bidRequest.videoParams = getVideoParams(validBidRequest); } + const bidFloor = getBidFloor(validBidRequest) + if (bidFloor) { + bidRequest.bidFloor = bidFloor; + } + return bidRequest; } @@ -271,7 +294,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests[]} validBidRequests an array of bids + * @param {bidderRequest} bidderRequest an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests(validBidRequests, bidderRequest) { @@ -395,7 +419,7 @@ export const spec = { /** * Register bidder specific code, which will execute if bidder timed out after an auction - * @param {data} Containing timeout specific data + * @param {TimedOutBid} data Containing timeout specific data */ onTimeout(data) { const url = getTimeoutUrl(data); @@ -404,7 +428,7 @@ export const spec = { /** * Function to call when the adapter wins the auction - * @param {bid} Bid information received from the server + * @param {Bid} bid The bid information received from the server */ onBidWon: function (bid) { if (bid && bid.nurl) { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index cf0d4114d04..14b2740497d 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -3,6 +3,7 @@ import { getTimeoutUrl, spec } from 'modules/seedtagBidAdapter.js'; import * as utils from 'src/utils.js'; import * as mockGpt from 'test/spec/integration/faker/googletag.js'; import { config } from '../../../src/config.js'; +import { BIDFLOOR_CURRENCY } from '../../../modules/seedtagBidAdapter.js'; const PUBLISHER_ID = '0000-0000-01'; const ADUNIT_ID = '000000'; @@ -253,6 +254,7 @@ describe('Seedtag Adapter', function () { }); describe('buildRequests method', function () { + const bidFloor = 0.60 const bidderRequest = { refererInfo: { page: 'referer' }, timeout: 1000, @@ -280,6 +282,11 @@ describe('Seedtag Adapter', function () { mandatoryVideoParams ), ]; + validBidRequests[0].getFloor = () => ({ + currency: BIDFLOOR_CURRENCY, + floor: bidFloor + }) + it('Url params should be correct ', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); expect(request.method).to.equal('POST'); @@ -426,6 +433,15 @@ describe('Seedtag Adapter', function () { expect(bannerBid).to.not.have.property('geom') } }) + + it('should have bidfloor parameter if available', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const bidRequests = data.bidRequests; + + expect(bidRequests[0].bidFloor).to.be.equal(bidFloor) + expect(bidRequests[1]).not.to.have.property('bidFloor') + }) }); describe('COPPA param', function () { From ffbe78711ba82c40e57df2714d2559cfe0e0ea40 Mon Sep 17 00:00:00 2001 From: AdsInteractive Date: Wed, 2 Oct 2024 16:48:56 +0300 Subject: [PATCH 0551/1097] add new adapter ads_interactive (#12251) --- modules/ads_interactiveBidAdapter.js | 19 + modules/ads_interactiveBidAdapter.md | 79 +++ .../modules/ads_interactiveBidAdapter_spec.js | 514 ++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 modules/ads_interactiveBidAdapter.js create mode 100755 modules/ads_interactiveBidAdapter.md create mode 100644 test/spec/modules/ads_interactiveBidAdapter_spec.js diff --git a/modules/ads_interactiveBidAdapter.js b/modules/ads_interactiveBidAdapter.js new file mode 100644 index 00000000000..c2234992f32 --- /dev/null +++ b/modules/ads_interactiveBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'ads_interactive'; +const AD_URL = 'https://bntb.adsintreactive.com/pbjs'; +const SYNC_URL = 'https://cstb.adsinteractive.com'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/ads_interactiveBidAdapter.md b/modules/ads_interactiveBidAdapter.md new file mode 100755 index 00000000000..43c4727db45 --- /dev/null +++ b/modules/ads_interactiveBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: AdsInteractive Bidder Adapter +Module Type: AdsInteractive Bidder Adapter +Maintainer: it@adsinteractive.com +``` + +# Description + +Connects to AdsInteractive exchange for bids. +AdsInteractive bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'ads_interactive', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'ads_interactive', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'ads_interactive', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js new file mode 100644 index 00000000000..93fd94a0f2d --- /dev/null +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -0,0 +1,514 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/ads_interactiveBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'ads_interactive'; + +describe('AdsInteractiveBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://bntb.adsintreactive.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cstb.adsinteractive.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cstb.adsinteractive.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cstb.adsinteractive.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); From 323bc372efc14bb771579bd713ad6b236d5c3ced Mon Sep 17 00:00:00 2001 From: artemAdp <133973660+artemAdp@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:52:05 +0300 Subject: [PATCH 0552/1097] AdPlayerPro Video Module : initial module release (#12150) * Add new video module for AdPlayer.Pro * Fix offEvent function with callback --- .../adPlayerPro/bidRequestScheduling.html | 143 +++++ .../adPlayerPro/eventListeners.html | 154 ++++++ libraries/video/constants/vendorCodes.js | 1 + modules/.submodules.json | 3 +- modules/adplayerproVideoProvider.js | 439 +++++++++++++++ modules/adplayerproVideoProvider.md | 54 ++ .../adplayerproVideoProvider_spec.js | 521 ++++++++++++++++++ 7 files changed, 1314 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html create mode 100644 integrationExamples/videoModule/adPlayerPro/eventListeners.html create mode 100644 modules/adplayerproVideoProvider.js create mode 100644 modules/adplayerproVideoProvider.md create mode 100644 test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js diff --git a/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html new file mode 100644 index 00000000000..7c73312c5c3 --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html @@ -0,0 +1,143 @@ + + + + + + + AdPlayer.Pro bid request scheduling + + + + + + + + +

AdPlayer.Pro Event Listeners

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/adPlayerPro/eventListeners.html b/integrationExamples/videoModule/adPlayerPro/eventListeners.html new file mode 100644 index 00000000000..3c26ef42bee --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/eventListeners.html @@ -0,0 +1,154 @@ + + + + + + + AdPlayer.Pro Event Listeners + + + + + + + + +

AdPlayer.Pro Event Listeners

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/libraries/video/constants/vendorCodes.js b/libraries/video/constants/vendorCodes.js index 370c151b997..4e3550ce431 100644 --- a/libraries/video/constants/vendorCodes.js +++ b/libraries/video/constants/vendorCodes.js @@ -1,6 +1,7 @@ // Video Vendors export const JWPLAYER_VENDOR = 1; export const VIDEO_JS_VENDOR = 2; +export const AD_PLAYER_PRO_VENDOR = 3; // Ad Server Vendors export const GAM_VENDOR = 'gam'; diff --git a/modules/.submodules.json b/modules/.submodules.json index 97017237909..3ac541ce4ea 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -110,7 +110,8 @@ ], "videoModule": [ "jwplayerVideoProvider", - "videojsVideoProvider" + "videojsVideoProvider", + "adplayerproVideoProvider" ], "paapi": [ "paapiForGpt", diff --git a/modules/adplayerproVideoProvider.js b/modules/adplayerproVideoProvider.js new file mode 100644 index 00000000000..826aee257ec --- /dev/null +++ b/modules/adplayerproVideoProvider.js @@ -0,0 +1,439 @@ +import { + API_FRAMEWORKS, + PLACEMENT, + PLAYBACK_METHODS, + PROTOCOLS, + VIDEO_MIME_TYPE, + VPAID_MIME_TYPE +} from '../libraries/video/constants/ortb.js'; +import { + AD_CLICK, + AD_COMPLETE, + AD_ERROR, + AD_IMPRESSION, + AD_LOADED, + AD_PAUSE, + AD_PLAY, + AD_REQUEST, + AD_SKIPPED, + AD_STARTED, + DESTROYED, + ERROR, + MUTE, + PLAYER_RESIZE, + SETUP_COMPLETE, + SETUP_FAILED, + VOLUME +} from '../libraries/video/constants/events.js'; +import {AD_PLAYER_PRO_VENDOR} from '../libraries/video/constants/vendorCodes.js'; +import {getEventHandler} from '../libraries/video/shared/eventHandler.js'; +import {submodule} from '../src/hook.js'; + +const setupFailMessage = 'Failed to instantiate the player'; + +/** + * @constructor + * @param {Object} config - videoProviderConfig + * @param {function} adPlayerPro_ + * @param {CallbackStorage} callbackStorage_ + * @param {Object} utils + * @returns {Object} - VideoProvider + */ +export function AdPlayerProProvider(config, adPlayerPro_, callbackStorage_, utils) { + const adPlayerPro = adPlayerPro_; + let player = null; + let playerVersion = null; + const playerConfig = config.playerConfig; + const divId = config.divId; + let callbackStorage = callbackStorage_; + let supportedMediaTypes = null; + let setupCompleteCallbacks = []; + let setupFailedCallbacks = []; + const MEDIA_TYPES = [ + VIDEO_MIME_TYPE.MP4, + VIDEO_MIME_TYPE.OGG, + VIDEO_MIME_TYPE.WEBM, + VIDEO_MIME_TYPE.AAC, + VIDEO_MIME_TYPE.HLS + ]; + + function init() { + if (!adPlayerPro) { + triggerSetupFailure(-1, setupFailMessage + ': player not present'); + return; + } + + // if (playerVersion < minimumSupportedPlayerVersion) { + // triggerSetupFailure(-2, setupFailMessage + ': player version not supported'); + // return; + // } + + if (!document.getElementById(divId)) { + triggerSetupFailure(-3, setupFailMessage + ': No div found with id ' + divId); + return; + } + + if (!playerConfig || !playerConfig.placementId) { + triggerSetupFailure(-4, setupFailMessage + ': placementId is required in playerConfig'); + return; + } + + triggerSetupComplete(); + } + + function getId() { + return divId; + } + + function getOrtbVideo() { + supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES); + + const video = { + mimes: supportedMediaTypes, + protocols: [ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ], + // h: player.getHeight(), + // w: player.getWidth(), + placement: utils.getPlacement(playerConfig.advertising), + maxextended: -1, // extension is allowed, and there is no time limit imposed. + boxingallowed: 1, + playbackmethod: [utils.getPlaybackMethod(config)], + playbackend: 1, + api: [ + API_FRAMEWORKS.VPAID_2_0, + API_FRAMEWORKS.OMID_1_0 + ], + }; + + return video; + } + + function getOrtbContent() { + } + + function setAdTagUrl(adTagUrl, options) { + setupPlayer(playerConfig, adTagUrl || options.adXml) + } + + function onEvent(externalEventName, callback, basePayload) { + if (externalEventName === SETUP_COMPLETE) { + setupCompleteCallbacks.push(callback); + return; + } + + if (externalEventName === SETUP_FAILED) { + setupFailedCallbacks.push(callback); + return; + } + + let getEventPayload; + + switch (externalEventName) { + case AD_REQUEST: + case AD_PLAY: + case AD_PAUSE: + case AD_LOADED: + case AD_STARTED: + case AD_IMPRESSION: + case AD_CLICK: + case AD_SKIPPED: + case AD_ERROR: + case AD_COMPLETE: + case MUTE: + case VOLUME: + case ERROR: + case PLAYER_RESIZE: + getEventPayload = e => ({ + height: player.getAdHeight(), + width: player.getAdWidth(), + }); + break; + default: + return; + } + + // eslint-disable-next-line no-unreachable + const playerEventName = utils.getPlayerEvent(externalEventName); + const eventHandler = getEventHandler(externalEventName, callback, basePayload, getEventPayload) + player && player.on(playerEventName, eventHandler); + callbackStorage.storeCallback(playerEventName, eventHandler, callback); + } + + function offEvent(event, callback) { + const playerEventName = utils.getPlayerEvent(event); + const eventHandler = callbackStorage.getCallback(playerEventName, callback); + if (eventHandler) { + player && player.off(playerEventName, eventHandler); + } else { + player && player.off(playerEventName); + } + callbackStorage.clearCallback(playerEventName, callback); + } + + function destroy() { + if (!player) { + return; + } + player.remove(); + player = null; + } + + return { + init, + getId, + getOrtbVideo, + getOrtbContent, + setAdTagUrl, + onEvent, + offEvent, + destroy + }; + + function setupPlayer(config, urlOrXml) { + if (!config || player) { + return; + } + const playerConfig = utils.getConfig(config, urlOrXml); + + if (!playerConfig) { + return; + } + + player = adPlayerPro(divId); + callbackStorage.addAllCallbacks(player.on); + player.on('AdStopped', () => player = null); + player.setup(playerConfig); + } + + function triggerSetupComplete() { + if (!setupCompleteCallbacks.length) { + return; + } + + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; + } + + function getSetupCompletePayload() { + return { + divId, + playerVersion, + type: SETUP_COMPLETE + }; + } + + function triggerSetupFailure(errorCode, msg, sourceError) { + if (!setupFailedCallbacks.length) { + return; + } + + const payload = { + divId, + playerVersion, + type: SETUP_FAILED, + errorCode: errorCode, + errorMessage: msg, + sourceError: sourceError + }; + + setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); + setupFailedCallbacks = []; + } +} + +/** + * @param {Object} config - videoProviderConfig + * @param {sharedUtils} sharedUtils + * @returns {Object} - VideoProvider + */ +const adPlayerProSubmoduleFactory = function (config, sharedUtils) { + const callbackStorage = callbackStorageFactory(); + return AdPlayerProProvider(config, window.playerPro, callbackStorage, utils); +} + +adPlayerProSubmoduleFactory.vendorCode = AD_PLAYER_PRO_VENDOR; +submodule('video', adPlayerProSubmoduleFactory); +export default adPlayerProSubmoduleFactory; + +// HELPERS + +export const utils = { + getConfig: function (config, urlOrXml) { + if (!config || !urlOrXml) { + return; + } + + const params = config.params || {}; + params.placementId = config.placementId; + params.advertising = params.advertising || {}; + params.advertising.tag = params.advertising.tag || {}; + + params._pType = 'pbjs'; + params.advertising.tag.url = urlOrXml; + return params; + }, + + getPlayerEvent: function (eventName) { + switch (eventName) { + case DESTROYED: + return 'AdStopped'; + + case AD_REQUEST: + return 'AdRequest'; + case AD_LOADED: + return 'AdLoaded'; + case AD_STARTED: + return 'AdStarted'; + case AD_IMPRESSION: + return 'AdImpression'; + case AD_PLAY: + return 'AdPlaying'; + case AD_PAUSE: + return 'AdPaused'; + case AD_CLICK: + return 'AdClickThru'; + case AD_SKIPPED: + return 'AdSkipped'; + case AD_ERROR: + return 'AdError'; + case AD_COMPLETE: + return 'AdCompleted'; + case VOLUME: + return 'AdVolumeChange'; + case PLAYER_RESIZE: + return 'AdSizeChange'; + // case FULLSCREEN: + // return FULLSCREEN; + default: + return eventName; + } + }, + + getSupportedMediaTypes: function (mediaTypes = []) { + const el = document.createElement('video'); + return mediaTypes + .filter(mediaType => el.canPlayType(mediaType)) + .concat(VPAID_MIME_TYPE); // Always allow VPAIDs. + }, + + /** + * Determine the ad placement + * @param {Object} adConfig + * @return {PLACEMENT|undefined} + */ + getPlacement: function (adConfig) { + adConfig = adConfig || {}; + + switch (adConfig.type) { + case 'inPage': + return PLACEMENT.ARTICLE; + case 'rewarded': + case 'inView': + return PLACEMENT.INTERSTITIAL_SLIDER_FLOATING; + default: + return PLACEMENT.BANNER; + } + }, + + getPlaybackMethod: function ({autoplay, mute}) { + if (autoplay) { + return mute ? PLAYBACK_METHODS.AUTOPLAY_MUTED : PLAYBACK_METHODS.AUTOPLAY; + } + return PLAYBACK_METHODS.CLICK_TO_PLAY; + } +} + +/** + * Tracks which functions are attached to events + * @typedef CallbackStorage + * @function storeCallback + * @function getCallback + * @function clearCallback + * @function addAllCallbacks + * @function clearStorage + */ + +/** + * @returns {CallbackStorage} + */ +export function callbackStorageFactory() { + let storage = {}; + let storageHandlers = {}; + + function storeCallback(eventType, eventHandler, callback) { + let eventHandlers = storage[eventType]; + if (!eventHandlers) { + eventHandlers = storage[eventType] = {}; + } + + eventHandlers[callback] = eventHandler; + addHandler(eventType, eventHandler); + } + + function getCallback(eventType, callback) { + let eventHandlers = storage[eventType]; + if (eventHandlers) { + return eventHandlers[callback]; + } + } + + function clearCallback(eventType, callback) { + if (!callback) { + delete storage[eventType]; + delete storageHandlers[eventType]; + return; + } + let eventHandlers = storage[eventType]; + if (eventHandlers) { + const eventHandler = eventHandlers[callback]; + if (eventHandler) { + delete eventHandlers[callback]; + clearHandler(eventType, eventHandler); + } + } + } + + function clearStorage() { + storage = {}; + storageHandlers = {}; + } + + function addHandler(eventType, eventHandler) { + let eventHandlers = storageHandlers[eventType]; + if (!eventHandlers) { + eventHandlers = storageHandlers[eventType] = []; + } + eventHandlers.push(eventHandler); + } + + function clearHandler(eventType, eventHandler) { + let eventHandlers = storageHandlers[eventType]; + eventHandlers = eventHandlers.filter(handler => handler !== eventHandler); + if (eventHandlers.length) { + storageHandlers[eventType] = eventHandlers; + } else { + delete storageHandlers[eventType]; + } + } + + function addAllCallbacks(functionOnPlayer) { + for (let eventType in storageHandlers) { + storageHandlers[eventType].forEach(handler => functionOnPlayer(eventType, handler)); + } + } + + return { + storeCallback, + getCallback, + clearCallback, + addAllCallbacks, + clearStorage, + } +} diff --git a/modules/adplayerproVideoProvider.md b/modules/adplayerproVideoProvider.md new file mode 100644 index 00000000000..d0b0601afeb --- /dev/null +++ b/modules/adplayerproVideoProvider.md @@ -0,0 +1,54 @@ +# Overview + +Module Name: AdPlayer.Pro Video Provider +Module Type: Video Submodule +Video Player: AdPlayer.Pro +Player website: https://adplayer.pro +Maintainer: support@adplayer.pro + +# Description + +Video provider to connect the Prebid Video Module to AdPlayer.Pro. + +# Requirements + +Your page must embed a build of AdPlayer.Pro. +i.e. +```html + + + +``` + +# Configuration + +The AdPlayer.Pro Video Provider requires the following configuration: + +```javascript +pbjs.setConfig({ + video: { + providers: [{ + divId: 'player', // required, this is the id of the div element where the player will be placed + vendorCode: 3, // AdPlayer.Pro vendorCode + playerConfig: { + placementId: 'c9gebfehcqjE', // required, this placementId is only for demo purposes + params: { + 'type': 'inView', + 'muted': true, + 'autoStart': true, + 'advertising': { + 'controls': true, + 'closeButton': true, + }, + 'width': '600', + 'height': '300' + } + }, + }] + } +}); +``` + +[Additional embed instructions](https://docs.adplayer.pro) + +[Obtaining a license](https://adplayer.pro/contacts) diff --git a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js new file mode 100644 index 00000000000..1a792411497 --- /dev/null +++ b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js @@ -0,0 +1,521 @@ +// Using require style imports for fine grained control of import time +import { + AD_CLICK, + AD_COMPLETE, + AD_ERROR, + AD_IMPRESSION, + AD_LOADED, + AD_PAUSE, + AD_PLAY, + AD_REQUEST, + AD_SKIPPED, + AD_STARTED, + DESTROYED, + PLAYER_RESIZE, + SETUP_COMPLETE, + SETUP_FAILED, + VOLUME +} from 'libraries/video/constants/events.js'; +import adPlayerProSubmoduleFactory, {callbackStorageFactory} from '../../../../../modules/adplayerproVideoProvider.js'; +import {PLACEMENT} from '../../../../../libraries/video/constants/ortb'; +import sinon from 'sinon'; + +const {AdPlayerProProvider, utils} = require('modules/adplayerproVideoProvider.js'); + +const { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE +} = require('libraries/video/constants/ortb.js'); + +function getPlayerMock() { + return { + setup: function () { + return this; + }, + load: function () { + }, + resize: function () { + }, + remove: function () { + }, + on: function () { + return this; + }, + off: function () { + return this; + }, + getAdWidth: function () { + return 600 + }, + getAdHeight: function () { + return 400; + } + }; +} + +function makePlayerFactoryMock(playerMock_) { + return () => playerMock_; +} + +function getUtilsMock() { + return { + getConfig: function () { + }, + getPlayerEvent: event => event, + getSupportedMediaTypes: function () { + }, + getPlacement: function () { + }, + getPlaybackMethod: function () { + } + }; +} + +function addDiv() { + const div = document.createElement('div'); + div.setAttribute('id', 'test'); + document.body.appendChild(div); +} + +function removeDiv() { + const div = document.getElementById('test'); + if (div) { + div.remove(); + } +} + +describe('AdPlayerProProvider', function () { + let config; + let callbackStorage; + let utilsMock; + let player; + + beforeEach(() => { + addDiv(); + config = {divId: 'test', playerConfig: {placementId: 'testId'}}; + callbackStorage = callbackStorageFactory(); + utilsMock = getUtilsMock(); + player = getPlayerMock(); + }); + + afterEach(() => { + removeDiv(); + }); + + describe('init', function () { + it('should trigger failure when Adplayer.Pro is missing', function () { + const provider = AdPlayerProProvider(config, null, callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-1); + }); + + it('should trigger failure when the div is not found', function () { + config.divId = 'fake-div' + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-3); + }); + + it('should trigger failure when the placementId is not found', function () { + config.playerConfig.placementId = ''; + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-4); + }); + + it('should instantiate the player after setAdTagUrl', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + }); + + it('should instantiate the player after setAdTagUrl for adPlayerProSubmoduleFactory', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + window.playerPro = makePlayerFactoryMock(player); + const provider = adPlayerProSubmoduleFactory(config, {}); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + }); + + it('should trigger setup complete when player is already instantiated', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + const setupComplete = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupSpy.called).to.be.false; + }); + + it('should support multiple setup complete event handlers', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + const setupComplete = sinon.spy(); + const setupComplete2 = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.onEvent(SETUP_COMPLETE, setupComplete2, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupComplete2.calledOnce).to.be.true; + expect(setupSpy.called).to.be.false; + }); + + it('should not reinstantiate player', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const onSpy = player.on = sinon.spy(player.on); + + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + + // test that the player is not reinitialized + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + + // get and call AdStopped event + const args = onSpy.args[0]; + expect(args[0]).to.be.equal('AdStopped'); + args[1](); + + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledTwice).to.be.true; + }); + }); + + describe('getId', function () { + it('should return configured div id', function () { + const provider = AdPlayerProProvider(config, undefined, undefined, utils); + expect(provider.getId()).to.be.equal('test'); + }); + }); + + describe('getOrtbVideo', function () { + it('should populate oRTB Video params', function () { + const test_media_type = VIDEO_MIME_TYPE.MP4; + const test_placement = PLACEMENT.ARTICLE; + const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; + + utilsMock.getSupportedMediaTypes = () => [test_media_type]; + utilsMock.getPlacement = () => test_placement; + utilsMock.getPlaybackMethod = () => test_playback_method; + + const provider = AdPlayerProProvider(config, null, null, utilsMock); + provider.init(); + let video = provider.getOrtbVideo(); + + expect(video.mimes).to.include(VIDEO_MIME_TYPE.MP4); + expect(video.protocols).to.include.members([ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ]); + expect(video.placement).to.equal(test_placement); + expect(video.maxextended).to.equal(-1); + expect(video.boxingallowed).to.equal(1); + expect(video.playbackmethod).to.include(test_playback_method); + expect(video.playbackend).to.equal(1); + expect(video.api).to.have.length(2); + expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); // + }); + }); + + describe('getOrtbContent', function () { + it('should populate oRTB Content params', function () { + const provider = AdPlayerProProvider(config, null, null, utils); + provider.init(); + expect(provider.getOrtbContent()).to.be.undefined; + }); + }); + + describe('setAdTagUrl', function () { + it('should call setup', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('', {adXml: 'https://test.com'}); + expect(setupSpy.calledOnce).to.be.true; + }); + + it('should not call setup', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('', {}); + expect(setupSpy.calledOnce).to.be.false; + }); + }); + + describe('events', function () { + it('should register event listener on player', function () { + const onSpy = player.on = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_REQUEST, callback, {}); + provider.onEvent('test', callback, {}); + expect(onSpy.calledTwice).to.be.true; + expect(onSpy.args[0][0]).to.be.equal('AdStopped'); + expect(onSpy.args[1][0]).to.be.equal('AdRequest'); + }); + + it('should remove event listener on player', function () { + const offSpy = player.off = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION, callback); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + const eventCallback = offSpy.args[0][1]; + expect(eventName).to.be.equal('AdImpression'); + expect(eventCallback).to.be.exist; + }); + + it('should remove event listener on player by eventName', function () { + const offSpy = player.off = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + expect(eventName).to.be.equal('AdImpression'); + }); + + it('should call event player resize', function () { + const onSpy = player.on = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callbackSpy = sinon.spy(); + provider.onEvent(PLAYER_RESIZE, callbackSpy, {}); + expect(onSpy.calledTwice).to.be.true; + expect(onSpy.args[1][0]).to.be.equal('AdSizeChange'); + expect(callbackSpy.notCalled).to.be.true; + onSpy.args[1][1](); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.args[0][1].width).to.be.equal(600); + expect(callbackSpy.args[0][1].height).to.be.equal(400); + }); + }); + + describe('destroy', function () { + it('should remove and null the player', function () { + const removeSpy = player.remove = sinon.spy(); + player.remove = removeSpy; + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + provider.destroy(); + provider.destroy(); + expect(removeSpy.calledOnce).to.be.true; + }); + }); +}); + +describe('AdPlayerProProvider utils', function () { + it('getConfig', function () { + expect(utils.getConfig()).to.be.undefined; + expect(utils.getConfig({})).to.be.undefined; + const config = utils.getConfig({}, 'https://test.com'); + expect(config.advertising.tag.url).to.be.equal('https://test.com'); + expect(config._pType).to.be.equal('pbjs'); + }); + + it('getPlayerEvent', function () { + function test(event, expected) { + expect(utils.getPlayerEvent(event)).to.be.equal(expected); + } + + test(DESTROYED, 'AdStopped'); + test(AD_REQUEST, 'AdRequest'); + test(AD_LOADED, 'AdLoaded'); + test(AD_STARTED, 'AdStarted'); + test(AD_IMPRESSION, 'AdImpression'); + test(AD_PLAY, 'AdPlaying'); + test(AD_PAUSE, 'AdPaused'); + test(AD_CLICK, 'AdClickThru'); + test(AD_SKIPPED, 'AdSkipped'); + test(AD_ERROR, 'AdError'); + test(AD_COMPLETE, 'AdCompleted'); + test(VOLUME, 'AdVolumeChange'); + test(PLAYER_RESIZE, 'AdSizeChange'); + test('test', 'test'); + }); + + it('getSupportedMediaTypes', function () { + let supportedMediaTypes = utils.getSupportedMediaTypes([]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + + supportedMediaTypes = utils.getSupportedMediaTypes([VIDEO_MIME_TYPE.MP4]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + }); + + it('getPlacement', function () { + function test(config, expected) { + expect(utils.getPlacement(config)).to.be.equal(expected); + } + + test(false, PLACEMENT.BANNER); + test({}, PLACEMENT.BANNER); + test({type: 'test'}, PLACEMENT.BANNER); + test({type: 'inPage'}, PLACEMENT.ARTICLE); + test({type: 'rewarded'}, PLACEMENT.INTERSTITIAL_SLIDER_FLOATING); + test({type: 'inView'}, PLACEMENT.INTERSTITIAL_SLIDER_FLOATING); + }); + + it('getPlaybackMethod', function () { + function test(autoplay, mute, expected) { + expect(utils.getPlaybackMethod({autoplay, mute})).to.be.equal(expected); + } + + test(false, false, PLAYBACK_METHODS.CLICK_TO_PLAY); + test(false, true, PLAYBACK_METHODS.CLICK_TO_PLAY); + test(true, false, PLAYBACK_METHODS.AUTOPLAY); + test(true, true, PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); +}); + +describe('AdPlayerProProvider callbackStorageFactory', function () { + let player; + let callbackStorage; + + beforeEach(() => { + player = getPlayerMock(); + callbackStorage = callbackStorageFactory(); + }); + + it('storeCallback and getCallback', function () { + const eventType = 'test'; + const callback = () => 11; + const eventHandler = () => 12; + callbackStorage.storeCallback(eventType, eventHandler, callback); + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + + const callback2 = () => 21; + const eventHandler2 = () => 22; + callbackStorage.storeCallback(eventType, eventHandler2, callback2); + expect(callbackStorage.getCallback(eventType, callback2)).to.be.equal(eventHandler2); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + }); + + it('clearCallback', function () { + const eventType = 'test'; + const callback = () => 11; + const callback2 = () => 21; + callbackStorage.storeCallback(eventType, () => 12, callback); + callbackStorage.storeCallback(eventType, () => 22, callback2); + + expect(callbackStorage.getCallback(eventType, callback)()).to.be.equal(12); + expect(callbackStorage.getCallback(eventType, callback2)()).to.be.equal(22); + + callbackStorage.clearCallback(eventType); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + expect(callbackStorage.getCallback(eventType, callback2)).to.be.undefined; + + callbackStorage.storeCallback(eventType, () => 12, callback); + callbackStorage.storeCallback(eventType, () => 22, callback2); + + callbackStorage.clearCallback(eventType, callback); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + expect(callbackStorage.getCallback(eventType, callback2)()).to.be.equal(22); + }); + + it('addAllCallbacks', function () { + const eventType = 'test'; + const eventType2 = 'test2'; + const callback = () => 11; + const callback2 = () => 21; + const eventHandler = () => 12; + const eventHandler2 = () => 22; + callbackStorage.storeCallback(eventType, eventHandler, callback); + callbackStorage.storeCallback(eventType, eventHandler2, callback2); + callbackStorage.storeCallback(eventType2, eventHandler, callback); + callbackStorage.storeCallback(eventType2, eventHandler2, callback2); + + let spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(4); + expect(spy.calledWith(eventType, eventHandler)).to.be.true; + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler2)).to.be.true; + + callbackStorage.clearCallback(eventType, callback); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(3); + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler2)).to.be.true; + + callbackStorage.clearCallback(eventType2); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(1); + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + + callbackStorage.storeCallback(eventType, eventHandler, callback); + callbackStorage.storeCallback(eventType2, eventHandler, callback); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(3); + expect(spy.calledWith(eventType, eventHandler)).to.be.true; + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + + callbackStorage.clearStorage(); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(0); + }); + + it('clearStorage', function () { + const eventType = 'test'; + const callback = () => 11; + const eventHandler = () => 12; + callbackStorage.storeCallback(eventType, eventHandler, callback); + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + + callbackStorage.clearStorage(); + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + }); +}); From 80f58266dae8f8335e86f48b24c8de7b2cfd4bb5 Mon Sep 17 00:00:00 2001 From: digital-matter Date: Thu, 3 Oct 2024 17:12:17 +0300 Subject: [PATCH 0553/1097] Digital Matter Bid Adapter: define GVLID (#12285) * Digital Matter Bid Adapter: Refactor Adapter * Digital Matter Bid Adapter: add aliases * Digital Matter Bid Adapter: add gvlid --- modules/digitalMatterBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index c6663434d1e..77df1af3886 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -5,10 +5,12 @@ import {BANNER} from '../src/mediaTypes.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; const BIDDER_CODE = 'digitalMatter'; +const GVLID = 1345; const ENDPOINT_URL = 'https://adx.digitalmatter.services/' export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER], aliases: ['dichange', 'digitalmatter'], bidParameters: ['accountId', 'siteId'], From edf14ba09f0374b7f77fb2ceec0147782ccd8e95 Mon Sep 17 00:00:00 2001 From: Leandro Marty Date: Mon, 7 Oct 2024 13:13:40 +0200 Subject: [PATCH 0554/1097] Smoot Bid Adapter: initial release (#12268) * New Adapter: Smoot * code dedupe warning fix * fix url --- modules/smootBidAdapter.js | 19 + modules/smootBidAdapter.md | 80 +++ test/spec/modules/smootBidAdapter_spec.js | 595 ++++++++++++++++++++++ 3 files changed, 694 insertions(+) create mode 100644 modules/smootBidAdapter.js create mode 100644 modules/smootBidAdapter.md create mode 100644 test/spec/modules/smootBidAdapter_spec.js diff --git a/modules/smootBidAdapter.js b/modules/smootBidAdapter.js new file mode 100644 index 00000000000..cd55dc5f253 --- /dev/null +++ b/modules/smootBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'smoot'; +const AD_URL = 'https://endpoint1.smoot.ai/pbjs'; +const SYNC_URL = 'https://usync.smxconv.com'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/smootBidAdapter.md b/modules/smootBidAdapter.md new file mode 100644 index 00000000000..322584f19ea --- /dev/null +++ b/modules/smootBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: Smoot Bidder Adapter +Module Type: Smoot Bidder Adapter +Maintainer: it.ops@smoot.ai +``` + +# Description + +Connects to Smoot exchange for bids. +Smoot bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters + +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'smoot', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'smoot', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'smoot', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/smootBidAdapter_spec.js b/test/spec/modules/smootBidAdapter_spec.js new file mode 100644 index 00000000000..cf72b41b348 --- /dev/null +++ b/test/spec/modules/smootBidAdapter_spec.js @@ -0,0 +1,595 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/smootBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'smoot'; + +describe('SmootBidAdapter', function () { + const userIdAsEids = [ + { + source: 'test.org', + uids: [ + { + id: '01**********', + atype: 1, + ext: { + third: '01***********', + }, + }, + ], + }, + ]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + }, + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids, + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60, + }, + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids, + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true, + }, + body: { + required: true, + }, + icon: { + required: true, + size: [64, 64], + }, + }, + }, + }, + params: { + placementId: 'testNative', + }, + userIdAsEids, + }, + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + }, + }, + params: {}, + }; + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: + 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {}, + }, + refererInfo: { + referer: 'https://test.com', + }, + timeout: 500, + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf([ + 'testBanner', + 'testVideo', + 'testNative', + ]); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + }, + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids, + }, + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf([ + 'testBanner', + 'testVideo', + 'testNative', + ]); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal( + bidderRequest.gdprConsent.consentString + ); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8], + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }); + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [ + { + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234, + }, + }, + ], + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys( + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + 'dealId', + 'mediaType', + 'meta' + ); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta) + .to.be.an('object') + .that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [ + { + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234, + }, + }, + ], + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys( + 'requestId', + 'cpm', + 'vastUrl', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + 'dealId', + 'mediaType', + 'meta' + ); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta) + .to.be.an('object') + .that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [ + { + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234, + }, + }, + ], + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys( + 'requestId', + 'cpm', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + 'mediaType', + 'native', + 'meta' + ); + expect(dataItem.native).to.have.keys( + 'clickUrl', + 'impressionTrackers', + 'title', + 'image' + ); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not + .empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta) + .to.be.an('object') + .that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [ + { + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + }, + ], + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [ + { + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + }, + ], + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [ + { + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }, + ], + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [ + { + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + }, + ], + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + it('Should return array of objects with proper sync config , include GDPR', function () { + const syncData = spec.getUserSyncs( + {}, + {}, + { + consentString: 'ALL', + gdprApplies: true, + }, + {} + ); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object'); + expect(syncData[0].type).to.be.a('string'); + expect(syncData[0].type).to.equal('image'); + expect(syncData[0].url).to.be.a('string'); + expect(syncData[0].url).to.equal( + 'https://usync.smxconv.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0' + ); + }); + it('Should return array of objects with proper sync config , include CCPA', function () { + const syncData = spec.getUserSyncs( + {}, + {}, + {}, + { + consentString: '1---', + } + ); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object'); + expect(syncData[0].type).to.be.a('string'); + expect(syncData[0].type).to.equal('image'); + expect(syncData[0].url).to.be.a('string'); + expect(syncData[0].url).to.equal( + 'https://usync.smxconv.com/image?pbjs=1&ccpa_consent=1---&coppa=0' + ); + }); + it('Should return array of objects with proper sync config , include GPP', function () { + const syncData = spec.getUserSyncs( + {}, + {}, + {}, + {}, + { + gppString: 'abc123', + applicableSections: [8], + } + ); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object'); + expect(syncData[0].type).to.be.a('string'); + expect(syncData[0].type).to.equal('image'); + expect(syncData[0].url).to.be.a('string'); + expect(syncData[0].url).to.equal( + 'https://usync.smxconv.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0' + ); + }); + }); +}); From 6a04e491dda66bc2ee8f3b007d2b6e6bfa554c88 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Mon, 7 Oct 2024 15:27:46 +0200 Subject: [PATCH 0555/1097] AdsInteractiveBidAdapter test fix (#12294) Co-authored-by: Marcin Komorski --- test/spec/modules/ads_interactiveBidAdapter_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js index 93fd94a0f2d..f33e24fd25d 100644 --- a/test/spec/modules/ads_interactiveBidAdapter_spec.js +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -137,6 +137,7 @@ describe('AdsInteractiveBidAdapter', function () { expect(data).to.have.all.keys( 'deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', From e101fb9837bc544ac94f62b0240c839d598d8b5b Mon Sep 17 00:00:00 2001 From: nalexand <35492736+nalexand@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:36:37 +0200 Subject: [PATCH 0556/1097] SmartyTech Bid Adapter : change contact email (#12291) * Add mediaimpact bid adapter * Add mediaimpact bid adapter tests * Add custom sizes * Refactor response meta * mediaimpact adapter fix tests * SmartytechBidAdapter: Add meta * MediaimpactBidAdapter: Fix tests * MediaimpactBidAdapter: Fix tests * Add meta to tests * rerun tests * rerun tests * SmartyTech Bid Adapter changed email --------- Co-authored-by: koshe --- modules/smartytechBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md index 9df57ddbde7..53b246e4cab 100644 --- a/modules/smartytechBidAdapter.md +++ b/modules/smartytechBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: SmartyTech Bid Adapter Module Type: Bidder Adapter -Maintainer: info@adpartner.pro +Maintainer: info@fusify.io ``` # Description From 22aa7ce4566aa53ef4976fbe462aadf1abe425dc Mon Sep 17 00:00:00 2001 From: Elad Yosifon Date: Mon, 7 Oct 2024 16:50:42 +0300 Subject: [PATCH 0557/1097] fix vidazooUtils auction timeout logic (#12298) --- libraries/vidazooUtils/bidderUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index fbb4300cb24..5c3409f4780 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -466,7 +466,7 @@ export function createBuildRequestsFn(createRequestDomain, createUniqueRequestDa return function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); + const bidderTimeout = bidderRequest.timeout || config.getConfig('bidderTimeout'); const singleRequestMode = allowSingleRequest && config.getConfig(`${bidderCode}.singleRequest`); From df04741265b0d8a1cfc9651452f05adf2d718083 Mon Sep 17 00:00:00 2001 From: "shubham.si" <60253122+shubham-si@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:56:00 +0530 Subject: [PATCH 0558/1097] Fix: Incorrect generation of analytics impression url when bid response is a VAST URL (videoTrackers.js) (#12252) * Fixed issue of adding analytics impression pixel via Prebid's vastTrackers.js module.Getting object Required Array. * Add test --------- Co-authored-by: shubham.si Co-authored-by: Demetrio Girardi --- libraries/vastTrackers/vastTrackers.js | 11 ++++-- test/spec/libraries/vastTrackers_spec.js | 43 ++++++++++++++++++------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/libraries/vastTrackers/vastTrackers.js b/libraries/vastTrackers/vastTrackers.js index b4ae98aba57..f414a65a18c 100644 --- a/libraries/vastTrackers/vastTrackers.js +++ b/libraries/vastTrackers/vastTrackers.js @@ -7,19 +7,24 @@ import {activityParams} from '../../src/activities/activityParams.js'; const vastTrackers = []; -addBidResponse.before(function (next, adUnitcode, bidResponse, reject) { +export function reset() { + vastTrackers.length = 0; +} + +export function addTrackersToResponse(next, adUnitcode, bidResponse, reject) { if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { const vastTrackers = getVastTrackers(bidResponse); if (vastTrackers) { bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); const impTrackers = vastTrackers.get('impressions'); if (impTrackers) { - bidResponse.vastImpUrl = [].concat(impTrackers).concat(bidResponse.vastImpUrl).filter(t => t); + bidResponse.vastImpUrl = [].concat([...impTrackers]).concat(bidResponse.vastImpUrl).filter(t => t); } } } next(adUnitcode, bidResponse, reject); -}); +} +addBidResponse.before(addTrackersToResponse); export function registerVastTrackers(moduleType, moduleName, trackerFn) { if (typeof trackerFn === 'function') { diff --git a/test/spec/libraries/vastTrackers_spec.js b/test/spec/libraries/vastTrackers_spec.js index 3849ea75b02..c336eec0321 100644 --- a/test/spec/libraries/vastTrackers_spec.js +++ b/test/spec/libraries/vastTrackers_spec.js @@ -1,17 +1,27 @@ -import {addImpUrlToTrackers, getVastTrackers, insertVastTrackers, registerVastTrackers} from 'libraries/vastTrackers/vastTrackers.js'; +import { + addImpUrlToTrackers, + addTrackersToResponse, + getVastTrackers, + insertVastTrackers, + registerVastTrackers, + reset +} from 'libraries/vastTrackers/vastTrackers.js'; import {MODULE_TYPE_ANALYTICS} from '../../../src/activities/modules.js'; describe('vast trackers', () => { + beforeEach(() => { + registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { + return [ + {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} + ]; + }); + }) + afterEach(() => { + reset(); + }); + it('insert into tracker list', function() { - let trackers = getVastTrackers({'cpm': 1.0}); - if (!trackers || !trackers.get('impressions')) { - registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { - return [ - {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} - ]; - }); - } - trackers = getVastTrackers({'cpm': 1.0}); + const trackers = getVastTrackers({'cpm': 1.0}); expect(trackers).to.be.a('map'); expect(trackers.get('impressions')).to.exists; expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true; @@ -30,4 +40,17 @@ describe('vast trackers', () => { expect(trackers.get('impressions')).to.exists; expect(trackers.get('impressions').has('imptracker.com')).to.be.true; }); + + if (FEATURES.VIDEO) { + it('should add trackers to bid response', () => { + const bidResponse = { + mediaType: 'video', + cpm: 1 + } + addTrackersToResponse(sinon.stub(), 'au', bidResponse); + expect(bidResponse.vastImpUrl).to.eql([ + 'https://vasttracking.mydomain.com/vast?cpm=1' + ]) + }); + } }) From 7ac768f6558f4b720ce32504aadd19ff33e587fd Mon Sep 17 00:00:00 2001 From: mkomorski Date: Tue, 8 Oct 2024 05:21:55 +0200 Subject: [PATCH 0559/1097] New Module: Bid response filter (#12147) * initial commit * update * review changes * + auction index * unit tests to ortb converter * review changes * battr setting * improvements * fix log message --------- Co-authored-by: Marcin Komorski Co-authored-by: Marcin Komorski Co-authored-by: Demetrio Girardi --- libraries/ortbConverter/processors/default.js | 7 + modules/bidResponseFilter/index.js | 40 ++++++ src/auctionIndex.js | 3 + src/prebid.js | 19 +++ test/spec/modules/bidResponseFilter_spec.js | 135 ++++++++++++++++++ .../spec/modules/dsp_genieeBidAdapter_spec.js | 4 +- test/spec/ortbConverter/banner_spec.js | 10 ++ test/spec/ortbConverter/native_spec.js | 10 ++ test/spec/ortbConverter/video_spec.js | 10 ++ test/spec/unit/core/auctionIndex_spec.js | 30 +++- test/spec/unit/pbjs_api_spec.js | 68 +++++++++ 11 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 modules/bidResponseFilter/index.js create mode 100644 test/spec/modules/bidResponseFilter_spec.js diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index d92a51daba2..9d916b87172 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -100,6 +100,13 @@ export const DEFAULT_PROCESSORS = { if (bid.ext?.dsa) { bidResponse.meta.dsa = bid.ext.dsa; } + if (bid.cat) { + bidResponse.meta.primaryCatId = bid.cat[0]; + bidResponse.meta.secondaryCatIds = bid.cat.slice(1); + } + if (bid.attr) { + bidResponse.meta.attr = bid.attr; + } } } } diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js new file mode 100644 index 00000000000..3ace8108d2b --- /dev/null +++ b/modules/bidResponseFilter/index.js @@ -0,0 +1,40 @@ +import { auctionManager } from '../../src/auctionManager.js'; +import { config } from '../../src/config.js'; +import { getHook } from '../../src/hook.js'; + +export const MODULE_NAME = 'bidResponseFilter'; +export const BID_CATEGORY_REJECTION_REASON = 'Category is not allowed'; +export const BID_ADV_DOMAINS_REJECTION_REASON = 'Adv domain is not allowed'; +export const BID_ATTR_REJECTION_REASON = 'Attr is not allowed'; + +function init() { + getHook('addBidResponse').before(addBidResponseHook); +}; + +export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { + const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; + const battr = index.getBidRequest(bid)?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; + const moduleConfig = config.getConfig(MODULE_NAME); + + const catConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.cat || {})}; + const advConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.adv || {})}; + const attrConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.attr || {})}; + + const { primaryCatId, secondaryCatIds = [], advertiserDomains = [], attr: metaAttr } = bid.meta || {}; + + // checking if bid fulfills ortb2 fields rules + if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.blockUnknown && !primaryCatId)) { + reject(BID_CATEGORY_REJECTION_REASON); + } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || + (advConfig.blockUnknown && !advertiserDomains.length)) { + reject(BID_ADV_DOMAINS_REJECTION_REASON); + } else if ((attrConfig.enforce && battr.includes(metaAttr)) || + (attrConfig.blockUnknown && !metaAttr)) { + reject(BID_ATTR_REJECTION_REASON); + } else { + return next(adUnitCode, bid, reject); + } +} + +init(); diff --git a/src/auctionIndex.js b/src/auctionIndex.js index afae2089518..d0b8355352a 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -65,6 +65,9 @@ export function AuctionIndex(getAuctions) { .flatMap(ber => ber.bids) .find(br => br && br.bidId === requestId); } + }, + getOrtb2(bid) { + return this.getBidderRequest(bid)?.ortb2 || this.getAuction(bid)?.getFPD()?.global?.ortb2 } }); } diff --git a/src/prebid.js b/src/prebid.js index 18a9127a793..975e4b4517b 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -100,6 +100,22 @@ function validateSizes(sizes, targLength) { return cleanSizes; } +export function setBattrForAdUnit(adUnit, mediaType) { + const ortb2Imp = adUnit.ortb2Imp || {}; + const mediaTypes = adUnit.mediaTypes || {}; + + if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) { + logWarn(`Ad unit ${adUnit.code} specifies conflicting ortb2Imp.${mediaType}.battr and mediaTypes.${mediaType}.battr, the latter will be ignored`, adUnit); + } + + const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr; + + if (battr != null) { + deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr); + deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr); + } +} + function validateBannerMediaType(adUnit) { const validatedAdUnit = deepClone(adUnit); const banner = validatedAdUnit.mediaTypes.banner; @@ -112,6 +128,7 @@ function validateBannerMediaType(adUnit) { logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); delete validatedAdUnit.mediaTypes.banner } + setBattrForAdUnit(validatedAdUnit, 'banner'); return validatedAdUnit; } @@ -135,6 +152,7 @@ function validateVideoMediaType(adUnit) { } } validateOrtbVideoFields(validatedAdUnit); + setBattrForAdUnit(validatedAdUnit, 'video'); return validatedAdUnit; } @@ -184,6 +202,7 @@ function validateNativeMediaType(adUnit) { logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); delete validatedAdUnit.mediaTypes.native.icon.sizes; } + setBattrForAdUnit(validatedAdUnit, 'native'); return validatedAdUnit; } diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js new file mode 100644 index 00000000000..3990cd3feb3 --- /dev/null +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -0,0 +1,135 @@ +import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGORY_REJECTION_REASON, MODULE_NAME, PUBLISHER_FILTER_REJECTION_REASON, addBidResponseHook } from '../../../modules/bidResponseFilter'; +import { config } from '../../../src/config'; + +describe('bidResponseFilter', () => { + let mockAuctionIndex + beforeEach(() => { + config.resetConfig(); + mockAuctionIndex = { + getBidRequest: () => {}, + getAdUnit: () => {} + }; + }); + + it('should pass the bid after successful ortb2 rules validation', () => { + const call = sinon.stub(); + + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'EXAMPLE-CAT-ID', + attr: 'attr' + } + }; + + addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex); + sinon.assert.calledOnce(call); + }); + + it('should reject the bid after failed ortb2 cat rule validation', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'BANNED_CAT1', + attr: 'attr' + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + }); + + it('should reject the bid after failed ortb2 adv domains rule validation', () => { + const rejection = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'VALID_CAT', + attr: 'attr' + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + addBidResponseHook(call, 'adcode', bid, rejection, mockAuctionIndex); + sinon.assert.calledWith(rejection, BID_ADV_DOMAINS_REJECTION_REASON); + }); + + it('should reject the bid after failed ortb2 attr rule validation', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: 'VALID_CAT', + attr: 'BANNED_ATTR' + }, + mediaType: 'video' + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + mockAuctionIndex.getBidRequest = () => ({ + ortb2Imp: { + video: { + battr: 'BANNED_ATTR' + } + } + }) + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_ATTR_REJECTION_REASON); + }); + + it('should omit the validation if the flag is set to false', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: 'BANNED_CAT1', + attr: 'valid_attr' + } + }; + + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + config.setConfig({[MODULE_NAME]: {cat: {enforce: false}}}); + + addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex); + sinon.assert.calledOnce(call); + }); + + it('should allow bid for unknown flag set to false', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: undefined, + attr: 'valid_attr' + } + }; + + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + config.setConfig({[MODULE_NAME]: {cat: {blockUnknown: false}}}); + + addBidResponseHook(call, 'adcode', bid, () => {}); + sinon.assert.calledOnce(call); + }); +}) diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js index 94ec1011fbf..6b2286a5fe5 100644 --- a/test/spec/modules/dsp_genieeBidAdapter_spec.js +++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js @@ -121,7 +121,9 @@ describe('Geniee adapter tests', () => { currency: 'JPY', mediaType: 'banner', meta: { - advertiserDomains: ['geniee.co.jp'] + advertiserDomains: ['geniee.co.jp'], + primaryCatId: 'IAB1', + secondaryCatIds: [] }, netRevenue: true, requestId: 'bid-id', diff --git a/test/spec/ortbConverter/banner_spec.js b/test/spec/ortbConverter/banner_spec.js index 0f6686283a1..a54dbcd0847 100644 --- a/test/spec/ortbConverter/banner_spec.js +++ b/test/spec/ortbConverter/banner_spec.js @@ -126,6 +126,16 @@ describe('pbjs -> ortb banner conversion', () => { expect(imp.banner.someParam).to.eql('someValue'); }); + it('should keep ortb2Imp.banner.battr', () => { + const imp = { + banner: { + battr: 'battr' + } + }; + fillBannerImp(imp, {mediaTypes: {banner: {sizes: [1, 2]}}}, {}); + expect(imp.banner.battr).to.eql('battr'); + }); + it('does nothing if context.mediaType is set but is not BANNER', () => { const imp = {}; fillBannerImp(imp, {mediaTypes: {banner: {sizes: [1, 2]}}}, {mediaType: VIDEO}); diff --git a/test/spec/ortbConverter/native_spec.js b/test/spec/ortbConverter/native_spec.js index 56c733817cd..8ff1f9254fb 100644 --- a/test/spec/ortbConverter/native_spec.js +++ b/test/spec/ortbConverter/native_spec.js @@ -46,6 +46,16 @@ describe('pbjs -> ortb native requests', () => { expect(imp.native.something).to.eql('orother') }); + it('should keep ortb2Imp.native.battr', () => { + const imp = { + native: { + battr: 'battr' + } + }; + fillNativeImp(imp, {mediaTypes: {native: {sizes: [1, 2]}}}, {}); + expect(imp.native.battr).to.eql('battr'); + }); + it('should do nothing if there are no assets', () => { const imp = {}; fillNativeImp(imp, {nativeOrtbRequest: {assets: []}}, {}); diff --git a/test/spec/ortbConverter/video_spec.js b/test/spec/ortbConverter/video_spec.js index ab4034bb60a..9a3675beb6d 100644 --- a/test/spec/ortbConverter/video_spec.js +++ b/test/spec/ortbConverter/video_spec.js @@ -122,6 +122,16 @@ describe('pbjs -> ortb video conversion', () => { expect(imp.video.someParam).to.eql('someValue'); }); + it('should keep ortb2Imp.video.battr', () => { + const imp = { + video: { + battr: 'battr' + } + }; + fillVideoImp(imp, {mediaTypes: {video: {sizes: [1, 2]}}}, {}); + expect(imp.video.battr).to.eql('battr'); + }); + it('does nothing is context.mediaType is set but is not VIDEO', () => { const imp = {}; fillVideoImp(imp, {mediaTypes: {video: {playerSize: [[1, 2]]}}}, {mediaType: BANNER}); diff --git a/test/spec/unit/core/auctionIndex_spec.js b/test/spec/unit/core/auctionIndex_spec.js index df29ed1a6cb..cc93e66adcf 100644 --- a/test/spec/unit/core/auctionIndex_spec.js +++ b/test/spec/unit/core/auctionIndex_spec.js @@ -3,11 +3,14 @@ import {AuctionIndex} from '../../../../src/auctionIndex.js'; describe('auction index', () => { let index, auctions; - function mockAuction(id, adUnits, bidderRequests) { + function mockAuction(id, adUnits, bidderRequests, ortb2) { return { getAuctionId() { return id }, getAdUnits() { return adUnits; }, - getBidRequests() { return bidderRequests; } + getBidRequests() { return bidderRequests; }, + getFPD() { + return { global: { ortb2 } } + } } } @@ -126,4 +129,27 @@ describe('auction index', () => { }); }) }); + + describe('getOrtb2', () => { + let bidderRequests, adUnits = []; + beforeEach(() => { + bidderRequests = [ + {bidderRequestId: 'ber1', ortb2: {}, bids: [{bidId: 'b1', adUnitId: 'au1'}, {}]}, + {bidderRequestId: 'ber2', bids: [{bidId: 'b2', adUnitId: 'au2'}]} + ] + auctions = [ + mockAuction('a1', [adUnits[0]], [bidderRequests[0], {}]), + mockAuction('a2', [adUnits[1]], [bidderRequests[1]], {ortb2Field: true}) + ] + }); + it('should return ortb2 for bid if exists on bidder request', () => { + const ortb2 = index.getOrtb2({bidderRequestId: 'ber1'}); + expect(ortb2).to.be.a('object'); + }) + + it('should return ortb2 from auction if does not exist on bidder request', () => { + const ortb2 = index.getOrtb2({bidderRequestId: 'ber2', auctionId: 'a2'}); + expect(ortb2).to.be.deep.equals({ortb2Field: true}); + }) + }) }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 69e608e0d65..60363ad359d 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -28,6 +28,7 @@ import {generateUUID} from '../../../src/utils.js'; import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; import {BID_STATUS, EVENTS, GRANULARITY_OPTIONS, PB_LOCATOR, TARGETING_KEYS} from 'src/constants.js'; import {getBidToRender} from '../../../src/adRendering.js'; +import { setBattrForAdUnit } from '../../../src/prebid.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -3745,4 +3746,71 @@ describe('Unit: Prebid Module', function () { expect(auctionManager.getBidsReceived().length).to.equal(0); }); }); + + describe('setBattrForAdUnit', () => { + it('should set copy battr to both places', () => { + const adUnit = { + ortb2Imp: { + video: { + battr: 'banned attribute' + } + }, + mediaTypes: { + video: {} + } + } + + setBattrForAdUnit(adUnit, 'video'); + + expect(adUnit.mediaTypes.video.battr).to.deep.equal('banned attribute'); + expect(adUnit.ortb2Imp.video.battr).to.deep.equal('banned attribute'); + + const adUnit2 = { + mediaTypes: { + video: { + battr: 'banned attribute' + } + }, + ortb2Imp: { + video: {} + } + } + + setBattrForAdUnit(adUnit2, 'video'); + + expect(adUnit2.ortb2Imp.video.battr).to.deep.equal('banned attribute'); + expect(adUnit2.mediaTypes.video.battr).to.deep.equal('banned attribute'); + }) + + it('should log warn if both are specified and differ from eachother', () => { + let spyLogWarn = sinon.spy(utils, 'logWarn'); + const adUnit = { + mediaTypes: { + native: { + battr: 'banned attribute' + } + }, + ortb2Imp: { + native: { + battr: 'banned attribute 2' + } + } + } + setBattrForAdUnit(adUnit, 'native'); + sinon.assert.calledOnce(spyLogWarn); + spyLogWarn.resetHistory(); + utils.logWarn.restore(); + }) + + it('should not copy for undefined battr', () => { + const adUnit = { + mediaTypes: { + native: {} + } + } + setBattrForAdUnit(adUnit, 'native'); + expect(adUnit.mediaTypes.native).to.deep.equal({}); + expect(adUnit.mediaTypes.ortb2Imp).to.not.exist; + }) + }) }); From da9f7ca37dd8227e325664d25018c44a952e03b9 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:09:18 -0600 Subject: [PATCH 0560/1097] Magnite Analytics Adapter : track to PBS Analytics Event and ATAG (#12043) * Update constants.js * Update magniteAnalyticsAdapter.js * Update magniteAnalyticsAdapter_spec.js --- modules/magniteAnalyticsAdapter.js | 51 +++++++++++++++++-- .../modules/magniteAnalyticsAdapter_spec.js | 42 ++++++++++++++- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index dd16baed66e..7eb20439f5f 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -65,7 +65,7 @@ const { BID_TIMEOUT, BID_WON, BILLABLE_EVENT, - SEAT_NON_BID, + PBS_ANALYTICS, BID_REJECTED } = EVENTS; @@ -927,8 +927,8 @@ magniteAdapter.track = ({ eventType, args }) => { const bidStatus = args.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET ? BID_REJECTED_IPF : 'rejected'; handleBidResponse(args, bidStatus); break; - case SEAT_NON_BID: - handleNonBidEvent(args); + case PBS_ANALYTICS: + handlePbsAnalytics(args); break; case BIDDER_DONE: const serverError = deepAccess(args, 'serverErrors.0'); @@ -1019,8 +1019,27 @@ magniteAdapter.track = ({ eventType, args }) => { } }; -const handleNonBidEvent = function(args) { - const {seatnonbid, auctionId} = args; +const handlePbsAnalytics = function (args) { + const {seatnonbid, auctionId, atag} = args; + if (seatnonbid) { + handleNonBidEvent(seatnonbid, auctionId); + } + if (atag) { + handleAtagEvent(atag, auctionId); + } +} + +const handleAtagEvent = function (atag, auctionId) { + const tags = findTimeoutOptimization(atag) + tags.forEach(tag => { + tag.activities.forEach(activity => { + if (activity.name === 'optimize-tmax' && activity.status === 'success') { + setAnalyticsTagData(activity.results[0]?.values, deepAccess(cache, `auctions.${auctionId}.auction`)) + } + }) + }); +} +const handleNonBidEvent = function(seatnonbid, auctionId) { const auction = deepAccess(cache, `auctions.${auctionId}.auction`); // if no auction just bail if (!auction) { @@ -1050,6 +1069,28 @@ const handleNonBidEvent = function(args) { }); }; +const findTimeoutOptimization = (atag) => { + let timeoutOpt; + atag.forEach(tag => { + if (tag.module === 'mgni-timeout-optimization') { + timeoutOpt = tag.analyticstags; + } + }) + return timeoutOpt; +} +const setAnalyticsTagData = (values, auction) => { + let data = { + name: values.scenario, + rule: values.rule, + value: values.tmax + } + + const experiments = deepAccess(auction, 'experiments') || []; + experiments.push(data); + + deepSetValue(auction, 'experiments', experiments); +} + const statusMap = { 0: { status: 'no-bid' diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index df06a4e38f7..a5644ffc6eb 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -26,6 +26,7 @@ const { BID_TIMEOUT, BILLABLE_EVENT, SEAT_NON_BID, + PBS_ANALYTICS, BID_REJECTED } = EVENTS; @@ -639,6 +640,45 @@ describe('magnite analytics adapter', function () { ]); }); + it('should pass along atag data', function () { + const PBS_ANALYTICS_EVENT = { + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + atag: [{ + 'stage': 'processed-auction-request', + 'module': 'mgni-timeout-optimization', + 'analyticstags': [{ + activities: [{ + name: 'optimize-tmax', + status: 'success', + results: [{ + status: 'success', + values: { + 'scenario': 'a', + 'rule': 'b', + 'tmax': 3 + } + }] + }] + }] + }] + } + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(PBS_ANALYTICS, PBS_ANALYTICS_EVENT) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].experiments[0]).to.deep.equal({ + name: 'a', + rule: 'b', + value: 3 + }); + }); + it('should pass along user ids', function () { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].userId = { @@ -2262,7 +2302,7 @@ describe('magnite analytics adapter', function () { const runNonBidAuction = () => { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(SEAT_NON_BID, seatnonbid) + events.emit(PBS_ANALYTICS, seatnonbid) events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); events.emit(AUCTION_END, MOCK.AUCTION_END); clock.tick(rubiConf.analyticsBatchTimeout + 1000); From ebe0698b2880eb71c31066608710e10297c1b83b Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Wed, 9 Oct 2024 01:31:37 +0300 Subject: [PATCH 0561/1097] AdMatic Bid Adapter : add admaticde and netaddiction alias (#12301) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index a5ebe91f98a..74c063042ef 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -41,8 +41,10 @@ export const spec = { code: BIDDER_CODE, gvlid: 1281, aliases: [ + {code: 'admaticde', gvlid: 1281}, {code: 'pixad', gvlid: 1281}, - {code: 'monetixads', gvlid: 1281} + {code: 'monetixads', gvlid: 1281}, + {code: 'netaddiction', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -144,12 +146,18 @@ export const spec = { if (payload) { switch (bidderName) { + case 'netaddiction': + SYNC_URL = 'https://static.cdn.netaddiction.tech/netaddiction/sync.html'; + break; case 'monetixads': - SYNC_URL = 'https://static.cdn.monetixads.com/sync.html'; + SYNC_URL = 'https://static.cdn.monetixads.com/monetixads/sync.html'; break; case 'pixad': SYNC_URL = 'https://static.cdn.pixad.com.tr/sync.html'; break; + case 'admaticde': + SYNC_URL = 'https://static.cdn.admatic.de/admaticde/sync.html'; + break; default: SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; break; From 4cfc7bbccf553cbc876d9335988c96286b420134 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:11:31 +0200 Subject: [PATCH 0562/1097] ZetaGlobalSspAnalytics Adapter: bugfix (#12306) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 4 +- .../zeta_global_sspAnalyticsAdapter_spec.js | 229 +++++++++++++++++- 2 files changed, 229 insertions(+), 4 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 02cfb8739b7..805e2c51c81 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -87,8 +87,8 @@ function auctionEndHandler(args) { function bidTimeoutHandler(args) { const event = { zetaParams: zetaParams, - domain: args.find(t => t?.ortb2?.site?.domain), - page: args.find(t => t?.ortb2?.site?.page), + domain: args.find(t => t?.ortb2?.site?.domain)?.ortb2?.site?.domain, + page: args.find(t => t?.ortb2?.site?.page)?.ortb2?.site?.page, timeouts: args.map(t => ({ bidId: t?.bidId, auctionId: t?.auctionId, diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 7abfc7dcc10..a308eb44987 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -354,7 +354,171 @@ const SAMPLE_EVENTS = { ] }, 'adId': '5759bb3ef7be1e8' - } + }, + BID_TIMEOUT: [ + { + 'bidder': 'zeta_global_ssp', + 'params': [ + { + 'tags': { + 'position': 'top', + 'shortname': 'someShortName', + }, + 'sid': 100 + } + ], + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'ad-1', + 'transactionId': '6d2c4757-d34b-4538-b812-c6e638f05eac', + 'adUnitId': '9dc8cbf6-9b2d-48c7-b424-be9ae4be3dfc', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '27c8c05823e2f', + 'bidderRequestId': '1e8e895de1708', + 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'domain': 'zetaglobal.com', + 'publisher': { + 'domain': 'zetaglobal.com' + }, + 'page': 'https://zetaglobal.com/page' + }, + 'device': { + 'w': 807, + 'h': 847, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '123' + ] + }, + { + 'brand': 'Not:A-Brand', + 'version': [ + '8' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '123' + ] + } + ], + 'mobile': 0 + } + } + }, + 'timeout': 3 + }, + { + 'bidder': 'zeta_global_ssp', + 'params': [ + { + 'tags': { + 'position': 'top', + 'shortname': 'someShortName', + }, + 'sid': 100 + } + ], + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'ad-2', + 'transactionId': '11eaa59f-221d-4027-a0a8-7841e0ffc4ee', + 'adUnitId': '4397b4bc-6368-40b8-91f1-987a31076886', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '31a3b551cbf1ed', + 'bidderRequestId': '1e8e895de1708', + 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'domain': 'zetaglobal.com', + 'publisher': { + 'domain': 'zetaglobal.com' + }, + 'page': 'https://zetaglobal.com/page' + }, + 'device': { + 'w': 807, + 'h': 847, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '123' + ] + }, + { + 'brand': 'Not:A-Brand', + 'version': [ + '8' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '123' + ] + } + ], + 'mobile': 0 + } + } + }, + 'timeout': 3 + } + ] } describe('Zeta Global SSP Analytics Adapter', function () { @@ -402,8 +566,9 @@ describe('Zeta Global SSP Analytics Adapter', function () { events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); + events.emit(EVENTS.BID_TIMEOUT, SAMPLE_EVENTS.BID_TIMEOUT); - expect(requests.length).to.equal(2); + expect(requests.length).to.equal(3); const auctionEnd = JSON.parse(requests[0].requestBody); expect(auctionEnd).to.be.deep.equal({ zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, @@ -475,6 +640,66 @@ describe('Zeta Global SSP Analytics Adapter', function () { cpm: 2.258302852806723 }); expect(auctionSucceeded.device.ua).to.not.be.empty; + + const bidTimeout = JSON.parse(requests[2].requestBody); + expect(bidTimeout.zetaParams).to.be.deep.equal({ + sid: 111, + tags: { + position: 'top', + shortname: 'name' + } + }); + expect(bidTimeout.domain).to.eql('zetaglobal.com'); + expect(bidTimeout.page).to.eql('https://zetaglobal.com/page'); + expect(bidTimeout.timeouts).to.be.deep.equal([{ + 'bidId': '27c8c05823e2f', + 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', + 'bidder': 'zeta_global_ssp', + 'mediaType': 'BANNER', + 'size': '300x250', + 'timeout': 3, + 'device': { + 'w': 807, + 'h': 847, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': {'brand': 'macOS'}, + 'browsers': [{'brand': 'Google Chrome', 'version': ['123']}, { + 'brand': 'Not:A-Brand', + 'version': ['8'] + }, {'brand': 'Chromium', 'version': ['123']}], + 'mobile': 0 + } + }, + 'adUnitCode': 'ad-1' + }, { + 'bidId': '31a3b551cbf1ed', + 'auctionId': 'fa9ef841-bcb9-401f-96ad-03a94ac64e63', + 'bidder': 'zeta_global_ssp', + 'mediaType': 'BANNER', + 'size': '300x250', + 'timeout': 3, + 'device': { + 'w': 807, + 'h': 847, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': {'brand': 'macOS'}, + 'browsers': [{'brand': 'Google Chrome', 'version': ['123']}, { + 'brand': 'Not:A-Brand', + 'version': ['8'] + }, {'brand': 'Chromium', 'version': ['123']}], + 'mobile': 0 + } + }, + 'adUnitCode': 'ad-2' + }]); }); }); }); From 7ee65bdccd68685cf62d5de71f99e72ec40bad08 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Wed, 9 Oct 2024 17:13:02 +0300 Subject: [PATCH 0563/1097] Adkernel Bid Adapter: add rxnetwork alias (#12307) --- modules/adkernelBidAdapter.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 36a359170c4..4997c92dffd 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -98,7 +98,8 @@ export const spec = { {code: 'monetix'}, {code: 'hyperbrainz'}, {code: 'voisetech'}, - {code: 'global_sun'} + {code: 'global_sun'}, + {code: 'rxnetwork'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -114,7 +115,9 @@ export const spec = { !isNaN(Number(bidRequest.params.zoneId)) && bidRequest.params.zoneId > 0 && bidRequest.mediaTypes && - (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video || (bidRequest.mediaTypes.native && validateNativeAdUnit(bidRequest.mediaTypes.native))); + (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video || + (bidRequest.mediaTypes.native && validateNativeAdUnit(bidRequest.mediaTypes.native)) + ); }, /** From cc5bf6b1a0dfab9dfd9b560b535404bc2c69aa83 Mon Sep 17 00:00:00 2001 From: Mikhail Malkov Date: Wed, 9 Oct 2024 17:17:36 +0300 Subject: [PATCH 0564/1097] nextMillenniumBidAdapter: fixed a bug where there was no requestId in the response (#12304) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * NextMillennium: Sending a request with several imp objects. * nextMillenniumBidAdapter: fixed bug - bid.requestId is undefined * nextMillenniumBidAdapter: fixed bug - bid.requestId is undefined - 2 --- modules/nextMillenniumBidAdapter.js | 9 +- .../modules/nextMillenniumBidAdapter_spec.js | 427 ++++++++++-------- 2 files changed, 244 insertions(+), 192 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 9dbf2e93dc6..1d37c237b70 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -20,7 +20,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '4.0.0'; +const NM_VERSION = '4.0.1'; const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; @@ -80,6 +80,7 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const requests = []; + const bidIds = {}; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; const site = getSiteObj(); const device = getDeviceObj(); @@ -109,6 +110,8 @@ export const spec = { if (i === 0) postBody.cur = cur; postBody.imp.push(getImp(bid, id, mediaTypes)); postBody.ext.next_mil_imps.push(getExtNextMilImp(bid)); + + bidIds[bid.adUnitCode] = bid.bidId; }); this.getUrlPixelMetric(EVENTS.BID_REQUESTED, validBidRequests); @@ -121,6 +124,8 @@ export const spec = { contentType: 'text/plain', withCredentials: true, }, + + bidIds, }); return requests; @@ -133,7 +138,7 @@ export const spec = { const bids = []; _each(response.seatbid, (resp) => { _each(resp.bid, (bid) => { - const requestId = bidRequest.bidId; + const requestId = bidRequest.bidIds[bid.impid]; const {ad, adUrl, vastUrl, vastXml} = getAd(bid); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 01b09320055..6b2e7ef9ba0 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -569,61 +569,6 @@ describe('nextMillenniumBidAdapterTests', () => { } }); - const bidRequestData = [{ - adUnitCode: 'test-div', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { placement_id: '-1' }, - sizes: [[300, 250]], - uspConsent: '1---', - gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - }, - - ortb2: { - device: { - w: 1500, - h: 1000 - }, - - site: { - domain: 'example.com', - page: 'http://example.com' - } - } - }]; - - const serverResponse = { - body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', - seatbid: [ - { - bid: [ - { - id: '7457329903666272789', - price: 0.5, - adm: 'Hello! It\'s a test ad!', - adid: '96846035', - adomain: ['test.addomain.com'], - w: 300, - h: 250 - } - ] - } - ], - cur: 'USD', - ext: { - sync: { - image: ['urlA?gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}'], - iframe: ['urlB'], - } - } - } - }; - const bidRequestDataGI = getBidRequestDataGI(); function getBidRequestDataGI(adUnitCodes = ['test-banner-gi', 'test-banner-gi', 'test-video-gi']) { return [ @@ -649,7 +594,7 @@ describe('nextMillenniumBidAdapterTests', () => { { adUnitCode: adUnitCodes[1], - bidId: 'bid1234', + bidId: 'bid1235', auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidder: 'nextMillennium', params: { group_id: '1234' }, @@ -669,7 +614,7 @@ describe('nextMillenniumBidAdapterTests', () => { { adUnitCode: adUnitCodes[2], - bidId: 'bid1234', + bidId: 'bid1236', auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidder: 'nextMillennium', params: { group_id: '1234' }, @@ -688,35 +633,33 @@ describe('nextMillenniumBidAdapterTests', () => { ]; } - it('validate_generated_params', function() { - const request = spec.buildRequests(bidRequestData, {bidderRequestId: 'mock-uuid'}); - expect(JSON.parse(request[0].data).id).to.exist; - }); - - it('check parameters group_id or placement_id', function() { + describe('check parameters group_id or placement_id', function() { + let numberTest = 0 for (let test of bidRequestDataGI) { - const request = spec.buildRequests([test]); - const requestData = JSON.parse(request[0].data); - const storeRequestId = (requestData.imp[0].ext.prebid.storedrequest.id || ''); - expect(storeRequestId.length).to.be.not.equal(0); - - const srId = storeRequestId.split(';'); - const isGroupId = (/^g[1-9]\d*/).test(srId[0]); - if (isGroupId) { - expect(srId.length).to.be.equal(3); - expect((/^g[1-9]\d*/).test(srId[0])).to.be.true; - const sizes = srId[1].split('|'); - for (let size of sizes) { - if (!(/^[1-9]\d*[xX,][1-9]\d*$/).test(size)) { - expect(storeRequestId).to.be.equal(''); - } + it(`test - ${++numberTest}`, () => { + const request = spec.buildRequests([test]); + const requestData = JSON.parse(request[0].data); + const storeRequestId = (requestData.imp[0].ext.prebid.storedrequest.id || ''); + expect(storeRequestId.length).to.be.not.equal(0); + + const srId = storeRequestId.split(';'); + const isGroupId = (/^g[1-9]\d*/).test(srId[0]); + if (isGroupId) { + expect(srId.length).to.be.equal(3); + expect((/^g[1-9]\d*/).test(srId[0])).to.be.true; + const sizes = srId[1].split('|'); + for (let size of sizes) { + if (!(/^[1-9]\d*[xX,][1-9]\d*$/).test(size)) { + expect(storeRequestId).to.be.equal(''); + } - expect((/^[1-9]\d*[xX,][1-9]\d*$/).test(size)).to.be.true; - } - } else { - expect(srId.length).to.be.equal(1); - expect((/^[1-9]\d*/).test(srId[0])).to.be.true; - }; + expect((/^[1-9]\d*[xX,][1-9]\d*$/).test(size)).to.be.true; + } + } else { + expect(srId.length).to.be.equal(1); + expect((/^[1-9]\d*/).test(srId[0])).to.be.true; + }; + }); }; }); @@ -752,112 +695,6 @@ describe('nextMillenniumBidAdapterTests', () => { }; }); - it('Check if domain was added', function() { - const request = spec.buildRequests(bidRequestData); - expect(JSON.parse(request[0].data).site.domain).to.exist; - }); - - it('Check if imp object was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).imp).to.be.an('array') - }); - - it('validate_response_params', function() { - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('96846035'); - expect(bid.ad).to.equal('Hello! It\'s a test ad!'); - expect(bid.cpm).to.equal(0.5); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.currency).to.equal('USD'); - }); - - it('validate_videowrapper_response_params', function() { - const serverResponse = { - body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', - seatbid: [ - { - bid: [ - { - id: '7457329903666272789', - price: 0.5, - adm: 'https://some_vast_host.com/vast.xml', - adid: '96846035', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - ext: { - prebid: { - type: 'video' - } - } - } - ] - } - ], - cur: 'USD' - } - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('96846035'); - expect(bid.vastUrl).to.equal('https://some_vast_host.com/vast.xml'); - expect(bid.cpm).to.equal(0.5); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.currency).to.equal('USD'); - }); - - it('validate_videoxml_response_params', function() { - const serverResponse = { - body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', - seatbid: [ - { - bid: [ - { - id: '7457329903666272789', - price: 0.5, - adm: '', - adid: '96846035', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - ext: { - prebid: { - type: 'video' - } - } - } - ] - } - ], - cur: 'USD' - } - }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('96846035'); - expect(bid.vastXml).to.equal(''); - expect(bid.cpm).to.equal(0.5); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.currency).to.equal('USD'); - }); - describe('function spec._getUrlPixelMetric', function() { const dataForTests = [ { @@ -1031,4 +868,214 @@ describe('nextMillenniumBidAdapterTests', () => { }); }; }); + + describe('check function buildRequests', () => { + const tests = [ + { + title: 'test - 1', + bidderRequest: {bidderRequestId: 'mock-uuid'}, + bidRequests: [ + { + adUnitCode: 'test-div', + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '-1' }, + sizes: [[300, 250]], + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + }, + + ortb2: { + device: { + w: 1500, + h: 1000 + }, + + site: { + domain: 'example.com', + page: 'http://example.com' + } + } + }, + + { + adUnitCode: 'test-div-2', + bidId: 'bid1235', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '333' }, + sizes: [[300, 250]], + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + }, + + ortb2: { + device: { + w: 1500, + h: 1000 + }, + + site: { + domain: 'example.com', + page: 'http://example.com' + } + }, + }, + ], + + expected: { + id: 'mock-uuid', + bidIds: {'test-div': 'bid1234', 'test-div-2': 'bid1235'}, + impSize: 2, + requestSize: 1, + domain: 'example.com', + }, + }, + ]; + + for (let {title, bidRequests, bidderRequest, expected} of tests) { + it(title, () => { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.length).to.equal(expected.requestSize); + expect(request[0].bidIds).to.deep.equal(expected.bidIds); + + const requestData = JSON.parse(request[0].data); + expect(requestData.id).to.equal(expected.id); + expect(requestData?.imp?.length).to.equal(expected.impSize); + }); + }; + }); + + describe('check function interpretResponse', () => { + const tests = [ + { + title: 'test - 1', + serverResponse: { + body: { + id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', + seatbid: [ + { + bid: [ + { + id: '7457329903666272789-0', + impid: 'ad-unit-0', + price: 0.5, + adm: 'Hello! It\'s a test ad!', + adid: '96846035-0', + adomain: ['test.addomain.com'], + w: 300, + h: 250, + }, + + { + id: '7457329903666272789-1', + impid: 'ad-unit-1', + price: 0.7, + adm: 'https://some_vast_host.com/vast.xml', + adid: '96846035-1', + adomain: ['test.addomain.com'], + w: 400, + h: 300, + ext: {prebid: {type: 'video'}}, + }, + + { + id: '7457329903666272789-2', + impid: 'ad-unit-3', + price: 1.0, + adm: '', + adid: '96846035-3', + adomain: ['test.addomain.com'], + w: 640, + h: 480, + ext: {prebid: {type: 'video'}}, + }, + ], + }, + ], + cur: 'USD', + }, + }, + + bidRequest: { + bidIds: { + 'ad-unit-0': 'bid-id-0', + 'ad-unit-1': 'bid-id-1', + 'ad-unit-2': 'bid-id-2', + 'ad-unit-3': 'bid-id-3', + }, + }, + + expected: [ + { + title: 'banner', + requestId: 'bid-id-0', + creativeId: '96846035-0', + ad: 'Hello! It\'s a test ad!', + vastUrl: undefined, + vastXml: undefined, + cpm: 0.5, + width: 300, + height: 250, + currency: 'USD', + }, + + { + title: 'video - vastUrl', + requestId: 'bid-id-1', + creativeId: '96846035-1', + ad: undefined, + vastUrl: 'https://some_vast_host.com/vast.xml', + vastXml: undefined, + cpm: 0.7, + width: 400, + height: 300, + currency: 'USD', + }, + + { + title: 'video - vastXml', + requestId: 'bid-id-3', + creativeId: '96846035-3', + ad: undefined, + vastUrl: undefined, + vastXml: '', + cpm: 1.0, + width: 640, + height: 480, + currency: 'USD', + }, + ], + }, + ]; + + for (let {title, serverResponse, bidRequest, expected} of tests) { + describe(title, () => { + const bids = spec.interpretResponse(serverResponse, bidRequest); + for (let i = 0; i < bids.length; i++) { + it(expected[i].title, () => { + expect(bids).to.have.lengthOf(expected.length); + + const bid = bids[i] + expect(bid.creativeId).to.equal(expected[i].creativeId); + expect(bid.requestId).to.equal(expected[i].requestId); + expect(bid.ad).to.equal(expected[i].ad); + expect(bid.vastUrl).to.equal(expected[i].vastUrl); + expect(bid.vastXml).to.equal(expected[i].vastXml); + expect(bid.cpm).to.equal(expected[i].cpm); + expect(bid.width).to.equal(expected[i].width); + expect(bid.height).to.equal(expected[i].height); + expect(bid.currency).to.equal(expected[i].currency); + }); + }; + }); + }; + }); }); From b4eccb043b7374cd8c469e70f424aaadcfee0e70 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 9 Oct 2024 15:22:38 +0100 Subject: [PATCH 0565/1097] 51Degrees RTD submodule: small improvements and fixes (#12302) * 51Degrees RTD submodule: calculate PPI based on physical screen size & small refactor in preparation for the next 51Degrees RTD submodule update * 51Degrees RTD submodule: add 51Degrees module to `.submodules.json` * 51Degrees RTD submodule: update documentation --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/.submodules.json | 1 + modules/51DegreesRtdProvider.js | 130 +++++++--- modules/51DegreesRtdProvider.md | 62 +++-- .../spec/modules/51DegreesRtdProvider_spec.js | 241 +++++++++++++----- 4 files changed, 309 insertions(+), 125 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index 3ac541ce4ea..36daa70e75b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -58,6 +58,7 @@ ], "rtdModule": [ "1plusXRtdProvider", + "51DegreesRtdProvider", "a1MediaRtdProvider", "aaxBlockmeterRtdProvider", "adagioRtdProvider", diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js index ec2e5235445..123eeeaddc3 100644 --- a/modules/51DegreesRtdProvider.js +++ b/modules/51DegreesRtdProvider.js @@ -1,6 +1,11 @@ import {loadExternalScript} from '../src/adloader.js'; import {submodule} from '../src/hook.js'; -import {prefixLog, deepAccess, mergeDeep} from '../src/utils.js'; +import { + deepAccess, + deepSetValue, + mergeDeep, + prefixLog, +} from '../src/utils.js'; const MODULE_NAME = '51Degrees'; export const LOG_PREFIX = `[${MODULE_NAME} RTD Submodule]:`; @@ -48,17 +53,49 @@ const ORTB_DEVICE_TYPE_MAP = new Map([ */ export const extractConfig = (moduleConfig, reqBidsConfigObj) => { // Resource key - const resourceKey = deepAccess(moduleConfig, 'params.resourceKey'); + let resourceKey = deepAccess(moduleConfig, 'params.resourceKey'); // On-premise JS URL - const onPremiseJSUrl = deepAccess(moduleConfig, 'params.onPremiseJSUrl'); + let onPremiseJSUrl = deepAccess(moduleConfig, 'params.onPremiseJSUrl'); + // Trim the values + if (typeof resourceKey === 'string') { + resourceKey = resourceKey.trim(); + } + if (typeof onPremiseJSUrl === 'string') { + onPremiseJSUrl = onPremiseJSUrl.trim(); + } + + // If this module is configured via a 3rd party wrapper, both form inputs + // might be mandatory. To handle this, 0 can be used as a value to skip + // the parameter. + if (typeof resourceKey === 'string' && resourceKey.trim() === '0') { + resourceKey = undefined; + } + if (typeof onPremiseJSUrl === 'string' && onPremiseJSUrl.trim() === '0') { + onPremiseJSUrl = undefined; + } + + // Verify that onPremiseJSUrl is a valid URL: either a full URL, relative + // path (/path/to/file.js), or a protocol-relative URL (//example.com/path/to/file.js) + if (typeof onPremiseJSUrl === 'string' && onPremiseJSUrl.length && !( + onPremiseJSUrl.startsWith('https://') || + onPremiseJSUrl.startsWith('http://') || + onPremiseJSUrl.startsWith('/')) + ) { + throw new Error(LOG_PREFIX + ' Invalid URL format for onPremiseJSUrl in moduleConfig'); + } + + // Verify that one of the parameters is provided, + // but not both at the same time if (!resourceKey && !onPremiseJSUrl) { throw new Error(LOG_PREFIX + ' Missing parameter resourceKey or onPremiseJSUrl in moduleConfig'); } else if (resourceKey && onPremiseJSUrl) { throw new Error(LOG_PREFIX + ' Only one of resourceKey or onPremiseJSUrl should be provided in moduleConfig'); } + + // Verify that the resource key is not the one provided as an example if (resourceKey === '') { - throw new Error(LOG_PREFIX + ' replace in configuration with a resource key obtained from https://configure.51degrees.com/tWrhNfY6'); + throw new Error(LOG_PREFIX + ' replace in configuration with a resource key obtained from https://configure.51degrees.com/HNZ75HT1'); } return {resourceKey, onPremiseJSUrl}; @@ -112,33 +149,57 @@ export const is51DegreesMetaPresent = () => { * @param {string} key The key to set * @param {any} value The value to set */ -export const setOrtb2KeyIfNotEmpty = (obj, key, value) => { +export const deepSetNotEmptyValue = (obj, key, value) => { if (!key) { throw new Error(LOG_PREFIX + ' Key is required'); } if (value) { - obj[key] = value; + deepSetValue(obj, key, value); } } +/** + * Converts all 51Degrees data to ORTB2 format + * + * @param {Object} data51 Response from 51Degrees API + * @param {Object} [data51.device] Device data + * + * @returns {Object} Enriched ORTB2 object + */ +export const convert51DegreesDataToOrtb2 = (data51) => { + let ortb2Data = {}; + + if (!data51) { + return ortb2Data; + } + + ortb2Data = convert51DegreesDeviceToOrtb2(data51.device); + + // placeholder for the next 51Degrees RTD submodule update + + return ortb2Data; +}; + /** * Converts 51Degrees device data to ORTB2 format * - * @param {Object} device + * @param {Object} device 51Degrees device object * @param {string} [device.deviceid] Device ID (unique 51Degrees identifier) - * @param {string} [device.devicetype] - * @param {string} [device.hardwarevendor] - * @param {string} [device.hardwaremodel] - * @param {string[]} [device.hardwarename] - * @param {string} [device.platformname] - * @param {string} [device.platformversion] - * @param {number} [device.screenpixelsheight] - * @param {number} [device.screenpixelswidth] - * @param {number} [device.pixelratio] - * @param {number} [device.screeninchesheight] + * @param {string} [device.devicetype] Device type + * @param {string} [device.hardwarevendor] Hardware vendor + * @param {string} [device.hardwaremodel] Hardware model + * @param {string[]} [device.hardwarename] Hardware name + * @param {string} [device.platformname] Platform name + * @param {string} [device.platformversion] Platform version + * @param {number} [device.screenpixelsheight] Screen height in pixels + * @param {number} [device.screenpixelswidth] Screen width in pixels + * @param {number} [device.screenpixelsphysicalheight] Screen physical height in pixels + * @param {number} [device.screenpixelsphysicalwidth] Screen physical width in pixels + * @param {number} [device.pixelratio] Pixel ratio + * @param {number} [device.screeninchesheight] Screen height in inches * - * @returns {Object} + * @returns {Object} Enriched ORTB2 object */ export const convert51DegreesDeviceToOrtb2 = (device) => { const ortb2Device = {}; @@ -154,27 +215,26 @@ export const convert51DegreesDeviceToOrtb2 = (device) => { : null ); + const devicePhysicalPPI = device.screenpixelsphysicalheight && device.screeninchesheight + ? Math.round(device.screenpixelsphysicalheight / device.screeninchesheight) + : null; + const devicePPI = device.screenpixelsheight && device.screeninchesheight ? Math.round(device.screenpixelsheight / device.screeninchesheight) : null; - setOrtb2KeyIfNotEmpty(ortb2Device, 'devicetype', ORTB_DEVICE_TYPE_MAP.get(device.devicetype)); - setOrtb2KeyIfNotEmpty(ortb2Device, 'make', device.hardwarevendor); - setOrtb2KeyIfNotEmpty(ortb2Device, 'model', deviceModel); - setOrtb2KeyIfNotEmpty(ortb2Device, 'os', device.platformname); - setOrtb2KeyIfNotEmpty(ortb2Device, 'osv', device.platformversion); - setOrtb2KeyIfNotEmpty(ortb2Device, 'h', device.screenpixelsheight); - setOrtb2KeyIfNotEmpty(ortb2Device, 'w', device.screenpixelswidth); - setOrtb2KeyIfNotEmpty(ortb2Device, 'pxratio', device.pixelratio); - setOrtb2KeyIfNotEmpty(ortb2Device, 'ppi', devicePPI); - - if (device.deviceid) { - ortb2Device.ext = { - 'fiftyonedegrees_deviceId': device.deviceid - }; - } + deepSetNotEmptyValue(ortb2Device, 'devicetype', ORTB_DEVICE_TYPE_MAP.get(device.devicetype)); + deepSetNotEmptyValue(ortb2Device, 'make', device.hardwarevendor); + deepSetNotEmptyValue(ortb2Device, 'model', deviceModel); + deepSetNotEmptyValue(ortb2Device, 'os', device.platformname); + deepSetNotEmptyValue(ortb2Device, 'osv', device.platformversion); + deepSetNotEmptyValue(ortb2Device, 'h', device.screenpixelsphysicalheight || device.screenpixelsheight); + deepSetNotEmptyValue(ortb2Device, 'w', device.screenpixelsphysicalwidth || device.screenpixelswidth); + deepSetNotEmptyValue(ortb2Device, 'pxratio', device.pixelratio); + deepSetNotEmptyValue(ortb2Device, 'ppi', devicePhysicalPPI || devicePPI); + deepSetNotEmptyValue(ortb2Device, 'ext.fiftyonedegrees_deviceId', device.deviceid); - return ortb2Device; + return {device: ortb2Device}; } /** @@ -211,7 +271,7 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user logMessage('51Degrees raw data: ', data); mergeDeep( reqBidsConfigObj.ortb2Fragments.global, - {device: convert51DegreesDeviceToOrtb2(data.device)}, + convert51DegreesDataToOrtb2(data), ); logMessage('reqBidsConfigObj: ', reqBidsConfigObj); callback(); diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md index 18f346d37a8..76fa73803c9 100644 --- a/modules/51DegreesRtdProvider.md +++ b/modules/51DegreesRtdProvider.md @@ -2,23 +2,23 @@ ## Overview - Module Name: 51Degrees Rtd Provider - Module Type: Rtd Provider + Module Name: 51Degrees RTD Provider + Module Type: RTD Provider Maintainer: support@51degrees.com ## Description -51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). +The 51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). -51Degrees module sets the following fields of the device object: `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio` - interested bidder adapters may use these fields as needed. In addition the module sets `device.ext.fiftyonedegrees_deviceId` to a permanent device ID which can be rapidly looked up in on premise data exposing over 250 properties including the device age, chip set, codec support, and price, operating system and app/browser versions, age, and embedded features. +The 51Degrees module sets the following fields of the device object: `devicetype`, `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio`. Interested bidder adapters may use these fields as needed. In addition, the module sets `device.ext.fiftyonedegrees_deviceId` to a permanent device ID, which can be rapidly looked up in on-premise data, exposing over 250 properties, including device age, chipset, codec support, price, operating system and app/browser versions, age, and embedded features. -The module supports on premise and cloud device detection services with free options for both. +The module supports on-premise and cloud device detection services, with free options for both. -A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/tWrhNfY6). This is the simplest approach to trial the module. +A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/HNZ75HT1). This is the simplest approach to trial the module. -An interface compatible self hosted service can be used with .NET, Java, Node, PHP, and Python. See [51Degrees examples](https://51degrees.com/documentation/_examples__device_detection__getting_started__web__on_premise.html). +An interface-compatible self-hosted service can be used with .NET, Java, Node, PHP, and Python. See [51Degrees examples](https://51degrees.com/documentation/_examples__device_detection__getting_started__web__on_premise.html). -Free cloud and on premise solutions can be expanded to support unlimited requests, additional properties, and automatic daily on premise data updates via a [subscription](https://51degrees.com/pricing). +Free cloud and on-premise solutions can be expanded to support unlimited requests, additional properties, and automatic daily on-premise data updates via a [subscription](https://51degrees.com/pricing). ## Usage @@ -27,7 +27,7 @@ Free cloud and on premise solutions can be expanded to support unlimited request Compile the 51Degrees RTD Module with other modules and adapters into your Prebid.js build: ``` -gulp build --modules="rtdModule,51DegreesRtdProvider,appnexusBidAdapter,..." +gulp build --modules=rtdModule,51DegreesRtdProvider,appnexusBidAdapter,... ``` > Note that the 51Degrees RTD module is dependent on the global real-time data module, `rtdModule`. @@ -36,14 +36,14 @@ gulp build --modules="rtdModule,51DegreesRtdProvider,appnexusBidAdapter,..." #### Resource Key -In order to use the module please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/HNZ75HT1) - choose the following properties: +In order to use the module, please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/HNZ75HT1) - choose the following properties: * DeviceId * DeviceType * HardwareVendor * HardwareName * HardwareModel -* PlatformName +* PlatformName * PlatformVersion * ScreenPixelsHeight * ScreenPixelsWidth @@ -53,13 +53,13 @@ In order to use the module please first obtain a Resource Key using the [Configu * ScreenInchesWidth * PixelRatio -The Cloud API is **free** to integrate and use. To increase limits please check [51Degrees pricing](https://51degrees.com/pricing). +The Cloud API is **free** to integrate and use. To increase limits, please check [51Degrees pricing](https://51degrees.com/pricing). #### User Agent Client Hint (UA-CH) Permissions -Some UA-CH headers are not available to third parties. To allow 51Degrees cloud service to access these headers for more accurate detection and lower latency, it is highly recommended to set `Permissions-Policy` in one of two ways: +Some UA-CH headers are not available to third parties. To allow the 51Degrees cloud service to access these headers for more accurate detection and lower latency, it is highly recommended to set `Permissions-Policy` in one of two ways: -In the HTML of the publisher's web page where Prebid.js wrapper is integrated: +In the HTML of the publisher's web page where the Prebid.js wrapper is integrated: ```html @@ -75,25 +75,25 @@ Accept-CH: sec-ch-ua-arch, sec-ch-ua-full-version, sec-ch-ua-full-version-list, See the [51Degrees documentation](https://51degrees.com/documentation/_device_detection__features__u_a_c_h__overview.html) for more information concerning UA-CH and permissions. -##### Why not use GetHighEntropyValues API instead? +##### Why not use the GetHighEntropyValues API instead? Thanks for asking. -The script this module injects has a fall back to the GetHighEntropyValues API, but does not rely on it as a first (or only) choice route - please see the illustrative cases below. Albeit it seems easier, GHEV API is not supported by all browsers (so the decision to call it should be conditional) and also even in Chrome this API will likely be a subject to the Privacy Budget in the future. +The script this module injects has a fallback to the GetHighEntropyValues API but does not rely on it as a first (or only) choice route. Please see the illustrative cases below. Although it seems easier, the GHEV API is not supported by all browsers (so the decision to call it should be conditional). Also, even in Chrome, this API will likely be subject to the Privacy Budget in the future. -In summary we recommend using `Delegate-CH` http-equiv as the preferred method of obtaining the necessary evidence because it is the fastest and future proof method. +In summary, we recommend using `Delegate-CH` http-equiv as the preferred method of obtaining the necessary evidence because it is the fastest and most future-proof method. ##### Illustrative Cases -* if the device is iPhone/iPad then there is no point checking for or calling GetHighEntropyValues at the moment because iOS does not support this API. However this might change in the future. Platforms like iOS require additional techniques to identify the model which are not covered via a single API call, and change from version to version of the operating system and browser rendering engine. **When used with iOS 51Degrees resolves the [iPhone/iPad model groups](https://51degrees.com/documentation/4.4/_device_detection__features__apple_device_table.html) using these techniques.** That is one of the benefits the module brings to the Prebid community as most solutions do not resolve iPhone/iPad model groups. More on Apple Device Detection [here](https://51degrees.com/documentation/4.4/_device_detection__features__apple_detection.html). +* If the device is iPhone/iPad, there is no point in checking for or calling GetHighEntropyValues at the moment because iOS does not support this API. However, this might change in the future. Platforms like iOS require additional techniques to identify the model, which are not covered via a single API call, and change from version to version of the operating system and browser rendering engine. **When used with iOS, 51Degrees resolves the [iPhone/iPad model groups](https://51degrees.com/documentation/4.4/_device_detection__features__apple_device_table.html) using these techniques.** That is one of the benefits the module brings to the Prebid community, as most solutions do not resolve iPhone/iPad model groups. More on Apple Device Detection [here](https://51degrees.com/documentation/4.4/_device_detection__features__apple_detection.html). -* if the browser is Firefox on Android or Desktop then there is similarly no point requesting GHEV as the API is not supported. +* If the browser is Firefox on Android or Desktop, there is similarly no point in requesting GHEV, as the API is not supported. -* if the browser is Chrome then the `Delegate-CH` if enabled by the publisher would enable the browser to provide the necessary evidence. However if this is not implemented - then the dynamic script would fall back to GHEV which is slower. +* If the browser is Chrome, the `Delegate-CH`, if enabled by the publisher, would allow the browser to provide the necessary evidence. However, if this is not implemented, then the dynamic script would fall back to GHEV, which is slower. ### Configuration -This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to at least 250 ms and make sure `waitForIt` is set to `true` for the `51Degrees` RTD provider. +This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to at least 250 ms and ensuring `waitForIt` is set to `true` for the `51Degrees` RTD provider. ```javascript pbjs.setConfig({ @@ -107,7 +107,7 @@ pbjs.setConfig({ params: { resourceKey: '', // Get your resource key from https://configure.51degrees.com/HNZ75HT1 - // alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen end point + // alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen endpoint // onPremiseJSUrl: 'https://localhost/51Degrees.core.js' }, }, @@ -118,16 +118,18 @@ pbjs.setConfig({ ### Parameters -> Note that `resourceKey` and `onPremiseJSUrl` are mutually exclusive parameters. Use strictly one of them: either a `resourceKey` for cloud integration and `onPremiseJSUrl` for the on-premise self-hosted integration. +> Note that `resourceKey` and `onPremiseJSUrl` are mutually exclusive parameters. Use strictly one of them: either a `resourceKey` for cloud integration or `onPremiseJSUrl` for the on-premise self-hosted integration. | Name | Type | Description | Default | |:----------------------|:--------|:---------------------------------------------------------------------------------------------|:-------------------| -| name | String | Real time data module name | Always '51Degrees' | +| name | String | Real-time data module name | Always '51Degrees' | | waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (mandatory) | `false` | | params | Object | | | | params.resourceKey | String | Your 51Degrees Cloud Resource Key | | | params.onPremiseJSUrl | String | Direct URL to your self-hosted on-premise JS file (e.g. https://localhost/51Degrees.core.js) | | +> Note: if you use a third-party Prebid.js wrapper, there might be a chance that the UI will force you to input both `resourceKey` and `onPremiseJSUrl`. In this case, you can set a redundant parameter to a string equal to "0", which will be ignored by the module. + ## Example > Note: you need to have a valid resource key to run the example.\ @@ -147,4 +149,14 @@ Open the browser console to see the logs. ## Customer Notices -When using the 51Degrees cloud service publishers need to reference the 51Degrees [client services privacy policy](https://51degrees.com/terms/client-services-privacy-policy) in their customer notices. \ No newline at end of file +When using the 51Degrees cloud service, publishers need to reference the 51Degrees [client services privacy policy](https://51degrees.com/terms/client-services-privacy-policy) in their customer notices. + +## Optimisation + +To reduce latency when loading the 51Degrees cloud service script, it's recommended to preconnect to the 51Degrees domain. This will establish an early connection, allowing the browser to resolve DNS, set up TCP, and perform the TLS handshake ahead of time, speeding up the script download. + +To enable `preconnect`, add the following in the `` of your HTML: + +```html + +``` diff --git a/test/spec/modules/51DegreesRtdProvider_spec.js b/test/spec/modules/51DegreesRtdProvider_spec.js index 9b634970ebb..4847587c7d3 100644 --- a/test/spec/modules/51DegreesRtdProvider_spec.js +++ b/test/spec/modules/51DegreesRtdProvider_spec.js @@ -2,11 +2,13 @@ import { extractConfig, get51DegreesJSURL, is51DegreesMetaPresent, - setOrtb2KeyIfNotEmpty, + deepSetNotEmptyValue, + convert51DegreesDataToOrtb2, convert51DegreesDeviceToOrtb2, getBidRequestData, fiftyOneDegreesSubmodule, } from 'modules/51DegreesRtdProvider'; +import {mergeDeep} from '../../../src/utils'; const inject51DegreesMeta = () => { const meta = document.createElement('meta'); @@ -16,6 +18,60 @@ const inject51DegreesMeta = () => { }; describe('51DegreesRtdProvider', function() { + const fiftyOneDegreesDevice = { + screenpixelswidth: 5120, + screenpixelsheight: 1440, + hardwarevendor: 'Apple', + hardwaremodel: 'Macintosh', + hardwarename: [ + 'Macintosh', + ], + platformname: 'macOS', + platformversion: '14.1.2', + screeninchesheight: 13.27, + screenincheswidth: 47.17, + devicetype: 'Desktop', + pixelratio: 1, + deviceid: '17595-131215-132535-18092', + }; + + const fiftyOneDegreesDeviceX2scaling = { + ...fiftyOneDegreesDevice, + screenpixelsheight: fiftyOneDegreesDevice.screenpixelsheight / 2, + screenpixelswidth: fiftyOneDegreesDevice.screenpixelswidth / 2, + screenpixelsphysicalheight: fiftyOneDegreesDevice.screenpixelsheight, + screenpixelsphysicalwidth: fiftyOneDegreesDevice.screenpixelswidth, + pixelratio: fiftyOneDegreesDevice.pixelratio * 2, + }; + + const fiftyOneDegreesData = { + device: fiftyOneDegreesDevice, + }; + + const expectedORTB2DeviceResult = { + device: { + devicetype: 2, + make: 'Apple', + model: 'Macintosh', + os: 'macOS', + osv: '14.1.2', + h: 1440, + w: 5120, + ppi: 109, + pxratio: 1, + ext: { + fiftyonedegrees_deviceId: '17595-131215-132535-18092', + }, + }, + }; + + const expectedORTB2Result = {}; + mergeDeep( + expectedORTB2Result, + expectedORTB2DeviceResult, + // placeholder for the next 51Degrees RTD submodule update + ); + describe('extractConfig', function() { it('returns the resourceKey from the moduleConfig', function() { const reqBidsConfigObj = {}; @@ -55,6 +111,52 @@ describe('51DegreesRtdProvider', function() { const moduleConfig = {params: {resourceKey: ''}}; expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); }); + + it('sets the resourceKey to undefined if it was set to "0"', function() { + const moduleConfig = {params: { + resourceKey: '0', + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + }}; + expect(extractConfig(moduleConfig, {})).to.deep.equal({ + resourceKey: undefined, + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + }); + }); + + it('sets the onPremiseJSUrl to undefined if it was set to "0"', function() { + const moduleConfig = {params: { + resourceKey: 'TEST_RESOURCE_KEY', + onPremiseJSUrl: '0', + }}; + expect(extractConfig(moduleConfig, {})).to.deep.equal({ + resourceKey: 'TEST_RESOURCE_KEY', + onPremiseJSUrl: undefined, + }); + }); + + it('throws an error if the onPremiseJSUrl is not a valid URL', function() { + expect(() => extractConfig({ + params: {onPremiseJSUrl: 'invalid URL'} + }, {})).to.throw(); + expect(() => extractConfig({ + params: {onPremiseJSUrl: 'www.example.com/51Degrees.core.js'} + }, {})).to.throw(); + }); + + it('allows the onPremiseJSUrl to be a valid URL', function() { + const VALID_URLS = [ + 'https://www.example.com/51Degrees.core.js', + 'http://example.com/51Degrees.core.js', + '//example.com/51Degrees.core.js', + '/51Degrees.core.js', + ]; + + VALID_URLS.forEach(url => { + expect(() => extractConfig({ + params: {onPremiseJSUrl: url} + }, {})).to.not.throw(); + }); + }); }); describe('get51DegreesJSURL', function() { @@ -106,61 +208,53 @@ describe('51DegreesRtdProvider', function() { }); }); - describe('setOrtb2KeyIfNotEmpty', function() { + describe('deepSetNotEmptyValue', function() { it('sets value of ORTB2 key if it is not empty', function() { const data = {}; - setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', 'TEST_ORTB2_VALUE'); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', 'TEST_ORTB2_VALUE'); expect(data).to.deep.equal({TEST_ORTB2_KEY: 'TEST_ORTB2_VALUE'}); + deepSetNotEmptyValue(data, 'test2.TEST_ORTB2_KEY_2', 'TEST_ORTB2_VALUE_2'); + expect(data).to.deep.equal({ + TEST_ORTB2_KEY: 'TEST_ORTB2_VALUE', + test2: { + TEST_ORTB2_KEY_2: 'TEST_ORTB2_VALUE_2' + }, + }); }); it('throws an error if the key is empty', function() { const data = {}; - expect(() => setOrtb2KeyIfNotEmpty(data, '', 'TEST_ORTB2_VALUE')).to.throw(); + expect(() => deepSetNotEmptyValue(data, '', 'TEST_ORTB2_VALUE')).to.throw(); }); it('does not set value of ORTB2 key if it is empty', function() { const data = {}; - setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', ''); - setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', 0); - setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', null); - setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', undefined); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', ''); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', 0); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', null); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', undefined); + deepSetNotEmptyValue(data, 'TEST.TEST_ORTB2_KEY', undefined); expect(data).to.deep.equal({}); }); }); - describe('convert51DegreesDeviceToOrtb2', function() { - const fiftyOneDegreesDevice = { - 'screenpixelswidth': 5120, - 'screenpixelsheight': 1440, - 'hardwarevendor': 'Apple', - 'hardwaremodel': 'Macintosh', - 'hardwarename': [ - 'Macintosh', - ], - 'platformname': 'macOS', - 'platformversion': '14.1.2', - 'screeninchesheight': 13.27, - 'screenincheswidth': 47.17, - 'devicetype': 'Desktop', - 'pixelratio': 1, - 'deviceid': '17595-131215-132535-18092', - }; + describe('convert51DegreesDataToOrtb2', function() { + it('returns empty object if data is null, undefined or empty', () => { + expect(convert51DegreesDataToOrtb2(null)).to.deep.equal({}); + expect(convert51DegreesDataToOrtb2(undefined)).to.deep.equal({}); + expect(convert51DegreesDataToOrtb2({})).to.deep.equal({}); + }); + it('converts all 51Degrees data to ORTB2 format', function() { + expect(convert51DegreesDataToOrtb2(fiftyOneDegreesData)).to.deep.equal(expectedORTB2Result); + }); + }); + + describe('convert51DegreesDeviceToOrtb2', function() { it('converts 51Degrees device data to ORTB2 format', function() { - expect(convert51DegreesDeviceToOrtb2(fiftyOneDegreesDevice)).to.deep.equal({ - devicetype: 2, - make: 'Apple', - model: 'Macintosh', - os: 'macOS', - osv: '14.1.2', - h: 1440, - w: 5120, - ppi: 109, - pxratio: 1, - ext: { - fiftyonedegrees_deviceId: '17595-131215-132535-18092', - }, - }); + expect( + convert51DegreesDeviceToOrtb2(fiftyOneDegreesDevice) + ).to.deep.equal(expectedORTB2DeviceResult); }); it('returns an empty object if the device data is not provided', function() { @@ -170,37 +264,57 @@ describe('51DegreesRtdProvider', function() { it('does not set the deviceid if it is not provided', function() { const device = {...fiftyOneDegreesDevice}; delete device.deviceid; - expect(convert51DegreesDeviceToOrtb2(device)).to.not.have.any.keys('ext'); + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('ext'); }); it('sets the model to hardwarename if hardwaremodel is not provided', function() { const device = {...fiftyOneDegreesDevice}; delete device.hardwaremodel; - expect(convert51DegreesDeviceToOrtb2(device)).to.deep.include({model: 'Macintosh'}); + expect(convert51DegreesDeviceToOrtb2(device).device).to.deep.include({model: 'Macintosh'}); }); it('does not set the model if hardwarename is empty', function() { const device = {...fiftyOneDegreesDevice}; delete device.hardwaremodel; device.hardwarename = []; - expect(convert51DegreesDeviceToOrtb2(device)).to.not.have.any.keys('model'); + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('model'); }); it('does not set the ppi if screeninchesheight is not provided', function() { const device = {...fiftyOneDegreesDevice}; delete device.screeninchesheight; - expect(convert51DegreesDeviceToOrtb2(device)).to.not.have.any.keys('ppi'); + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('ppi'); + }); + + it('sets correct ppi if screenpixelsphysicalheight & screeninchesheight are provided', function() { + expect(convert51DegreesDeviceToOrtb2(fiftyOneDegreesDeviceX2scaling).device).to.deep.include({ + ppi: expectedORTB2DeviceResult.device.ppi, + }); + }); + + it('if screenpixelsphysical properties are available, use them for screen size', function() { + expect(fiftyOneDegreesDevice.screenpixelswidth).to.not.equal(fiftyOneDegreesDeviceX2scaling.screenpixelswidth); + expect(fiftyOneDegreesDevice.screenpixelsheight).to.not.equal(fiftyOneDegreesDeviceX2scaling.screenpixelsheight); + expect(fiftyOneDegreesDevice.screenpixelsphysicalwidth).to.equal(undefined); + expect(fiftyOneDegreesDevice.screenpixelsphysicalheight).to.equal(undefined); + expect(convert51DegreesDeviceToOrtb2(fiftyOneDegreesDeviceX2scaling).device).to.deep.include({ + h: expectedORTB2DeviceResult.device.h, + w: expectedORTB2DeviceResult.device.w, + }); }); }); describe('getBidRequestData', function() { let initialHeadInnerHTML; - const reqBidsConfigObj = { - ortb2Fragments: { - global: { - device: {}, + let reqBidsConfigObj = {}; + const resetReqBidsConfigObj = () => { + reqBidsConfigObj = { + ortb2Fragments: { + global: { + device: {}, + }, }, - }, + }; }; before(function() { @@ -208,27 +322,15 @@ describe('51DegreesRtdProvider', function() { const mockScript = document.createElement('script'); mockScript.innerHTML = ` - const fiftyOneDegreesDevice = { - 'screenpixelswidth': 5120, - 'screenpixelsheight': 1440, - 'hardwarevendor': 'Apple', - 'hardwaremodel': 'Macintosh', - 'hardwarename': [ - 'Macintosh', - ], - 'platformname': 'macOS', - 'platformversion': '14.1.2', - 'screeninchesheight': 13.27, - 'screenincheswidth': 47.17, - 'devicetype': 'Desktop', - 'pixelratio': 1, - 'deviceid': '17595-131215-132535-18092', - }; - window.fod = {complete: (_callback) => _callback({device: fiftyOneDegreesDevice})}; + window.fod = {complete: (_callback) => _callback(${JSON.stringify(fiftyOneDegreesData)})}; `; document.head.appendChild(mockScript); }); + beforeEach(function() { + resetReqBidsConfigObj(); + }); + after(function() { document.head.innerHTML = initialHeadInnerHTML; }); @@ -266,6 +368,15 @@ describe('51DegreesRtdProvider', function() { await new Promise(resolve => setTimeout(resolve, 100)); expect(callback.calledOnce).to.be.true; }); + + it('has the correct ORTB2 data', async function() { + const callback = sinon.spy(); + const moduleConfig = {params: {resourceKey: 'INVALID_RESOURCE_KEY'}}; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + await new Promise(resolve => setTimeout(resolve, 100)); + expect(callback.calledOnce).to.be.true; + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal(expectedORTB2Result); + }); }); describe('init', function() { From 317235dbe51f707261c3ad76944b22ae84b56be6 Mon Sep 17 00:00:00 2001 From: SebRobert Date: Wed, 9 Oct 2024 16:37:48 +0200 Subject: [PATCH 0566/1097] PSEGS are now part of BPSEGS (#15) (#12282) --- modules/beopBidAdapter.js | 3 +-- test/spec/modules/beopBidAdapter_spec.js | 13 +++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 0b2a965448b..5237f3d7573 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -68,8 +68,7 @@ export const spec = { nid: firstSlot.nid, nptnid: firstSlot.nptnid, pid: firstSlot.pid, - psegs: psegs, - bpsegs: (userBpSegs.concat(siteBpSegs)).map(item => item.toString()), + bpsegs: (userBpSegs.concat(siteBpSegs, psegs)).map(item => item.toString()), url: pageUrl, lang: (window.navigator.language || window.navigator.languages[0]), kwds: keywords, diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 663d622e505..d044e71ceb7 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -132,7 +132,7 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.url).to.equal('http://test.te'); }); - it('should call the endpoint with psegs and bpsegs (stringified) data if any or [] if none', function () { + it('should call the endpoint with bpsegs (stringified) data if any or [] if none', function () { let bidderRequest = { 'ortb2': { @@ -149,15 +149,14 @@ describe('BeOp Bid Adapter tests', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); - expect(payload.psegs).to.exist; - expect(payload.psegs).to.include(1234); - expect(payload.psegs).to.include(5678); - expect(payload.psegs).to.include(910); - expect(payload.psegs).to.not.include(1); expect(payload.bpsegs).to.exist; expect(payload.bpsegs).to.include('axed'); expect(payload.bpsegs).to.include('axec'); expect(payload.bpsegs).to.include('1234'); + expect(payload.bpsegs).to.include('1234'); + expect(payload.bpsegs).to.include('5678'); + expect(payload.bpsegs).to.include('910'); + expect(payload.bpsegs).to.not.include('1'); let bidderRequest2 = { @@ -166,8 +165,6 @@ describe('BeOp Bid Adapter tests', () => { const request2 = spec.buildRequests(bidRequests, bidderRequest2); const payload2 = JSON.parse(request2.data); - expect(payload2.psegs).to.exist; - expect(payload2.psegs).to.be.empty; expect(payload2.bpsegs).to.exist; expect(payload2.bpsegs).to.be.empty; }); From 5e6f716c5c448c479e02e659c7b7a19749a82bfc Mon Sep 17 00:00:00 2001 From: Andrius Versockas Date: Wed, 9 Oct 2024 19:38:38 +0300 Subject: [PATCH 0567/1097] Eskimi Bid Adapter: support string placementId, adjust user-sync processing logic (#12286) Co-authored-by: Andrius Versockas --- modules/eskimiBidAdapter.js | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index dd3f634462d..36feda03ec1 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -3,7 +3,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; import {getBidIdParameter, logInfo, mergeDeep} from '../src/utils.js'; -import {hasPurpose1Consent} from '../src/utils/gdpr.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -47,7 +46,6 @@ const REGION_SUBDOMAIN_SUFFIX = { export const spec = { code: BIDDER_CODE, - aliases: ['eskimi'], gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, @@ -120,7 +118,7 @@ function isBidRequestValid(bidRequest) { } function isPlacementIdValid(bidRequest) { - return utils.isNumber(bidRequest.params.placementId); + return !!parseInt(bidRequest.params.placementId); } function isValidBannerRequest(bidRequest) { @@ -215,7 +213,7 @@ function createRequest(bidRequests, bidderRequest, mediaType) { const bid = bidRequests.find((b) => b.params.placementId) if (!data.site) data.site = {} - data.site.ext = {placementId: bid.params.placementId} + data.site.ext = {placementId: parseInt(bid.params.placementId)} if (bidderRequest.gdprConsent) { if (!data.user) data.user = {}; @@ -255,46 +253,31 @@ function isBannerBid(bid) { * @return {{type: (string), url: (*|string)}[]} */ function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent, gppConsent) { - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && hasSyncConsent(gdprConsent, uspConsent, gppConsent)) { + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let query = []; let syncUrl = getUserSyncUrlByRegion(); - // Attaching GDPR Consent Params in UserSync url + // GDPR Consent Params in UserSync url if (gdprConsent) { query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); } - // CCPA + // US Privacy Consent if (uspConsent) { query.push('us_privacy=' + encodeURIComponent(uspConsent)); } - // GPP Consent + // Global Privacy Platform Consent if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { query.push('gpp=' + encodeURIComponent(gppConsent.gppString)); query.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); } return [{ type: pixelType, - url: `${syncUrl}${query.length > 0 ? '?' + query.join('&') : ''}` + url: `${syncUrl}${query.length > 0 ? '&' + query.join('&') : ''}` }]; } } -function hasSyncConsent(gdprConsent, uspConsent, gppConsent) { - return hasPurpose1Consent(gdprConsent) && hasUspConsent(uspConsent) && hasGppConsent(gppConsent); -} - -function hasUspConsent(uspConsent) { - return typeof uspConsent !== 'string' || !(uspConsent[0] === '1' && uspConsent[2] === 'Y'); -} - -function hasGppConsent(gppConsent) { - return ( - !(gppConsent && Array.isArray(gppConsent.applicableSections)) || - gppConsent.applicableSections.every((section) => typeof section === 'number' && section <= 5) - ); -} - /** * Get Bid Request endpoint url by region * @return {string} From 42f5cb383103e140b302c1e39a0c462c05d887af Mon Sep 17 00:00:00 2001 From: bretg Date: Wed, 9 Oct 2024 15:45:36 -0400 Subject: [PATCH 0568/1097] Fix JS Doc lint errors in rubiconBidAdapter (#12309) Fixes the Rubicon portion of https://github.com/prebid/Prebid.js/issues/12171 --- modules/rubiconBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 0a27a1116a4..16239b6d1c3 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1187,7 +1187,7 @@ export function determineRubiconVideoSizeId(bid) { } /** - * @param {PrebidConfig} config + * @param {Object} config * @returns {{ranges: {ranges: Object[]}}} */ export function getPriceGranularity(config) { @@ -1236,7 +1236,6 @@ export function hasValidVideoParams(bid) { /** * Make sure the required params are present * @param {Object} schain - * @param {boolean} */ export function hasValidSupplyChainParams(schain) { let isValid = false; From 0471cf6c0a8ef775e835e25936331aa0eddf22d1 Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Wed, 9 Oct 2024 15:07:23 -0700 Subject: [PATCH 0569/1097] Prebid-Server adapter: remove openx PBS (#12310) --- modules/prebidServerBidAdapter/config.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index a1bad2d69ba..4a5ac1d8564 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -26,19 +26,6 @@ export const S2S_VENDORS = { }, maxTimeout: 500 }, - 'openx': { - adapter: 'prebidServer', - enabled: true, - endpoint: { - p1Consent: 'https://prebid.openx.net/openrtb2/auction', - noP1Consent: 'https://prebid.openx.net/openrtb2/auction' - }, - syncEndpoint: { - p1Consent: 'https://prebid.openx.net/cookie_sync', - noP1Consent: 'https://prebid.openx.net/cookie_sync' - }, - maxTimeout: 1000 - }, 'openwrap': { adapter: 'prebidServer', enabled: true, From 1f963266796727186476e2bf4c98b15935b1f977 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Thu, 10 Oct 2024 16:46:51 +0100 Subject: [PATCH 0570/1097] Teads Bid Adapter: Add ORTB2 device data to request payload (#12054) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/teadsBidAdapter.js | 1 + test/spec/modules/teadsBidAdapter_spec.js | 27 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 73ea5dae575..d7b9ef9be2b 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -62,6 +62,7 @@ export const spec = { networkBandwidth: getConnectionDownLink(window.navigator), timeToFirstByte: getTimeToFirstByte(window), data: bids, + device: bidderRequest?.ortb2?.device || {}, deviceWidth: screen.width, deviceHeight: screen.height, devicePixelRatio: topWindow.devicePixelRatio, diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 8ccfdd44649..71ed9a21efb 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -378,6 +378,33 @@ describe('teadsBidAdapter', () => { expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); }); + it('should add ortb2 device data to payload', function () { + const ortb2DeviceBidderRequest = { + ...bidderRequestDefault, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }, + }, + }, + }; + const request = spec.buildRequests(bidRequests, ortb2DeviceBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); + }); + it('should add hardwareConcurrency info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); From aff6daf1e0802785fd79df8da2af93a24f535d05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:47:56 -0600 Subject: [PATCH 0571/1097] Bump cookie, express and socket.io (#12316) Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.2 and updates ancestor dependencies [cookie](https://github.com/jshttp/cookie), [express](https://github.com/expressjs/express) and [socket.io](https://github.com/socketio/socket.io). These dependencies need to be updated together. Updates `cookie` from 0.6.0 to 0.7.2 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.2) Updates `express` from 4.21.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1) Updates `socket.io` from 4.7.5 to 4.8.0 - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/socket.io@4.7.5...socket.io@4.8.0) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: express dependency-type: direct:production - dependency-name: socket.io dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 85 +++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7370a7f8896..334ecf746f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8434,9 +8434,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -9947,18 +9947,17 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dev": true, - "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -9969,18 +9968,18 @@ } }, "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "engines": { "node": ">=10.0.0" } }, "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "engines": { "node": ">= 0.6" @@ -11338,16 +11337,16 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -23852,16 +23851,16 @@ } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dev": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -34179,9 +34178,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -35327,9 +35326,9 @@ } }, "engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -35337,7 +35336,7 @@ "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -35345,17 +35344,17 @@ }, "dependencies": { "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true } } }, "engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true }, "enhanced-resolve": { @@ -36415,16 +36414,16 @@ } }, "express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -46027,16 +46026,16 @@ } }, "socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } From 7651c77ecaa7077bb2d59b80aee92361f3d74fe8 Mon Sep 17 00:00:00 2001 From: mp4symitri Date: Thu, 10 Oct 2024 21:20:07 +0530 Subject: [PATCH 0572/1097] SymitriDap RTD Module : segment taxonomy values changed to Symitri registered values (#12311) * Segment Taxonomy values changed to Symitri Registered Values * Fixed test for akamaiDapRtdProvider which is extended from symitriDapRtdProvider --------- Co-authored-by: Manan --- modules/symitriDapRtdProvider.js | 8 ++--- modules/symitriDapRtdProvider.md | 4 +-- .../spec/modules/akamaiDapRtdProvider_spec.js | 32 +++++++++---------- .../modules/symitriDapRtdProvider_spec.js | 32 +++++++++---------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index aeb42226470..1cdef460d9b 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -114,7 +114,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { dapRetryTokenize = 0; var jsonData = null; if (rtdConfig && isPlainObject(rtdConfig.params)) { - if (rtdConfig.params.segtax == 504) { + if (rtdConfig.params.segtax == 710) { let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); if (encMembership) { jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) @@ -191,7 +191,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { const ortb2 = bidConfig.ortb2Fragments.global; 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 + if (config.segtax == 710) { // Follow the encrypted membership path dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone) // Get the encrypted membership from server refreshMembership = false; } else { @@ -248,7 +248,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); } if (refreshMembership) { - if (config.segtax == 504) { + if (config.segtax == 710) { dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone); } else { dapUtils.dapRefreshMembership(ortb2, config, token, onDone); @@ -449,7 +449,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { checkAndAddRealtimeData: function(ortb2, data, segtax) { if (data.rtd) { - if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504)) { + if (segtax == 710 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 710)) { logMessage('DEBUG(handleInit): rtb Object already added'); } else { addRealTimeData(ortb2, data.rtd); diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index a8b5753465c..e3429e24144 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -47,7 +47,7 @@ pbjs.setConfig({ domain: 'your-domain.com', identityType: 'simpleid'|'compositeid'|'hashedid'|'dap-signature:1.0.0', identityValue: '', - segtax: 501, + segtax: 708, dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', dapEntropyTimeout: 1500, pixelUrl: '', @@ -80,7 +80,7 @@ Use 'hashedid' to pass in single already hashed id. Use 'compositeid' to pass in } | | identityValue | String | This is optional field to pass user hid. Will be used only if identityType is hid | | -| segtax | Integer | The taxonomy for Symitri | The value should be 501 | +| segtax | Integer | The taxonomy for Symitri | The value should be 708 | | dapEntropyUrl | String | URL to dap entropy script | Optional if the script is directly included on the webpage. Contact your Symitri account rep for more details | | dapEntropyTimeout | Integer | Maximum time allotted for the entropy calculation to happen | | | pixelUrl | String | Pixel URL provided by Symitri which will be triggered when bid matching with Symitri dealid wins and creative gets rendered | | diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index 337fcf57a33..0787f911591 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -44,7 +44,7 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503 + 'segtax': 708 } } @@ -56,7 +56,7 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 504 + 'segtax': 710 } } @@ -64,7 +64,7 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503, + 'segtax': 708, 'identity': sampleIdentity } @@ -72,7 +72,7 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 504, + 'segtax': 710, 'identity': sampleIdentity } let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds @@ -97,7 +97,7 @@ describe('akamaiDapRtdProvider', function() { const encRtdUserObj = { name: 'www.dataprovider3.com', ext: { - segtax: 504, + segtax: 710, taxonomyname: 'iab_audience_taxonomy' }, segment: [] @@ -262,13 +262,13 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', - 'segtax': 503 + 'segtax': 708 }; const encConfig = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', - 'segtax': 504 + 'segtax': 710 }; let identity = { type: 'dap-signature:1.0.0' @@ -396,7 +396,7 @@ describe('akamaiDapRtdProvider', function() { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - segtax: 503 + segtax: 708 }; expect(dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} @@ -405,11 +405,11 @@ describe('akamaiDapRtdProvider', function() { }); describe('checkAndAddRealtimeData test', function () { - it('add realtime data for segtax 503 and 504', function () { - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); + it('add realtime data for segtax 708 and 710', function () { + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); - dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503); + dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 708); expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); }); }); @@ -466,7 +466,7 @@ describe('akamaiDapRtdProvider', function() { let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) expect(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); }); @@ -477,7 +477,7 @@ describe('akamaiDapRtdProvider', function() { let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) expect(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); }); @@ -508,7 +508,7 @@ describe('akamaiDapRtdProvider', function() { dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503); + let rtdObj = dapUtils.dapGetRtdObj(membership, 708); expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); }); @@ -517,7 +517,7 @@ describe('akamaiDapRtdProvider', function() { dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + let rtdObj = dapUtils.dapGetRtdObj(membership, 708) expect(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); }); diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 8289d02710a..ec3ba4fdbed 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -49,7 +49,7 @@ describe('symitriDapRtdProvider', function() { 'apiAuthToken': 'Token 1234', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503 + 'segtax': 708 } } @@ -61,7 +61,7 @@ describe('symitriDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 504, + 'segtax': 710, 'pixelUrl': 'https://www.test.com/pixel' } } @@ -70,7 +70,7 @@ describe('symitriDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503, + 'segtax': 708, 'identity': sampleIdentity } @@ -78,7 +78,7 @@ describe('symitriDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 504, + 'segtax': 710, 'identity': sampleIdentity } let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds @@ -104,7 +104,7 @@ describe('symitriDapRtdProvider', function() { const encRtdUserObj = { name: 'www.dataprovider3.com', ext: { - segtax: 504, + segtax: 710, taxonomyname: 'iab_audience_taxonomy' }, segment: [] @@ -269,13 +269,13 @@ describe('symitriDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', - 'segtax': 503 + 'segtax': 708 }; const encConfig = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', - 'segtax': 504 + 'segtax': 710 }; let identity = { type: 'dap-signature:1.0.0' @@ -403,7 +403,7 @@ describe('symitriDapRtdProvider', function() { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - segtax: 503 + segtax: 708 }; expect(dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} @@ -412,11 +412,11 @@ describe('symitriDapRtdProvider', function() { }); describe('checkAndAddRealtimeData test', function () { - it('add realtime data for segtax 503 and 504', function () { - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); + it('add realtime data for segtax 708 and 710', function () { + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); - dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503); + dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 708); expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); }); }); @@ -473,7 +473,7 @@ describe('symitriDapRtdProvider', function() { let request = server.requests[0]; responseHeader['Symitri-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) expect(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); }); @@ -484,7 +484,7 @@ describe('symitriDapRtdProvider', function() { let request = server.requests[0]; responseHeader['Symitri-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) expect(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); }); @@ -515,7 +515,7 @@ describe('symitriDapRtdProvider', function() { dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503); + let rtdObj = dapUtils.dapGetRtdObj(membership, 708); expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); }); @@ -524,7 +524,7 @@ describe('symitriDapRtdProvider', function() { dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + let rtdObj = dapUtils.dapGetRtdObj(membership, 708) expect(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); }); From 4bc6b8f173f4b818b054b436e58bc8660ce4ba8c Mon Sep 17 00:00:00 2001 From: ehb-mtk <163182361+ehb-mtk@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:07:27 -0400 Subject: [PATCH 0573/1097] Mobian RTD module: add ap values to mobian RTD provider (#12289) * add ap values to mobian RTD provider * add ext object if it doesn't already exist * add test for case of missing ext --- modules/mobianRtdProvider.js | 17 ++-- test/spec/modules/mobianRtdProvider_spec.js | 106 ++++++++++++++++++-- 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 8f08b21e14a..3b9d2632246 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -48,6 +48,7 @@ function getBidRequestData(bidReqConfig, callback, config) { const themes = results.mobianThemes || []; const tones = results.mobianTones || []; const genres = results.mobianGenres || []; + const apValues = results.ap || {}; const risk = { risk: mobianRisk, @@ -57,15 +58,17 @@ function getBidRequestData(bidReqConfig, callback, config) { themes: themes, tones: tones, genres: genres, + apValues: apValues, }; - deepSetValue(ortb2Site.ext, 'data.mobianRisk', mobianRisk); - deepSetValue(ortb2Site.ext, 'data.mobianContentCategories', contentCategories); - deepSetValue(ortb2Site.ext, 'data.mobianSentiment', sentiment); - deepSetValue(ortb2Site.ext, 'data.mobianEmotions', emotions); - deepSetValue(ortb2Site.ext, 'data.mobianThemes', themes); - deepSetValue(ortb2Site.ext, 'data.mobianTones', tones); - deepSetValue(ortb2Site.ext, 'data.mobianGenres', genres); + deepSetValue(ortb2Site, 'ext.data.mobianRisk', mobianRisk); + deepSetValue(ortb2Site, 'ext.data.mobianContentCategories', contentCategories); + deepSetValue(ortb2Site, 'ext.data.mobianSentiment', sentiment); + deepSetValue(ortb2Site, 'ext.data.mobianEmotions', emotions); + deepSetValue(ortb2Site, 'ext.data.mobianThemes', themes); + deepSetValue(ortb2Site, 'ext.data.mobianTones', tones); + deepSetValue(ortb2Site, 'ext.data.mobianGenres', genres); + deepSetValue(ortb2Site, 'ext.data.apValues', apValues); resolve(risk); callback(); diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index cbe04b9f893..088e5559a17 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -39,7 +39,8 @@ describe('Mobian RTD Submodule', function () { mobianEmotions: ['joy'], mobianThemes: [], mobianTones: [], - mobianGenres: [] + mobianGenres: [], + ap: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] } } })); }); @@ -52,7 +53,8 @@ describe('Mobian RTD Submodule', function () { emotions: ['joy'], themes: [], tones: [], - genres: [] + genres: [], + apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] } }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'low', @@ -61,12 +63,13 @@ describe('Mobian RTD Submodule', function () { mobianEmotions: ['joy'], mobianThemes: [], mobianTones: [], - mobianGenres: [] + mobianGenres: [], + apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] } }); }); }); - it('should handle response with content categories and multiple emotions', function () { + it('should handle response with content categories, multiple emotions, and ap values', function () { ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { callbacks.success(JSON.stringify({ meta: { @@ -80,7 +83,8 @@ describe('Mobian RTD Submodule', function () { mobianEmotions: ['anger', 'fear'], mobianThemes: ['conflict', 'international relations'], mobianTones: ['factual', 'serious'], - mobianGenres: ['news', 'political_analysis'] + mobianGenres: ['news', 'political_analysis'], + ap: { a0: [100], a1: [200, 300], p0: [400, 500], p1: [600] } } })); }); @@ -93,7 +97,8 @@ describe('Mobian RTD Submodule', function () { emotions: ['anger', 'fear'], themes: ['conflict', 'international relations'], tones: ['factual', 'serious'], - genres: ['news', 'political_analysis'] + genres: ['news', 'political_analysis'], + apValues: { a0: [100], a1: [200, 300], p0: [400, 500], p1: [600] } }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'medium', @@ -102,7 +107,8 @@ describe('Mobian RTD Submodule', function () { mobianEmotions: ['anger', 'fear'], mobianThemes: ['conflict', 'international relations'], mobianTones: ['factual', 'serious'], - mobianGenres: ['news', 'political_analysis'] + mobianGenres: ['news', 'political_analysis'], + apValues: { a0: [100], a1: [200, 300], p0: [400, 500], p1: [600] } }); }); }); @@ -121,7 +127,7 @@ describe('Mobian RTD Submodule', function () { return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { expect(result).to.deep.equal({}); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.not.have.any.keys( - 'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions', 'mobianThemes', 'mobianTones', 'mobianGenres' + 'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions', 'mobianThemes', 'mobianTones', 'mobianGenres', 'apValues' ); }); }); @@ -171,6 +177,7 @@ describe('Mobian RTD Submodule', function () { themes: [], tones: [], genres: [], + apValues: {} }); expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ mobianRisk: 'high', @@ -179,7 +186,88 @@ describe('Mobian RTD Submodule', function () { mobianEmotions: [], mobianThemes: [], mobianTones: [], - mobianGenres: [] + mobianGenres: [], + apValues: {} + }); + }); + }); + + it('should handle response with only ap values', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + ap: { a0: [1, 2], a1: [3, 4], p0: [5, 6], p1: [7, 8] } + } + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ + risk: 'unknown', + contentCategories: [], + sentiment: 'unknown', + emotions: [], + themes: [], + tones: [], + genres: [], + apValues: { a0: [1, 2], a1: [3, 4], p0: [5, 6], p1: [7, 8] } + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'unknown', + mobianContentCategories: [], + mobianSentiment: 'unknown', + mobianEmotions: [], + mobianThemes: [], + mobianTones: [], + mobianGenres: [], + apValues: { a0: [1, 2], a1: [3, 4], p0: [5, 6], p1: [7, 8] } + }); + }); + }); + + it('should set key-value pairs when ext object is missing', function () { + bidReqConfig = { + ortb2Fragments: { + global: { + site: {} + } + } + }; + + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'low', + mobianSentiment: 'positive', + mobianContentCategories: [], + mobianEmotions: ['joy'], + mobianThemes: [], + mobianTones: [], + mobianGenres: [], + ap: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] } + } + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, () => {}, {}).then(() => { + expect(bidReqConfig.ortb2Fragments.global.site.ext).to.exist; + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'low', + mobianContentCategories: [], + mobianSentiment: 'positive', + mobianEmotions: ['joy'], + mobianThemes: [], + mobianTones: [], + mobianGenres: [], + apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] } }); }); }); From 169638edf1f57b410215e53239546d82e4979c42 Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Thu, 10 Oct 2024 19:33:37 +0300 Subject: [PATCH 0574/1097] Missena Bid Adapter : send schain & uspConsent (#12296) * Missena bidAdapter send schain & us_privacy * Add tests for new payload fields --- modules/missenaBidAdapter.js | 116 +++++++++++--------- test/spec/modules/missenaBidAdapter_spec.js | 15 +++ 2 files changed, 80 insertions(+), 51 deletions(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 99cad1c7bc6..bba8f988be6 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -14,9 +14,11 @@ import { getStorageManager } from '../src/storageManager.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid */ const BIDDER_CODE = 'missena'; @@ -43,6 +45,62 @@ function getFloor(bidRequest) { } } +/* Helper function that converts the prebid data to the payload expected by our servers */ +function toPayload(bidRequest, bidderRequest) { + const payload = { + adunit: bidRequest.adUnitCode, + ik: window.msna_ik, + request_id: bidRequest.bidId, + timeout: bidderRequest.timeout, + }; + + if (bidderRequest && bidderRequest.refererInfo) { + // TODO: is 'topmostLocation' the right value here? + payload.referer = bidderRequest.refererInfo.topmostLocation; + payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.consent_string = bidderRequest.gdprConsent.consentString; + payload.consent_required = bidderRequest.gdprConsent.gdprApplies; + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; + if (bidRequest.params.test) { + payload.test = bidRequest.params.test; + } + if (bidRequest.params.placement) { + payload.placement = bidRequest.params.placement; + } + if (bidRequest.params.formats) { + payload.formats = bidRequest.params.formats; + } + if (bidRequest.params.isInternal) { + payload.is_internal = bidRequest.params.isInternal; + } + if (bidRequest.ortb2?.device?.ext?.cdep) { + payload.cdep = bidRequest.ortb2?.device?.ext?.cdep; + } + payload.userEids = bidRequest.userIdAsEids || []; + payload.version = '$prebid.version$'; + + const bidFloor = getFloor(bidRequest); + payload.floor = bidFloor?.floor; + payload.floor_currency = bidFloor?.currency; + payload.currency = config.getConfig('currency.adServerCurrency') || 'EUR'; + payload.schain = bidRequest.schain; + + return { + method: 'POST', + url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), + data: JSON.stringify(payload), + }; +} + export const spec = { aliases: ['msna'], code: BIDDER_CODE, @@ -62,7 +120,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {Array} validBidRequests + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { @@ -78,54 +137,9 @@ export const spec = { return []; } - return validBidRequests.map((bidRequest) => { - const payload = { - adunit: bidRequest.adUnitCode, - ik: window.msna_ik, - request_id: bidRequest.bidId, - timeout: bidderRequest.timeout, - }; - - if (bidderRequest && bidderRequest.refererInfo) { - // TODO: is 'topmostLocation' the right value here? - payload.referer = bidderRequest.refererInfo.topmostLocation; - payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - payload.consent_string = bidderRequest.gdprConsent.consentString; - payload.consent_required = bidderRequest.gdprConsent.gdprApplies; - } - const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; - if (bidRequest.params.test) { - payload.test = bidRequest.params.test; - } - if (bidRequest.params.placement) { - payload.placement = bidRequest.params.placement; - } - if (bidRequest.params.formats) { - payload.formats = bidRequest.params.formats; - } - if (bidRequest.params.isInternal) { - payload.is_internal = bidRequest.params.isInternal; - } - if (bidRequest.ortb2?.device?.ext?.cdep) { - payload.cdep = bidRequest.ortb2?.device?.ext?.cdep; - } - payload.userEids = bidRequest.userIdAsEids || []; - payload.version = '$prebid.version$'; - - const bidFloor = getFloor(bidRequest); - payload.floor = bidFloor?.floor; - payload.floor_currency = bidFloor?.currency; - payload.currency = config.getConfig('currency.adServerCurrency') || 'EUR'; - - return { - method: 'POST', - url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), - data: JSON.stringify(payload), - }; - }); + return validBidRequests.map((bidRequest) => + toPayload(bidRequest, bidderRequest), + ); }, /** @@ -170,7 +184,7 @@ export const spec = { }, /** * Register bidder specific code, which will execute if bidder timed out after an auction - * @param {data} Containing timeout specific data + * @param {TimedOutBid} timeoutData - Containing timeout specific data */ onTimeout: function onTimeout(timeoutData) { logInfo('Missena - Timeout from adapter', timeoutData); @@ -178,7 +192,7 @@ export const spec = { /** * Register bidder specific code, which@ will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction + * @param {Bid} bid - The bid that won the auction */ onBidWon: function (bid) { const hostname = bid.params[0].baseUrl ? EVENTS_DOMAIN_DEV : EVENTS_DOMAIN; diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index ab1fbdcc074..2b05a1f0830 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -29,6 +29,12 @@ describe('Missena Adapter', function () { placement: 'sticky', formats: ['sticky-banner'], }, + schain: { + validation: 'strict', + config: { + ver: '1.0', + }, + }, getFloor: (inputParams) => { if (inputParams.mediaType === BANNER) { return { @@ -58,6 +64,7 @@ describe('Missena Adapter', function () { consentString: consentString, gdprApplies: true, }, + uspConsent: 'IDO', refererInfo: { topmostLocation: REFERRER, canonicalUrl: 'https://canonical', @@ -100,6 +107,14 @@ describe('Missena Adapter', function () { const payload = JSON.parse(request.data); const payloadNoFloor = JSON.parse(requests[1].data); + it('should contain uspConsent', function () { + expect(payload.us_privacy).to.equal('IDO'); + }); + + it('should contain schain', function () { + expect(payload.schain.config.ver).to.equal('1.0'); + }); + it('should return as many server requests as bidder requests', function () { expect(requests.length).to.equal(2); }); From 9965e0e654e55d2cfdb13721c41717b49352362b Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 10 Oct 2024 18:56:05 +0200 Subject: [PATCH 0575/1097] Core: New activity control - load external script (#12207) * #11010 New activity control - load external script * improvements * adding module type to load external script * update loadScript stub * Swap moduleType/moduleCode for consistency --------- Co-authored-by: Marcin Komorski Co-authored-by: Marcin Komorski Co-authored-by: Demetrio Girardi --- modules/51DegreesRtdProvider.js | 3 +- modules/a1MediaRtdProvider.js | 2 +- modules/aaxBlockmeterRtdProvider.js | 3 +- modules/adagioRtdProvider.js | 5 ++- modules/adlooxAnalyticsAdapter.js | 3 +- modules/airgridRtdProvider.js | 2 +- modules/arcspanRtdProvider.js | 3 +- modules/azerionedgeRtdProvider.js | 2 +- modules/brandmetricsRtdProvider.js | 3 +- modules/browsiRtdProvider.js | 2 +- modules/cleanioRtdProvider.js | 3 +- modules/confiantRtdProvider.js | 4 ++- modules/contxtfulRtdProvider.js | 2 +- modules/dynamicAdBoostRtdProvider.js | 3 +- modules/ftrackIdSystem.js | 2 +- modules/geoedgeRtdProvider.js | 5 +-- modules/hadronRtdProvider.js | 2 +- modules/id5IdSystem.js | 2 +- modules/improvedigitalBidAdapter.js | 3 +- modules/justIdSystem.js | 3 +- modules/mediafilterRtdProvider.js | 4 ++- modules/medianetRtdProvider.js | 3 +- modules/qortexRtdProvider.js | 3 +- modules/showheroes-bsBidAdapter.js | 3 +- modules/symitriDapRtdProvider.js | 4 ++- modules/tncIdSystem.js | 3 +- modules/wurflRtdProvider.js | 3 +- src/Renderer.js | 3 +- src/activities/activities.js | 5 +++ src/adloader.js | 14 ++++++-- src/debugging.js | 3 +- test/mocks/adloaderStub.js | 4 +-- test/spec/adloader_spec.js | 32 +++++++++++++------ .../modules/adlooxAnalyticsAdapter_spec.js | 2 +- test/spec/modules/cleanioRtdProvider_spec.js | 5 +-- test/spec/modules/justIdSystem_spec.js | 2 +- test/spec/modules/wurflRtdProvider_spec.js | 2 +- 37 files changed, 102 insertions(+), 50 deletions(-) diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js index 123eeeaddc3..573c79902c6 100644 --- a/modules/51DegreesRtdProvider.js +++ b/modules/51DegreesRtdProvider.js @@ -1,3 +1,4 @@ +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; import {loadExternalScript} from '../src/adloader.js'; import {submodule} from '../src/hook.js'; import { @@ -263,7 +264,7 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user } // Inject 51Degrees script, get device data and merge it into the ORTB2 object - loadExternalScript(scriptURL, MODULE_NAME, () => { + loadExternalScript(scriptURL, MODULE_TYPE_RTD, MODULE_NAME, () => { logMessage('Successfully injected 51Degrees script'); const fod = /** @type {Object} */ (window.fod); // Convert and merge device data in the callback diff --git a/modules/a1MediaRtdProvider.js b/modules/a1MediaRtdProvider.js index 445ed47181d..1fbe88ecfa0 100644 --- a/modules/a1MediaRtdProvider.js +++ b/modules/a1MediaRtdProvider.js @@ -39,7 +39,7 @@ function loadLbScript(tagname) { linkback.l = true; const scriptUrl = `${SCRIPT_URL}/${tagname}`; - loadExternalScript(scriptUrl, MODULE_NAME); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, MODULE_NAME); } } diff --git a/modules/aaxBlockmeterRtdProvider.js b/modules/aaxBlockmeterRtdProvider.js index a3b7b4812a7..0a72e4e36f1 100644 --- a/modules/aaxBlockmeterRtdProvider.js +++ b/modules/aaxBlockmeterRtdProvider.js @@ -1,6 +1,7 @@ import {isEmptyStr, isStr, logError, isFn, logWarn} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; export const _config = { MODULE: 'aaxBlockmeter', @@ -28,7 +29,7 @@ function loadBlockmeter(_rtdConfig) { } const scriptUrl = `https://${url}&${params.join('&')}`; - loadExternalScript(scriptUrl, _config.MODULE); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, _config.MODULE); return true; } diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index 91f446e68fd..7098406be0d 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -211,7 +211,10 @@ function loadAdagioScript(config) { return; } - loadExternalScript(SCRIPT_URL, SUBMODULE_NAME, undefined, undefined, { id: `adagiojs-${getUniqueIdentifierStr()}`, 'data-pid': config.params.organizationId }); + loadExternalScript(SCRIPT_URL, MODULE_TYPE_RTD, SUBMODULE_NAME, undefined, undefined, { + id: `adagiojs-${getUniqueIdentifierStr()}`, + 'data-pid': config.params.organizationId + }); }); } diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index f18c9e72337..0a953584e26 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -28,6 +28,7 @@ import { parseUrl } from '../src/utils.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; const MODULE = 'adlooxAnalyticsAdapter'; @@ -262,7 +263,7 @@ analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { [ 'creatype', '%%creatype%%' ] ]); - loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); + loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), MODULE_TYPE_ANALYTICS, 'adloox'); } adapterManager.registerAnalyticsAdapter({ diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 079628c88fc..300744d62fe 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -43,7 +43,7 @@ export function attachScriptTagToDOM(rtdConfig) { edktInitializor.apiKey = rtdConfig.params.apiKey; edktInitializor.invoked = true; const moduleSrc = getModuleUrl(rtdConfig.params.accountId); - loadExternalScript(moduleSrc, SUBMODULE_NAME); + loadExternalScript(moduleSrc, MODULE_TYPE_RTD, SUBMODULE_NAME); } } diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js index 8ccf3d160b9..3a9e9b175d8 100644 --- a/modules/arcspanRtdProvider.js +++ b/modules/arcspanRtdProvider.js @@ -1,6 +1,7 @@ import { submodule } from '../src/hook.js'; import { mergeDeep } from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -28,7 +29,7 @@ function init(config, userConsent) { } else { scriptUrl = 'https://silo' + config.params.silo + '.p7cloud.net/as.js'; } - loadExternalScript(scriptUrl, SUBMODULE_NAME); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME); } return true; } diff --git a/modules/azerionedgeRtdProvider.js b/modules/azerionedgeRtdProvider.js index 852639972c2..7e8a32e6f93 100644 --- a/modules/azerionedgeRtdProvider.js +++ b/modules/azerionedgeRtdProvider.js @@ -50,7 +50,7 @@ function getScriptURL(config) { */ export function attachScript(config, userConsent) { const script = getScriptURL(config); - loadExternalScript(script, SUBREAL_TIME_MODULE, () => { + loadExternalScript(script, MODULE_TYPE_RTD, SUBREAL_TIME_MODULE, () => { if (typeof window.azerionPublisherAudiences === 'function') { const publisherConfig = config.params?.process || {}; window.azerionPublisherAudiences({ diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index b463a5480f8..7502a579745 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -10,6 +10,7 @@ import { deepAccess, deepSetValue, logError, mergeDeep, generateUUID } from '../ import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -140,7 +141,7 @@ function initializeBrandmetrics(scriptId) { const file = scriptId + '.js' const url = path + file - loadExternalScript(url, MODULE_CODE) + loadExternalScript(url, MODULE_TYPE_RTD, MODULE_CODE) } } diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 46393b37a05..8f5fea80ffa 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -52,7 +52,7 @@ let _ic = {}; * @param {Object} data */ export function addBrowsiTag(data) { - let script = loadExternalScript(data.u, 'browsi'); + let script = loadExternalScript(data.u, MODULE_TYPE_RTD, 'browsi'); script.async = true; script.setAttribute('data-sitekey', _moduleParams.siteKey); script.setAttribute('data-pubkey', _moduleParams.pubKey); diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index 1a5e64de3cc..35751210878 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -11,6 +11,7 @@ import { loadExternalScript } from '../src/adloader.js'; import { logError, generateUUID, insertElement } from '../src/utils.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -58,7 +59,7 @@ function pageInitStepPreloadScript(scriptURL) { * @param {string} scriptURL The script URL to add to the page for protection */ function pageInitStepProtectPage(scriptURL) { - loadExternalScript(scriptURL, 'clean.io'); + loadExternalScript(scriptURL, MODULE_TYPE_RTD, 'clean.io'); } /** diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js index 4c5475421bb..7aee63472c9 100644 --- a/modules/confiantRtdProvider.js +++ b/modules/confiantRtdProvider.js @@ -13,6 +13,7 @@ import { logError, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * Injects the Confiant Inc. configuration script into the page, based on proprtyId provided @@ -21,7 +22,8 @@ import { EVENTS } from '../src/constants.js'; function injectConfigScript(propertyId) { const scriptSrc = `https://cdn.confiant-integrations.net/${propertyId}/gpt_and_prebid/config.js`; - loadExternalScript(scriptSrc, 'confiant', () => {}); + loadExternalScript(scriptSrc, MODULE_TYPE_RTD, 'confiant', () => { + }); } /** diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index d4bcd94ff4a..a0d11328427 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -147,7 +147,7 @@ function initCustomer(config) { }); addConnectorEventListener(customer, config); - loadExternalScript(CONNECTOR_URL, MODULE_NAME); + loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME); } /** diff --git a/modules/dynamicAdBoostRtdProvider.js b/modules/dynamicAdBoostRtdProvider.js index 697bd7340d3..a68567b1ca3 100644 --- a/modules/dynamicAdBoostRtdProvider.js +++ b/modules/dynamicAdBoostRtdProvider.js @@ -8,6 +8,7 @@ import { submodule } from '../src/hook.js' import { loadExternalScript } from '../src/adloader.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { deepAccess, deepSetValue, isEmptyStr } from '../src/utils.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -72,7 +73,7 @@ function loadLmScript(keyId) { let viewableAdUnits = Object.keys(dynamicAdBoostAdUnits); let viewableAdUnitsCSV = viewableAdUnits.join(','); const scriptUrl = `${SCRIPT_URL}/${keyId}.js?viewableAdUnits=${viewableAdUnitsCSV}`; - loadExternalScript(scriptUrl, MODULE_NAME); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, MODULE_NAME); observer.disconnect(); } diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 1794c3f76f4..fa6c7d4050c 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -142,7 +142,7 @@ export const ftrackIdSubmodule = { } // Creates an async script element and appends it to the document - loadExternalScript(config.params.url, MODULE_NAME); + loadExternalScript(config.params.url, MODULE_TYPE_UID, MODULE_NAME); } }; }, diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 0291ff12ad2..09e717a112f 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -23,6 +23,7 @@ import { EVENTS } from '../src/constants.js'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -108,7 +109,7 @@ export function preloadClient(key) { insertElement(iframe); iframe.contentWindow.grumi = getInitialParams(key); let url = getClientUrl(key); - loadExternalScript(url, SUBMODULE_NAME, markAsLoaded, iframe.contentDocument); + loadExternalScript(url, MODULE_TYPE_RTD, SUBMODULE_NAME, markAsLoaded, iframe.contentDocument); } /** @@ -259,7 +260,7 @@ function fireBillableEventsForApplicableBids(params) { function setupInPage(params) { window.grumi = params; window.grumi.fromPrebid = true; - loadExternalScript(getInPageUrl(params.key), SUBMODULE_NAME); + loadExternalScript(getInPageUrl(params.key), MODULE_TYPE_RTD, SUBMODULE_NAME); } function init(config, userConsent) { diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 930e0922829..f9a2eaed9c9 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -184,7 +184,7 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds), `partner_id=${partnerId}&_it=prebid` ); - loadExternalScript(scriptUrl, 'hadron', () => { + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, 'hadron', () => { logInfo(LOG_PREFIX, 'hadronIdTag loaded', scriptUrl); }) } diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index d08efdcb232..65e9be46a56 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -467,7 +467,7 @@ async function loadExternalModule(url) { resolve(); } else { try { - loadExternalScript(url, 'id5', resolve); + loadExternalScript(url, MODULE_TYPE_UID, 'id5', resolve); } catch (error) { reject(error); } diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index ddf13caa833..f189a3f4ae4 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -12,6 +12,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; */ // eslint-disable-next-line no-restricted-imports import {loadExternalScript} from '../src/adloader.js'; +import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -396,7 +397,7 @@ const ID_RAZR = { ns.q.push(data); if (!ns.loaded) { - loadExternalScript(ID_RAZR.RENDERER_URL, BIDDER_CODE); + loadExternalScript(ID_RAZR.RENDERER_URL, MODULE_TYPE_BIDDER, BIDDER_CODE); } }); diff --git a/modules/justIdSystem.js b/modules/justIdSystem.js index a5698023020..e4f1828c573 100644 --- a/modules/justIdSystem.js +++ b/modules/justIdSystem.js @@ -9,6 +9,7 @@ import * as utils from '../src/utils.js' import { submodule } from '../src/hook.js' import { loadExternalScript } from '../src/adloader.js' import {includes} from '../src/polyfill.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -154,7 +155,7 @@ const CombinedUidProvider = function(configWrapper, consentData, cacheIdObj) { const url = configWrapper.getUrl(); this.getUid = function(idCallback, errCallback) { - const scriptTag = loadExternalScript(url, EXTERNAL_SCRIPT_MODULE_CODE, () => { + const scriptTag = loadExternalScript(url, MODULE_TYPE_UID, EXTERNAL_SCRIPT_MODULE_CODE, () => { utils.logInfo(LOG_PREFIX, 'script loaded', url); const eventDetails = { diff --git a/modules/mediafilterRtdProvider.js b/modules/mediafilterRtdProvider.js index fae5c9e769b..5472c4a6ce0 100644 --- a/modules/mediafilterRtdProvider.js +++ b/modules/mediafilterRtdProvider.js @@ -15,6 +15,7 @@ import { logError, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** The event type for Media Filter. */ export const MEDIAFILTER_EVENT_TYPE = 'com.mediatrust.pbjs.'; @@ -54,7 +55,8 @@ export const MediaFilter = { * @param {string} configurationHash - The configuration hash. */ setupScript: function(configurationHash) { - loadExternalScript(MEDIAFILTER_BASE_URL.concat(configurationHash), 'mediafilter', () => {}); + loadExternalScript(MEDIAFILTER_BASE_URL.concat(configurationHash), MODULE_TYPE_RTD, 'mediafilter', () => { + }); }, /** diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index 5a159b39081..42fcffbf576 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -3,6 +3,7 @@ import {loadExternalScript} from '../src/adloader.js'; import {submodule} from '../src/hook.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {includes} from '../src/polyfill.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; const MODULE_NAME = 'medianet'; const SOURCE = MODULE_NAME + 'rtd'; @@ -84,7 +85,7 @@ function executeCommand(command) { function loadRtdScript(customerId) { const url = getClientUrl(customerId, window.location.hostname); - loadExternalScript(url, MODULE_NAME) + loadExternalScript(url, MODULE_TYPE_RTD, MODULE_NAME) } function getAdUnits(adUnits, adUnitCodes) { diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js index 88b4339b38e..aaf9b983c78 100644 --- a/modules/qortexRtdProvider.js +++ b/modules/qortexRtdProvider.js @@ -4,6 +4,7 @@ import { logWarn, mergeDeep, logMessage, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; let requestUrl; let bidderArray; @@ -136,7 +137,7 @@ export function loadScriptTag(config) { } }) - loadExternalScript(src, code, undefined, undefined, attr); + loadExternalScript(src, MODULE_TYPE_RTD, code, undefined, undefined, attr); } /** diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index 062e567a1c1..026f89c5faa 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -16,6 +16,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; */ // eslint-disable-next-line no-restricted-imports import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; const STAGE_ENDPOINT = 'https://bid-service.stage.showheroes.com/api/v1/bid'; @@ -338,7 +339,7 @@ function createOutstreamEmbedCode(bid) { const fragment = window.document.createDocumentFragment(); - let script = loadExternalScript(urls.pubTag, 'showheroes-bs', function () { + let script = loadExternalScript(urls.pubTag, MODULE_TYPE_BIDDER, 'showheroes-bs', function () { window.ShowheroesTag = this; }); script.setAttribute('data-player-host', urls.vlHost); diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 1cdef460d9b..7bf523170fe 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -86,7 +86,9 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { window.dapCalculateEntropy(resolve, reject); } else { if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { - loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_TYPE_RTD, MODULE_CODE, () => { + dapUtils.dapGetEntropy(resolve, reject) + }); } else { reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); } diff --git a/modules/tncIdSystem.js b/modules/tncIdSystem.js index 59254a9430f..c05e0ce4713 100644 --- a/modules/tncIdSystem.js +++ b/modules/tncIdSystem.js @@ -1,6 +1,7 @@ import { submodule } from '../src/hook.js'; import { logInfo } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const MODULE_NAME = 'tncId'; let url = null; @@ -20,7 +21,7 @@ const waitTNCScript = (tncNS) => { const loadRemoteScript = () => { return new Promise((resolve) => { - loadExternalScript(url, MODULE_NAME, resolve); + loadExternalScript(url, MODULE_TYPE_UID, MODULE_NAME, resolve); }) } diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js index 94bb8c6440a..f019d2dbe52 100644 --- a/modules/wurflRtdProvider.js +++ b/modules/wurflRtdProvider.js @@ -5,6 +5,7 @@ import { mergeDeep, prefixLog, } from '../src/utils.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; // Constants const REAL_TIME_MODULE = 'realTimeData'; @@ -67,7 +68,7 @@ const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => { logger.logMessage('url', url.toString()); try { - loadExternalScript(url.toString(), MODULE_NAME, () => { + loadExternalScript(url.toString(), MODULE_TYPE_RTD, MODULE_NAME, () => { logger.logMessage('script injected'); window.WURFLPromises.complete.then((res) => { logger.logMessage('received data', res); diff --git a/src/Renderer.js b/src/Renderer.js index 2f9b2e025cb..912259206c4 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -4,6 +4,7 @@ import { } from './utils.js'; import {find} from './polyfill.js'; import {getGlobal} from './prebidGlobal.js'; +import { MODULE_TYPE_PREBID } from './activities/modules.js'; const pbjsInstance = getGlobal(); const moduleCode = 'outstream'; @@ -62,7 +63,7 @@ export function Renderer(options) { } else { // we expect to load a renderer url once only so cache the request to load script this.cmd.unshift(runRender) // should render run first ? - loadExternalScript(url, moduleCode, this.callback, this.documentContext); + loadExternalScript(url, MODULE_TYPE_PREBID, moduleCode, this.callback, this.documentContext); } }.bind(this); // bind the function to this object to avoid 'this' errors } diff --git a/src/activities/activities.js b/src/activities/activities.js index 0a17750b0b0..40f43fb9114 100644 --- a/src/activities/activities.js +++ b/src/activities/activities.js @@ -50,3 +50,8 @@ export const ACTIVITY_TRANSMIT_PRECISE_GEO = 'transmitPreciseGeo'; * transmit TID: some component wants access ot (and send along) transaction IDs */ export const ACTIVITY_TRANSMIT_TID = 'transmitTid'; + +/** + * loadExternalScript: adLoader.js is allowed to load external script + */ +export const LOAD_EXTERNAL_SCRIPT = 'loadExternalScript'; diff --git a/src/adloader.js b/src/adloader.js index 5e71b89f284..bf695dd627b 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,5 +1,8 @@ -import {includes} from './polyfill.js'; -import { logError, logWarn, insertElement, setScriptAttributes } from './utils.js'; +import { LOAD_EXTERNAL_SCRIPT } from './activities/activities.js'; +import { activityParams } from './activities/activityParams.js'; +import { isActivityAllowed } from './activities/rules.js'; +import { includes } from './polyfill.js'; +import { insertElement, logError, logWarn, setScriptAttributes } from './utils.js'; const _requestCache = new WeakMap(); // The below list contains modules or vendors whom Prebid allows to load external JS. @@ -45,12 +48,17 @@ const _approvedLoadExternalJSList = [ * Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy * Each unique URL will be loaded at most 1 time. * @param {string} url the url to load + * @param {string} moduleType moduleType of the module requesting this resource * @param {string} moduleCode bidderCode or module code of the module requesting this resource * @param {function} [callback] callback function to be called after the script is loaded * @param {Document} [doc] the context document, in which the script will be loaded, defaults to loaded document * @param {object} attributes an object of attributes to be added to the script with setAttribute by [key] and [value]; Only the attributes passed in the first request of a url will be added. */ -export function loadExternalScript(url, moduleCode, callback, doc, attributes) { +export function loadExternalScript(url, moduleType, moduleCode, callback, doc, attributes) { + if (!isActivityAllowed(LOAD_EXTERNAL_SCRIPT, activityParams(moduleType, moduleCode))) { + return; + } + if (!moduleCode || !url) { logError('cannot load external script without url and moduleCode'); return; diff --git a/src/debugging.js b/src/debugging.js index 045855ccb28..69c129da9a2 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -5,6 +5,7 @@ import {logMessage, prefixLog} from './utils.js'; import {createBid} from './bidfactory.js'; import {loadExternalScript} from './adloader.js'; import {GreedyPromise} from './utils/promise.js'; +import { MODULE_TYPE_PREBID } from './activities/modules.js'; export const DEBUG_KEY = '__$$PREBID_GLOBAL$$_debugging__'; @@ -14,7 +15,7 @@ function isDebuggingInstalled() { function loadScript(url) { return new GreedyPromise((resolve) => { - loadExternalScript(url, 'debugging', resolve); + loadExternalScript(url, MODULE_TYPE_PREBID, 'debugging', resolve); }); } diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js index 6e7f5756100..24f5781dfc4 100644 --- a/test/mocks/adloaderStub.js +++ b/test/mocks/adloaderStub.js @@ -8,9 +8,7 @@ export let loadExternalScriptStub = createStub(); function createStub() { return sinon.stub(adloader, 'loadExternalScript').callsFake((...args) => { - if (typeof args[2] === 'function') { - args[2](); - } else if (typeof args[3] === 'function') { + if (typeof args[3] === 'function') { args[3](); } return document.createElement('script'); diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index fcc388c2d3b..900358a766b 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -1,5 +1,8 @@ import * as utils from 'src/utils.js'; import * as adLoader from 'test/mocks/adloaderStub.js'; +import { LOAD_EXTERNAL_SCRIPT } from '../../src/activities/activities'; +import { registerActivityControl } from '../../src/activities/rules'; +import { MODULE_TYPE_PREBID } from '../../src/activities/modules'; describe('adLoader', function () { let utilsinsertElementStub; @@ -23,19 +26,19 @@ describe('adLoader', function () { }); it('only allows whitelisted vendors to load scripts', function () { - adLoader.loadExternalScript('someURL', 'debugging'); + adLoader.loadExternalScript('someURL', MODULE_TYPE_PREBID, 'debugging'); expect(utilsLogErrorStub.called).to.be.false; expect(utilsinsertElementStub.called).to.be.true; }); it('should not load cached script again', function() { - adLoader.loadExternalScript('someURL', 'debugging'); + adLoader.loadExternalScript('someURL', 'debugging', MODULE_TYPE_PREBID); expect(utilsinsertElementStub.called).to.be.false; }); it('callback function can be passed to the function', function() { let callback = function() {}; - adLoader.loadExternalScript('someURL1', 'debugging', callback); + adLoader.loadExternalScript('someURL1', MODULE_TYPE_PREBID, 'debugging', callback); expect(utilsinsertElementStub.called).to.be.true; }); @@ -61,11 +64,11 @@ describe('adLoader', function () { } const doc1 = getDocSpec(); const doc2 = getDocSpec(); - adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc1); - adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc1); - adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc1); - adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc2); - adLoader.loadExternalScript('someURL', 'debugging', () => {}, doc2); + adLoader.loadExternalScript('someURL', MODULE_TYPE_PREBID, 'debugging', () => {}, doc1); + adLoader.loadExternalScript('someURL', MODULE_TYPE_PREBID, 'debugging', () => {}, doc1); + adLoader.loadExternalScript('someURL', MODULE_TYPE_PREBID, 'debugging', () => {}, doc1); + adLoader.loadExternalScript('someURL', MODULE_TYPE_PREBID, 'debugging', () => {}, doc2); + adLoader.loadExternalScript('someURL', MODULE_TYPE_PREBID, 'debugging', () => {}, doc2); expect(utilsinsertElementStub.callCount).to.equal(2); }); }); @@ -88,8 +91,19 @@ describe('adLoader', function () { } }, attrs = {'z': 'A', 'y': 2}; - let script = adLoader.loadExternalScript('someUrl', 'debugging', undefined, doc, attrs); + let script = adLoader.loadExternalScript('someUrl', MODULE_TYPE_PREBID, 'debugging', undefined, doc, attrs); expect(script.z).to.equal('A'); expect(script.y).to.equal(2); }); + + it('should disable loading external script for activity rule set', function () { + let unregisterRule; + try { + unregisterRule = registerActivityControl(LOAD_EXTERNAL_SCRIPT, 'loadExternalScript config', () => ({allow: false})); + adLoader.loadExternalScript(null, 'debugging'); + expect(utilsLogErrorStub.called).to.be.false; + } finally { + unregisterRule?.(); + } + }) }); diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index 450dd83f86d..fa8204a9dc5 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -169,7 +169,7 @@ describe('Adloox Analytics Adapter', function () { events.emit(EVENTS.BID_WON, bid); - const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; + const [urlInserted, _, moduleCode] = loadExternalScriptStub.getCall(0).args; expect(urlInserted.substr(0, url.length)).to.equal(url); expect(moduleCode).to.equal(analyticsAdapterName); diff --git a/test/spec/modules/cleanioRtdProvider_spec.js b/test/spec/modules/cleanioRtdProvider_spec.js index 3145108c373..0211a9ae588 100644 --- a/test/spec/modules/cleanioRtdProvider_spec.js +++ b/test/spec/modules/cleanioRtdProvider_spec.js @@ -5,6 +5,7 @@ import * as events from '../../../src/events.js'; import { EVENTS } from '../../../src/constants.js'; import { __TEST__ } from '../../../modules/cleanioRtdProvider.js'; +import {MODULE_TYPE_RTD} from '../../../src/activities/modules.js'; const { readConfig, @@ -70,7 +71,7 @@ describe('clean.io RTD module', function () { pageInitStepProtectPage(fakeScriptURL); sinon.assert.calledOnce(loadExternalScriptStub); - sinon.assert.calledWith(loadExternalScriptStub, fakeScriptURL, 'clean.io'); + sinon.assert.calledWith(loadExternalScriptStub, fakeScriptURL, MODULE_TYPE_RTD, 'clean.io'); }); }); @@ -139,7 +140,7 @@ describe('clean.io RTD module', function () { const { init, onBidResponseEvent } = getModule(); expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } }, {})).to.equal(true); sinon.assert.calledOnce(loadExternalScriptStub); - sinon.assert.calledWith(loadExternalScriptStub, 'https://abc1234567890.cloudfront.net/script.js', 'clean.io'); + sinon.assert.calledWith(loadExternalScriptStub, 'https://abc1234567890.cloudfront.net/script.js', MODULE_TYPE_RTD, 'clean.io'); const fakeBidResponse = makeFakeBidResponse(); onBidResponseEvent(fakeBidResponse, {}, {}); diff --git a/test/spec/modules/justIdSystem_spec.js b/test/spec/modules/justIdSystem_spec.js index b6a8cd2d310..95d964d807c 100644 --- a/test/spec/modules/justIdSystem_spec.js +++ b/test/spec/modules/justIdSystem_spec.js @@ -143,7 +143,7 @@ describe('JustIdSystem', function () { var scriptTagCallback; beforeEach(() => { - loadExternalScriptStub.callsFake((url, moduleCode, callback) => { + loadExternalScriptStub.callsFake((url, moduleCode, moduleType, callback) => { scriptTagCallback = callback; return scriptTag; }); diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js index 5b1cc5b751f..434abfc4e22 100644 --- a/test/spec/modules/wurflRtdProvider_spec.js +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -164,7 +164,7 @@ describe('wurflRtdProvider', function () { expect(loadExternalScriptStub.calledOnce).to.be.true; const loadExternalScriptCall = loadExternalScriptStub.getCall(0); expect(loadExternalScriptCall.args[0]).to.equal(expectedURL.toString()); - expect(loadExternalScriptCall.args[1]).to.equal('wurfl'); + expect(loadExternalScriptCall.args[2]).to.equal('wurfl'); }); it('onAuctionEndEvent: should send analytics data using navigator.sendBeacon, if available', () => { From b301a328d904b95420ec2ee64b43f74cbb6517ce Mon Sep 17 00:00:00 2001 From: Filipe Neves Date: Thu, 10 Oct 2024 21:35:20 +0200 Subject: [PATCH 0576/1097] Updated bid adapter to log errors (#12300) --- modules/impactifyBidAdapter.js | 16 +++++++++++++++- modules/impactifyBidAdapter.md | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index ea446bd150d..5d78f8eff6c 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -21,6 +21,7 @@ const DEFAULT_VIDEO_WIDTH = 640; const DEFAULT_VIDEO_HEIGHT = 360; const ORIGIN = 'https://sonic.impactify.media'; const LOGGER_URI = 'https://logger.impactify.media'; +const LOGGER_JS_URI = 'https://log.impactify.it' const AUCTION_URI = '/bidder'; const COOKIE_SYNC_URI = '/static/cookie_sync.html'; const GVL_ID = 606; @@ -389,6 +390,19 @@ export const spec = { }); return true; - } + }, + + /** + * Register bidder specific code, which will execute if the bid request failed + * @param {*} param0 + */ + onBidderError: function ({ error, bidderRequest }) { + ajax(`${LOGGER_JS_URI}/logger`, null, JSON.stringify({ error, bidderRequest }), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + }, }; registerBidder(spec); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md index de3373395dc..e08f2f3ce01 100644 --- a/modules/impactifyBidAdapter.md +++ b/modules/impactifyBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Impactify Bidder Adapter Module Type: Bidder Adapter -Maintainer: thomas.destefano@impactify.io +Maintainer: programmatic@impactify.io ``` # Description From 73da00fa2ce4abc9508eed668780ed3277fca3d8 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Thu, 10 Oct 2024 19:19:59 -0600 Subject: [PATCH 0577/1097] Humansecurity Rtd Provider : update activity control for loadExternalScript (#12318) * Update humansecurityRtdProvider.js * fix lint * fix tests --- modules/humansecurityRtdProvider.js | 3 ++- test/spec/modules/humansecurityRtdProvider_spec.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/humansecurityRtdProvider.js b/modules/humansecurityRtdProvider.js index 7d1dc1a33c1..aeb872beb8d 100644 --- a/modules/humansecurityRtdProvider.js +++ b/modules/humansecurityRtdProvider.js @@ -17,6 +17,7 @@ import { import { getRefererInfo } from '../src/refererDetection.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -91,7 +92,7 @@ function load(config) { const scriptAttrs = { 'data-sid': sessionId }; const scriptUrl = `${SCRIPT_URL}?r=${refDomain}${clientId ? `&c=${clientId}` : ''}`; - loadExternalScript(scriptUrl, SUBMODULE_NAME, onImplLoaded, null, scriptAttrs); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, onImplLoaded, null, scriptAttrs); } /** diff --git a/test/spec/modules/humansecurityRtdProvider_spec.js b/test/spec/modules/humansecurityRtdProvider_spec.js index 8a0438c306f..627c3a6c1e4 100644 --- a/test/spec/modules/humansecurityRtdProvider_spec.js +++ b/test/spec/modules/humansecurityRtdProvider_spec.js @@ -68,10 +68,10 @@ describe('humansecurity RTD module', function () { const args = loadExternalScriptStub.getCall(0).args; expect(args[0]).to.be.equal(`${SCRIPT_URL}?r=example.com`); - expect(args[1]).to.be.equal(SUBMODULE_NAME); - expect(args[2]).to.be.equal(onImplLoaded); - expect(args[3]).to.be.equal(null); - expect(args[4]).to.be.deep.equal({ 'data-sid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }); + expect(args[2]).to.be.equal(SUBMODULE_NAME); + expect(args[3]).to.be.equal(onImplLoaded); + expect(args[4]).to.be.equal(null); + expect(args[5]).to.be.deep.equal({ 'data-sid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }); }); it('should insert external script element with "customerId" info from config', () => { From 953a7ac10d5af21da313c8218ff102b63c2140be Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Fri, 11 Oct 2024 15:34:31 +0100 Subject: [PATCH 0578/1097] GumGum Bid Adapter : add ORTB2 device data to request payload (#12008) * GumGum Bid Adapter: Add ORTB2 device data to request payload * GumGum Bid Adapter: add `ip` and `ipv6` to request payload --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/gumgumBidAdapter.js | 42 +++++++++++++++++++++- test/spec/modules/gumgumBidAdapter_spec.js | 34 ++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 7f48a562177..dbfdbef2e91 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -247,6 +247,41 @@ function _getFloor(mediaTypes, staticBidFloor, bid) { return bidFloor; } +/** + * Retrieves the device data from the ORTB2 object + * @param {Object} ortb2Data ORTB2 object + * @returns {Object} Device data + */ +function _getDeviceData(ortb2Data) { + const _device = deepAccess(ortb2Data, 'device') || {}; + + // set device data params from ortb2 + const _deviceRequestParams = { + ip: _device.ip, + ipv6: _device.ipv6, + ua: _device.ua, + dnt: _device.dnt, + os: _device.os, + osv: _device.osv, + dt: _device.devicetype, + lang: _device.language, + make: _device.make, + model: _device.model, + ppi: _device.ppi, + pxratio: _device.pxratio, + foddid: _device?.ext?.fiftyonedegrees_deviceId, + }; + + // return device data params with only non-empty values + return Object.keys(_deviceRequestParams) + .reduce((r, key) => { + if (_deviceRequestParams[key] !== undefined) { + r[key] = _deviceRequestParams[key]; + } + return r; + }, {}); +} + /** * loops through bannerSizes array to get greatest slot dimensions * @param {number[][]} sizes @@ -437,6 +472,11 @@ function buildRequests(validBidRequests, bidderRequest) { if (schain && schain.nodes) { data.schain = _serializeSupplyChainObj(schain); } + Object.assign( + data, + _getBrowserParams(topWindowUrl, mosttopLocation), + _getDeviceData(bidderRequest?.ortb2), + ); bids.push({ id: bidId, @@ -447,7 +487,7 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl, mosttopLocation)) + data }); }); return bids; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index e6fa6d468ca..8e7a80281ab 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -785,6 +785,40 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.pu.includes('ggad')).to.be.false; expect(bidRequest.data.pu.includes('ggdeal')).to.be.false; }); + + it('should handle ORTB2 device data', function () { + const ortb2 = { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + ip: '127.0.0.1', + ipv6: '51dc:5e20:fd6a:c955:66be:03b4:dfa3:35b2', + }, + }; + + const bidRequest = spec.buildRequests(bidRequests, { ortb2 })[0]; + + expect(bidRequest.data.dnt).to.equal(ortb2.device.dnt); + expect(bidRequest.data.ua).to.equal(ortb2.device.ua); + expect(bidRequest.data.lang).to.equal(ortb2.device.language); + expect(bidRequest.data.dt).to.equal(ortb2.device.devicetype); + expect(bidRequest.data.make).to.equal(ortb2.device.make); + expect(bidRequest.data.model).to.equal(ortb2.device.model); + expect(bidRequest.data.os).to.equal(ortb2.device.os); + expect(bidRequest.data.osv).to.equal(ortb2.device.osv); + expect(bidRequest.data.foddid).to.equal(ortb2.device.ext.fiftyonedegrees_deviceId); + expect(bidRequest.data.ip).to.equal(ortb2.device.ip); + expect(bidRequest.data.ipv6).to.equal(ortb2.device.ipv6); + }); }) describe('interpretResponse', function () { From d013fbba81df7253a46e533c3e93e2540deefc0f Mon Sep 17 00:00:00 2001 From: AdsInteractive Date: Fri, 11 Oct 2024 18:30:17 +0300 Subject: [PATCH 0579/1097] Ads Interactive: fix main domain (#12319) * add new adapter ads_interactive * fix domain --- modules/ads_interactiveBidAdapter.js | 2 +- test/spec/modules/ads_interactiveBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ads_interactiveBidAdapter.js b/modules/ads_interactiveBidAdapter.js index c2234992f32..d21f8bcbee6 100644 --- a/modules/ads_interactiveBidAdapter.js +++ b/modules/ads_interactiveBidAdapter.js @@ -3,7 +3,7 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'ads_interactive'; -const AD_URL = 'https://bntb.adsintreactive.com/pbjs'; +const AD_URL = 'https://bntb.adsinteractive.com/pbjs'; const SYNC_URL = 'https://cstb.adsinteractive.com'; export const spec = { diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js index f33e24fd25d..ade03a9c49b 100644 --- a/test/spec/modules/ads_interactiveBidAdapter_spec.js +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -128,7 +128,7 @@ describe('AdsInteractiveBidAdapter', function () { }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://bntb.adsintreactive.com/pbjs'); + expect(serverRequest.url).to.equal('https://bntb.adsinteractive.com/pbjs'); }); it('Returns general data valid', function () { From dbc638e219d566429b9347f03d7e0baef4a1ca1d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 11 Oct 2024 15:46:55 +0000 Subject: [PATCH 0580/1097] Prebid 9.16.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 334ecf746f1..2b5012e98f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.16.0-pre", + "version": "9.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.16.0-pre", + "version": "9.16.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 5ab0a6ae5ab..d1bf50f0578 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.16.0-pre", + "version": "9.16.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From afa7c9b978e4646f04408aaa70a8d1fcf96aee71 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 11 Oct 2024 15:46:55 +0000 Subject: [PATCH 0581/1097] Increment version to 9.17.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b5012e98f6..0091ce17f24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.16.0", + "version": "9.17.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.16.0", + "version": "9.17.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index d1bf50f0578..47ec8b7f81b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.16.0", + "version": "9.17.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 176660fd2b29683be2a16b6663d861a0a0d18aea Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Fri, 11 Oct 2024 21:13:48 +0300 Subject: [PATCH 0582/1097] Add excoBidAdapter and corresponding tests (#12317) Introduce the excoBidAdapter for handling bid requests and responses, including support for banner and video media types. Implement unit tests to validate the functionality and ensure proper integration with the prebid framework. --- modules/excoBidAdapter.js | 37 ++ modules/excoBidAdapter.md | 35 ++ test/spec/modules/excoBidAdapter_spec.js | 655 +++++++++++++++++++++++ 3 files changed, 727 insertions(+) create mode 100644 modules/excoBidAdapter.js create mode 100644 modules/excoBidAdapter.md create mode 100644 test/spec/modules/excoBidAdapter_spec.js diff --git a/modules/excoBidAdapter.js b/modules/excoBidAdapter.js new file mode 100644 index 00000000000..150d467a08a --- /dev/null +++ b/modules/excoBidAdapter.js @@ -0,0 +1,37 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { + isBidRequestValid, createUserSyncGetter, createInterpretResponseFn, createBuildRequestsFn +} from '../libraries/vidazooUtils/bidderUtils.js'; + +const DEFAULT_SUB_DOMAIN = 'rtb'; +const BIDDER_CODE = 'exco'; +const BIDDER_VERSION = '1.0.0'; +const GVLID = 444; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.exco-pb.com`; +} + +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); + +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); + +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://cs.exco-pb.com/api/sync/iframe', imageSyncUrl: 'https://cs.exco-pb.com/api/sync/image' +}); + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/excoBidAdapter.md b/modules/excoBidAdapter.md new file mode 100644 index 00000000000..f171de73883 --- /dev/null +++ b/modules/excoBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** Exco Bid Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** Itadmin@ex.co + +# Description + +Module that connects to Exco's demand sources. + +# Test Parameters + ```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'exco', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/excoBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js new file mode 100644 index 00000000000..39844f0bc6a --- /dev/null +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -0,0 +1,655 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + storage +} from 'modules/excoBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from '../../../libraries/vidazooUtils/bidderUtils.js'; + +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'rtb'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + }, + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7], + 'coppa': 0 + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['exco.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('ExcoBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + exco: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000, + enableTIDs: true + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + prebidVersion: version, + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '0123456789', + cat: [], + contentData: [], + contentLang: 'en', + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000, + enableTIDs: true + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + contentLang: 'en', + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://cs.exco-pb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['exco.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['exco.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['exco.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cId': '1'}); + const pid = extractPID({'pId': '2'}); + const subDomain = extractSubDomain({'subDomain': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + exco: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(storage, key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(storage, key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(storage, key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + exco: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem(storage, 'myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From 4cc7d25a6ed778ac63eff1888a776da16a2b6d6f Mon Sep 17 00:00:00 2001 From: Pranav Sheth <57259342+pranavsheth@users.noreply.github.com> Date: Fri, 11 Oct 2024 23:46:41 +0530 Subject: [PATCH 0583/1097] New Bidder:tapnative (#12322) * New Bidder:tapnative * removed duplicate --- libraries/audUtils/bidderUtils.js | 105 +++++- modules/tapnativeBidAdapter.js | 41 +++ modules/tapnativeBidAdapter.md | 61 ++++ test/spec/modules/tapnativeBidAdapter_spec.js | 322 ++++++++++++++++++ 4 files changed, 526 insertions(+), 3 deletions(-) create mode 100644 modules/tapnativeBidAdapter.js create mode 100644 modules/tapnativeBidAdapter.md create mode 100644 test/spec/modules/tapnativeBidAdapter_spec.js diff --git a/libraries/audUtils/bidderUtils.js b/libraries/audUtils/bidderUtils.js index 6f4f2bdc6e0..0d3392ec951 100644 --- a/libraries/audUtils/bidderUtils.js +++ b/libraries/audUtils/bidderUtils.js @@ -5,6 +5,15 @@ import { logError } from '../../src/utils.js'; +// Declare native assets +const NATIVE_ASSETS = [ + { id: 1, required: 1, title: { len: 100 } }, // Title + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } }, // Main image + { id: 3, required: 0, data: { type: 1, len: 140 } }, // Body + { id: 4, required: 1, data: { type: 2 } }, // Sponsored by + { id: 5, required: 1, icon: { w: 50, h: 50 } }, // Icon + { id: 6, required: 1, data: { type: 12, len: 15 } } // Call to action +]; // Function to get Request export const getBannerRequest = (bidRequests, bidderRequest, ENDPOINT) => { let request = []; @@ -34,6 +43,7 @@ export const getBannerRequest = (bidRequests, bidderRequest, ENDPOINT) => { if (bidderRequest?.uspConsent) { deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + req.MediaType = getMediaType(bidReq); request.push(req); }); // Return the array of request @@ -48,6 +58,15 @@ export const getBannerRequest = (bidRequests, bidderRequest, ENDPOINT) => { } // Function to get Response export const getBannerResponse = (bidResponse, mediaType) => { + return formatResponse(bidResponse, mediaType); +} +// Function to get NATIVE Response +export const getNativeResponse = (bidResponse, bidRequest, mediaType) => { + const assets = JSON.parse(JSON.parse(bidRequest.data)[0].imp[0].native.request).assets; + return formatResponse(bidResponse, mediaType, assets); +} +// Function to format response +const formatResponse = (bidResponse, mediaType, assets) => { let responseArray = []; if (bidResponse) { try { @@ -61,7 +80,9 @@ export const getBannerResponse = (bidResponse, mediaType) => { response.height = bidReq.h; response.ad = bidReq.adm; response.meta = { - advertiserDomains: bidReq.adomain + advertiserDomains: bidReq.adomain, + primaryCatId: bidReq.cat || [], + attr: bidReq.attr || [] }; response.creativeId = bidReq.crid; response.netRevenue = false; @@ -69,6 +90,18 @@ export const getBannerResponse = (bidResponse, mediaType) => { response.ttl = 300; response.dealId = bidReq.dealId; response.mediaType = mediaType; + if (mediaType == 'native') { + let nativeResp = JSON.parse(bidReq.adm).native; + let nativeData = { + clickUrl: nativeResp.link.url, + impressionTrackers: nativeResp.imptrackers + }; + nativeResp.assets.forEach(asst => { + let data = getNativeAssestData(asst, assets); + nativeData[data.key] = data.value; + }); + response.native = nativeData; + } responseArray.push(response); }); } @@ -78,13 +111,18 @@ export const getBannerResponse = (bidResponse, mediaType) => { } return responseArray; } -// Function to get imp +// Function to get imp based on Media Type const getImpDetails = (bidReq) => { let imp = {}; if (bidReq) { imp.id = bidReq.bidId; imp.bidfloor = getFloorPrice(bidReq); - imp.banner = getBannerDetails(bidReq); + if (bidReq.mediaTypes.native) { + let assets = { assets: NATIVE_ASSETS }; + imp.native = { request: JSON.stringify(assets) }; + } else if (bidReq.mediaTypes.banner) { + imp.banner = getBannerDetails(bidReq); + } } return imp; } @@ -137,3 +175,64 @@ const getUserDetails = (bidReq) => { } return user; } +// Function to get asset data for response +const getNativeAssestData = (params, assets) => { + let response = {}; + if (params.title) { + response.key = 'title'; + response.value = params.title.text; + } + if (params.data) { + response.key = getAssetData(params.id, assets); + response.value = params.data.value; + } + if (params.img) { + response.key = getAssetImageDataType(params.id, assets); + response.value = { + url: params.img.url, + height: params.img.h, + width: params.img.w + } + } + return response; +} +// Function to get asset data types based on id +const getAssetData = (paramId, asset) => { + let resp = ''; + for (let i = 0; i < asset.length; i++) { + if (asset[i].id == paramId) { + switch (asset[i].data.type) { + case 1 : resp = 'sponsored'; + break; + case 2 : resp = 'desc'; + break; + case 12 : resp = 'cta'; + break; + } + } + } + return resp; +} +// Function to get image type based on the id +const getAssetImageDataType = (paramId, asset) => { + let resp = ''; + for (let i = 0; i < asset.length; i++) { + if (asset[i].id == paramId) { + switch (asset[i].img.type) { + case 1 : resp = 'icon'; + break; + case 3 : resp = 'image'; + break; + } + } + } + return resp; +} +// Function to get Media Type +const getMediaType = (bidReq) => { + if (bidReq.mediaTypes.native) { + return 'native'; + } else if (bidReq.mediaTypes.banner) { + return 'banner'; + } +} diff --git a/modules/tapnativeBidAdapter.js b/modules/tapnativeBidAdapter.js new file mode 100644 index 00000000000..a09c746c960 --- /dev/null +++ b/modules/tapnativeBidAdapter.js @@ -0,0 +1,41 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, + getNativeResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const ENDPOINT = 'https://rtb-east.tapnative.com/hb'; +// Export const spec +export const spec = { + code: 'tapnative', + supportedMediaTypes: [BANNER, NATIVE], + // Determines whether or not the given bid request is valid + isBidRequestValid: function(bidParam) { + return !!(bidParam.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: function(bidRequests, serverRequest) { + // Get Requests based on media types + return getBannerRequest(bidRequests, serverRequest, ENDPOINT); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: function(serverResponse, serverRequest) { + let bidderResponse = {}; + const mType = JSON.parse(serverRequest.data)[0].MediaType; + if (mType == BANNER) { + bidderResponse = getBannerResponse(serverResponse, BANNER); + } else if (mType == NATIVE) { + bidderResponse = getNativeResponse(serverResponse, serverRequest, NATIVE); + } + return bidderResponse; + } +} + +registerBidder(spec); diff --git a/modules/tapnativeBidAdapter.md b/modules/tapnativeBidAdapter.md new file mode 100644 index 00000000000..2cc39475dcb --- /dev/null +++ b/modules/tapnativeBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Tapnative Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@tapnative.com +``` + +# Description + +Tapnative currently supports the BANNER and NATIVE type ads through prebid js + +Module that connects to tapnative's demand sources. + +# Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'tapnative', + params: { + placement_id: 111520, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + }, + { + code: 'native-ad-container', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + bids: [ + { + bidder: 'tapnative', + params: { + placement_id: 111519, // Required parameter + bid_floor: 1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/tapnativeBidAdapter_spec.js b/test/spec/modules/tapnativeBidAdapter_spec.js new file mode 100644 index 00000000000..8a94b93168c --- /dev/null +++ b/test/spec/modules/tapnativeBidAdapter_spec.js @@ -0,0 +1,322 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/tapnativeBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('tapnative adapter', function () { + let bannerRequest, nativeRequest; + let bannerResponse, nativeResponse, invalidBannerResponse, invalidNativeResponse; + + beforeEach(function () { + bannerRequest = [ + { + bidder: 'tapnative', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placement_id: 111520, + bid_floor: 0.5 + } + } + ]; + nativeRequest = [ + { + bidder: 'tapnative', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + params: { + placement_id: 111519, + bid_floor: 1 + } + } + ]; + bannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': "", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.tapnative.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + nativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': '{\"native\":{\"ver\":\"1.1\",\"assets\": [{\"id\":1,\"required\":1,\"title\":{\"text\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":2,\"required\":1,\"img\":{\"url\":\"https://cdn.tapnative.com/1_368852_0.png\",\"w\":500,\"h\":300,\"type\":\"3\"}},{\"id\":3,\"required\":0,\"data\":{\"value\":\"Diabetes In Control. A free weekly diabetes newsletter for Medical Professionals.\"}},{\"id\":4,\"required\":1,\"data\":{\"value\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":6,\"required\":1,\"data\":{\"value\":\"URL\"}}],\"link\":{\"url\":\"https://r.tapnative.com/adx-rtb-d/servlet/WebF_AdManager.AdLinkManager?qs=H4sIAAAAAAAAAx2U2QHDIAxDVwKDr3F84P1HqNLPtMSRpSeG9RiPXH+5a474KzO/47YX7UoP50m61fLujlNjb76/8ZiblkimHq5nL/ZRedp3031x1tnk55LjSNN6h9/Zq+qmaLLuWTl74m1ZJKnb+m2OtQm/3L4sb933pM92qMOgjJ41MYmPXKnndRVKs+9bfSEumoZIFpTXuXbCP+WXuzl725E3O+9odi5OJrnBzhwjx9+UnFN3nTNt1/HY5aeljKtvZYpoJHNXr8BWa8ysKQY7ZmNA3DHK2qRwY7+zLu+xm9z5eheJ4Pv2usSptvO3p7JHrnXn0T5yVWdccp9Yz7hhoz2iu2zqsXsGFZ9hh14J6yU4TkJ0BgnOY8tY3tS+n2qsw7xZfKuanSNbAo+9nkJ83i20+FwhfbJeDVOllXsdxmDWauYcSRgS9+yG5qHwUDjAxxA0iZnOjlsnI+y09+ATeTEwbAVGgp0Qu/ceP0kjUvpu1Ty7O9MoegfrmLPxdjUh3mJL+XhARby+Ax8iBckf6BQdn9W+DMlvmlzYLuLlIy7YociFOIvXvEiYYCMboVk8BLHbnw3Zmr5us3xbjtXL67L96F15acJXkM5BOmTaUbBkYGdCI+Et8XmlpbuE3xVQwmxryc2y4wP3ByuuP8GogPZz8OpPaBv8diWWUTrC2nnLhdNUrJRTKc9FepDvwHTDwfbbMCTSb4LhUIFkyFrw/i7GtkPi6NCCai6N47TgNsTnzZWRoVtOSLq7FsLiF29y0Gj0GHVPVYG3QOPS7Swc3UuiFAQZJx3YvpHA2geUgVBASMEL4vcDi2Dw3NPtBSC4EQEvH/uMILu6WyUwraywTeVpoqoHTqOoD84FzReKoWemJy6jyuiBieGlQIe6wY2elTaMOwEUFF5NagzPj6nauc0+aXzQN3Q72hxFAgtfORK60RRAHYZLYymIzSJcXLgRFsqrb1UoD+5Atq7TWojaLTfOyUvH9EeJvZEOilQAXrf/ALoI8ZhABQAA\"},\"imptrackers\":[\"https://i.tapnative.com/adx-rtb-d/servlet/WebF_AdManager.ImpCounter?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=https%3A%2F%2Fqa-jboss.audiencelogy.com%2Ftn_native_prod.html\",\"https://i.tapnative.com/adx-rtb-d/servlet/WebF_AdManager.ImpTracker?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=\",\"https://rtb-east.tapnative.com:9001/beacon?uid=44636f6605b06ec6d4389d6efb7e5054&cc=468132&fccap=5&nid=1\"]}}', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.tapnative.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidBannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.tapnative.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidNativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': 'invalid response', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.tapnative.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'tapnative', + params: { + placement_id: 111520 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'tapnative', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Banner Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(bannerRequest), + bidRequest = spec.buildRequests(bannerRequest); + expect(bannerRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(bannerRequest); + expect(_Request.url).to.equal('https://rtb-east.tapnative.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(bannerRequest[0].bidId); + expect(data[0].placementId).to.equal(111520); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(bannerRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate banner response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(bannerRequest); + let bResponse = spec.interpretResponse(bannerResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(bannerRequest); + let response = spec.interpretResponse(invalidBannerResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('Validate Native Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(nativeRequest), + bidRequest = spec.buildRequests(nativeRequest); + expect(nativeRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(nativeRequest); + expect(_Request.url).to.equal('https://rtb-east.tapnative.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(nativeRequest[0].bidId); + expect(data[0].placementId).to.equal(111519); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(nativeRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate native response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(nativeRequest); + let bResponse = spec.interpretResponse(nativeResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(nativeResponse.body.seatbid[0].bid[0].impid); + // expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + // expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('native'); + expect(bResponse[0].native.clickUrl).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.impressionTrackers).to.be.an('array').with.length.above(0); + expect(bResponse[0].native.title).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.image.url).to.be.a('string').and.not.be.empty; + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['www.diabetesincontrol.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(nativeResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(nativeResponse.body.seatbid[0].bid[0].dealId); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); From 5c246b3a531fabcee5eab9aef4d70caf459d709b Mon Sep 17 00:00:00 2001 From: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:03:25 +0300 Subject: [PATCH 0584/1097] IQzone Bid Adapter : update user sync domain (#12320) * add IQZone adapter * add endpointId param * add user sync * added gpp support * added support of transanctionId and eids * updated tests * changed placement to plcmt * upd user sync domain --- modules/iqzoneBidAdapter.js | 2 +- test/spec/modules/iqzoneBidAdapter_spec.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index c03d579d2a5..9603a509ac5 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -4,7 +4,7 @@ import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } fro const BIDDER_CODE = 'iqzone'; const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; -const SYNC_URL = 'https://cs.smartssp.iqzone.com'; +const SYNC_URL = 'https://cs.iqzone.com'; export const spec = { code: BIDDER_CODE, diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index 5e9b69d7a44..f642a935f69 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -473,7 +473,6 @@ describe('IQZoneBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); - describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -485,7 +484,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].type).to.be.a('string') expect(syncData[0].type).to.equal('image') expect(syncData[0].url).to.be.a('string') - expect(syncData[0].url).to.equal('https://cs.smartssp.iqzone.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') }); it('Should return array of objects with proper sync config , include CCPA', function() { const syncData = spec.getUserSyncs({}, {}, {}, { @@ -496,7 +495,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].type).to.be.a('string') expect(syncData[0].type).to.equal('image') expect(syncData[0].url).to.be.a('string') - expect(syncData[0].url).to.equal('https://cs.smartssp.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); it('Should return array of objects with proper sync config , include GPP', function() { const syncData = spec.getUserSyncs({}, {}, {}, {}, { @@ -508,7 +507,7 @@ describe('IQZoneBidAdapter', function () { expect(syncData[0].type).to.be.a('string') expect(syncData[0].type).to.equal('image') expect(syncData[0].url).to.be.a('string') - expect(syncData[0].url).to.equal('https://cs.smartssp.iqzone.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); From 1fed96d5a40f10ecffe051ad6dcce821f73460ad Mon Sep 17 00:00:00 2001 From: tanguylemeur-sparteo <163292075+tanguylemeur-sparteo@users.noreply.github.com> Date: Fri, 18 Oct 2024 04:14:05 +0200 Subject: [PATCH 0585/1097] Sparteo Bid Adapter: Adapt error messages following param deprecation (#12321) --- modules/sparteoBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index 0bccc1ec140..b5454be82ff 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -76,7 +76,8 @@ export const spec = { } if (!bid.params.networkId && !bid.params.publisherId) { - logError('The networkId or publisherId is required'); + // publisherId is deprecated but is still accepted for now for retrocompatibility purpose. + logError('The networkId is required'); return false; } From 22a169f6ec7198bd96d5dff2f97e1cc56b4068a1 Mon Sep 17 00:00:00 2001 From: maelmrgt <77864748+maelmrgt@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:21:19 +0200 Subject: [PATCH 0586/1097] Greenbids RTD Module : add flag to disable filtering (#12331) * feat(Rtd): add a flag to disable the filtering of rtd module * review * add log * modify log level * Refresh CI --- modules/greenbidsRtdProvider.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js index e7423abf115..e350cebb33e 100644 --- a/modules/greenbidsRtdProvider.js +++ b/modules/greenbidsRtdProvider.js @@ -1,4 +1,4 @@ -import { logError, logInfo, logWarn, deepClone, generateUUID, deepSetValue, deepAccess, getParameterByName } from '../src/utils.js'; +import { logError, logInfo, logWarn, logMessage, deepClone, generateUUID, deepSetValue, deepAccess, getParameterByName } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import * as events from '../src/events.js'; @@ -85,6 +85,7 @@ function processSuccessResponse(response, timeoutId, reqBidsConfigObj, greenbids function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { const isFilteringForced = getParameterByName('greenbids_force_filtering'); + const isFilteringDisabled = getParameterByName('greenbids_disable_filtering'); adUnits.forEach((adUnit) => { const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); if (matchingAdUnit) { @@ -93,10 +94,12 @@ function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { keptInAuction: matchingAdUnit.bidders, isExploration: matchingAdUnit.isExploration }); - if (isFilteringForced) { + if (matchingAdUnit.isExploration || isFilteringDisabled) { + logMessage('Greenbids Rtd: either exploration traffic, or disabled filtering flag detected'); + } else if (isFilteringForced) { adUnit.bids = []; logInfo('Greenbids Rtd: filtering flag detected, forcing filtering of Rtd module.'); - } else if (!matchingAdUnit.isExploration) { + } else { removeFalseBidders(adUnit, matchingAdUnit); } } From b8dcc7c9f2bb75e081024f2b73e9670d128227d3 Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov <61063794+lyubomirshishkov@users.noreply.github.com> Date: Sat, 19 Oct 2024 04:35:34 +0300 Subject: [PATCH 0587/1097] Improve Digital Bid Adapter: Bid floor is sent in USD when possible (#12341) * 12238 - Azerion / Improve: does not properly support currency module * **Type:** Fix * **Scope:** improvedigitalBidAdapter * **Subject:** Bid floors are always converted to USD. * **Details:** * Adds `DEFAULT_CURRENCY` variable which is set to USD * Adds `convertBidFloorCurrency` function which in used to convert the bid floor when both `imp.bidfloor` and `imp.bidfloorcur` are present, and `imp.bidfloorcur` is not equal to the adapter's `DEFAULT_CURRENCY`; * **Breaks:** N/A * restored accidentally discarded change from unit test expect * * Modifies behavior to pass bid floor as is when it cannot be converted to USD; * Removes rounding of bid floor when converting its currency to USD; * remove unnecessary uses of `toUpperCase()` * * fix `convertCurrency` mock * remove redundant checks for type and NaN from `convertBidFloorCurrency` function --------- Co-authored-by: Lyubomir Shishkov Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> --- modules/improvedigitalBidAdapter.js | 23 +++++++++- .../modules/improvedigitalBidAdapter_spec.js | 43 +++++++++++++++---- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index f189a3f4ae4..158c2bd6c75 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -5,6 +5,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {convertCurrency} from '../libraries/currencyUtils/currency.js'; /** * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception * ImproveDigital only imports after winning a bid and only if the creative cannot reach top @@ -30,6 +31,7 @@ const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com'; const PB_ENDPOINT = 'pb'; const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; +const DEFAULT_CURRENCY = 'USD'; const VIDEO_PARAMS = { DEFAULT_MIMES: ['video/mp4'] @@ -125,6 +127,21 @@ export const spec = { registerBidder(spec); +const convertBidFloorCurrency = (imp) => { + try { + const bidFloor = convertCurrency( + imp.bidfloor, + imp.bidfloorcur, + DEFAULT_CURRENCY, + false, + ); + imp.bidfloor = bidFloor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } catch (err) { + logWarn(`Failed to convert bid floor to ${DEFAULT_CURRENCY}. Passing floor price in its original currency.`, err); + } +}; + export const CONVERTER = ortbConverter({ context: { ttl: CREATIVE_TTL, @@ -139,7 +156,11 @@ export const CONVERTER = ortbConverter({ imp.secure = Number(window.location.protocol === 'https:'); if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; - imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' + imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || DEFAULT_CURRENCY; + } + + if (imp.bidfloor && imp.bidfloorcur && imp.bidfloorcur !== DEFAULT_CURRENCY) { + convertBidFloorCurrency(imp); } const bidderParamsPath = context.extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; const placementId = bidRequest.params.placementId; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 5dc94f544ea..0c0789ced48 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -16,6 +16,7 @@ import 'modules/schain.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; @@ -205,12 +206,18 @@ describe('Improve Digital Adapter Tests', function () { describe('buildRequests', function () { let getConfigStub = null; + let getGlobalStub = null; afterEach(function () { if (getConfigStub) { getConfigStub.restore(); getConfigStub = null; } + + if (getGlobalStub) { + getGlobalStub.restore(); + getGlobalStub = null; + } }); it('should make a well-formed request objects', function () { @@ -349,10 +356,21 @@ describe('Improve Digital Adapter Tests', function () { } }); - it('should add bid floor', function () { - const bidRequest = Object.assign({}, simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + it('should add bid floor correctly', function () { + getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ + convertCurrency: (cpm, from, to) => { + const conversionKeys = { 'EUR-USD': 1.75 }; + const conversionRate = conversionKeys[`${from}-${to}`]; + if (!conversionRate) { + throw new Error(`No conversion rate found for ${from}-${to}`); + } + return cpm * conversionRate; + } + }); + const bidRequest = deepClone(simpleBidRequest); + // Floor price currency shouldn't be populated without a floor price + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloorcur).to.not.exist; // Default floor price currency @@ -361,18 +379,25 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].bidfloor).to.equal(0.05); expect(payload.imp[0].bidfloorcur).to.equal('USD'); - // Floor price currency - bidRequest.params.bidFloorCur = 'eUR'; + // Floor price sent as is when currency cannot be converted to default bid adapter currency + bidRequest.params.bidFloorCur = 'UAH'; + bidRequest.params.bidFloor = 0.05; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloor).to.equal(0.05); - expect(payload.imp[0].bidfloorcur).to.equal('EUR'); + expect(payload.imp[0].bidfloorcur).to.equal('UAH'); + + // Floor price currency converted to default bid adapter currency + bidRequest.params.bidFloorCur = 'eUR'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(0.08750000000000001); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); // getFloor defined -> use it over bidFloor let getFloorResponse = { currency: 'USD', floor: 3 }; bidRequest.getFloor = () => getFloorResponse; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloor).to.equal(3); - // expect(payload.imp[0].bidfloorcur).to.equal('USD'); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); }); it('should add GDPR consent string', function () { @@ -981,7 +1006,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1004,7 +1029,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '479163', dealId: 320896, netRevenue: false, From 1cdbb9d7b4dd514fb1a125ff8db1ed0049b40ee2 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Sat, 19 Oct 2024 03:37:36 +0200 Subject: [PATCH 0588/1097] LiveIntent User ID Module: Add DEFAULT_DELAY (#12334) * Add DEFAULT_DELAY * Artificial change to trigger the build --- libraries/liveIntentId/idSystem.js | 2 +- libraries/liveIntentId/shared.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/liveIntentId/idSystem.js b/libraries/liveIntentId/idSystem.js index 973dd9ba2eb..2077df8d8bf 100644 --- a/libraries/liveIntentId/idSystem.js +++ b/libraries/liveIntentId/idSystem.js @@ -11,7 +11,7 @@ import { submodule } from '../../src/hook.js'; import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { getStorageManager } from '../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../src/activities/modules.js'; -import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, DEFAULT_DELAY, GVLID, PRIMARY_IDS, parseRequestedAttributes } from './shared.js' +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, GVLID, DEFAULT_DELAY, PRIMARY_IDS, parseRequestedAttributes } from './shared.js' /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js index 66b0c8611ab..2e831a899f4 100644 --- a/libraries/liveIntentId/shared.js +++ b/libraries/liveIntentId/shared.js @@ -6,6 +6,7 @@ import { coppaDataHandler } from '../../src/adapterManager.js'; export const PRIMARY_IDS = ['libp']; export const GVLID = 148; export const DEFAULT_AJAX_TIMEOUT = 5000; +export const DEFAULT_DELAY = 500; export const MODULE_NAME = 'liveIntentId'; export const LI_PROVIDER_DOMAIN = 'liveintent.com'; export const DEFAULT_REQUESTED_ATTRIBUTES = { 'nonId': true }; From 68f879a154cd3bc5c731d8449be464b82a3a4af5 Mon Sep 17 00:00:00 2001 From: tanguylemeur-sparteo <163292075+tanguylemeur-sparteo@users.noreply.github.com> Date: Sat, 19 Oct 2024 03:38:37 +0200 Subject: [PATCH 0589/1097] sparteoBidAdapter: add adUnitCode (#12305) --- modules/sparteoBidAdapter.js | 1 + test/spec/modules/sparteoBidAdapter_spec.js | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index b5454be82ff..5649afd3946 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -38,6 +38,7 @@ const converter = ortbConverter({ const imp = buildImp(bidRequest, context); deepSetValue(imp, 'ext.sparteo.params', bidRequest.params); + imp.ext.sparteo.params.adUnitCode = bidRequest.adUnitCode; return imp; }, diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js index 293f7da30a1..e0b7349cb92 100644 --- a/test/spec/modules/sparteoBidAdapter_spec.js +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -71,6 +71,7 @@ const VALID_REQUEST_BANNER = { 'sparteo': { 'params': { 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-1234', 'formats': ['corner'] } } @@ -112,7 +113,8 @@ const VALID_REQUEST_VIDEO = { 'pbadslot': 'video', 'sparteo': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-5678' } } } @@ -147,6 +149,7 @@ const VALID_REQUEST = { 'sparteo': { 'params': { 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-1234', 'formats': ['corner'] } } @@ -170,7 +173,8 @@ const VALID_REQUEST = { 'pbadslot': 'video', 'sparteo': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-5678' } } } From c84b201edd21a7dd01d858e995892197f5946730 Mon Sep 17 00:00:00 2001 From: Muki Seiler Date: Sat, 19 Oct 2024 13:19:15 +0200 Subject: [PATCH 0590/1097] Richaudience Bid Adapter : add gvlid to alias (#12330) * add gvlid to alias Prebid.js doesn't use the `spec.gvlid` for aliases as it seems. In order to avoid `gvlMapping` definitions, the shorter alias also gets a gvlid * Fix alias test * Use deep equal --- modules/richaudienceBidAdapter.js | 2 +- test/spec/modules/richaudienceBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 776b855dfba..edf5c03cf15 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -11,7 +11,7 @@ let REFERER = ''; export const spec = { code: BIDDER_CODE, gvlid: 108, - aliases: ['ra'], + aliases: [{code: 'ra', gvlid: 108}], supportedMediaTypes: [BANNER, VIDEO], /*** diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 782955103b3..a1baaf52a77 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -796,7 +796,7 @@ describe('Richaudience adapter tests', function () { it('Verifies bidder aliases', function () { expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('ra'); + expect(spec.aliases[0]).to.deep.equal({code: 'ra', gvlid: 108}); }); it('Verifies bidder gvlid', function () { From f1b4705c3bad512944b9fccdadb9a999461002be Mon Sep 17 00:00:00 2001 From: annavane <101708287+annavane@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:31:26 +0200 Subject: [PATCH 0591/1097] tnc Id System : fixes for docs and performance improvements (#12315) * Bug Fixes: modules/tncIdSystem.js - Optimized User ID Recovery: Replaced the existing user ID recovery function with a faster and more efficient method, improving performance. modules/userId/userId.md - Documentation Correction: Resolved inconsistencies in the documentation, ensuring accurate information for module configuration and usage. * - Tests fixed --- modules/tncIdSystem.js | 8 +++----- modules/userId/userId.md | 19 +++++++++---------- test/spec/modules/tncIdSystem_spec.js | 14 +++++--------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/modules/tncIdSystem.js b/modules/tncIdSystem.js index c05e0ce4713..9d3187462be 100644 --- a/modules/tncIdSystem.js +++ b/modules/tncIdSystem.js @@ -11,10 +11,9 @@ const waitTNCScript = (tncNS) => { var tnc = window[tncNS]; if (!tnc) reject(new Error('No TNC Object')); if (tnc.tncid) resolve(tnc.tncid); - tnc.ready(() => { - tnc = window[tncNS]; - if (tnc.tncid) resolve(tnc.tncid); - else tnc.on('data-sent', () => resolve(tnc.tncid)); + tnc.ready(async () => { + let tncid = await tnc.getTNCID('prebid'); + resolve(tncid); }); }); } @@ -32,7 +31,6 @@ const tncCallback = function (cb) { tncNS = '__tncPbjs'; promiseArray.push(loadRemoteScript()); } - return Promise.all(promiseArray).then(() => waitTNCScript(tncNS)).then(cb).catch(() => cb()); } diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 9fb53c2c7b3..9aea3e8d533 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -366,16 +366,15 @@ pbjs.setConfig({ Example showing how to configure a `params` object to pass directly to bid adapters ``` - pbjs.setConfig({ -userSync: { -userIds: [{ -name: 'tncId', -params: { -providerId: "c8549079-f149-4529-a34b-3fa91ef257d1" -} -}], -syncDelay: 5000 -} + userSync: { + userIds: [{ + name: 'tncId', + params: { + url: 'https://js.tncid.app/remote.min.js' //Optional + } + }], + syncDelay: 5000 + } }); ``` diff --git a/test/spec/modules/tncIdSystem_spec.js b/test/spec/modules/tncIdSystem_spec.js index 56b97ff0561..4626c940a59 100644 --- a/test/spec/modules/tncIdSystem_spec.js +++ b/test/spec/modules/tncIdSystem_spec.js @@ -53,7 +53,6 @@ describe('TNCID tests', function () { Object.defineProperty(window, '__tnc', { value: { ready: (readyFunc) => { readyFunc() }, - on: (name, cb) => { cb() }, tncid: 'TNCID_TEST_ID_1', providerId: 'TEST_PROVIDER_ID_1', }, @@ -71,8 +70,8 @@ describe('TNCID tests', function () { it('GDPR is OK and page has TNC script with ns: __tnc but not loaded, TNCID is assigned and returned', function () { Object.defineProperty(window, '__tnc', { value: { - ready: (readyFunc) => { readyFunc() }, - on: (name, cb) => { cb() }, + ready: async (readyFunc) => { await readyFunc() }, + getTNCID: async (name) => { return 'TNCID_TEST_ID_1' }, providerId: 'TEST_PROVIDER_ID_1', }, configurable: true @@ -82,18 +81,15 @@ describe('TNCID tests', function () { const {callback} = tncidSubModule.getId({}, { gdprApplies: false }); return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; + expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; }) }); it('GDPR is OK and page has TNC script with ns: __tncPbjs, TNCID is returned', function () { Object.defineProperty(window, '__tncPbjs', { value: { - ready: (readyFunc) => { readyFunc() }, - on: (name, cb) => { - window.__tncPbjs.tncid = 'TNCID_TEST_ID_2'; - cb(); - }, + ready: async (readyFunc) => { await readyFunc() }, + getTNCID: async (name) => { return 'TNCID_TEST_ID_2' }, providerId: 'TEST_PROVIDER_ID_1', options: {}, }, From 0960bcbcf5538697d1d4e82ec4f4e503691039e0 Mon Sep 17 00:00:00 2001 From: AdsInteractive Date: Mon, 21 Oct 2024 15:43:25 +0300 Subject: [PATCH 0592/1097] Ads Interactive Bid Adpter : add gvlid (#12324) * add new adapter ads_interactive * fix domain * add gvlid --- modules/ads_interactiveBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ads_interactiveBidAdapter.js b/modules/ads_interactiveBidAdapter.js index d21f8bcbee6..9a827cb4914 100644 --- a/modules/ads_interactiveBidAdapter.js +++ b/modules/ads_interactiveBidAdapter.js @@ -5,9 +5,11 @@ import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } fro const BIDDER_CODE = 'ads_interactive'; const AD_URL = 'https://bntb.adsinteractive.com/pbjs'; const SYNC_URL = 'https://cstb.adsinteractive.com'; +const GVLID = 1212; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(), From 49798b76ce874a96e7b0f303c9f1d3206f79ce0a Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 21 Oct 2024 17:08:32 +0400 Subject: [PATCH 0593/1097] Viqeo Bid Adapter: Updated endpoint (#12338) --- modules/viqeoBidAdapter.js | 147 ++++++++++++---------- modules/viqeoBidAdapter.md | 36 +++--- test/spec/modules/viqeoBidAdapter_spec.js | 13 +- 3 files changed, 104 insertions(+), 92 deletions(-) diff --git a/modules/viqeoBidAdapter.js b/modules/viqeoBidAdapter.js index 28f4de1fd52..aac9ee69a24 100644 --- a/modules/viqeoBidAdapter.js +++ b/modules/viqeoBidAdapter.js @@ -1,7 +1,15 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {logError, logInfo, _each, mergeDeep, isFn, isNumber, isPlainObject} from '../src/utils.js' -import {VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { + logError, + logInfo, + _each, + mergeDeep, + isFn, + isNumber, + isPlainObject, +} from '../src/utils.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -12,36 +20,48 @@ import {Renderer} from '../src/Renderer.js'; */ const BIDDER_CODE = 'viqeo'; -const DEFAULT_MIMES = ['application/javascript']; -const VIQEO_ENDPOINT = 'https://ads.betweendigital.com/openrtb_bid'; +const DEFAULT_MIMES = [ + 'video/3gpp', + 'video/mp4', + 'video/mpeg', + 'video/webm', + 'application/javascript', +]; +const VIQEO_ENDPOINT = 'https://ad.vqserve.com/ads/prebid'; const RENDERER_URL = 'https://cdn.viqeo.tv/js/vq_starter.js'; const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_SSPID = 44697; function getBidFloor(bid) { - const {floor, currency} = bid.params; + const { floor, currency } = bid.params; const curr = currency || DEFAULT_CURRENCY; if (!isFn(bid.getFloor)) { - return {floor: isNumber(floor) ? floor : 0, currency: curr}; + return { floor: isNumber(floor) ? floor : 0, currency: curr }; } - const floorInfo = bid.getFloor({currency: curr, mediaType: VIDEO, size: '*'}); - if (isPlainObject(floorInfo) && isNumber(floorInfo.floor) && floorInfo.currency === curr) { + const floorInfo = bid.getFloor({ + currency: curr, + mediaType: VIDEO, + size: '*', + }); + if ( + isPlainObject(floorInfo) && + isNumber(floorInfo.floor) && + floorInfo.currency === curr + ) { return floorInfo; } - return {floor: floor || 0, currency: currency || DEFAULT_CURRENCY}; + return { floor: floor || 0, currency: currency || DEFAULT_CURRENCY }; } -function getVideoTargetingParams({mediaTypes: {video}}) { +function getVideoTargetingParams({ mediaTypes: { video } }) { const result = {}; - Object.keys(Object(video)) - .forEach(key => { - if (key === 'playerSize') { - result.w = video.playerSize[0][0]; - result.h = video.playerSize[0][1]; - } else if (key !== 'context') { - result[key] = video[key]; - } - }) + Object.keys(Object(video)).forEach((key) => { + if (key === 'playerSize') { + result.w = video.playerSize[0][0]; + result.h = video.playerSize[0][1]; + } else if (key !== 'context') { + result[key] = video[key]; + } + }); return result; } @@ -55,20 +75,20 @@ export const spec = { * @param {BidRequest} bidRequest The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: ({params}) => { + isBidRequestValid: ({ params }) => { if (!params) { logError('failed validation: params not declared'); return false; } - if (!params.user && !params.user?.buyeruid) { - logError('failed validation: user.buyeruid not declared'); + if (!params.tagId) { + logError('failed validation: tagId not declared'); return false; } if (!params.playerOptions) { logError('failed validation: playerOptions not declared'); return false; } - const {profileId, videoId, playerId} = params.playerOptions; + const { profileId, videoId, playerId } = params.playerOptions; if (!profileId) { logError('failed validation: profileId not declared'); return false; @@ -88,46 +108,42 @@ export const spec = { const bidRequests = []; _each(validBidRequests, (bid, i) => { const { - params: {test, sspId, endpointUrl}, - mediaTypes: {video}, + params: { test, tagId, endpointUrl, bcat, badv }, + mediaTypes: { video }, } = bid; const ortb2 = bid.ortb2 || {}; const user = bid.params.user || {}; const device = bid.params.device || {}; const site = bid.params.site || {}; - const w = window; const floorInfo = getBidFloor(bid); + const data = { id: bid.bidId, test, - imp: [{ - id: `${i}`, - tagid: bid.adUnitCode, - video: { - ...getVideoTargetingParams(bid), - mimes: video.mimes || DEFAULT_MIMES, + imp: [ + { + id: `${i}`, + video: { + ...getVideoTargetingParams(bid), + mimes: video.mimes || DEFAULT_MIMES, + }, + tagid: `${tagId}`, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, + secure: 1, }, - bidfloor: floorInfo.floor, - bidfloorcur: floorInfo.currency, - secure: 1 - }], - site: test === 1 ? { - page: 'https://viqeo.tv', - domain: 'viqeo.tv' - } : mergeDeep({ - domain: w.location.hostname, - page: w.location.href - }, ortb2.site, site), - device: mergeDeep({ - w: w.screen.width, - h: w.screen.height, - ua: w.navigator.userAgent, - }, ortb2.device, device), - user: mergeDeep({...user}, ortb2.user), - app: bid.params.app, + ], + site: mergeDeep( + site, + ortb2.site + ), + device: mergeDeep(device, ortb2.device), + user: mergeDeep(user, ortb2.user), + bcat: ortb2.bcat || bcat, + badv: ortb2.badv || badv }; bidRequests.push({ - url: endpointUrl || `${VIQEO_ENDPOINT}/?sspId=${sspId || DEFAULT_SSPID}`, + url: endpointUrl || `${VIQEO_ENDPOINT}`, method: 'POST', data, bids: validBidRequests, @@ -148,24 +164,24 @@ export const spec = { return []; } try { - const {id, seatbid, cur} = serverResponse.body; + const { id, seatbid, cur } = serverResponse.body; _each(seatbid, (sb) => { - const {bid} = sb; + const { bid } = sb; _each(bid, (b) => { - const bidRequest = bidRequests.bids.find(({bidId}) => bidId === id); + const bidRequest = bidRequests.bids.find(({ bidId }) => bidId === id); const renderer = Renderer.install({ url: bidRequest?.params?.renderUrl || RENDERER_URL, }); - renderer.setRender((bid) => { + renderer.setRender((bid, doc) => { if (window.VIQEO) { - window.VIQEO.renderPrebid(bid); + window.VIQEO.renderPrebid(bid, doc); } else { logError('failed get window.VIQEO'); } }); bidResponses.push({ requestId: id, - currency: cur, + currency: cur || DEFAULT_CURRENCY, cpm: b.price, ttl: b.exp, netRevenue: true, @@ -176,13 +192,18 @@ export const spec = { vastUrl: b.nurl, mediaType: VIDEO, renderer, - }) - }) + meta: { + secondaryCatIds: b.cat, + attr: b.attr, + advertiserDomains: b.adomain, + }, + }); + }); }); } catch (error) { logError(error); } return bidResponses; }, -} +}; registerBidder(spec); diff --git a/modules/viqeoBidAdapter.md b/modules/viqeoBidAdapter.md index b4a020bf057..b3a0c59d3a5 100644 --- a/modules/viqeoBidAdapter.md +++ b/modules/viqeoBidAdapter.md @@ -11,23 +11,21 @@ Viqeo Bidder Adapter for Prebid.js. About: https://viqeo.tv/ ### Bid params {: .table .table-bordered .table-striped } -| Name | Scope | Description | Example | Type | -|-----------------------------|----------|----------------------------------------------------------------------------------------------------------------------------|--------------------------|-----------| -| `user` | required | The object containing user data (See OpenRTB spec) | `user: {}` | `object` | -| `user.buyeruid` | required | User id | `"12345"` | `string` | -| `playerOptions` | required | The object containing Viqeo player options | `playerOptions: {}` | `object` | -| `playerOptions.profileId` | required | Viqeo profile id | `1382` | `number` | -| `playerOptions.videId` | optional | Viqeo video id | `"ed584da454c7205ca7e4"` | `string` | -| `playerOptions.playerId` | optional | Viqeo player id | `1` | `number` | -| `device` | optional | The object containing device data (See OpenRTB spec) | `device: {}` | `object` | -| `site` | optional | The object containing site data (See OpenRTB spec) | `site: {}` | `object` | -| `app` | optional | The object containing app data (See OpenRTB spec) | `app: {}` | `object` | -| `floor` | optional | Bid floor price | `0.5` | `number` | -| `currency` | optional | 3-letter ISO 4217 code defining the currency of the bid. | `EUR` | `string` | -| `test` | optional | Flag which will induce a sample bid response when true; only set to true for testing purposes (1 = true, 0 = false) | `1` | `integer` | -| `sspId` | optional | For debug, request id | `1` | `number` | -| `renderUrl` | optional | For debug, script player url | `"https://viqeo.tv"` | `string` | -| `endpointUrl` | optional | For debug, api endpoint | `"https://viqeo.tv"` | `string` | +| Name | Scope | Description | Example | Type | +|-----------------------------|----------|---------------------------------------------------------------------------------------------------------------------|--------------------------|-----------| +| `tagid` | required | The unique identifier of the ad placement. Could be obtained from the Viqeo UI or from your account manager. | `2` | `string` | +| `playerOptions` | required | The object containing Viqeo player options | `playerOptions: {}` | `object` | +| `playerOptions.profileId` | required | Viqeo profile id | `1382` | `number` | +| `playerOptions.videId` | optional | Viqeo video id | `"ed584da454c7205ca7e4"` | `string` | +| `playerOptions.playerId` | optional | Viqeo player id | `1` | `number` | +| `user` | optional | The object containing user data (See OpenRTB spec) | `user: {}` | `object` | +| `device` | optional | The object containing device data (See OpenRTB spec) | `device: {}` | `object` | +| `site` | optional | The object containing site data (See OpenRTB spec) | `site: {}` | `object` | +| `floor` | optional | Bid floor price | `0.5` | `number` | +| `currency` | optional | 3-letter ISO 4217 code defining the currency of the bid. | `EUR` | `string` | +| `test` | optional | Flag which will induce a sample bid response when true; only set to true for testing purposes (1 = true, 0 = false) | `1` | `integer` | +| `renderUrl` | optional | For debug, script player url | `"https://viqeo.tv"` | `string` | +| `endpointUrl` | optional | For debug, api endpoint | `"https://viqeo.tv"` | `string` | # Test Parameters ``` @@ -42,9 +40,7 @@ Viqeo Bidder Adapter for Prebid.js. About: https://viqeo.tv/ bids: [{ bidder: 'viqeo', params: { - user: { - buyeruid: '1', - }, + tagId: '2', playerOptions: { videoId: 'ed584da454c7205ca7e4', profileId: 1382, diff --git a/test/spec/modules/viqeoBidAdapter_spec.js b/test/spec/modules/viqeoBidAdapter_spec.js index 8f597318af9..af397393a51 100644 --- a/test/spec/modules/viqeoBidAdapter_spec.js +++ b/test/spec/modules/viqeoBidAdapter_spec.js @@ -6,9 +6,7 @@ describe('viqeoBidAdapter', function () { expect(spec.isBidRequestValid({ bidder: 'viqeo', params: { - user: { - buyeruid: '1', - }, + tagId: '2', playerOptions: { videoId: 'ed584da454c7205ca7e4', profileId: 1382, @@ -27,9 +25,7 @@ describe('viqeoBidAdapter', function () { bidId: 'id1', bidder: 'viqeo', params: { - user: { - buyeruid: '1', - }, + tagId: '2', currency: 'EUR', floor: 0.5, playerOptions: { @@ -48,7 +44,7 @@ describe('viqeoBidAdapter', function () { expect(requestData.imp[0].bidfloor).to.equal(0.5); expect(requestData.imp[0].video.w).to.equal(240); expect(requestData.imp[0].video.h).to.equal(400); - expect(requestData.user.buyeruid).to.equal('1'); + expect(requestData.imp[0].tagid).to.equal('2'); }); it('build request check url', function () { const bidRequestData = [{ @@ -58,14 +54,13 @@ describe('viqeoBidAdapter', function () { videoId: 'ed584da454c7205ca7e4', profileId: 1382, }, - sspId: 42, }, mediaTypes: { video: { playerSize: [[240, 400]] } }, }]; const request = spec.buildRequests(bidRequestData); - expect(request[0].url).to.equal('https://ads.betweendigital.com/openrtb_bid/?sspId=42') + expect(request[0].url).to.equal('https://ad.vqserve.com/ads/prebid') }); it('response_params common case', function () { const bidRequestData = { From 4b46054f75ddc7116b23a129062bb473bd06dfc0 Mon Sep 17 00:00:00 2001 From: Oleksandr Solodovnikov Date: Mon, 21 Oct 2024 16:19:05 +0300 Subject: [PATCH 0594/1097] Aniview Bid Adapter: oRTB support; Plain banner support; Refactoring (#12275) * Update AniviewBidAdapter * - Changed adapter's name from ANIVIEW to Aniview inside doc; - Double quotes to single quotes * Removed extra block from doc * Removed code for local testing; Fixed `interpretResponse`. --- modules/aniviewBidAdapter.js | 533 +++++++++--------- modules/aniviewBidAdapter.md | 8 +- test/spec/modules/aniviewBidAdapter_spec.js | 589 ++++++++++++-------- 3 files changed, 632 insertions(+), 498 deletions(-) diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 84552638421..9a58308278b 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -1,317 +1,318 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; -import { logError } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { + deepAccess, + deepSetValue, + mergeDeep, + isFn, + isStr, + isEmptyStr, + isPlainObject, + getUniqueIdentifierStr +} from '../src/utils.js'; const BIDDER_CODE = 'aniview'; const GVLID = 780; const TTL = 600; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_PLAYER_DOMAIN = 'player.aniview.com'; +const SSP_ENDPOINT = 'https://rtb.aniview.com/sspRTB2'; +const RENDERER_FILENAME = 'prebidRenderer.js'; -function avRenderer(bid) { - bid.renderer.push(function() { - let eventCallback = bid && bid.renderer && bid.renderer.handleVideoEvent ? bid.renderer.handleVideoEvent : null; - window.aniviewRenderer.renderAd({ - id: bid.adUnitCode + '_' + bid.adId, - debug: window.location.href.indexOf('pbjsDebug') >= 0, - placement: bid.adUnitCode, - width: bid.width, - height: bid.height, - vastUrl: bid.vastUrl, - vastXml: bid.vastXml, - config: bid.params[0].rendererConfig, - eventsCallback: eventCallback, - bid: bid - }); - }); -} +const converter = ortbConverter({ + context: { + netRevenue: true, // required + ttl: TTL, // required + currency: DEFAULT_CURRENCY, + }, -function newRenderer(bidRequest) { - let playerDomain = 'player.aniview.com'; - const config = {}; + imp(buildImp, bidRequest, context) { + const { mediaType } = context; + const imp = buildImp(bidRequest, context); + const isVideo = mediaType === VIDEO; + const isBanner = mediaType === BANNER; + const { width, height } = getSize(context, bidRequest); + const floor = getFloor(bidRequest, { width, height }, mediaType); - if (bidRequest && bidRequest.bidRequest && bidRequest.bidRequest.params) { - const params = bidRequest.bidRequest.params + imp.tagid = deepAccess(bidRequest, 'params.AV_CHANNELID'); - if (params.playerDomain) { - playerDomain = params.playerDomain; + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; } - if (params.AV_PUBLISHERID) { - config.AV_PUBLISHERID = params.AV_PUBLISHERID; + if (isVideo) { + deepSetValue(imp, `ext.${BIDDER_CODE}`, { + AV_WIDTH: width, + AV_HEIGHT: height, + bidWidth: width, + bidHeight: height, + }); + } else if (isBanner) { + // TODO: remove once serving will be fixed + deepSetValue(imp, 'banner', { w: width, h: height }); } - if (params.AV_CHANNELID) { - config.AV_CHANNELID = params.AV_CHANNELID; - } - } + return imp; + }, - const renderer = Renderer.install({ - url: 'https://' + playerDomain + '/script/6.1/prebidRenderer.js', - config: config, - loaded: false, - }); + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); - try { - renderer.setRender(avRenderer); - } catch (err) { - } + mergeDeep(request, { + ext: { + [BIDDER_CODE]: { + pbjs: 1, + pbv: '$prebid.version$', + } + } + }) - return renderer; -} + return request; + }, -function isBidRequestValid(bid) { - if (!bid.params || !bid.params.AV_PUBLISHERID || !bid.params.AV_CHANNELID) { return false; } + bidResponse(buildBidResponse, bid, context) { + const { bidRequest, mediaType } = context; + const { width, height } = getSize(context, bidRequest); + const isVideoBid = mediaType === VIDEO; + const isBannerBid = mediaType === BANNER; - return true; -} -let irc = 0; -function buildRequests(validBidRequests, bidderRequest) { - let bidRequests = []; - - for (let i = 0; i < validBidRequests.length; i++) { - let bidRequest = validBidRequests[i]; - var sizes = [[640, 480]]; - - if (bidRequest.mediaTypes && bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.playerSize) { - sizes = bidRequest.mediaTypes.video.playerSize; - } else { - if (bidRequest.sizes) { - sizes = bidRequest.sizes; - } + if (isVideoBid) { + context.vastXml = bid.adm; } - if (sizes.length === 2 && typeof sizes[0] === 'number') { - sizes = [[sizes[0], sizes[1]]]; + + const bidResponse = buildBidResponse(bid, context); + + if (isEmptyStr(bidRequest?.bidId) || !bid.adm || bidResponse.cpm <= 0) { + return bidResponse; } - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - let playerWidth; - let playerHeight; + mergeDeep(bidResponse, { + width, + height, + creativeId: bid.crid || 'creativeId', + meta: { advertiserDomains: [] }, + adId: getUniqueIdentifierStr(), + }); - if (size && size.length == 2) { - playerWidth = size[0]; - playerHeight = size[1]; + if (isVideoBid) { + if (bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = createRenderer(bidRequest); + } + } else if (isBannerBid) { + if (bid.adm?.trim().startsWith(' { + Object.keys(bidRequest.mediaTypes).forEach((mediaType) => { + const endpoint = bidRequest.params.dev?.endpoint || SSP_ENDPOINT; + + requests.push({ + method: 'POST', + url: endpoint, + bids: [bidRequest], + options: { withCredentials: false }, + data: converter.toORTB({ + bidderRequest, + bidRequests: [bidRequest], + context: { mediaType }, + }), + }); }); + }); + + return requests; + }, + + interpretResponse(serverResponse, bidderRequest) { + const { bids, data } = bidderRequest; + const { body } = serverResponse; + const bidResponse = body?.seatbid?.[0]?.bid?.[0]; + + if (!bids || !data || !bidResponse) { + return []; } - } - return bidRequests; -} -function getCpmData(xml) { - let ret = {cpm: 0, currency: 'USD'}; - if (xml) { - let ext = xml.getElementsByTagName('Extensions'); - if (ext && ext.length > 0) { - ext = ext[0].getElementsByTagName('Extension'); - if (ext && ext.length > 0) { - for (var i = 0; i < ext.length; i++) { - if (ext[i].getAttribute('type') == 'ANIVIEW') { - let price = ext[i].getElementsByTagName('Cpm'); - if (price && price.length == 1) { - ret.cpm = price[0].textContent; - } - break; - } + const response = converter.fromORTB({ response: body, request: data }); + + return response.bids.map(bid => { + const replacements = { + auctionPrice: bid.cpm, + auctionId: bid.requestId, + auctionBidId: bidResponse.impid, + auctionImpId: bidResponse.impid, + auctionSeatId: bid.seatBidId, + auctionAdId: bid.adId, + }; + + const bidAdmWithReplacedMacros = replaceMacros(bidResponse.adm, replacements); + + if (bid.mediaType === VIDEO) { + bid.vastXml = bidAdmWithReplacedMacros; + + if (bidResponse?.nurl) { + bid.vastUrl = replaceMacros(bidResponse.nurl, replacements); } + } else { + bid.ad = bidAdmWithReplacedMacros; } + + return bid; + }); + }, + + getUserSyncs(syncOptions, serverResponses) { + if (!serverResponses?.[0]?.body || serverResponses.error) { + return []; } - } - return ret; + + try { + const syncs = serverResponses[0].body.ext?.[BIDDER_CODE]?.sync; + + if (syncs) { + return getValidSyncs(syncs, syncOptions); + } + } catch (error) {} + + return []; + }, +}; + +function getValidSyncs(syncs, options) { + return syncs + .filter(sync => isSyncValid(sync, options)) + .map(sync => processSync(sync)) || []; } -function buildBanner(xmlStr, bidRequest, bidResponse) { - var rendererData = JSON.stringify({ - id: bidRequest.adUnitCode, - debug: window.location.href.indexOf('pbjsDebug') >= 0, - placement: bidRequest.bidRequest.adUnitCode, - width: bidResponse.width, - height: bidResponse.height, - vastXml: xmlStr, - bid: bidResponse, - config: bidRequest.bidRequest.params.rendererConfig - }); - var playerDomain = bidRequest.bidRequest.params.playerDomain || 'player.aniview.com'; - var ad = ''; - ad += '' - return ad; + +function isSyncValid(sync, options) { + return isPlainObject(sync) && + isStr(sync.url) && + (sync.e === 'inventory' || sync.e === 'sync') && + ((sync.t === 1 && options?.pixelEnabled) || (sync.t === 3 && options?.iframeEnabled)); } -function interpretResponse(serverResponse, bidRequest) { - let bidResponses = []; - if (serverResponse && serverResponse.body) { - if (serverResponse.error) { - return bidResponses; - } else { - try { - let bidResponse = {}; - if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') { - let mediaType = VIDEO; - if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && !bidRequest.bidRequest.mediaTypes[VIDEO]) { - mediaType = BANNER; - } - let xmlStr = serverResponse.body; - let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); - if (xml && xml.getElementsByTagName('parsererror').length == 0) { - let cpmData = getCpmData(xml); - if (cpmData.cpm > 0) { - bidResponse.requestId = bidRequest.data.bidId; - bidResponse.ad = ''; - bidResponse.cpm = cpmData.cpm; - bidResponse.width = bidRequest.data.AV_WIDTH; - bidResponse.height = bidRequest.data.AV_HEIGHT; - bidResponse.ttl = TTL; - bidResponse.creativeId = xml.getElementsByTagName('Ad') && xml.getElementsByTagName('Ad')[0] && xml.getElementsByTagName('Ad')[0].getAttribute('id') ? xml.getElementsByTagName('Ad')[0].getAttribute('id') : 'creativeId'; - bidResponse.currency = cpmData.currency; - bidResponse.netRevenue = true; - bidResponse.mediaType = mediaType; - if (mediaType === VIDEO) { - try { - var blob = new Blob([xmlStr], { - type: 'application/xml' - }); - bidResponse.vastUrl = window.URL.createObjectURL(blob); - } catch (ex) { - logError('Aniview Debug create vastXml error:\n\n' + ex); - } - bidResponse.vastXml = xmlStr; - if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { - bidResponse.renderer = newRenderer(bidRequest); - } - } else { - bidResponse.ad = buildBanner(xmlStr, bidRequest, bidResponse); - } - bidResponse.meta = { - advertiserDomains: [] - }; - - bidResponses.push(bidResponse); - } - } else {} - } else {} - } catch (e) {} - } - } else {} - return bidResponses; +function processSync(sync) { + return { url: sync.url, type: sync.t === 1 ? 'image' : 'iframe' }; } -function getSyncData(xml, options) { - let ret = []; - if (xml) { - let ext = xml.getElementsByTagName('Extensions'); - if (ext && ext.length > 0) { - ext = ext[0].getElementsByTagName('Extension'); - if (ext && ext.length > 0) { - for (var i = 0; i < ext.length; i++) { - if (ext[i].getAttribute('type') == 'ANIVIEW') { - let syncs = ext[i].getElementsByTagName('AdServingSync'); - if (syncs && syncs.length == 1) { - try { - let data = JSON.parse(syncs[0].textContent); - if (data && data.trackers && data.trackers.length) { - data = data.trackers; - for (var j = 0; j < data.length; j++) { - if (typeof data[j] === 'object' && - typeof data[j].url === 'string' && - (data[j].e === 'inventory' || data[j].e === 'sync') - ) { - if (data[j].t == 1 && options.pixelEnabled) { - ret.push({url: data[j].url, type: 'image'}); - } else { - if (data[j].t == 3 && options.iframeEnabled) { - ret.push({url: data[j].url, type: 'iframe'}); - } - } - } - } - } - } catch (e) {} - } - break; - } - } - } - } +function getSize(context, bid) { + const isVideoBid = context.mediaType === VIDEO; + let size = [640, 480]; + + if (isVideoBid && bid.mediaTypes?.video?.playerSize?.length) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (bid.sizes?.length) { + size = bid.sizes[0]; } - return ret; + + return { + width: size[0], + height: size[1], + }; } -function getUserSyncs(syncOptions, serverResponses) { - if (serverResponses && serverResponses[0] && serverResponses[0].body) { - if (serverResponses.error) { - return []; - } else { - try { - let xmlStr = serverResponses[0].body; - let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); - if (xml && xml.getElementsByTagName('parsererror').length == 0) { - let syncData = getSyncData(xml, syncOptions); - return syncData; - } - } catch (e) {} +// https://docs.prebid.org/dev-docs/modules/floors.html#example-getfloor-scenarios +function getFloor(bid, size, mediaType) { + if (!isFn(bid?.getFloor)) { + return null; + } + + try { + const bidFloor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType, // or '*' for all media types + size: [size.width, size.height], // or '*' for all sizes + }); + + if (isPlainObject(bidFloor) && !isNaN(bidFloor.floor) && bidFloor.currency === DEFAULT_CURRENCY) { + return bidFloor.floor; } + } catch {} + + return null; +} + +function replaceMacros(str, replacements) { + if (!replacements || !isStr(str)) { + return str; } + + return str + .replaceAll(`\${AUCTION_PRICE}`, replacements.auctionPrice || '') + .replaceAll(`\${AUCTION_ID}`, replacements.auctionId || '') + .replaceAll(`\${AUCTION_BID_ID}`, replacements.auctionBidId || '') + .replaceAll(`\${AUCTION_IMP_ID}`, replacements.auctionImpId || '') + .replaceAll(`\${AUCTION_SEAT_ID}`, replacements.auctionSeatId || '') + .replaceAll(`\${AUCTION_AD_ID}`, replacements.auctionAdId || ''); } -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo', 'ottadvisors', 'pgammedia'], - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; +function createRenderer(bidRequest) { + const config = {}; + const { params = {} } = bidRequest; + const playerDomain = params.playerDomain || DEFAULT_PLAYER_DOMAIN; + + if (params.AV_PUBLISHERID) { + config.AV_PUBLISHERID = params.AV_PUBLISHERID; + } + + if (params.AV_CHANNELID) { + config.AV_CHANNELID = params.AV_CHANNELID; + } + + const renderer = Renderer.install({ + url: `https://${playerDomain}/script/6.1/${RENDERER_FILENAME}`, + config, + loaded: false, + }); + + try { + renderer.setRender(avRenderer); + } catch (error) {} + + return renderer; +} + +function avRenderer(bid) { + bid.renderer.push(function() { + const eventsCallback = bid?.renderer?.handleVideoEvent ?? null; + const { ad, adId, adUnitCode, vastUrl, vastXml, width, height, params = [] } = bid; + + window.aniviewRenderer.renderAd({ + id: adUnitCode + '_' + adId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: adUnitCode, + config: params[0]?.rendererConfig, + width, + height, + vastUrl, + vastXml: vastXml || ad, + eventsCallback, + bid, + }); + }); +} registerBidder(spec); diff --git a/modules/aniviewBidAdapter.md b/modules/aniviewBidAdapter.md index 63c91ca009a..b067166b080 100644 --- a/modules/aniviewBidAdapter.md +++ b/modules/aniviewBidAdapter.md @@ -1,16 +1,16 @@ # Overview ``` -Module Name: ANIVIEW Bidder Adapter +Module Name: Aniview Bidder Adapter Module Type: Bidder Adapter Maintainer: support@aniview.com ``` # Description -Connects to ANIVIEW Ad server for bids. +Connects to Aniview Ad server for bids. -ANIVIEW bid adapter supports Banner and Video currently. +Aniview bid adapter supports Banner and Video currently. For more information about [Aniview](http://www.aniview.com), please contact [support@aniview.com](support@aniview.com). @@ -34,5 +34,3 @@ var videoAdUnit = [ }] }]; ``` - -``` diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index 0b261c80848..d87c84a05c5 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -2,267 +2,402 @@ import { spec } from 'modules/aniviewBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; const { expect } = require('chai'); -describe('ANIVIEW Bid Adapter Test', function () { +const PUBLISHER_ID_1 = 'publisher_id_1'; +const CHANNEL_ID_1 = 'channel_id_1'; +const PUBLISHER_ID_2 = 'publisher_id_2'; +const CHANNEL_ID_2 = 'channel_id_2'; +const BID_ID_1 = 'bid_id_1'; +const BID_ID_2 = 'bid_id_2'; +const BIDDER_REQUEST_ID = 'bidder_request_id'; +const CUSTOM_DOMAIN = 'example.com'; + +const BASE_URL = 'https://' + CUSTOM_DOMAIN + '/track' + + '?rtbbp=10' + + '&cpm=${AUCTION_PRICE}' + + '&aucid=${AUCTION_ID}' + + '&aucbid=${AUCTION_BID_ID}' + + '&limid=${AUCTION_IMP_ID}' + + '&aucseid=${AUCTION_SEAT_ID}' + + '&aucadid=${AUCTION_AD_ID}'; +const LURL = `${BASE_URL}&e=AV_M40&rcd=\${AUCTION_LOSS}`; +const NURL = `${BASE_URL}&e=AV_M4`; + +const VIDEO_VAST = `` +const BANNER_VAST = VIDEO_VAST; +const BANNER_HTML = '

HTML BANNER

'; + +const CURRENCY = 'USD'; +const PRICE = 10; +const FLOOR_PRICE = PRICE * 0.5; +const TTL = 600; + +const VIDEO_SIZE = { width: 640, height: 360 }; +const BANNER_SIZE = { width: 250, height: 250 }; + +const CUSTOM_RENDERER_URL = `https://${CUSTOM_DOMAIN}/script/6.1/prebidRenderer.js`; +const DEFAULT_RENDERER_URL = `https://player.aniview.com/script/6.1/prebidRenderer.js`; + +const MOCK = { + bidRequest: () => ({ + bidderCode: 'aniview', + auctionId: null, + bidderRequestId: BIDDER_REQUEST_ID, + bids: [ + { + bidder: 'aniview', + params: { + AV_PUBLISHERID: PUBLISHER_ID_1, + AV_CHANNELID: CHANNEL_ID_1, + playerDomain: CUSTOM_DOMAIN, + }, + mediaTypes: { + video: { + playerSize: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + context: 'outstream', + mimes: ['video/mpeg', 'video/mp4', 'application/javascript'], + } + }, + bidId: BID_ID_1, + bidderRequestId: BIDDER_REQUEST_ID, + sizes: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + }, + { + bidder: 'aniview', + params: { + AV_PUBLISHERID: PUBLISHER_ID_2, + AV_CHANNELID: CHANNEL_ID_2, + playerDomain: CUSTOM_DOMAIN, + }, + mediaTypes: { + video: { + playerSize: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + context: 'outstream', + mimes: ['video/mpeg', 'video/mp4', 'application/javascript'], + } + }, + bidId: BID_ID_2, + bidderRequestId: BIDDER_REQUEST_ID, + sizes: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + floorData: { + currency: CURRENCY, + floor: FLOOR_PRICE, + }, + getFloor: _ => ({ + currency: CURRENCY, + floor: FLOOR_PRICE, + }), + }, + ], + auctionStart: 1722343584268, + timeout: 1_000, + start: 1722343584269, + ortb2: { + source: {}, + site: { + page: 'http://localhost:8080/', + ref: 'http://localhost:8080/', + domain: 'http://localhost:8080', + publisher: { + domain: 'http://localhost:8080', + } + }, + device: { + w: 1800, + h: 1169, + language: 'en', + }, + }, + }), + + bidderResponse: () => ({ + body: { + id: 'bidder_response_id', + bidid: 'bidder_response_bid_id', + cur: CURRENCY, + ext: { + aniview: { + sync: [ + { url: 'https://iframe-1.example.com/sync', e: 'sync', pr: '14', t: 3 }, + { url: 'https://iframe-2.example.com/sync', e: 'sync', pr: '28', t: 3 }, + { url: 'https://image.example.com/sync', e: 'sync', pr: 'abc12', t: 1 } + ] + } + }, + seatbid: [ + { + seat: '', + bid: [ + { + adm: VIDEO_VAST, + adomain: [''], + id: 'seatbid_bid_id_1', + impid: BID_ID_1, + lurl: LURL, + nurl: NURL, + price: PRICE, + } + ] + } + ] + }, + }) +} + +describe('Aniview Bid Adapter', function () { const adapter = newBidder(spec); - describe('inherited functions', function () { + describe('Inherited function', function () { it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'aniview', - 'params': { - 'AV_PUBLISHERID': '123456', - 'AV_CHANNELID': '123456' - }, - 'adUnitCode': 'video1', - 'sizes': [[300, 250], [640, 480]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', - 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + let videoBidRequest; + + beforeEach(() => { + videoBidRequest = MOCK.bidRequest(); }); - it('should return false when required params are not passed', function () { - let invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = { - something: 'is wrong' - }; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + it('should return `true` when required params found', function () { + expect(spec.isBidRequestValid(videoBidRequest.bids[0])).to.be.true; + }); + + it('should return `false` when required params are wrong', function () { + videoBidRequest.bids[0].params = { something: 'is wrong' }; + + expect(spec.isBidRequestValid(videoBidRequest.bids[0])).to.be.false; }); }); describe('buildRequests', function () { - let bid2Requests = [ - { - 'bidder': 'aniview', - 'params': { - 'AV_PUBLISHERID': '123456', - 'AV_CHANNELID': '123456' - }, - 'adUnitCode': 'test1', - 'sizes': [[300, 250], [640, 480]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', - 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', - } - ]; - let bid1Request = [ - { - 'bidder': 'aniview', - 'params': { - 'AV_PUBLISHERID': '123456', - 'AV_CHANNELID': '123456' - }, - 'adUnitCode': 'test1', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', - 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', - } - ]; - - it('Test 2 requests', function () { - const requests = spec.buildRequests(bid2Requests); - expect(requests.length).to.equal(2); - const r1 = requests[0]; - const d1 = requests[0].data; - expect(d1).to.have.property('AV_PUBLISHERID'); - expect(d1.AV_PUBLISHERID).to.equal('123456'); - expect(d1).to.have.property('AV_CHANNELID'); - expect(d1.AV_CHANNELID).to.equal('123456'); - expect(d1).to.have.property('AV_WIDTH'); - expect(d1.AV_WIDTH).to.equal(300); - expect(d1).to.have.property('AV_HEIGHT'); - expect(d1.AV_HEIGHT).to.equal(250); - expect(d1).to.have.property('AV_URL'); - expect(d1).to.have.property('cb'); - expect(d1).to.have.property('s2s'); - expect(d1.s2s).to.equal('1'); - expect(d1).to.have.property('pbjs'); - expect(d1.pbjs).to.equal(1); - expect(r1).to.have.property('url'); - expect(r1.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); - const r2 = requests[1]; - const d2 = requests[1].data; - expect(d2).to.have.property('AV_PUBLISHERID'); - expect(d2.AV_PUBLISHERID).to.equal('123456'); - expect(d2).to.have.property('AV_CHANNELID'); - expect(d2.AV_CHANNELID).to.equal('123456'); - expect(d2).to.have.property('AV_WIDTH'); - expect(d2.AV_WIDTH).to.equal(640); - expect(d2).to.have.property('AV_HEIGHT'); - expect(d2.AV_HEIGHT).to.equal(480); - expect(d2).to.have.property('AV_URL'); - expect(d2).to.have.property('cb'); - expect(d2).to.have.property('s2s'); - expect(d2.s2s).to.equal('1'); - expect(d2).to.have.property('pbjs'); - expect(d2.pbjs).to.equal(1); - expect(r2).to.have.property('url'); - expect(r2.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + let videoBidRequest; + + beforeEach(() => { + videoBidRequest = MOCK.bidRequest(); }); - it('Test 1 request', function () { - const requests = spec.buildRequests(bid1Request); - expect(requests.length).to.equal(1); - const r = requests[0]; - const d = requests[0].data; - expect(d).to.have.property('AV_PUBLISHERID'); - expect(d.AV_PUBLISHERID).to.equal('123456'); - expect(d).to.have.property('AV_CHANNELID'); - expect(d.AV_CHANNELID).to.equal('123456'); - expect(d).to.have.property('AV_WIDTH'); - expect(d.AV_WIDTH).to.equal(640); - expect(d).to.have.property('AV_HEIGHT'); - expect(d.AV_HEIGHT).to.equal(480); - expect(d).to.have.property('AV_URL'); - expect(d).to.have.property('cb'); - expect(d).to.have.property('s2s'); - expect(d.s2s).to.equal('1'); - expect(d).to.have.property('pbjs'); - expect(d.pbjs).to.equal(1); - expect(r).to.have.property('url'); - expect(r.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + it('should return expected request object', function () { + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + + expect(bidRequest).to.exist.and.to.be.a('array').and.to.have.lengthOf(2); + + const { url, method, data } = bidRequest[0]; + const { ext, imp } = data; + + expect(url).equal('https://rtb.aniview.com/sspRTB2'); + expect(method).equal('POST'); + expect(imp[0].tagid).equal(CHANNEL_ID_1); + expect(imp[0].ext.aniview.AV_HEIGHT).equal(VIDEO_SIZE.height); + expect(imp[0].id).equal(videoBidRequest.bids[0].bidId); + expect(ext.aniview.pbjs).equal(1); }); - }); - describe('interpretResponse', function () { - let bidRequest = { - 'url': 'https://gov.aniview.com/api/adserver/vast3/', - 'data': { - 'bidId': '253dcb69fb2577', - AV_PUBLISHERID: '55b78633181f4603178b4568', - AV_CHANNELID: '55b7904d181f46410f8b4568', - } - }; - let serverResponse = {}; - serverResponse.body = 'FORDFORD00:00:15'; - - it('Check bid interpretResponse', function () { - const BIDDER_CODE = 'aniview'; - let bidResponses = spec.interpretResponse(serverResponse, bidRequest); - expect(bidResponses.length).to.equal(1); - let bidResponse = bidResponses[0]; - expect(bidResponse.requestId).to.equal(bidRequest.data.bidId); - expect(bidResponse.cpm).to.equal('2'); - expect(bidResponse.ttl).to.equal(600); - expect(bidResponse.currency).to.equal('USD'); - expect(bidResponse.netRevenue).to.equal(true); - expect(bidResponse.mediaType).to.equal('video'); - expect(bidResponse.meta.advertiserDomains).to.be.an('array').that.is.empty; + it('should have floor data inside imp', function () { + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + const imp = bidRequest[1].data.imp[0]; + + expect(imp.bidfloor).equal(FLOOR_PRICE); + expect(imp.bidfloorcur).equal(CURRENCY); }); - it('safely handles XML parsing failure from invalid bid response', function () { - let invalidServerResponse = {}; - invalidServerResponse.body = ''; + it('should not have floor data in imp if getFloor returns empty object', function () { + videoBidRequest.bids[1].getFloor = () => ({}); + + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + const imp = bidRequest[1].data.imp[0]; - let result = spec.interpretResponse(invalidServerResponse, bidRequest); - expect(result.length).to.equal(0); + expect(imp.bidfloor).not.exist; + expect(imp.bidfloorcur).not.exist; }); - it('handles nobid responses', function () { - let nobidResponse = {}; - nobidResponse.body = ''; + it('should use dev environment', function () { + const DEV_ENDPOINT = 'https://dev.aniview.com/sspRTB2'; + videoBidRequest.bids[0].params.dev = { endpoint: DEV_ENDPOINT }; - let result = spec.interpretResponse(nobidResponse, bidRequest); - expect(result.length).to.equal(0); + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + + expect(bidRequest[0].url).to.equal(DEV_ENDPOINT); }); + }); - it('should add renderer if outstream context', function () { - const bidRequest = spec.buildRequests([ - { - bidId: '253dcb69fb2577', - params: { - playerDomain: 'example.com', - AV_PUBLISHERID: '55b78633181f4603178b4568', - AV_CHANNELID: '55b7904d181f46410f8b4568' - }, - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream' - } - } - } - ])[0] - const bidResponse = spec.interpretResponse(serverResponse, bidRequest)[0] - - expect(bidResponse.renderer.url).to.equal('https://example.com/script/6.1/prebidRenderer.js') - expect(bidResponse.renderer.config.AV_PUBLISHERID).to.equal('55b78633181f4603178b4568') - expect(bidResponse.renderer.config.AV_CHANNELID).to.equal('55b7904d181f46410f8b4568') - expect(bidResponse.renderer.loaded).to.equal(false) - expect(bidResponse.width).to.equal(640) - expect(bidResponse.height).to.equal(480) + describe('interpretResponse', function () { + describe('Video format', function () { + let bidRequests, bidderResponse; + + beforeEach(function() { + const videoBidRequest = MOCK.bidRequest(); + + bidRequests = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + bidderResponse = MOCK.bidderResponse(); + }); + + it('should return empty bids array for empty response', function () { + const emptyResponse = bidderResponse.body = {}; + const bids = spec.interpretResponse(emptyResponse, bidRequests[0]); + + expect(bids).to.be.empty; + }); + + it('should return valid bids array', function () { + const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); + const bid = bids[0]; + + expect(bids.length).to.greaterThan(0); + expect(bid.vastXml).to.exist.and.to.not.have.string('${AUCTION_PRICE}'); + expect(bid.vastXml).to.have.string('cpm=' + PRICE); + expect(bid.vastUrl).to.exist.and.to.not.have.string('${AUCTION_PRICE}'); + expect(bid.vastUrl).to.have.string('cpm=' + PRICE); + expect(bid.requestId).to.equal(bidRequests[0].data.imp[0].id); + expect(bid.cpm).to.equal(PRICE); + expect(bid.ttl).to.equal(TTL); + expect(bid.currency).to.equal(CURRENCY); + expect(bid.netRevenue).to.equal(true); + expect(bid.mediaType).to.equal('video'); + expect(bid.meta.advertiserDomains).to.be.an('array'); + expect(bid.creativeId).to.exist; + expect(bid.width).to.exist; + expect(bid.height).to.exist; + }); + + it('should return bid without required properties if cpm less or equal 0', function () { + bidderResponse.body.seatbid[0].bid[0].price = 0; + + const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); + const bid = bids[0]; + + expect(bid.width).to.not.exist; + expect(bid.height).to.not.exist; + expect(bid.creativeId).to.not.exist; + }); + + it('should return empty bids array if no bids in response', function () { + bidderResponse.body.seatbid[0].bid = []; + + const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); + + expect(bids).to.exist.and.to.be.a('array').and.to.have.lengthOf(0); + }); + + it('should add renderer if outstream context', function () { + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.renderer.url).to.equal(CUSTOM_RENDERER_URL); + expect(bid.renderer.config.AV_PUBLISHERID).to.equal(PUBLISHER_ID_1); + expect(bid.renderer.config.AV_CHANNELID).to.equal(CHANNEL_ID_1); + expect(bid.renderer.loaded).to.equal(false); + expect(bid.width).to.equal(VIDEO_SIZE.width); + expect(bid.height).to.equal(VIDEO_SIZE.height); + }); + + it('should use default renderer domain', function () { + delete bidRequests[0].bids[0].params.playerDomain; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.renderer.url).to.equal(DEFAULT_RENDERER_URL); + }); + + it('should not add renderer if context is not outstream', function () { + bidRequests[0].bids[0].mediaTypes.video.context = 'instream'; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bidRequests[0].bids[0].mediaTypes.video.context).to.be.not.equal('outstream'); + expect(bid.renderer).to.not.exist; + }); }); - it('Support banner format', function () { - const bidRequest = spec.buildRequests([ - { - bidId: '253dcb69fb2577', - params: { - playerDomain: 'example.com', - AV_PUBLISHERID: '55b78633181f4603178b4568', - AV_CHANNELID: '55b7904d181f46410f8b4568' - }, - mediaTypes: { - banner: { - sizes: [[640, 480]], - } - } - } - ])[0] - const bidResponse = spec.interpretResponse(serverResponse, bidRequest)[0] + describe('Banner format', function () { + let bidRequests, bidderResponse; - expect(bidResponse.ad).to.have.string('https://example.com/script/6.1/prebidRenderer.js'); - expect(bidResponse.width).to.equal(640) - expect(bidResponse.height).to.equal(480) - }) + beforeEach(function() { + const bannerBidRequest = MOCK.bidRequest(); + + // Converting video bid request to banner bid request + + delete bannerBidRequest.bids[0].mediaTypes.video; + + bannerBidRequest.bids[0].sizes = [[BANNER_SIZE.width, BANNER_SIZE.height]]; + bannerBidRequest.bids[0].mediaTypes.banner = { + sizes: [ + [BANNER_SIZE.width, BANNER_SIZE.height], + [BANNER_SIZE.width * 2, BANNER_SIZE.height], + ], + }; + + bidRequests = spec.buildRequests(bannerBidRequest.bids, bannerBidRequest); + bidderResponse = MOCK.bidderResponse(); + }); + + it('should return valid banner bids (HTML)', function () { + bidderResponse.body.seatbid[0].bid[0].adm = BANNER_HTML; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.ad).to.exist; + expect(bid.cpm).to.equal(PRICE); + expect(bid.width).to.equal(BANNER_SIZE.width); + expect(bid.height).to.equal(BANNER_SIZE.height); + expect(bid.renderer).to.not.exist; + }); + + it('should return valid banner bids (VAST) with renderer', function () { + bidderResponse.body.seatbid[0].bid[0].adm = BANNER_VAST; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.ad).to.exist; + expect(bid.ad).to.not.have.string('${AUCTION_PRICE}'); + expect(bid.ad).to.have.string('cpm=' + PRICE); + expect(bid.cpm).to.equal(PRICE); + expect(bid.width).to.equal(BANNER_SIZE.width); + expect(bid.height).to.equal(BANNER_SIZE.height); + expect(bid.renderer.url).to.equal(CUSTOM_RENDERER_URL); + expect(bid.renderer.config.AV_PUBLISHERID).to.equal(PUBLISHER_ID_1); + expect(bid.renderer.config.AV_CHANNELID).to.equal(CHANNEL_ID_1); + expect(bid.renderer.loaded).to.equal(false); + }); + }); }); describe('getUserSyncs', function () { - let pixelUrl = 'https://sync.pixel.url/sync'; - function createBidResponse (pixelEvent, pixelType) { - let pixelStr = '{"url":"' + pixelUrl + '", "e":"' + pixelEvent + '", "t":' + pixelType + '}'; - return 'FORDFORD00:00:15'; - } - - it('Check get iframe sync pixels from response on inventory', function () { - let pixelEvent = 'inventory'; - let pixelType = '3'; - let bidResponse = createBidResponse(pixelEvent, pixelType); - let serverResponse = [ - {body: bidResponse} - ]; - let syncPixels = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); - expect(syncPixels.length).to.equal(1); - let pixel = syncPixels[0]; - expect(pixel.url).to.equal(pixelUrl); - expect(pixel.type).to.equal('iframe'); + let bidRequest, bidderResponse; + + beforeEach(function() { + const videoBidRequest = MOCK.bidRequest(); + + bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + bidderResponse = MOCK.bidderResponse(); }); - it('Check get image sync pixels from response on sync', function () { - let pixelEvent = 'sync'; - let pixelType = '1'; - let bidResponse = createBidResponse(pixelEvent, pixelType); - let serverResponse = [ - {body: bidResponse} - ]; - let syncPixels = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); - expect(syncPixels.length).to.equal(1); - let pixel = syncPixels[0]; - expect(pixel.url).to.equal(pixelUrl); - expect(pixel.type).to.equal('image'); + it('should get syncs from response', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(3); + }); + + it('should get only pixel syncs from response', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('should return empty array of syncs if no syncs in response', function () { + delete bidderResponse.body.ext.aniview.sync + + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: false }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(0); + }); + + it('should return empty array of syncs if no body in response', function () { + delete bidderResponse.body + + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(0); }); }); }); From 1f0dba269481e11bccbc61c4d2c0bd54c318f1ec Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Mon, 21 Oct 2024 16:31:07 +0100 Subject: [PATCH 0595/1097] 51Degrees RTD submodule: add `crossorigin` attribute to `script` tag (#12329) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/51DegreesRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js index 573c79902c6..4f8024aa84f 100644 --- a/modules/51DegreesRtdProvider.js +++ b/modules/51DegreesRtdProvider.js @@ -277,7 +277,7 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user logMessage('reqBidsConfigObj: ', reqBidsConfigObj); callback(); }); - }); + }, document, {crossOrigin: 'anonymous'}); } catch (error) { // In case of an error, log it and continue logError(error); From 18ae4dca6ed5b2b79384cb98f6020f8e1fd8d556 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 21 Oct 2024 11:11:31 -0700 Subject: [PATCH 0596/1097] ads_interactiveBidAdapter: fix broken test (#12350) --- test/spec/modules/ads_interactiveBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js index ade03a9c49b..273df230287 100644 --- a/test/spec/modules/ads_interactiveBidAdapter_spec.js +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -134,7 +134,7 @@ describe('AdsInteractiveBidAdapter', function () { it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys( + expect(data).to.include.all.keys( 'deviceWidth', 'deviceHeight', 'device', From fa44eace5869922db985f299a55ab77461855712 Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:31:17 +0200 Subject: [PATCH 0597/1097] Criteo bid adapter: write cookie only on TLD+1 (#12323) Avoid writing cookies on anything higher than TLD+1 level. e.g. We should write on orange.fr and not on actu.orange.fr --- modules/criteoBidAdapter.js | 25 +++++++++++++--- test/spec/modules/criteoBidAdapter_spec.js | 35 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index bfb703b8a8e..d44f947dd3e 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -278,10 +278,10 @@ export const spec = { if (response.optout) { deleteFromAllStorages(BUNDLE_COOKIE_NAME); - saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR); + saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR, refererInfo.domain); } else { if (response.bundle) { - saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR); + saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR, refererInfo.domain); } if (response.callbacks) { @@ -428,12 +428,29 @@ function readFromAllStorages(name) { return fromCookie || fromLocalStorage || undefined; } -function saveOnAllStorages(name, value, expirationTimeHours) { +function saveOnAllStorages(name, value, expirationTimeHours, domain) { const date = new Date(); date.setTime(date.getTime() + (expirationTimeHours * 60 * 60 * 1000)); const expires = `expires=${date.toUTCString()}`; - storage.setCookie(name, value, expires); + const subDomains = domain.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + + try { + storage.setCookie(name, value, expires, null, '.' + domain); + + // Try to read the cookie to check if we wrote it + const check = storage.getCookie(name); + if (check && check === value) { + break; + } + } catch (error) { + + } + } + storage.setDataInLocalStorage(name, value); } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 123117027ea..30de96e33d0 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -123,6 +123,7 @@ describe('The Criteo bidding adapter', function () { getCookieStub, setCookieStub, getDataFromLocalStorageStub, + setDataInLocalStorageStub, removeDataFromLocalStorageStub, triggerPixelStub; @@ -146,6 +147,7 @@ describe('The Criteo bidding adapter', function () { getCookieStub = sinon.stub(storage, 'getCookie'); setCookieStub = sinon.stub(storage, 'setCookie'); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); removeDataFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage'); triggerPixelStub = sinon.stub(utils, 'triggerPixel'); @@ -160,6 +162,7 @@ describe('The Criteo bidding adapter', function () { getCookieStub.restore(); setCookieStub.restore(); getDataFromLocalStorageStub.restore(); + setDataInLocalStorageStub.restore(); removeDataFromLocalStorageStub.restore(); triggerPixelStub.restore(); }); @@ -329,6 +332,38 @@ describe('The Criteo bidding adapter', function () { done(); }, 0); }); + + it('should write cookie only on TLD+1 level', function(done) { + const cookies = {}; + + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); + + setCookieStub.callsFake((name, value, expires, _, domain) => { + if (domain != '.com') { + cookies[name] = value; + } + }); + + getCookieStub.callsFake((name) => cookies[name]); + + const event = new MessageEvent('message', { + data: { + requestId: '123456', + bundle: 'bundle' + }, + origin: 'https://gum.criteo.com' + }); + + window.dispatchEvent(event); + setTimeout(() => { + expect(setCookieStub.calledWith('cto_bundle', 'bundle', sinon.match.string, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', 'bundle', sinon.match.string, null, '.abc.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', 'bundle', sinon.match.string, null, '.www.abc.com')).to.be.false; + expect(cookies).to.deep.equal({ 'cto_bundle': 'bundle' }); + + done(); + }, 0); + }); }); describe('isBidRequestValid', function () { From c90f9b3cd75b9cfa4b5701ef8d4dd0d2fedee4c8 Mon Sep 17 00:00:00 2001 From: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:16:34 +0530 Subject: [PATCH 0598/1097] docereeAdManager Bid Adapter : updated bid adapter (#12333) * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Update docereeAdManagerBidAdapter.js * added test cases for payload formation in DocereeAdManager * Added support for publisherUrl * added some parameters * Added support for TCF 2.2 * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Written test cases for new method implemented. * indentation issues resolved * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js --------- Co-authored-by: lokesh-doceree Co-authored-by: Patrick McCann --- modules/docereeAdManagerBidAdapter.js | 12 +++++++++++- .../modules/docereeAdManagerBidAdapter_spec.js | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index d7a28d6b788..e26045c8f1f 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -70,10 +70,20 @@ export const spec = { }, }; +export function getPageUrl() { + let url = ''; + try { + url = window.location.href; + } catch (error) { + } + return url; +} + export function getPayload(bid, userData, bidderRequest) { if (!userData || !bid) { return false; } + const { bidId, params } = bid; const { placementId, publisherUrl } = params; const { @@ -121,7 +131,7 @@ export function getPayload(bid, userData, bidderRequest) { dob: dob || '', userconsent: 1, mobile: mobile || '', - pageurl: publisherUrl || '' + pageurl: publisherUrl || getPageUrl() || '' }; try { diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js index 268e30f542d..704b9c48d3a 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec, getPayload } from '../../../modules/docereeAdManagerBidAdapter.js'; +import { spec, getPayload, getPageUrl } from '../../../modules/docereeAdManagerBidAdapter.js'; import { config } from '../../../src/config.js'; +import * as utils from '../../../src/utils.js'; describe('docereeadmanager', function () { config.setConfig({ @@ -125,8 +126,15 @@ describe('docereeadmanager', function () { }); }); - describe('payload', function() { - it('should return payload with the correct data', function() { + describe('getPageUrl', function () { + it('should return an url string', function () { + const result = getPageUrl(); + expect(result).to.equal(utils.getWindowSelf().location.href); + }); + }); + + describe('payload', function () { + it('should return payload with the correct data', function () { const data = { userId: 'xxxxx', email: 'xxxx@mail.com', @@ -148,7 +156,7 @@ describe('docereeadmanager', function () { platformUid: 'Xx.xxx.xxxxxx', mobile: 'XXXXXXXXXX', } - bid = {...bid, params: {...bid.params, placementId: 'DOC-19-1'}} + bid = { ...bid, params: { ...bid.params, placementId: 'DOC-19-1' } } const buildRequests = { gdprConsent: { consentString: 'COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', @@ -156,6 +164,7 @@ describe('docereeadmanager', function () { } } const payload = getPayload(bid, data, buildRequests); + const payloadData = payload.data; expect(payloadData).to.have.all.keys( 'userid', From 24306f3c9d25058c7df0137829dfd85fa9dbe42d Mon Sep 17 00:00:00 2001 From: Solta Date: Tue, 22 Oct 2024 16:12:23 +0300 Subject: [PATCH 0599/1097] Kimberlite Bidder Adapter: expand auction price & currency macros (#12325) * Expand price & currency macro * lint fix --------- Co-authored-by: Oleg Stolonogov --- modules/kimberliteBidAdapter.js | 16 +- .../spec/modules/kimberliteBidAdapter_spec.js | 187 ++++++++++-------- 2 files changed, 117 insertions(+), 86 deletions(-) diff --git a/modules/kimberliteBidAdapter.js b/modules/kimberliteBidAdapter.js index 6ad8b9eda05..fbb9974d52d 100644 --- a/modules/kimberliteBidAdapter.js +++ b/modules/kimberliteBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { deepSetValue } from '../src/utils.js'; +import { deepSetValue, replaceMacros } from '../src/utils.js'; import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; const VERSION = '1.1.0'; @@ -45,6 +45,12 @@ const converter = ortbConverter({ context.mediaType = type; } + bid.adm = expandAuctionMacros(bid.adm, bid.price, context.ortbResponse.cur); + + if (bid.nurl && bid.nurl != '') { + bid.nurl = expandAuctionMacros(bid.nurl, bid.price, context.ortbResponse.cur); + } + const bidResponse = buildBidResponse(bid, context); return bidResponse; }, @@ -85,4 +91,12 @@ export const spec = { } }; +export function expandAuctionMacros(str, price, currency) { + if (!str) return; + + const defaultCurrency = 'RUB'; + + return replaceMacros(str, {AUCTION_PRICE: price, AUCTION_CURRENCY: currency || defaultCurrency}); +}; + registerBidder(spec); diff --git a/test/spec/modules/kimberliteBidAdapter_spec.js b/test/spec/modules/kimberliteBidAdapter_spec.js index c0394a2090b..739a970603c 100644 --- a/test/spec/modules/kimberliteBidAdapter_spec.js +++ b/test/spec/modules/kimberliteBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { spec, ENDPOINT_URL } from 'modules/kimberliteBidAdapter.js'; +import { spec, ENDPOINT_URL, expandAuctionMacros } from 'modules/kimberliteBidAdapter.js'; import { assert } from 'chai'; import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; @@ -144,100 +144,117 @@ describe('kimberliteBidAdapter', function () { let bidderResponse, bidderRequest, bidRequest, expectedBids; const requestId = '07fba8b0-8812-4dc6-b91e-4a525d81729c'; - const bannerAdm = 'landing'; - const videoAdm = 'test vast'; + const bannerAdm = 'landing'; + const videoAdm = 'http://video-test.landing.com?p=${AUCTION_PRICE}&c=${AUCTION_CURRENCY}test vast'; + const nurl = 'http://nurl.landing.com?p=${AUCTION_PRICE}&c=${AUCTION_CURRENCY}'; + const nurlPixel = `
`; + + const currencies = [ + undefined, + 'USD' + ]; + + currencies.forEach(function(currency) { + beforeEach(function () { + bidderResponse = { + body: { + id: requestId, + seatbid: [{ + bid: [ + { + crid: 1, + impid: 1, + price: 1, + adm: bannerAdm, + nurl: nurl + }, + { + crid: 2, + impid: 2, + price: 1, + adm: videoAdm + } + ] + }] + } + }; - beforeEach(function () { - bidderResponse = { - body: { - id: requestId, - seatbid: [{ - bid: [ - { - crid: 1, - impid: 1, - price: 1, - adm: bannerAdm + bidderRequest = { + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', + }, + bids: [ + { + bidId: 1, + mediaTypes: { + banner: {sizes: sizes} }, - { - crid: 2, - impid: 2, - price: 1, - adm: videoAdm + params: { + placementId: 'test-placement' } - ] - }] - } - }; - - bidderRequest = { - refererInfo: { - domain: 'example.com', - page: 'https://www.example.com/test.html', - }, - bids: [ - { - bidId: 1, - mediaTypes: { - banner: {sizes: sizes} }, - params: { - placementId: 'test-placement' - } - }, - { - bidId: 2, - mediaTypes: { - video: { - mimes: ['video/mp4'] + { + bidId: 2, + mediaTypes: { + video: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' } - }, - params: { - placementId: 'test-placement' } - } - ] - }; + ] + }; - expectedBids = [ - { - mediaType: 'banner', - requestId: 1, - cpm: 1, - creative_id: 1, - creativeId: 1, - ttl: 300, - netRevenue: true, - ad: bannerAdm, - meta: {} - }, - { - mediaType: 'video', - requestId: 2, - cpm: 1, - creative_id: 2, - creativeId: 2, - ttl: 300, - netRevenue: true, - vastXml: videoAdm, - meta: {} - }, - ]; + expectedBids = [ + { + mediaType: 'banner', + requestId: 1, + cpm: 1, + creative_id: 1, + creativeId: 1, + ttl: 300, + netRevenue: true, + ad: bannerAdm + nurlPixel, + meta: {} + }, + { + mediaType: 'video', + requestId: 2, + cpm: 1, + creative_id: 2, + creativeId: 2, + ttl: 300, + netRevenue: true, + vastXml: videoAdm, + meta: {} + }, + ]; - bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); - }); + if (currency) { + expectedBids[0].currency = expectedBids[1].currency = bidderResponse.body.cur = currency; + } - it('pass on valid request', function () { - const bids = spec.interpretResponse(bidderResponse, bidRequest); - assert.deepEqual(bids[0], expectedBids[0]); - if (FEATURES.VIDEO) { - assert.deepEqual(bids[1], expectedBids[1]); - } - }); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + }); + + it('pass on valid request', function () { + const bids = spec.interpretResponse(bidderResponse, bidRequest); + expectedBids[0].ad = expandAuctionMacros(expectedBids[0].ad, expectedBids[0].cpm, bidderResponse.body.cur); + assert.deepEqual(bids[0], expectedBids[0]); + if (FEATURES.VIDEO) { + expectedBids[1].vastXml = + expandAuctionMacros(expectedBids[1].vastXml, expectedBids[1].cpm, bidderResponse.body.cur); + assert.deepEqual(bids[1], expectedBids[1]); + } + }); - it('fails on empty response', function () { - const bids = spec.interpretResponse({body: ''}, bidRequest); - assert.empty(bids); + it('fails on empty response', function () { + const bids = spec.interpretResponse({body: ''}, bidRequest); + assert.empty(bids); + }); }); }); }); From d60736431bcc08c42193969ab3b0ed8815fd5eb8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 22 Oct 2024 08:30:14 -0700 Subject: [PATCH 0600/1097] Core: fix missing BID_WON for some native ad units (#12349) --- src/adRendering.js | 4 ++++ src/secureCreatives.js | 13 ++++++++++--- test/spec/unit/secureCreatives_spec.js | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/adRendering.js b/src/adRendering.js index 6f268ca1aec..47f335664e5 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -219,6 +219,10 @@ export function deferRendering(bidResponse, renderFn) { if (!bidResponse.deferRendering) { renderIfDeferred(bidResponse); } + markWinner(bidResponse); +} + +export function markWinner(bidResponse) { if (!WINNERS.has(bidResponse)) { WINNERS.add(bidResponse); markWinningBid(bidResponse); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index a69585d28e8..baa8a82eec7 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -7,7 +7,14 @@ import {getAllAssetsMessage, getAssetMessage} from './native.js'; import {BID_STATUS, MESSAGES} from './constants.js'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; import {find, includes} from './polyfill.js'; -import {deferRendering, getBidToRender, handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; +import { + deferRendering, + getBidToRender, + handleCreativeEvent, + handleNativeMessage, + handleRender, + markWinner +} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; @@ -102,7 +109,6 @@ function handleNativeRequest(reply, data, adObject) { logError(`Cannot find ad for x-origin event request: '${data.adId}'`); return; } - switch (data.action) { case 'assetRequest': deferRendering(adObject, () => reply(getAssetMessage(data, adObject))); @@ -111,7 +117,8 @@ function handleNativeRequest(reply, data, adObject) { deferRendering(adObject, () => reply(getAllAssetsMessage(data, adObject))); break; default: - handleNativeMessage(data, adObject, {resizeFn: getResizer(data.adId, adObject)}) + handleNativeMessage(data, adObject, {resizeFn: getResizer(data.adId, adObject)}); + markWinner(adObject); } } diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 64213073eee..aeb78501d51 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -411,6 +411,21 @@ describe('secureCreatives', () => { }); }); + it('should fire BID_WON when no asset is requested', () => { + pushBidResponseToAuction({}); + const data = { + adId: bidId, + message: 'Prebid Native', + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + }); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + }); + }) + describe('resizing', () => { let container, slot; before(() => { From cce8964562569c80551a1507d6c8cf1445ac67b2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 22 Oct 2024 09:30:14 -0700 Subject: [PATCH 0601/1097] Core: do not send native targeting keys on ortb requests (#12348) --- src/native.js | 8 ++++---- test/spec/native_spec.js | 22 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/native.js b/src/native.js index 05347e47f1f..a641beb71d5 100644 --- a/src/native.js +++ b/src/native.js @@ -364,7 +364,7 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { let keyValues = {}; const adUnit = index.getAdUnit(bid); - const globalSendTargetingKeys = deepAccess( + const globalSendTargetingKeys = adUnit?.nativeParams?.ortb == null && deepAccess( adUnit, `nativeParams.sendTargetingKeys` ) !== false; @@ -806,20 +806,20 @@ export function toOrtbNativeResponse(legacyResponse, ortbRequest) { export function toLegacyResponse(ortbResponse, ortbRequest) { const legacyResponse = {}; const requestAssets = ortbRequest?.assets || []; - legacyResponse.clickUrl = ortbResponse.link.url; + legacyResponse.clickUrl = ortbResponse.link?.url; legacyResponse.privacyLink = ortbResponse.privacy; for (const asset of ortbResponse?.assets || []) { const requestAsset = requestAssets.find(reqAsset => asset.id === reqAsset.id); if (asset.title) { legacyResponse.title = asset.title.text; } else if (asset.img) { - legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = { + legacyResponse[requestAsset?.img?.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = { url: asset.img.url, width: asset.img.w, height: asset.img.h }; } else if (asset.data) { - legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value; + legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset?.data?.type]]] = asset.data.value; } } diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index a611a9dd789..877306c1c04 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -22,7 +22,7 @@ import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '.. import {auctionManager} from '../../src/auctionManager.js'; import {getRenderingData} from '../../src/adRendering.js'; import {getCreativeRendererSource} from '../../src/creativeRenderers.js'; -import {deepClone} from '../../src/utils.js'; +import {deepClone, deepSetValue} from '../../src/utils.js'; const utils = require('src/utils'); const bid = { @@ -211,6 +211,18 @@ describe('native.js', function () { expect(targeting.hb_native_foo).to.equal(bid.native.foo); }); + it('does not include targeting keys if request is ortb', () => { + const targeting = getNativeTargeting(bid, deps({ + adUnitId: bid.adUnitId, + nativeParams: { + ortb: { + assets: [{id: 1, type: '2'}] + } + } + })); + expect(Object.keys(targeting)).to.eql([]); + }); + it('can get targeting from null native keys', () => { const targeting = getNativeTargeting({...bid, native: {...bid.native, displayUrl: null}}); expect(targeting.hb_native_displayurl).to.not.be.ok; @@ -647,6 +659,14 @@ describe('native.js', function () { expect(actual.impressionTrackers).to.contain('https://sampleurl.com'); expect(actual.impressionTrackers).to.contain('https://sample-imp.com'); }); + ['img.type', 'title.text', 'data.type'].forEach(prop => { + it(`does not choke when the request does not have ${prop}, but the response does`, () => { + const request = {ortb: {assets: [{id: 1}]}}; + const response = {ortb: {assets: [{id: 1}]}}; + deepSetValue(response, `assets.0.${prop}`, 'value'); + toLegacyResponse(response, request); + }) + }) }); describe('setNativeResponseProperties', () => { From 2fb16e22a6432fed0e04f1dcf00282aaaacbb939 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 22 Oct 2024 11:04:59 -0700 Subject: [PATCH 0602/1097] gptPreAuction: fix missing gpid when using mcmEnabled (#12356) --- modules/gptPreAuction.js | 16 +++++++---- test/spec/modules/gptPreAuction_spec.js | 38 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 29b9257d325..7052e6f97ea 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -76,21 +76,25 @@ export const appendGptSlots = adUnits => { return acc; }, {}); + const adUnitPaths = {}; + window.googletag.pubads().getSlots().forEach(slot => { const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching ? customGptSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)); if (matchingAdUnitCode) { + const path = adUnitPaths[matchingAdUnitCode] = slot.getAdUnitPath(); const adserver = { name: 'gam', - adslot: sanitizeSlotPath(slot.getAdUnitPath()) + adslot: sanitizeSlotPath(path) }; adUnitMap[matchingAdUnitCode].forEach((adUnit) => { deepSetValue(adUnit, 'ortb2Imp.ext.data.adserver', Object.assign({}, adUnit.ortb2Imp?.ext?.data?.adserver, adserver)); }); } }); + return adUnitPaths; }; const sanitizeSlotPath = (path) => { @@ -103,7 +107,7 @@ const sanitizeSlotPath = (path) => { return path; } -const defaultPreAuction = (adUnit, adServerAdSlot) => { +const defaultPreAuction = (adUnit, adServerAdSlot, adUnitPath) => { const context = adUnit.ortb2Imp.ext.data; // use pbadslot if supplied @@ -117,7 +121,7 @@ const defaultPreAuction = (adUnit, adServerAdSlot) => { } // find all GPT slots with this name - var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adServerAdSlot); + var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adUnitPath); if (gptSlots.length === 0) { return; // should never happen @@ -167,7 +171,7 @@ function warnDeprecation(adUnit) { } export const makeBidRequestsHook = (fn, adUnits, ...args) => { - appendGptSlots(adUnits); + const adUnitPaths = appendGptSlots(adUnits); const { useDefaultPreAuction, customPreAuction } = _currentConfig; adUnits.forEach(adUnit => { // init the ortb2Imp if not done yet @@ -190,9 +194,9 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { let adserverSlot = deepAccess(context, 'data.adserver.adslot'); let result; if (customPreAuction) { - result = customPreAuction(adUnit, adserverSlot); + result = customPreAuction(adUnit, adserverSlot, adUnitPaths[adUnit.code]); } else if (useDefaultPreAuction) { - result = defaultPreAuction(adUnit, adserverSlot); + result = defaultPreAuction(adUnit, adserverSlot, adUnitPaths[adUnit.code]); } if (result) { context.gpid = context.data.pbadslot = result; diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 88062f2b785..bc41c811e7d 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -209,6 +209,17 @@ describe('GPT pre-auction module', () => { expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: '/12345/slotCode2' }); }); + it('returns full ad unit path even if mcmEnabled is true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + expect(appendGptSlots([adUnit])).to.eql({ + '/12345,21212/slot': '/12345,21212/slot' + }) + }) + it('will not trim child id if mcmEnabled is not set to true', () => { window.googletag.pubads().setSlots([ makeSlot({ code: '/12345,21212/slotCode1', divId: 'div1' }), @@ -459,6 +470,23 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); + it('should pass full slot path to customPreAuction when mcmEnabled is true', () => { + const customPreAuction = sinon.stub(); + config.setConfig({ + gptPreAuction: { + enabled: true, + mcmEnabled: true, + customPreAuction + } + }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + makeBidRequestsHook(sinon.stub(), [adUnit]); + sinon.assert.calledWith(customPreAuction, adUnit, '/12345/slot', adUnit.code); + }); + it('should use useDefaultPreAuction logic', () => { config.setConfig({ gptPreAuction: { @@ -540,6 +568,16 @@ describe('GPT pre-auction module', () => { runMakeBidRequests(testAdUnits); expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); + + it('sets gpid when mcmEnabled: true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + makeBidRequestsHook(sinon.stub(), [adUnit]); + expect(adUnit.ortb2Imp.ext.gpid).to.eql('/12345/slot'); + }); }); describe('pps gpt config', () => { From 75f2a1ffb6040a7730b40fb0568a88b9e4a54f2e Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Tue, 22 Oct 2024 20:07:49 +0100 Subject: [PATCH 0603/1097] Grid bid adapter add ortb2 device (#11786) * Grid Bid Adapter: Add full ORTB2 device data to request payload * Grid Bid Adapter: Add test to verify presence of ORTB2 device data in request --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/gridBidAdapter.js | 5 +++++ test/spec/modules/gridBidAdapter_spec.js | 27 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 4c1876dfb6f..8d10a0f18d2 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -275,6 +275,11 @@ export const spec = { userExt.device = { ...ortb2UserExtDevice }; } + // if present, add device data object from ortb2 to the request + if (bidderRequest?.ortb2?.device) { + request.device = bidderRequest.ortb2.device; + } + if (userIdAsEids && userIdAsEids.length) { userExt = userExt || {}; userExt.eids = [...userIdAsEids]; diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 4e13b1957b5..18f1f7aa7c8 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -950,6 +950,33 @@ describe('TheMediaGrid Adapter', function () { expect(payload.tmax).to.equal(null); }) + it('should add ORTB2 device data to the request', function () { + const bidderRequestWithDevice = { + ...bidderRequest, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }, + }; + + const [request] = spec.buildRequests([bidRequests[0]], bidderRequestWithDevice); + const payload = parseRequest(request.data); + + expect(payload.device).to.deep.equal(bidderRequestWithDevice.ortb2.device); + }); + describe('floorModule', function () { const floorTestData = { 'currency': 'USD', From ac44557b92bec43baeb25c529454d9c5f07836a8 Mon Sep 17 00:00:00 2001 From: dLepetynskyiIntentiq Date: Wed, 23 Oct 2024 14:05:25 +0300 Subject: [PATCH 0604/1097] IntentIq ID & Analytics Modules : manual reporting, bug fixes, refactoring (#12314) * update intentIqAnalyticsAdapter.js && intentIqIdSystem.js * fix lint issues * fix tests * move info * resolve issues * update storeFirstPartyData * remove unused code * update defineEmptyDataAndFireCallback * update fix lint * update reportExternalWin * small fixes * update test && add docs --------- Co-authored-by: DimaIntentIQ Co-authored-by: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> --- .../intentIqConstants/intentIqConstants.js | 10 ++ modules/intentIqAnalyticsAdapter.js | 121 ++++++++++++------ modules/intentIqAnalyticsAdapter.md | 62 ++++++++- modules/intentIqIdSystem.js | 99 +++++++------- modules/intentIqIdSystem.md | 25 ++-- src/events.js | 1 + .../modules/intentIqAnalyticsAdapter_spec.js | 34 +++-- test/spec/modules/intentIqIdSystem_spec.js | 25 +++- 8 files changed, 264 insertions(+), 113 deletions(-) create mode 100644 libraries/intentIqConstants/intentIqConstants.js diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js new file mode 100644 index 00000000000..46c466e936f --- /dev/null +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -0,0 +1,10 @@ +export const FIRST_PARTY_KEY = '_iiq_fdata'; + +export const WITH_IIQ = 'A'; +export const WITHOUT_IIQ = 'B'; +export const NOT_YET_DEFINED = 'U'; +export const OPT_OUT = 'O'; +export const BLACK_LIST = 'L'; +export const CLIENT_HINTS_KEY = '_iiq_ch'; +export const EMPTY = 'EMPTY' +export const VERSION = 0.21 diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 76615730dd5..7962080a138 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -1,24 +1,21 @@ -import { logInfo, logError, getWindowSelf, getWindowTop, getWindowLocation } from '../src/utils.js'; +import {getWindowLocation, getWindowSelf, getWindowTop, logError, logInfo} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { config } from '../src/config.js'; -import { EVENTS } from '../src/constants.js'; -import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; -import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; +import {EVENTS} from '../src/constants.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import {detectBrowser} from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js'; const MODULE_NAME = 'iiqAnalytics' const analyticsType = 'endpoint'; const defaultUrl = 'https://reports.intentiq.com/report'; -const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME }); +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); const prebidVersion = '$prebid.version$'; export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); -const FIRST_PARTY_KEY = '_iiq_fdata'; -const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -const JSVERSION = 0.2 - const PARAMS_NAMES = { abTestGroup: 'abGroup', pbPauseUntil: 'pbPauseUntil', @@ -55,7 +52,7 @@ const PARAMS_NAMES = { firstPartyId: 'pcid' }; -let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl, analyticsType}), { initOptions: { lsValueInitialized: false, partner: null, @@ -64,13 +61,16 @@ let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analytics dataInLs: null, eidl: null, lsIdsInitialized: false, - manualReport: false + manualWinReportEnabled: false }, - track({ eventType, args }) { + track({eventType, args}) { switch (eventType) { case BID_WON: bidWon(args); break; + case BID_REQUESTED: + defineGlobalVariableName(); + break; default: break; } @@ -79,7 +79,8 @@ let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analytics // Events needed const { - BID_WON + BID_WON, + BID_REQUESTED } = EVENTS; function readData(key) { @@ -113,6 +114,7 @@ function initLsValues() { iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; } iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqArr[0].params.manualWinReportEnabled || false } } @@ -123,20 +125,27 @@ function initReadLsIds() { if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; } - let iData = readData(FIRST_PARTY_DATA_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); - if (iData) { + const partnerData = readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); + const clientsHints = readData(CLIENT_HINTS_KEY) || ''; + + if (partnerData) { iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; - let pData = JSON.parse(iData); + let pData = JSON.parse(partnerData); + iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; } + + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints } catch (e) { logError(e) } } -function bidWon(args) { - if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { initLsValues(); } +function bidWon(args, isReportExternal) { + if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { + initLsValues(); + } if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner == -1) return; @@ -146,12 +155,31 @@ function bidWon(args) { return; } - if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { initReadLsIds(); } - if (!iiqAnalyticsAnalyticsAdapter.initOptions.manualReport) { - ajax(constructFullUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + initReadLsIds(); + } + if ((isReportExternal && iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled) || (!isReportExternal && !iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled)) { + ajax(constructFullUrl(preparePayload(args, true)), undefined, null, {method: 'GET'}); + logInfo('IIQ ANALYTICS -> BID WON') + return true; + } + return false; +} + +function defineGlobalVariableName() { + function reportExternalWin(args) { + return bidWon(args, true) } - logInfo('IIQ ANALYTICS -> BID WON') + let partnerId = 0 + const userConfig = config.getConfig('userSync.userIds') + + if (userConfig) { + const iiqArr = userConfig.filter(m => m.name == 'intentIqId'); + if (iiqArr.length) partnerId = iiqArr[0].params.partner + } + + window[`intentIqAnalyticsAdapter_${partnerId}`] = {reportExternalWin: reportExternalWin} } function getRandom(start, end) { @@ -160,11 +188,11 @@ function getRandom(start, end) { export function preparePayload(data) { let result = getDefaultDataObject(); - + readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; result[PARAMS_NAMES.prebidVersion] = prebidVersion; result[PARAMS_NAMES.referrer] = getReferrer(); - + result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause; result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup == 'A'; @@ -187,13 +215,27 @@ function fillEidsData(result) { } function fillPrebidEventData(eventData, result) { - if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; } - if (eventData.cpm) { result.cpm = eventData.cpm; } - if (eventData.currency) { result.currency = eventData.currency; } - if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; } - if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; } - if (eventData.status) { result.status = eventData.status; } - if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; } + if (eventData.bidderCode) { + result.bidderCode = eventData.bidderCode; + } + if (eventData.cpm) { + result.cpm = eventData.cpm; + } + if (eventData.currency) { + result.currency = eventData.currency; + } + if (eventData.originalCpm) { + result.originalCpm = eventData.originalCpm; + } + if (eventData.originalCurrency) { + result.originalCurrency = eventData.originalCurrency; + } + if (eventData.status) { + result.status = eventData.status; + } + if (eventData.auctionId) { + result.prebidAuctionId = eventData.auctionId; + } result.biddingPlatformId = 1; result.partnerAuctionId = 'BW'; @@ -206,7 +248,7 @@ function getDefaultDataObject() { 'partnerAuctionId': 'BW', 'reportSource': 'pbjs', 'abGroup': 'U', - 'jsversion': JSVERSION, + 'jsversion': VERSION, 'partnerId': -1, 'biddingPlatformId': 1, 'idls': false, @@ -224,18 +266,19 @@ function constructFullUrl(data) { ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid) ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + '&agid=' + REPORTER_ID + - '&jsver=' + JSVERSION + + '&jsver=' + VERSION + '&vrref=' + getReferrer() + '&source=pbjs' + - '&payload=' + JSON.stringify(report) + '&payload=' + JSON.stringify(report) + + '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints } export function getReferrer() { try { if (getWindowSelf() === getWindowTop()) { - return getWindowLocation().href; + return encodeURIComponent(getWindowLocation().href); } else { - return getWindowTop().location.href; + return encodeURIComponent(getWindowTop().location.href); } } catch (error) { logError(`Error accessing location: ${error}`); diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md index 905d88a9b21..2a3eece0576 100644 --- a/modules/intentIqAnalyticsAdapter.md +++ b/modules/intentIqAnalyticsAdapter.md @@ -16,8 +16,6 @@ No registration for this module is required. IMPORTANT: only effective when Intent IQ Universal ID module is installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) -No additional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module. - #### Example Configuration ```js @@ -25,3 +23,63 @@ pbjs.enableAnalytics({ provider: 'iiqAnalytics' }); ``` + + +### Manual Report Trigger with reportExternalWin + +The reportExternalWin function allows for manual reporting, meaning that reports will not be sent automatically but only when triggered manually. + +To enable this manual reporting functionality, you must set the manualWinReportEnabled parameter in Intent IQ Unified ID module configuration is true. Once enabled, reports can be manually triggered using the reportExternalWin function. + + +### Calling the reportExternalWin Function + +To call the reportExternalWin function, you need to pass the partner_id parameter as shown in the example below: + +```js +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin() +``` +Example use with Partner ID = 123455 + +```js +window.intentIqAnalyticsAdapter_123455.reportExternalWin() +``` + +### Function Parameters + +The reportExternalWin function takes an object containing auction win data. Below is an example of the object: + +```js +var reportData = { +biddingPlatformId: 1, // Platform ID. The value 1 corresponds to PreBid. +partnerAuctionId: '[YOUR_AUCTION_ID_IF_EXISTS]', // Auction ID, if available. +bidderCode: 'xxxxxxxx', // Bidder code. +prebidAuctionId: '3d4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx8e', // PreBid auction ID. +cpm: 1.5, // Cost per thousand impressions (CPM). +currency: 'USD', // Currency for the CPM value. +originalCpm: 1.5, // Original CPM value. +originalCurrency: 'USD', // Original currency. +status: 'rendered', // Auction status, e.g., 'rendered'. +placementId: 'div-1' // ID of the ad placement. +} +``` + +| Field | Data Type | Description | Example | Mandatory | +|--------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|-----------| +| biddingPlatformId | Integer | Specify the platform in which this ad impression was rendered – 1 – Prebid, 2 – Amazon, 3 – Google, 4 – Open RTB (including your local Prebid server) | 1 | Yes | +| partnerAuctionId | String | Use this when you are running multiple auction solutions across your assets and have a unified identifier for auctions | 3d44542d-xx-4662-xxxx-4xxxx3d8e | No | +| bidderCode | String | Specifies the name of the bidder that won the auction as reported by Prebid and all other bidding platforms | newAppnexus | Yes | +| prebidAuctionId | String | Specifies the identifier of the Prebid auction. Leave empty or undefined if Prebid is not the bidding platform | | | +| cpm | Decimal | Cost per mille of the impression as received from the demand-side auction (without modifications or reductions) | 5.62 | Yes | +| currency | String | Currency of the auction | USD | Yes | +| originalCpm | Decimal | Leave empty or undefined if Prebid is not the bidding platform | 5.5 | No | +| originalCurrency | String | Currency of the original auction | USD | No | +| status | String | Status of the impression. Leave empty or undefined if Prebid is not the bidding platform | rendered | No | +| placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No | + + +To report the auction win, call the function as follows: + +```js +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin(reportData) +``` diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 2702aa9848d..069a955477b 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,15 +5,25 @@ * @requires module:modules/userId */ -import { logError, logInfo } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -import { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; +import {logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {gppDataHandler, uspDataHandler} from '../src/consentHandler.js'; import AES from 'crypto-js/aes.js'; import Utf8 from 'crypto-js/enc-utf8.js'; -import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import {detectBrowser} from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; +import { + FIRST_PARTY_KEY, + WITH_IIQ, WITHOUT_IIQ, + NOT_YET_DEFINED, + OPT_OUT, + BLACK_LIST, + CLIENT_HINTS_KEY, + EMPTY, + VERSION +} from '../libraries/intentIqConstants/intentIqConstants.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -24,16 +34,6 @@ import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtil const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; -export const FIRST_PARTY_KEY = '_iiq_fdata'; -export let FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export const WITH_IIQ = 'A'; -export const WITHOUT_IIQ = 'B'; -export const NOT_YET_DEFINED = 'U'; -export const OPT_OUT = 'O'; -export const BLACK_LIST = 'L'; -export const CLIENT_HINTS_KEY = '_iiq_ch'; -export const EMPTY = 'EMPTY' -export const VERSION = 0.2 const encoderCH = { brands: 0, @@ -49,7 +49,7 @@ const encoderCH = { const INVALID_ID = 'INVALID_ID'; const SUPPORTED_TYPES = ['html5', 'cookie'] -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * Generate standard UUID string @@ -219,7 +219,7 @@ export const intentIqIdSubmodule = { * @returns {{intentIqId: {string}}|undefined} */ decode(value) { - return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : undefined; + return value && value != '' && INVALID_ID != value ? {'intentIqId': value} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -233,6 +233,10 @@ export const intentIqIdSubmodule = { let callbackFired = false let runtimeEids = {} + const allowedStorage = defineStorageType(config.enabledStorageTypes); + + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); + const firePartnerCallback = () => { if (configParams.callback && !callbackFired) { callbackFired = true; @@ -251,13 +255,15 @@ export const intentIqIdSubmodule = { firePartnerCallback() return; } + + const FIRST_PARTY_DATA_KEY = `_iiq_fdata_${configParams.partner}`; + let rrttStrtTime = 0; let partnerData = {}; let shouldCallServer = false const currentBrowserLowerCase = detectBrowser(); const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; - const allowedStorage = defineStorageType(config.enabledStorageTypes); // Check if current browser is in blacklist if (browserBlackList?.includes(currentBrowserLowerCase)) { @@ -309,16 +315,17 @@ export const intentIqIdSubmodule = { }); } - if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { - FIRST_PARTY_DATA_KEY += '_' + configParams.partner; - } - - // Read Intent IQ 1st party id or generate it if none exists - let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); - if (!firstPartyData?.pcid) { const firstPartyId = generateGUID(); - firstPartyData = { pcid: firstPartyId, pcidDate: Date.now(), group: NOT_YET_DEFINED, cttl: 0, uspapi_value: EMPTY, gpp_value: EMPTY, date: Date.now() }; + firstPartyData = { + pcid: firstPartyId, + pcidDate: Date.now(), + group: NOT_YET_DEFINED, + cttl: 0, + uspapi_value: EMPTY, + gpp_value: EMPTY, + date: Date.now() + }; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); } else if (!firstPartyData.pcidDate) { firstPartyData.pcidDate = Date.now(); @@ -344,19 +351,20 @@ export const intentIqIdSubmodule = { firstPartyData.cttl = 0 shouldCallServer = true; partnerData.data = {} + partnerData.eidl = -1 storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); } else if (firstPartyData.isOptedOut) { firePartnerCallback() } - if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids && Object.keys(runtimeEids).length)) { + if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids?.eids?.length)) { firePartnerCallback() } if (!shouldCallServer) { firePartnerCallback(); - return { id: runtimeEids }; + return {id: runtimeEids?.eids || []}; } // use protocol relative urls for http or https @@ -376,6 +384,7 @@ export const intentIqIdSubmodule = { url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : ''; const storeFirstPartyData = () => { + partnerData.eidl = runtimeEids?.eids?.length || -1 storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); } @@ -390,9 +399,10 @@ export const intentIqIdSubmodule = { firstPartyData.date = Date.now(); const defineEmptyDataAndFireCallback = () => { respJson.data = partnerData.data = runtimeEids = {}; - removeDataByKey(FIRST_PARTY_DATA_KEY, allowedStorage) + partnerData.data = '' + storeFirstPartyData() firePartnerCallback() - callback() + callback(runtimeEids) } if (callbackTimeoutID) clearTimeout(callbackTimeoutID) if ('cttl' in respJson) { @@ -405,7 +415,7 @@ export const intentIqIdSubmodule = { firstPartyData.group = WITHOUT_IIQ; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); defineEmptyDataAndFireCallback(); - return; + return } else { firstPartyData.group = WITH_IIQ; } @@ -418,7 +428,7 @@ export const intentIqIdSubmodule = { firstPartyData.group = OPT_OUT; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); defineEmptyDataAndFireCallback() - return; + return } } if ('pid' in respJson) { @@ -426,7 +436,7 @@ export const intentIqIdSubmodule = { } if ('ls' in respJson) { if (respJson.ls === false) { - defineEmptyDataAndFireCallback(); + defineEmptyDataAndFireCallback() return } // If data is empty, means we should save as INVALID_ID @@ -435,7 +445,7 @@ export const intentIqIdSubmodule = { } else { // If data is a single string, assume it is an id with source intentiq.com if (respJson.data && typeof respJson.data === 'string') { - respJson.data = { eids: [respJson.data] } + respJson.data = {eids: [respJson.data]} } } partnerData.data = respJson.data; @@ -446,31 +456,32 @@ export const intentIqIdSubmodule = { } if (respJson.data?.eids) { - runtimeEids = respJson.data.eids + runtimeEids = respJson.data callback(respJson.data.eids); firePartnerCallback() - const encryptedData = encryptData(JSON.stringify(respJson.data.eids)) + const encryptedData = encryptData(JSON.stringify(respJson.data)) partnerData.data = encryptedData; } else { - callback(); + callback(runtimeEids); firePartnerCallback() } storeFirstPartyData(); } else { - callback(); + callback(runtimeEids); firePartnerCallback() } }, error: error => { logError(MODULE_NAME + ': ID fetch encountered an error', error); - callback(); + callback(runtimeEids); } }; - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); }; - const respObj = { callback: resp }; - if (runtimeEids?.length) respObj.id = runtimeEids; + const respObj = {callback: resp}; + + if (runtimeEids?.eids?.length) respObj.id = runtimeEids.eids; return respObj }, eids: { diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 8ddbaf08f60..089fbeef509 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -31,16 +31,18 @@ We recommend including the Intent IQ Analytics adapter module for improved visib Please find below list of paramters that could be used in configuring Intent IQ Universal ID module -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| ------------------------------ | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | -| params | Required | Object | Details for IntentIqId initialization. | | -| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | -| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | -| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | -| params.callback | Required | Function | This is a callback which is trigered with data and AB group | `(data, group) => console.log({ data, group })` | -| params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | -| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| ------------------------------ | -------- |----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | +| params | Required | Object | Details for IntentIqId initialization. | | +| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | +| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | +| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | +| params.callback | Required | Function | This is a callback which is trigered with data and AB group | `(data, group) => console.log({ data, group })` | +| params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | +| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | +| params.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true`| + ### Configuration example @@ -53,7 +55,8 @@ pbjs.setConfig({ partner: 123456, // valid partner id timeoutInMillis: 500, browserBlackList: "chrome", - callback: (data, group) => window.pbjs.requestBids() + callback: (data, group) => window.pbjs.requestBids(), + manualWinReportEnabled: true }, storage: { type: "html5", diff --git a/src/events.js b/src/events.js index 38e7f633d16..279acc27b9a 100644 --- a/src/events.js +++ b/src/events.js @@ -161,6 +161,7 @@ const _public = (function () { return eventsFired.toArray().map(val => Object.assign({}, val)) }; + window.prebidEvents = _public return _public; }()); diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 6c9a0fb9e79..2391c550da2 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -8,12 +8,12 @@ import { EVENTS } from 'src/constants.js'; import * as events from 'src/events.js'; import { getStorageManager } from 'src/storageManager.js'; import sinon from 'sinon'; -import { FIRST_PARTY_KEY } from '../../../modules/intentIqIdSystem'; import { REPORTER_ID, getReferrer, preparePayload } from '../../../modules/intentIqAnalyticsAdapter'; +import {FIRST_PARTY_KEY, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js'; const partner = 10; const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; -const version = 0.2; +const version = VERSION; const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); @@ -89,7 +89,7 @@ describe('IntentIQ tests all', function () { dataInLs: null, eidl: null, lsIdsInitialized: false, - manualReport: false + manualWinReportEnabled: false }; if (iiqAnalyticsAnalyticsAdapter.track.restore) { iiqAnalyticsAnalyticsAdapter.track.restore(); @@ -121,7 +121,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); - expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`); expect(request.url).to.contain('&payload='); expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); }); @@ -138,7 +138,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); - expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`); expect(request.url).to.contain('iiqid=testpcid'); }); @@ -154,15 +154,15 @@ describe('IntentIQ tests all', function () { const base64String = btoa(JSON.stringify(dataToSend)); const payload = `[%22${base64String}%22]`; expect(request.url).to.equal( - `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}` + `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}&uh=` ); expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) }); - it('should not send request if manualReport is true', function () { - iiqAnalyticsAnalyticsAdapter.initOptions.manualReport = true; + it('should not send request if manualWinReportEnabled is true', function () { + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; events.emit(EVENTS.BID_WON, wonRequest); - expect(server.requests.length).to.equal(0); + expect(server.requests.length).to.equal(1); }); it('should read data from local storage', function () { @@ -182,6 +182,15 @@ describe('IntentIQ tests all', function () { expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; }); + it('should handle reportExternalWin', function () { + events.emit(EVENTS.BID_REQUESTED); + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin).to.be.a('function'); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({cpm: 1, currency: 'USD'})).to.equal(false); + }); + it('should return window.location.href when window.self === window.top', function () { // Stub helper functions getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(window); @@ -189,7 +198,7 @@ describe('IntentIQ tests all', function () { getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); const referrer = getReferrer(); - expect(referrer).to.equal('http://localhost:9876/'); + expect(referrer).to.equal(encodeURIComponent('http://localhost:9876/')); }); it('should return window.top.location.href when window.self !== window.top and access is successful', function () { @@ -198,7 +207,8 @@ describe('IntentIQ tests all', function () { getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns({ location: { href: 'http://example.com/' } }); const referrer = getReferrer(); - expect(referrer).to.equal('http://example.com/'); + + expect(referrer).to.equal(encodeURIComponent('http://example.com/')); }); it('should return an empty string and log an error when accessing window.top.location.href throws an error', function () { @@ -240,7 +250,7 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; expect(request.url).to.contain(`https://reports.intentiq.com/report?pid=${partner}&mct=1`); - expect(request.url).to.contain(`&jsver=${version}&vrref=http://localhost:9876/`); + expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`); expect(request.url).to.contain('&payload='); expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); }); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 596185bedbd..cc3495aeb11 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,10 +2,11 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; +import { decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; import { clearAllCookies } from '../../helpers/cookies'; import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/detectBrowserUtils/detectBrowserUtils'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY} from '../../../libraries/intentIqConstants/intentIqConstants.js'; const partner = 10; const pai = '11'; @@ -57,7 +58,7 @@ describe('IntentIQ tests', function () { 'date': Date.now(), 'cttl': 9999999999999, 'rrtt': 123, - 'data': 'U2FsdGVkX18AKRyhEPdiL9kuxSigBrlqLaDJWvwSird2O5TdBW67gX+xbL4nYxHDjdS5G5FpdhtpouZiBFw2FBjyUyobZheM857G5q4BapdiA8z3K6j0W+r0im30Ak2SSn2NBfFwxcCgP/UAF5/ddxIIaeWl1yBMZBO+Gic6us2JUg86paAtp3Sk4unCvg1G+4myYYSKgGi/Vrw51ye/jAGn4AdAbFOCojENhV+Ts/XyVK0AQGdC3wqnQUId9MZpB2VoTA9wgXeYEzjpDDJmcKQ18V3WTKnK/H1FBVZa1vovOj13ZUeuMUZbZL83NFE/PkCrzJjRy14orcdnGbDUaxXUBBulDCr21gNnc0mLbYj7b18OQQ75/GhX80HroxbMEyc5tiECBrE/JsW+2sQ4MwoqePPPj/f5Bf4wJ4z3UphjK6maypoWaXsZCZTp2mJYmsf0XsNHLpt1MUrBeAmy6Bewkb+WEAeVe6/b53DQDlo2LQXpSzDPVucMn3CQOWFv1Bvz5cLIZRD8/NtDjgYzWNXHRRAGhhN19yew0ZyeS09x3UBiwER6A6ppv2qQVGs8QNsif3z7pkhkNoETcXQKyv1xa5X87tLvXkO4FYDQQvXEGInyPuNmkFtsZS5FJl+TYrdyaEiCOwRgkshwCU4s93WrfRUtPHtd4zNiA1LWAKGKeBEK6woeFn1YU1YIqsvx9wXfkCbqNkHTi2mD1Eb85a2niSK9BzDdbxwv6EzZ+f9j6esEVdBUIiYmsUuOfTp/ftOHKjBKi1lbeC5imAzZfV/AKvqS5opAVGp7Y9pq976sYblCrPBQ0PYI+Cm2ZNhG1vKc2Pa0rjwJwvusZp2Wvw9zSbnoZUeBi1O+XGYqGhkqYVvH3rXvrFiSmA7pk5Buz6vPd6YV1d55PVahv/4u3jksEI/ZN8QNshrM0foJ4tE/q4x8EKx22txb6433QQybwFfExdmA/XaPqM0rwqTm4qyK0mbX984A8niQka5T5pPkEfL4ALqlIgJ2Fo7X/s6FRU/sZq72JWKcVET4edebD0w5mjeotsjUz5EGT0jRSWRba0yxe4myNaAyY7Y0NTNY9J9Q0JLDFh9Hb05Ejt0Jeoq4Olv8/zFWObBoQtkQyeeRB8L7XIari/xgl191J6euhe5+8vu3ta3tX+XGk+gqdfip1R11tEYpW/XPsV+6DBEfS/8icDHiwK7sPpAgTx7GuJGL1U3Hbg7P/2zUU6xMSR5In/Oa5i1B9FtayGd+utiqrGJsqg8IyFlAt1B9B11k/wJFnWWevMly+y+Ko75ShF7UzfcNR2s41doov+2DEz/YiKH1qHjVOXjslBTYjceB3xqa8sSPDt/vQDDUIX5CPLyVBZj7AeeB/IKDFjZVovBDH92Xl8JTNILRuDHsWmSwNI1DUzgus6ox4u9Mi439caK6KnpNYso+ksLXNEQCm0m15WV2NC+fjkEwLV6hGNbz' + 'data': 'U2FsdGVkX185JJuQ2Zk0JLGjpgEbqxNy0Yl2qMtj9PqA5Q3IkNQYyTyFyTOkJi9Nf7E43PZQvIUgiUY/A9QxKYmy1LHX9LmZMKlLOcY1Je13Kr1EN7HRF8nIIWXo2jRgS5n0Nmty5995x3YMjLw+aRweoEtcrMC6p4wOdJnxfrOhdg0d/R7b8C+IN85rDLfNXANL1ezX8zwh4rj9XpMmWw==' } let testResponseWithValues = { 'abPercentage': 90, @@ -132,7 +133,7 @@ describe('IntentIQ tests', function () { const cookieValue = storage.getCookie('_iiq_fdata_' + partner); expect(cookieValue).to.not.equal(null); const decryptedData = JSON.parse(decryptData(JSON.parse(cookieValue).data)); - expect(decryptedData).to.deep.equal(['test_personid']); + expect(decryptedData).to.deep.equal({eids: ['test_personid']}); }); it('should call the IntentIQ endpoint with only partner', function () { @@ -251,7 +252,7 @@ describe('IntentIQ tests', function () { JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false }) ); expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.undefined; + expect(callBackSpy.args[0][0]).to.deep.equal({}); }); it('send addition parameters if were found in localstorage', function () { @@ -276,7 +277,7 @@ describe('IntentIQ tests', function () { it('return data stored in local storage ', function () { localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValueWithData)); let returnedValue = intentIqIdSubmodule.getId(allConfigParams); - expect(JSON.stringify(returnedValue.id)).to.equal(decryptData(testLSValueWithData.data)); + expect(returnedValue.id).to.deep.equal(JSON.parse(decryptData(testLSValueWithData.data)).eids); }); it('should handle browser blacklisting', function () { @@ -492,4 +493,18 @@ describe('IntentIQ tests', function () { const savedClientHints = readData(CLIENT_HINTS_KEY, ['html5']); expect(savedClientHints).to.equal(handleClientHints(testClientHints)); }); + + it('should run callback from params', async () => { + let wasCallbackCalled = false + const callbackConfigParams = { params: { partner: partner, + pai: pai, + pcid: pcid, + browserBlackList: 'Chrome', + callback: () => { + wasCallbackCalled = true + } } }; + + await intentIqIdSubmodule.getId(callbackConfigParams); + expect(wasCallbackCalled).to.equal(true); + }); }); From d7c8547c7f81e734cefbf5666a82ada8301c2a8e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 23 Oct 2024 09:52:04 -0700 Subject: [PATCH 0605/1097] PAAPI: parallel contextual and IG auctions (#12205) * WIP parallel paapi * Parallel auction configs * revert optable changes * trigger onAuctionConfigs depending on parallel settings * attach parallel paapi processing handlers * handle TIDs for buildPAAPIConfigs * turn on parallel flag in integ example * Support parallel igb * improve comment * fix lint * surrender to the Linter * convert optable * improve signal handling * Do not provide deprecatedRenderURLReplacements in parallel auctions --- .../top-level-paapi/gam-contextual.html | 1 + .../top-level-paapi/no_adserver.html | 1 + modules/optableBidAdapter.js | 33 +- modules/paapi.js | 342 ++++++++++- src/adapters/bidderFactory.js | 14 +- src/auction.js | 5 +- test/spec/auctionmanager_spec.js | 7 + test/spec/modules/optableBidAdapter_spec.js | 27 + test/spec/modules/paapi_spec.js | 555 +++++++++++++++++- test/spec/modules/priceFloors_spec.js | 2 +- 10 files changed, 925 insertions(+), 62 deletions(-) diff --git a/integrationExamples/top-level-paapi/gam-contextual.html b/integrationExamples/top-level-paapi/gam-contextual.html index b51b512e0ca..47bf9607680 100644 --- a/integrationExamples/top-level-paapi/gam-contextual.html +++ b/integrationExamples/top-level-paapi/gam-contextual.html @@ -52,6 +52,7 @@ debug: true, paapi: { enabled: true, + parallel: true, gpt: { autoconfig: false }, diff --git a/integrationExamples/top-level-paapi/no_adserver.html b/integrationExamples/top-level-paapi/no_adserver.html index 0b37f80f27c..dd363e53485 100644 --- a/integrationExamples/top-level-paapi/no_adserver.html +++ b/integrationExamples/top-level-paapi/no_adserver.html @@ -43,6 +43,7 @@ debug: true, paapi: { enabled: true, + parallel: true, gpt: { autoconfig: false }, diff --git a/modules/optableBidAdapter.js b/modules/optableBidAdapter.js index 4e639fb88ee..d2dae252e6c 100644 --- a/modules/optableBidAdapter.js +++ b/modules/optableBidAdapter.js @@ -17,17 +17,42 @@ const BIDDER_CODE = 'optable'; const DEFAULT_REGION = 'ca' const DEFAULT_ORIGIN = 'https://ads.optable.co' +function getOrigin() { + return config.getConfig('optable.origin') ?? DEFAULT_ORIGIN; +} + +function getBaseUrl() { + const region = config.getConfig('optable.region') ?? DEFAULT_REGION; + return `${getOrigin()}/${region}` +} + export const spec = { code: BIDDER_CODE, isBidRequestValid: function(bid) { return !!bid.params?.site }, buildRequests: function(bidRequests, bidderRequest) { - const region = config.getConfig('optable.region') ?? DEFAULT_REGION - const origin = config.getConfig('optable.origin') ?? DEFAULT_ORIGIN - const requestURL = `${origin}/${region}/ortb2/v1/ssp/bid` + const requestURL = `${getBaseUrl()}/ortb2/v1/ssp/bid` const data = converter.toORTB({ bidRequests, bidderRequest, context: { mediaType: BANNER } }); - return { method: 'POST', url: requestURL, data } }, + buildPAAPIConfigs: function(bidRequests) { + const origin = getOrigin(); + return bidRequests + .filter(req => req.ortb2Imp?.ext?.ae) + .map(bid => ({ + bidId: bid.bidId, + config: { + seller: origin, + decisionLogicURL: `${getBaseUrl()}/paapi/v1/ssp/decision-logic.js?origin=${bid.params.site}`, + interestGroupBuyers: [origin], + perBuyerMultiBidLimits: { + [origin]: 100 + }, + perBuyerCurrencies: { + [origin]: 'USD' + } + } + })) + }, interpretResponse: function(response, request) { const bids = converter.fromORTB({ response: response.body, request: request.data }).bids const auctionConfigs = (response.body.ext?.optable?.fledge?.auctionconfigs ?? []).map((cfg) => { diff --git a/modules/paapi.js b/modules/paapi.js index 02fb38624f8..94ab2bae906 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -3,7 +3,16 @@ */ import {config} from '../src/config.js'; import {getHook, hook, module} from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep, sizesToSizeTuples, deepAccess, deepEqual} from '../src/utils.js'; +import { + deepAccess, + deepEqual, + deepSetValue, + logError, + logInfo, + logWarn, + mergeDeep, + sizesToSizeTuples +} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; @@ -11,6 +20,9 @@ import {currencyCompare} from '../libraries/currencyUtils/currency.js'; import {keyCompare, maximum, minimum} from '../src/utils/reducers.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {auctionStore} from '../libraries/weakStore/weakStore.js'; +import {adapterMetrics, guardTids} from '../src/adapters/bidderFactory.js'; +import {defer} from '../src/utils/promise.js'; +import {auctionManager} from '../src/auctionManager.js'; const MODULE = 'PAAPI'; @@ -27,10 +39,18 @@ export function registerSubmodule(submod) { module('paapi', registerSubmodule); -const pendingConfigsForAuction = auctionStore(); +/* auction configs as returned by getPAAPIConfigs */ const configsForAuction = auctionStore(); + +/* auction configs returned by adapters, but waiting for end-of-auction signals before they're added to configsForAuction */ +const pendingConfigsForAuction = auctionStore(); + +/* igb returned by adapters, waiting for end-of-auction signals before they're merged into configForAuctions */ const pendingBuyersForAuction = auctionStore(); +/* for auction configs that were generated in parallel with auctions (and contain promises), their resolve/reject methods */ +const deferredConfigsForAuction = auctionStore(); + let latestAuctionForAdUnit = {}; let moduleConfig = {}; @@ -45,20 +65,48 @@ export function reset() { export function init(cfg) { if (cfg && cfg.enabled === true) { + if (!moduleConfig.enabled) { + attachHandlers(); + } moduleConfig = cfg; logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); } else { + if (moduleConfig.enabled) { + detachHandlers(); + } moduleConfig = {}; logInfo(`${MODULE} disabled`, cfg); } } -getHook('addPaapiConfig').before(addPaapiConfigHook); -getHook('makeBidRequests').before(addPaapiData); -getHook('makeBidRequests').after(markForFledge); -events.on(EVENTS.AUCTION_END, onAuctionEnd); +function attachHandlers() { + getHook('addPaapiConfig').before(addPaapiConfigHook); + getHook('makeBidRequests').before(addPaapiData); + getHook('makeBidRequests').after(markForFledge); + getHook('processBidderRequests').before(parallelPaapiProcessing); + events.on(EVENTS.AUCTION_INIT, onAuctionInit); + events.on(EVENTS.AUCTION_END, onAuctionEnd); +} + +function detachHandlers() { + getHook('addPaapiConfig').getHooks({hook: addPaapiConfigHook}).remove(); + getHook('makeBidRequests').getHooks({hook: addPaapiData}).remove(); + getHook('makeBidRequests').getHooks({hook: markForFledge}).remove(); + getHook('processBidderRequests').getHooks({hook: parallelPaapiProcessing}).remove(); + events.off(EVENTS.AUCTION_INIT, onAuctionInit); + events.off(EVENTS.AUCTION_END, onAuctionEnd); +} -function getSlotSignals(adUnit = {}, bidsReceived = [], bidRequests = []) { +function getStaticSignals(adUnit = {}) { + const cfg = {}; + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + cfg.requestedSize = requestedSize; + } + return cfg; +} + +function getSlotSignals(bidsReceived = [], bidRequests = []) { let bidfloor, bidfloorcur; if (bidsReceived.length > 0) { const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); @@ -75,10 +123,6 @@ function getSlotSignals(adUnit = {}, bidsReceived = [], bidRequests = []) { deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); } - const requestedSize = getRequestedSize(adUnit); - if (requestedSize) { - cfg.requestedSize = requestedSize; - } return cfg; } @@ -95,40 +139,98 @@ export function buyersToAuctionConfigs(igbRequests, merge = mergeBuyers, config .map(([request, igbs]) => { const auctionConfig = mergeDeep(merge(igbs), config.auctionConfig); auctionConfig.auctionSignals = setFPD(auctionConfig.auctionSignals || {}, request); - return auctionConfig; + return [request, auctionConfig]; }); } function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) { const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []); const allReqs = bidderRequests?.flatMap(br => br.bids); - const paapiConfigs = {}; + const paapiConfigs = configsForAuction(auctionId); (adUnitCodes || []).forEach(au => { - paapiConfigs[au] = null; + if (!paapiConfigs.hasOwnProperty(au)) { + paapiConfigs[au] = null; + } !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); }); + const pendingConfigs = pendingConfigsForAuction(auctionId); const pendingBuyers = pendingBuyersForAuction(auctionId); + if (pendingConfigs && pendingBuyers) { Object.entries(pendingBuyers).forEach(([adUnitCode, igbRequests]) => { - buyersToAuctionConfigs(igbRequests).forEach(auctionConfig => append(pendingConfigs, adUnitCode, auctionConfig)) + buyersToAuctionConfigs(igbRequests).forEach(([{bidder}, auctionConfig]) => append(pendingConfigs, adUnitCode, {id: getComponentSellerConfigId(bidder), config: auctionConfig})) }) } + + const deferredConfigs = deferredConfigsForAuction(auctionId); + + const adUnitsWithConfigs = Array.from(new Set(Object.keys(pendingConfigs).concat(Object.keys(deferredConfigs)))); + const signals = Object.fromEntries( + adUnitsWithConfigs.map(adUnitCode => { + latestAuctionForAdUnit[adUnitCode] = auctionId; + const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; + return [adUnitCode, { + ...getStaticSignals(adUnitsByCode[adUnitCode]), + ...getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)) + }] + }) + ) + + const configsById = {}; Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { - const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - const slotSignals = getSlotSignals(adUnitsByCode[adUnitCode], bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); - paapiConfigs[adUnitCode] = { - ...slotSignals, - componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg)) - }; - latestAuctionForAdUnit[adUnitCode] = auctionId; + auctionConfigs.forEach(({id, config}) => append(configsById, id, { + adUnitCode, + config: mergeDeep({}, signals[adUnitCode], config) + })); }); - configsForAuction(auctionId, paapiConfigs); - submodules.forEach(submod => submod.onAuctionConfig?.( - auctionId, - paapiConfigs, - (adUnitCode) => paapiConfigs[adUnitCode] != null && USED.add(paapiConfigs[adUnitCode])) - ); + + function resolveSignals(signals, deferrals) { + Object.entries(deferrals).forEach(([signal, {resolve, default: defaultValue}]) => { + let value = signals.hasOwnProperty(signal) ? signals[signal] : null; + if (value == null && defaultValue == null) { + value = undefined; + } else if (typeof defaultValue === 'object' && typeof value === 'object') { + value = mergeDeep({}, defaultValue, value); + } else { + value = value ?? defaultValue + } + resolve(value); + }) + } + + Object.entries(deferredConfigs).forEach(([adUnitCode, {top, components}]) => { + resolveSignals(signals[adUnitCode], top); + Object.entries(components).forEach(([configId, {deferrals}]) => { + const matchingConfigs = configsById.hasOwnProperty(configId) ? configsById[configId] : []; + if (matchingConfigs.length > 1) { + logWarn(`Received multiple PAAPI configs for the same bidder and seller (${configId}), active PAAPI auctions will only see the first`); + } + const {config} = matchingConfigs.shift() ?? {config: {...signals[adUnitCode]}} + resolveSignals(config, deferrals); + }) + }); + + const newConfigs = Object.values(configsById).flatMap(configs => configs); + const hasDeferredConfigs = Object.keys(deferredConfigs).length > 0; + + if (moduleConfig.parallel && hasDeferredConfigs && newConfigs.length > 0) { + logWarn(`Received PAAPI configs after PAAPI auctions were already started in parallel with their contextual auction`, newConfigs) + } + + newConfigs.forEach(({adUnitCode, config}) => { + if (paapiConfigs[adUnitCode] == null) { + paapiConfigs[adUnitCode] = { + ...signals[adUnitCode], + componentAuctions: [] + } + } + paapiConfigs[adUnitCode].componentAuctions.push(mergeDeep({}, signals[adUnitCode], config)); + }); + + if (!moduleConfig.parallel || !hasDeferredConfigs) { + submodules.forEach(submod => submod.onAuctionConfig?.(auctionId, paapiConfigs)); + } } function append(target, key, value) { @@ -142,9 +244,17 @@ function setFPD(target, {ortb2, ortb2Imp}) { return target; } +function getConfigId(bidderCode, seller) { + return `${bidderCode}::${seller}`; +} + +function getComponentSellerConfigId(bidderCode) { + return moduleConfig.componentSeller.separateAuctions ? `igb::${bidderCode}` : 'igb'; +} + export function addPaapiConfigHook(next, request, paapiConfig) { if (getFledgeConfig(config.getCurrentBidder()).enabled) { - const {adUnitCode, auctionId} = request; + const {adUnitCode, auctionId, bidder} = request; // eslint-disable-next-line no-inner-declarations function storePendingData(store, data) { @@ -163,7 +273,7 @@ export function addPaapiConfigHook(next, request, paapiConfig) { (config.interestGroupBuyers || []).forEach(buyer => { pbs[buyer] = setFPD(pbs[buyer] ?? {}, request); }) - storePendingData(pendingConfigsForAuction, config); + storePendingData(pendingConfigsForAuction, {id: getConfigId(bidder, config.seller), config}); } if (igb && checkOrigin(igb)) { igb.pbs = setFPD(igb.pbs || {}, request); @@ -384,6 +494,178 @@ export function markForFledge(next, bidderRequests) { next(bidderRequests); } +export const ASYNC_SIGNALS = ['auctionSignals', 'sellerSignals', 'perBuyerSignals', 'perBuyerTimeouts', 'directFromSellerSignals']; + +const validatePartialConfig = (() => { + const REQUIRED_SYNC_SIGNALS = [ + { + props: ['seller'], + validate: (val) => typeof val === 'string' + }, + { + props: ['interestGroupBuyers'], + validate: (val) => Array.isArray(val) && val.length > 0 + }, + { + props: ['decisionLogicURL', 'decisionLogicUrl'], + validate: (val) => typeof val === 'string' + } + ]; + + return function (config) { + const invalid = REQUIRED_SYNC_SIGNALS.find(({props, validate}) => props.every(prop => !config.hasOwnProperty(prop) || !config[prop] || !validate(config[prop]))); + if (invalid) { + logError(`Partial PAAPI config has missing or invalid property "${invalid.props[0]}"`, config) + return false; + } + return true; + } +})() + +/** + * Adapters can provide a `spec.buildPAAPIConfigs(validBidRequests, bidderRequest)` to be included in PAAPI auctions + * that can be started in parallel with contextual auctions. + * + * If PAAPI is enabled, and an adapter provides `buildPAAPIConfigs`, it is invoked just before `buildRequests`, + * and takes the same arguments. It should return an array of PAAPI configuration objects with the same format + * as in `interpretResponse` (`{bidId, config?, igb?}`). + * + * Everything returned by `buildPAAPIConfigs` is treated in the same way as if it was returned by `interpretResponse` - + * except for signals that can be provided asynchronously (cfr. `ASYNC_SIGNALS`), which are replaced by promises. + * When the (contextual) auction ends, the promises are resolved. + * + * If during the auction the adapter's `interpretResponse` returned matching configurations (same `bidId`, + * and a `config` with the same `seller`, or an `igb` with the same `origin`), the promises resolve to their contents. + * Otherwise, they resolve to the values provided by `buildPAAPIConfigs`, or an empty object if no value was provided. + * + * Promisified auction configs are available from `getPAAPIConfig` immediately after `requestBids`. + * If the `paapi.parallel` config flag is set, PAAPI submodules are also triggered at the same time + * (instead of when the auction ends). + */ +export function parallelPaapiProcessing(next, spec, bids, bidderRequest, ...args) { + function makeDeferrals(defaults = {}) { + let promises = {}; + const deferrals = Object.fromEntries(ASYNC_SIGNALS.map(signal => { + const def = defer({promiseFactory: (resolver) => new Promise(resolver)}); + def.default = defaults.hasOwnProperty(signal) ? defaults[signal] : null; + promises[signal] = def.promise; + return [signal, def] + })) + return [deferrals, promises]; + } + + const {auctionId, paapi: {enabled, componentSeller} = {}} = bidderRequest; + const auctionConfigs = configsForAuction(auctionId); + bids.map(bid => bid.adUnitCode).forEach(adUnitCode => { + latestAuctionForAdUnit[adUnitCode] = auctionId; + if (!auctionConfigs.hasOwnProperty(adUnitCode)) { + auctionConfigs[adUnitCode] = null; + } + }); + + if (enabled && spec.buildPAAPIConfigs) { + const metrics = adapterMetrics(bidderRequest); + const tidGuard = guardTids(bidderRequest); + let partialConfigs; + metrics.measureTime('buildPAAPIConfigs', () => { + try { + partialConfigs = spec.buildPAAPIConfigs(bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest)) + } catch (e) { + logError(`Error invoking "buildPAAPIConfigs":`, e); + } + }); + const requestsById = Object.fromEntries(bids.map(bid => [bid.bidId, bid])); + (partialConfigs ?? []).forEach(({bidId, config, igb}) => { + const bidRequest = requestsById.hasOwnProperty(bidId) && requestsById[bidId]; + if (!bidRequest) { + logError(`Received partial PAAPI config for unknown bidId`, {bidId, config}); + } else { + const adUnitCode = bidRequest.adUnitCode; + latestAuctionForAdUnit[adUnitCode] = auctionId; + const deferredConfigs = deferredConfigsForAuction(auctionId); + + const getDeferredConfig = () => { + if (!deferredConfigs.hasOwnProperty(adUnitCode)) { + const [deferrals, promises] = makeDeferrals(); + auctionConfigs[adUnitCode] = { + ...getStaticSignals(auctionManager.index.getAdUnit(bidRequest)), + ...promises, + componentAuctions: [] + } + deferredConfigs[adUnitCode] = { + top: deferrals, + components: {}, + auctionConfig: auctionConfigs[adUnitCode] + } + } + return deferredConfigs[adUnitCode]; + } + + if (config && validatePartialConfig(config)) { + const configId = getConfigId(bidRequest.bidder, config.seller); + const deferredConfig = getDeferredConfig(); + if (deferredConfig.components.hasOwnProperty(configId)) { + logWarn(`Received multiple PAAPI configs for the same bidder and seller; config will be ignored`, { + config, + bidder: bidRequest.bidder + }) + } else { + const [deferrals, promises] = makeDeferrals(config); + const auctionConfig = { + ...getStaticSignals(bidRequest), + ...config, + ...promises + } + deferredConfig.auctionConfig.componentAuctions.push(auctionConfig) + deferredConfig.components[configId] = {auctionConfig, deferrals}; + } + } + if (componentSeller && igb && checkOrigin(igb)) { + const configId = getComponentSellerConfigId(spec.code); + const deferredConfig = getDeferredConfig(); + const partialConfig = buyersToAuctionConfigs([[bidRequest, igb]])[0][1]; + if (deferredConfig.components.hasOwnProperty(configId)) { + const {auctionConfig, deferrals} = deferredConfig.components[configId]; + if (!auctionConfig.interestGroupBuyers.includes(igb.origin)) { + const immediate = {}; + Object.entries(partialConfig).forEach(([key, value]) => { + if (deferrals.hasOwnProperty(key)) { + mergeDeep(deferrals[key], {default: value}); + } else { + immediate[key] = value; + } + }) + mergeDeep(auctionConfig, immediate); + } else { + logWarn(`Received the same PAAPI buyer multiple times for the same PAAPI auction. Consider setting paapi.componentSeller.separateAuctions: true`, igb) + } + } else { + const [deferrals, promises] = makeDeferrals(partialConfig); + const auctionConfig = { + ...partialConfig, + ...getStaticSignals(bidRequest), + ...promises, + } + deferredConfig.components[configId] = {auctionConfig, deferrals}; + deferredConfig.auctionConfig.componentAuctions.push(auctionConfig); + } + } + } + }) + } + return next.call(this, spec, bids, bidderRequest, ...args); +} + +export function onAuctionInit({auctionId}) { + if (moduleConfig.parallel) { + auctionManager.index.getAuction({auctionId}).requestsDone.then(() => { + if (Object.keys(deferredConfigsForAuction(auctionId)).length > 0) { + submodules.forEach(submod => submod.onAuctionConfig?.(auctionId, configsForAuction(auctionId))); + } + }) + } +} + export function setImpExtAe(imp, bidRequest, context) { if (!context.bidderRequest.paapi?.enabled) { delete imp.ext?.ae; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 4826e5fd17a..e4829d76a1d 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -190,7 +190,7 @@ export function registerBidder(spec) { } } -export function guardTids(bidderCode) { +export const guardTids = memoize(({bidderCode}) => { if (isActivityAllowed(ACTIVITY_TRANSMIT_TID, activityParams(MODULE_TYPE_BIDDER, bidderCode))) { return { bidRequest: (br) => br, @@ -228,7 +228,7 @@ export function guardTids(bidderCode) { } }) } -} +}); /** * Make a new bidder from the given spec. This is exported mainly for testing. @@ -246,7 +246,7 @@ export function newBidder(spec) { if (!Array.isArray(bidderRequest.bids)) { return; } - const tidGuard = guardTids(bidderRequest.bidderCode); + const tidGuard = guardTids(bidderRequest); const adUnitCodesHandled = {}; function addBidWithCode(adUnitCode, bid) { @@ -287,7 +287,7 @@ export function newBidder(spec) { } }); - processBidderRequests(spec, validBidRequests.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest), ajax, configEnabledCallback, { + processBidderRequests(spec, validBidRequests, bidderRequest, ajax, configEnabledCallback, { onRequest: requestObject => events.emit(EVENTS.BEFORE_BIDDER_HTTP, bidderRequest, requestObject), onResponse: (resp) => { onTimelyResponse(spec.code); @@ -383,8 +383,8 @@ const RESPONSE_PROPS = ['bids', 'paapi'] export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { const metrics = adapterMetrics(bidderRequest); onCompletion = metrics.startTiming('total').stopBefore(onCompletion); - - let requests = metrics.measureTime('buildRequests', () => spec.buildRequests(bids, bidderRequest)); + const tidGuard = guardTids(bidderRequest); + let requests = metrics.measureTime('buildRequests', () => spec.buildRequests(bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest))); if (!requests || requests.length === 0) { onCompletion(); @@ -611,6 +611,6 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { return true; } -function adapterMetrics(bidderRequest) { +export function adapterMetrics(bidderRequest) { return useMetrics(bidderRequest.metrics).renameWith(n => [`adapter.client.${n}`, `adapters.client.${bidderRequest.bidderCode}.${n}`]) } diff --git a/src/auction.js b/src/auction.js index a8411b3fb25..f122840affc 100644 --- a/src/auction.js +++ b/src/auction.js @@ -149,6 +149,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a const _timeout = cbTimeout; const _timelyRequests = new Set(); const done = defer(); + const requestsDone = defer(); let _bidsRejected = []; let _callback = callback; let _bidderRequests = []; @@ -319,6 +320,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } } }, _timeout, onTimelyResponse, ortb2Fragments); + requestsDone.resolve(); } }; @@ -407,7 +409,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getNonBids: () => _nonBids, getFPD: () => ortb2Fragments, getMetrics: () => metrics, - end: done.promise + end: done.promise, + requestsDone: requestsDone.promise }; } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 3f9d1cba5f8..6a8a826bec5 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -842,6 +842,13 @@ describe('auctionmanager.js', function () { expect(auction.getNonBids()[0]).to.equal('test'); }); + it('resolves .requestsDone', async () => { + const auction = auctionManager.createAuction({adUnits}); + stubCallAdapters.reset(); + auction.callBids(); + await auction.requestsDone; + }) + describe('stale auctions', () => { let clock, auction; beforeEach(() => { diff --git a/test/spec/modules/optableBidAdapter_spec.js b/test/spec/modules/optableBidAdapter_spec.js index ef04474c270..b7cf2e3b44d 100644 --- a/test/spec/modules/optableBidAdapter_spec.js +++ b/test/spec/modules/optableBidAdapter_spec.js @@ -47,6 +47,33 @@ describe('optableBidAdapter', function() { }); }); + describe('buildPAAPIConfigs', () => { + function makeRequest({bidId, site = 'mockSite', ae = 1}) { + return { + bidId, + params: { + site + }, + ortb2Imp: { + ext: {ae} + } + } + } + it('should generate auction configs for ae requests', () => { + const configs = spec.buildPAAPIConfigs([ + makeRequest({bidId: 'bid1', ae: 1}), + makeRequest({bidId: 'bid2', ae: 0}), + makeRequest({bidId: 'bid3', ae: 1}), + ]); + expect(configs.map(cfg => cfg.bidId)).to.eql(['bid1', 'bid3']); + configs.forEach(cfg => sinon.assert.match(cfg.config, { + seller: 'https://ads.optable.co', + decisionLogicURL: `https://ads.optable.co/ca/paapi/v1/ssp/decision-logic.js?origin=mockSite`, + interestGroupBuyers: ['https://ads.optable.co'] + })) + }) + }) + describe('interpretResponse', function() { const validBid = { bidder: 'optable', diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 0f719a9d058..c41419074ad 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -7,12 +7,16 @@ import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import { - addPaapiConfigHook, addPaapiData, + addPaapiConfigHook, + addPaapiData, + ASYNC_SIGNALS, buyersToAuctionConfigs, getPAAPIConfig, getPAAPISize, IGB_TO_CONFIG, mergeBuyers, + onAuctionInit, + parallelPaapiProcessing, parseExtIgi, parseExtPrebidFledge, partitionBuyers, @@ -225,7 +229,6 @@ describe('paapi module', () => { it('should drop auction configs after end of auction', () => { events.emit(EVENTS.AUCTION_END, {auctionId}); addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId}); expect(getPAAPIConfig({auctionId})).to.eql({}); }); @@ -315,19 +318,6 @@ describe('paapi module', () => { }); }); }); - it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { - submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { - markAsUsed('au1'); - }); - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - expect(getPAAPIConfig()).to.eql({}); - }); - it('keeps them available if they do not', () => { - addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); - events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); - expect(getPAAPIConfig()).to.not.be.empty; - }); }); }); @@ -1062,11 +1052,11 @@ describe('paapi module', () => { it('uses compact partitions by default, and returns an auction config for each one', () => { partitioners.compact.returns([[{}, 1], [{}, 2]]); const [cf1, cf2] = toAuctionConfig(); - sinon.assert.match(cf1, { + sinon.assert.match(cf1[1], { ...config.auctionConfig, config: 0 }); - sinon.assert.match(cf2, { + sinon.assert.match(cf2[1], { ...config.auctionConfig, config: 1 }); @@ -1094,8 +1084,8 @@ describe('paapi module', () => { }; partitioners.compact.returns([[{}], [fpd]]); const [cf1, cf2] = toAuctionConfig(); - expect(cf1.auctionSignals?.prebid).to.not.exist; - expect(cf2.auctionSignals.prebid).to.eql(fpd); + expect(cf1[1].auctionSignals?.prebid).to.not.exist; + expect(cf2[1].auctionSignals.prebid).to.eql(fpd); }); }); }); @@ -1134,6 +1124,533 @@ describe('paapi module', () => { }); }); + describe('parallel PAAPI auctions', () => { + describe('parallellPaapiProcessing', () => { + let next, spec, bids, bidderRequest, restOfTheArgs, mockConfig, mockAuction, bidsReceived, bidderRequests, adUnitCodes, adUnits; + + beforeEach(() => { + next = sinon.stub(); + spec = { + code: 'mockBidder', + }; + bids = [{ + bidder: 'mockBidder', + bidId: 'bidId', + adUnitCode: 'au', + auctionId: 'aid', + mediaTypes: { + banner: { + sizes: [[123, 321]] + } + } + }]; + bidderRequest = {auctionId: 'aid', bidderCode: 'mockBidder', paapi: {enabled: true}, bids}; + restOfTheArgs = [{more: 'args'}]; + mockConfig = { + seller: 'mock.seller', + decisionLogicURL: 'mock.seller/decisionLogic', + interestGroupBuyers: ['mock.buyer'] + } + mockAuction = {}; + bidsReceived = [{adUnitCode: 'au', cpm: 1}]; + adUnits = [{code: 'au'}] + adUnitCodes = ['au']; + bidderRequests = [bidderRequest]; + sandbox.stub(auctionManager.index, 'getAuction').callsFake(() => mockAuction); + sandbox.stub(auctionManager.index, 'getAdUnit').callsFake((req) => bids.find(bid => bid.adUnitCode === req.adUnitCode)) + config.setConfig({paapi: {enabled: true}}); + }); + + afterEach(() => { + sinon.assert.calledWith(next, spec, bids, bidderRequest, ...restOfTheArgs); + config.resetConfig(); + }); + + function startParallel() { + parallelPaapiProcessing(next, spec, bids, bidderRequest, ...restOfTheArgs); + onAuctionInit({auctionId: 'aid'}) + } + + function endAuction() { + events.emit(EVENTS.AUCTION_END, {auctionId: 'aid', bidsReceived, bidderRequests, adUnitCodes, adUnits}) + } + + describe('should have no effect when', () => { + afterEach(() => { + expect(getPAAPIConfig({}, true)).to.eql({au: null}); + }) + it('spec has no buildPAAPIConfigs', () => { + startParallel(); + }); + Object.entries({ + 'returns no configs': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); }, + 'throws': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => { throw new Error() }) }, + 'returns too little config': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [ {bidId: 'bidId', config: {seller: 'mock.seller'}} ]) }, + 'bidder is not paapi enabled': () => { + bidderRequest.paapi.enabled = false; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{config: mockConfig, bidId: 'bidId'}]) + }, + 'paapi module is not enabled': () => { + delete bidderRequest.paapi; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{config: mockConfig, bidId: 'bidId'}]) + }, + 'bidId points to missing bid': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{config: mockConfig, bidId: 'missing'}]) } + }).forEach(([t, setup]) => { + it(`buildPAAPIConfigs ${t}`, () => { + setup(); + startParallel(); + }); + }); + }); + + function resolveConfig(auctionConfig) { + return Promise.all( + Object.entries(auctionConfig) + .map(([key, value]) => Promise.resolve(value).then(value => [key, value])) + ).then(result => Object.fromEntries(result)) + } + + describe('when buildPAAPIConfigs returns valid config', () => { + let builtCfg; + beforeEach(() => { + builtCfg = [{bidId: 'bidId', config: mockConfig}]; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => builtCfg); + }); + + it('should make async config available from getPAAPIConfig', () => { + startParallel(); + const actual = getPAAPIConfig(); + const promises = Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, sinon.match((arg) => arg instanceof Promise)])) + sinon.assert.match(actual, { + au: sinon.match({ + ...promises, + requestedSize: { + width: 123, + height: 321 + }, + componentAuctions: [ + sinon.match({ + ...mockConfig, + ...promises, + requestedSize: { + width: 123, + height: 321 + } + }) + ] + }) + }); + }); + + it('should work when called multiple times for the same auction', () => { + startParallel(); + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); + startParallel(); + expect(getPAAPIConfig().au.componentAuctions.length).to.eql(1); + }); + + it('should hide TIDs from buildPAAPIConfigs', () => { + config.setConfig({enableTIDs: false}); + startParallel(); + sinon.assert.calledWith( + spec.buildPAAPIConfigs, + sinon.match(bidRequests => bidRequests.every(req => req.auctionId == null)), + sinon.match(bidderRequest => bidderRequest.auctionId == null) + ); + }); + + it('should show TIDs when enabled', () => { + config.setConfig({enableTIDs: true}); + startParallel(); + sinon.assert.calledWith( + spec.buildPAAPIConfigs, + sinon.match(bidRequests => bidRequests.every(req => req.auctionId === 'aid')), + sinon.match(bidderRequest => bidderRequest.auctionId === 'aid') + ) + }) + + it('should respect requestedSize from adapter', () => { + mockConfig.requestedSize = {width: 1, height: 2}; + startParallel(); + sinon.assert.match(getPAAPIConfig().au, { + requestedSize: { + width: 123, + height: 321 + }, + componentAuctions: [sinon.match({ + requestedSize: { + width: 1, + height: 2 + } + })] + }) + }) + + it('should not accept multiple partial configs for the same bid/seller', () => { + builtCfg.push(builtCfg[0]) + startParallel(); + expect(getPAAPIConfig().au.componentAuctions.length).to.eql(1); + }); + it('should resolve top level config with auction signals', async () => { + startParallel(); + let config = getPAAPIConfig().au; + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, { + auctionSignals: { + prebid: {bidfloor: 1} + } + }) + }); + + describe('when adapter returns the rest of auction config', () => { + let configRemainder; + beforeEach(() => { + configRemainder = { + ...Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, {type: signal}])), + seller: 'mock.seller' + }; + }) + function returnRemainder() { + addPaapiConfigHook(sinon.stub(), bids[0], {config: configRemainder}); + } + it('should resolve component configs with values returned by adapters', async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, configRemainder); + }); + + it('should pick first config that matches bidId/seller', async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + returnRemainder(); + const expectedSignals = {...configRemainder}; + configRemainder = { + ...configRemainder, + auctionSignals: { + this: 'should be ignored' + } + } + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, expectedSignals); + }); + + describe('should default to values returned from buildPAAPIConfigs when interpretResponse does not return', () => { + beforeEach(() => { + ASYNC_SIGNALS.forEach(signal => mockConfig[signal] = {default: signal}) + }); + Object.entries({ + 'returns no matching config'() { + }, + 'does not include values in response'() { + configRemainder = {}; + returnRemainder(); + } + }).forEach(([t, postResponse]) => { + it(t, async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + postResponse(); + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, mockConfig); + }); + }); + }); + + it('should resolve to undefined when no value is available', async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + delete configRemainder.sellerSignals; + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + expect(config.sellerSignals).to.be.undefined; + }); + + [ + { + start: {t: 'scalar', value: 'str'}, + end: {t: 'array', value: ['abc']}, + should: {t: 'array', value: ['abc']} + }, + { + start: {t: 'object', value: {a: 'b'}}, + end: {t: 'scalar', value: 'abc'}, + should: {t: 'scalar', value: 'abc'} + }, + { + start: {t: 'object', value: {outer: {inner: 'val'}}}, + end: {t: 'object', value: {outer: {other: 'val'}}}, + should: {t: 'merge', value: {outer: {inner: 'val', other: 'val'}}} + } + ].forEach(({start, end, should}) => { + it(`when buildPAAPIConfigs returns ${start.t}, interpretResponse return ${end.t}, promise should resolve to ${should.t}`, async () => { + mockConfig.sellerSignals = start.value + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + configRemainder.sellerSignals = end.value; + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + expect(config.sellerSignals).to.eql(should.value); + }) + }) + + it('should make extra configs available', async () => { + startParallel(); + returnRemainder(); + configRemainder = {...configRemainder, seller: 'other.seller'}; + returnRemainder(); + endAuction(); + let configs = getPAAPIConfig().au.componentAuctions; + configs = [await resolveConfig(configs[0]), configs[1]]; + expect(configs.map(cfg => cfg.seller)).to.eql(['mock.seller', 'other.seller']); + }); + + describe('submodule\'s onAuctionConfig', () => { + let onAuctionConfig; + beforeEach(() => { + onAuctionConfig = sinon.stub(); + registerSubmodule({onAuctionConfig}) + }); + + Object.entries({ + 'parallel=true, some configs deferred': { + setup() { + config.mergeConfig({paapi: {parallel: true}}) + }, + delayed: false, + }, + 'parallel=true, no deferred configs': { + setup() { + config.mergeConfig({paapi: {parallel: true}}); + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); + }, + delayed: true + }, + 'parallel=false, some configs deferred': { + setup() { + config.mergeConfig({paapi: {parallel: false}}) + }, + delayed: true + } + }).forEach(([t, {setup, delayed}]) => { + describe(`when ${t}`, () => { + beforeEach(() => { + mockAuction.requestsDone = Promise.resolve(); + setup(); + }); + + function expectInvoked(shouldBeInvoked) { + if (shouldBeInvoked) { + sinon.assert.calledWith(onAuctionConfig, 'aid', sinon.match(arg => arg.au.componentAuctions[0].seller === 'mock.seller')); + } else { + sinon.assert.notCalled(onAuctionConfig); + } + } + + it(`should invoke onAuctionConfig when ${delayed ? 'auction ends' : 'auction requests have started'}`, async () => { + startParallel(); + await mockAuction.requestsDone; + expectInvoked(!delayed); + onAuctionConfig.reset(); + returnRemainder(); + endAuction(); + expectInvoked(delayed); + }) + }) + }) + }) + }); + }); + describe('when buildPAAPIConfigs returns igb', () => { + let builtCfg, igb, auctionConfig; + beforeEach(() => { + igb = {origin: 'mock.buyer'} + builtCfg = [{bidId: 'bidId', igb}]; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => builtCfg); + auctionConfig = { + seller: 'mock.seller', + decisionLogicUrl: 'mock.seller/decisionLogic' + } + config.mergeConfig({ + paapi: { + componentSeller: { + auctionConfig + } + } + }) + bidderRequest.paapi.componentSeller = true; + }); + Object.entries({ + 'componentSeller not configured'() { + bidderRequest.paapi.componentSeller = false; + }, + 'buildPAAPIconfig returns nothing'() { + builtCfg = [] + }, + 'returned igb is not valid'() { + builtCfg = [{bidId: 'bidId', igb: {}}]; + } + }).forEach(([t, setup]) => { + it(`should have no effect when ${t}`, () => { + setup(); + startParallel(); + expect(getPAAPIConfig()).to.eql({}); + }) + }) + + describe('when component seller is set up', () => { + it('should generate a deferred auctionConfig', () => { + startParallel(); + sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { + ...auctionConfig, + interestGroupBuyers: ['mock.buyer'], + }) + }); + + it('should use signal values from componentSeller.auctionConfig', async () => { + auctionConfig.auctionSignals = {test: 'signal'}; + config.mergeConfig({ + paapi: {componentSeller: {auctionConfig}} + }) + startParallel(); + endAuction(); + const cfg = await resolveConfig(getPAAPIConfig().au.componentAuctions[0]); + sinon.assert.match(cfg.auctionSignals, auctionConfig.auctionSignals); + }) + + it('should collate buyers', () => { + startParallel(); + startParallel(); + sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { + interestGroupBuyers: ['mock.buyer'] + }); + }); + + function returnIgb(igb) { + addPaapiConfigHook(sinon.stub(), bids[0], {igb}); + } + + it('should resolve to values from interpretResponse as well as buildPAAPIConfigs', async () => { + igb.cur = 'cur'; + igb.pbs = {over: 'ridden'} + startParallel(); + let cfg = getPAAPIConfig().au.componentAuctions[0]; + returnIgb({ + origin: 'mock.buyer', + pbs: {some: 'signal'} + }); + endAuction(); + cfg = await resolveConfig(cfg); + sinon.assert.match(cfg, { + perBuyerSignals: { + [igb.origin]: {some: 'signal'}, + }, + perBuyerCurrencies: { + [igb.origin]: 'cur' + } + }) + }); + + it('should not overwrite config once resolved', () => { + startParallel(); + returnIgb({ + origin: 'mock.buyer', + }); + endAuction(); + const cfg = getPAAPIConfig().au; + sinon.assert.match(cfg, Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, sinon.match(arg => arg instanceof Promise)]))) + }) + + it('can resolve multiple igbs', async () => { + igb.cur = 'cur1'; + startParallel(); + spec.code = 'other'; + igb.origin = 'other.buyer' + igb.cur = 'cur2' + startParallel(); + let cfg = getPAAPIConfig().au.componentAuctions[0]; + returnIgb({ + origin: 'mock.buyer', + pbs: {signal: 1} + }); + returnIgb({ + origin: 'other.buyer', + pbs: {signal: 2} + }); + endAuction(); + cfg = await resolveConfig(cfg); + sinon.assert.match(cfg, { + perBuyerSignals: { + 'mock.buyer': {signal: 1}, + 'other.buyer': {signal: 2} + }, + perBuyerCurrencies: { + 'mock.buyer': 'cur1', + 'other.buyer': 'cur2' + } + }) + }) + + function startMultiple() { + startParallel(); + spec.code = 'other'; + igb.origin = 'other.buyer' + startParallel(); + } + + describe('when using separateAuctions=false', () => { + beforeEach(() => { + config.mergeConfig({ + paapi: { + componentSeller: { + separateAuctions: false + } + } + }) + }); + + it('should merge igb from different specs into a single auction config', () => { + startMultiple(); + sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { + interestGroupBuyers: ['mock.buyer', 'other.buyer'] + }); + }); + }) + + describe('when using separateAuctions=true', () => { + beforeEach(() => { + config.mergeConfig({ + paapi: { + componentSeller: { + separateAuctions: true + } + } + }) + }); + it('should generate an auction config for each bidder', () => { + startMultiple(); + const components = getPAAPIConfig().au.componentAuctions; + sinon.assert.match(components[0], { + interestGroupBuyers: ['mock.buyer'] + }) + sinon.assert.match(components[1], { + interestGroupBuyers: ['other.buyer'] + }) + }) + }) + }) + }) + }); + }); + describe('ortb processors for fledge', () => { it('imp.ext.ae should be removed if fledge is not enabled', () => { const imp = {ext: {ae: 1, igs: {}}}; diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 7223940bc45..ceb3446b570 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1751,7 +1751,7 @@ describe('the price floors module', function () { const req = utils.deepClone(bidRequest); _floorDataForAuction[req.auctionId] = utils.deepClone(basicFloorConfig); - expect(guardTids('mock-bidder').bidRequest(req).getFloor({})).to.deep.equal({ + expect(guardTids({bidderCode: 'mock-bidder'}).bidRequest(req).getFloor({})).to.deep.equal({ currency: 'USD', floor: 1.0 }); From 91587a19096dc09f9391c3cc5b34879c2d2f8475 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 23 Oct 2024 10:36:01 -0700 Subject: [PATCH 0606/1097] Revert "gptPreAuction: fix missing gpid when using mcmEnabled (#12356)" (#12360) This reverts commit 2fb16e22a6432fed0e04f1dcf00282aaaacbb939. --- modules/gptPreAuction.js | 16 ++++------- test/spec/modules/gptPreAuction_spec.js | 38 ------------------------- 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 7052e6f97ea..29b9257d325 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -76,25 +76,21 @@ export const appendGptSlots = adUnits => { return acc; }, {}); - const adUnitPaths = {}; - window.googletag.pubads().getSlots().forEach(slot => { const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching ? customGptSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)); if (matchingAdUnitCode) { - const path = adUnitPaths[matchingAdUnitCode] = slot.getAdUnitPath(); const adserver = { name: 'gam', - adslot: sanitizeSlotPath(path) + adslot: sanitizeSlotPath(slot.getAdUnitPath()) }; adUnitMap[matchingAdUnitCode].forEach((adUnit) => { deepSetValue(adUnit, 'ortb2Imp.ext.data.adserver', Object.assign({}, adUnit.ortb2Imp?.ext?.data?.adserver, adserver)); }); } }); - return adUnitPaths; }; const sanitizeSlotPath = (path) => { @@ -107,7 +103,7 @@ const sanitizeSlotPath = (path) => { return path; } -const defaultPreAuction = (adUnit, adServerAdSlot, adUnitPath) => { +const defaultPreAuction = (adUnit, adServerAdSlot) => { const context = adUnit.ortb2Imp.ext.data; // use pbadslot if supplied @@ -121,7 +117,7 @@ const defaultPreAuction = (adUnit, adServerAdSlot, adUnitPath) => { } // find all GPT slots with this name - var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adUnitPath); + var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adServerAdSlot); if (gptSlots.length === 0) { return; // should never happen @@ -171,7 +167,7 @@ function warnDeprecation(adUnit) { } export const makeBidRequestsHook = (fn, adUnits, ...args) => { - const adUnitPaths = appendGptSlots(adUnits); + appendGptSlots(adUnits); const { useDefaultPreAuction, customPreAuction } = _currentConfig; adUnits.forEach(adUnit => { // init the ortb2Imp if not done yet @@ -194,9 +190,9 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { let adserverSlot = deepAccess(context, 'data.adserver.adslot'); let result; if (customPreAuction) { - result = customPreAuction(adUnit, adserverSlot, adUnitPaths[adUnit.code]); + result = customPreAuction(adUnit, adserverSlot); } else if (useDefaultPreAuction) { - result = defaultPreAuction(adUnit, adserverSlot, adUnitPaths[adUnit.code]); + result = defaultPreAuction(adUnit, adserverSlot); } if (result) { context.gpid = context.data.pbadslot = result; diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index bc41c811e7d..88062f2b785 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -209,17 +209,6 @@ describe('GPT pre-auction module', () => { expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: '/12345/slotCode2' }); }); - it('returns full ad unit path even if mcmEnabled is true', () => { - config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); - window.googletag.pubads().setSlots([ - makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), - ]); - const adUnit = {code: '/12345,21212/slot'}; - expect(appendGptSlots([adUnit])).to.eql({ - '/12345,21212/slot': '/12345,21212/slot' - }) - }) - it('will not trim child id if mcmEnabled is not set to true', () => { window.googletag.pubads().setSlots([ makeSlot({ code: '/12345,21212/slotCode1', divId: 'div1' }), @@ -470,23 +459,6 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); - it('should pass full slot path to customPreAuction when mcmEnabled is true', () => { - const customPreAuction = sinon.stub(); - config.setConfig({ - gptPreAuction: { - enabled: true, - mcmEnabled: true, - customPreAuction - } - }); - window.googletag.pubads().setSlots([ - makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), - ]); - const adUnit = {code: '/12345,21212/slot'}; - makeBidRequestsHook(sinon.stub(), [adUnit]); - sinon.assert.calledWith(customPreAuction, adUnit, '/12345/slot', adUnit.code); - }); - it('should use useDefaultPreAuction logic', () => { config.setConfig({ gptPreAuction: { @@ -568,16 +540,6 @@ describe('GPT pre-auction module', () => { runMakeBidRequests(testAdUnits); expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); - - it('sets gpid when mcmEnabled: true', () => { - config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); - window.googletag.pubads().setSlots([ - makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), - ]); - const adUnit = {code: '/12345,21212/slot'}; - makeBidRequestsHook(sinon.stub(), [adUnit]); - expect(adUnit.ortb2Imp.ext.gpid).to.eql('/12345/slot'); - }); }); describe('pps gpt config', () => { From 0c310c3aa7ae1af8654b05cbdd3cb9d8e4318b94 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 23 Oct 2024 12:28:05 -0700 Subject: [PATCH 0607/1097] gptPreAuction: fix missing gpid when using mcmEnabled (#12361) * gptPreAuction: fix missing gpid when using mcmEnabled * Fix npe when gpt is not present --- modules/gptPreAuction.js | 16 +++++---- test/spec/modules/gptPreAuction_spec.js | 48 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 29b9257d325..a6495e3570e 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -76,21 +76,25 @@ export const appendGptSlots = adUnits => { return acc; }, {}); + const adUnitPaths = {}; + window.googletag.pubads().getSlots().forEach(slot => { const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching ? customGptSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)); if (matchingAdUnitCode) { + const path = adUnitPaths[matchingAdUnitCode] = slot.getAdUnitPath(); const adserver = { name: 'gam', - adslot: sanitizeSlotPath(slot.getAdUnitPath()) + adslot: sanitizeSlotPath(path) }; adUnitMap[matchingAdUnitCode].forEach((adUnit) => { deepSetValue(adUnit, 'ortb2Imp.ext.data.adserver', Object.assign({}, adUnit.ortb2Imp?.ext?.data?.adserver, adserver)); }); } }); + return adUnitPaths; }; const sanitizeSlotPath = (path) => { @@ -103,7 +107,7 @@ const sanitizeSlotPath = (path) => { return path; } -const defaultPreAuction = (adUnit, adServerAdSlot) => { +const defaultPreAuction = (adUnit, adServerAdSlot, adUnitPath) => { const context = adUnit.ortb2Imp.ext.data; // use pbadslot if supplied @@ -117,7 +121,7 @@ const defaultPreAuction = (adUnit, adServerAdSlot) => { } // find all GPT slots with this name - var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adServerAdSlot); + var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adUnitPath); if (gptSlots.length === 0) { return; // should never happen @@ -167,7 +171,7 @@ function warnDeprecation(adUnit) { } export const makeBidRequestsHook = (fn, adUnits, ...args) => { - appendGptSlots(adUnits); + const adUnitPaths = appendGptSlots(adUnits); const { useDefaultPreAuction, customPreAuction } = _currentConfig; adUnits.forEach(adUnit => { // init the ortb2Imp if not done yet @@ -190,9 +194,9 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { let adserverSlot = deepAccess(context, 'data.adserver.adslot'); let result; if (customPreAuction) { - result = customPreAuction(adUnit, adserverSlot); + result = customPreAuction(adUnit, adserverSlot, adUnitPaths?.[adUnit.code]); } else if (useDefaultPreAuction) { - result = defaultPreAuction(adUnit, adserverSlot); + result = defaultPreAuction(adUnit, adserverSlot, adUnitPaths?.[adUnit.code]); } if (result) { context.gpid = context.data.pbadslot = result; diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 88062f2b785..989a5f376bb 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -209,6 +209,17 @@ describe('GPT pre-auction module', () => { expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: '/12345/slotCode2' }); }); + it('returns full ad unit path even if mcmEnabled is true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + expect(appendGptSlots([adUnit])).to.eql({ + '/12345,21212/slot': '/12345,21212/slot' + }) + }) + it('will not trim child id if mcmEnabled is not set to true', () => { window.googletag.pubads().setSlots([ makeSlot({ code: '/12345,21212/slotCode1', divId: 'div1' }), @@ -459,6 +470,33 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); + it('should pass full slot path to customPreAuction when mcmEnabled is true', () => { + const customPreAuction = sinon.stub(); + config.setConfig({ + gptPreAuction: { + enabled: true, + mcmEnabled: true, + customPreAuction + } + }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + makeBidRequestsHook(sinon.stub(), [adUnit]); + sinon.assert.calledWith(customPreAuction, adUnit, '/12345/slot', adUnit.code); + }); + + it('should not choke if gpt is not available', () => { + config.setConfig({ + gptPreAuction: { + enabled: true + } + }); + sandbox.stub(window, 'googletag').value(null); + makeBidRequestsHook(sinon.stub(), [{}]); + }) + it('should use useDefaultPreAuction logic', () => { config.setConfig({ gptPreAuction: { @@ -540,6 +578,16 @@ describe('GPT pre-auction module', () => { runMakeBidRequests(testAdUnits); expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); + + it('sets gpid when mcmEnabled: true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + makeBidRequestsHook(sinon.stub(), [adUnit]); + expect(adUnit.ortb2Imp.ext.gpid).to.eql('/12345/slot'); + }); }); describe('pps gpt config', () => { From c0122320ea329f04ac826e5e0535ebec4f8d6ff2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 23 Oct 2024 15:25:55 -0700 Subject: [PATCH 0608/1097] Paapi: log error instead of warning when auction configs are received too late (#12363) --- modules/paapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/paapi.js b/modules/paapi.js index 94ab2bae906..8b214ebe135 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -215,7 +215,7 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adU const hasDeferredConfigs = Object.keys(deferredConfigs).length > 0; if (moduleConfig.parallel && hasDeferredConfigs && newConfigs.length > 0) { - logWarn(`Received PAAPI configs after PAAPI auctions were already started in parallel with their contextual auction`, newConfigs) + logError(`Received PAAPI configs after PAAPI auctions were already started in parallel with their contextual auction`, newConfigs) } newConfigs.forEach(({adUnitCode, config}) => { From 6fc52d6297b03177f6c3daef2100fe7d0fbbf691 Mon Sep 17 00:00:00 2001 From: talbotja Date: Thu, 24 Oct 2024 11:58:16 +0100 Subject: [PATCH 0609/1097] Permutive Identity Manager: initial implementation (#12337) * Implement permutiveIdSystem userId submodule * minor changes following internal review * rename permutiveId -> permutiveIdentityManagerId emphasizes that permutive is not actually providing any IDs itself --- modules/.submodules.json | 1 + modules/permutiveIdentityManagerIdSystem.js | 151 ++++++++++++++++++ modules/permutiveIdentityManagerIdSystem.md | 58 +++++++ .../permutiveIdentityManagerIdSystem_spec.js | 126 +++++++++++++++ 4 files changed, 336 insertions(+) create mode 100644 modules/permutiveIdentityManagerIdSystem.js create mode 100644 modules/permutiveIdentityManagerIdSystem.md create mode 100644 test/spec/modules/permutiveIdentityManagerIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 36daa70e75b..d998a62500a 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -36,6 +36,7 @@ "novatiqIdSystem", "oneKeyIdSystem", "operaadsIdSystem", + "permutiveIdentityManagerIdSystem", "pubProvidedIdSystem", "publinkIdSystem", "quantcastIdSystem", diff --git a/modules/permutiveIdentityManagerIdSystem.js b/modules/permutiveIdentityManagerIdSystem.js new file mode 100644 index 00000000000..5dc12d44edb --- /dev/null +++ b/modules/permutiveIdentityManagerIdSystem.js @@ -0,0 +1,151 @@ +import {MODULE_TYPE_UID} from '../src/activities/modules.js' +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js' +import {prefixLog, safeJSONParse} from '../src/utils.js' +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const MODULE_NAME = 'permutiveIdentityManagerId' +const PERMUTIVE_ID_DATA_STORAGE_KEY = 'permutive-prebid-id' + +const ID5_DOMAIN = 'id5-sync.com' +const LIVERAMP_DOMAIN = 'liveramp.com' +const UID_DOMAIN = 'uidapi.com' + +const PRIMARY_IDS = ['id5id', 'idl_env', 'uid2'] + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}) + +const logger = prefixLog('[PermutiveID]') + +const readFromSdkLocalStorage = () => { + const data = safeJSONParse(storage.getDataFromLocalStorage(PERMUTIVE_ID_DATA_STORAGE_KEY)) + const id = {} + if (data && typeof data === 'object' && 'providers' in data && typeof data.providers === 'object') { + const now = Date.now() + for (const [idName, value] of Object.entries(data.providers)) { + if (PRIMARY_IDS.includes(idName) && value.userId) { + if (!value.expiryTime || value.expiryTime > now) { + id[idName] = value.userId + } + } + } + } + return id +} + +/** + * Catch and log errors + * @param {function} fn - Function to safely evaluate + */ +function makeSafe (fn) { + try { + return fn() + } catch (e) { + logger.logError(e) + } +} + +const waitAndRetrieveFromSdk = (timeoutMs) => + new Promise( + resolve => { + const fallback = setTimeout(() => { + logger.logInfo('timeout expired waiting for SDK - attempting read from local storage again') + resolve(readFromSdkLocalStorage()) + }, timeoutMs) + return window?.permutive?.ready(() => makeSafe(() => { + logger.logInfo('Permutive SDK is ready') + const onReady = makeSafe(() => window.permutive.addons.identity_manager.prebid.onReady) + if (typeof onReady === 'function') { + onReady((ids) => { + logger.logInfo('Permutive SDK has provided ids') + resolve(ids) + clearTimeout(fallback) + }) + } else { + logger.logError('Permutive SDK initialised but identity manager prebid api not present') + } + })) + } + ) + +/** @type {Submodule} */ +export const permutiveIdentityManagerIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @param {SubmoduleConfig|undefined} config + * @returns {(Object|undefined)} + */ + decode(value, config) { + return value + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function getId + * @param {SubmoduleConfig} submoduleConfig + * @param {ConsentData} consentData + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId(submoduleConfig, consentData, cacheIdObj) { + const id = readFromSdkLocalStorage() + if (Object.entries(id).length > 0) { + logger.logInfo('found id in sdk storage') + return { id } + } else if ('params' in submoduleConfig && submoduleConfig.params.ajaxTimeout) { + logger.logInfo('failed to find id in sdk storage - waiting for sdk') + // Is ajaxTimeout an appropriate timeout to use here? + return { callback: (done) => waitAndRetrieveFromSdk(submoduleConfig.params.ajaxTimeout).then(done) } + } else { + logger.logInfo('failed to find id in sdk storage and no wait time specified') + } + }, + + primaryIds: PRIMARY_IDS, + + eids: { + 'id5id': { + getValue: function (data) { + return data.uid + }, + source: ID5_DOMAIN, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext + } + } + }, + 'idl_env': { + source: LIVERAMP_DOMAIN, + atype: 3, + }, + 'uid2': { + source: UID_DOMAIN, + atype: 3, + getValue: function(data) { + return data.id + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext + } + } + } + } +} + +submodule('userId', permutiveIdentityManagerIdSubmodule) diff --git a/modules/permutiveIdentityManagerIdSystem.md b/modules/permutiveIdentityManagerIdSystem.md new file mode 100644 index 00000000000..ae249803d11 --- /dev/null +++ b/modules/permutiveIdentityManagerIdSystem.md @@ -0,0 +1,58 @@ +# Permutive Identity Manager + +This module supports [Permutive](https://permutive.com/) customers in using Permutive's Identity Manager functionality. + +To use this Prebid.js module it is assumed that the site includes Permutive's SDK, with Identity Manager configuration +enabled. See Permutive's user documentation for more information on Identity Manager. + +## Building Prebid.js with Permutive Identity Manager Support + +Prebid.js must be built with the `permutiveIdentityManagerIdSystem` module in order for Permutive's Identity Manager to be able to +activate relevant user identities to Prebid. + +To build Prebid.js with the `permutiveIdentityManagerIdSystem` module included: + +``` +gulp build --modules=userId,permutiveIdentityManagerIdSystem +``` + +## Prebid configuration + +There is minimal configuration required to be set on Prebid.js, since the bulk of the behaviour is managed through +Permutive's dashboard and SDK. + +It is recommended to keep the Prebid.js caching for this module short, since the mechanism by which Permutive's SDK +communicates with Prebid.js is effectively a local cache anyway. + +``` +pbjs.setConfig({ + ... + userSync: { + userIds: [ + { + name: 'permutiveIdentityManagerId', + params: { + ajaxTimeout: 90 + }, + storage: { + type: 'html5', + name: 'permutiveIdentityManagerId', + refreshInSeconds: 5 + } + } + ], + auctionDelay: 100 + }, + ... +}); +``` + +### ajaxTimeout + +By default this module will read IDs provided by the Permutive SDK from local storage when requested by prebid, and if +nothing is found, will not provide any identities. If a timeout is provided via the `ajaxTimeout` parameter, it will +instead wait for up to the specified number of milliseconds for Permutive's SDK to become available, and will retrieve +identities from the SDK directly if/when this happens. + +This value should be set to a value smaller than the `auctionDelay` set on the `userSync` configuration object, since +there is no point waiting longer than this as the auction will already have been triggered. \ No newline at end of file diff --git a/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js b/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js new file mode 100644 index 00000000000..96c581844c1 --- /dev/null +++ b/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js @@ -0,0 +1,126 @@ +import { permutiveIdentityManagerIdSubmodule, storage } from 'modules/permutiveIdentityManagerIdSystem' +import { deepSetValue } from 'src/utils.js' + +const STORAGE_KEY = 'permutive-prebid-id' + +describe('permutiveIdentityManagerIdSystem', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(STORAGE_KEY) + }) + + describe('decode', () => { + it('returns the input unchanged', () => { + const input = { + id5id: { + uid: '0', + ext: { + abTestingControlGroup: false, + linkType: 2, + pba: 'somepba' + } + } + } + const result = permutiveIdentityManagerIdSubmodule.decode(input) + expect(result).to.be.equal(input) + }) + }) + + describe('getId', () => { + it('returns relevant IDs from localStorage and does not return unexpected IDs', () => { + const data = getUserIdData() + storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify(data)) + const result = permutiveIdentityManagerIdSubmodule.getId({}) + const expected = { + 'id': { + 'id5id': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + } + } + } + expect(result).to.deep.equal(expected) + }) + + it('returns undefined if no relevant IDs are found in localStorage', () => { + storage.setDataInLocalStorage(STORAGE_KEY, '{}') + const result = permutiveIdentityManagerIdSubmodule.getId({}) + expect(result).to.be.undefined + }) + + it('will optionally wait for Permutive SDK if no identities are in local storage already', async () => { + const cleanup = setWindowPermutive() + const result = permutiveIdentityManagerIdSubmodule.getId({params: {ajaxTimeout: 50}}) + expect(result).not.to.be.undefined + expect(result.id).to.be.undefined + expect(result.callback).not.to.be.undefined + const expected = { + 'id5id': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + } + } + const r = await new Promise(result.callback) + expect(r).to.deep.equal(expected) + cleanup() + }) + }) +}) + +const setWindowPermutive = () => { + // Read from Permutive + const backup = window.permutive + + deepSetValue(window, 'permutive.ready', (f) => { + setTimeout(() => f(), 5) + }) + + deepSetValue(window, 'permutive.addons.identity_manager.prebid.onReady', (f) => { + setTimeout(() => f(sdkUserIdData()), 5) + }) + + // Cleanup + return () => window.permutive = backup +} + +const sdkUserIdData = () => ({ + 'id5id': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + }, +}) + +const getUserIdData = () => ({ + 'providers': { + 'id5id': { + 'userId': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + } + }, + 'fooid': { + 'userId': { + 'id': '1' + } + } + } +}) From 8b5d33ec963abd0ff4294a968c5cd8f944d10a03 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Fri, 25 Oct 2024 00:56:13 +1100 Subject: [PATCH 0610/1097] Supporting multi-format ads in prebid (#12265) --- .../gpt/adnuntius_multiformat_example.html | 132 ++++++++++ modules/adnuntiusBidAdapter.js | 86 ++++-- test/spec/modules/adnuntiusBidAdapter_spec.js | 249 +++++++++++++++++- 3 files changed, 446 insertions(+), 21 deletions(-) create mode 100644 integrationExamples/gpt/adnuntius_multiformat_example.html diff --git a/integrationExamples/gpt/adnuntius_multiformat_example.html b/integrationExamples/gpt/adnuntius_multiformat_example.html new file mode 100644 index 00000000000..87b30d5887a --- /dev/null +++ b/integrationExamples/gpt/adnuntius_multiformat_example.html @@ -0,0 +1,132 @@ + + + + + + + +

Adnuntius NATIVE

+
Ad Slot 1
+ + +
+ +
+ + + diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index d017b6a8398..cce1b5332ad 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -12,13 +12,20 @@ const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => { const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; -const DEFAULT_VAST_VERSION = 'vast4' +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const MAXIMUM_DEALS_LIMIT = 5; const VALID_BID_TYPES = ['netBid', 'grossBid']; const METADATA_KEY = 'adn.metaData'; const METADATA_KEY_SEPARATOR = '@@@'; export const misc = { + findHighestPrice: function(arr, bidType) { + return arr.reduce((highest, cur) => { + const currentBid = cur[bidType]; + const highestBid = highest[bidType] + return currentBid.currency === highestBid.currency && currentBid.amount > highestBid.amount ? cur : highest; + }, arr[0]); + } }; const storageTool = (function () { @@ -219,7 +226,7 @@ export const spec = { code: BIDDER_CODE, aliases: BIDDER_CODE_DEAL_ALIASES, gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function (bid) { // The auId MUST be a hexadecimal string const validAuId = AU_ID_REGEX.test(bid.params.auId); @@ -266,10 +273,6 @@ export const spec = { } let network = bid.params.network || 'network'; - if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') { - network += '_video' - } - bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); @@ -291,20 +294,40 @@ export const spec = { const bidTargeting = {...bid.params.targeting || {}}; targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest); - const adUnit = { ...bidTargeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; - const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); - if (maxDeals > 0) { - adUnit.maxDeals = maxDeals; + const mediaTypes = bid.mediaTypes || {}; + const validMediaTypes = SUPPORTED_MEDIA_TYPES.filter(mt => { + return mediaTypes[mt]; + }) || []; + if (validMediaTypes.length === 0) { + // banner ads by default if nothing specified, dimensions to be derived from the ad unit within adnuntius system + validMediaTypes.push(BANNER); } - if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes - networks[network].adUnits.push(adUnit); + const isSingleFormat = validMediaTypes.length === 1; + validMediaTypes.forEach(mediaType => { + const mediaTypeData = mediaTypes[mediaType]; + if (mediaType === VIDEO && mediaTypeData && mediaTypeData.context === 'outstream') { + return; + } + const targetId = (bid.params.targetId || bid.bidId) + (isSingleFormat || mediaType === BANNER ? '' : ('-' + mediaType)); + const adUnit = {...bidTargeting, auId: bid.params.auId, targetId: targetId}; + if (mediaType === VIDEO) { + adUnit.adType = 'VAST'; + } + const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); + if (maxDeals > 0) { + adUnit.maxDeals = maxDeals; + } + if (mediaType === BANNER && mediaTypeData && mediaTypeData.sizes) { + adUnit.dimensions = mediaTypeData.sizes; + } + networks[network].adUnits.push(adUnit); + }); } const requests = []; const networkKeys = Object.keys(networks); for (let j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; - if (network.indexOf('_video') > -1) { queryParamsAndValues.push('tt=' + DEFAULT_VAST_VERSION) } const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL requests.push({ method: 'POST', @@ -321,7 +344,7 @@ export const spec = { if (serverResponse.body.metaData) { storageTool.saveToStorage(serverResponse.body.metaData, serverResponse.body.network); } - const adUnits = serverResponse.body.adUnits; + const responseAdUnits = serverResponse.body.adUnits; let validatedBidType = validateBidType(config.getConfig().bidType); if (bidRequest.bid) { @@ -367,6 +390,35 @@ export const spec = { return adResponse; } + const highestYieldingAdUnits = []; + if (responseAdUnits.length === 1) { + highestYieldingAdUnits.push(responseAdUnits[0]); + } else if (responseAdUnits.length > 1) { + bidRequest.bid.forEach((resp) => { + const multiFormatAdUnits = []; + SUPPORTED_MEDIA_TYPES.forEach((mediaType) => { + const suffix = mediaType === BANNER ? '' : '-' + mediaType; + const targetId = (resp?.params?.targetId || resp.bidId) + suffix; + + const au = responseAdUnits.find((rAu) => { + return rAu.targetId === targetId && rAu.matchedAdCount > 0; + }); + if (au) { + multiFormatAdUnits.push(au); + } + }); + if (multiFormatAdUnits.length > 0) { + const highestYield = multiFormatAdUnits.length === 1 ? multiFormatAdUnits[0] : multiFormatAdUnits.reduce((highest, cur) => { + const highestBid = misc.findHighestPrice(highest.ads, validatedBidType)[validatedBidType]; + const curBid = misc.findHighestPrice(cur.ads, validatedBidType)[validatedBidType]; + return curBid.currency === highestBid.currency && curBid.amount > highestBid.amount ? cur : highest; + }, multiFormatAdUnits[0]); + highestYield.targetId = resp.bidId; + highestYieldingAdUnits.push(highestYield); + } + }); + } + const bidsById = bidRequest.bid.reduce((response, bid) => { return { ...response, @@ -374,7 +426,7 @@ export const spec = { }; }, {}); - const hasBidAdUnits = adUnits.filter((au) => { + const hasBidAdUnits = highestYieldingAdUnits.filter((au) => { const bid = bidsById[au.targetId]; if (bid && bid.bidder && BIDDER_CODE_DEAL_ALIASES.indexOf(bid.bidder) < 0) { return au.matchedAdCount > 0; @@ -384,7 +436,7 @@ export const spec = { return false; } }); - const hasDealsAdUnits = adUnits.filter((au) => { + const hasDealsAdUnits = highestYieldingAdUnits.filter((au) => { return au.deals && au.deals.length > 0; }); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index d4802ffd4c0..a0846a829a8 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -50,7 +50,6 @@ describe('adnuntiusBidAdapter', function () { const tzo = new Date().getTimezoneOffset(); const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; - const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`; @@ -102,7 +101,66 @@ describe('adnuntiusBidAdapter', function () { } }, } - ] + ]; + + const multiBidderInResponse = { + bid: [{ + bidder: 'adnuntius', + bidId: '3a602680158a85', + params: { + auId: '381535', + network: '1287', + bidType: 'netBid', + }, + mediaTypes: { + banner: { + sizes: [[200, 200]] + }, + video: { + playerSize: [200, 200], + context: 'instream' + } + } + }, + { + bidder: 'adnuntius', + params: { + auId: '381535', + network: '1287', + bidType: 'netBid', + targetId: 'fred', + }, + mediaTypes: { + banner: { + sizes: [[200, 200]] + }, + video: { + playerSize: [200, 200], + context: 'instream' + } + } + }] + }; + + const multiBidderRequest = [ + { + bidId: 'adn-0000000000000551', + bidder: 'adnuntius', + params: { + auId: '0000000000000551', + network: 'adnuntius', + }, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + }, + banner: { + sizes: [[1640, 1480], [1600, 1400]], + } + }, + } + ]; const singleBidRequest = { bid: [ @@ -184,6 +242,131 @@ describe('adnuntiusBidAdapter', function () { } ]; + const multiFormatServerResponse = { + body: { + 'adUnits': [ + { + 'auId': '0000000000381535', + 'targetId': '3a602680158a85-video', + 'vastXml': '\n', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-453419729', + 'ads': [ + { + 'cpm': { + 'amount': 1500.0, + 'currency': 'NOK' + }, + 'bid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.5, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'adId': 'adn-id-615465411', + 'vastXml': '' + } + ] + }, + { + 'auId': '0000000000381535', + 'targetId': 'fred-video', + 'vastXml': '', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp--1809523040', + 'ads': [ + { + 'cpm': { + 'amount': 1500.0, + 'currency': 'NOK' + }, + 'bid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.5, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'adId': 'adn-id-344789675', + 'selectedColumn': '0', + 'selectedColumnPosition': '0', + 'vastXml': '\n', + } + ] + }, + { + 'auId': '0000000000381535', + 'targetId': '3a602680158a85', + 'html': '\u003C!DOCTYPE html\u003E\n\n\u003C/html\u003E', + 'matchedAdCount': 0, + 'responseId': '', + 'ads': [] + }, + { + 'auId': '0000000000381535', + 'renderOption': 'DIV', + 'targetId': 'fred', + 'html': '\u003C!DOCTYPE html\u003E\n\u003C\u003E\n\u003C/html\u003E', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-1620340740', + 'ads': [ + { + 'destinationUrlEsc': '', + 'cpm': { + 'amount': 1250.0, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'bid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'html': '\u003Ca \'\u003E\u003C/script\u003E', + } + ] + } + ], + 'network': '1287', + 'keywords': [] + } + }; + const serverResponse = { body: { 'adUnits': [ @@ -565,11 +748,34 @@ describe('adnuntiusBidAdapter', function () { it('Test Video requests', function () { const request = spec.buildRequests(videoBidderRequest, {}); expect(request.length).to.equal(1); + + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(1); + expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551'); + expect(data.adUnits[0].adType).to.equal('VAST'); + expect(request[0]).to.have.property('bid'); const bid = request[0].bid[0] expect(bid).to.have.property('bidId'); expect(request[0]).to.have.property('url'); - expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + + it('Test multiformat requests', function () { + const request = spec.buildRequests(multiBidderRequest, {}); + expect(request.length).to.equal(1); + expect(request.data) + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(2); + expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551'); + expect(data.adUnits[0]).not.to.have.property('adType'); + expect(data.adUnits[1].targetId).to.equal('adn-0000000000000551-video'); + expect(data.adUnits[1].adType).to.equal('VAST'); + expect(request[0]).to.have.property('bid'); + const bid = request[0].bid[0] + expect(bid).to.have.property('bidId'); + expect(request[0]).to.have.property('url'); + expect(request[0].url).to.equal(ENDPOINT_URL); }); it('should pass segments if available in config and merge from targeting', function () { @@ -960,7 +1166,7 @@ describe('adnuntiusBidAdapter', function () { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(5); }); - it('Should allow a minumum of 0 deals.', function () { + it('Should allow a minimum of 0 deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -1102,6 +1308,41 @@ describe('adnuntiusBidAdapter', function () { expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90)); }); + it('should return valid response when passed valid multiformat server response', function () { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + bidType: 'netBid', + maxDeals: 0 + } + }); + + const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidderInResponse)); + expect(interpretedResponse).to.have.lengthOf(2); + + let ad = multiFormatServerResponse.body.adUnits[0].ads[0]; + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(ad.netBid.amount * 1000); + expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].ad).to.equal(multiFormatServerResponse.body.adUnits[0].html); + expect(interpretedResponse[0].ttl).to.equal(360); + + ad = multiFormatServerResponse.body.adUnits[3].ads[0]; + expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000); + expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[1].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[1].netRevenue).to.equal(false); + expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[3].html); + expect(interpretedResponse[1].ttl).to.equal(360); + }); + it('should not process valid response when passed alt bidder that is an adndeal', function () { const altBidder = { bid: [ From d4f57ee85a0fb5ae2d56affdd90994ac0f4ea627 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 24 Oct 2024 07:47:42 -0700 Subject: [PATCH 0611/1097] bidResponseFilter Module : do not run if not configured (#12362) * bidResponseFilter: do not run if not configured * fix lint * fix tests --- modules/bidResponseFilter/index.js | 21 ++++++-- test/spec/modules/bidResponseFilter_spec.js | 57 ++++++++++++++++++--- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index 3ace8108d2b..5b138965983 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -7,14 +7,29 @@ export const BID_CATEGORY_REJECTION_REASON = 'Category is not allowed'; export const BID_ADV_DOMAINS_REJECTION_REASON = 'Adv domain is not allowed'; export const BID_ATTR_REJECTION_REASON = 'Attr is not allowed'; +let moduleConfig; +let enabled = false; + function init() { - getHook('addBidResponse').before(addBidResponseHook); -}; + config.getConfig(MODULE_NAME, (cfg) => { + moduleConfig = cfg[MODULE_NAME]; + if (enabled && !moduleConfig) { + reset(); + } else if (!enabled && moduleConfig) { + enabled = true; + getHook('addBidResponse').before(addBidResponseHook); + } + }) +} + +export function reset() { + enabled = false; + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); +} export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; const battr = index.getBidRequest(bid)?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; - const moduleConfig = config.getConfig(MODULE_NAME); const catConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.cat || {})}; const advConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.adv || {})}; diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js index 3990cd3feb3..c4b4a776243 100644 --- a/test/spec/modules/bidResponseFilter_spec.js +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -1,15 +1,53 @@ -import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGORY_REJECTION_REASON, MODULE_NAME, PUBLISHER_FILTER_REJECTION_REASON, addBidResponseHook } from '../../../modules/bidResponseFilter'; -import { config } from '../../../src/config'; +import { + addBidResponseHook, + BID_ADV_DOMAINS_REJECTION_REASON, + BID_ATTR_REJECTION_REASON, + BID_CATEGORY_REJECTION_REASON, + init, + MODULE_NAME + , reset} from '../../../modules/bidResponseFilter'; +import {config} from '../../../src/config'; +import {addBidResponse} from '../../../src/auction.js'; describe('bidResponseFilter', () => { let mockAuctionIndex beforeEach(() => { - config.resetConfig(); mockAuctionIndex = { - getBidRequest: () => {}, - getAdUnit: () => {} + getBidRequest: () => { + }, + getAdUnit: () => { + } }; }); + afterEach(() => { + config.resetConfig(); + reset(); + }) + + describe('enable/disable', () => { + let reject, dispatch; + + beforeEach(() => { + reject = sinon.stub(); + dispatch = sinon.stub(); + }); + + it('should not run if not configured', () => { + reset(); + addBidResponse.call({dispatch}, 'au', {}, reject); + sinon.assert.notCalled(reject); + sinon.assert.called(dispatch); + }); + + it('should run if configured', () => { + config.setConfig({ + bidResponseFilter: {} + }); + addBidResponse.call({dispatch}, 'au', {}, reject); + sinon.assert.called(reject); + sinon.assert.notCalled(dispatch); + }) + }); it('should pass the bid after successful ortb2 rules validation', () => { const call = sinon.stub(); @@ -26,7 +64,8 @@ describe('bidResponseFilter', () => { } }; - addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex); + addBidResponseHook(call, 'adcode', bid, () => { + }, mockAuctionIndex); sinon.assert.calledOnce(call); }); @@ -109,7 +148,8 @@ describe('bidResponseFilter', () => { config.setConfig({[MODULE_NAME]: {cat: {enforce: false}}}); - addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex); + addBidResponseHook(call, 'adcode', bid, () => { + }, mockAuctionIndex); sinon.assert.calledOnce(call); }); @@ -129,7 +169,8 @@ describe('bidResponseFilter', () => { config.setConfig({[MODULE_NAME]: {cat: {blockUnknown: false}}}); - addBidResponseHook(call, 'adcode', bid, () => {}); + addBidResponseHook(call, 'adcode', bid, () => { + }); sinon.assert.calledOnce(call); }); }) From 51f329ea3437039a9455064430ac0b926e1b62e3 Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:37:12 -0400 Subject: [PATCH 0612/1097] Contxtful Bid Adapter : initial release (#12256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: contxtful bid adapter * fix: ajax * fix: config, valid bid request * fix: config, valid bid request * fix: tests * refactor: construct url * fix: test * fix: test * fix: space * fix: added beacon * fix: test * fix: test * fix: pbjs version * doc: beacon for ci * doc: spec to trigger ci * doc: log trigger ci * fix: imports --------- Co-authored-by: Sébastien Rufiange --- modules/contxtfulBidAdapter.js | 217 ++++++++ modules/contxtfulBidAdapter.md | 98 ++++ test/spec/modules/contxtfulBidAdapter_spec.js | 496 ++++++++++++++++++ 3 files changed, 811 insertions(+) create mode 100644 modules/contxtfulBidAdapter.js create mode 100644 modules/contxtfulBidAdapter.md create mode 100644 test/spec/modules/contxtfulBidAdapter_spec.js diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js new file mode 100644 index 00000000000..7f1a8702a3b --- /dev/null +++ b/modules/contxtfulBidAdapter.js @@ -0,0 +1,217 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { _each, buildUrl, isStr, isEmptyStr, logInfo, logError } from '../src/utils.js'; +import { sendBeacon, ajax } from '../src/ajax.js'; +import { config as pbjsConfig } from '../src/config.js'; +import { + isBidRequestValid, + interpretResponse, + getUserSyncs as getUserSyncsLib, +} from '../libraries/teqblazeUtils/bidderUtils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; + +// Constants +const BIDDER_CODE = 'contxtful'; +const BIDDER_ENDPOINT = 'prebid.receptivity.io'; +const MONITORING_ENDPOINT = 'monitoring.receptivity.io'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_TTL = 300; +const PREBID_VERSION = '$prebid.version$'; + +// ORTB conversion +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL + }, + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const reqData = buildRequest(imps, bidderRequest, context); + return reqData; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + return bidResponse; + } +}); + +// Get Bid Floor +const _getRequestBidFloor = (mediaTypes, paramsBidFloor, bid) => { + const bidMediaType = Object.keys(mediaTypes)[0] || 'banner'; + const bidFloor = { floor: 0, currency: 'USD' }; + + if (typeof bid.getFloor === 'function') { + const { currency, floor } = bid.getFloor({ + mediaType: bidMediaType, + size: '*' + }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); + } else if (paramsBidFloor) { + bidFloor.floor = paramsBidFloor + } + + return bidFloor; +} + +// Get Parameters from the config. +const extractParameters = (config) => { + const version = config?.contxtful?.version; + if (!isStr(version) || isEmptyStr(version)) { + throw Error(`contxfulBidAdapter: contxtful.version should be a non-empty string`); + } + + const customer = config?.contxtful?.customer; + if (!isStr(customer) || isEmptyStr(customer)) { + throw Error(`contxfulBidAdapter: contxtful.customer should be a non-empty string`); + } + + return { version, customer }; +} + +// Construct the Payload towards the Bidding endpoint +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); + + const bidRequests = []; + _each(validBidRequests, bidRequest => { + const { + mediaTypes = {}, + params = {}, + } = bidRequest; + bidRequest.bidFloor = _getRequestBidFloor(mediaTypes, params.bidfloor, bidRequest); + bidRequests.push(bidRequest) + }); + const config = pbjsConfig.getConfig(); + config.pbjsVersion = PREBID_VERSION; + const {version, customer} = extractParameters(config) + const adapterUrl = buildUrl({ + protocol: 'https', + host: BIDDER_ENDPOINT, + pathname: `/${version}/prebid/${customer}/bid`, + }); + + // https://docs.prebid.org/dev-docs/bidder-adaptor.html + let req = { + url: adapterUrl, + method: 'POST', + data: { + ortb2, + bidRequests, + bidderRequest, + config, + }, + }; + + return req; +}; + +// Prepare a sync object compatible with getUserSyncs. +const constructUrl = (userSyncsDefault, userSyncServer) => { + const urlSyncServer = (userSyncServer?.url ?? '').split('?'); + const userSyncUrl = userSyncsDefault?.url || ''; + const baseSyncUrl = urlSyncServer[0] || ''; + + let url = `${baseSyncUrl}${userSyncUrl}`; + + if (urlSyncServer.length > 1) { + const urlParams = urlSyncServer[1]; + url += url.includes('?') ? `&${urlParams}` : `?${urlParams}`; + } + + return { + ...userSyncsDefault, + url, + }; +}; + +// Returns the list of user synchronization objects. +const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + // Get User Sync Defaults from pbjs lib + const userSyncsDefaultLib = getUserSyncsLib('')(syncOptions, null, gdprConsent, uspConsent, gppConsent); + const userSyncsDefault = userSyncsDefaultLib?.find(item => item.url !== undefined); + + // Map Server Responses to User Syncs list + const serverSyncsData = serverResponses?.flatMap(response => response.body || []) + .map(data => data.syncs) + .find(syncs => Array.isArray(syncs) && syncs.length > 0) || []; + const userSyncs = serverSyncsData + .map(sync => constructUrl(userSyncsDefault, sync)) + .filter(Boolean); // Filter out nulls + return userSyncs; +}; + +// Retrieve the sampling rate for events +const getSamplingRate = (bidderConfig, eventType) => { + const entry = Object.entries(bidderConfig?.contxtful?.sampling ?? {}).find(([key, value]) => key.toLowerCase() === eventType.toLowerCase()); + return entry ? entry[1] : 0.001; +}; + +// Handles the logging of events +const logEvent = (eventType, data, options = {}) => { + const { + samplingEnabled = false, + } = options; + + try { + // Log event + logInfo(BIDDER_CODE, `[${eventType}] ${JSON.stringify(data)}`); + + // Get Config + const bidderConfig = pbjsConfig.getConfig(); + const {version, customer} = extractParameters(bidderConfig); + + // Sampled monitoring + if (samplingEnabled) { + const shouldSampleDice = Math.random(); + const samplingRate = getSamplingRate(bidderConfig, eventType); + if (shouldSampleDice >= samplingRate) { + return; // Don't sample + } + } + + const payload = { type: eventType, data }; + const eventUrl = buildUrl({ + protocol: 'https', + host: MONITORING_ENDPOINT, + pathname: `/${version}/prebid/${customer}/log/${eventType}`, + }); + + // Try sending a beacon + if (sendBeacon(eventUrl, JSON.stringify(payload))) { + logInfo(BIDDER_CODE, `[${eventType}] Logging data sent using Beacon and payload: ${JSON.stringify(data)}`); + } else { + // Fallback to using ajax + ajax(eventUrl, null, JSON.stringify(payload), { + method: 'POST', + contentType: 'application/json', + withCredentials: true, + }); + logInfo(BIDDER_CODE, `[${eventType}] Logging data sent using Ajax and payload: ${JSON.stringify(data)}`); + } + } catch (error) { + logError(BIDDER_CODE, `Failed to log event: ${eventType}`); + } +}; + +// Bidder public specification +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon: function(bid, options) { logEvent('onBidWon', bid, { samplingEnabled: false, ...options }); }, + onBidBillable: function(bid, options) { logEvent('onBidBillable', bid, { samplingEnabled: false, ...options }); }, + onAdRenderSucceeded: function(bid, options) { logEvent('onAdRenderSucceeded', bid, { samplingEnabled: false, ...options }); }, + onSetTargeting: function(bid, options) { }, + onTimeout: function(timeoutData, options) { logEvent('onTimeout', timeoutData, { samplingEnabled: true, ...options }); }, + onBidderError: function(args, options) { logEvent('onBidderError', args, { samplingEnabled: true, ...options }); }, +}; + +registerBidder(spec); diff --git a/modules/contxtfulBidAdapter.md b/modules/contxtfulBidAdapter.md new file mode 100644 index 00000000000..87a78c38a85 --- /dev/null +++ b/modules/contxtfulBidAdapter.md @@ -0,0 +1,98 @@ +# Overview + +``` +Module Name: Contxtful Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@contxtful.com +``` + +# Description + +The Contxtful Bidder Adapter supports all mediatypes and connects to demand sources for bids. + +# Configuration +## Global Configuration +Contxtful uses the global configuration to store params once instead of duplicating for each ad unit. +Also, enabling user syncing greatly increases match rates and monetization. +Be sure to call `pbjs.setConfig()` only once. + +```javascript +pbjs.setConfig({ + debug: false, + contxtful: { + customer: '', // Required + version: '', // Required + }, + userSync: { + filterSettings: { + iframe: { + bidders: ['contxtful'], + filter: 'include' + } + } + } + // [...] +}); +``` + +## Bidder Setting +Contxtful leverages local storage for user syncing. + +```javascript +pbjs.bidderSettings = { + contxtful: { + storageAllowed: true + } +} +``` + +# Example Ad-units configuration +```javascript +var adUnits = [ + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [{ + bidder: 'contxtful', + }] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [{ + bidder: 'contxtful', + }] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [{ + bidder: 'contxtful', + }] + } +]; +``` diff --git a/test/spec/modules/contxtfulBidAdapter_spec.js b/test/spec/modules/contxtfulBidAdapter_spec.js new file mode 100644 index 00000000000..02cb3ccef8a --- /dev/null +++ b/test/spec/modules/contxtfulBidAdapter_spec.js @@ -0,0 +1,496 @@ +import { spec } from 'modules/contxtfulBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import * as ajax from 'src/ajax.js'; +const VERSION = 'v1'; +const CUSTOMER = 'CUSTOMER'; +const BIDDER_ENDPOINT = 'prebid.receptivity.io'; +const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' }; + +describe('contxtful bid adapter', function () { + const adapter = newBidder(spec); + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('is a functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('valid code', function () { + it('should return the bidder code of contxtful', function () { + expect(spec.code).to.eql('contxtful'); + }); + }); + + let bidRequests = + [ + { + bidder: 'contxtful', + bidId: 'bId1', + custom_param_1: 'value_1', + transactionId: 'tId1', + params: { + bcat: ['cat1', 'cat2'], + badv: ['adv1', 'adv2'], + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + }, + }, + ortb2Imp: { + ext: { + tid: 't-id-test-1', + gpid: 'gpid-id-unitest-1' + }, + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'schain-seller-1.com', + sid: '00001', + hp: 1, + }, + ], + }, + getFloor: () => ({ currency: 'CAD', floor: 10 }), + } + ]; + + let expectedReceptivityData = { + rx: RX_FROM_API, + params: { + ev: VERSION, + ci: CUSTOMER, + }, + }; + + let bidderRequest = { + refererInfo: { + ref: 'https://my-referer-custom.com', + }, + ortb2: { + source: { + tid: 'auction-id', + }, + property_1: 'string_val_1', + regs: { + coppa: 1, + ext: { + us_privacy: '12345' + } + }, + user: { + data: [ + { + name: 'contxtful', + ext: expectedReceptivityData + } + ], + ext: { + eids: [ + { + source: 'id5-sync.com', + uids: [ + { + atype: 1, + id: 'fake-id5id', + }, + ] + } + ] + } + } + + }, + timeout: 1234, + uspConsent: '12345' + }; + + describe('valid configuration', function() { + const theories = [ + [ + null, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'null object for config', + ], + [ + {}, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'empty object for config', + ], + [ + { customer: CUSTOMER }, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'customer only in config', + ], + [ + { version: VERSION }, + 'contxfulBidAdapter: contxtful.customer should be a non-empty string', + 'version only in config', + ], + [ + { customer: CUSTOMER, version: '' }, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'empty string for version', + ], + [ + { customer: '', version: VERSION }, + 'contxfulBidAdapter: contxtful.customer should be a non-empty string', + 'empty string for customer', + ], + [ + { customer: '', version: '' }, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'empty string for version & customer', + ], + ]; + + theories.forEach(([params, expectedErrorMessage, description]) => { + it('detects invalid configuration and throws the expected error (' + description + ')', () => { + config.setConfig({ + contxtful: params + }); + expect(() => spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + })).to.throw( + expectedErrorMessage + ); + }); + }); + + it('uses a valid configuration and returns the right url', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION} + }); + const bidRequest = spec.buildRequests(bidRequests); + expect(bidRequest.url).to.eq('https://' + BIDDER_ENDPOINT + `/${VERSION}/prebid/${CUSTOMER}/bid`) + }); + + it('will take specific ortb2 configuration parameters and returns it in ortb2 object', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(bidRequest.data.ortb2.property_1).to.equal('string_val_1'); + }); + }); + + describe('valid bid request', function () { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + + it('will return a data property containing properties ortb2, bidRequests, bidderRequest and config', () => { + expect(bidRequest.data).not.to.be.undefined; + expect(bidRequest.data.ortb2).not.to.be.undefined; + expect(bidRequest.data.bidRequests).not.to.be.undefined; + expect(bidRequest.data.bidderRequest).not.to.be.undefined; + expect(bidRequest.data.config).not.to.be.undefined; + }); + + it('will take custom parameters in the bid request and within the bidRequests array', () => { + expect(bidRequest.data.bidRequests[0].custom_param_1).to.equal('value_1') + }); + + it('will return any supply chain parameters within the bidRequests array', () => { + expect(bidRequest.data.bidRequests[0].schain.ver).to.equal('1.0'); + }); + + it('will return floor request within the bidFloor parameter in the bidRequests array', () => { + expect(bidRequest.data.bidRequests[0].bidFloor.currency).to.equal('CAD'); + expect(bidRequest.data.bidRequests[0].bidFloor.floor).to.equal(10); + }); + + it('will return the usp string in the uspConsent parameter within the bidderRequest property', () => { + expect(bidRequest.data.bidderRequest.uspConsent).to.equal('12345'); + }); + + it('will contains impressions array on ortb2.imp object for all ad units', () => { + expect(bidRequest.data.ortb2.imp.length).to.equal(1); + expect(bidRequest.data.ortb2.imp[0].id).to.equal('bId1'); + }); + + it('will contains the registration on ortb2.regs object', () => { + expect(bidRequest.data.ortb2.regs).not.to.be.undefined; + expect(bidRequest.data.ortb2.regs.coppa).to.equal(1); + expect(bidRequest.data.ortb2.regs.ext.us_privacy).to.equal('12345') + }) + + it('will contains the eids modules within the ortb2.user.ext.eids', () => { + expect(bidRequest.data.ortb2.user.ext.eids).not.to.be.undefined; + expect(bidRequest.data.ortb2.user.ext.eids[0].source).to.equal('id5-sync.com'); + expect(bidRequest.data.ortb2.user.ext.eids[0].uids[0].id).to.equal('fake-id5id'); + }); + + it('will contains the receptivity value within the ortb2.user.data with contxtful name', () => { + let obtained_receptivity_data = bidRequest.data.ortb2.user.data.filter(function(userData) { + return userData.name == 'contxtful'; + }); + expect(obtained_receptivity_data.length).to.equal(1); + expect(obtained_receptivity_data[0].ext).to.deep.equal(expectedReceptivityData); + }); + + it('will contains ortb2Imp of the bid request within the ortb2.imp.ext', () => { + let first_imp = bidRequest.data.ortb2.imp[0]; + expect(first_imp.ext).not.to.be.undefined; + expect(first_imp.ext.tid).to.equal('t-id-test-1'); + expect(first_imp.ext.gpid).to.equal('gpid-id-unitest-1'); + }); + }); + + describe('valid bid request with no floor module', () => { + let noFloorsBidRequests = + [ + { + bidder: 'contxtful', + bidId: 'bId1', + transactionId: 'tId1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + }, + }, + }, + { + bidder: 'contxtful', + bidId: 'bId2', + transactionId: 'tId2', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + }, + }, + params: { + bidfloor: 54 + } + }, + ]; + + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const bidRequest = spec.buildRequests(noFloorsBidRequests, bidderRequest); + it('will contains default value of floor if the bid request do not contains floor function', () => { + expect(bidRequest.data.bidRequests[0].bidFloor.currency).to.equal('USD'); + expect(bidRequest.data.bidRequests[0].bidFloor.floor).to.equal(0); + }); + + it('will take the param.bidfloor as floor value if possible', () => { + expect(bidRequest.data.bidRequests[1].bidFloor.currency).to.equal('USD'); + expect(bidRequest.data.bidRequests[1].bidFloor.floor).to.equal(54); + }); + }); + + describe('valid bid response', () => { + const bidResponse = [ + { + 'requestId': 'arequestId', + 'originalCpm': 1.5, + 'cpm': 1.35, + 'currency': 'CAD', + 'width': 300, + 'height': 600, + 'creativeId': 'creativeid', + 'netRevenue': true, + 'ttl': 300, + 'ad': '', + 'mediaType': 'banner', + 'syncs': [ + { + 'url': 'mysyncurl.com?qparam1=qparamv1&qparam2=qparamv2' + } + ] + } + ]; + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + + it('will interpret response correcly', () => { + const bids = spec.interpretResponse({ body: bidResponse }, bidRequest); + expect(bids).not.to.be.undefined; + expect(bids).to.have.lengthOf(1); + expect(bids).to.deep.equal(bidResponse); + }); + + it('will return empty response if bid response is empty', () => { + const bids = spec.interpretResponse({ body: [] }, bidRequest); + expect(bids).to.have.lengthOf(0); + }) + + it('will trigger user sync if enable pixel mode', () => { + const syncOptions = { + pixelEnabled: true + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [{ body: bidResponse }]); + expect(userSyncs).to.deep.equal([ + { + 'url': 'mysyncurl.com/image?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2', + 'type': 'image' + } + ]); + }); + + it('will trigger user sync if enable iframe mode', () => { + const syncOptions = { + iframeEnabled: true + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [{ body: bidResponse }]); + expect(userSyncs).to.deep.equal([ + { + 'url': 'mysyncurl.com/iframe?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2', + 'type': 'iframe' + } + ]); + }); + + describe('no sync option', () => { + it('will return image sync if no sync options', () => { + const userSyncs = spec.getUserSyncs({}, [{ body: bidResponse }]); + expect(userSyncs).to.deep.equal([ + { + 'url': 'mysyncurl.com/image?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2', + 'type': 'image' + } + ]); + }); + it('will return empty value if no server response', () => { + const userSyncs = spec.getUserSyncs({}, []); + expect(userSyncs).to.have.lengthOf(0); + const userSyncs2 = spec.getUserSyncs({}, null); + expect(userSyncs2).to.have.lengthOf(0); + }); + }); + + it('will return empty value if no server response', () => { + const syncOptions = { + iframeEnabled: true + }; + + const userSyncs = spec.getUserSyncs(syncOptions, []); + expect(userSyncs).to.have.lengthOf(0); + const userSyncs2 = spec.getUserSyncs(syncOptions, null); + expect(userSyncs2).to.have.lengthOf(0); + }); + + describe('on timeout callback', () => { + it('will never call server if sampling is 0 with sendBeacon available', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 0.0}}, + }); + + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw; + expect(beaconStub.called).to.be.false; + expect(ajaxStub.called).to.be.false; + }); + + it('will always call server if sampling is 1 with sendBeacon available', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 1.0}}, + }); + + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw; + expect(beaconStub.called).to.be.true; + expect(ajaxStub.called).to.be.false; + }); + + it('will always call server if sampling is 1 with sendBeacon not available', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 1.0}}, + }); + + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw; + expect(beaconStub.called).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + expect(ajaxStub.calledOnce).to.be.true; + }); + }); + + describe('on onBidderError callback', () => { + it('will always call server if sampling is 1', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onBidderError': 1.0}}, + }); + + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onBidderError({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + + describe('on onBidWon callback', () => { + it('will always call server', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onBidWon({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + + describe('on onBidBillable callback', () => { + it('will always call server', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onBidBillable({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + + describe('on onAdRenderSucceeded callback', () => { + it('will always call server', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onAdRenderSucceeded({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + }); +}); From 34f0a55c90d4819fcc932bff2fd86e86fa91e28e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 24 Oct 2024 18:05:56 +0000 Subject: [PATCH 0613/1097] Prebid 9.17.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0091ce17f24..4b348d91230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.17.0-pre", + "version": "9.17.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.17.0-pre", + "version": "9.17.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 47ec8b7f81b..97949598f70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.17.0-pre", + "version": "9.17.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From aafef47d62771faa140df4d2d787c1e5f2cc4da1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 24 Oct 2024 18:05:56 +0000 Subject: [PATCH 0614/1097] Increment version to 9.18.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b348d91230..655fb074ca1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.17.0", + "version": "9.18.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.17.0", + "version": "9.18.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 97949598f70..b58bb7a2700 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.17.0", + "version": "9.18.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 260a1b69ad316538c21948740c4ff47277ce9f99 Mon Sep 17 00:00:00 2001 From: Nikhil <137479857+NikhilGopalChennissery@users.noreply.github.com> Date: Fri, 25 Oct 2024 00:15:49 +0530 Subject: [PATCH 0615/1097] Preciso Bid adapter : Native implemented (#12278) * Bid adapter added * added the coverage code * precisonatBidAdapter.md file added * bid price macro replacement * tracking url encoding removed * fix * test * modified the adapter for native * test logs removed * updated * error fixed * Added new library bidNativeUtils.js --------- Co-authored-by: PrecisoSRL <134591565+PrecisoSRL@users.noreply.github.com> --- .../gpt/precisonativeExample.html | 203 ++++++++++++++++++ libraries/precisoUtils/bidNativeUtils.js | 104 +++++++++ libraries/precisoUtils/bidUtils.js | 196 ++++++++++++++--- libraries/precisoUtils/bidUtilsCommon.js | 11 - modules/admaticBidAdapter.js | 62 +----- modules/precisoBidAdapter.js | 25 +-- modules/precisoBidAdapter.md | 61 +++--- modules/rtbhouseBidAdapter.js | 89 +------- .../precisoUtils/bidNativeUtils_spec.js | 78 +++++++ .../libraries/precisoUtils/bidUtils_spec.js | 2 +- test/spec/modules/precisoBidAdapter_spec.js | 184 ++++++++++++++++ test/spec/modules/rtbhouseBidAdapter_spec.js | 3 +- 12 files changed, 787 insertions(+), 231 deletions(-) create mode 100644 integrationExamples/gpt/precisonativeExample.html create mode 100644 libraries/precisoUtils/bidNativeUtils.js create mode 100644 test/spec/libraries/precisoUtils/bidNativeUtils_spec.js diff --git a/integrationExamples/gpt/precisonativeExample.html b/integrationExamples/gpt/precisonativeExample.html new file mode 100644 index 00000000000..4e7009e2c58 --- /dev/null +++ b/integrationExamples/gpt/precisonativeExample.html @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + +

Ad Serverless Test Page

+

+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's + standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make + a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, + remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing + Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions + of Lorem Ipsum +

+
+

+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin + literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney + College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and + going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum + comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by + Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. + The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. +

+
+ + + \ No newline at end of file diff --git a/libraries/precisoUtils/bidNativeUtils.js b/libraries/precisoUtils/bidNativeUtils.js new file mode 100644 index 00000000000..29b39f6d77d --- /dev/null +++ b/libraries/precisoUtils/bidNativeUtils.js @@ -0,0 +1,104 @@ +import { deepAccess, logInfo } from '../../src/utils.js'; +import { NATIVE } from '../../src/mediaTypes.js'; +import { macroReplace } from './bidUtils.js'; + +const TTL = 55; +// Codes defined by OpenRTB Native Ads 1.1 specification +export const OPENRTB = { + NATIVE: { + IMAGE_TYPE: { + ICON: 1, + MAIN: 3, + }, + ASSET_ID: { + TITLE: 1, + IMAGE: 2, + ICON: 3, + BODY: 4, + SPONSORED: 5, + CTA: 6 + }, + DATA_ASSET_TYPE: { + SPONSORED: 1, + DESC: 2, + CTA_TEXT: 12, + }, + } +}; + +/** + * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3 + * @returns {object} Prebid native bidObject + */ +export function interpretNativeBid(serverBid) { + return { + requestId: serverBid.impid, + mediaType: NATIVE, + cpm: serverBid.price, + creativeId: serverBid.adid || serverBid.crid, + width: 1, + height: 1, + ttl: TTL, + meta: { + advertiserDomains: serverBid.adomain + }, + netRevenue: true, + currency: 'USD', + // native: interpretNativeAd(serverBid.adm) + native: interpretNativeAd(macroReplace(serverBid.adm, serverBid.price)) + } +} + +/** + * @param {string} adm JSON-encoded Request by OpenRTB Native Ads 1.1 §4.1 + * @returns {object} Prebid bidObject.native + */ + +export function interpretNativeAd(adm) { + try { + const native = JSON.parse(adm).native; + if (native) { + const result = { + clickUrl: encodeURI(native.link.url), + impressionTrackers: native.imptrackers || native.eventtrackers[0].url, + }; + if (native.link.clicktrackers) { + result.clickTrackers = native.link.clicktrackers[0]; + } + + native.assets.forEach(asset => { + switch (asset.id) { + case OPENRTB.NATIVE.ASSET_ID.TITLE: + result.title = deepAccess(asset, 'title.text'); + break; + case OPENRTB.NATIVE.ASSET_ID.IMAGE: + result.image = { + url: encodeURI(asset.img.url), + width: deepAccess(asset, 'img.w'), + height: deepAccess(asset, 'img.h') + }; + break; + case OPENRTB.NATIVE.ASSET_ID.ICON: + result.icon = { + url: encodeURI(asset.img.url), + width: deepAccess(asset, 'img.w'), + height: deepAccess(asset, 'img.h') + }; + break; + case OPENRTB.NATIVE.ASSET_ID.BODY: + result.body = deepAccess(asset, 'data.value'); + break; + case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + result.sponsoredBy = deepAccess(asset, 'data.value'); + break; + case OPENRTB.NATIVE.ASSET_ID.CTA: + result.cta = deepAccess(asset, 'data.value'); + break; + } + }); + return result; + } + } catch (error) { + logInfo('Error in bidUtils interpretNativeAd' + error); + } +} diff --git a/libraries/precisoUtils/bidUtils.js b/libraries/precisoUtils/bidUtils.js index 95278cd1013..8359963cd03 100644 --- a/libraries/precisoUtils/bidUtils.js +++ b/libraries/precisoUtils/bidUtils.js @@ -1,33 +1,16 @@ import { convertOrtbRequestToProprietaryNative } from '../../src/native.js'; -import { replaceAuctionPrice } from '../../src/utils.js'; +import { replaceAuctionPrice, deepAccess } from '../../src/utils.js'; import { ajax } from '../../src/ajax.js'; -import { consentCheck } from './bidUtilsCommon.js'; +// import { NATIVE } from '../../src/mediaTypes.js'; +import { consentCheck, getBidFloor } from './bidUtilsCommon.js'; +import { interpretNativeBid } from './bidNativeUtils.js'; export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var city = Intl.DateTimeFormat().resolvedOptions().timeZone; let req = { - // bidRequest: bidderRequest, id: validBidRequests[0].auctionId, - cur: validBidRequests[0].params.currency || ['USD'], - imp: validBidRequests.map(req => { - const { bidId, sizes } = req - const impValue = { - id: bidId, - bidfloor: req.params.bidFloor, - bidfloorcur: req.params.currency - } - if (req.mediaTypes.banner) { - impValue.banner = { - format: (req.mediaTypes.banner.sizes || sizes).map(size => { - return { w: size[0], h: size[1] } - }), - - } - } - return impValue - }), + imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)), user: { id: validBidRequests[0].userId.pubcid || '', buyeruid: validBidRequests[0].buyerUid || '', @@ -55,7 +38,6 @@ export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest publisherId: validBidRequests[0].params.publisherId }; - // req.language.indexOf('-') != -1 && (req.language = req.language.split('-')[0]) consentCheck(bidderRequest, req); return { method: 'POST', @@ -96,8 +78,172 @@ export function onBidWon(bid) { } } -/* replacing auction_price macro from adm */ -function macroReplace(adm, cpm) { +export function macroReplace(adm, cpm) { let replacedadm = replaceAuctionPrice(adm, cpm); return replacedadm; } + +function mapImpression(slot, bidderRequest) { + const imp = { + id: slot.bidId, + bidFloor: getBidFloor(slot), + }; + + if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) { + imp.native = mapNative(slot) + } else { + imp.banner = mapBanner(slot) + } + return imp +} + +function mapNative(slot) { + if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) { + let request = { + assets: slot.nativeOrtbRequest.assets || slot.nativeParams.ortb.assets, + ver: '1.2' + }; + return { + request: JSON.stringify(request) + } + } +} + +function mapBanner(slot) { + if (slot.mediaTypes.banner) { + let format = (slot.mediaTypes.banner.sizes || slot.sizes).map(size => { + return { w: size[0], h: size[1] } + }); + + return { + format + } + } +} + +export function buildBidResponse(serverResponse) { + const responseBody = serverResponse.body; + const bids = []; + responseBody.seatbid.forEach(seat => { + seat.bid.forEach(serverBid => { + if (!serverBid.price) { + return; + } + if (serverBid.adm.indexOf('{') === 0) { + let interpretedBid = interpretNativeBid(serverBid); + bids.push(interpretedBid + ); + } else { + bids.push({ + requestId: serverBid.impid, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.crid, + ad: macroReplace(serverBid.adm, serverBid.price), + currency: 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: serverBid.adomain || '', + }, + }); + } + }) + }); + return bids; +} + +// export function interpretNativeAd(adm) { +// try { +// // logInfo('adm::' + adm); +// const native = JSON.parse(adm).native; +// if (native) { +// const result = { +// clickUrl: encodeURI(native.link.url), +// impressionTrackers: native.eventtrackers[0].url, +// }; +// if (native.link.clicktrackers[0]) { +// result.clickTrackers = native.link.clicktrackers[0]; +// } + +// native.assets.forEach(asset => { +// switch (asset.id) { +// case OPENRTB.NATIVE.ASSET_ID.TITLE: +// result.title = deepAccess(asset, 'title.text'); +// break; +// case OPENRTB.NATIVE.ASSET_ID.IMAGE: +// result.image = { +// url: encodeURI(asset.img.url), +// width: deepAccess(asset, 'img.w'), +// height: deepAccess(asset, 'img.h') +// }; +// break; +// case OPENRTB.NATIVE.ASSET_ID.ICON: +// result.icon = { +// url: encodeURI(asset.img.url), +// width: deepAccess(asset, 'img.w'), +// height: deepAccess(asset, 'img.h') +// }; +// break; +// case OPENRTB.NATIVE.ASSET_ID.DATA: +// result.body = deepAccess(asset, 'data.value'); +// break; +// case OPENRTB.NATIVE.ASSET_ID.SPONSORED: +// result.sponsoredBy = deepAccess(asset, 'data.value'); +// break; +// case OPENRTB.NATIVE.ASSET_ID.CTA: +// result.cta = deepAccess(asset, 'data.value'); +// break; +// } +// }); +// return result; +// } +// } catch (error) { +// logInfo('Error in bidUtils interpretNativeAd' + error); +// } +// } + +// export const OPENRTB = { +// NATIVE: { +// IMAGE_TYPE: { +// ICON: 1, +// MAIN: 3, +// }, +// ASSET_ID: { +// TITLE: 1, +// IMAGE: 2, +// ICON: 3, +// BODY: 4, +// SPONSORED: 5, +// CTA: 6 +// }, +// DATA_ASSET_TYPE: { +// SPONSORED: 1, +// DESC: 2, +// CTA_TEXT: 12, +// }, +// } +// }; + +// /** +// * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3 +// * @returns {object} Prebid native bidObject +// */ +// export function interpretNativeBid(serverBid) { +// return { +// requestId: serverBid.impid, +// mediaType: NATIVE, +// cpm: serverBid.price, +// creativeId: serverBid.adid || serverBid.crid, +// width: 1, +// height: 1, +// ttl: 56, +// meta: { +// advertiserDomains: serverBid.adomain +// }, +// netRevenue: true, +// currency: 'USD', +// native: interpretNativeAd(macroReplace(serverBid.adm, serverBid.price)) +// } +// } diff --git a/libraries/precisoUtils/bidUtilsCommon.js b/libraries/precisoUtils/bidUtilsCommon.js index cb9699035fb..74cf00b8450 100644 --- a/libraries/precisoUtils/bidUtilsCommon.js +++ b/libraries/precisoUtils/bidUtilsCommon.js @@ -61,16 +61,6 @@ export const buildBidRequests = (adurl) => (validBidRequests = [], bidderRequest placements: placements }; consentCheck(bidderRequest, request); - - // if (bidderRequest) { - // if (bidderRequest.uspConsent) { - // request.ccpa = bidderRequest.uspConsent; - // } - // if (bidderRequest.gdprConsent) { - // request.gdpr = bidderRequest.gdprConsent; - // } - // } - const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; @@ -134,7 +124,6 @@ export const buildUserSyncs = (syncOptions, serverResponses, gdprConsent, uspCon let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; const isCk2trk = syncEndpoint.includes('ck.2trk.info'); - // Base sync URL let syncUrl = isCk2trk ? syncEndpoint : `${syncEndpoint}/${syncType}?pbjs=1`; if (gdprConsent && gdprConsent.consentString) { diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 74c063042ef..78e76651177 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -4,6 +4,7 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; +import { interpretNativeAd } from '../libraries/precisoUtils/bidNativeUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -11,28 +12,6 @@ import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest */ -export const OPENRTB = { - N: { - IMAGE_TYPE: { - ICON: 1, - MAIN: 3, - }, - ASSET_ID: { - TITLE: 1, - IMAGE: 2, - ICON: 3, - BODY: 4, - SPONSORED: 5, - CTA: 6 - }, - DATA_ASSET_TYPE: { - SPONSORED: 1, - DESC: 2, - CTA_TEXT: 12, - }, - } -}; - let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -416,45 +395,6 @@ function concatSizes(bid) { } } -function interpretNativeAd(adm) { - const native = JSON.parse(adm).native; - const result = { - clickUrl: encodeURI(native.link.url), - impressionTrackers: native.imptrackers - }; - native.assets.forEach(asset => { - switch (asset.id) { - case OPENRTB.N.ASSET_ID.TITLE: - result.title = asset.title.text; - break; - case OPENRTB.N.ASSET_ID.IMAGE: - result.image = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.N.ASSET_ID.ICON: - result.icon = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.N.ASSET_ID.BODY: - result.body = asset.data.value; - break; - case OPENRTB.N.ASSET_ID.SPONSORED: - result.sponsoredBy = asset.data.value; - break; - case OPENRTB.N.ASSET_ID.CTA: - result.cta = asset.data.value; - break; - } - }); - return result; -} - function _validateId(id) { return (parseInt(id) > 0); } diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 6f6ab82eca2..8c244dca040 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,15 +1,14 @@ import { logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -import { buildRequests, interpretResponse, onBidWon } from '../libraries/precisoUtils/bidUtils.js'; +import { buildBidResponse, buildRequests, onBidWon } from '../libraries/precisoUtils/bidUtils.js'; import { buildUserSyncs } from '../libraries/precisoUtils/bidUtilsCommon.js'; const BIDDER__CODE = 'preciso'; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE }); -// export const storage2 = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE }); -const SUPPORTED_MEDIA_TYPES = [BANNER]; +const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE]; const GVLID = 874; let precisoId = 'NA'; let sharedId = 'NA'; @@ -37,17 +36,10 @@ export const spec = { return Boolean(bid.bidId && bid.params && bid.params.publisherId && precisoBid); }, buildRequests: buildRequests(endpoint), - interpretResponse, + interpretResponse: buildBidResponse, onBidWon, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - const isSpec = syncOptions.spec; - if (!Object.is(isSpec, true)) { - let syncId = storage.getCookie('_sharedid'); - syncEndpoint = syncEndpoint + 'id=' + syncId; - } else { - syncEndpoint = syncEndpoint + 'id=NA'; - } - + syncEndpoint = syncEndpoint + 'id=' + sharedId; return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint); } }; @@ -56,17 +48,16 @@ registerBidder(spec); async function getapi(url) { try { - // Storing response const response = await fetch(url); - - // Storing data in form of JSON var data = await response.json(); const dataMap = new Map(Object.entries(data)); const uuidValue = dataMap.get('UUID'); if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) { - storage.setDataInLocalStorage('_pre|id', uuidValue); + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage('_pre|id', uuidValue); + } } return data; } catch (error) { diff --git a/modules/precisoBidAdapter.md b/modules/precisoBidAdapter.md index b1fb0d062da..97521f195d8 100644 --- a/modules/precisoBidAdapter.md +++ b/modules/precisoBidAdapter.md @@ -14,9 +14,9 @@ Module that connects to preciso' demand sources | Name | Scope | Description | Example | | :------------ | :------- | :------------------------ | :------------------- | -| `region` | required (for prebid.js) | region | "prebid-eu" | -| `publisherId` | required (for prebid-server) | partner ID | "1901" | -| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | +| `region` | optional (for prebid.js) | 3 letter country code | "USA" | +| `publisherId` | required (for prebid-server) | partner ID provided by preciso | PreTest_0001 | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native | "banner" | # Test Parameters ``` @@ -25,7 +25,35 @@ Module that connects to preciso' demand sources { code: 'placementId_0', mediaTypes: { - native: {} + native: { + ortb: { + assets: [ + { + id: 3, + required: 1, + img: { + type: 3, + w: 300, + h: 250 + } + }, + { + id: 1, + required: 1, + title: { + len: 800 + } + }, + { + id: 4, + required: 0, + data: { + type: 1 + } + } + ] + } + } }, bids: [ { @@ -33,7 +61,7 @@ Module that connects to preciso' demand sources params: { host: 'prebid', publisherId: '0', - region: 'prebid-eu', + region: 'USA', traffic: 'native' } } @@ -53,32 +81,11 @@ Module that connects to preciso' demand sources params: { host: 'prebid', publisherId: '0', - region: 'prebid-eu', + region: 'USA', traffic: 'banner' } } ] - }, - // Will return test vast xml. All video params are stored under placement in publishers UI - { - code: 'placementId_0', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream' - } - }, - bids: [ - { - bidder: 'preciso', - params: { - host: 'prebid', - publisherId: '0', - region: 'prebid-eu', - traffic: 'video' - } - } - ] } ]; ``` \ No newline at end of file diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 4f8c8c1c550..bcc076f1781 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -5,6 +5,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {includes} from '../src/polyfill.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {config} from '../src/config.js'; +import { interpretNativeBid, OPENRTB } from '../libraries/precisoUtils/bidNativeUtils.js'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; @@ -24,29 +25,6 @@ const DSA_ATTRIBUTES = [ { name: 'datatopub', 'min': 0, 'max': 2 } ]; -// Codes defined by OpenRTB Native Ads 1.1 specification -export const OPENRTB = { - NATIVE: { - IMAGE_TYPE: { - ICON: 1, - MAIN: 3, - }, - ASSET_ID: { - TITLE: 1, - IMAGE: 2, - ICON: 3, - BODY: 4, - SPONSORED: 5, - CTA: 6 - }, - DATA_ASSET_TYPE: { - SPONSORED: 1, - DESC: 2, - CTA_TEXT: 12, - }, - } -}; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, @@ -489,71 +467,6 @@ function interpretBannerBid(serverBid) { } } -/** - * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3 - * @returns {object} Prebid native bidObject - */ -function interpretNativeBid(serverBid) { - return { - requestId: serverBid.impid, - mediaType: NATIVE, - cpm: serverBid.price, - creativeId: serverBid.adid, - width: 1, - height: 1, - ttl: TTL, - meta: { - advertiserDomains: serverBid.adomain - }, - netRevenue: true, - currency: 'USD', - native: interpretNativeAd(serverBid.adm), - } -} - -/** - * @param {string} adm JSON-encoded Request by OpenRTB Native Ads 1.1 §4.1 - * @returns {object} Prebid bidObject.native - */ -function interpretNativeAd(adm) { - const native = JSON.parse(adm).native; - const result = { - clickUrl: encodeURI(native.link.url), - impressionTrackers: native.imptrackers - }; - native.assets.forEach(asset => { - switch (asset.id) { - case OPENRTB.NATIVE.ASSET_ID.TITLE: - result.title = asset.title.text; - break; - case OPENRTB.NATIVE.ASSET_ID.IMAGE: - result.image = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.NATIVE.ASSET_ID.ICON: - result.icon = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.NATIVE.ASSET_ID.BODY: - result.body = asset.data.value; - break; - case OPENRTB.NATIVE.ASSET_ID.SPONSORED: - result.sponsoredBy = asset.data.value; - break; - case OPENRTB.NATIVE.ASSET_ID.CTA: - result.cta = asset.data.value; - break; - } - }); - return result; -} - /** * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/dsa_transparency.md * diff --git a/test/spec/libraries/precisoUtils/bidNativeUtils_spec.js b/test/spec/libraries/precisoUtils/bidNativeUtils_spec.js new file mode 100644 index 00000000000..edbc078a740 --- /dev/null +++ b/test/spec/libraries/precisoUtils/bidNativeUtils_spec.js @@ -0,0 +1,78 @@ +import {expect} from 'chai'; +import { NATIVE } from '../../../../src/mediaTypes'; +import { interpretNativeBid, OPENRTB } from '../../../../libraries/precisoUtils/bidNativeUtils'; + +const DEFAULT_PRICE = 1 +const DEFAULT_BANNER_WIDTH = 300 +const DEFAULT_BANNER_HEIGHT = 250 +const BIDDER_CODE = 'test'; + +describe('bidNativeUtils', function () { + describe('interpretNativeBid', function () { + it('should get correct native bid response', function () { + const adm = { + native: { + ver: 1.2, + link: { + url: 'https://example.com', + clicktrackers: 'https://example.com/clktracker' + }, + eventtrackers: [ + { + url: 'https://example.com/imptracker' + } + ], + imptrackers: [ + 'https://example.com/imptracker' + ], + assets: [{ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 1, + img: { + url: 'https://example.com/image.jpg', + w: 150, + h: 50 + } + }], + } + } + let bid = { + id: '123', + impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + price: DEFAULT_PRICE, + adm: JSON.stringify(adm), + cid: 'test_cid', + crid: 'test_banner_crid', + w: DEFAULT_BANNER_WIDTH, + h: DEFAULT_BANNER_HEIGHT, + adomain: [], + } + + let expectedResponse = { + requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + mediaType: NATIVE, + cpm: DEFAULT_PRICE, + creativeId: 'test_banner_crid', + width: 1, + height: 1, + ttl: 300, + netRevenue: true, + currency: 'USD', + meta: { advertiserDomains: [] }, + native: { + clickUrl: encodeURI('https://example.com'), + impressionTrackers: ['https://example.com/imptracker'], + image: { + url: encodeURI('https://example.com/image.jpg'), + width: 150, + height: 50 + }, + } + } + + let result = interpretNativeBid(bid); + + expect(Object.keys(result)).to.have.members(Object.keys(expectedResponse)); + }) + }); +}); diff --git a/test/spec/libraries/precisoUtils/bidUtils_spec.js b/test/spec/libraries/precisoUtils/bidUtils_spec.js index eecfea4b70e..05c480f41bc 100644 --- a/test/spec/libraries/precisoUtils/bidUtils_spec.js +++ b/test/spec/libraries/precisoUtils/bidUtils_spec.js @@ -10,7 +10,7 @@ const BIDDER_CODE = 'preciso'; const TESTDOMAIN = 'test.org' const bidEndPoint = `https://${TESTDOMAIN}/bid_request/openrtb`; -describe('bidderOperations', function () { +describe('bidUtils', function () { let bid = { bidId: '23fhj33i987f', bidder: BIDDER_CODE, diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 60cdd43504a..9a5ce1ae330 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,5 +1,8 @@ import { expect } from 'chai'; import { spec } from '../../../modules/precisoBidAdapter.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; +import { OPENRTB } from '../../../libraries/precisoUtils/bidNativeUtils.js'; + // simport { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 @@ -53,6 +56,74 @@ describe('PrecisoAdapter', function () { }; + let nativeBid = { + + precisoBid: true, + bidId: '23fhj33i987f', + bidder: 'precisonat', + buyerUid: 'testuid', + params: { + host: 'prebid', + sourceid: '0', + publisherId: '0', + mediaType: 'native', + region: 'IND' + + }, + userId: { + pubcid: '12355454test' + + }, + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + }, + mediaType: 'native', + nativeOrtbRequest: { + assets: [ + { + id: 2, + required: 1, + img: { + type: 3, + w: 300, + h: 250 + } + } + ] + }, + nativeParams: { + ortb: { + assets: [ + { + id: 2, + required: 1, + img: { + type: 3, + w: 300, + h: 250 + } + } + ] + } + } + }; + describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and sourceid parameters present', function () { expect(spec.isBidRequestValid(bid)).to.be.true; @@ -61,6 +132,14 @@ describe('PrecisoAdapter', function () { delete bid.params.publisherId; expect(spec.isBidRequestValid(bid)).to.be.false; }); + it('Should return true if there are bidId, params and sourceid parameters present native Bid', function () { + expect(spec.isBidRequestValid(nativeBid)).to.be.true; + }); + + it('Should return false if at least one of parameters is not present in native bid', function () { + delete nativeBid.params.publisherId; + expect(spec.isBidRequestValid(nativeBid)).to.be.false; + }); }); describe('buildRequests', function () { @@ -91,6 +170,31 @@ describe('PrecisoAdapter', function () { let data = serverRequest.data; expect(data.device).to.be.undefined; }); + + let ServeNativeRequest = spec.buildRequests([nativeBid]); + it('Creates a valid nativeServerRequest object ', function () { + expect(ServeNativeRequest).to.exist; + expect(ServeNativeRequest.method).to.exist; + expect(ServeNativeRequest.url).to.exist; + expect(ServeNativeRequest.data).to.exist; + expect(ServeNativeRequest.method).to.equal('POST'); + expect(ServeNativeRequest.url).to.equal('https://ssp-bidder.mndtrk.com/bid_request/openrtb'); + }); + + it('should extract the native params', function () { + let nativeData = ServeNativeRequest.data; + const asset = JSON.parse(nativeData.imp[0].native.request).assets[0] + expect(asset).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 1, + img: { + w: 300, + h: 250, + type: OPENRTB.NATIVE.IMAGE_TYPE.MAIN, + } + } + ) + }); }); describe('interpretResponse', function () { @@ -137,7 +241,87 @@ describe('PrecisoAdapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])) }) + + it('should get correct native bid response', function () { + const adm = { + native: { + ver: 1.2, + link: { + url: 'https://example.com', + clicktrackers: 'https://example.com/clktracker' + }, + eventtrackers: [ + { + url: 'https://example.com/imptracker' + } + ], + imptrackers: [ + 'https://example.com/imptracker' + ], + assets: [{ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 1, + img: { + url: 'https://example.com/image.jpg', + w: 150, + h: 50 + } + }], + } + } + let nativeResponse = { + bidderRequestId: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + seatbid: [ + { + bid: [ + { + id: '123', + impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + price: DEFAULT_PRICE, + adm: JSON.stringify(adm), + cid: 'test_cid', + crid: 'test_banner_crid', + w: DEFAULT_BANNER_WIDTH, + h: DEFAULT_BANNER_HEIGHT, + adomain: [], + } + ], + seat: BIDDER_CODE + } + ], + } + + let expectedNativeResponse = [ + { + requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + mediaType: NATIVE, + cpm: DEFAULT_PRICE, + creativeId: 'test_banner_crid', + width: 1, + height: 1, + ttl: 300, + meta: { + advertiserDomains: [] + }, + netRevenue: true, + currency: 'USD', + // meta: { advertiserDomains: [] }, + native: { + clickUrl: encodeURI('https://example.com'), + impressionTrackers: ['https://example.com/imptracker'], + image: { + url: encodeURI('https://example.com/image.jpg'), + width: 150, + height: 50 + }, + } + } + ] + let result = spec.interpretResponse({ body: nativeResponse }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedNativeResponse[0])); + }) }) + describe('getUserSyncs', function () { const syncUrl = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=NA&gdpr=0&gdpr_consent=&us_privacy=&t=4'; const syncOptions = { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index f3f9ce8616f..dded1fe15a0 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; -import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js'; +import { spec } from 'modules/rtbhouseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; import { mergeDeep } from '../../../src/utils'; +import { OPENRTB } from '../../../libraries/precisoUtils/bidNativeUtils'; describe('RTBHouseAdapter', () => { const adapter = newBidder(spec); From 9073a02ed8c8abef8a2f3ce91d91a9dff6f4b086 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Fri, 25 Oct 2024 04:35:22 +0200 Subject: [PATCH 0616/1097] Showheroes Bid Adapter : full rework of the adapter (#12283) * switch to openRTB endpoint * refactor and add more tests * bring back original sync * read expired from the response * remove unused sync url * read if netRevenue from response, include extra params * set displaymanager to prebid.js * update docs * test is working * don't merge pixel and iframe syncs * provide renderer * lint fix * remove banner support * switch to openRTB for response * use fromORTB to simplify bidder * don't pass entire bid to renderer * set video context in ext * endpoint should end with / * update documentation unitId * check if video features are enabled for tests * fix after review --------- Co-authored-by: Michele Nasti --- modules/showheroes-bsBidAdapter.js | 429 +++------- modules/showheroes-bsBidAdapter.md | 135 +--- .../modules/showheroes-bsBidAdapter_spec.js | 752 +++++------------- 3 files changed, 289 insertions(+), 1027 deletions(-) diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index 026f89c5faa..afac0a88567 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -1,200 +1,114 @@ import { deepAccess, - getWindowTop, + deepSetValue, triggerPixel, - logInfo, - logError, getBidIdParameter + isFn, + logInfo } from '../src/utils.js'; -import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -/** - * See https://github.com/prebid/Prebid.js/pull/4222 for details on linting exception - * ShowHeroes only imports after winning a bid - * Also see https://github.com/prebid/Prebid.js/issues/11656 - */ -// eslint-disable-next-line no-restricted-imports -import { loadExternalScript } from '../src/adloader.js'; -import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; +import { VIDEO } from '../src/mediaTypes.js'; -const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; -const STAGE_ENDPOINT = 'https://bid-service.stage.showheroes.com/api/v1/bid'; -const VIRALIZE_ENDPOINT = 'https://ads.viralize.tv/prebid-sh/'; -const PROD_PUBLISHER_TAG = 'https://static.showheroes.com/publishertag.js'; -const STAGE_PUBLISHER_TAG = 'https://pubtag.stage.showheroes.com/publishertag.js'; -const PROD_VL = 'https://video-library.showheroes.com'; -const STAGE_VL = 'https://video-library.stage.showheroes.com'; +const ENDPOINT = 'https://ads.viralize.tv/openrtb2/auction/'; const BIDDER_CODE = 'showheroes-bs'; const TTL = 300; -function getEnvURLs(isStage) { - return { - pubTag: isStage ? STAGE_PUBLISHER_TAG : PROD_PUBLISHER_TAG, - vlHost: isStage ? STAGE_VL : PROD_VL - } -} - -const GVLID = 111; - -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: ['showheroesBs'], - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid: function(bid) { - return !!bid.params.playerId || !!bid.params.unitId; +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: TTL, + currency: 'EUR', + mediaType: VIDEO, }, - buildRequests: function(validBidRequests, bidderRequest) { - let adUnits = []; - const pageURL = validBidRequests[0].params.contentPageUrl || - bidderRequest.refererInfo.canonicalUrl || - deepAccess(window, 'location.href'); - const isStage = !!validBidRequests[0].params.stage; - const isViralize = !!validBidRequests[0].params.unitId; - const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; - const isCustomRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender'); - const isNodeRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.slot') || deepAccess(validBidRequests[0], 'params.outstreamOptions.iframe'); - const isNativeRender = deepAccess(validBidRequests[0], 'renderer'); - const outstreamOptions = deepAccess(validBidRequests[0], 'params.outstreamOptions'); - const isBanner = !!validBidRequests[0].mediaTypes.banner || (isOutstream && !(isCustomRender || isNativeRender || isNodeRender)); - const defaultSchain = validBidRequests[0].schain || {}; - - const consentData = bidderRequest.gdprConsent || {}; - const uspConsent = bidderRequest.uspConsent || ''; - const gdprConsent = { - apiVersion: consentData.apiVersion || 2, - gdprApplies: consentData.gdprApplies || 0, - consentString: consentData.consentString || '', + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.context', videoContext); + imp.ext = imp.ext || {}; + imp.ext.params = bidRequest.params; + imp.ext.adUnitCode = bidRequest.adUnitCode; + + if (!imp.displaymanager) { + imp.displaymanager = 'Prebid.js'; + imp.displaymanagerver = '$prebid.version$'; // prebid version } - validBidRequests.forEach((bid) => { - const videoSizes = getVideoSizes(bid); - const bannerSizes = getBannerSizes(bid); - const vpaidMode = getBidIdParameter('vpaidMode', bid.params); - - const makeBids = (type, size, isViralize) => { - let context = ''; - let streamType = 2; - - if (type === BANNER) { - streamType = 5; - } else { - context = deepAccess(bid, 'mediaTypes.video.context'); - if (vpaidMode && context === 'instream') { - streamType = 1; - } - if (context === 'outstream') { - streamType = 5; - } - } + if (!isFn(bidRequest.getFloor)) { + return imp + } - let rBid = { - type: streamType, - adUnitCode: bid.adUnitCode, - bidId: bid.bidId, - context: context, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bidderRequest.auctionId, - start: +new Date(), - timeout: 3000, - params: bid.params, - schain: bid.schain || defaultSchain - }; + let floor = bidRequest.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + if (!isNaN(floor?.floor) && floor?.currency === 'EUR') { + imp.bidfloor = floor.floor; + imp.bidfloorcur = 'EUR'; + } + return imp; + }, - if (isViralize) { - rBid.unitId = getBidIdParameter('unitId', bid.params); - rBid.sizes = size; - rBid.mediaTypes = { - [type]: {'context': context} - }; - } else { - rBid.playerId = getBidIdParameter('playerId', bid.params); - rBid.mediaType = type; - rBid.size = { - width: size[0], - height: size[1] - }; - rBid.gdprConsent = gdprConsent; - rBid.uspConsent = uspConsent; - } + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); - return rBid; + if (context.imp?.video?.ext?.context === 'outstream') { + const renderConfig = { + rendererUrl: bid.ext?.rendererConfig?.rendererUrl, + renderFunc: bid.ext?.rendererConfig?.renderFunc, + renderOptions: bid.ext?.rendererConfig?.renderOptions, }; - - if (isViralize) { - if (videoSizes && videoSizes[0]) { - adUnits.push(makeBids(VIDEO, videoSizes, isViralize)); - } - if (bannerSizes && bannerSizes[0]) { - adUnits.push(makeBids(BANNER, bannerSizes, isViralize)); - } - } else { - videoSizes.forEach((size) => { - adUnits.push(makeBids(VIDEO, size)); - }); - - bannerSizes.forEach((size) => { - adUnits.push(makeBids(BANNER, size)); - }); + if (renderConfig.renderFunc && renderConfig.rendererUrl) { + bidResponse.renderer = createRenderer(bidResponse, renderConfig); } - }); - - let endpointUrl; - let data; + } + bidResponse.callbacks = bid.ext?.callbacks; + bidResponse.extra = bid.ext?.extra; + return bidResponse; + }, +}) - const QA = validBidRequests[0].params.qa || {}; +const GVLID = 111; - if (isViralize) { - endpointUrl = VIRALIZE_ENDPOINT; - data = { - 'bidRequests': adUnits, - 'context': { - 'gdprConsent': gdprConsent, - 'uspConsent': uspConsent, - 'schain': defaultSchain, - 'pageURL': QA.pageURL || encodeURIComponent(pageURL) - } - } - } else { - endpointUrl = isStage ? STAGE_ENDPOINT : PROD_ENDPOINT; +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ['showheroesBs'], + supportedMediaTypes: [VIDEO], + isBidRequestValid: (bid) => { + return !!bid.params.unitId; + }, + buildRequests: (bidRequests, bidderRequest) => { + const QA = bidRequests[0].params.qa; - data = { - 'user': [], - 'meta': { - 'adapterVersion': 2, - 'pageURL': QA.pageURL || encodeURIComponent(pageURL), - 'vastCacheEnabled': (!!config.getConfig('cache') && !isBanner && !outstreamOptions) || false, - 'isDesktop': getWindowTop().document.documentElement.clientWidth > 700, - 'xmlAndTag': !!(isOutstream && isCustomRender) || false, - 'stage': isStage || undefined - }, - 'requests': adUnits, - 'debug': validBidRequests[0].params.debug || false, - } - } + const ortbData = converter.toORTB({ bidRequests, bidderRequest }) return { - url: QA.endpoint || endpointUrl, + url: QA?.endpoint || ENDPOINT, method: 'POST', - options: {contentType: 'application/json', accept: 'application/json'}, - data: data + data: ortbData, }; }, - interpretResponse: function(response, request) { - return createBids(response.body, request.data); + interpretResponse: (response, request) => { + if (!response.body) { + return []; + } + + const bids = converter.fromORTB({response: response.body, request: request.data}).bids; + return bids; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: (syncOptions, serverResponses) => { const syncs = []; - if (!serverResponses.length || !serverResponses[0].body.userSync) { + if (!serverResponses.length || !serverResponses[0].body?.ext?.userSync) { return syncs; } - const userSync = serverResponses[0].body.userSync; + const userSync = serverResponses[0].body.ext.userSync; - if (syncOptions.iframeEnabled) { - (userSync.iframes || []).forEach(url => { + if (syncOptions.iframeEnabled && userSync?.iframes?.length) { + userSync.iframes.forEach(url => { syncs.push({ type: 'iframe', url @@ -210,6 +124,7 @@ export const spec = { }); }); } + return syncs; }, @@ -223,177 +138,33 @@ export const spec = { }, }; -function createBids(bidRes, reqData) { - if (!bidRes) { - return []; - } - const responseBids = bidRes.bids || bidRes.bidResponses; - if (!Array.isArray(responseBids) || responseBids.length < 1) { - return []; - } - - const bids = []; - const bidMap = {}; - (reqData.requests || reqData.bidRequests || []).forEach((bid) => { - bidMap[bid.bidId] = bid; - }); - - responseBids.forEach(function (bid) { - const requestId = bid.bidId || bid.requestId; - const reqBid = bidMap[requestId]; - const currentBidParams = reqBid.params; - const isViralize = !!reqBid.params.unitId; - const size = { - width: bid.width || bid.size.width, - height: bid.height || bid.size.height - }; - - let bidUnit = {}; - bidUnit.cpm = bid.cpm; - bidUnit.requestId = requestId; - bidUnit.adUnitCode = reqBid.adUnitCode; - bidUnit.currency = bid.currency; - bidUnit.mediaType = bid.mediaType || VIDEO; - bidUnit.ttl = TTL; - bidUnit.creativeId = 'c_' + requestId; - bidUnit.netRevenue = true; - bidUnit.width = size.width; - bidUnit.height = size.height; - bidUnit.meta = { - advertiserDomains: bid.adomain || [] - }; - if (bid.vastXml) { - bidUnit.vastXml = bid.vastXml; - bidUnit.adResponse = { - content: bid.vastXml, - }; - } - if (bid.vastTag || bid.vastUrl) { - bidUnit.vastUrl = bid.vastTag || bid.vastUrl; +function outstreamRender(response, renderConfig) { + response.renderer.push(() => { + const func = deepAccess(window, renderConfig.renderFunc); + if (!isFn(func)) { + return; } - if (bid.mediaType === BANNER) { - bidUnit.ad = getBannerHtml(bid, reqBid, reqData); - } else if (bid.context === 'outstream') { - const renderer = Renderer.install({ - id: requestId, - url: 'https://static.showheroes.com/renderer.js', - adUnitCode: reqBid.adUnitCode, - config: { - playerId: reqBid.playerId, - width: size.width, - height: size.height, - vastUrl: bid.vastTag, - vastXml: bid.vastXml, - ad: bid.ad, - debug: reqData.debug, - isStage: reqData.meta && !!reqData.meta.stage, - isViralize: isViralize, - customRender: getBidIdParameter('customRender', currentBidParams.outstreamOptions), - slot: getBidIdParameter('slot', currentBidParams.outstreamOptions), - iframe: getBidIdParameter('iframe', currentBidParams.outstreamOptions), - } - }); - renderer.setRender(outstreamRender); - bidUnit.renderer = renderer; + const renderPayload = { ...renderConfig.renderOptions }; + if (response.vastXml) { + renderPayload.adResponse = { + content: response.vastXml, + }; } - bids.push(bidUnit); + func(renderPayload); }); - - return bids; } -function outstreamRender(bid) { - let embedCode; - if (bid.renderer.config.isViralize) { - embedCode = createOutstreamEmbedCodeV2(bid); - } else { - embedCode = createOutstreamEmbedCode(bid); - } - if (typeof bid.renderer.config.customRender === 'function') { - bid.renderer.config.customRender(bid, embedCode); - } else { - try { - const inIframe = getBidIdParameter('iframe', bid.renderer.config); - if (inIframe && window.document.getElementById(inIframe).nodeName === 'IFRAME') { - const iframe = window.document.getElementById(inIframe); - let framedoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); - framedoc.body.appendChild(embedCode); - return; - } - - const slot = getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(embedCode); - } else if (slot) { - logError('[ShowHeroes][renderer] Error: spot not found'); - } - } catch (err) { - logError('[ShowHeroes][renderer] Error:' + err.message); - } - } -} - -function createOutstreamEmbedCode(bid) { - const isStage = getBidIdParameter('isStage', bid.renderer.config); - const urls = getEnvURLs(isStage); - - const fragment = window.document.createDocumentFragment(); - - let script = loadExternalScript(urls.pubTag, MODULE_TYPE_BIDDER, 'showheroes-bs', function () { - window.ShowheroesTag = this; +function createRenderer(bid, renderConfig) { + const renderer = Renderer.install({ + id: bid.id, + url: renderConfig.rendererUrl, + loaded: false, + adUnitCode: bid.adUnitCode, }); - script.setAttribute('data-player-host', urls.vlHost); - - const spot = window.document.createElement('div'); - spot.setAttribute('class', 'showheroes-spot'); - spot.setAttribute('data-player', getBidIdParameter('playerId', bid.renderer.config)); - spot.setAttribute('data-debug', getBidIdParameter('debug', bid.renderer.config)); - spot.setAttribute('data-ad-vast-tag', getBidIdParameter('vastUrl', bid.renderer.config)); - spot.setAttribute('data-stream-type', 'outstream'); - - fragment.appendChild(spot); - fragment.appendChild(script); - return fragment; -} - -function createOutstreamEmbedCodeV2(bid) { - const range = document.createRange(); - range.selectNode(document.getElementsByTagName('body')[0]); - return range.createContextualFragment(getBidIdParameter('ad', bid.renderer.config)); -} - -function getBannerHtml (bid, reqBid, reqData) { - const isStage = !!reqData.meta.stage; - const urls = getEnvURLs(isStage); - return ` - - - -
- - `; -} - -function getVideoSizes(bidRequest) { - return formatSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize') || []); -} - -function getBannerSizes(bidRequest) { - return formatSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []); -} - -function formatSizes(sizes) { - if (!sizes || !sizes.length) { - return [] - } - return Array.isArray(sizes[0]) ? sizes : [sizes]; + renderer.setRender((render) => { + return outstreamRender(render, renderConfig); + }); + return renderer; } registerBidder(spec); diff --git a/modules/showheroes-bsBidAdapter.md b/modules/showheroes-bsBidAdapter.md index a32a77a2525..f306593a783 100644 --- a/modules/showheroes-bsBidAdapter.md +++ b/modules/showheroes-bsBidAdapter.md @@ -1,16 +1,14 @@ # Overview +``` Module Name: ShowHeroes Bidder Adapter - Module Type: Bidder Adapter - Alias: showheroesBs - Maintainer: tech@showheroes.com - +``` # Description -Module that connects to ShowHeroes demand source to fetch bids. +A module that connects to ShowHeroes demand source to fetch bids. # Test Parameters ``` @@ -27,122 +25,7 @@ Module that connects to ShowHeroes demand source to fetch bids. { bidder: "showheroes-bs", params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - vpaidMode: true // by default is 'false' - } - } - ] - }, - { - // if you have adSlot renderer or oustream should be returned as banner - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - } - } - ] - }, - { - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - - outstreamOptions: { - // Required for the outstream renderer to exact node, one of - iframe: 'iframe_id', - // or - slot: 'slot_id' - } - } - } - ] - }, - { - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - - outstreamOptions: { - // Custom outstream rendering function - customRender: function(bid, embedCode) { - // Example with embedCode - someContainer.appendChild(embedCode); - - // bid config data - var vastUrl = bid.renderer.config.vastUrl; - var vastXML = bid.renderer.config.vastXML; - var videoWidth = bid.renderer.config.width; - var videoHeight = bid.renderer.config.height; - var playerId = bid.renderer.config.playerId; - }, - } - } - } - ] - }, - { - code: 'banner', - mediaTypes: { - banner: { - sizes: [[640, 480]], - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - } - } - ] - } - ]; -``` - -# Test Parameters (V2) -``` - var adUnits = [ - { - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - unitId: 'AACBWAcof-611K4U', - vpaidMode: true // by default is 'false' + unitId: '1234abcd-5678efgh', } } ] @@ -159,18 +42,10 @@ Module that connects to ShowHeroes demand source to fetch bids. { bidder: "showheroes-bs", params: { - unitId: 'AACBTwsZVANd9NlB', - - outstreamOptions: { - // Required for the outstream renderer to exact node, one of - iframe: 'iframe_id', - // or - slot: 'slot_id' - } + unitId: '1234abcd-5678efgh', } } ] } ]; ``` - diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index 30e95b04ccf..b03f6eb9a8a 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -1,98 +1,67 @@ -import {expect} from 'chai' -import {spec} from 'modules/showheroes-bsBidAdapter.js' -import {newBidder} from 'src/adapters/bidderFactory.js' -import {VIDEO, BANNER} from 'src/mediaTypes.js' +import { expect } from 'chai' +import { spec } from 'modules/showheroes-bsBidAdapter.js' +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import { VIDEO } from 'src/mediaTypes.js' const bidderRequest = { refererInfo: { - canonicalUrl: 'https://example.com' + page: 'https://example.com/home', + ref: 'https://referrer' } } const adomain = ['showheroes.com']; const gdpr = { - 'gdprConsent': { - 'apiVersion': 2, - 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', - 'gdprApplies': true + gdprConsent: { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, } } const uspConsent = '1---'; const schain = { - 'schain': { - 'validation': 'strict', - 'config': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'some.com', - 'sid': '00001', - 'hp': 1 + asi: 'some.com', + sid: '00001', + hp: 1 } ] } } } -const bidRequestCommonParams = { - 'bidder': 'showheroes-bs', - 'params': { - 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[640, 480]], - 'bidId': '38b373e1e31c18', - 'bidderRequestId': '12e3ade2543ba6', - 'auctionId': '43aa080090a47f', -} - const bidRequestCommonParamsV2 = { - 'bidder': 'showheroes-bs', - 'params': { - 'unitId': 'AACBWAcof-611K4U', + bidder: 'showheroes-bs', + params: { + unitId: 'AACBWAcof-611K4U', }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[640, 480]], - 'bidId': '38b373e1e31c18', - 'bidderRequestId': '12e3ade2543ba6', - 'auctionId': '43aa080090a47f', -} - -const bidRequestVideo = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream', - } - } - } -} - -const bidRequestOutstream = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'outstream', - } - } - } + adUnitCode: 'adunit-code-1', + bidId: '38b373e1e31c18', + bidderRequestId: '12e3ade2543ba6', + auctionId: '43aa080090a47f', } const bidRequestVideoV2 = { ...bidRequestCommonParamsV2, ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', } } } @@ -101,541 +70,188 @@ const bidRequestVideoV2 = { const bidRequestOutstreamV2 = { ...bidRequestCommonParamsV2, ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'outstream' - } - } - } -} - -const bidRequestVideoVpaid = { - ...bidRequestCommonParams, - ...{ - 'params': { - 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', - 'vpaidMode': true, - }, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream', - } - } - } -} - -const bidRequestBanner = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 360]] - } - } - } -} - -const bidRequestBannerMultiSizes = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 360], [480, 320]] - } + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + }, } } } -const bidRequestVideoAndBanner = { - ...bidRequestCommonParams, - 'mediaTypes': { - ...bidRequestBanner.mediaTypes, - ...bidRequestVideo.mediaTypes - } -} - -describe('shBidAdapter', function () { - const adapter = newBidder(spec) - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) - }) - - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { - const requestV1 = { - 'params': { - 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', - } - } - expect(spec.isBidRequestValid(requestV1)).to.equal(true) - - const requestV2 = { - 'params': { - 'unitId': 'AACBTwsZVANd9NlB', - } - } - expect(spec.isBidRequestValid(requestV2)).to.equal(true) - }) - - it('should return false when required params are not passed', function () { - const request = { - 'params': {} - } - expect(spec.isBidRequestValid(request)).to.equal(false) - }) - }) - - describe('buildRequests', function () { - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests([bidRequestVideo], bidderRequest) - expect(request.method).to.equal('POST') - - const requestV2 = spec.buildRequests([bidRequestVideoV2], bidderRequest) - expect(requestV2.method).to.equal('POST') - }) - - it('check sizes formats', function () { - const request = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'banner': { - 'sizes': [[320, 240]] - } - }, - }], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.size).to.have.property('width', 320); - expect(payload.size).to.have.property('height', 240); - - const request2 = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 360] - } - }, - }], bidderRequest) - const payload2 = request2.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload2.size).to.have.property('width', 640); - expect(payload2.size).to.have.property('height', 360); - }) - - it('should get size from mediaTypes when sizes property is empty', function () { - const request = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480] - } - }, - 'sizes': [], - }], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.size).to.have.property('width', 640); - expect(payload.size).to.have.property('height', 480); - - const request2 = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'banner': { - 'sizes': [[320, 240]] - } - }, - 'sizes': [], - }], bidderRequest) - const payload2 = request2.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload2.size).to.have.property('width', 320); - expect(payload2.size).to.have.property('height', 240); - }) - - it('should attach valid params to the payload when type is video', function () { - const request = spec.buildRequests([bidRequestVideo], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', VIDEO); - expect(payload).to.have.property('type', 2); - }) - - it('should attach valid params to the payload when type is video & vpaid mode on', function () { - const request = spec.buildRequests([bidRequestVideoVpaid], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', VIDEO); - expect(payload).to.have.property('type', 1); - }) - - it('should attach valid params to the payload when type is banner', function () { - const request = spec.buildRequests([bidRequestBanner], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', BANNER); - expect(payload).to.have.property('type', 5); - }) - - it('should attach valid params to the payload when type is banner (multi sizes)', function () { - const request = spec.buildRequests([bidRequestBannerMultiSizes], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', BANNER); - expect(payload).to.have.property('type', 5); - expect(payload).to.have.nested.property('size.width', 640); - expect(payload).to.have.nested.property('size.height', 360); - const payload2 = request.data.requests[1]; - expect(payload2).to.be.an('object'); - expect(payload2).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload2).to.have.property('mediaType', BANNER); - expect(payload2).to.have.property('type', 5); - expect(payload2).to.have.nested.property('size.width', 480); - expect(payload2).to.have.nested.property('size.height', 320); - }) - - it('should attach valid params to the payload when type is banner and video', function () { - const request = spec.buildRequests([bidRequestVideoAndBanner], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', VIDEO); - expect(payload).to.have.property('type', 2); - const payload2 = request.data.requests[1]; - expect(payload2).to.be.an('object'); - expect(payload2).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload2).to.have.property('mediaType', BANNER); - expect(payload2).to.have.property('type', 5); - }) - - it('should attach valid params to the payload when type is video (instream V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], bidderRequest) - const payload = request.data.bidRequests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('unitId', 'AACBWAcof-611K4U'); - expect(payload.mediaTypes).to.eql({ - [VIDEO]: { - 'context': 'instream' - } - }); - }) - - it('should attach valid params to the payload when type is video (outstream V2)', function () { - const request = spec.buildRequests([bidRequestOutstreamV2], bidderRequest) - const payload = request.data.bidRequests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('unitId', 'AACBWAcof-611K4U'); - expect(payload.mediaTypes).to.eql({ - [VIDEO]: { - 'context': 'outstream' - } - }); - }) - - it('passes gdpr & uspConsent if present', function () { - const request = spec.buildRequests([bidRequestVideo], { - ...bidderRequest, - ...gdpr, - uspConsent, - }) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.gdprConsent).to.eql(gdpr.gdprConsent) - expect(payload.uspConsent).to.eql(uspConsent) - }) - - it('passes gdpr & usp if present (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], { - ...bidderRequest, - ...gdpr, - uspConsent, - }) - const context = request.data.context; - expect(context).to.be.an('object'); - expect(context.gdprConsent).to.eql(gdpr.gdprConsent) - expect(context.uspConsent).to.eql(uspConsent) - }) - - it('passes schain object if present', function() { - const request = spec.buildRequests([{ - ...bidRequestVideo, - ...schain - }], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.schain).to.eql(schain.schain); - }) - - it('passes schain object if present (V2)', function() { - const request = spec.buildRequests([{ - ...bidRequestVideoV2, - ...schain - }], bidderRequest) - const context = request.data.context; - expect(context).to.be.an('object'); - expect(context.schain).to.eql(schain.schain); - }) - }) - - describe('interpretResponse', function () { - it('handles nobid responses', function () { - expect(spec.interpretResponse({body: {}}, {data: {meta: {}}}).length).to.equal(0) - expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0) - }) - - const vastTag = 'https://test.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' - const vastXml = '' - - const basicResponse = { - 'cpm': 5, - 'currency': 'EUR', - 'mediaType': VIDEO, - 'context': 'instream', - 'bidId': '38b373e1e31c18', - 'size': {'width': 640, 'height': 480}, - 'vastTag': 'https:\/\/test.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', - 'vastXml': vastXml, - 'adomain': adomain, - }; - - const responseVideo = { - 'bids': [{ - ...basicResponse, - }], +describe('shBidAdapter', () => { + it('validates request', () => { + const bid = { + params: { + testKey: 'testValue', + }, }; - - const responseVideoOutstream = { - 'bids': [{ - ...basicResponse, - 'context': 'outstream', - }], + expect(spec.isBidRequestValid(bid)).to.eql(false); + bid.params = { + unitId: 'test_unit', }; + expect(spec.isBidRequestValid(bid)).to.eql(true); + }); - const responseBanner = { - 'bids': [{ - ...basicResponse, - 'mediaType': BANNER, - }], + it('passes gdpr, usp, schain, floor in ortb request', () => { + const bidRequest = Object.assign({}, bidRequestVideoV2) + const fullRequest = { + bids: [bidRequestVideoV2], + ...bidderRequest, + ...gdpr, + ...schain, + ...{ uspConsent: uspConsent }, }; + bidRequest.schain = schain.schain.config; + const getFloorResponse = { currency: 'EUR', floor: 3 }; + bidRequest.getFloor = () => getFloorResponse; + const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(fullRequest)); + const payload = request.data; + expect(payload.regs.ext.gdpr).to.eql(Number(gdpr.gdprConsent.gdprApplies)); + expect(payload.regs.ext.us_privacy).to.eql(uspConsent); + expect(payload.user.ext.consent).to.eql(gdpr.gdprConsent.consentString); + expect(payload.source.ext.schain).to.eql(bidRequest.schain); + expect(payload.test).to.eql(0); + expect(payload.imp[0].bidfloor).eql(3); + expect(payload.imp[0].bidfloorcur).eql('EUR'); + expect(payload.imp[0].displaymanager).eql('Prebid.js'); + expect(payload.site.page).to.eql('https://example.com/home'); + }); - const basicResponseV2 = { - 'requestId': '38b373e1e31c18', - 'adUnitCode': 'adunit-code-1', - 'cpm': 1, - 'currency': 'EUR', - 'width': 640, - 'height': 480, - 'advertiserDomain': [], - 'callbacks': { - 'won': ['https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] - }, - 'mediaType': 'video', - 'adomain': adomain, - }; + describe('interpretResponse', function () { + const vastXml = '' + const callback_won = 'https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok' + const basicResponse = { + cur: 'EUR', + seatbid: [{ + bid: [{ + price: 1, + w: 640, + h: 480, + adm: vastXml, + impid: '38b373e1e31c18', + crid: 'c_38b373e1e31c18', + adomain: adomain, + ext: { + callbacks: { + won: [callback_won], + }, + extra: 'test', + }, + }], + seat: 'showheroes', + }] + } const vastUrl = 'https://test.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' - const responseVideoV2 = { - 'bidResponses': [{ - ...basicResponseV2, - 'context': 'instream', - 'vastUrl': vastUrl, - }], - }; - - const responseVideoOutstreamV2 = { - 'bidResponses': [{ - ...basicResponseV2, - 'context': 'outstream', - 'ad': '', - }], - }; - - it('should get correct bid response when type is video', function () { - const request = spec.buildRequests([bidRequestVideo], bidderRequest) - const expectedResponse = [ - { - 'cpm': 5, - 'creativeId': 'c_38b373e1e31c18', - 'adUnitCode': 'adunit-code-1', - 'currency': 'EUR', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'netRevenue': true, - 'vastUrl': vastTag, - 'vastXml': vastXml, - 'requestId': '38b373e1e31c18', - 'ttl': 300, - 'adResponse': { - 'content': vastXml - }, - 'meta': { - 'advertiserDomains': adomain + if (FEATURES.VIDEO) { + it('should get correct bid response when type is video (V2)', function () { + const request = spec.buildRequests([bidRequestVideoV2], bidderRequest) + const expectedResponse = [ + { + cpm: 1, + creativeId: 'c_38b373e1e31c18', + creative_id: 'c_38b373e1e31c18', + currency: 'EUR', + width: 640, + height: 480, + playerHeight: 480, + playerWidth: 640, + mediaType: 'video', + netRevenue: true, + requestId: '38b373e1e31c18', + ttl: 300, + meta: { + advertiserDomains: adomain + }, + vastXml: vastXml, + callbacks: { + won: [callback_won], + }, + extra: 'test', } - } - ] + ] - const result = spec.interpretResponse({'body': responseVideo}, request) - expect(result).to.deep.equal(expectedResponse) - }) + const result = spec.interpretResponse({ 'body': basicResponse }, request) + expect(result).to.deep.equal(expectedResponse) + }) - it('should get correct bid response when type is video (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], bidderRequest) - const expectedResponse = [ - { - 'cpm': 1, - 'creativeId': 'c_38b373e1e31c18', - 'adUnitCode': 'adunit-code-1', - 'currency': 'EUR', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'netRevenue': true, - 'vastUrl': vastUrl, - 'requestId': '38b373e1e31c18', - 'ttl': 300, - 'meta': { - 'advertiserDomains': adomain + it('should get correct bid response when type is outstream (slot V2)', function () { + window.myRenderer = { + renderAd: function() { + return null; } } - ] - - const result = spec.interpretResponse({'body': responseVideoV2}, request) - expect(result).to.deep.equal(expectedResponse) - }) - - it('should get correct bid response when type is banner', function () { - const request = spec.buildRequests([bidRequestBanner], bidderRequest) - - const result = spec.interpretResponse({'body': responseBanner}, request) - expect(result[0]).to.have.property('mediaType', BANNER); - expect(result[0].ad).to.include('", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + nativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': '{\"native\":{\"ver\":\"1.1\",\"assets\": [{\"id\":1,\"required\":1,\"title\":{\"text\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":2,\"required\":1,\"img\":{\"url\":\"https://cdn.ehealthcaresolutions.com/1_368852_0.png\",\"w\":500,\"h\":300,\"type\":\"3\"}},{\"id\":3,\"required\":0,\"data\":{\"value\":\"Diabetes In Control. A free weekly diabetes newsletter for Medical Professionals.\"}},{\"id\":4,\"required\":1,\"data\":{\"value\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":6,\"required\":1,\"data\":{\"value\":\"URL\"}}],\"link\":{\"url\":\"https://r.ehealthcaresolutions.com/adx-rtb-d/servlet/WebF_AdManager.AdLinkManager?qs=H4sIAAAAAAAAAx2U2QHDIAxDVwKDr3F84P1HqNLPtMSRpSeG9RiPXH+5a474KzO/47YX7UoP50m61fLujlNjb76/8ZiblkimHq5nL/ZRedp3031x1tnk55LjSNN6h9/Zq+qmaLLuWTl74m1ZJKnb+m2OtQm/3L4sb933pM92qMOgjJ41MYmPXKnndRVKs+9bfSEumoZIFpTXuXbCP+WXuzl725E3O+9odi5OJrnBzhwjx9+UnFN3nTNt1/HY5aeljKtvZYpoJHNXr8BWa8ysKQY7ZmNA3DHK2qRwY7+zLu+xm9z5eheJ4Pv2usSptvO3p7JHrnXn0T5yVWdccp9Yz7hhoz2iu2zqsXsGFZ9hh14J6yU4TkJ0BgnOY8tY3tS+n2qsw7xZfKuanSNbAo+9nkJ83i20+FwhfbJeDVOllXsdxmDWauYcSRgS9+yG5qHwUDjAxxA0iZnOjlsnI+y09+ATeTEwbAVGgp0Qu/ceP0kjUvpu1Ty7O9MoegfrmLPxdjUh3mJL+XhARby+Ax8iBckf6BQdn9W+DMlvmlzYLuLlIy7YociFOIvXvEiYYCMboVk8BLHbnw3Zmr5us3xbjtXL67L96F15acJXkM5BOmTaUbBkYGdCI+Et8XmlpbuE3xVQwmxryc2y4wP3ByuuP8GogPZz8OpPaBv8diWWUTrC2nnLhdNUrJRTKc9FepDvwHTDwfbbMCTSb4LhUIFkyFrw/i7GtkPi6NCCai6N47TgNsTnzZWRoVtOSLq7FsLiF29y0Gj0GHVPVYG3QOPS7Swc3UuiFAQZJx3YvpHA2geUgVBASMEL4vcDi2Dw3NPtBSC4EQEvH/uMILu6WyUwraywTeVpoqoHTqOoD84FzReKoWemJy6jyuiBieGlQIe6wY2elTaMOwEUFF5NagzPj6nauc0+aXzQN3Q72hxFAgtfORK60RRAHYZLYymIzSJcXLgRFsqrb1UoD+5Atq7TWojaLTfOyUvH9EeJvZEOilQAXrf/ALoI8ZhABQAA\"},\"imptrackers\":[\"https://i.ehealthcaresolutions.com/adx-rtb-d/servlet/WebF_AdManager.ImpCounter?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=https%3A%2F%2Fqa-jboss.audiencelogy.com%2Ftn_native_prod.html\",\"https://i.ehealthcaresolutions.com/adx-rtb-d/servlet/WebF_AdManager.ImpTracker?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=\",\"https://rtb-east.ehealthcaresolutions.com:9001/beacon?uid=44636f6605b06ec6d4389d6efb7e5054&cc=468132&fccap=5&nid=1\"]}}', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidBannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidNativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': 'invalid response', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'ehealthcaresolutions', + params: { + placement_id: 111520 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'ehealthcaresolutions', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Banner Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(bannerRequest), + bidRequest = spec.buildRequests(bannerRequest); + expect(bannerRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(bannerRequest); + expect(_Request.url).to.equal('https://rtb.ehealthcaresolutions.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(bannerRequest[0].bidId); + expect(data[0].placementId).to.equal(111520); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(bannerRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate banner response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(bannerRequest); + let bResponse = spec.interpretResponse(bannerResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(bannerRequest); + let response = spec.interpretResponse(invalidBannerResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('Validate Native Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(nativeRequest), + bidRequest = spec.buildRequests(nativeRequest); + expect(nativeRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(nativeRequest); + expect(_Request.url).to.equal('https://rtb.ehealthcaresolutions.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(nativeRequest[0].bidId); + expect(data[0].placementId).to.equal(111519); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(nativeRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate native response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(nativeRequest); + let bResponse = spec.interpretResponse(nativeResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(nativeResponse.body.seatbid[0].bid[0].impid); + // expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + // expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('native'); + expect(bResponse[0].native.clickUrl).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.impressionTrackers).to.be.an('array').with.length.above(0); + expect(bResponse[0].native.title).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.image.url).to.be.a('string').and.not.be.empty; + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['www.diabetesincontrol.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(nativeResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(nativeResponse.body.seatbid[0].bid[0].dealId); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); From fee94cde778c71cd2f84539f79e454df9d71066f Mon Sep 17 00:00:00 2001 From: vdo-ai-tech <126867429+vdo-ai-tech@users.noreply.github.com> Date: Fri, 15 Nov 2024 02:06:35 +0530 Subject: [PATCH 0663/1097] Vdo.ai Bid Adapter : update to prebid version 9 (#12284) * upgraded vdo.ai prebid adapter to prebid version 9 * linting issues fixed * refactored code * removed page visibility and timing function since they are no longer required * getting coppa from ortb2 object, then fallback to config * lint issue fixed * refactored just to rerun CI/CD build --------- Co-authored-by: rishabhsehrawat1 --- modules/vdoaiBidAdapter.js | 179 +++++++++++-- test/spec/modules/vdoaiBidAdapter_spec.js | 311 ++++++++++++++++++++-- 2 files changed, 442 insertions(+), 48 deletions(-) diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index f375e161f88..8fda9cba593 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,6 +1,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import { deepClone, logError, deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -9,7 +11,86 @@ import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; */ const BIDDER_CODE = 'vdoai'; -const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; +const ENDPOINT_URL = 'https://prebid-v2.vdo.ai/auction'; + +function getFrameNesting() { + let topmostFrame = window; + let parent = window.parent; + try { + while (topmostFrame !== topmostFrame.parent) { + parent = topmostFrame.parent; + // eslint-disable-next-line no-unused-expressions + parent.location.href; + topmostFrame = topmostFrame.parent; + } + } catch (e) { } + return topmostFrame; +} + +/** + * Returns information about the page needed by the server in an object to be converted in JSON + * + * @returns {{location: *, referrer: (*|string), stack: (*|Array.), numIframes: (*|Number), wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} + */ +function getPageInfo(bidderRequest) { + const topmostFrame = getFrameNesting(); + return { + referrer: deepAccess(bidderRequest, 'refererInfo.ref', null), + stack: deepAccess(bidderRequest, 'refererInfo.stack', []), + numIframes: deepAccess(bidderRequest, 'refererInfo.numIframes', 0), + wWidth: topmostFrame.innerWidth, + location: deepAccess(bidderRequest, 'refererInfo.page', null), + wHeight: topmostFrame.innerHeight, + aWidth: topmostFrame.screen.availWidth, + aHeight: topmostFrame.screen.availHeight, + oWidth: topmostFrame.outerWidth, + oHeight: topmostFrame.outerHeight, + sWidth: topmostFrame.screen.width, + sHeight: topmostFrame.screen.height, + sLeft: 'screenLeft' in topmostFrame ? topmostFrame.screenLeft : topmostFrame.screenX, + sTop: 'screenTop' in topmostFrame ? topmostFrame.screenTop : topmostFrame.screenY, + xOffset: topmostFrame.pageXOffset, + docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, + hLength: history.length, + yOffset: topmostFrame.pageYOffset, + version: { + prebid_version: '$prebid.version$', + adapter_version: '1.0.0', + vendor: '$$PREBID_GLOBAL$$', + } + }; +} + +export function isSchainValid(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain || !schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node.hasOwnProperty(field)); + }, true); + if (!isValid) { + logError('VDO.AI: required schain params missing'); + } + return isValid; +} + +function parseVideoSize(bid) { + const playerSize = bid.mediaTypes.video.playerSize; + if (typeof playerSize !== 'undefined' && Array.isArray(playerSize) && playerSize.length > 0) { + return getSizes(playerSize) + } + return []; +} + +function getSizes(sizes) { + const ret = []; + for (let i = 0; i < sizes.length; i++) { + const size = sizes[i]; + ret.push({ width: size[0], height: size[1] }) + } + return ret; +} export const spec = { code: BIDDER_CODE, @@ -21,7 +102,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params.placementId); + return !!(bid.params.placementId) && typeof bid.params.placementId === 'string'; }, /** @@ -31,23 +112,82 @@ export const spec = { * @param validBidRequests * @param bidderRequest */ + buildRequests: function (validBidRequests, bidderRequest) { if (validBidRequests.length === 0) { return []; } - return validBidRequests.map(bidRequest => { const sizes = getAdUnitSizes(bidRequest); - const payload = { + let payload = { placementId: bidRequest.params.placementId, sizes: sizes, bidId: bidRequest.bidId, - // TODO: is 'page' the right value here? - referer: bidderRequest.refererInfo.page, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - id: bidRequest.auctionId, - mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' + mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner', + domain: bidderRequest.ortb2.site.domain, + publisherDomain: bidderRequest.ortb2.site.publisher.domain, + adUnitCode: bidRequest.adUnitCode, + bidder: bidRequest.bidder, + tmax: bidderRequest.timeout }; + + payload.bidderRequestId = bidRequest.bidderRequestId; + payload.auctionId = deepAccess(bidRequest, 'ortb2.source.tid'); + payload.transactionId = deepAccess(bidRequest, 'ortb2Imp.ext.tid'); + payload.gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); + payload.ortb2Imp = deepAccess(bidRequest, 'ortb2Imp'); + + if (payload.mediaType === 'video') { + payload.context = bidRequest.mediaTypes.video.context; + payload.playerSize = parseVideoSize(bidRequest); + payload.mediaTypeInfo = deepClone(bidRequest.mediaTypes.video); + } + + if (typeof bidRequest.getFloor === 'function') { + let floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (floor && floor.floor && floor.currency === 'USD') { + payload.bidFloor = floor.floor; + } + } else if (bidRequest.params.bidFloor) { + payload.bidFloor = bidRequest.params.bidFloor; + } + + payload.pageInfo = getPageInfo(bidderRequest); + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdprConsent = { + consentRequired: bidderRequest.gdprConsent.gdprApplies, + consentString: bidderRequest.gdprConsent.consentString, + addtlConsent: bidderRequest.gdprConsent.addtlConsent + }; + } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + applicableSections: bidderRequest.gppConsent.applicableSections, + consentString: bidderRequest.gppConsent.gppString, + } + } + if (bidderRequest && bidderRequest.ortb2) { + payload.ortb2 = bidderRequest.ortb2; + } + if (bidderRequest && bidderRequest.uspConsent) { + payload.usPrivacy = bidderRequest.uspConsent; + } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].schain && isSchainValid(validBidRequests[0].schain)) { + payload.schain = validBidRequests[0].schain; + } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userIdAsEids) { + payload.userId = validBidRequests[0].userIdAsEids; + } + let coppaOrtb2 = !!deepAccess(bidderRequest, 'ortb2.regs.coppa'); + let coppaConfig = config.getConfig('coppa'); + if (coppaOrtb2 === true || coppaConfig === true) { + payload.coppa = true; + } return { method: 'POST', url: ENDPOINT_URL, @@ -67,35 +207,24 @@ export const spec = { const bidResponses = []; const response = serverResponse.body; const creativeId = response.adid || 0; - // const width = response.w || 0; - const width = response.width; - // const height = response.h || 0; - const height = response.height; + const width = response.w; + const height = response.h; const cpm = response.price || 0; - response.rWidth = width; - response.rHeight = height; - const adCreative = response.vdoCreative; if (width !== 0 && height !== 0 && cpm !== 0 && creativeId !== 0) { - // const dealId = response.dealid || ''; const currency = response.cur || 'USD'; const netRevenue = true; - // const referrer = bidRequest.data.referer; const bidResponse = { requestId: response.bidId, cpm: cpm, width: width, height: height, creativeId: creativeId, - // dealId: dealId, currency: currency, netRevenue: netRevenue, ttl: 60, - // referrer: referrer, - // ad: response.adm - // ad: adCreative, mediaType: response.mediaType }; @@ -104,9 +233,9 @@ export const spec = { } else { bidResponse.ad = adCreative; } - if (response.adDomain) { + if (response.adomain) { bidResponse.meta = { - advertiserDomains: response.adDomain + advertiserDomains: response.adomain }; } bidResponses.push(bidResponse); @@ -130,7 +259,7 @@ export const spec = { return []; }, - onTImeout: function(data) {}, + onTimeout: function(data) {}, onBidWon: function(bid) {}, onSetTargeting: function(bid) {} }; diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index b1cfa606d84..e7f153fa237 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -1,11 +1,19 @@ import {assert, expect} from 'chai'; import {spec} from 'modules/vdoaiBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; -const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; +const ENDPOINT_URL = 'https://prebid-v2.vdo.ai/auction'; describe('vdoaiBidAdapter', function () { const adapter = newBidder(spec); + const sandbox = sinon.sandbox.create(); + beforeEach(function() { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + }); + afterEach(function() { + sandbox.restore(); + }); describe('isBidRequestValid', function () { let bid = { 'bidder': 'vdoai', @@ -20,16 +28,33 @@ describe('vdoaiBidAdapter', function () { 'bidderRequestId': '1234asdf1234asdf', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' }; + let invalidParams = { + 'bidder': 'vdoai', + 'params': { + placementId: false + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; it('should return true where required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return false where required params not found', function () { + expect(spec.isBidRequestValid(invalidParams)).to.equal(false); + }); }); describe('buildRequests', function () { let bidRequests = [ { 'bidder': 'vdoai', 'params': { - placementId: 'testPlacementId' + placementId: 'testPlacementId', + bidFloor: 0.1 }, 'sizes': [ [300, 250] @@ -37,16 +62,84 @@ describe('vdoaiBidAdapter', function () { 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - 'mediaTypes': 'banner' + 'mediaType': 'banner', + 'adUnitCode': '1234', + 'mediaTypes': { + banner: { + sizes: [300, 250] + } + } + }, + { + 'bidder': 'vdoai', + 'params': { + placementId: 'testPlacementId', + bidFloor: 0.1 + }, + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com', + 'mediaType': 'video', + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [[640, 360]] + } + } } ]; let bidderRequests = { + timeout: 3000, 'refererInfo': { 'numIframes': 0, 'reachedTop': true, 'referer': 'https://example.com', - 'stack': ['https://example.com'] + 'stack': ['https://example.com'], + 'page': 'example.com', + 'ref': 'example2.com' + }, + 'ortb2': { + source: { + tid: 123456789 + }, + 'site': { + 'domain': 'abc.com', + 'publisher': { + 'domain': 'abc.com' + } + } + }, + 'ortb2Imp': { + ext: { + tid: '12345', + gpid: '1234' + } + }, + gdprConsent: { + consentString: 'abc', + gdprApplies: true, + addtlConsent: 'xyz' + }, + gppConsent: { + gppString: 'abcd', + applicableSections: '' + }, + uspConsent: { + uspConsent: '12345' + }, + userIdAsEids: {}, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'vdo.ai', + 'sid': '4359', + 'hp': 1 + } + ] } }; @@ -57,6 +150,44 @@ describe('vdoaiBidAdapter', function () { it('attaches source and version to endpoint URL as query params', function () { expect(request[0].url).to.equal(ENDPOINT_URL); }); + it('should contain all keys', function() { + expect(request[0].data.pageInfo).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'xOffset', 'yOffset', 'version'); + }) + it('should return empty array if no valid bid was passed', function () { + expect(spec.buildRequests([], bidderRequests)).to.be.empty; + }); + it('should not send invalid schain', function () { + delete bidderRequests.schain.nodes[0].asi; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.schain).to.be.undefined; + }); + it('should not send invalid schain', function () { + delete bidderRequests.schain; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.schain).to.be.undefined; + }); + it('should check for correct sizes', function () { + delete bidRequests[1].mediaTypes.video.playerSize; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[1].data.playerSize).to.be.empty; + }); + it('should not pass undefined in GDPR string', function () { + delete bidderRequests.gdprConsent; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.gdprConsent).to.be.undefined; + }); + + it('should not pass undefined in gppConsent', function () { + delete bidderRequests.gppConsent; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.gppConsent).to.be.undefined; + }); + + it('should not pass undefined in uspConsent', function () { + delete bidderRequests.uspConsent; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.uspConsent).to.be.undefined; + }); }); describe('interpretResponse', function () { @@ -75,29 +206,31 @@ describe('vdoaiBidAdapter', function () { } ]; let serverResponse = { - body: { - 'vdoCreative': '

I am an ad

', - 'price': 4.2, - 'adid': '12345asdfg', - 'currency': 'EUR', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true, - 'adDomain': ['text.abc'] + body: + { + 'price': 2, + 'adid': 'test-ad', + 'adomain': [ + 'text.abc' + ], + 'w': 300, + 'h': 250, + 'vdoCreative': '

I am an ad

', + 'bidId': '31d1375caab87a', + 'mediaType': 'banner' } }; + it('should get the correct bid response', function () { let expectedResponse = [{ - 'requestId': 'bidId123', - 'cpm': 4.2, + 'requestId': '31d1375caab87a', + 'cpm': 2, 'width': 300, 'height': 250, - 'creativeId': '12345asdfg', - 'currency': 'EUR', + 'creativeId': 'test-ad', + 'currency': 'USD', 'netRevenue': true, - 'ttl': 3000, + 'ttl': 60, 'ad': '

I am an ad

', 'meta': { 'advertiserDomains': ['text.abc'] @@ -112,9 +245,9 @@ describe('vdoaiBidAdapter', function () { let serverResponse = { body: { 'vdoCreative': '', - 'price': 4.2, + 'price': 2, 'adid': '12345asdfg', - 'currency': 'EUR', + 'currency': 'USD', 'statusMessage': 'Bid available', 'requestId': 'bidId123', 'width': 300, @@ -133,7 +266,13 @@ describe('vdoaiBidAdapter', function () { 'height': '200', 'bidId': 'bidId123', 'referer': 'www.example.com', - 'mediaType': 'video' + 'mediaType': 'video', + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [[640, 360]] + } + } } } ]; @@ -142,5 +281,131 @@ describe('vdoaiBidAdapter', function () { expect(result[0]).to.have.property('vastXml'); expect(result[0]).to.have.property('mediaType', 'video'); }); + it('should not return invalid responses', function() { + serverResponse.body.w = 0; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + it('should not return invalid responses with invalid height', function() { + serverResponse.body.w = 300; + serverResponse.body.h = 0; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + it('should not return invalid responses with invalid cpm', function() { + serverResponse.body.h = 250; + serverResponse.body.price = 0; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + it('should not return invalid responses with invalid creative ID', function() { + serverResponse.body.price = 2; + serverResponse.body.adid = undefined; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + }); + describe('getUserSyncs', function() { + it('should return correct sync urls', function() { + let serverResponse = [{ + body: { + 'vdoCreative': '', + 'price': 2, + 'adid': '12345asdfg', + 'currency': 'USD', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'video', + 'cookiesync': { + 'status': 'no_cookie', + 'bidder_status': [ + { + 'bidder': 'vdoai', + 'no_cookie': true, + 'usersync': { + 'url': 'https://rtb.vdo.ai/setuid/', + 'type': 'iframe' + } + } + ] + } + } + }]; + + let syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0].url).to.be.equal(serverResponse[0].body.cookiesync.bidder_status[0].usersync.url); + + syncUrls = spec.getUserSyncs({ + iframeEnabled: false + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + }); + it('should not return invalid sync urls', function() { + let serverResponse = [{ + body: { + 'vdoCreative': '', + 'price': 2, + 'adid': '12345asdfg', + 'currency': 'USD', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'video', + 'cookiesync': { + 'status': 'no_cookie', + 'bidder_status': [ + ] + } + } + }]; + + var syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + + delete serverResponse[0].body.cookiesync.bidder_status; + syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + + delete serverResponse[0].body.cookiesync; + syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + + delete serverResponse[0].body; + syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + }); + }); + + describe('onTimeout', function() { + it('should run without errors', function() { + spec.onTimeout(); + }); + }); + + describe('onBidWon', function() { + it('should run without errors', function() { + spec.onBidWon(); + }); + }); + + describe('onSetTargeting', function() { + it('should run without errors', function() { + spec.onSetTargeting(); + }); }); }); From bb586b85fb59424d366808d1dad82b2602ee0fc8 Mon Sep 17 00:00:00 2001 From: Dejan Grbavcic Date: Thu, 14 Nov 2024 23:22:51 +0100 Subject: [PATCH 0664/1097] Brid Bid Adapter : user sync and response changes (#12248) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * Add Brid bid adapter * Brid adapter requested changes * BridBidAdapter: switching to plcmt * Brid Bid Adapter: getUserSyncs method and interpretResponse updates * Adding missing semicolon --- libraries/targetVideoUtils/constants.js | 2 + modules/bridBidAdapter.js | 175 +++++++++++------------ test/spec/modules/bridBidAdapter_spec.js | 20 +++ 3 files changed, 103 insertions(+), 94 deletions(-) diff --git a/libraries/targetVideoUtils/constants.js b/libraries/targetVideoUtils/constants.js index 8ce94c0eaeb..1f47846eba4 100644 --- a/libraries/targetVideoUtils/constants.js +++ b/libraries/targetVideoUtils/constants.js @@ -6,6 +6,7 @@ const BIDDER_CODE = 'targetVideo'; const TIME_TO_LIVE = 300; const BANNER_ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; const VIDEO_ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; +const SYNC_URL = 'https://bppb.link/static/'; const VIDEO_PARAMS = [ 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'plcmt', 'playbackmethod', 'protocols', 'startdelay' @@ -16,6 +17,7 @@ export { GVLID, MARGIN, BIDDER_CODE, + SYNC_URL, TIME_TO_LIVE, BANNER_ENDPOINT_URL, VIDEO_ENDPOINT_URL, diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js index 527cb9d5d5d..74f5e66b90b 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -1,27 +1,17 @@ -import {_each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; +import {_each, deepAccess, formatQS, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getAd, getSiteObj} from '../libraries/targetVideoUtils/bidderUtils.js' +import {GVLID, SOURCE, SYNC_URL, TIME_TO_LIVE, VIDEO_ENDPOINT_URL, VIDEO_PARAMS} from '../libraries/targetVideoUtils/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ - -const SOURCE = 'pbjs'; -const BIDDER_CODE = 'brid'; -const ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; -const GVLID = 934; -const TIME_TO_LIVE = 300; -const VIDEO_PARAMS = [ - 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'plcmt', - 'playbackmethod', 'protocols', 'startdelay' -]; - export const spec = { - code: BIDDER_CODE, + code: 'brid', gvlid: GVLID, supportedMediaTypes: [VIDEO], @@ -117,7 +107,7 @@ export const spec = { requests.push({ method: 'POST', - url: ENDPOINT_URL, + url: VIDEO_ENDPOINT_URL, data: JSON.stringify(postBody), options: { withCredentials: true @@ -138,92 +128,89 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { const response = serverResponse.body; - const bidResponses = []; - - _each(response.seatbid, (resp) => { - _each(resp.bid, (bid) => { - const requestId = bidRequest.bidId; - const params = bidRequest.params; - - const {ad, adUrl, vastUrl, vastXml} = getAd(bid); - - const bidResponse = { - requestId, - params, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.adid, - currency: response.cur, - netRevenue: false, - ttl: TIME_TO_LIVE, - meta: { - advertiserDomains: bid.adomain || [] - } - }; + let highestBid = null; + + if (response && response.seatbid && response.seatbid.length && response.seatbid[0].bid && response.seatbid[0].bid.length) { + _each(response.seatbid, (resp) => { + _each(resp.bid, (bid) => { + const requestId = bidRequest.bidId; + const params = bidRequest.params; + + const {ad, adUrl, vastUrl, vastXml} = getAd(bid); + + const bidResponse = { + requestId, + params, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.adid, + currency: response.cur, + netRevenue: false, + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: bid.adomain || [] + } + }; - if (vastUrl || vastXml) { - bidResponse.mediaType = VIDEO; - if (vastUrl) bidResponse.vastUrl = vastUrl; - if (vastXml) bidResponse.vastXml = vastXml; - } else { - bidResponse.ad = ad; - bidResponse.adUrl = adUrl; - }; + if (vastUrl || vastXml) { + bidResponse.mediaType = VIDEO; + if (vastUrl) bidResponse.vastUrl = vastUrl; + if (vastXml) bidResponse.vastXml = vastXml; + } else { + bidResponse.ad = ad; + bidResponse.adUrl = adUrl; + }; - bidResponses.push(bidResponse); + if (!highestBid || highestBid.cpm < bidResponse.cpm) { + highestBid = bidResponse; + } + }); }); - }); + } - return bidResponses; + return highestBid ? [highestBid] : []; }, -} + /** + * Determine the user sync type (either 'iframe' or 'image') based on syncOptions. + * Construct the sync URL by appending required query parameters such as gdpr, ccpa, and coppa consents. + * Return an array containing an object with the sync type and the constructed URL. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + const params = { + endpoint: 'brid' + }; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + params.gdpr = (gdprConsent.gdprApplies ? 1 : 0); + params.gdpr_consent = encodeURIComponent(gdprConsent.consentString || ''); + } + + // CCPA + if (uspConsent && typeof uspConsent === 'string') { + params.us_privacy = encodeURIComponent(uspConsent); + } + + // GPP Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + params.gpp = encodeURIComponent(gppConsent.gppString); + params.gpp_sid = encodeURIComponent(gppConsent?.applicableSections?.join(',')); + } + + const queryParams = Object.keys(params).length > 0 ? formatQS(params) : ''; + let response = []; + if (syncOptions.iframeEnabled) { + response = [{ + type: 'iframe', + url: SYNC_URL + 'load-cookie.html?' + queryParams + }]; + } + + return response; + } -// /** -// * Helper function to get ad -// * -// * @param {object} bid The bid. -// * @return {object} ad object. -// */ -// function getAd(bid) { -// let ad, adUrl, vastXml, vastUrl; - -// switch (deepAccess(bid, 'ext.prebid.type')) { -// case VIDEO: -// if (bid.adm.substr(0, 4) === 'http') { -// vastUrl = bid.adm; -// } else { -// vastXml = bid.adm; -// }; -// break; -// default: -// if (bid.adm && bid.nurl) { -// ad = bid.adm; -// ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); -// } else if (bid.adm) { -// ad = bid.adm; -// } else if (bid.nurl) { -// adUrl = bid.nurl; -// }; -// } - -// return {ad, adUrl, vastXml, vastUrl}; -// } - -// /** -// * Helper function to get site object -// * -// * @return {object} siteObj. -// */ -// function getSiteObj() { -// const refInfo = (getRefererInfo && getRefererInfo()) || {}; - -// return { -// page: refInfo.page, -// ref: refInfo.ref, -// domain: refInfo.domain -// }; -// } +} registerBidder(spec); diff --git a/test/spec/modules/bridBidAdapter_spec.js b/test/spec/modules/bridBidAdapter_spec.js index 7503c748999..fdda6d840e8 100644 --- a/test/spec/modules/bridBidAdapter_spec.js +++ b/test/spec/modules/bridBidAdapter_spec.js @@ -1,4 +1,5 @@ import { spec } from '../../../modules/bridBidAdapter.js' +import { SYNC_URL } from '../../../libraries/targetVideoUtils/constants.js'; describe('Brid Bid Adapter', function() { const videoRequest = [{ @@ -126,4 +127,23 @@ describe('Brid Bid Adapter', function() { expect(payload.regs.ext.gdpr).to.be.undefined; expect(payload.regs.ext.us_privacy).to.equal(uspConsentString); }); + + it('Test userSync have only one object and it should have a property type=iframe', function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true }); + expect(userSync).to.be.an('array'); + expect(userSync.length).to.be.equal(1); + expect(userSync[0]).to.have.property('type'); + expect(userSync[0].type).to.be.equal('iframe'); + }); + + it('Test userSync valid sync url for iframe', function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, {}, {consentString: 'anyString'}); + expect(userSync.url).to.contain(SYNC_URL + 'load-cookie.html?endpoint=brid&gdpr=0&gdpr_consent=anyString'); + expect(userSync.type).to.be.equal('iframe'); + }); + + it('Test userSyncs iframeEnabled=false', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false}); + expect(userSyncs).to.have.lengthOf(0); + }); }); From ee12e82f4b3891a1b786a16f3899633c855b41ed Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Fri, 15 Nov 2024 18:04:43 +0300 Subject: [PATCH 0665/1097] AdMatic Bid Adapter : add adt alias (#12451) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 78e76651177..ed38cc975e8 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -23,7 +23,8 @@ export const spec = { {code: 'admaticde', gvlid: 1281}, {code: 'pixad', gvlid: 1281}, {code: 'monetixads', gvlid: 1281}, - {code: 'netaddiction', gvlid: 1281} + {code: 'netaddiction', gvlid: 1281}, + {code: 'adt', gvlid: 779} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -54,7 +55,7 @@ export const spec = { const bids = validBidRequests.map(buildRequestObject); const ortb = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); - const host = getValue(validBidRequests[0].params, 'host'); + let host = getValue(validBidRequests[0].params, 'host'); const bidderName = validBidRequests[0].bidder; const payload = { @@ -137,11 +138,15 @@ export const spec = { case 'admaticde': SYNC_URL = 'https://static.cdn.admatic.de/admaticde/sync.html'; break; + case 'adt': + SYNC_URL = 'https://static.cdn.adtarget.org/adt/sync.html'; + break; default: SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; break; } + host = host?.replace('https://', '')?.replace('http://', '')?.replace('/', ''); return { method: 'POST', url: `https://${host}/pb`, data: payload, options: { contentType: 'application/json' } }; } }, @@ -150,7 +155,7 @@ export const spec = { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); - params = Object.keys(params).length ? `?${formatQS(params)}` : ''; + params = Object.keys(params).length ? `&${formatQS(params)}` : ''; hasSynced = true; return { From f44c6cf4675b529f59602dccc3a85c0449026770 Mon Sep 17 00:00:00 2001 From: pm-nitin-shirsat <107102698+pm-nitin-shirsat@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:57:53 +0530 Subject: [PATCH 0666/1097] Support for InBannerVideo (IBV) Field in Bid Response (#12453) --- modules/pubmaticBidAdapter.js | 9 +++ test/spec/modules/pubmaticBidAdapter_spec.js | 72 +++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a03091f1f4a..c29cea5b395 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -859,6 +859,14 @@ function _handleEids(payload, validBidRequests) { } } +// Setting IBV field into the bid response +export function setIBVField(bid, newBid) { + if (bid?.ext?.ibv) { + newBid.ext = newBid.ext || {}; + newBid.ext['ibv'] = bid.ext.ibv; + } +} + function _checkMediaType(bid, newBid) { // Create a regex here to check the strings if (bid.ext && bid.ext['bidtype'] != undefined) { @@ -1401,6 +1409,7 @@ export const spec = { } }); } + setIBVField(bid, newBid); if (bid.ext && bid.ext.deal_channel) { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 71b22e25272..a8790f099e3 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType } from 'modules/pubmaticBidAdapter.js'; +import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; @@ -3579,6 +3579,30 @@ describe('PubMatic adapter', function () { expect(response[0].renderer).to.not.exist; }); + it('should set ibv field in bid.ext when bid.ext.ibv exists', function() { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + + let copyOfBidResponse = utils.deepClone(bannerBidResponse); + let bidExt = utils.deepClone(copyOfBidResponse.body.seatbid[0].bid[0].ext); + copyOfBidResponse.body.seatbid[0].bid[0].ext = Object.assign(bidExt, { + ibv: true + }); + + let response = spec.interpretResponse(copyOfBidResponse, request); + expect(response[0].ext.ibv).to.equal(true); + }); + + it('should not set ibv field when bid.ext does not exist ', function() { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + + let response = spec.interpretResponse(bannerBidResponse, request); + expect(response[0].ext).to.not.exist; + }); + if (FEATURES.VIDEO) { it('should check for valid video mediaType in case of multiformat request', function() { let request = spec.buildRequests(videoBidRequests, { @@ -4176,6 +4200,52 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['banner']['battr']).to.equal(undefined); }); }); + + describe('setIBVField', function() { + it('should set ibv field in newBid.ext when bid.ext.ibv exists', function() { + const bid = { + ext: { + ibv: true + } + }; + const newBid = {}; + setIBVField(bid, newBid); + expect(newBid.ext).to.exist; + expect(newBid.ext.ibv).to.equal(true); + }); + + it('should not set ibv field when bid.ext.ibv does not exist', function() { + const bid = { + ext: {} + }; + const newBid = {}; + setIBVField(bid, newBid); + expect(newBid.ext).to.not.exist; + }); + + it('should not set ibv field when bid.ext does not exist', function() { + const bid = {}; + const newBid = {}; + setIBVField(bid, newBid); + expect(newBid.ext).to.not.exist; + }); + + it('should preserve existing newBid.ext properties', function() { + const bid = { + ext: { + ibv: true + } + }; + const newBid = { + ext: { + existingProp: 'should remain' + } + }; + setIBVField(bid, newBid); + expect(newBid.ext.existingProp).to.equal('should remain'); + expect(newBid.ext.ibv).to.equal(true); + }); + }); }); if (FEATURES.VIDEO) { From 84226e07f62d8d78d46340335015c66e9e98025d Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Fri, 15 Nov 2024 11:55:24 -0700 Subject: [PATCH 0667/1097] Revert "Support for InBannerVideo (IBV) Field in Bid Response (#12453)" (#12455) This reverts commit f44c6cf4675b529f59602dccc3a85c0449026770. --- modules/pubmaticBidAdapter.js | 9 --- test/spec/modules/pubmaticBidAdapter_spec.js | 72 +------------------- 2 files changed, 1 insertion(+), 80 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index c29cea5b395..a03091f1f4a 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -859,14 +859,6 @@ function _handleEids(payload, validBidRequests) { } } -// Setting IBV field into the bid response -export function setIBVField(bid, newBid) { - if (bid?.ext?.ibv) { - newBid.ext = newBid.ext || {}; - newBid.ext['ibv'] = bid.ext.ibv; - } -} - function _checkMediaType(bid, newBid) { // Create a regex here to check the strings if (bid.ext && bid.ext['bidtype'] != undefined) { @@ -1409,7 +1401,6 @@ export const spec = { } }); } - setIBVField(bid, newBid); if (bid.ext && bid.ext.deal_channel) { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index a8790f099e3..71b22e25272 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField } from 'modules/pubmaticBidAdapter.js'; +import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; @@ -3579,30 +3579,6 @@ describe('PubMatic adapter', function () { expect(response[0].renderer).to.not.exist; }); - it('should set ibv field in bid.ext when bid.ext.ibv exists', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - - let copyOfBidResponse = utils.deepClone(bannerBidResponse); - let bidExt = utils.deepClone(copyOfBidResponse.body.seatbid[0].bid[0].ext); - copyOfBidResponse.body.seatbid[0].bid[0].ext = Object.assign(bidExt, { - ibv: true - }); - - let response = spec.interpretResponse(copyOfBidResponse, request); - expect(response[0].ext.ibv).to.equal(true); - }); - - it('should not set ibv field when bid.ext does not exist ', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - - let response = spec.interpretResponse(bannerBidResponse, request); - expect(response[0].ext).to.not.exist; - }); - if (FEATURES.VIDEO) { it('should check for valid video mediaType in case of multiformat request', function() { let request = spec.buildRequests(videoBidRequests, { @@ -4200,52 +4176,6 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['banner']['battr']).to.equal(undefined); }); }); - - describe('setIBVField', function() { - it('should set ibv field in newBid.ext when bid.ext.ibv exists', function() { - const bid = { - ext: { - ibv: true - } - }; - const newBid = {}; - setIBVField(bid, newBid); - expect(newBid.ext).to.exist; - expect(newBid.ext.ibv).to.equal(true); - }); - - it('should not set ibv field when bid.ext.ibv does not exist', function() { - const bid = { - ext: {} - }; - const newBid = {}; - setIBVField(bid, newBid); - expect(newBid.ext).to.not.exist; - }); - - it('should not set ibv field when bid.ext does not exist', function() { - const bid = {}; - const newBid = {}; - setIBVField(bid, newBid); - expect(newBid.ext).to.not.exist; - }); - - it('should preserve existing newBid.ext properties', function() { - const bid = { - ext: { - ibv: true - } - }; - const newBid = { - ext: { - existingProp: 'should remain' - } - }; - setIBVField(bid, newBid); - expect(newBid.ext.existingProp).to.equal('should remain'); - expect(newBid.ext.ibv).to.equal(true); - }); - }); }); if (FEATURES.VIDEO) { From a9de3c15ac9a108b43a1e2df04abd6dfb5297530 Mon Sep 17 00:00:00 2001 From: dev-adverxo Date: Fri, 15 Nov 2024 20:20:51 +0100 Subject: [PATCH 0668/1097] New bid adapter: Adverxo (#12376) * init adverxo adapter * Add support for user sync * Make host parameter optional Introduce new aliases and an auction url for each. * Set skipPbsAliasing=true for aliases We will also have the aliseses in pbs. * Add new alias --- modules/adverxoBidAdapter.js | 356 ++++++++++ modules/adverxoBidAdapter.md | 101 +++ test/spec/modules/adverxoBidAdapter_spec.js | 724 ++++++++++++++++++++ 3 files changed, 1181 insertions(+) create mode 100644 modules/adverxoBidAdapter.js create mode 100644 modules/adverxoBidAdapter.md create mode 100644 test/spec/modules/adverxoBidAdapter_spec.js diff --git a/modules/adverxoBidAdapter.js b/modules/adverxoBidAdapter.js new file mode 100644 index 00000000000..f293d7e01a6 --- /dev/null +++ b/modules/adverxoBidAdapter.js @@ -0,0 +1,356 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import {ortbConverter as OrtbConverter} from '../libraries/ortbConverter/converter.js'; +import {Renderer} from '../src/Renderer.js'; +import {deepAccess, deepSetValue} from '../src/utils.js'; +import {config} from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/auction.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + +const BIDDER_CODE = 'adverxo'; + +const ALIASES = [ + {code: 'adport', skipPbsAliasing: true}, + {code: 'bidsmind', skipPbsAliasing: true}, + {code: 'mobupps', skipPbsAliasing: true} +]; + +const AUCTION_URLS = { + adverxo: 'js.pbsadverxo.com', + adport: 'diclotrans.com', + bidsmind: 'egrevirda.com', + mobupps: 'traffhb.com' +}; + +const ENDPOINT_URL_AD_UNIT_PLACEHOLDER = '{AD_UNIT}'; +const ENDPOINT_URL_AUTH_PLACEHOLDER = '{AUTH}'; +const ENDPOINT_URL_HOST_PLACEHOLDER = '{HOST}'; + +const ENDPOINT_URL = `https://${ENDPOINT_URL_HOST_PLACEHOLDER}/pickpbs?id=${ENDPOINT_URL_AD_UNIT_PLACEHOLDER}&auth=${ENDPOINT_URL_AUTH_PLACEHOLDER}`; + +const ORTB_MTYPES = { + 1: BANNER, + 2: VIDEO, + 4: NATIVE +}; + +const USYNC_TYPES = { + IFRAME: 'iframe', + REDIRECT: 'image' +}; + +const DEFAULT_CURRENCY = 'USD'; + +const ortbConverter = OrtbConverter({ + context: { + netRevenue: true, + ttl: 60, + }, + request: function request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + utils.deepSetValue(request, 'device.ip', 'caller'); + utils.deepSetValue(request, 'ext.avx_add_vast_url', 1); + + const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); + + if (eids && eids.length) { + deepSetValue(request, 'user.ext.eids', eids); + } + + return request; + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floor = adverxoUtils.getBidFloor(bidRequest); + + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } + + return imp; + }, + bidResponse: function (buildBidResponse, bid, context) { + bid.adm = bid.adm.replaceAll(`\${AUCTION_PRICE}`, bid.price); + + if (FEATURES.NATIVE && ORTB_MTYPES[bid.mtype] === NATIVE) { + if (typeof bid?.adm === 'string') { + bid.adm = JSON.parse(bid.adm); + } + + if (bid?.adm?.native) { + bid.adm = bid.adm.native; + } + } + + const result = buildBidResponse(bid, context); + + if (FEATURES.VIDEO) { + if (bid?.ext?.avx_vast_url) { + result.vastUrl = bid.ext.avx_vast_url; + } + + if (bid?.ext?.avx_video_renderer_url) { + result.avxVideoRendererUrl = bid.ext.avx_video_renderer_url; + } + } + + return result; + } +}); + +const userSyncUtils = { + buildUsyncParams: function (gdprConsent, uspConsent, gppConsent) { + const params = []; + + if (gdprConsent) { + params.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + params.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + + if (config.getConfig('coppa') === true) { + params.push('coppa=1'); + } + + if (uspConsent) { + params.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + params.push('gpp=' + encodeURIComponent(gppConsent.gppString)); + params.push('gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(','))); + } + + return params.length ? params.join('&') : ''; + } +}; + +const videoUtils = { + createOutstreamVideoRenderer: function (bid) { + const renderer = Renderer.install({ + id: bid.bidId, + url: bid.avxVideoRendererUrl, + loaded: false, + adUnitCode: bid.adUnitCode + }); + + try { + renderer.setRender(this.outstreamRender.bind(this)); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; + }, + + outstreamRender: function (bid, doc) { + bid.renderer.push(() => { + const win = (doc) ? doc.defaultView : window; + + win.adxVideoRenderer.renderAd({ + targetId: bid.adUnitCode, + adResponse: {content: bid.vastXml} + }); + }); + } +}; + +const adverxoUtils = { + buildAuctionUrl: function (bidderCode, host, adUnitId, adUnitAuth) { + const auctionUrl = host || AUCTION_URLS[bidderCode]; + + return ENDPOINT_URL + .replace(ENDPOINT_URL_HOST_PLACEHOLDER, auctionUrl) + .replace(ENDPOINT_URL_AD_UNIT_PLACEHOLDER, adUnitId) + .replace(ENDPOINT_URL_AUTH_PLACEHOLDER, adUnitAuth); + }, + + groupBidRequestsByAdUnit: function (bidRequests) { + const groupedBidRequests = new Map(); + + bidRequests.forEach(bidRequest => { + const adUnit = { + host: bidRequest.params.host, + id: bidRequest.params.adUnitId, + auth: bidRequest.params.auth, + }; + + if (!groupedBidRequests.get(adUnit)) { + groupedBidRequests.set(adUnit, []); + } + + groupedBidRequests.get(adUnit).push(bidRequest); + }); + + return groupedBidRequests; + }, + + getBidFloor: function (bid) { + if (utils.isFn(bid.getFloor)) { + const floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*', + }); + + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) { + return floor.floor; + } + } + + return null; + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + aliases: ALIASES, + + /** + * Determines whether the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return {boolean} True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!utils.isPlainObject(bid.params) || !Object.keys(bid.params).length) { + utils.logWarn('Adverxo Bid Adapter: bid params must be provided.'); + return false; + } + + if (!bid.params.adUnitId || typeof bid.params.adUnitId !== 'number') { + utils.logWarn('Adverxo Bid Adapter: adUnitId bid param is required and must be a number'); + return false; + } + + if (!bid.params.auth || typeof bid.params.auth !== 'string') { + utils.logWarn('Adverxo Bid Adapter: auth bid param is required and must be a string'); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest an array of bids + * @return {ServerRequest} Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const result = []; + + const bidRequestsByAdUnit = adverxoUtils.groupBidRequestsByAdUnit(validBidRequests); + + bidRequestsByAdUnit.forEach((adUnitBidRequests, adUnit) => { + const ortbRequest = ortbConverter.toORTB({ + bidRequests: adUnitBidRequests, + bidderRequest + }); + + result.push({ + method: 'POST', + url: adverxoUtils.buildAuctionUrl(bidderRequest.bidderCode, adUnit.host, adUnit.id, adUnit.auth), + data: ortbRequest, + bids: adUnitBidRequests + }); + }); + + return result; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest Adverxo bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse || !bidRequest) { + return []; + } + + const bids = ortbConverter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + return bids.map((bid) => { + const thisRequest = utils.getBidRequest(bid.requestId, [bidRequest]); + const context = utils.deepAccess(thisRequest, 'mediaTypes.video.context'); + + if (FEATURES.VIDEO && bid.mediaType === 'video' && context === 'outstream') { + bid.renderer = videoUtils.createOutstreamVideoRenderer(bid); + } + + return bid; + }); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} responses List of server's responses. + * @param {*} gdprConsent + * @param {*} uspConsent + * @param {*} gppConsent + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { + if (!responses || responses.length === 0 || (!syncOptions.pixelEnabled && !syncOptions.iframeEnabled)) { + return []; + } + + const privacyParams = userSyncUtils.buildUsyncParams(gdprConsent, uspConsent, gppConsent); + const syncType = syncOptions.iframeEnabled ? USYNC_TYPES.IFRAME : USYNC_TYPES.REDIRECT; + + const result = []; + + for (const response of responses) { + const syncUrls = response.body?.ext?.avx_usync; + + if (!syncUrls || syncUrls.length === 0) { + continue; + } + + for (const url of syncUrls) { + let finalUrl = url; + + if (!finalUrl.includes('?')) { + finalUrl += '?'; + } else { + finalUrl += '&'; + } + + finalUrl += 'type=' + syncType; + + if (privacyParams.length !== 0) { + finalUrl += `&${privacyParams}`; + } + + result.push({ + type: syncType, + url: finalUrl + }); + } + } + + return result; + } +} + +registerBidder(spec); diff --git a/modules/adverxoBidAdapter.md b/modules/adverxoBidAdapter.md new file mode 100644 index 00000000000..ae6072d2738 --- /dev/null +++ b/modules/adverxoBidAdapter.md @@ -0,0 +1,101 @@ +# Overview + +``` +Module Name: Adverxo Bidder Adapter +Module Type: Bidder Adapter +Maintainer: developer@adverxo.com +``` + +# Description + +Module that connects to Adverxo to fetch bids. +Banner, native and video formats are supported. + +# Bid Parameters + +| Name | Required? | Description | Example | Type | +|------------|-----------|-------------------------------------------------------------------|----------------------------------------------|-----------| +| `host` | No | Ad network host. | `prebidTest.adverxo.com` | `String` | +| `adUnitId` | Yes | Unique identifier for the ad unit in Adverxo platform. | `413` | `Integer` | +| `auth` | Yes | Authentication token provided by Adverxo platform for the AdUnit. | `'61336d75e414c77c367ce5c47c2599ce80a8x32b'` | `String` | + +# Test Parameters + +```javascript +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [400, 300], + [320, 50] + ] + } + }, + bids: [{ + bidder: 'adverxo', + params: { + host: 'example.com', + adUnitId: 1, + auth: '61336e753414c77c367deac47c2595ce80a8032b' + } + }] + }, + { + code: 'native-ad-div', + mediaTypes: { + native: { + image: { + required: true, + sizes: [400, 300] + }, + title: { + required: true, + len: 75 + }, + body: { + required: false, + len: 200 + }, + cta: { + required: false, + len: 75 + }, + sponsoredBy: { + required: false + } + } + }, + bids: [{ + bidder: 'adverxo', + params: { + host: 'example.com', + adUnitId: 2, + auth: '9a640dfccc3381e71fxc29ffd4a72wabd29g9d86' + } + }] + }, + { + code: 'video-div', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + maxduration: 30, + skip: 1 + } + }, + bids: [{ + bidder: 'adverxo', + params: { + host: 'example.com', + adUnitId: 3, + auth: '1ax23d9621f21da28a2eab6f79bd5fbcf4d037c1' + } + }] + } +]; + +``` diff --git a/test/spec/modules/adverxoBidAdapter_spec.js b/test/spec/modules/adverxoBidAdapter_spec.js new file mode 100644 index 00000000000..17ea33f8e67 --- /dev/null +++ b/test/spec/modules/adverxoBidAdapter_spec.js @@ -0,0 +1,724 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adverxoBidAdapter.js'; +import {config} from 'src/config'; + +describe('Adverxo Bid Adapter', () => { + function makeBidRequestWithParams(params) { + return { + bidId: '2e9f38ea93bb9e', + bidder: 'adverxo', + adUnitCode: 'adunit-code', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: params, + bidderRequestId: 'test-bidder-request-id' + }; + } + + const bannerBidRequests = [ + { + bidId: 'bid-banner', + bidder: 'adverxo', + adUnitCode: 'adunit-code', + userIdAsEids: [{ + 'source': 'pubcid.org', + 'uids': [{ + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + }] + }], + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample', + }, + bidderRequestId: 'test-bidder-request-id', + }, + ]; + + const bannerBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: bannerBidRequests, + auctionId: 'new-auction-id' + }; + + const nativeOrtbRequest = { + assets: [ + { + id: 1, + required: 1, + img: {type: 3, w: 150, h: 50} + }, + { + id: 2, + required: 1, + title: {len: 80} + }, + { + id: 3, + required: 0, + data: {type: 1} + } + ] + }; + + const nativeBidRequests = [ + { + bidId: 'bid-native', + mediaTypes: { + native: { + ortb: nativeOrtbRequest + } + }, + nativeOrtbRequest, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample' + } + }, + ]; + + const nativeBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: nativeBidRequests, + auctionId: 'new-auction-id' + }; + + const videoInstreamBidRequests = [ + { + bidId: 'bid-video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample' + } + } + ]; + + const videoInstreamBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: videoInstreamBidRequests, + auctionId: 'new-auction-id' + }; + + const videoOutstreamBidRequests = [ + { + bidId: 'bid-video', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample' + } + } + ]; + + const videoOutstreamBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: videoOutstreamBidRequests, + auctionId: 'new-auction-id' + }; + + afterEach(function () { + config.resetConfig(); + }); + + describe('isBidRequestValid', function () { + it('should validate bid request with valid params', () => { + const validBid = makeBidRequestWithParams({ + adUnitId: 1, + auth: 'authExample', + host: 'www.bidExample.com' + }); + + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.be.true; + }); + + it('should not validate bid request with empty params', () => { + const invalidBid = makeBidRequestWithParams({}); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + + it('should not validate bid request with missing param(adUnitId)', () => { + const invalidBid = makeBidRequestWithParams({ + auth: 'authExample', + host: 'www.bidExample.com' + }); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + + it('should not validate bid request with missing param(auth)', () => { + const invalidBid = makeBidRequestWithParams({ + adUnitId: 1, + host: 'www.bidExample.com' + }); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + + it('should validate bid request with missing param(host)', () => { + const invalidBid = makeBidRequestWithParams({ + adUnitId: 1, + auth: 'authExample', + }); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.true; + }); + }); + + describe('buildRequests', () => { + it('should add eids information to the request', function () { + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + + expect(request.data.user.ext.eids).to.exist; + expect(request.data.user.ext.eids).to.deep.equal(bannerBidRequests[0].userIdAsEids); + }); + + it('should use correct bidUrl for an alias', () => { + const bidRequests = [ + { + bidder: 'bidsmind', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + adUnitId: 1, + auth: 'authExample', + } + }, + ]; + + const bidderRequest = { + bidderCode: 'bidsmind', + bids: bidRequests, + }; + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://egrevirda.com/pickpbs?id=1&auth=authExample'); + }); + + it('should use correct default bidUrl', () => { + const bidRequests = [ + { + bidder: 'adverxo', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + adUnitId: 1, + auth: 'authExample', + } + }, + ]; + + const bidderRequest = { + bidderCode: 'adverxo', + bids: bidRequests + }; + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://js.pbsadverxo.com/pickpbs?id=1&auth=authExample'); + }); + + it('should build post request for banner', () => { + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.example.com/pickpbs?id=1&auth=authExample'); + expect(request.data.device.ip).to.equal('caller'); + expect(request.data.ext.avx_add_vast_url).to.equal(1); + }); + + if (FEATURES.NATIVE) { + it('should build post request for native', () => { + const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.example.com/pickpbs?id=1&auth=authExample'); + + const nativeRequest = JSON.parse(request.data.imp[0]['native'].request); + + expect(nativeRequest.assets).to.have.lengthOf(3); + + expect(nativeRequest.assets[0]).to.deep.equal({ + id: 1, + required: 1, + img: {w: 150, h: 50, type: 3} + }); + + expect(nativeRequest.assets[1]).to.deep.equal({ + id: 2, + required: 1, + title: {len: 80} + }); + + expect(nativeRequest.assets[2]).to.deep.equal({ + id: 3, + required: 0, + data: {type: 1} + }); + }); + } + + if (FEATURES.VIDEO) { + it('should build post request for video', function () { + const request = spec.buildRequests(videoInstreamBidRequests, videoInstreamBidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.example.com/pickpbs?id=1&auth=authExample'); + + const ortbRequest = request.data; + + expect(ortbRequest.imp).to.have.lengthOf(1); + + expect(ortbRequest.imp[0]).to.deep.equal({ + id: 'bid-video', + video: { + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }); + }); + } + + it('should add bid floor to request', function () { + const bannerBidRequestWithFloor = { + ...bannerBidRequests[0], + getFloor: () => ({currency: 'USD', floor: 3}) + }; + + const request = spec.buildRequests([bannerBidRequestWithFloor], {})[0].data; + + expect(request.imp[0].bidfloor).to.equal(3); + expect(request.imp[0].bidfloorcur).to.equal('USD'); + }); + }); + + describe('interpretResponse', () => { + it('should return empty array if serverResponse is not defined', () => { + const bidRequest = spec.buildRequests(bannerBidRequests, bannerBidderRequest); + const bids = spec.interpretResponse(undefined, bidRequest); + + expect(bids.length).to.equal(0); + }); + + it('should interpret banner response', () => { + const bidResponse = { + body: { + id: 'bid-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-banner', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 1, + adomain: ['test.com'], + adm: '
' + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'banner', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + requestId: 'bid-banner', + ttl: 60, + width: 300, + ad: '
' + }, + ]; + + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + + it('should replace openrtb auction price macro', () => { + const bidResponse = { + body: { + id: 'bid-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-banner', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 1, + adomain: ['test.com'], + adm: '' + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids[0].ad).to.equal(''); + }); + + if (FEATURES.NATIVE) { + it('should interpret native response', () => { + const bidResponse = { + body: { + id: 'native-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-native', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 4, + adomain: ['test.com'], + adm: '{"native":{"assets":[{"id":2,"title":{"text":"Title"}},{"id":3,"data":{"value":"Description"}},{"id":1,"img":{"url":"http://example.com?img","w":150,"h":50}}],"link":{"url":"http://example.com?link"}}}' + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'native', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + requestId: 'bid-native', + ttl: 60, + width: 300, + native: { + ortb: { + assets: [ + {id: 2, title: {text: 'Title'}}, + {id: 3, data: {value: 'Description'}}, + {id: 1, img: {url: 'http://example.com?img', w: 150, h: 50}} + ], + link: {url: 'http://example.com?link'} + } + } + } + ]; + + const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + } + + if (FEATURES.VIDEO) { + it('should interpret video instream response', () => { + const bidResponse = { + body: { + id: 'video-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-video', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 2, + adomain: ['test.com'], + adm: 'vastXml', + ext: { + avx_vast_url: 'vastUrl' + } + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'video', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + playerHeight: 300, + playerWidth: 400, + requestId: 'bid-video', + ttl: 60, + vastUrl: 'vastUrl', + vastXml: 'vastXml', + width: 300 + } + ]; + + const request = spec.buildRequests(videoInstreamBidRequests, videoInstreamBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + + it('should interpret video outstream response', () => { + const bidResponse = { + body: { + id: 'video-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-video', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 2, + adomain: ['test.com'], + adm: 'vastXml', + ext: { + avx_vast_url: 'vastUrl', + avx_video_renderer_url: 'videoRendererUrl', + } + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + avxVideoRendererUrl: 'videoRendererUrl', + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'video', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + playerHeight: 300, + playerWidth: 400, + requestId: 'bid-video', + ttl: 60, + vastUrl: 'vastUrl', + vastXml: 'vastXml', + width: 300 + } + ]; + + const request = spec.buildRequests(videoOutstreamBidRequests, videoOutstreamBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids[0].renderer.url).to.equal('videoRendererUrl'); + + delete (bids[0].renderer); + expect(bids).to.deep.equal(expectedBids); + }); + } + }); + + describe('getUserSyncs', () => { + const exampleUrl = 'https://example.com/usync?id=5'; + const iframeConfig = {iframeEnabled: true}; + + const responses = [{ + body: {ext: {avx_usync: [exampleUrl]}} + }]; + + const responseWithoutQueryString = [{ + body: {ext: {avx_usync: ['https://example.com/usync/sf/5']}} + }]; + + it('should not return empty list if not allowed', function () { + expect(spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, responses, undefined, undefined, undefined)).to.be.empty; + }); + + it('should not return iframe if not allowed', function () { + expect(spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, responses, undefined, undefined, undefined)).to.deep.equal([{ + type: 'image', url: `${exampleUrl}&type=image` + }]); + }); + + it('should add query string to url when missing', function () { + expect(spec.getUserSyncs(iframeConfig, responseWithoutQueryString, undefined, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `https://example.com/usync/sf/5?type=iframe` + }]); + }); + + it('should not add parameters if not provided', function () { + expect(spec.getUserSyncs(iframeConfig, responses, undefined, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should add GDPR parameters if provided', function () { + expect(spec.getUserSyncs(iframeConfig, responses, {gdprApplies: true}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gdpr=1&gdpr_consent=` + }]); + + expect(spec.getUserSyncs(iframeConfig, responses, + {gdprApplies: true, consentString: 'foo?'}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gdpr=1&gdpr_consent=foo%3F` + }]); + + expect(spec.getUserSyncs(iframeConfig, responses, + {gdprApplies: false, consentString: 'bar'}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gdpr=0&gdpr_consent=bar` + }]); + }); + + it('should add CCPA parameters if provided', function () { + expect(spec.getUserSyncs(iframeConfig, responses, undefined, 'foo?', undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&us_privacy=foo%3F` + }]); + }); + + it('should not apply if not gppConsent.gppString', function () { + const gppConsent = {gppString: '', applicableSections: [123]}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should not apply if not gppConsent.applicableSections', function () { + const gppConsent = {gppString: '', applicableSections: undefined}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should not apply if empty gppConsent.applicableSections', function () { + const gppConsent = {gppString: '', applicableSections: []}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should apply if all above are available', function () { + const gppConsent = {gppString: 'foo?', applicableSections: [123]}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gpp=foo%3F&gpp_sid=123` + }]); + }); + + it('should support multiple sections', function () { + const gppConsent = {gppString: 'foo', applicableSections: [123, 456]}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gpp=foo&gpp_sid=123%2C456` + }]); + }); + }); +}); From eb130ca3cdc4bc7ec12c308fa9eb379277783b49 Mon Sep 17 00:00:00 2001 From: Olivier Date: Sat, 16 Nov 2024 17:53:28 +0100 Subject: [PATCH 0669/1097] AdagioRtdProvider: add number of pages in session data (#12450) --- modules/adagioRtdProvider.js | 4 +++- test/spec/modules/adagioRtdProvider_spec.js | 15 ++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index 7098406be0d..2260ae8d31a 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -73,13 +73,14 @@ const _SESSION = (function() { storage.getDataFromLocalStorage('adagio', (storageValue) => { // session can be an empty object - const { rnd, new: isNew = false, vwSmplg, vwSmplgNxt, lastActivityTime, id, testName, testVersion, initiator } = _internal.getSessionFromLocalStorage(storageValue); + const { rnd, new: isNew = false, vwSmplg, vwSmplgNxt, lastActivityTime, id, testName, testVersion, initiator, pages } = _internal.getSessionFromLocalStorage(storageValue); // isNew can be `true` if the session has been initialized by the A/B test snippet (external) const isNewSess = (initiator === 'snippet') ? isNew : isNewSession(lastActivityTime); data.session = { rnd, + pages: pages || 1, new: isNewSess, // legacy: `new` was used but the choosen name is not good. // Don't use values if they are not defined. ...(vwSmplg !== undefined && { vwSmplg }), @@ -685,6 +686,7 @@ function registerEventsForAdServers(config) { * @property {number} vwSmplg - View sampling rate. * @property {number} vwSmplgNxt - Next view sampling rate. * @property {number} lastActivityTime - Last activity time. + * @property {number} pages - current number of pages seen. */ /** diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index 6dbe4b34985..64c006fbc3f 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -121,7 +121,8 @@ describe('Adagio Rtd Provider', function () { id: 'uid-1234', rnd: 0.5697, vwSmplg: 0.1, - vwSmplgNxt: 0.1 + vwSmplgNxt: 0.1, + pages: 1 }; it('store new session data for further usage', function () { @@ -139,7 +140,8 @@ describe('Adagio Rtd Provider', function () { session: { new: true, id: utils.generateUUID(), - rnd: Math.random() + rnd: Math.random(), + pages: 1, } } @@ -211,7 +213,8 @@ describe('Adagio Rtd Provider', function () { vwSmplgNxt: 0.1, testName: 'adg-test', testVersion: 'srv', - initiator: 'snippet' + initiator: 'snippet', + pages: 1 }; it('store new session data instancied by the AB Test snippet for further usage', function () { @@ -591,7 +594,8 @@ describe('Adagio Rtd Provider', function () { 'new': true, 'rnd': 0.020644826280300954, 'vwSmplg': 0.1, - 'vwSmplgNxt': 0.1 + 'vwSmplgNxt': 0.1, + 'pages': 1 } } } @@ -617,7 +621,8 @@ describe('Adagio Rtd Provider', function () { 'new': true, 'rnd': 0.020644826280300954, 'vwSmplg': 0.1, - 'vwSmplgNxt': 0.1 + 'vwSmplgNxt': 0.1, + 'pages': 1 } } } From 72566c2755dc051085a7734f0f0cdb5aa51d615f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Sat, 16 Nov 2024 08:56:26 -0800 Subject: [PATCH 0670/1097] gppControl: accept flat section data (#12444) --- libraries/mspa/activityControls.js | 2 +- test/spec/libraries/mspa/activityControls_spec.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js index eaf515e2385..f8a5c7abe9b 100644 --- a/libraries/mspa/activityControls.js +++ b/libraries/mspa/activityControls.js @@ -105,7 +105,7 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat } function flatSection(subsections) { - if (subsections == null) return subsections; + if (!Array.isArray(subsections)) return subsections; return subsections.reduceRight((subsection, consent) => { return Object.assign(consent, subsection); }, {}); diff --git a/test/spec/libraries/mspa/activityControls_spec.js b/test/spec/libraries/mspa/activityControls_spec.js index f232dc2563f..2ec34a3fa43 100644 --- a/test/spec/libraries/mspa/activityControls_spec.js +++ b/test/spec/libraries/mspa/activityControls_spec.js @@ -229,6 +229,13 @@ describe('setupRules', () => { sinon.assert.calledWith(rules.mockActivity, {mock: 'consent'}) }); + it('should accept already flattened section data', () => { + consent.parsedSections.mockApi = {flat: 'consent'}; + runSetup('mockApi', [1]); + isAllowed('mockActivity', {}); + sinon.assert.calledWith(rules.mockActivity, {flat: 'consent'}) + }) + it('should not choke when no consent data is available', () => { consent = null; runSetup('mockApi', [1]); From 8b039f4ff0a17c949d2bb1e2415f3066b192e92f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Sat, 16 Nov 2024 08:56:51 -0800 Subject: [PATCH 0671/1097] Core: add analytics option to markWinningBidAsUsed (#12437) --- src/prebid.js | 16 ++++++-- test/spec/unit/pbjs_api_spec.js | 70 +++++++++++++++++++++------------ 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 975e4b4517b..9a77f6d3bd3 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -39,7 +39,13 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; -import {insertLocatorFrame, markBidAsRendered, renderAdDirect, renderIfDeferred} from './adRendering.js'; +import { + insertLocatorFrame, + markBidAsRendered, + markWinningBid, + renderAdDirect, + renderIfDeferred +} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; import {fillVideoDefaults, validateOrtbVideoFields} from './video.js'; @@ -905,7 +911,7 @@ if (FEATURES.VIDEO) { * * @alias module:pbjs.markWinningBidAsUsed */ - pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode}) { + pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode, analytics = false}) { let bids; if (adUnitCode && adId == null) { bids = targeting.getWinningBids(adUnitCode); @@ -915,7 +921,11 @@ if (FEATURES.VIDEO) { logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); } if (bids.length > 0) { - auctionManager.addWinningBid(bids[0]); + if (analytics) { + markWinningBid(bids[0]); + } else { + auctionManager.addWinningBid(bids[0]); + } markBidAsRendered(bids[0]) } } diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 60363ad359d..ac8978907a9 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -3531,7 +3531,7 @@ describe('Unit: Prebid Module', function () { if (FEATURES.VIDEO) { describe('markWinningBidAsUsed', function () { const adUnitCode = '/19968336/header-bid-tag-0'; - let winningBid; + let winningBid, markedBid; beforeEach(() => { const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); @@ -3540,20 +3540,56 @@ describe('Unit: Prebid Module', function () { // mark the bid and verify the state has changed to RENDERED winningBid = targeting.getWinningBids(adUnitCode)[0]; auction.getAuctionId = function() { return winningBid.auctionId }; + sandbox.stub(events, 'emit'); + markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); }) afterEach(() => { resetAuction(); }) - it('marks the bid object as used for the given adUnitCode/adId combination', function () { - // make sure the auction has "state" and does not reload the fixtures - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - + function checkBidRendered() { expect(markedBid.status).to.equal(BID_STATUS.RENDERED); - }); + } + + Object.entries({ + 'analytics=true': { + mark(options = {}) { + $$PREBID_GLOBAL$$.markWinningBidAsUsed(Object.assign({analytics: true}, options)) + }, + checkBidWon() { + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, markedBid); + } + }, + 'analytics=false': { + mark(options = {}) { + $$PREBID_GLOBAL$$.markWinningBidAsUsed(options) + }, + checkBidWon() { + sinon.assert.notCalled(events.emit) + } + } + }).forEach(([t, {mark, checkBidWon}]) => { + describe(`when ${t}`, () => { + it('marks the bid object as used for the given adUnitCode/adId combination', function () { + mark({ adUnitCode, adId: winningBid.adId }); + checkBidRendered(); + checkBidWon(); + }); + it('marks the winning bid object as used for the given adUnitCode', function () { + mark({ adUnitCode }); + checkBidRendered(); + checkBidWon(); + }); + + it('marks a bid object as used for the given adId', function () { + mark({ adId: winningBid.adId }); + checkBidRendered(); + checkBidWon(); + }); + }) + }) it('try and mark the bid object, but fail because we supplied the wrong adId', function () { $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); @@ -3562,24 +3598,6 @@ describe('Unit: Prebid Module', function () { expect(markedBid.status).to.not.equal(BID_STATUS.RENDERED); }); - - it('marks the winning bid object as used for the given adUnitCode', function () { - // make sure the auction has "state" and does not reload the fixtures - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - - expect(markedBid.status).to.equal(BID_STATUS.RENDERED); - }); - - it('marks a bid object as used for the given adId', function () { - // make sure the auction has "state" and does not reload the fixtures - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - - expect(markedBid.status).to.equal(BID_STATUS.RENDERED); - }); }); } From 7c5700f741594a77bbe119aacf97af63d957bd64 Mon Sep 17 00:00:00 2001 From: thede-ri <161479447+thede-ri@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:02:46 -0500 Subject: [PATCH 0672/1097] New User ID Submodule: Rewarded Interest (#12340) * New UserID submodule: rewardedInterestIdSystem * UserID submodule: rewardedInterestIdSystem - fixed styles --------- Co-authored-by: Konstantin Mikhalyov --- .../gpt/rewardedInterestIdSystem_example.html | 114 +++++++++++ modules/.submodules.json | 1 + modules/rewardedInterestIdSystem.js | 142 +++++++++++++ modules/rewardedInterestIdSystem.md | 23 +++ .../modules/rewardedInterestIdSystem_spec.js | 190 ++++++++++++++++++ 5 files changed, 470 insertions(+) create mode 100644 integrationExamples/gpt/rewardedInterestIdSystem_example.html create mode 100644 modules/rewardedInterestIdSystem.js create mode 100644 modules/rewardedInterestIdSystem.md create mode 100644 test/spec/modules/rewardedInterestIdSystem_spec.js diff --git a/integrationExamples/gpt/rewardedInterestIdSystem_example.html b/integrationExamples/gpt/rewardedInterestIdSystem_example.html new file mode 100644 index 00000000000..c9730f354b3 --- /dev/null +++ b/integrationExamples/gpt/rewardedInterestIdSystem_example.html @@ -0,0 +1,114 @@ + + + + + Rewarded Interest ID Example + + + + + + + + + + + + + +

Rewarded Interest ID Example

+ +

Generated IDs:

+

+
+

Generated EIDs

+

+
+
+
diff --git a/modules/.submodules.json b/modules/.submodules.json
index d998a62500a..6dac11bf0ed 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -40,6 +40,7 @@
       "pubProvidedIdSystem",
       "publinkIdSystem",
       "quantcastIdSystem",
+      "rewardedInterestIdSystem",
       "sharedIdSystem",
       "tapadIdSystem",
       "teadsIdSystem",
diff --git a/modules/rewardedInterestIdSystem.js b/modules/rewardedInterestIdSystem.js
new file mode 100644
index 00000000000..8cf514f372b
--- /dev/null
+++ b/modules/rewardedInterestIdSystem.js
@@ -0,0 +1,142 @@
+/**
+ * This module adds rewarded interest ID to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/rewardedInterestIdSystem
+ * @requires module:modules/userId
+ */
+
+/**
+ * @typedef {import('../modules/userId/index.js').Submodule} Submodule
+ * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
+ */
+
+/**
+ * @typedef RewardedInterestApi
+ * @property {getApiVersion} getApiVersion
+ * @property {getIdentityToken} getIdentityToken
+ */
+
+/**
+ * Retrieves the Rewarded Interest API version.
+ * @callback getApiVersion
+ * @return {string}
+ */
+
+/**
+ * Retrieves the current identity token.
+ * @callback getIdentityToken
+ * @return {Promise}
+ */
+
+import {submodule} from '../src/hook.js';
+import {logError} from '../src/utils.js';
+
+export const MODULE_NAME = 'rewardedInterestId';
+export const SOURCE = 'rewardedinterest.com';
+
+/**
+ * Get rewarded interest API
+ * @function
+ * @returns {RewardedInterestApi|undefined}
+ */
+export function getRewardedInterestApi() {
+  if (window.__riApi && window.__riApi.getIdentityToken) {
+    return window.__riApi;
+  }
+}
+
+/**
+ * Wait while rewarded interest API to be set and execute the callback function
+ * @param {function} callback
+ */
+export function watchRewardedInterestApi(callback) {
+  Object.defineProperties(window, {
+    __rewardedInterestApi: {
+      value: undefined,
+      writable: true
+    },
+    __riApi: {
+      get: () => {
+        return window.__rewardedInterestApi;
+      },
+      set: value => {
+        window.__rewardedInterestApi = value;
+        callback(value);
+      },
+      configurable: true,
+    }
+  });
+}
+
+/**
+ * Get rewarded interest ID from API and pass it to the callback function
+ * @param {RewardedInterestApi} rewardedInterestApi
+ * @param {function} callback User ID callbackCompleted
+ */
+export function getRewardedInterestId(rewardedInterestApi, callback) {
+  rewardedInterestApi.getIdentityToken().then(callback).catch(error => {
+    callback();
+    logError(`${MODULE_NAME} module: ID fetch encountered an error`, error);
+  });
+}
+
+/**
+ * @param {function} callback User ID callbackCompleted
+ */
+export function apiNotAvailable(callback) {
+  callback();
+  logError(`${MODULE_NAME} module: Rewarded Interest API not found`);
+}
+
+/** @type {Submodule} */
+export const rewardedInterestIdSubmodule = {
+  /**
+   * Used to link submodule with config
+   * @type {string}
+   */
+  name: MODULE_NAME,
+
+  /**
+   * Decode the stored id value for passing to bid requests
+   * @function
+   * @param {string} value
+   * @returns {{rewardedInterestId: string}|undefined}
+   */
+  decode(value) {
+    return value ? {[MODULE_NAME]: value} : undefined;
+  },
+
+  /**
+   * Performs action to obtain id and return a value in the callback's response argument
+   * @function
+   * @returns {IdResponse|undefined}
+   */
+  getId() {
+    return {
+      callback: cb => {
+        const api = getRewardedInterestApi();
+        if (api) {
+          getRewardedInterestId(api, cb);
+        } else if (document.readyState === 'complete') {
+          apiNotAvailable(cb);
+        } else {
+          watchRewardedInterestApi(api => getRewardedInterestId(api, cb));
+          // Ensure that cb is called when API is not available
+          window.addEventListener('load', () => {
+            if (!getRewardedInterestApi()) {
+              apiNotAvailable(cb);
+            }
+          })
+        }
+      },
+    };
+  },
+  eids: {
+    [MODULE_NAME]: {
+      source: SOURCE,
+      atype: 3,
+    },
+  },
+};
+
+submodule('userId', rewardedInterestIdSubmodule);
diff --git a/modules/rewardedInterestIdSystem.md b/modules/rewardedInterestIdSystem.md
new file mode 100644
index 00000000000..8d12aa86e61
--- /dev/null
+++ b/modules/rewardedInterestIdSystem.md
@@ -0,0 +1,23 @@
+## Rewarded Interest User ID Submodule
+
+This module adds rewarded interest advertising token to the user ID module
+
+*Note: The storage config should be omitted
+
+### Prebid Params
+
+```javascript
+pbjs.setConfig({
+    userSync: {
+        userIds: [{
+            name: 'rewardedInterestId',
+        }]
+    }
+});
+```
+
+## Parameter Descriptions for the `usersync` Configuration Section
+
+| Param under usersync.userIds[] | Scope    | Type   | Description              | Example                |
+|--------------------------------|----------|--------|--------------------------|------------------------|
+| name                           | Required | String | The name of this module. | `"rewardedInterestId"` |
diff --git a/test/spec/modules/rewardedInterestIdSystem_spec.js b/test/spec/modules/rewardedInterestIdSystem_spec.js
new file mode 100644
index 00000000000..b6ce1e03f76
--- /dev/null
+++ b/test/spec/modules/rewardedInterestIdSystem_spec.js
@@ -0,0 +1,190 @@
+import sinon from 'sinon';
+import {expect} from 'chai';
+import * as utils from 'src/utils.js';
+import {attachIdSystem} from 'modules/userId';
+import {createEidsArray} from 'modules/userId/eids';
+import {
+  MODULE_NAME,
+  SOURCE,
+  getRewardedInterestApi,
+  watchRewardedInterestApi,
+  getRewardedInterestId,
+  apiNotAvailable,
+  rewardedInterestIdSubmodule
+} from 'modules/rewardedInterestIdSystem.js';
+
+describe('rewardedInterestIdSystem', () => {
+  const mockUserId = 'rewarded_interest_id';
+  const mockApi = {
+    getApiVersion: () => '1.0',
+    getIdentityToken: () => Promise.resolve(mockUserId)
+  };
+  const errorApiNotFound = `${MODULE_NAME} module: Rewarded Interest API not found`;
+  const errorIdFetch = `${MODULE_NAME} module: ID fetch encountered an error`;
+  let mockReadySate = 'complete';
+  let logErrorSpy;
+  let callbackSpy;
+
+  before(() => {
+    Object.defineProperty(document, 'readyState', {
+      get() {
+        return mockReadySate;
+      },
+    });
+  });
+
+  beforeEach(() => {
+    logErrorSpy = sinon.spy(utils, 'logError');
+    callbackSpy = sinon.spy();
+  });
+
+  afterEach(() => {
+    mockReadySate = 'complete';
+    delete window.__riApi;
+    logErrorSpy.restore();
+  });
+
+  describe('getRewardedInterestApi', () => {
+    it('should return Rewarded Interest Api if exists', () => {
+      expect(getRewardedInterestApi()).to.be.undefined;
+      window.__riApi = {};
+      expect(getRewardedInterestApi()).to.be.undefined;
+      window.__riApi.getIdentityToken = mockApi.getIdentityToken;
+      expect(getRewardedInterestApi()).to.deep.equal(window.__riApi);
+    });
+  });
+
+  describe('watchRewardedInterestApi', () => {
+    it('should execute callback when __riApi is set', () => {
+      watchRewardedInterestApi(callbackSpy);
+      expect(window.__riApi).to.be.undefined;
+      window.__riApi = mockApi;
+      expect(callbackSpy.calledOnceWithExactly(mockApi)).to.be.true;
+      expect(getRewardedInterestApi()).to.deep.equal(mockApi);
+    });
+  });
+
+  describe('getRewardedInterestId', () => {
+    it('should get id from API and pass it to callback', async () => {
+      await getRewardedInterestId(mockApi, callbackSpy);
+      expect(callbackSpy.calledOnceWithExactly(mockUserId)).to.be.true;
+    });
+  });
+
+  describe('apiNotAvailable', () => {
+    it('should call callback without ID and log error', () => {
+      apiNotAvailable(callbackSpy);
+      expect(callbackSpy.calledOnceWithExactly()).to.be.true;
+      expect(logErrorSpy.calledOnceWithExactly(errorApiNotFound)).to.be.true;
+    });
+  });
+
+  describe('rewardedInterestIdSubmodule.name', () => {
+    it('should expose the name of the submodule', () => {
+      expect(rewardedInterestIdSubmodule).to.be.an.instanceof(Object);
+      expect(rewardedInterestIdSubmodule.name).to.equal(MODULE_NAME);
+    });
+  });
+
+  describe('rewardedInterestIdSubmodule.decode', () => {
+    it('should wrap the given value inside an object literal', () => {
+      expect(rewardedInterestIdSubmodule.decode(mockUserId)).to.deep.equal({ [MODULE_NAME]: mockUserId });
+      expect(rewardedInterestIdSubmodule.decode('')).to.be.undefined;
+      expect(rewardedInterestIdSubmodule.decode(null)).to.be.undefined;
+    });
+  });
+
+  describe('rewardedInterestIdSubmodule.getId', () => {
+    it('should return object with callback property', () => {
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      expect(idResponse).to.be.an.instanceof(Object);
+      expect(idResponse).to.have.property('callback');
+      expect(idResponse.callback).to.be.a('function');
+    });
+
+    it('API not found, window loaded', async () => {
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      idResponse.callback(callbackSpy);
+      await Promise.resolve();
+      expect(callbackSpy.calledOnceWithExactly()).to.be.true;
+      expect(logErrorSpy.calledOnceWithExactly(errorApiNotFound)).to.be.true;
+    });
+
+    it('API not found, window not loaded', async () => {
+      mockReadySate = 'loading';
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      idResponse.callback(callbackSpy);
+      window.dispatchEvent(new Event('load'));
+      expect(callbackSpy.calledOnceWithExactly()).to.be.true;
+      expect(logErrorSpy.calledOnceWithExactly(errorApiNotFound)).to.be.true;
+    });
+
+    it('API is set before getId, getIdentityToken return error', async () => {
+      const error = Error();
+      window.__riApi = {getIdentityToken: () => Promise.reject(error)};
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      idResponse.callback(callbackSpy);
+      await window.__riApi.getIdentityToken().catch(() => {});
+      expect(callbackSpy.calledOnceWithExactly()).to.be.true;
+      expect(logErrorSpy.calledOnceWithExactly(errorIdFetch, error)).to.be.true;
+    });
+
+    it('API is set after getId, getIdentityToken return error', async () => {
+      const error = Error();
+      mockReadySate = 'loading';
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      idResponse.callback(callbackSpy);
+      window.__riApi = {getIdentityToken: () => Promise.reject(error)};
+      await window.__riApi.getIdentityToken().catch(() => {});
+      expect(callbackSpy.calledOnceWithExactly()).to.be.true;
+      expect(logErrorSpy.calledOnceWithExactly(errorIdFetch, error)).to.be.true;
+    });
+
+    it('API is set before getId, getIdentityToken return user ID', async () => {
+      window.__riApi = mockApi;
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      idResponse.callback(callbackSpy);
+      await mockApi.getIdentityToken();
+      expect(callbackSpy.calledOnceWithExactly(mockUserId)).to.be.true;
+    });
+
+    it('API is set after getId, getIdentityToken return user ID', async () => {
+      mockReadySate = 'loading';
+      const idResponse = rewardedInterestIdSubmodule.getId();
+      idResponse.callback(callbackSpy);
+      window.__riApi = mockApi;
+      window.dispatchEvent(new Event('load'));
+      await window.__riApi.getIdentityToken().catch(() => {});
+      expect(callbackSpy.calledOnceWithExactly(mockUserId)).to.be.true;
+    });
+  });
+
+  describe('rewardedInterestIdSubmodule.eids', () => {
+    it('should expose the eids of the submodule', () => {
+      expect(rewardedInterestIdSubmodule).to.have.property('eids');
+      expect(rewardedInterestIdSubmodule.eids).to.be.a('object');
+      expect(rewardedInterestIdSubmodule.eids).to.deep.equal({
+        [MODULE_NAME]: {
+          source: SOURCE,
+          atype: 3,
+        },
+      });
+    });
+
+    it('createEidsArray', () => {
+      attachIdSystem(rewardedInterestIdSubmodule);
+      const eids = createEidsArray({
+        [MODULE_NAME]: mockUserId
+      });
+      expect(eids).to.be.a('array');
+      expect(eids.length).to.equal(1);
+      expect(eids[0]).to.deep.equal({
+        source: SOURCE,
+        uids: [{
+          id: mockUserId,
+          atype: 3,
+        }]
+      });
+    });
+  });
+});

From 9ef4335f930b6c439c3819558387b32d3c4a8dc8 Mon Sep 17 00:00:00 2001
From: Patrick McCann 
Date: Sat, 16 Nov 2024 13:04:45 -0500
Subject: [PATCH 0673/1097] Brave utils: initial commit (#12412)

* Create index.js

* Create nativeAssets.js

* Update braveBidAdapter.js

* Update videoheroesBidAdapter.js

* Update videoheroesBidAdapter.js

* Update braveBidAdapter.js

* Update adotBidAdapter.js

* Update adotBidAdapter.js

* Update adotBidAdapter.js

* Update adotBidAdapter.js

* Update adotBidAdapter.js

* Update adotBidAdapter.js

* Update braveBidAdapter.js

* Update videoheroesBidAdapter.js

* Update videoheroesBidAdapter.js

* Update braveBidAdapter.js

* Update adotBidAdapter.js

* Create buildAndInterpret.js

* Update braveBidAdapter.js

* Update videoheroesBidAdapter.js

* Update buildAndInterpret.js

* Update braveBidAdapter.js

* Update videoheroesBidAdapter.js

* Update braveBidAdapter.js

* Update videoheroesBidAdapter.js

* Update buildAndInterpret.js
---
 libraries/braveUtils/buildAndInterpret.js |  78 +++++++
 libraries/braveUtils/index.js             |  95 +++++++++
 libraries/braveUtils/nativeAssets.js      |  23 ++
 modules/adotBidAdapter.js                 |  41 +++-
 modules/braveBidAdapter.js                | 247 +---------------------
 modules/videoheroesBidAdapter.js          | 240 +--------------------
 6 files changed, 242 insertions(+), 482 deletions(-)
 create mode 100644 libraries/braveUtils/buildAndInterpret.js
 create mode 100644 libraries/braveUtils/index.js
 create mode 100644 libraries/braveUtils/nativeAssets.js

diff --git a/libraries/braveUtils/buildAndInterpret.js b/libraries/braveUtils/buildAndInterpret.js
new file mode 100644
index 00000000000..66cd63896f7
--- /dev/null
+++ b/libraries/braveUtils/buildAndInterpret.js
@@ -0,0 +1,78 @@
+import { isEmpty, parseUrl } from '../../src/utils.js';
+import {config} from '../../src/config.js';
+import { createNativeRequest, createBannerRequest, createVideoRequest } from './index.js';
+import { convertOrtbRequestToProprietaryNative } from '../../src/native.js';
+
+export const buildRequests = (validBidRequests, bidderRequest, endpointURL, defaultCur) => {
+  validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
+  if (!validBidRequests.length || !bidderRequest) return [];
+
+  const endpoint = endpointURL.replace('hash', validBidRequests[0].params.placementId);
+  const imp = validBidRequests.map((br) => {
+    const impObject = { id: br.bidId, secure: 1 };
+    if (br.mediaTypes.banner) impObject.banner = createBannerRequest(br);
+    else if (br.mediaTypes.video) impObject.video = createVideoRequest(br);
+    else if (br.mediaTypes.native) impObject.native = { id: br.transactionId, ver: '1.2', request: createNativeRequest(br) };
+    return impObject;
+  });
+
+  const page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation;
+  const data = {
+    id: bidderRequest.bidderRequestId,
+    cur: [defaultCur],
+    device: { w: screen.width, h: screen.height, language: navigator.language?.split('-')[0], ua: navigator.userAgent },
+    site: { domain: parseUrl(page).hostname, page: page },
+    tmax: bidderRequest.timeout,
+    imp,
+  };
+
+  if (bidderRequest.refererInfo.ref) data.site.ref = bidderRequest.refererInfo.ref;
+  if (bidderRequest.gdprConsent) {
+    data.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } };
+    data.user = { ext: { consent: bidderRequest.gdprConsent.consentString || '' } };
+  }
+  if (bidderRequest.uspConsent) data.regs.ext.us_privacy = bidderRequest.uspConsent;
+  if (config.getConfig('coppa')) data.regs.coppa = 1;
+  if (validBidRequests[0].schain) data.source = { ext: { schain: validBidRequests[0].schain } };
+
+  return { method: 'POST', url: endpoint, data };
+};
+
+export const interpretResponse = (serverResponse, defaultCur, parseNative) => {
+  if (!serverResponse || isEmpty(serverResponse.body)) return [];
+
+  let bids = [];
+  serverResponse.body.seatbid.forEach(response => {
+    response.bid.forEach(bid => {
+      const mediaType = bid.ext?.mediaType || 'banner';
+
+      const bidObj = {
+        requestId: bid.impid,
+        cpm: bid.price,
+        width: bid.w,
+        height: bid.h,
+        ttl: 1200,
+        currency: defaultCur,
+        netRevenue: true,
+        creativeId: bid.crid,
+        dealId: bid.dealid || null,
+        mediaType,
+      };
+
+      switch (mediaType) {
+        case 'video':
+          bidObj.vastUrl = bid.adm;
+          break;
+        case 'native':
+          bidObj.native = parseNative(bid.adm);
+          break;
+        default:
+          bidObj.ad = bid.adm;
+      }
+
+      bids.push(bidObj);
+    });
+  });
+
+  return bids;
+};
diff --git a/libraries/braveUtils/index.js b/libraries/braveUtils/index.js
new file mode 100644
index 00000000000..5756e09ae5c
--- /dev/null
+++ b/libraries/braveUtils/index.js
@@ -0,0 +1,95 @@
+import { NATIVE_ASSETS, NATIVE_ASSETS_IDS } from './nativeAssets.js';
+
+/**
+ * Builds a native request object based on the bid request
+ * @param {object} br - The bid request
+ * @returns {object} The native request object
+ */
+export function createNativeRequest(br) {
+  let impObject = {
+    ver: '1.2',
+    assets: []
+  };
+
+  Object.keys(br.mediaTypes.native).forEach((key) => {
+    const props = NATIVE_ASSETS[key];
+    if (props) {
+      const asset = {
+        required: br.mediaTypes.native[key].required ? 1 : 0,
+        id: props.id,
+        [props.name]: {}
+      };
+
+      if (props.type) asset[props.name]['type'] = props.type;
+      if (br.mediaTypes.native[key].len) asset[props.name]['len'] = br.mediaTypes.native[key].len;
+      if (br.mediaTypes.native[key].sizes && br.mediaTypes.native[key].sizes[0]) {
+        asset[props.name]['w'] = br.mediaTypes.native[key].sizes[0];
+        asset[props.name]['h'] = br.mediaTypes.native[key].sizes[1];
+      }
+
+      impObject.assets.push(asset);
+    }
+  });
+
+  return impObject;
+}
+
+/**
+ * Builds a banner request object based on the bid request
+ * @param {object} br - The bid request
+ * @returns {object} The banner request object
+ */
+export function createBannerRequest(br) {
+  let size = br.mediaTypes.banner.sizes?.[0] || [300, 250];
+  return { id: br.transactionId, w: size[0], h: size[1] };
+}
+
+/**
+ * Builds a video request object based on the bid request
+ * @param {object} br - The bid request
+ * @returns {object} The video request object
+ */
+export function createVideoRequest(br) {
+  let videoObj = { id: br.transactionId };
+  const supportedParams = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity'];
+
+  supportedParams.forEach((param) => {
+    if (br.mediaTypes.video[param] !== undefined) {
+      videoObj[param] = br.mediaTypes.video[param];
+    }
+  });
+
+  const playerSize = br.mediaTypes.video.playerSize;
+  if (playerSize) {
+    videoObj.w = Array.isArray(playerSize[0]) ? playerSize[0][0] : playerSize[0];
+    videoObj.h = Array.isArray(playerSize[0]) ? playerSize[0][1] : playerSize[1];
+  } else {
+    videoObj.w = 640;
+    videoObj.h = 480;
+  }
+
+  return videoObj;
+}
+
+/**
+ * Parses the native ad response
+ * @param {object} adm - The native ad response
+ * @returns {object} Parsed native ad object
+ */
+export function parseNative(adm) {
+  let bid = {
+    clickUrl: adm.native.link?.url,
+    impressionTrackers: adm.native.imptrackers || [],
+    clickTrackers: adm.native.link?.clicktrackers || [],
+    jstracker: adm.native.jstracker || []
+  };
+  adm.native.assets.forEach((asset) => {
+    const kind = NATIVE_ASSETS_IDS[asset.id];
+    const content = kind && asset[NATIVE_ASSETS[kind].name];
+    if (content) {
+      bid[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h };
+    }
+  });
+
+  return bid;
+}
diff --git a/libraries/braveUtils/nativeAssets.js b/libraries/braveUtils/nativeAssets.js
new file mode 100644
index 00000000000..07de1264d0c
--- /dev/null
+++ b/libraries/braveUtils/nativeAssets.js
@@ -0,0 +1,23 @@
+/**
+ * IDs and asset types for native ad assets.
+ */
+export const NATIVE_ASSETS_IDS = {
+  1: 'title',
+  2: 'icon',
+  3: 'image',
+  4: 'body',
+  5: 'sponsoredBy',
+  6: 'cta'
+};
+
+/**
+ * Native assets definition for mapping purposes.
+ */
+export const NATIVE_ASSETS = {
+  title: { id: 1, name: 'title' },
+  icon: { id: 2, type: 1, name: 'img' },
+  image: { id: 3, type: 3, name: 'img' },
+  body: { id: 4, type: 2, name: 'data' },
+  sponsoredBy: { id: 5, type: 1, name: 'data' },
+  cta: { id: 6, type: 12, name: 'data' }
+};
diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js
index fd24ccdeecb..5167eeb8244 100644
--- a/modules/adotBidAdapter.js
+++ b/modules/adotBidAdapter.js
@@ -6,6 +6,7 @@ import {find} from '../src/polyfill.js';
 import {config} from '../src/config.js';
 import {OUTSTREAM} from '../src/video.js';
 import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
+import { NATIVE_ASSETS_IDS as NATIVE_ID_MAPPING, NATIVE_ASSETS as NATIVE_PLACEMENTS } from '../libraries/braveUtils/nativeAssets.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -24,6 +25,37 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
  * @typedef {import('../src/adapters/bidderFactory.js').Imp} Imp
  */
 
+/**
+ * @typedef {Object} OpenRtbRequest
+ * @property {string} id - Unique request ID
+ * @property {Array} imp - List of impression objects
+ * @property {Site} site - Site information
+ * @property {Device} device - Device information
+ * @property {User} user - User information
+ * @property {object} regs - Regulatory data, including GDPR and COPPA
+ * @property {object} ext - Additional extensions, such as custom data for the bid request
+ * @property {number} at - Auction type, typically first-price or second-price
+ */
+
+/**
+ * @typedef {Object} OpenRtbBid
+ * @property {string} impid - ID of the impression this bid relates to
+ * @property {number} price - Bid price for the impression
+ * @property {string} adid - Ad ID for the bid
+ * @property {number} [crid] - Creative ID, if available
+ * @property {string} [dealid] - Deal ID if the bid is part of a private marketplace deal
+ * @property {object} [ext] - Additional bid-specific extensions, such as media type
+ * @property {string} [adm] - Ad markup if it’s directly included in the bid response
+ * @property {string} [nurl] - Notification URL to be called when the bid wins
+ */
+
+/**
+ * @typedef {Object} OpenRtbBidResponse
+ * @property {string} id - ID of the bid response
+ * @property {Array<{bid: Array}>} seatbid - Array of seat bids, each containing a list of bids
+ * @property {string} cur - Currency in which bid amounts are expressed
+ */
+
 const BIDDER_CODE = 'adot';
 const ADAPTER_VERSION = 'v2.0.0';
 const GVLID = 272;
@@ -32,15 +64,6 @@ const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidreq
 const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols'];
 const FIRST_PRICE = 1;
 const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative };
-const NATIVE_PLACEMENTS = {
-  title: { id: 1, name: 'title' },
-  icon: { id: 2, type: 1, name: 'img' },
-  image: { id: 3, type: 3, name: 'img' },
-  sponsoredBy: { id: 4, name: 'data', type: 1 },
-  body: { id: 5, name: 'data', type: 2 },
-  cta: { id: 6, type: 12, name: 'data' }
-};
-const NATIVE_ID_MAPPING = { 1: 'title', 2: 'icon', 3: 'image', 4: 'sponsoredBy', 5: 'body', 6: 'cta' };
 const OUTSTREAM_VIDEO_PLAYER_URL = 'https://adserver.adotmob.com/video/player.min.js';
 const BID_RESPONSE_NET_REVENUE = true;
 const BID_RESPONSE_TTL = 10;
diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js
index 4c5448482db..7689aade114 100644
--- a/modules/braveBidAdapter.js
+++ b/modules/braveBidAdapter.js
@@ -1,8 +1,8 @@
-import {isEmpty, isStr, parseUrl, triggerPixel} from '../src/utils.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
-import {config} from '../src/config.js';
-import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
+import { isStr, triggerPixel } from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { parseNative } from '../libraries/braveUtils/index.js';
+import { buildRequests, interpretResponse } from '../libraries/braveUtils/buildAndInterpret.js'
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -13,160 +13,15 @@ const BIDDER_CODE = 'brave';
 const DEFAULT_CUR = 'USD';
 const ENDPOINT_URL = `https://point.bravegroup.tv/?t=2&partner=hash`;
 
-const NATIVE_ASSETS_IDS = { 1: 'title', 2: 'icon', 3: 'image', 4: 'body', 5: 'sponsoredBy', 6: 'cta' };
-const NATIVE_ASSETS = {
-  title: { id: 1, name: 'title' },
-  icon: { id: 2, type: 1, name: 'img' },
-  image: { id: 3, type: 3, name: 'img' },
-  body: { id: 4, type: 2, name: 'data' },
-  sponsoredBy: { id: 5, type: 1, name: 'data' },
-  cta: { id: 6, type: 12, name: 'data' }
-};
-
 export const spec = {
   code: BIDDER_CODE,
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 
-  /**
-   * Determines whether or not the given bid request is valid.
-   *
-   * @param {object} bid The bid to validate.
-   * @return boolean True if this is a valid bid, and false otherwise.
-   */
-  isBidRequestValid: (bid) => {
-    return !!(bid.params.placementId && bid.params.placementId.toString().length === 32);
-  },
-
-  /**
-   * Make a server request from the list of BidRequests.
-   *
-   * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server.
-   * @return ServerRequest Info describing the request to the server.
-   */
-  buildRequests: (validBidRequests, bidderRequest) => {
-    // convert Native ORTB definition to old-style prebid native definition
-    validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
-
-    if (validBidRequests.length === 0 || !bidderRequest) return [];
-
-    const endpointURL = ENDPOINT_URL.replace('hash', validBidRequests[0].params.placementId);
-
-    let imp = validBidRequests.map(br => {
-      let impObject = {
-        id: br.bidId,
-        secure: 1
-      };
-
-      if (br.mediaTypes.banner) {
-        impObject.banner = createBannerRequest(br);
-      } else if (br.mediaTypes.video) {
-        impObject.video = createVideoRequest(br);
-      } else if (br.mediaTypes.native) {
-        impObject.native = {
-          // TODO: `id` is not part of the ORTB native spec, is this intentional?
-          id: br.bidId,
-          ver: '1.2',
-          request: createNativeRequest(br)
-        };
-      }
-      return impObject;
-    });
-
-    // TODO: do these values make sense?
-    let page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation;
-    let r = bidderRequest.refererInfo.ref;
-
-    let data = {
-      id: bidderRequest.bidderRequestId,
-      cur: [ DEFAULT_CUR ],
-      device: {
-        w: screen.width,
-        h: screen.height,
-        language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '',
-        ua: navigator.userAgent,
-      },
-      site: {
-        domain: parseUrl(page).hostname,
-        page: page,
-      },
-      tmax: bidderRequest.timeout,
-      imp
-    };
-
-    if (r) {
-      data.site.ref = r;
-    }
-
-    if (bidderRequest.gdprConsent) {
-      data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}};
-      data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}};
-    }
-
-    if (bidderRequest.uspConsent !== undefined) {
-      if (!data['regs'])data['regs'] = {'ext': {}};
-      data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent;
-    }
-
-    if (config.getConfig('coppa') === true) {
-      if (!data['regs'])data['regs'] = {'coppa': 1};
-      else data['regs']['coppa'] = 1;
-    }
-
-    if (validBidRequests[0].schain) {
-      data['source'] = {'ext': {'schain': validBidRequests[0].schain}};
-    }
-
-    return {
-      method: 'POST',
-      url: endpointURL,
-      data: data
-    };
-  },
-
-  /**
-   * Unpack the response from the server into a list of bids.
-   *
-   * @param {*} serverResponse A successful response from the server.
-   * @return {Bid[]} An array of bids which were nested inside the server.
-   */
-  interpretResponse: (serverResponse) => {
-    if (!serverResponse || isEmpty(serverResponse.body)) return [];
-
-    let bids = [];
-    serverResponse.body.seatbid.forEach(response => {
-      response.bid.forEach(bid => {
-        let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner';
-
-        let bidObj = {
-          requestId: bid.impid,
-          cpm: bid.price,
-          width: bid.w,
-          height: bid.h,
-          ttl: 1200,
-          currency: DEFAULT_CUR,
-          netRevenue: true,
-          creativeId: bid.crid,
-          dealId: bid.dealid || null,
-          mediaType: mediaType
-        };
-
-        switch (mediaType) {
-          case 'video':
-            bidObj.vastUrl = bid.adm;
-            break;
-          case 'native':
-            bidObj.native = parseNative(bid.adm);
-            break;
-          default:
-            bidObj.ad = bid.adm;
-        }
+  isBidRequestValid: (bid) => !!(bid.params.placementId && bid.params.placementId.toString().length === 32),
 
-        bids.push(bidObj);
-      });
-    });
+  buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT_URL, DEFAULT_CUR),
 
-    return bids;
-  },
+  interpretResponse: (serverResponse) => interpretResponse(serverResponse, DEFAULT_CUR, parseNative),
 
   onBidWon: (bid) => {
     if (isStr(bid.nurl) && bid.nurl !== '') {
@@ -175,90 +30,4 @@ export const spec = {
   }
 };
 
-const parseNative = adm => {
-  let bid = {
-    clickUrl: adm.native.link && adm.native.link.url,
-    impressionTrackers: adm.native.imptrackers || [],
-    clickTrackers: (adm.native.link && adm.native.link.clicktrackers) || [],
-    jstracker: adm.native.jstracker || []
-  };
-  adm.native.assets.forEach(asset => {
-    let kind = NATIVE_ASSETS_IDS[asset.id];
-    let content = kind && asset[NATIVE_ASSETS[kind].name];
-    if (content) {
-      bid[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h };
-    }
-  });
-
-  return bid;
-}
-
-const createNativeRequest = br => {
-  let impObject = {
-    ver: '1.2',
-    assets: []
-  };
-
-  let keys = Object.keys(br.mediaTypes.native);
-
-  for (let key of keys) {
-    const props = NATIVE_ASSETS[key];
-    if (props) {
-      const asset = {
-        required: br.mediaTypes.native[key].required ? 1 : 0,
-        id: props.id,
-        [props.name]: {}
-      };
-
-      if (props.type) asset[props.name]['type'] = props.type;
-      if (br.mediaTypes.native[key].len) asset[props.name]['len'] = br.mediaTypes.native[key].len;
-      if (br.mediaTypes.native[key].sizes && br.mediaTypes.native[key].sizes[0]) {
-        asset[props.name]['w'] = br.mediaTypes.native[key].sizes[0];
-        asset[props.name]['h'] = br.mediaTypes.native[key].sizes[1];
-      }
-
-      impObject.assets.push(asset);
-    }
-  }
-
-  return impObject;
-}
-
-const createBannerRequest = br => {
-  let size = [];
-
-  if (br.mediaTypes.banner.sizes && Array.isArray(br.mediaTypes.banner.sizes)) {
-    if (Array.isArray(br.mediaTypes.banner.sizes[0])) { size = br.mediaTypes.banner.sizes[0]; } else { size = br.mediaTypes.banner.sizes; }
-  } else size = [300, 250];
-
-  return { id: br.transactionId, w: size[0], h: size[1] };
-};
-
-const createVideoRequest = br => {
-  // TODO: `id` is not part of imp.video in ORTB; is this intentional?
-  let videoObj = {id: br.bidId};
-  let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity'];
-
-  for (let param of supportParamsList) {
-    if (br.mediaTypes.video[param] !== undefined) {
-      videoObj[param] = br.mediaTypes.video[param];
-    }
-  }
-
-  if (br.mediaTypes.video.playerSize && Array.isArray(br.mediaTypes.video.playerSize)) {
-    if (Array.isArray(br.mediaTypes.video.playerSize[0])) {
-      videoObj.w = br.mediaTypes.video.playerSize[0][0];
-      videoObj.h = br.mediaTypes.video.playerSize[0][1];
-    } else {
-      videoObj.w = br.mediaTypes.video.playerSize[0];
-      videoObj.h = br.mediaTypes.video.playerSize[1];
-    }
-  } else {
-    videoObj.w = 640;
-    videoObj.h = 480;
-  }
-
-  return videoObj;
-}
-
 registerBidder(spec);
diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js
index ee2c2deef8b..4adabacc33b 100644
--- a/modules/videoheroesBidAdapter.js
+++ b/modules/videoheroesBidAdapter.js
@@ -1,8 +1,8 @@
-import { isEmpty, parseUrl, isStr, triggerPixel } from '../src/utils.js';
+import { triggerPixel, isStr } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
-import { config } from '../src/config.js';
-import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
+import { parseNative } from '../libraries/braveUtils/index.js';
+import { buildRequests, interpretResponse } from '../libraries/braveUtils/buildAndInterpret.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -13,158 +13,15 @@ const BIDDER_CODE = 'videoheroes';
 const DEFAULT_CUR = 'USD';
 const ENDPOINT_URL = `https://point.contextualadv.com/?t=2&partner=hash`;
 
-const NATIVE_ASSETS_IDS = { 1: 'title', 2: 'icon', 3: 'image', 4: 'body', 5: 'sponsoredBy', 6: 'cta' };
-const NATIVE_ASSETS = {
-  title: { id: 1, name: 'title' },
-  icon: { id: 2, type: 1, name: 'img' },
-  image: { id: 3, type: 3, name: 'img' },
-  body: { id: 4, type: 2, name: 'data' },
-  sponsoredBy: { id: 5, type: 1, name: 'data' },
-  cta: { id: 6, type: 12, name: 'data' }
-};
-
 export const spec = {
   code: BIDDER_CODE,
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 
-  /**
-   * Determines whether or not the given bid request is valid.
-   *
-   * @param {object} bid The bid to validate.
-   * @return boolean True if this is a valid bid, and false otherwise.
-   */
-  isBidRequestValid: (bid) => {
-    return !!(bid.params.placementId && bid.params.placementId.toString().length === 32);
-  },
-
-  /**
-   * Make a server request from the list of BidRequests.
-   *
-   * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server.
-   * @return ServerRequest Info describing the request to the server.
-   */
-  buildRequests: (validBidRequests, bidderRequest) => {
-    // convert Native ORTB definition to old-style prebid native definition
-    validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
-    if (validBidRequests.length === 0 || !bidderRequest) return [];
-
-    const endpointURL = ENDPOINT_URL.replace('hash', validBidRequests[0].params.placementId);
-
-    let imp = validBidRequests.map(br => {
-      let impObject = {
-        id: br.bidId,
-        secure: 1
-      };
-
-      if (br.mediaTypes.banner) {
-        impObject.banner = createBannerRequest(br);
-      } else if (br.mediaTypes.video) {
-        impObject.video = createVideoRequest(br);
-      } else if (br.mediaTypes.native) {
-        impObject.native = {
-          // TODO: fix transactionId leak: https://github.com/prebid/Prebid.js/issues/9781
-          // Also, `id` is not in the ORTB native spec
-          id: br.transactionId,
-          ver: '1.2',
-          request: createNativeRequest(br)
-        };
-      }
-      return impObject;
-    });
-
-    let page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation;
-
-    let data = {
-      id: bidderRequest.bidderRequestId,
-      cur: [ DEFAULT_CUR ],
-      device: {
-        w: screen.width,
-        h: screen.height,
-        language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '',
-        ua: navigator.userAgent,
-      },
-      site: {
-        domain: parseUrl(page).hostname,
-        page: page,
-      },
-      tmax: bidderRequest.timeout,
-      imp
-    };
-
-    if (bidderRequest.refererInfo.ref) {
-      data.site.ref = bidderRequest.refererInfo.ref;
-    }
-
-    if (bidderRequest.gdprConsent) {
-      data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}};
-      data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}};
-    }
-
-    if (bidderRequest.uspConsent !== undefined) {
-      if (!data['regs'])data['regs'] = {'ext': {}};
-      data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent;
-    }
-
-    if (config.getConfig('coppa') === true) {
-      if (!data['regs'])data['regs'] = {'coppa': 1};
-      else data['regs']['coppa'] = 1;
-    }
-
-    if (validBidRequests[0].schain) {
-      data['source'] = {'ext': {'schain': validBidRequests[0].schain}};
-    }
-
-    return {
-      method: 'POST',
-      url: endpointURL,
-      data: data
-    };
-  },
-
-  /**
-   * Unpack the response from the server into a list of bids.
-   *
-   * @param {*} serverResponse A successful response from the server.
-   * @return {Bid[]} An array of bids which were nested inside the server.
-   */
-  interpretResponse: (serverResponse) => {
-    if (!serverResponse || isEmpty(serverResponse.body)) return [];
-
-    let bids = [];
-    serverResponse.body.seatbid.forEach(response => {
-      response.bid.forEach(bid => {
-        let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner';
-
-        let bidObj = {
-          requestId: bid.impid,
-          cpm: bid.price,
-          width: bid.w,
-          height: bid.h,
-          ttl: 1200,
-          currency: DEFAULT_CUR,
-          netRevenue: true,
-          creativeId: bid.crid,
-          dealId: bid.dealid || null,
-          mediaType: mediaType
-        };
-
-        switch (mediaType) {
-          case 'video':
-            bidObj.vastUrl = bid.adm;
-            break;
-          case 'native':
-            bidObj.native = parseNative(bid.adm);
-            break;
-          default:
-            bidObj.ad = bid.adm;
-        }
+  isBidRequestValid: (bid) => !!(bid.params.placementId && bid.params.placementId.toString().length === 32),
 
-        bids.push(bidObj);
-      });
-    });
+  buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT_URL, DEFAULT_CUR),
 
-    return bids;
-  },
+  interpretResponse: (serverResponse) => interpretResponse(serverResponse, DEFAULT_CUR, parseNative),
 
   onBidWon: (bid) => {
     if (isStr(bid.nurl) && bid.nurl !== '') {
@@ -173,89 +30,4 @@ export const spec = {
   }
 };
 
-const parseNative = adm => {
-  let bid = {
-    clickUrl: adm.native.link && adm.native.link.url,
-    impressionTrackers: adm.native.imptrackers || [],
-    clickTrackers: (adm.native.link && adm.native.link.clicktrackers) || [],
-    jstracker: adm.native.jstracker || []
-  };
-  adm.native.assets.forEach(asset => {
-    let kind = NATIVE_ASSETS_IDS[asset.id];
-    let content = kind && asset[NATIVE_ASSETS[kind].name];
-    if (content) {
-      bid[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h };
-    }
-  });
-
-  return bid;
-}
-
-const createNativeRequest = br => {
-  let impObject = {
-    ver: '1.2',
-    assets: []
-  };
-
-  let keys = Object.keys(br.mediaTypes.native);
-
-  for (let key of keys) {
-    const props = NATIVE_ASSETS[key];
-    if (props) {
-      const asset = {
-        required: br.mediaTypes.native[key].required ? 1 : 0,
-        id: props.id,
-        [props.name]: {}
-      };
-
-      if (props.type) asset[props.name]['type'] = props.type;
-      if (br.mediaTypes.native[key].len) asset[props.name]['len'] = br.mediaTypes.native[key].len;
-      if (br.mediaTypes.native[key].sizes && br.mediaTypes.native[key].sizes[0]) {
-        asset[props.name]['w'] = br.mediaTypes.native[key].sizes[0];
-        asset[props.name]['h'] = br.mediaTypes.native[key].sizes[1];
-      }
-
-      impObject.assets.push(asset);
-    }
-  }
-
-  return impObject;
-}
-
-const createBannerRequest = br => {
-  let size = [];
-
-  if (br.mediaTypes.banner.sizes && Array.isArray(br.mediaTypes.banner.sizes)) {
-    if (Array.isArray(br.mediaTypes.banner.sizes[0])) { size = br.mediaTypes.banner.sizes[0]; } else { size = br.mediaTypes.banner.sizes; }
-  } else size = [300, 250];
-
-  return { id: br.transactionId, w: size[0], h: size[1] };
-};
-
-const createVideoRequest = br => {
-  let videoObj = {id: br.transactionId};
-  let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity'];
-
-  for (let param of supportParamsList) {
-    if (br.mediaTypes.video[param] !== undefined) {
-      videoObj[param] = br.mediaTypes.video[param];
-    }
-  }
-
-  if (br.mediaTypes.video.playerSize && Array.isArray(br.mediaTypes.video.playerSize)) {
-    if (Array.isArray(br.mediaTypes.video.playerSize[0])) {
-      videoObj.w = br.mediaTypes.video.playerSize[0][0];
-      videoObj.h = br.mediaTypes.video.playerSize[0][1];
-    } else {
-      videoObj.w = br.mediaTypes.video.playerSize[0];
-      videoObj.h = br.mediaTypes.video.playerSize[1];
-    }
-  } else {
-    videoObj.w = 640;
-    videoObj.h = 480;
-  }
-
-  return videoObj;
-}
-
 registerBidder(spec);

From 3b7b4341dd373b763b27c8c7f80fc2d07dd5813d Mon Sep 17 00:00:00 2001
From: vivekyadav15 
Date: Sun, 17 Nov 2024 01:33:14 +0530
Subject: [PATCH 0674/1097] - FIX: Replace deprecated pageXOffset and
 pageYOffset with scrollX and scrollY respectively (#12354)

- ADD: Viewport coordinate in bidRequest.ext.vcoords
---
 libraries/viewport/viewport.js               | 13 +++
 modules/medianetBidAdapter.js                | 19 ++--
 test/spec/modules/medianetBidAdapter_spec.js | 94 ++++++++++++++++++++
 3 files changed, 121 insertions(+), 5 deletions(-)
 create mode 100644 libraries/viewport/viewport.js

diff --git a/libraries/viewport/viewport.js b/libraries/viewport/viewport.js
new file mode 100644
index 00000000000..b385e9a27ec
--- /dev/null
+++ b/libraries/viewport/viewport.js
@@ -0,0 +1,13 @@
+import {getWindowTop} from '../../src/utils.js';
+
+export function getViewportCoordinates() {
+  try {
+    const win = getWindowTop();
+    let { scrollY: top, scrollX: left, innerHeight, innerWidth } = win;
+    innerHeight = innerHeight || win.document.documentElement.clientWidth || win.document.body.clientWidth;
+    innerWidth = innerWidth || win.document.documentElement.clientHeight || win.document.body.clientHeight
+    return { top, right: left + innerWidth, bottom: top + innerHeight, left };
+  } catch (e) {
+    return {};
+  }
+}
diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js
index c90292a74ac..0c43b04f53c 100644
--- a/modules/medianetBidAdapter.js
+++ b/modules/medianetBidAdapter.js
@@ -21,6 +21,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
 import {getGlobal} from '../src/prebidGlobal.js';
 import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
 import {ajax} from '../src/ajax.js';
+import {getViewportCoordinates} from '../libraries/viewport/viewport.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -180,6 +181,7 @@ function extParams(bidRequest, bidderRequests) {
   const gdprApplies = !!(gdpr && gdpr.gdprApplies);
   const uspApplies = !!(uspConsent);
   const coppaApplies = !!(config.getConfig('coppa'));
+  const {top = -1, right = -1, bottom = -1, left = -1} = getViewportCoordinates();
   return Object.assign({},
     { customer_id: params.cid },
     { prebid_version: 'v' + '$prebid.version$' },
@@ -191,7 +193,13 @@ function extParams(bidRequest, bidderRequests) {
     windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize },
     userId && { user_id: userId },
     getGlobal().medianetGlobals.analyticsEnabled && { analytics: true },
-    !isEmpty(sChain) && {schain: sChain}
+    !isEmpty(sChain) && {schain: sChain},
+    {
+      vcoords: {
+        top_left: { x: left, y: top },
+        bottom_right: { x: right, y: bottom }
+      }
+    }
   );
 }
 
@@ -317,14 +325,15 @@ function getOverlapArea(topLeft1, bottomRight1, topLeft2, bottomRight2) {
 }
 
 function normalizeCoordinates(coordinates) {
+  const {scrollX, scrollY} = window;
   return {
     top_left: {
-      x: coordinates.top_left.x + window.pageXOffset,
-      y: coordinates.top_left.y + window.pageYOffset,
+      x: coordinates.top_left.x + scrollX,
+      y: coordinates.top_left.y + scrollY,
     },
     bottom_right: {
-      x: coordinates.bottom_right.x + window.pageXOffset,
-      y: coordinates.bottom_right.y + window.pageYOffset,
+      x: coordinates.bottom_right.x + scrollX,
+      y: coordinates.bottom_right.y + scrollY,
     }
   }
 }
diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js
index d3eab769f4a..5f2efc7864f 100644
--- a/test/spec/modules/medianetBidAdapter_spec.js
+++ b/test/spec/modules/medianetBidAdapter_spec.js
@@ -531,6 +531,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -622,6 +632,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -714,6 +734,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -807,6 +837,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -901,6 +941,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -1008,6 +1058,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -1104,6 +1164,16 @@ let VALID_BID_REQUEST = [{
         'w': 1000,
         'h': 1000
       },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
+      }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
     'imp': [
@@ -1640,6 +1710,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -1747,6 +1827,16 @@ let VALID_BID_REQUEST = [{
       'screen': {
         'w': 1000,
         'h': 1000
+      },
+      'vcoords': {
+        'top_left': {
+          'x': 50,
+          'y': 100
+        },
+        'bottom_right': {
+          'x': 490,
+          'y': 880
+        }
       }
     },
     'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
@@ -1829,6 +1919,10 @@ describe('Media.net bid adapter', function () {
   let sandbox;
   beforeEach(function () {
     sandbox = sinon.sandbox.create();
+    sandbox.stub(window.top, 'innerHeight').value(780)
+    sandbox.stub(window.top, 'innerWidth').value(440)
+    sandbox.stub(window.top, 'scrollY').value(100)
+    sandbox.stub(window.top, 'scrollX').value(50)
   });
 
   afterEach(function () {

From 874c337a501d925a08293d4177ee8b4d74d19b86 Mon Sep 17 00:00:00 2001
From: LyricWulf 
Date: Sat, 16 Nov 2024 12:21:12 -0800
Subject: [PATCH 0675/1097] Update adkernelBidAdapter.js (#12233)

---
 modules/adkernelBidAdapter.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 109f228a029..361bdd2d046 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -84,7 +84,6 @@ export const spec = {
     {code: 'unibots'},
     {code: 'ergadx'},
     {code: 'turktelekom'},
-    {code: 'felixads'},
     {code: 'motionspots'},
     {code: 'sonic_twist'},
     {code: 'displayioads'},

From 70471f7bc73f0201be5e701b784535d7dabd3809 Mon Sep 17 00:00:00 2001
From: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com>
Date: Mon, 18 Nov 2024 14:27:44 +0200
Subject: [PATCH 0676/1097] PgamSSP Bid Adapter: add gvlid (#12464)

* new adapter PGAMSSP

* upd

* support UID 2.0

* del obj

* Add id5id

* add support gpp

* fix spaces

* add gvl_id

* upd

---------

Co-authored-by: Chris Huie 
---
 modules/pgamsspBidAdapter.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js
index 859bfc9de7e..36dbd1159cc 100644
--- a/modules/pgamsspBidAdapter.js
+++ b/modules/pgamsspBidAdapter.js
@@ -3,11 +3,13 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
 import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js';
 
 const BIDDER_CODE = 'pgamssp';
+const GVLID = 1353;
 const AD_URL = 'https://us-east.pgammedia.com/pbjs';
 const SYNC_URL = 'https://cs.pgammedia.com';
 
 export const spec = {
   code: BIDDER_CODE,
+  gvlid: GVLID,
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 
   isBidRequestValid: isBidRequestValid(),

From 86cbc21be22ae385529b06ddaff41c0600413c34 Mon Sep 17 00:00:00 2001
From: Rich Audience 
Date: Mon, 18 Nov 2024 16:38:49 +0100
Subject: [PATCH 0677/1097] RichAudience Bid Adapter: add support to adomain
 (#12465)

* Update richaudienceBidAdapter.md

Update maintainer e-mail to integrations@richaudience.com

* Add richaudienceBidAdapter.js file

* Add richaudienceBidAdapter_spec.js

* Update richaudienceBidAdapter.js

* RichaudienceBidAdapter add compability with DSA

* RichaudienceBidAdapter add compability with DSA

* RichaudienceBidAdapter add compability with DSA

* Richaudience Bid Adapter: update adomain

* Richaudience Bid Adapter: update adomain test

---------

Co-authored-by: Patrick McCann 
Co-authored-by: sergigimenez 
---
 modules/richaudienceBidAdapter.js                | 4 +++-
 test/spec/modules/richaudienceBidAdapter_spec.js | 8 ++++----
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js
index edf5c03cf15..b88e9474c28 100644
--- a/modules/richaudienceBidAdapter.js
+++ b/modules/richaudienceBidAdapter.js
@@ -118,7 +118,9 @@ export const spec = {
         netRevenue: response.netRevenue,
         currency: response.currency,
         ttl: response.ttl,
-        meta: response.adomain,
+        meta: {
+          advertiserDomains: [response.adomain[0]]
+        },
         dealId: response.dealId
       };
 
diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js
index a1baaf52a77..26d226b65f3 100644
--- a/test/spec/modules/richaudienceBidAdapter_spec.js
+++ b/test/spec/modules/richaudienceBidAdapter_spec.js
@@ -258,7 +258,7 @@ describe('Richaudience adapter tests', function () {
       currency: 'USD',
       ttl: 300,
       dealId: 'dealId',
-      adomain: 'richaudience.com'
+      adomain: ['richaudience.com']
     }
   };
 
@@ -274,7 +274,7 @@ describe('Richaudience adapter tests', function () {
       ttl: 300,
       vastXML: '',
       dealId: 'dealId',
-      adomain: 'richaudience.com'
+      adomain: ['richaudience.com']
     }
   };
 
@@ -710,7 +710,7 @@ describe('Richaudience adapter tests', function () {
     expect(bid.currency).to.equal('USD');
     expect(bid.ttl).to.equal(300);
     expect(bid.dealId).to.equal('dealId');
-    expect(bid.meta).to.equal('richaudience.com');
+    expect(bid.meta.advertiserDomains[0]).to.equal('richaudience.com');
   });
 
   it('no banner media response inestream', function () {
@@ -739,7 +739,7 @@ describe('Richaudience adapter tests', function () {
     expect(bid.currency).to.equal('USD');
     expect(bid.ttl).to.equal(300);
     expect(bid.dealId).to.equal('dealId');
-    expect(bid.meta).to.equal('richaudience.com');
+    expect(bid.meta.advertiserDomains[0]).to.equal('richaudience.com');
   });
 
   it('no banner media response outstream', function () {

From a42abddc117cdcd3ea272556ffcb929e45b1a1db Mon Sep 17 00:00:00 2001
From: Denis Logachov 
Date: Mon, 18 Nov 2024 17:48:18 +0200
Subject: [PATCH 0678/1097] Adkernel Bid Adapter: add spinx alias (#12460)

---
 modules/adkernelBidAdapter.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 361bdd2d046..34f9c297e12 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -99,7 +99,8 @@ export const spec = {
     {code: 'voisetech'},
     {code: 'global_sun'},
     {code: 'rxnetwork'},
-    {code: 'revbid'}
+    {code: 'revbid'},
+    {code: 'spinx', gvlid: 1308}
   ],
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 

From b67898b9afa6e0b94e9b14227cd9c410ed578efa Mon Sep 17 00:00:00 2001
From: Patrick McCann 
Date: Mon, 18 Nov 2024 13:53:27 -0500
Subject: [PATCH 0679/1097] Media impact and adpartner bid adapters: reduce
 duplication (#12411)

* Create index.js

* Update mediaimpactBidAdapter.js

* Update adpartnerBidAdapter.js

* Update adpartnerBidAdapter_spec.js

* Update mediaimpactBidAdapter_spec.js

* Update adpartnerBidAdapter_spec.js

* Update adpartnerBidAdapter.js

* Update adpartnerBidAdapter.js

* Update mediaimpactBidAdapter.js

* Update index.js

* Update mediaimpactBidAdapter.js

* Update adpartnerBidAdapter.js

* Update adpartnerBidAdapter.js

* Update index.js

* Update mediaimpactBidAdapter.js

* Update mediaimpactBidAdapter_spec.js

* Update mediaimpactBidAdapter.js

* Update mediaimpactBidAdapter_spec.js
---
 libraries/mediaImpactUtils/index.js           |  61 +++++++++
 modules/adpartnerBidAdapter.js                | 114 ++++-------------
 modules/mediaimpactBidAdapter.js              | 117 ++++--------------
 test/spec/modules/adpartnerBidAdapter_spec.js |   5 +-
 .../modules/mediaimpactBidAdapter_spec.js     |   5 +-
 5 files changed, 117 insertions(+), 185 deletions(-)
 create mode 100644 libraries/mediaImpactUtils/index.js

diff --git a/libraries/mediaImpactUtils/index.js b/libraries/mediaImpactUtils/index.js
new file mode 100644
index 00000000000..a56761d8ed4
--- /dev/null
+++ b/libraries/mediaImpactUtils/index.js
@@ -0,0 +1,61 @@
+import { buildUrl } from '../../src/utils.js';
+import { ajax } from '../../src/ajax.js';
+
+/**
+ * Builds the bid requests and beacon parameters.
+ * @param {Array} validBidRequests - The array of valid bid requests.
+ * @param {string} referer - The referer URL.
+ * @returns {Object} - An object containing bidRequests and beaconParams.
+ */
+export function buildBidRequestsAndParams(validBidRequests, referer) {
+  const bidRequests = [];
+  const beaconParams = { tag: [], partner: [], sizes: [], referer: encodeURIComponent(referer) };
+
+  validBidRequests.forEach(function (validBidRequest) {
+    const sizes = validBidRequest.params.sizes || validBidRequest.sizes;
+
+    const bidRequestObject = {
+      adUnitCode: validBidRequest.adUnitCode,
+      sizes: sizes,
+      bidId: validBidRequest.bidId,
+      referer: referer,
+    };
+
+    if (parseInt(validBidRequest.params.unitId)) {
+      bidRequestObject.unitId = parseInt(validBidRequest.params.unitId);
+      beaconParams.tag.push(validBidRequest.params.unitId);
+    }
+
+    if (parseInt(validBidRequest.params.partnerId)) {
+      bidRequestObject.unitId = 0;
+      bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId);
+      beaconParams.partner.push(validBidRequest.params.partnerId);
+    }
+
+    bidRequests.push(bidRequestObject);
+    beaconParams.sizes.push(joinSizesToString(sizes));
+  });
+
+  // Finalize beaconParams
+  if (beaconParams.partner.length > 0) {
+    beaconParams.partner = beaconParams.partner.join(',');
+  } else {
+    delete beaconParams.partner;
+  }
+  beaconParams.tag = beaconParams.tag.join(',');
+  beaconParams.sizes = beaconParams.sizes.join(',');
+
+  return { bidRequests, beaconParams };
+}
+
+export function joinSizesToString(sizes) {
+  return sizes.map(size => size.join('x')).join('|');
+}
+
+export function postRequest(endpoint, data) {
+  ajax(endpoint, null, data, { method: 'POST' });
+}
+
+export function buildEndpointUrl(protocol, hostname, pathname, searchParams) {
+  return buildUrl({ protocol, hostname, pathname, search: searchParams });
+}
diff --git a/modules/adpartnerBidAdapter.js b/modules/adpartnerBidAdapter.js
index 471a0bba64a..504809afa87 100644
--- a/modules/adpartnerBidAdapter.js
+++ b/modules/adpartnerBidAdapter.js
@@ -1,6 +1,5 @@
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import { buildUrl } from '../src/utils.js'
-import {ajax} from '../src/ajax.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { buildBidRequestsAndParams, postRequest, buildEndpointUrl } from '../libraries/mediaImpactUtils/index.js';
 
 const BIDDER_CODE = 'adpartner';
 export const ENDPOINT_PROTOCOL = 'https';
@@ -15,74 +14,25 @@ export const spec = {
   },
 
   buildRequests: function (validBidRequests, bidderRequest) {
-    // TODO does it make sense to fall back to window.location.href?
     const referer = bidderRequest?.refererInfo?.page || window.location.href;
 
-    let bidRequests = [];
-    let beaconParams = {
-      tag: [],
-      partner: [],
-      sizes: [],
-      referer: ''
-    };
-
-    validBidRequests.forEach(function(validBidRequest) {
-      let bidRequestObject = {
-        adUnitCode: validBidRequest.adUnitCode,
-        sizes: validBidRequest.sizes,
-        bidId: validBidRequest.bidId,
-        referer: referer
-      };
-
-      if (parseInt(validBidRequest.params.unitId)) {
-        bidRequestObject.unitId = parseInt(validBidRequest.params.unitId);
-        beaconParams.tag.push(validBidRequest.params.unitId);
-      }
-
-      if (parseInt(validBidRequest.params.partnerId)) {
-        bidRequestObject.unitId = 0;
-        bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId);
-        beaconParams.partner.push(validBidRequest.params.partnerId);
-      }
-
-      bidRequests.push(bidRequestObject);
+    // Use the common function to build bidRequests and beaconParams
+    const { bidRequests, beaconParams } = buildBidRequestsAndParams(validBidRequests, referer);
 
-      beaconParams.sizes.push(spec.joinSizesToString(validBidRequest.sizes));
-      beaconParams.referer = encodeURIComponent(referer);
-    });
-
-    if (beaconParams.partner.length > 0) {
-      beaconParams.partner = beaconParams.partner.join(',');
-    } else {
-      delete beaconParams.partner;
-    }
-
-    beaconParams.tag = beaconParams.tag.join(',');
-    beaconParams.sizes = beaconParams.sizes.join(',');
-
-    let adPartnerRequestUrl = buildUrl({
-      protocol: ENDPOINT_PROTOCOL,
-      hostname: ENDPOINT_DOMAIN,
-      pathname: ENDPOINT_PATH,
-      search: beaconParams
-    });
+    const adPartnerRequestUrl = buildEndpointUrl(
+      ENDPOINT_PROTOCOL,
+      ENDPOINT_DOMAIN,
+      ENDPOINT_PATH,
+      beaconParams
+    );
 
     return {
       method: 'POST',
       url: adPartnerRequestUrl,
-      data: JSON.stringify(bidRequests)
+      data: JSON.stringify(bidRequests),
     };
   },
 
-  joinSizesToString: function(sizes) {
-    let res = [];
-    sizes.forEach(function(size) {
-      res.push(size.join('x'));
-    });
-
-    return res.join('|');
-  },
-
   interpretResponse: function (serverResponse, bidRequest) {
     const validBids = JSON.parse(bidRequest.data);
 
@@ -91,15 +41,12 @@ export const spec = {
     }
 
     return validBids
-      .map(bid => ({
-        bid: bid,
-        ad: serverResponse.body[bid.adUnitCode]
-      }))
+      .map(bid => ({ bid: bid, ad: serverResponse.body[bid.adUnitCode] }))
       .filter(item => item.ad)
       .map(item => spec.adResponse(item.bid, item.ad));
   },
 
-  adResponse: function(bid, ad) {
+  adResponse: function (bid, ad) {
     const bidObject = {
       requestId: bid.bidId,
       ad: ad.ad,
@@ -110,38 +57,30 @@ export const spec = {
       creativeId: ad.creativeId,
       netRevenue: ad.netRevenue,
       currency: ad.currency,
-      winNotification: ad.winNotification
-    }
-
-    bidObject.meta = {};
-    if (ad.adomain && ad.adomain.length > 0) {
-      bidObject.meta.advertiserDomains = ad.adomain;
-    }
+      winNotification: ad.winNotification,
+      meta: ad.adomain && ad.adomain.length > 0 ? { advertiserDomains: ad.adomain } : {},
+    };
 
     return bidObject;
   },
 
-  onBidWon: function(data) {
-    data.winNotification.forEach(function(unitWon) {
-      let adPartnerBidWonUrl = buildUrl({
-        protocol: ENDPOINT_PROTOCOL,
-        hostname: ENDPOINT_DOMAIN,
-        pathname: unitWon.path
-      });
+  onBidWon: function (data) {
+    data.winNotification.forEach(function (unitWon) {
+      const adPartnerBidWonUrl = buildEndpointUrl(
+        ENDPOINT_PROTOCOL,
+        ENDPOINT_DOMAIN,
+        unitWon.path
+      );
 
       if (unitWon.method === 'POST') {
-        spec.postRequest(adPartnerBidWonUrl, JSON.stringify(unitWon.data));
+        postRequest(adPartnerBidWonUrl, JSON.stringify(unitWon.data));
       }
     });
 
     return true;
   },
 
-  postRequest(endpoint, data) {
-    ajax(endpoint, null, data, {method: 'POST'});
-  },
-
-  getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
+  getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) {
     const syncs = [];
 
     if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) {
@@ -205,7 +144,6 @@ export const spec = {
 
     return syncs;
   },
-
-}
+};
 
 registerBidder(spec);
diff --git a/modules/mediaimpactBidAdapter.js b/modules/mediaimpactBidAdapter.js
index d62cb789ea4..a1f04a44142 100644
--- a/modules/mediaimpactBidAdapter.js
+++ b/modules/mediaimpactBidAdapter.js
@@ -1,6 +1,5 @@
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import { buildUrl } from '../src/utils.js'
-import {ajax} from '../src/ajax.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { buildBidRequestsAndParams, postRequest, buildEndpointUrl } from '../libraries/mediaImpactUtils/index.js';
 
 const BIDDER_CODE = 'mediaimpact';
 export const ENDPOINT_PROTOCOL = 'https';
@@ -15,79 +14,25 @@ export const spec = {
   },
 
   buildRequests: function (validBidRequests, bidderRequest) {
-    // TODO does it make sense to fall back to window.location.href?
     const referer = bidderRequest?.refererInfo?.page || window.location.href;
 
-    let bidRequests = [];
-    let beaconParams = {
-      tag: [],
-      partner: [],
-      sizes: [],
-      referer: ''
-    };
-
-    validBidRequests.forEach(function(validBidRequest) {
-      let sizes = validBidRequest.sizes;
-      if (typeof validBidRequest.params.sizes !== 'undefined') {
-        sizes = validBidRequest.params.sizes;
-      }
-
-      let bidRequestObject = {
-        adUnitCode: validBidRequest.adUnitCode,
-        sizes: sizes,
-        bidId: validBidRequest.bidId,
-        referer: referer
-      };
-
-      if (parseInt(validBidRequest.params.unitId)) {
-        bidRequestObject.unitId = parseInt(validBidRequest.params.unitId);
-        beaconParams.tag.push(validBidRequest.params.unitId);
-      }
-
-      if (parseInt(validBidRequest.params.partnerId)) {
-        bidRequestObject.unitId = 0;
-        bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId);
-        beaconParams.partner.push(validBidRequest.params.partnerId);
-      }
-
-      bidRequests.push(bidRequestObject);
-
-      beaconParams.sizes.push(spec.joinSizesToString(sizes));
-      beaconParams.referer = encodeURIComponent(referer);
-    });
-
-    if (beaconParams.partner.length > 0) {
-      beaconParams.partner = beaconParams.partner.join(',');
-    } else {
-      delete beaconParams.partner;
-    }
+    // Use the common function to build bidRequests and beaconParams
+    const { bidRequests, beaconParams } = buildBidRequestsAndParams(validBidRequests, referer);
 
-    beaconParams.tag = beaconParams.tag.join(',');
-    beaconParams.sizes = beaconParams.sizes.join(',');
-
-    let adRequestUrl = buildUrl({
-      protocol: ENDPOINT_PROTOCOL,
-      hostname: ENDPOINT_DOMAIN,
-      pathname: ENDPOINT_PATH,
-      search: beaconParams
-    });
+    const adRequestUrl = buildEndpointUrl(
+      ENDPOINT_PROTOCOL,
+      ENDPOINT_DOMAIN,
+      ENDPOINT_PATH,
+      beaconParams
+    );
 
     return {
       method: 'POST',
       url: adRequestUrl,
-      data: JSON.stringify(bidRequests)
+      data: JSON.stringify(bidRequests),
     };
   },
 
-  joinSizesToString: function(sizes) {
-    let res = [];
-    sizes.forEach(function(size) {
-      res.push(size.join('x'));
-    });
-
-    return res.join('|');
-  },
-
   interpretResponse: function (serverResponse, bidRequest) {
     const validBids = JSON.parse(bidRequest.data);
 
@@ -96,16 +41,13 @@ export const spec = {
     }
 
     return validBids
-      .map(bid => ({
-        bid: bid,
-        ad: serverResponse.body[bid.adUnitCode]
-      }))
+      .map(bid => ({ bid: bid, ad: serverResponse.body[bid.adUnitCode] }))
       .filter(item => item.ad)
       .map(item => spec.adResponse(item.bid, item.ad));
   },
 
-  adResponse: function(bid, ad) {
-    const bidObject = {
+  adResponse: function (bid, ad) {
+    return {
       requestId: bid.bidId,
       ad: ad.ad,
       cpm: ad.cpm,
@@ -116,36 +58,26 @@ export const spec = {
       netRevenue: ad.netRevenue,
       currency: ad.currency,
       winNotification: ad.winNotification,
-      meta: {}
+      meta: ad.meta || {},
     };
-
-    if (ad.meta) {
-      bidObject.meta = ad.meta;
-    }
-
-    return bidObject;
   },
 
-  onBidWon: function(data) {
-    data.winNotification.forEach(function(unitWon) {
-      let adBidWonUrl = buildUrl({
-        protocol: ENDPOINT_PROTOCOL,
-        hostname: ENDPOINT_DOMAIN,
-        pathname: unitWon.path
-      });
+  onBidWon: function (data) {
+    data.winNotification.forEach(function (unitWon) {
+      const adBidWonUrl = buildEndpointUrl(
+        ENDPOINT_PROTOCOL,
+        ENDPOINT_DOMAIN,
+        unitWon.path
+      );
 
       if (unitWon.method === 'POST') {
-        spec.postRequest(adBidWonUrl, JSON.stringify(unitWon.data));
+        postRequest(adBidWonUrl, JSON.stringify(unitWon.data));
       }
     });
 
     return true;
   },
 
-  postRequest(endpoint, data) {
-    ajax(endpoint, null, data, {method: 'POST'});
-  },
-
   getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
     const syncs = [];
 
@@ -210,7 +142,6 @@ export const spec = {
 
     return syncs;
   },
-
-}
+};
 
 registerBidder(spec);
diff --git a/test/spec/modules/adpartnerBidAdapter_spec.js b/test/spec/modules/adpartnerBidAdapter_spec.js
index d9f9b0d0074..597974acce8 100644
--- a/test/spec/modules/adpartnerBidAdapter_spec.js
+++ b/test/spec/modules/adpartnerBidAdapter_spec.js
@@ -1,6 +1,7 @@
 import {expect} from 'chai';
 import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/adpartnerBidAdapter.js';
 import {newBidder} from 'src/adapters/bidderFactory.js';
+import * as miUtils from 'libraries/mediaImpactUtils/index.js';
 
 const BIDDER_CODE = 'adpartner';
 
@@ -117,7 +118,7 @@ describe('AdpartnerAdapter', function () {
 
   describe('joinSizesToString', function () {
     it('success convert sizes list to string', function () {
-      const sizesStr = spec.joinSizesToString([[300, 250], [300, 600]]);
+      const sizesStr = miUtils.joinSizesToString([[300, 250], [300, 600]]);
       expect(sizesStr).to.equal('300x250|300x600');
     });
   });
@@ -238,7 +239,7 @@ describe('AdpartnerAdapter', function () {
     let ajaxStub;
 
     beforeEach(() => {
-      ajaxStub = sinon.stub(spec, 'postRequest')
+      ajaxStub = sinon.stub(miUtils, 'postRequest')
     })
 
     afterEach(() => {
diff --git a/test/spec/modules/mediaimpactBidAdapter_spec.js b/test/spec/modules/mediaimpactBidAdapter_spec.js
index 806f0adeabe..5bf088c0334 100644
--- a/test/spec/modules/mediaimpactBidAdapter_spec.js
+++ b/test/spec/modules/mediaimpactBidAdapter_spec.js
@@ -1,6 +1,7 @@
 import {expect} from 'chai';
 import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/mediaimpactBidAdapter.js';
 import {newBidder} from 'src/adapters/bidderFactory.js';
+import * as miUtils from 'libraries/mediaImpactUtils/index.js';
 
 const BIDDER_CODE = 'mediaimpact';
 
@@ -117,7 +118,7 @@ describe('MediaimpactAdapter', function () {
 
   describe('joinSizesToString', function () {
     it('success convert sizes list to string', function () {
-      const sizesStr = spec.joinSizesToString([[300, 250], [300, 600]]);
+      const sizesStr = miUtils.joinSizesToString([[300, 250], [300, 600]]);
       expect(sizesStr).to.equal('300x250|300x600');
     });
   });
@@ -238,7 +239,7 @@ describe('MediaimpactAdapter', function () {
     let ajaxStub;
 
     beforeEach(() => {
-      ajaxStub = sinon.stub(spec, 'postRequest')
+      ajaxStub = sinon.stub(miUtils, 'postRequest')
     })
 
     afterEach(() => {

From c6bf9cbd0f451cc3330df1d9cbea10312631c6d9 Mon Sep 17 00:00:00 2001
From: Demetrio Girardi 
Date: Mon, 18 Nov 2024 19:59:35 -0800
Subject: [PATCH 0680/1097] gppControl: check for usnat consent version
 (#12469)

---
 libraries/mspa/activityControls.js            |  3 +++
 .../libraries/mspa/activityControls_spec.js   | 20 ++++++++++++-------
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js
index f8a5c7abe9b..eb68259d585 100644
--- a/libraries/mspa/activityControls.js
+++ b/libraries/mspa/activityControls.js
@@ -97,6 +97,9 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat
       if (consent == null) {
         return {allow: false, reason: 'consent data not available'};
       }
+      if (consent.Version !== 1) {
+        return {allow: false, reason: `unsupported consent specification version "${consent.Version}"`}
+      }
       if (denies(consent)) {
         return {allow: false};
       }
diff --git a/test/spec/libraries/mspa/activityControls_spec.js b/test/spec/libraries/mspa/activityControls_spec.js
index 2ec34a3fa43..80d9fc500b1 100644
--- a/test/spec/libraries/mspa/activityControls_spec.js
+++ b/test/spec/libraries/mspa/activityControls_spec.js
@@ -181,13 +181,18 @@ describe('mspaRule', () => {
       expect(mkRule()().allow).to.equal(false);
     });
 
+    it('should deny when consent is using version != 1', () => {
+      consent = {Version: 2};
+      expect(mkRule()().allow).to.equal(false);
+    })
+
     Object.entries({
       'denies': true,
       'allows': false
     }).forEach(([t, denied]) => {
       it(`should check if deny fn ${t}`, () => {
         denies.returns(denied);
-        consent = {mock: 'value'};
+        consent = {mock: 'value', Version: 1};
         const result = mkRule()();
         sinon.assert.calledWith(denies, consent);
         if (denied) {
@@ -212,6 +217,7 @@ describe('setupRules', () => {
       parsedSections: {
         mockApi: [
           {
+            Version: 1,
             mock: 'consent'
           }
         ]
@@ -226,14 +232,14 @@ describe('setupRules', () => {
   it('should use flatten section data for the given api', () => {
     runSetup('mockApi', [1]);
     expect(isAllowed('mockActivity', {})).to.equal(false);
-    sinon.assert.calledWith(rules.mockActivity, {mock: 'consent'})
+    sinon.assert.calledWith(rules.mockActivity, consent.parsedSections.mockApi[0])
   });
 
   it('should accept already flattened section data', () => {
-    consent.parsedSections.mockApi = {flat: 'consent'};
+    consent.parsedSections.mockApi = {flat: 'consent', Version: 1};
     runSetup('mockApi', [1]);
     isAllowed('mockActivity', {});
-    sinon.assert.calledWith(rules.mockActivity, {flat: 'consent'})
+    sinon.assert.calledWith(rules.mockActivity, consent.parsedSections.mockApi)
   })
 
   it('should not choke when no consent data is available', () => {
@@ -248,11 +254,11 @@ describe('setupRules', () => {
   });
 
   it('should pass flattened consent through normalizeConsent', () => {
-    const normalize = sinon.stub().returns({normalized: 'consent'})
+    const normalize = sinon.stub().returns({normalized: 'consent', Version: 1})
     runSetup('mockApi', [1], normalize);
     expect(isAllowed('mockActivity', {})).to.equal(false);
-    sinon.assert.calledWith(normalize, {mock: 'consent'});
-    sinon.assert.calledWith(rules.mockActivity, {normalized: 'consent'});
+    sinon.assert.calledWith(normalize, {mock: 'consent', Version: 1});
+    sinon.assert.calledWith(rules.mockActivity, {normalized: 'consent', Version: 1});
   });
 
   it('should return a function that unregisters activity controls', () => {

From 7c95dc0e74f4505667bb546384c248a55f7a336d Mon Sep 17 00:00:00 2001
From: mkomorski 
Date: Tue, 19 Nov 2024 05:01:24 +0100
Subject: [PATCH 0681/1097] Various adapters: setting imp secure (#12385)

* Various adapters: setting imp secure

* tests

* Default imp.secure to pub-provided imp.secure

* Update common_spec.js

---------

Co-authored-by: Marcin Komorski 
Co-authored-by: Marcin Komorski 
Co-authored-by: Demetrio Girardi 
---
 libraries/ortbConverter/processors/default.js   |  6 ++++++
 modules/adkernelBidAdapter.js                   |  2 +-
 modules/adtrgtmeBidAdapter.js                   |  2 +-
 modules/adxcgBidAdapter.js                      |  2 +-
 modules/asoBidAdapter.js                        |  2 +-
 modules/equativBidAdapter.js                    |  2 +-
 modules/eskimiBidAdapter.js                     |  2 +-
 modules/improvedigitalBidAdapter.js             |  2 +-
 modules/interactiveOffersBidAdapter.js          |  3 +--
 modules/prebidServerBidAdapter/ortbConverter.js |  2 +-
 modules/tappxBidAdapter.js                      |  2 +-
 modules/yahooAdsBidAdapter.js                   |  2 +-
 test/spec/modules/a1MediaBidAdapter_spec.js     |  3 ++-
 test/spec/modules/aidemBidAdapter_spec.js       |  4 ++--
 test/spec/modules/bidmaticBidAdapter_spec.js    |  1 +
 test/spec/modules/dsp_genieeBidAdapter_spec.js  |  3 ++-
 .../modules/improvedigitalBidAdapter_spec.js    |  4 ++--
 test/spec/modules/openxBidAdapter_spec.js       |  3 ++-
 test/spec/modules/smaatoBidAdapter_spec.js      |  1 +
 test/spec/modules/sparteoBidAdapter_spec.js     |  4 ++++
 test/spec/modules/taboolaBidAdapter_spec.js     |  1 +
 test/spec/ortbConverter/common_spec.js          | 17 ++++++++++++++++-
 22 files changed, 50 insertions(+), 20 deletions(-)

diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js
index 9d916b87172..f9c7e3baefc 100644
--- a/libraries/ortbConverter/processors/default.js
+++ b/libraries/ortbConverter/processors/default.js
@@ -61,6 +61,12 @@ export const DEFAULT_PROCESSORS = {
           delete imp.ext?.data?.pbadslot;
         }
       }
+    },
+    secure: {
+      // should set imp.secure to 1 unless publisher has set it
+      fn(imp, bidRequest) {
+        imp.secure = imp.secure ?? 1;
+      }
     }
   },
   [BID_RESPONSE]: {
diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 34f9c297e12..22ff06966dc 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -264,7 +264,7 @@ function buildImps(bidRequest, secure) {
     'tagid': bidRequest.adUnitCode
   };
   if (secure) {
-    imp.secure = 1;
+    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
   }
   var sizes = [];
   let mediaTypes = bidRequest.mediaTypes;
diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js
index 955e908f000..c342c2625be 100644
--- a/modules/adtrgtmeBidAdapter.js
+++ b/modules/adtrgtmeBidAdapter.js
@@ -55,7 +55,7 @@ function extractUserSyncUrls(syncOptions, pixels) {
 }
 
 function isSecure(bid) {
-  return deepAccess(bid, 'params.bidOverride.imp.secure') || (document.location.protocol === 'https:') ? 1 : 0;
+  return deepAccess(bid, 'params.bidOverride.imp.secure') ?? deepAccess(bid, 'ortb2Imp.secure') ?? 1;
 };
 
 function getMediaType(bid) {
diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js
index c6dde0d3f6c..a0e99572809 100644
--- a/modules/adxcgBidAdapter.js
+++ b/modules/adxcgBidAdapter.js
@@ -122,7 +122,7 @@ const converter = ortbConverter({
       };
     }
 
-    imp.secure = Number(window.location.protocol === 'https:');
+    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
 
     if (!imp.bidfloor && bidRequest.params.bidFloor) {
       imp.bidfloor = bidRequest.params.bidFloor;
diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js
index a4a6c78566e..43215c05d5b 100644
--- a/modules/asoBidAdapter.js
+++ b/modules/asoBidAdapter.js
@@ -106,7 +106,7 @@ const converter = ortbConverter({
     const imp = buildImp(bidRequest, context);
 
     imp.tagid = bidRequest.adUnitCode;
-    imp.secure = Number(window.location.protocol === 'https:');
+    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
     return imp;
   },
 
diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js
index c7cb304d22b..7f59e46920e 100644
--- a/modules/equativBidAdapter.js
+++ b/modules/equativBidAdapter.js
@@ -94,7 +94,7 @@ export const converter = ortbConverter({
     delete imp.dt;
 
     imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest);
-    imp.secure = 1;
+    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
     imp.tagid = bidRequest.adUnitCode;
 
     if (siteId || pageId || formatId) {
diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js
index 36feda03ec1..8b1beba09c3 100644
--- a/modules/eskimiBidAdapter.js
+++ b/modules/eskimiBidAdapter.js
@@ -80,7 +80,7 @@ const CONVERTER = ortbConverter({
   },
   imp(buildImp, bidRequest, context) {
     let imp = buildImp(bidRequest, context);
-    imp.secure = Number(window.location.protocol === 'https:');
+    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
     if (!imp.bidfloor && bidRequest.params.bidFloor) {
       imp.bidfloor = bidRequest.params.bidFloor;
       imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD'
diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js
index 158c2bd6c75..33ba08e8fd2 100644
--- a/modules/improvedigitalBidAdapter.js
+++ b/modules/improvedigitalBidAdapter.js
@@ -153,7 +153,7 @@ export const CONVERTER = ortbConverter({
   },
   imp(buildImp, bidRequest, context) {
     const imp = buildImp(bidRequest, context);
-    imp.secure = Number(window.location.protocol === 'https:');
+    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
     if (!imp.bidfloor && bidRequest.params.bidFloor) {
       imp.bidfloor = bidRequest.params.bidFloor;
       imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || DEFAULT_CURRENCY;
diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js
index 8d2f0a7734d..bb27239fef4 100644
--- a/modules/interactiveOffersBidAdapter.js
+++ b/modules/interactiveOffersBidAdapter.js
@@ -79,7 +79,6 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) {
   // TODO: these should probably look at refererInfo
   let pageURL = window.location.href;
   let domain = window.location.hostname;
-  let secure = (window.location.protocol == 'https:' ? 1 : 0);
   let openRTBRequest = deepClone(DEFAULT['OpenRTBBidRequest']);
   openRTBRequest.id = bidderRequest.bidderRequestId;
   openRTBRequest.ext = {
@@ -120,7 +119,7 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) {
     }
     let imp = deepClone(DEFAULT['OpenRTBBidRequestImp']);
     imp.id = bid.bidId;
-    imp.secure = secure;
+    imp.secure = bid.ortb2Imp?.secure ?? 1;
     imp.tagid = bid.adUnitCode;
     imp.ext = {
       rawdata: bid
diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js
index 31ea363c9df..67cc5a372cf 100644
--- a/modules/prebidServerBidAdapter/ortbConverter.js
+++ b/modules/prebidServerBidAdapter/ortbConverter.js
@@ -37,7 +37,7 @@ const PBS_CONVERTER = ortbConverter({
       }
     });
     if (Object.values(SUPPORTED_MEDIA_TYPES).some(mtype => imp[mtype])) {
-      imp.secure = context.s2sBidRequest.s2sConfig.secure;
+      imp.secure = proxyBidRequest.ortb2Imp?.secure ?? 1;
       return imp;
     }
   },
diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js
index 28965cf8ad0..cab3d4f35c2 100644
--- a/modules/tappxBidAdapter.js
+++ b/modules/tappxBidAdapter.js
@@ -369,7 +369,7 @@ function buildOneRequest(validBidRequests, bidderRequest) {
 
   imp.id = validBidRequests.bidId;
   imp.tagid = tagid;
-  imp.secure = 1;
+  imp.secure = validBidRequests.ortb2Imp?.secure ?? 1;
 
   imp.bidfloor = deepAccess(validBidRequests, 'params.bidfloor');
   if (isFn(validBidRequests.getFloor)) {
diff --git a/modules/yahooAdsBidAdapter.js b/modules/yahooAdsBidAdapter.js
index eaffa1f6fae..eec5420ec6c 100644
--- a/modules/yahooAdsBidAdapter.js
+++ b/modules/yahooAdsBidAdapter.js
@@ -150,7 +150,7 @@ function getSupportedEids(bid) {
 }
 
 function isSecure(bid) {
-  return deepAccess(bid, 'params.bidOverride.imp.secure') || (document.location.protocol === 'https:') ? 1 : 0;
+  return deepAccess(bid, 'params.bidOverride.imp.secure') ?? bid.ortb2Imp?.secure ?? 1;
 };
 
 function getPubIdMode(bid) {
diff --git a/test/spec/modules/a1MediaBidAdapter_spec.js b/test/spec/modules/a1MediaBidAdapter_spec.js
index e1db2b9ad8d..8031b584d65 100644
--- a/test/spec/modules/a1MediaBidAdapter_spec.js
+++ b/test/spec/modules/a1MediaBidAdapter_spec.js
@@ -75,7 +75,8 @@ const getConvertedBidReq = () => {
         },
         bidfloor: 0,
         bidfloorcur: 'JPY',
-        id: '2e9f38ea93bb9e'
+        id: '2e9f38ea93bb9e',
+        secure: 1
       }
     ],
     test: 0,
diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js
index c9d29ff09dd..c07b999afe6 100644
--- a/test/spec/modules/aidemBidAdapter_spec.js
+++ b/test/spec/modules/aidemBidAdapter_spec.js
@@ -455,7 +455,7 @@ describe('Aidem adapter', () => {
       expect(data.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length)
 
       expect(data.imp[0]).to.be.a('object').that.has.all.keys(
-        'banner', 'id', 'tagId'
+        'banner', 'id', 'tagId', 'secure'
       )
       expect(data.imp[0].banner).to.be.a('object').that.has.all.keys(
         'format', 'topframe'
@@ -471,7 +471,7 @@ describe('Aidem adapter', () => {
         expect(data.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length)
 
         expect(data.imp[0]).to.be.a('object').that.has.all.keys(
-          'video', 'id', 'tagId'
+          'video', 'id', 'tagId', 'secure'
         )
         expect(data.imp[0].video).to.be.a('object').that.has.all.keys(
           'mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h'
diff --git a/test/spec/modules/bidmaticBidAdapter_spec.js b/test/spec/modules/bidmaticBidAdapter_spec.js
index dcf35d032ea..4b213c6dcc4 100644
--- a/test/spec/modules/bidmaticBidAdapter_spec.js
+++ b/test/spec/modules/bidmaticBidAdapter_spec.js
@@ -3,6 +3,7 @@ import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js';
 import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils';
 
 const expectedImp = {
+  'secure': 1,
   'id': '2eb89f0f062afe',
   'banner': {
     'topframe': 0,
diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js
index 6b2286a5fe5..576fd9e404b 100644
--- a/test/spec/modules/dsp_genieeBidAdapter_spec.js
+++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js
@@ -38,7 +38,8 @@ describe('Geniee adapter tests', () => {
               ext: {
                 test: 1
               },
-              id: 'bid-id'
+              id: 'bid-id',
+              secure: 1
             }
           ],
           test: 1
diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js
index 0c0789ced48..46e07bacbe4 100644
--- a/test/spec/modules/improvedigitalBidAdapter_spec.js
+++ b/test/spec/modules/improvedigitalBidAdapter_spec.js
@@ -238,7 +238,7 @@ describe('Improve Digital Adapter Tests', function () {
       sinon.assert.match(payload.imp, [
         sinon.match({
           id: '33e9500b21129f',
-          secure: 0,
+          secure: 1,
           ext: {
             bidder: {
               placementId: 1053688,
@@ -265,7 +265,7 @@ describe('Improve Digital Adapter Tests', function () {
       sinon.assert.match(payload.imp, [
         sinon.match({
           id: '33e9500b21129f',
-          secure: 0,
+          secure: 1,
           ext: {
             bidder: {
               placementId: 1053688,
diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js
index faaab727b17..5dc60b25ab0 100644
--- a/test/spec/modules/openxBidAdapter_spec.js
+++ b/test/spec/modules/openxBidAdapter_spec.js
@@ -1489,7 +1489,8 @@ describe('OpenxRtbAdapter', function () {
               },
               ext: {
                 divid: 'adunit-code',
-              }
+              },
+              secure: 1
             }
           }
         });
diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js
index 302a5fa1aa6..f9d300d84e4 100644
--- a/test/spec/modules/smaatoBidAdapter_spec.js
+++ b/test/spec/modules/smaatoBidAdapter_spec.js
@@ -174,6 +174,7 @@ describe('smaatoBidAdapterTest', () => {
             id: 'bidId',
             banner: BANNER_OPENRTB_IMP,
             tagid: 'adspaceId',
+            secure: 1
           }
         ]);
       });
diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js
index c83559175e6..ec90d4c7eeb 100644
--- a/test/spec/modules/sparteoBidAdapter_spec.js
+++ b/test/spec/modules/sparteoBidAdapter_spec.js
@@ -59,6 +59,7 @@ const VALID_REQUEST_BANNER = {
   url: REQUEST_URL,
   data: {
     'imp': [{
+      'secure': 1,
       'id': '1a2b3c4d',
       'banner': {
         'format': [{
@@ -96,6 +97,7 @@ const VALID_REQUEST_VIDEO = {
   url: REQUEST_URL,
   data: {
     'imp': [{
+      'secure': 1,
       'id': '5e6f7g8h',
       'video': {
         'w': 640,
@@ -139,6 +141,7 @@ const VALID_REQUEST = {
   url: REQUEST_URL,
   data: {
     'imp': [{
+      'secure': 1,
       'id': '1a2b3c4d',
       'banner': {
         'format': [{
@@ -157,6 +160,7 @@ const VALID_REQUEST = {
         }
       }
     }, {
+      'secure': 1,
       'id': '5e6f7g8h',
       'video': {
         'w': 640,
diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js
index bcf388a67e2..5db0c8cf306 100644
--- a/test/spec/modules/taboolaBidAdapter_spec.js
+++ b/test/spec/modules/taboolaBidAdapter_spec.js
@@ -181,6 +181,7 @@ describe('Taboola Adapter', function () {
       const expectedData = {
         'imp': [{
           'id': res.data.imp[0].id,
+          'secure': 1,
           'banner': {
             format: [{
               w: displayBidRequestParams.sizes[0][0],
diff --git a/test/spec/ortbConverter/common_spec.js b/test/spec/ortbConverter/common_spec.js
index d2d61e6778c..4c2f41749a4 100644
--- a/test/spec/ortbConverter/common_spec.js
+++ b/test/spec/ortbConverter/common_spec.js
@@ -1,5 +1,5 @@
 import {DEFAULT_PROCESSORS} from '../../../libraries/ortbConverter/processors/default.js';
-import {BID_RESPONSE} from '../../../src/pbjsORTB.js';
+import {BID_RESPONSE, IMP} from '../../../src/pbjsORTB.js';
 
 describe('common processors', () => {
   describe('bid response properties', () => {
@@ -26,4 +26,19 @@ describe('common processors', () => {
       })
     })
   })
+  describe('bid imp fpd', () => {
+    const impFpd = DEFAULT_PROCESSORS[IMP].secure.fn;
+
+    it('should set secure as 1 if publisher did not set it', () => {
+      const imp = {};
+      impFpd(imp);
+      expect(imp.secure).to.eql(1);
+    });
+
+    it('should not overwrite secure if set by publisher', () => {
+      const imp = {secure: 0};
+      impFpd(imp);
+      expect(imp.secure).to.eql(0);
+    });
+  })
 })

From 5bc249c5c5151d563bde8d721beb121d7402cff5 Mon Sep 17 00:00:00 2001
From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com>
Date: Tue, 19 Nov 2024 22:35:50 +0200
Subject: [PATCH 0682/1097] Rubicon Bid Adapter: expand fastlane EID protocol
 and pass p_site.mobile; Support device.ip and device.ipv6. (#12471)

* Rubicon Bid Adapter: Remove special source handling, add eid_ for each source, retain ppuid logic; Add ip and ipv6 from device in bidRequest

* Rubicon Bid Adapter: Change check for mobile property in bidRequest

* Rubicon Bid Adapter: Add tests for p_site.mobile property in bidRequest
---
 modules/rubiconBidAdapter.js                |  64 ++--
 test/spec/modules/rubiconBidAdapter_spec.js | 373 ++++++++++----------
 2 files changed, 215 insertions(+), 222 deletions(-)

diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index c62876b9605..c9a81e152b6 100644
--- a/modules/rubiconBidAdapter.js
+++ b/modules/rubiconBidAdapter.js
@@ -3,7 +3,6 @@ import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { config } from '../src/config.js';
 import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
-import { find } from '../src/polyfill.js';
 import { getGlobal } from '../src/prebidGlobal.js';
 import { Renderer } from '../src/Renderer.js';
 import {
@@ -499,6 +498,8 @@ export const spec = {
       'x_imp.ext.tid': bidRequest.ortb2Imp?.ext?.tid,
       'l_pb_bid_id': bidRequest.bidId,
       'o_cdep': bidRequest.ortb2?.device?.ext?.cdep,
+      'ip': bidRequest.ortb2?.device?.ip,
+      'ipv6': bidRequest.ortb2?.device?.ipv6,
       'p_screen_res': _getScreenResolution(),
       'tk_user_key': params.userId,
       'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4),
@@ -542,42 +543,47 @@ export const spec = {
     if (bidRequest?.ortb2Imp?.ext?.ae) {
       data['o_ae'] = 1;
     }
+    // If the bid request contains a 'mobile' property under 'ortb2.site', add it to 'data' as 'p_site.mobile'.
+    if (typeof bidRequest?.ortb2?.site?.mobile === 'number') {
+      data['p_site.mobile'] = bidRequest.ortb2.site.mobile
+    }
 
     addDesiredSegtaxes(bidderRequest, data);
     // loop through userIds and add to request
-    if (bidRequest.userIdAsEids) {
-      bidRequest.userIdAsEids.forEach(eid => {
+    if (bidRequest?.ortb2?.user?.ext?.eids) {
+      bidRequest.ortb2.user.ext.eids.forEach(({ source, uids = [], inserter, matcher, mm, ext = {} }) => {
         try {
-          // special cases
-          if (eid.source === 'adserver.org') {
-            data['tpid_tdid'] = eid.uids[0].id;
-            data['eid_adserver.org'] = eid.uids[0].id;
-          } else if (eid.source === 'liveintent.com') {
-            data['tpid_liveintent.com'] = eid.uids[0].id;
-            data['eid_liveintent.com'] = eid.uids[0].id;
-            if (eid.ext && Array.isArray(eid.ext.segments) && eid.ext.segments.length) {
-              data['tg_v.LIseg'] = eid.ext.segments.join(',');
-            }
-          } else if (eid.source === 'liveramp.com') {
-            data['x_liverampidl'] = eid.uids[0].id;
-          } else if (eid.source === 'id5-sync.com') {
-            data['eid_id5-sync.com'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.linkType) || ''}`;
-          } else {
-            // add anything else with this generic format
-            // if rubicon drop ^
-            const id = eid.source === 'rubiconproject.com' ? eid.uids[0].id : `${eid.uids[0].id}^${eid.uids[0].atype || ''}`
-            data[`eid_${eid.source}`] = id;
-          }
-          // send AE "ppuid" signal if exists, and hasn't already been sent
+          // Ensure there is at least one valid UID in the 'uids' array
+          const uidData = uids[0];
+          if (!uidData) return; // Skip processing if no valid UID exists
+
+          // Function to build the EID value in the required format
+          const buildEidValue = (uidData) => [
+            uidData.id, // uid: The user ID
+            uidData.atype || '',
+            '', // third: Always empty, as specified in the requirement
+            inserter || '',
+            matcher || '',
+            mm || '',
+            uidData?.ext?.rtipartner || ''
+          ].join('^'); // Return a single string formatted with '^' delimiter
+
+          const eidValue = buildEidValue(uidData); // Build the EID value string
+
+          // Store the constructed EID value for the given source
+          data[`eid_${source}`] = eidValue;
+
+          // Handle the "ppuid" signal, ensuring it is set only once
           if (!data['ppuid']) {
-            // get the first eid.uids[*].ext.stype === 'ppuid', if one exists
-            const ppId = find(eid.uids, uid => uid.ext && uid.ext.stype === 'ppuid');
-            if (ppId && ppId.id) {
-              data['ppuid'] = ppId.id;
+            // Search for a UID with the 'stype' field equal to 'ppuid' in its extension
+            const ppId = uids.find(uid => uid.ext?.stype === 'ppuid');
+            if (ppId?.id) {
+              data['ppuid'] = ppId.id; // Store the ppuid if found
             }
           }
         } catch (e) {
-          logWarn('Rubicon: error reading eid:', eid, e);
+          // Log any errors encountered during processing
+          logWarn('Rubicon: error reading eid:', { source, uids }, e);
         }
       });
     }
diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js
index 5be3040ddd4..6cfdb664d27 100644
--- a/test/spec/modules/rubiconBidAdapter_spec.js
+++ b/test/spec/modules/rubiconBidAdapter_spec.js
@@ -710,6 +710,28 @@ describe('the rubicon adapter', function () {
           expect(data.get('o_cdep')).to.equal('3');
         });
 
+        it('should correctly send ip signal when ortb2.device.ip is provided', () => {
+          const ipRequest = utils.deepClone(bidderRequest);
+          ipRequest.bids[0].ortb2 = { device: { ip: '123.45.67.89' } };
+
+          let [request] = spec.buildRequests(ipRequest.bids, ipRequest);
+          let data = new URLSearchParams(request.data);
+
+          // Verify if 'ip' is correctly added to the request data
+          expect(data.get('ip')).to.equal('123.45.67.89');
+        });
+
+        it('should correctly send ipv6 signal when ortb2.device.ipv6 is provided', () => {
+          const ipv6Request = utils.deepClone(bidderRequest);
+          ipv6Request.bids[0].ortb2 = { device: { ipv6: '2001:db8::ff00:42:8329' } };
+
+          let [request] = spec.buildRequests(ipv6Request.bids, ipv6Request);
+          let data = new URLSearchParams(request.data);
+
+          // Verify if 'ipv6' is correctly added to the request data
+          expect(data.get('ipv6')).to.equal('2001:db8::ff00:42:8329');
+        });
+
         it('ad engine query params should be ordered correctly', function () {
           sandbox.stub(Math, 'random').callsFake(() => 0.1);
           let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest);
@@ -1332,173 +1354,60 @@ describe('the rubicon adapter', function () {
         });
 
         describe('user id config', function () {
-          it('should send tpid_tdid when userIdAsEids contains unifiedId', function () {
-            const clonedBid = utils.deepClone(bidderRequest.bids[0]);
-            clonedBid.userId = {
-              tdid: 'abcd-efgh-ijkl-mnop-1234'
-            };
-            clonedBid.userIdAsEids = [
-              {
-                'source': 'adserver.org',
-                'uids': [
-                  {
-                    'id': 'abcd-efgh-ijkl-mnop-1234',
-                    'atype': 1,
-                    'ext': {
-                      'rtiPartner': 'TDID'
-                    }
-                  }
-                ]
-              }
-            ];
-            let [request] = spec.buildRequests([clonedBid], bidderRequest);
-            let data = new URLSearchParams(request.data);
-
-            expect(data.get('tpid_tdid')).to.equal('abcd-efgh-ijkl-mnop-1234');
-            expect(data.get('eid_adserver.org')).to.equal('abcd-efgh-ijkl-mnop-1234');
-          });
-
-          describe('LiveIntent support', function () {
-            it('should send tpid_liveintent.com when userIdAsEids contains liveintentId', function () {
-              const clonedBid = utils.deepClone(bidderRequest.bids[0]);
-              clonedBid.userId = {
-                lipb: {
-                  lipbid: '0000-1111-2222-3333',
-                  segments: ['segA', 'segB']
-                }
-              };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'liveintent.com',
-                  'uids': [
-                    {
-                      'id': '0000-1111-2222-3333',
-                      'atype': 3
-                    }
-                  ],
-                  'ext': {
-                    'segments': [
-                      'segA',
-                      'segB'
-                    ]
-                  }
-                }
-              ];
-              let [request] = spec.buildRequests([clonedBid], bidderRequest);
-              let data = new URLSearchParams(request.data);
-
-              expect(data.get('tpid_liveintent.com')).to.equal('0000-1111-2222-3333');
-              expect(data.get('eid_liveintent.com')).to.equal('0000-1111-2222-3333');
-              expect(data.get('tg_v.LIseg')).to.equal('segA,segB');
-            });
-
-            it('should send tg_v.LIseg when userIdAsEids contains liveintentId with ext.segments as array', function () {
-              const clonedBid = utils.deepClone(bidderRequest.bids[0]);
-              clonedBid.userId = {
-                lipb: {
-                  lipbid: '1111-2222-3333-4444',
-                  segments: ['segD', 'segE']
-                }
-              };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'liveintent.com',
-                  'uids': [
-                    {
-                      'id': '1111-2222-3333-4444',
-                      'atype': 3
-                    }
-                  ],
-                  'ext': {
-                    'segments': [
-                      'segD',
-                      'segE'
-                    ]
-                  }
-                }
-              ]
-              let [request] = spec.buildRequests([clonedBid], bidderRequest);
-              const unescapedData = unescape(request.data);
-
-              expect(unescapedData.indexOf('&tpid_liveintent.com=1111-2222-3333-4444&') !== -1).to.equal(true);
-              expect(unescapedData.indexOf('&tg_v.LIseg=segD,segE&') !== -1).to.equal(true);
-            });
-          });
-
-          describe('LiveRamp support', function () {
-            it('should send x_liverampidl when userIdAsEids contains liverampId', function () {
-              const clonedBid = utils.deepClone(bidderRequest.bids[0]);
-              clonedBid.userId = {
-                idl_env: '1111-2222-3333-4444'
-              };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'liveramp.com',
-                  'uids': [
-                    {
-                      'id': '1111-2222-3333-4444',
-                      'atype': 3
-                    }
-                  ]
-                }
-              ]
-              let [request] = spec.buildRequests([clonedBid], bidderRequest);
-              let data = new URLSearchParams(request.data);
-
-              expect(data.get('x_liverampidl')).to.equal('1111-2222-3333-4444');
-            });
-          });
-
           describe('pubcid support', function () {
-            it('should send eid_pubcid.org when userIdAsEids contains pubcid', function () {
+            it('should send eid_pubcid.org when ortb2.user.ext.eids contains pubcid', function () {
               const clonedBid = utils.deepClone(bidderRequest.bids[0]);
               clonedBid.userId = {
                 pubcid: '1111'
               };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'pubcid.org',
-                  'uids': [
-                    {
-                      'id': '1111',
-                      'atype': 1
-                    }
-                  ]
+              clonedBid.ortb2 = {
+                user: {
+                  ext: {
+                    eids: [{
+                      'source': 'pubcid.org',
+                      'uids': [{
+                        'id': '1111',
+                        'atype': 1
+                      }]
+                    }]
+                  }
                 }
-              ]
+              };
               let [request] = spec.buildRequests([clonedBid], bidderRequest);
               let data = new URLSearchParams(request.data);
 
-              expect(data.get('eid_pubcid.org')).to.equal('1111^1');
+              expect(data.get('eid_pubcid.org')).to.equal('1111^1^^^^^');
             });
           });
 
           describe('Criteo support', function () {
-            it('should send eid_criteo.com when userIdAsEids contains criteo', function () {
+            it('should send eid_criteo.com when ortb2.user.ext.eids contains criteo', function () {
               const clonedBid = utils.deepClone(bidderRequest.bids[0]);
               clonedBid.userId = {
                 criteoId: '1111'
               };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'criteo.com',
-                  'uids': [
-                    {
-                      'id': '1111',
-                      'atype': 1
-                    }
-                  ]
+              clonedBid.ortb2 = {
+                user: {
+                  ext: {
+                    eids: [{
+                      'source': 'criteo.com',
+                      'uids': [{
+                        'id': '1111',
+                        'atype': 1
+                      }]
+                    }]
+                  }
                 }
-              ]
+              };
               let [request] = spec.buildRequests([clonedBid], bidderRequest);
               let data = new URLSearchParams(request.data);
 
-              expect(data.get('eid_criteo.com')).to.equal('1111^1');
+              expect(data.get('eid_criteo.com')).to.equal('1111^1^^^^^');
             });
           });
 
           describe('pubProvidedId support', function () {
-            it('should send pubProvidedId when userIdAsEids contains pubProvidedId ids', function () {
+            it('should send pubProvidedId when ortb2.user.ext.eids contains pubProvidedId ids', function () {
               const clonedBid = utils.deepClone(bidderRequest.bids[0]);
               clonedBid.userId = {
                 pubProvidedId: [{
@@ -1516,27 +1425,27 @@ describe('the rubicon adapter', function () {
                   }]
                 }]
               };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'example.com',
-                  'uids': [
-                    {
-                      'id': '11111',
-                      'ext': {
-                        'stype': 'ppuid'
-                      }
-                    }
-                  ]
-                },
-                {
-                  'source': 'id-partner.com',
-                  'uids': [
+              clonedBid.ortb2 = {
+                user: {
+                  ext: {
+                    eids: [{
+                      'source': 'example.com',
+                      'uids': [{
+                        'id': '11111',
+                        'ext': {
+                          'stype': 'ppuid'
+                        }
+                      }]
+                    },
                     {
-                      'id': '222222'
-                    }
-                  ]
+                      'source': 'id-partner.com',
+                      'uids': [{
+                        'id': '222222'
+                      }]
+                    }]
+                  }
                 }
-              ];
+              };
               let [request] = spec.buildRequests([clonedBid], bidderRequest);
               let data = new URLSearchParams(request.data);
 
@@ -1545,7 +1454,7 @@ describe('the rubicon adapter', function () {
           });
 
           describe('ID5 support', function () {
-            it('should send ID5 id when userIdAsEids contains ID5', function () {
+            it('should send ID5 id when ortb2.user.ext.eids contains ID5', function () {
               const clonedBid = utils.deepClone(bidderRequest.bids[0]);
               clonedBid.userId = {
                 id5id: {
@@ -1555,24 +1464,26 @@ describe('the rubicon adapter', function () {
                   }
                 }
               };
-              clonedBid.userIdAsEids = [
-                {
-                  'source': 'id5-sync.com',
-                  'uids': [
-                    {
-                      'id': '11111',
-                      'atype': 1,
-                      'ext': {
-                        'linkType': '22222'
-                      }
-                    }
-                  ]
+              clonedBid.ortb2 = {
+                user: {
+                  ext: {
+                    eids: [{
+                      'source': 'id5-sync.com',
+                      'uids': [{
+                        'id': '11111',
+                        'atype': 1,
+                        'ext': {
+                          'linkType': '22222'
+                        }
+                      }]
+                    }]
+                  }
                 }
-              ];
+              };
               let [request] = spec.buildRequests([clonedBid], bidderRequest);
               let data = new URLSearchParams(request.data);
 
-              expect(data.get('eid_id5-sync.com')).to.equal('11111^1^22222');
+              expect(data.get('eid_id5-sync.com')).to.equal('11111^1^^^^^');
             });
           });
 
@@ -1580,36 +1491,82 @@ describe('the rubicon adapter', function () {
             it('should send user id with generic format', function () {
               const clonedBid = utils.deepClone(bidderRequest.bids[0]);
               // Hardcoding userIdAsEids since createEidsArray returns empty array if source not found in eids.js
-              clonedBid.userIdAsEids = [{
-                source: 'catchall',
-                uids: [{
-                  id: '11111',
-                  atype: 2
-                }]
-              }]
+              clonedBid.ortb2 = {
+                user: {
+                  ext: {
+                    eids: [{
+                      'source': 'catchall',
+                      'uids': [{
+                        'id': '11111',
+                        'atype': 2
+                      }]
+                    }]
+                  }
+                }
+              };
               let [request] = spec.buildRequests([clonedBid], bidderRequest);
               let data = new URLSearchParams(request.data);
 
-              expect(data.get('eid_catchall')).to.equal('11111^2');
+              expect(data.get('eid_catchall')).to.equal('11111^2^^^^^');
             });
 
             it('should send rubiconproject special case', function () {
               const clonedBid = utils.deepClone(bidderRequest.bids[0]);
               // Hardcoding userIdAsEids since createEidsArray returns empty array if source not found in eids.js
-              clonedBid.userIdAsEids = [{
-                source: 'rubiconproject.com',
-                uids: [{
-                  id: 'some-cool-id',
-                  atype: 3
-                }]
-              }]
+              clonedBid.ortb2 = {
+                user: {
+                  ext: {
+                    eids: [{
+                      source: 'rubiconproject.com',
+                      uids: [{
+                        id: 'some-cool-id',
+                        atype: 3
+                      }]
+                    }]
+                  }
+                }
+              };
               let [request] = spec.buildRequests([clonedBid], bidderRequest);
               let data = new URLSearchParams(request.data);
 
-              expect(data.get('eid_rubiconproject.com')).to.equal('some-cool-id');
+              expect(data.get('eid_rubiconproject.com')).to.equal('some-cool-id^3^^^^^');
             });
-          });
+            describe('Full eidValue format validation', function () {
+              it('should send complete eidValue in the format uid^atype^third^inserter^matcher^mm^rtipartner', function () {
+                const clonedBid = utils.deepClone(bidderRequest.bids[0]);
+                // Simulating a full EID object with multiple fields
+                clonedBid.ortb2 = {
+                  user: {
+                    ext: {
+                      eids: [{
+                        source: 'example.com',
+                        uids: [{
+                          id: '11111', // UID
+                          atype: 2, // atype
+                          ext: {
+                            rtipartner: 'rtipartner123', // rtipartner
+                            stype: 'ppuid' // stype
+                          }
+                        }],
+                        inserter: 'inserter123', // inserter
+                        matcher: 'matcher123', // matcher
+                        mm: 'mm123' // mm
+                      }]
+                    }
+                  }
+                };
 
+                let [request] = spec.buildRequests([clonedBid], bidderRequest);
+                let data = new URLSearchParams(request.data);
+
+                // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner
+                const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123';
+
+                // Check if the generated EID value matches the expected format
+                expect(data.get('eid_example.com')).to.equal(expectedEidValue);
+              });
+            });
+          });
           describe('Config user.id support', function () {
             it('should send ppuid when config defines user.id', function () {
               config.setConfig({user: {id: '123'}});
@@ -2871,6 +2828,36 @@ describe('the rubicon adapter', function () {
           expect(slotParams['tg_i.tax10']).is.equal('2,3');
           expect(slotParams['tg_v.tax404']).is.equal(undefined);
         });
+
+        it('should add p_site.mobile if mobile is a number in ortb2.site', function () {
+          // Set up a bidRequest with mobile property as a number
+          const localBidderRequest = Object.assign({}, bidderRequest);
+          localBidderRequest.bids[0].ortb2 = {
+            site: {
+              mobile: 1 // Valid mobile value (number)
+            }
+          };
+
+          // Call the function
+          const slotParams = spec.createSlotParams(localBidderRequest.bids[0], localBidderRequest);
+          // Check that p_site.mobile was added to the slotParams with the correct value
+          expect(slotParams['p_site.mobile']).to.equal(1);
+        });
+        it('should not add p_site.mobile if mobile is not a number in ortb2.site', function () {
+          // Set up a bidRequest with mobile property as a string (invalid value)
+          const localBidderRequest = Object.assign({}, bidderRequest);
+          localBidderRequest.bids[0].ortb2 = {
+            site: {
+              mobile: 'not-a-number' // Invalid mobile value (string)
+            }
+          };
+
+          // Call the function
+          const slotParams = spec.createSlotParams(localBidderRequest.bids[0], localBidderRequest);
+
+          // Check that p_site.mobile is not added to the slotParams
+          expect(slotParams['p_site.mobile']).to.be.undefined;
+        });
       });
 
       describe('classifiedAsVideo', function () {

From de49e52e328de3c4ec747957f1c41556e53de058 Mon Sep 17 00:00:00 2001
From: "Md. Soman Mia Sarker" 
Date: Wed, 20 Nov 2024 21:35:31 +0600
Subject: [PATCH 0683/1097] Video Support (#12457)

---
 modules/adgridBidAdapter.js                | 22 ++++++++++++---
 test/spec/modules/adgridBidAdapter_spec.js | 33 +++++++++++++++++++++-
 2 files changed, 50 insertions(+), 5 deletions(-)

diff --git a/modules/adgridBidAdapter.js b/modules/adgridBidAdapter.js
index 17156280c0d..71c55a54395 100644
--- a/modules/adgridBidAdapter.js
+++ b/modules/adgridBidAdapter.js
@@ -1,14 +1,14 @@
 import { _each, isEmpty, deepAccess } from '../src/utils.js';
 import { config } from '../src/config.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER } from '../src/mediaTypes.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
 
 const BIDDER = Object.freeze({
   CODE: 'adgrid',
   HOST: 'https://api-prebid.adgrid.io',
   REQUEST_METHOD: 'POST',
   REQUEST_ENDPOINT: '/api/v1/auction',
-  SUPPORTED_MEDIA_TYPES: [BANNER],
+  SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO],
 });
 
 const CURRENCY = Object.freeze({
@@ -132,10 +132,19 @@ function interpretResponse(response, bidRequest) {
       creativeId: adUnit.creativeId,
       netRevenue: true,
       currency: adUnit.currency || bidRequest.currency,
-      mediaType: adUnit.mediaType,
-      ad: adUnit.ad,
+      mediaType: adUnit.mediaType
     };
 
+    if (adUnit.mediaType == 'video') {
+      if (adUnit.admUrl) {
+        bidResponse.vastUrl = adUnit.admUrl;
+      } else {
+        bidResponse.vastXml = adUnit.adm;
+      }
+    } else {
+      bidResponse.ad = adUnit.ad;
+    }
+
     bidResponses.push(bidResponse);
   });
 
@@ -158,6 +167,11 @@ function getBidData(bid) {
     if (bid.mediaTypes.banner != null) {
       bidData.mediaType = 'banner';
       bidData.sizes = bid.mediaTypes.banner.sizes;
+    } else if (bid.mediaTypes.video != null) {
+      bidData.mediaType = 'video';
+      bidData.sizes = bid.mediaTypes.video.playerSize;
+      bidData.videoData = bid.mediaTypes.video;
+      bidData.videoParams = bid.params.video;
     }
   }
 
diff --git a/test/spec/modules/adgridBidAdapter_spec.js b/test/spec/modules/adgridBidAdapter_spec.js
index f7da18403fd..0d7ad9c245d 100644
--- a/test/spec/modules/adgridBidAdapter_spec.js
+++ b/test/spec/modules/adgridBidAdapter_spec.js
@@ -20,6 +20,22 @@ describe('AdGrid Bid Adapter', function () {
     }
   }];
 
+  const videoRequest = [{
+    bidId: 123456,
+    auctionId: 98765,
+    mediaTypes: {
+      video: {
+        playerSize: [
+          [640, 480]
+        ],
+        context: 'instream'
+      }
+    },
+    params: {
+      domainId: 12345
+    }
+  }];
+
   describe('isBidRequestValid', function () {
     it('Should return true when domainId exist inside params object', function () {
       const isBidValid = spec.isBidRequestValid(bannerRequest[0]);
@@ -34,6 +50,7 @@ describe('AdGrid Bid Adapter', function () {
 
   describe('buildRequests', function () {
     const request = spec.buildRequests(bannerRequest, bannerRequest[0]);
+    const requestVideo = spec.buildRequests(videoRequest, videoRequest[0]);
     const payload = request.data;
     const apiURL = request.url;
     const method = request.method;
@@ -50,9 +67,23 @@ describe('AdGrid Bid Adapter', function () {
       expect(apiURL).to.equal(globalConfig.endPoint);
     });
 
-    it('Test the API Method', function () {
+    it('should send the correct method', function () {
       expect(method).to.equal(globalConfig.method);
     });
+
+    it('should send the correct requestId', function () {
+      expect(request.data.bids[0].requestId).to.equal(bannerRequest[0].bidId);
+      expect(requestVideo.data.bids[0].requestId).to.equal(videoRequest[0].bidId);
+    });
+
+    it('should send the correct sizes array', function () {
+      expect(request.data.bids[0].sizes).to.be.an('array');
+    });
+
+    it('should send the correct media type', function () {
+      expect(request.data.bids[0].mediaType).to.equal('banner')
+      expect(requestVideo.data.bids[0].mediaType).to.equal('video')
+    });
   });
 
   describe('interpretResponse', function () {

From 24cf886d860d3bbeb32bbf89532004f9903e7354 Mon Sep 17 00:00:00 2001
From: Harry King-Riches <109534328+harrykingriches@users.noreply.github.com>
Date: Wed, 20 Nov 2024 15:49:40 +0000
Subject: [PATCH 0684/1097] FIX: Update adUnit attachment to use adUnitCode
 selector in rubiconBidAdapter (#12462)

---
 modules/rubiconBidAdapter.js                | 2 +-
 test/spec/modules/rubiconBidAdapter_spec.js | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index c9a81e152b6..7bcc7bbd69b 100644
--- a/modules/rubiconBidAdapter.js
+++ b/modules/rubiconBidAdapter.js
@@ -846,7 +846,7 @@ function renderBid(bid) {
       height: bid.height,
       vastUrl: bid.vastUrl,
       placement: {
-        attachTo: adUnitElement,
+        attachTo: `#${bid.adUnitCode}`,
         align: config.align,
         position: config.position
       },
diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js
index 6cfdb664d27..aff6c6df171 100644
--- a/test/spec/modules/rubiconBidAdapter_spec.js
+++ b/test/spec/modules/rubiconBidAdapter_spec.js
@@ -4197,6 +4197,7 @@ describe('the rubicon adapter', function () {
             const bid = bids[0];
             bid.adUnitCode = 'outstream_video1_placement';
             const adUnit = document.createElement('div');
+            const adUnitSelector = `#${bid.adUnitCode}`
             adUnit.id = bid.adUnitCode;
             document.body.appendChild(adUnit);
 
@@ -4210,7 +4211,7 @@ describe('the rubicon adapter', function () {
               label: undefined,
               placement: {
                 align: 'left',
-                attachTo: adUnit,
+                attachTo: adUnitSelector,
                 position: 'append',
               },
               vastUrl: 'https://test.com/vast.xml',
@@ -4266,6 +4267,7 @@ describe('the rubicon adapter', function () {
             const bid = bids[0];
             bid.adUnitCode = 'outstream_video1_placement';
             const adUnit = document.createElement('div');
+            const adUnitSelector = `#${bid.adUnitCode}`
             adUnit.id = bid.adUnitCode;
             document.body.appendChild(adUnit);
 
@@ -4279,7 +4281,7 @@ describe('the rubicon adapter', function () {
               label: undefined,
               placement: {
                 align: 'left',
-                attachTo: adUnit,
+                attachTo: adUnitSelector,
                 position: 'append',
               },
               vastUrl: 'https://test.com/vast.xml',

From 09f963212823052de9f0857eb2935bee8c43f30a Mon Sep 17 00:00:00 2001
From: Sebastien Boisvert 
Date: Wed, 20 Nov 2024 10:54:20 -0500
Subject: [PATCH 0685/1097] contxtfulBidAdapter: revamp the sampling of events
 (#12466)

---
 modules/contxtfulBidAdapter.js                | 27 +++++++--------
 test/spec/modules/contxtfulBidAdapter_spec.js | 34 ++++++-------------
 2 files changed, 24 insertions(+), 37 deletions(-)

diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js
index 7f1a8702a3b..acd9811b871 100644
--- a/modules/contxtfulBidAdapter.js
+++ b/modules/contxtfulBidAdapter.js
@@ -151,11 +151,7 @@ const getSamplingRate = (bidderConfig, eventType) => {
 };
 
 // Handles the logging of events
-const logEvent = (eventType, data, options = {}) => {
-  const {
-    samplingEnabled = false,
-  } = options;
-
+const logEvent = (eventType, data) => {
   try {
     // Log event
     logInfo(BIDDER_CODE, `[${eventType}] ${JSON.stringify(data)}`);
@@ -165,12 +161,15 @@ const logEvent = (eventType, data, options = {}) => {
     const {version, customer} = extractParameters(bidderConfig);
 
     // Sampled monitoring
-    if (samplingEnabled) {
-      const shouldSampleDice = Math.random();
+    if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) {
+      const randomNumber = Math.random();
       const samplingRate = getSamplingRate(bidderConfig, eventType);
-      if (shouldSampleDice >= samplingRate) {
+      if (randomNumber >= samplingRate) {
         return; // Don't sample
       }
+    } else if (!['onTimeout', 'onBidderError', 'onBidWon'].includes(eventType)) {
+      // Unsupported event type.
+      return;
     }
 
     const payload = { type: eventType, data };
@@ -206,12 +205,12 @@ export const spec = {
   buildRequests,
   interpretResponse,
   getUserSyncs,
-  onBidWon: function(bid, options) { logEvent('onBidWon', bid, { samplingEnabled: false, ...options }); },
-  onBidBillable: function(bid, options) { logEvent('onBidBillable', bid, { samplingEnabled: false, ...options }); },
-  onAdRenderSucceeded: function(bid, options) { logEvent('onAdRenderSucceeded', bid, { samplingEnabled: false, ...options }); },
-  onSetTargeting: function(bid, options) { },
-  onTimeout: function(timeoutData, options) { logEvent('onTimeout', timeoutData, { samplingEnabled: true, ...options }); },
-  onBidderError: function(args, options) { logEvent('onBidderError', args, { samplingEnabled: true, ...options }); },
+  onBidWon: function(bid) { logEvent('onBidWon', bid); },
+  onBidBillable: function(bid) { logEvent('onBidBillable', bid); },
+  onAdRenderSucceeded: function(bid) { logEvent('onAdRenderSucceeded', bid); },
+  onSetTargeting: function(bid) { },
+  onTimeout: function(timeoutData) { logEvent('onTimeout', timeoutData); },
+  onBidderError: function({ error, bidderRequest }) { logEvent('onBidderError', { error, bidderRequest }); },
 };
 
 registerBidder(spec);
diff --git a/test/spec/modules/contxtfulBidAdapter_spec.js b/test/spec/modules/contxtfulBidAdapter_spec.js
index 02cb3ccef8a..14b4b94f062 100644
--- a/test/spec/modules/contxtfulBidAdapter_spec.js
+++ b/test/spec/modules/contxtfulBidAdapter_spec.js
@@ -400,22 +400,10 @@ describe('contxtful bid adapter', function () {
       expect(userSyncs2).to.have.lengthOf(0);
     });
 
-    describe('on timeout callback', () => {
-      it('will never call server if sampling is 0 with sendBeacon available', () => {
+    describe('onTimeout callback', () => {
+      it('will always call server with sendBeacon available', () => {
         config.setConfig({
-          contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 0.0}},
-        });
-
-        const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true);
-        const ajaxStub = sandbox.stub(ajax, 'ajax');
-        expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw;
-        expect(beaconStub.called).to.be.false;
-        expect(ajaxStub.called).to.be.false;
-      });
-
-      it('will always call server if sampling is 1 with sendBeacon available', () => {
-        config.setConfig({
-          contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 1.0}},
+          contxtful: {customer: CUSTOMER, version: VERSION},
         });
 
         const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true);
@@ -425,9 +413,9 @@ describe('contxtful bid adapter', function () {
         expect(ajaxStub.called).to.be.false;
       });
 
-      it('will always call server if sampling is 1 with sendBeacon not available', () => {
+      it('will always call server with sendBeacon not available', () => {
         config.setConfig({
-          contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 1.0}},
+          contxtful: {customer: CUSTOMER, version: VERSION},
         });
 
         const ajaxStub = sandbox.stub(ajax, 'ajax');
@@ -440,9 +428,9 @@ describe('contxtful bid adapter', function () {
     });
 
     describe('on onBidderError callback', () => {
-      it('will always call server if sampling is 1', () => {
+      it('will always call server', () => {
         config.setConfig({
-          contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onBidderError': 1.0}},
+          contxtful: {customer: CUSTOMER, version: VERSION},
         });
 
         const ajaxStub = sandbox.stub(ajax, 'ajax');
@@ -468,9 +456,9 @@ describe('contxtful bid adapter', function () {
     });
 
     describe('on onBidBillable callback', () => {
-      it('will always call server', () => {
+      it('will always call server when sampling rate is configured to be 1.0', () => {
         config.setConfig({
-          contxtful: {customer: CUSTOMER, version: VERSION},
+          contxtful: {customer: CUSTOMER, version: VERSION, sampling: {onBidBillable: 1.0}},
         });
         const ajaxStub = sandbox.stub(ajax, 'ajax');
         const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);
@@ -481,9 +469,9 @@ describe('contxtful bid adapter', function () {
     });
 
     describe('on onAdRenderSucceeded callback', () => {
-      it('will always call server', () => {
+      it('will always call server when sampling rate is configured to be 1.0', () => {
         config.setConfig({
-          contxtful: {customer: CUSTOMER, version: VERSION},
+          contxtful: {customer: CUSTOMER, version: VERSION, sampling: {onAdRenderSucceeded: 1.0}},
         });
         const ajaxStub = sandbox.stub(ajax, 'ajax');
         const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);

From 6a76566c4cd5189330b41ac16c4eb4a5c5f068c6 Mon Sep 17 00:00:00 2001
From: Chris Huie 
Date: Wed, 20 Nov 2024 12:15:58 -0700
Subject: [PATCH 0686/1097] Update adverxoBidAdapter_spec.js (#12478)

---
 test/spec/modules/adverxoBidAdapter_spec.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/spec/modules/adverxoBidAdapter_spec.js b/test/spec/modules/adverxoBidAdapter_spec.js
index 17ea33f8e67..e3b98d49f07 100644
--- a/test/spec/modules/adverxoBidAdapter_spec.js
+++ b/test/spec/modules/adverxoBidAdapter_spec.js
@@ -317,6 +317,7 @@ describe('Adverxo Bid Adapter', () => {
 
         expect(ortbRequest.imp[0]).to.deep.equal({
           id: 'bid-video',
+          secure: 1,
           video: {
             w: 400,
             h: 300,

From b05916d5ba16864cc19be6c3313bab28d2499120 Mon Sep 17 00:00:00 2001
From: joseluis laso 
Date: Wed, 20 Nov 2024 21:55:20 +0100
Subject: [PATCH 0687/1097] Hadron RTD : cleaning things up (#12480)

* Cleaning things up in HadronRTD module

* solving lint errors
---
 modules/hadronRtdProvider.js | 113 +++++------------------------------
 1 file changed, 16 insertions(+), 97 deletions(-)

diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js
index d85f049c2de..56f4861b41b 100644
--- a/modules/hadronRtdProvider.js
+++ b/modules/hadronRtdProvider.js
@@ -5,12 +5,11 @@
  * @module modules/hadronRtdProvider
  * @requires module:modules/realTimeData
  */
-import {ajax} from '../src/ajax.js';
 import {config} from '../src/config.js';
 import {getGlobal} from '../src/prebidGlobal.js';
 import {getStorageManager} from '../src/storageManager.js';
 import {submodule} from '../src/hook.js';
-import {isFn, isStr, isArray, isEmpty, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js';
+import {isFn, isStr, isArray, deepEqual, isPlainObject, logInfo} from '../src/utils.js';
 import {loadExternalScript} from '../src/adloader.js';
 import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
 
@@ -22,8 +21,7 @@ const LOG_PREFIX = '[HadronRtdProvider] ';
 const MODULE_NAME = 'realTimeData';
 const SUBMODULE_NAME = 'hadron';
 const AU_GVLID = 561;
-const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid';
-const HADRON_SEGMENT_URL = 'https://prebid-rtd.audigent.workers.dev'; // https://id.hadron.ad.gt/api/v1/rtd';
+const HADRON_JS_URL = 'https://cdn.hadronid.net/hadron.js';
 const LS_TAM_KEY = 'auHadronId';
 const RTD_LOCAL_NAME = 'auHadronRtd';
 export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME});
@@ -37,19 +35,6 @@ const urlAddParams = (url, params) => {
   return url + (url.indexOf('?') > -1 ? '&' : '?') + params
 };
 
-/**
- * Deep set an object unless value present.
- * @param {Object} obj
- * @param {String} path
- * @param {Object} val
- */
-function set(obj, path, val) {
-  const keys = path.split('.');
-  const lastKey = keys.pop();
-  const lastObj = keys.reduce((obj, key) => obj[key] = obj[key] || {}, obj);
-  lastObj[lastKey] = lastObj[lastKey] || val;
-}
-
 /**
  * Deep object merging with array deduplication.
  * @param {Object} target
@@ -62,11 +47,11 @@ function mergeDeep(target, ...sources) {
   if (isPlainObject(target) && isPlainObject(source)) {
     for (const key in source) {
       if (isPlainObject(source[key])) {
-        if (!target[key]) Object.assign(target, { [key]: {} });
+        if (!target[key]) Object.assign(target, {[key]: {}});
         mergeDeep(target[key], source[key]);
       } else if (isArray(source[key])) {
         if (!target[key]) {
-          Object.assign(target, { [key]: source[key] });
+          Object.assign(target, {[key]: source[key]});
         } else if (isArray(target[key])) {
           source[key].forEach(obj => {
             let e = 1;
@@ -82,7 +67,7 @@ function mergeDeep(target, ...sources) {
           });
         }
       } else {
-        Object.assign(target, { [key]: source[key] });
+        Object.assign(target, {[key]: source[key]});
       }
     }
   }
@@ -150,6 +135,17 @@ export function addRealTimeData(bidConfig, rtd, rtdConfig) {
  * @param {Object} userConsent
  */
 export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
+  if (!storage.getDataFromLocalStorage(LS_TAM_KEY)) {
+    const partnerId = rtdConfig.params.partnerId | 0;
+    const hadronIdUrl = rtdConfig.params && rtdConfig.params.hadronIdUrl;
+    const scriptUrl = urlAddParams(
+      paramOrDefault(hadronIdUrl, HADRON_JS_URL, {}),
+      `partner_id=${partnerId}&_it=prebid`
+    );
+    loadExternalScript(scriptUrl, SUBMODULE_NAME, () => {
+      logInfo(LOG_PREFIX, 'hadronId JS snippet loaded', scriptUrl);
+    })
+  }
   if (rtdConfig && isPlainObject(rtdConfig.params) && rtdConfig.params.segmentCache) {
     let jsonData = storage.getDataFromLocalStorage(RTD_LOCAL_NAME);
 
@@ -177,83 +173,6 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
       logInfo(LOG_PREFIX, 'hadronId TAM found', hadronId);
     }
   }
-  if (!isEmpty(userIds)) {
-    // if (typeof getGlobal().refreshUserIds === 'function') {
-    //   (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'});
-    // }
-    // userIds.hadronId = hadronId;
-    getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds);
-  } else {
-    // the hadronId was not found, reasons can be:
-    //    1) prebid wasn't compiled with hadronIdSystem
-    //    2) prebid wasn't configured to use hadronId user module
-    //    3) all previous and no other hadronId snippet configured in the page
-    // then need to load hadron.js from the CDN
-    window.pubHadronCb = (hadronId) => {
-      userIds.hadronId = hadronId;
-      getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds);
-    }
-    const partnerId = rtdConfig.params.partnerId | 0;
-    const hadronIdUrl = rtdConfig.params && rtdConfig.params.hadronIdUrl;
-    const scriptUrl = urlAddParams(
-      paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds),
-      `partner_id=${partnerId}&_it=prebid`
-    );
-    loadExternalScript(scriptUrl, SUBMODULE_NAME, () => {
-      logInfo(LOG_PREFIX, 'hadronId JS snippet loaded', scriptUrl);
-    })
-  }
-}
-
-/**
- * Async rtd retrieval from Audigent
- * @param {Object} bidConfig
- * @param {function} onDone
- * @param {Object} rtdConfig
- * @param {Object} userConsent
- * @param {Object} userIds
- */
-function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) {
-  let reqParams = {};
-
-  if (isPlainObject(rtdConfig)) {
-    set(rtdConfig, 'params.requestParams.ortb2', bidConfig.ortb2Fragments.global);
-    reqParams = rtdConfig.params.requestParams;
-  }
-
-  if (isPlainObject(window.pubHadronPm)) {
-    reqParams.pubHadronPm = window.pubHadronPm;
-  }
-
-  ajax(HADRON_SEGMENT_URL, {
-    success: function (response, req) {
-      if (req.status === 200) {
-        try {
-          const data = JSON.parse(response);
-          if (data && data.rtd) {
-            addRealTimeData(bidConfig, data.rtd, rtdConfig);
-            onDone();
-            storage.setDataInLocalStorage(RTD_LOCAL_NAME, JSON.stringify(data));
-          } else {
-            onDone();
-          }
-        } catch (err) {
-          logError(LOG_PREFIX, 'unable to parse audigent segment data');
-          onDone();
-        }
-      } else if (req.status === 204) {
-        // unrecognized partner config
-        onDone();
-      }
-    },
-    error: function () {
-      onDone();
-      logError(LOG_PREFIX, 'unable to get audigent segment data');
-    }
-  },
-  JSON.stringify({'userIds': userIds, 'config': reqParams}),
-  {contentType: 'application/json'}
-  );
 }
 
 /**

From 835a3d35679e2c541abb262982ef8b45ca1ee7db Mon Sep 17 00:00:00 2001
From: yuva-inmobi-1 
Date: Thu, 21 Nov 2024 11:02:26 +0530
Subject: [PATCH 0688/1097] InMobi Bid Adapter : initial release (#12449)

* intital commit new bid adapter inmobi

* fixed linting issues

* added comments on some custom functions

* reverted comments on some custom functions

* resolved PR review comments
---
 modules/inmobiBidAdapter.js                |  347 ++++
 modules/inmobiBidAdapter.md                |   87 +
 test/spec/modules/inmobiBidAdapter_spec.js | 1965 ++++++++++++++++++++
 3 files changed, 2399 insertions(+)
 create mode 100644 modules/inmobiBidAdapter.js
 create mode 100644 modules/inmobiBidAdapter.md
 create mode 100644 test/spec/modules/inmobiBidAdapter_spec.js

diff --git a/modules/inmobiBidAdapter.js b/modules/inmobiBidAdapter.js
new file mode 100644
index 00000000000..910c53bf838
--- /dev/null
+++ b/modules/inmobiBidAdapter.js
@@ -0,0 +1,347 @@
+import { deepAccess, deepSetValue, isFn } from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
+import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js';
+import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js';
+
+/**
+ * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
+ * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
+ * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest
+ * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec
+ * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid
+ */
+
+const GVLID = 333;
+export const ADAPTER_VERSION = 1.0;
+const BIDDER_CODE = 'inmobi';
+const BID_ENDPOINT = 'https://api.w.inmobi.com/openrtb/bidder/prebidjs';
+export const EVENT_ENDPOINT = 'https://sync.inmobi.com';
+export const SYNC_ENDPOINT = 'https://sync.inmobi.com/prebidjs?';
+const TRANSLATOR = ortb25Translator();
+const CURRENCY = 'USD';
+const POST_METHOD = 'POST';
+const PLAIN_CONTENT_TYPE = 'text/plain';
+
+/**
+ * Defines the core oRTB converter inherited from converter library and all customization functions.
+ */
+const CONVERTER = ortbConverter({
+  context: {
+    netRevenue: true,
+    ttl: 3600,
+    currency: CURRENCY
+  },
+  imp,
+  request,
+  bidResponse,
+  response
+});
+
+/**
+ * Builds an impression object for oRTB 2.5 requests based on the bid request.
+ *
+ * @param {function} buildImp - Function to build the imp object.
+ * @param {Object} bidRequest - The request containing bid details.
+ * @param {Object} context - Context for the impression.
+ * @returns {Object} The constructed impression object.
+ */
+function imp(buildImp, bidRequest, context) {
+  let imp = buildImp(bidRequest, context);
+  const params = bidRequest.params;
+
+  imp.tagid = bidRequest.adUnitCode;
+  let floorInfo = {};
+
+  if (isFn(bidRequest.getFloor)) {
+    floorInfo = bidRequest.getFloor({
+      currency: CURRENCY,
+      size: '*',
+      mediaType: '*'
+    });
+  }
+
+  // if floor price module is not set reading from bidRequest.params
+  if (!imp.bidfloor && bidRequest.params.bidfloor) {
+    imp.bidfloor = bidRequest.params.bidfloor;
+    imp.bidfloorcur = CURRENCY;
+  }
+
+  deepSetValue(imp, 'ext', {
+    ...imp.ext,
+    params: bidRequest.params,
+    bidder: {
+      plc: params?.plc,
+    },
+    moduleFloors: floorInfo
+  });
+  imp.secure = Number(window.location.protocol === 'https:');
+
+  return imp;
+}
+
+/**
+ * Constructs the oRTB 2.5 request object.
+ *
+ * @param {function} buildRequest - Function to build the request.
+ * @param {Array} imps - Array of impression objects.
+ * @param {Object} bidderRequest - Object containing bidder request information.
+ * @param {Object} context - Additional context.
+ * @returns {Object} The complete oRTB request object.
+ */
+function request(buildRequest, imps, bidderRequest, context) {
+  let request = buildRequest(imps, bidderRequest, context);
+
+  deepSetValue(request, 'ext.prebid.channel.name', 'pbjs_InMobi');
+  deepSetValue(request, 'ext.prebid.channel.pbjsversion', '$prebid.version$');
+  deepSetValue(request, 'ext.prebid.channel.adapterversion', ADAPTER_VERSION);
+
+  request = TRANSLATOR(request);
+  return request;
+}
+
+/**
+ * Transforms an oRTB 2.5 bid into a bid response format for Prebid.js.
+ *
+ * @param {function} buildBidResponse - Function to build a bid response.
+ * @param {Object} bid - The bid to be transformed.
+ * @param {Object} context - Context for the bid.
+ * @returns {Object} Formatted bid response.
+ */
+function bidResponse(buildBidResponse, bid, context) {
+  context.mtype = deepAccess(bid, 'mtype');
+  if (context.mtype === 4) {
+    const admJson = JSON.parse(bid.adm);
+    bid.adm = JSON.stringify(admJson.native);
+  }
+  let bidResponse = buildBidResponse(bid, context);
+
+  if (typeof deepAccess(bid, 'ext') !== 'undefined') {
+    deepSetValue(bidResponse, 'meta', {
+      ...bidResponse.meta,
+      ...bid.ext,
+    });
+  }
+
+  return bidResponse;
+}
+
+/**
+ * Converts the oRTB 2.5 bid response into the format required by Prebid.js.
+ *
+ * @param {function} buildResponse - Function to build the response.
+ * @param {Array} bidResponses - List of bid responses.
+ * @param {Object} ortbResponse - Original oRTB response data.
+ * @param {Object} context - Additional context.
+ * @returns {Object} Prebid.js compatible bid response.
+ */
+function response(buildResponse, bidResponses, ortbResponse, context) {
+  let response = buildResponse(bidResponses, ortbResponse, context);
+
+  return response;
+}
+
+/** @type {BidderSpec} */
+export const spec = {
+  code: BIDDER_CODE,
+  gvlid: GVLID,
+  supportedMediaTypes: [BANNER, VIDEO, NATIVE],
+
+  /**
+   * Determines user sync options based on consent and supported sync types.
+   *
+   * @param {Object} syncOptions - Options for user syncing (iframe, pixel).
+   * @param {Array} responses - List of bid responses.
+   * @param {Object} gdprConsent - GDPR consent details.
+   * @param {Object} uspConsent - CCPA consent details.
+   * @param {Object} gppConsent - GPP consent details.
+   * @returns {Array} List of user sync URLs.
+   */
+  getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => {
+    const urls = [];
+    if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) {
+      return urls;
+    }
+    const pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image';
+
+    let query = '';
+    if (gdprConsent) {
+      query = tryAppendQueryString(query, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0));
+    }
+    if (gdprConsent && typeof gdprConsent.consentString === 'string') {
+      query = tryAppendQueryString(query, 'gdpr_consent', gdprConsent.consentString);
+    }
+    if (uspConsent) {
+      query = tryAppendQueryString(query, 'us_privacy', uspConsent);
+    }
+    if (gppConsent?.gppString && gppConsent?.applicableSections?.length) {
+      query = tryAppendQueryString(query, 'gpp', gppConsent.gppString);
+      query = tryAppendQueryString(query, 'gpp_sid', gppConsent?.applicableSections?.join(','));
+    }
+    if (query.slice(-1) === '&') {
+      query = query.slice(0, -1);
+    }
+
+    if (pixelType === 'iframe' || (!responses || responses.length === 0)) {
+      return [{
+        type: pixelType,
+        url: SYNC_ENDPOINT + query
+      }];
+    } else {
+      responses.forEach(resp => {
+        const userSyncs = deepAccess(resp, 'body.ext.prebidjs.urls');
+        if (!userSyncs) {
+          return;
+        }
+
+        userSyncs.forEach(us => {
+          let url = us.url;
+          if (query) {
+            url = url + (url.indexOf('?') === -1 ? '?' : '&') + query;
+          }
+
+          urls.push({
+            type: pixelType,
+            url: url
+          });
+        });
+      });
+      return urls;
+    }
+  },
+
+  /**
+   * Validates if a bid request contains the required parameters for InMobi.
+   *
+   * @param {Object} bid - Bid request to validate.
+   * @returns {boolean} True if the bid request is valid, otherwise false.
+   */
+  isBidRequestValid: (bid) => {
+    if (!(bid && bid.params && bid.params.plc)) {
+      return false;
+    }
+
+    return true;
+  },
+
+  /**
+   * Builds the server request from bid requests for InMobi.
+   *
+   * @param {BidRequest[]} bidRequests - Array of bid requests.
+   * @param {Object} bidderRequest - Additional request details.
+   * @returns {ServerRequest} The server request for bidding.
+   */
+  buildRequests: (bidRequests, bidderRequest) => {
+    const data = CONVERTER.toORTB({ bidderRequest, bidRequests });
+
+    if (data) {
+      const requestPayload = {
+        method: POST_METHOD,
+        url: BID_ENDPOINT,
+        data: data,
+        options: {
+          contentType: PLAIN_CONTENT_TYPE,
+          crossOrigin: true,
+          withCredentials: true
+        }
+      };
+      return requestPayload;
+    }
+  },
+
+  /**
+   * Interprets the server response and formats it into bids.
+   *
+   * @param {Object} response - Response from the server.
+   * @param {ServerRequest} request - Original bid request.
+   * @returns {Bid[]} Parsed bids or configurations.
+   */
+  interpretResponse: (response, request) => {
+    if (typeof response?.body == 'undefined') {
+      return [];
+    }
+
+    const interpretedResponse = CONVERTER.fromORTB({ response: response.body, request: request.data });
+    const bids = interpretedResponse.bids || [];
+
+    return bids;
+  },
+
+  /**
+   * Callback to report timeout event.
+   *
+   * @param {TimedOutBid[]} timeoutData - Array of timeout details.
+   */
+  onTimeout: (timeoutData) => {
+    report('onTimeout', timeoutData);
+  },
+
+  /**
+   * Callback to report targeting event.
+   *
+   * @param {Bid} bid - The bid object
+   */
+  onSetTargeting: (bid) => {
+    report('onSetTargeting', bid?.meta);
+  },
+
+  /**
+   * Callback to report successful ad render event.
+   *
+   * @param {Bid} bid - The bid that successfully rendered.
+   */
+  onAdRenderSucceeded: (bid) => {
+    report('onAdRenderSucceeded', bid?.meta);
+  },
+
+  /**
+   * Callback to report bidder error event.
+   *
+   * @param {Object} errorData - Details about the error.
+   */
+  onBidderError: (errorData) => {
+    report('onBidderError', errorData);
+  },
+
+  /**
+   * Callback to report bid won event.
+   *
+   * @param {Bid} bid - The bid that won the auction.
+   */
+  onBidWon: (bid) => {
+    report('onBidWon', bid?.meta);
+  }
+
+};
+
+function isReportingAllowed(loggingPercentage) {
+  return loggingPercentage != 0;
+}
+
+function report(type, data) {
+  if (!data) {
+    return;
+  }
+  if (['onBidWon', 'onAdRenderSucceeded', 'onSetTargeting'].includes(type) && !isReportingAllowed(data.loggingPercentage)) {
+    return;
+  }
+  const payload = JSON.stringify({
+    domain: location.hostname,
+    eventPayload: data
+  });
+
+  fetch(`${EVENT_ENDPOINT}/report/${type}`, {
+    body: payload,
+    keepalive: true,
+    credentials: 'include',
+    method: POST_METHOD,
+    headers: {
+      'Content-Type': PLAIN_CONTENT_TYPE
+    }
+  }).catch((_e) => {
+    // do nothing; ignore errors
+  });
+}
+
+registerBidder(spec);
diff --git a/modules/inmobiBidAdapter.md b/modules/inmobiBidAdapter.md
new file mode 100644
index 00000000000..bf309fcb01d
--- /dev/null
+++ b/modules/inmobiBidAdapter.md
@@ -0,0 +1,87 @@
+# Overview
+
+Module Name: InMobi Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: prebid-support@inmobi.com
+
+# Description
+
+Module that connects to InMobi's demand sources.
+
+# Bid Params
+
+| Name       | Scope    | Description  |  Example  | Type     |
+|------------|----------|--------------|-----------|--------  |
+| `plc`      | required | Placement ID | `'1234'`  | `string` |
+| `bidfloor` | optional | Bid Floor    | `1.2`     | `float`  |
+
+
+# Test Parameters
+
+## Banner
+```
+    var adUnits = [{
+            code: 'div-gpt-ad-1460505748561-0',
+            mediaTypes: {
+                banner: {
+                    sizes: [[300, 250]],
+                }
+            },
+
+            bids: [{
+                bidder: 'inmobi',
+                params: {
+                    plc: '1719108420057' // Mandatory
+                }
+            }]
+
+    }];
+```
+
+## Video
+```
+    var adUnits = [{
+            code: 'div-gpt-ad-1460505748561-0',
+            mediaTypes: {
+                video: {
+                    playerSize : [300,250],
+                    mimes :  ["video/x-ms-wmv", "video/mp4"],
+                    minduration : 0,
+                    maxduration: 30,
+                    protocols : [1,2],
+                    api: [1, 2, 4, 6],
+                    protocols: [3, 4, 7, 8, 10],
+                    placement: 1,
+                    plcmt: 1
+                }
+            },
+
+            // Replace this object to test a new Adapter!
+            bids: [{
+                bidder: 'inmobi',
+                params: {
+                    plc: '1443164204446401' //Mandatory
+                }
+            }]
+    }];
+```
+
+## Native
+```
+    var adUnits = [{
+            code: 'div-gpt-ad-1460505748561-0',
+            mediaTypes: {
+                native: {
+                        type: 'image'
+                }
+            },
+
+            bids: [{
+                bidder: 'inmobi',
+                params: {
+                    plc: '10000033152',
+                    bidfloor: 0.9
+                }
+            }]
+    }];
+```        
\ No newline at end of file
diff --git a/test/spec/modules/inmobiBidAdapter_spec.js b/test/spec/modules/inmobiBidAdapter_spec.js
new file mode 100644
index 00000000000..a7074a2eed8
--- /dev/null
+++ b/test/spec/modules/inmobiBidAdapter_spec.js
@@ -0,0 +1,1965 @@
+import { expect } from 'chai';
+import {
+  spec,
+} from 'modules/inmobiBidAdapter.js';
+import * as utils from 'src/utils.js';
+import * as ajax from 'src/ajax.js';
+import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js';
+import { hook } from '../../../src/hook';
+import { config } from '../../../src/config.js';
+import { syncAddFPDToBidderRequest } from '../../helpers/fpd';
+import 'modules/consentManagementTcf.js';
+import 'modules/consentManagementUsp.js';
+import 'modules/consentManagementGpp.js';
+import 'modules/priceFloors.js';
+import sinon from 'sinon';
+
+// constants
+const GVLID = 333;
+export const ADAPTER_VERSION = 1.0;
+const BIDDER_CODE = 'inmobi';
+export const EVENT_ENDPOINT = 'https://sync.inmobi.com';
+
+describe('The inmobi bidding adapter', function () {
+  let utilsMock, sandbox, ajaxStub, fetchStub; ;
+
+  beforeEach(function () {
+    // mock objects
+    utilsMock = sinon.mock(utils);
+    sandbox = sinon.sandbox.create();
+    ajaxStub = sandbox.stub(ajax, 'ajax');
+    fetchStub = sinon.stub(global, 'fetch').resolves(new Response('OK'));
+  });
+
+  afterEach(function () {
+    utilsMock.restore();
+    sandbox.restore();
+    ajaxStub.restore();
+    fetchStub.restore();
+  });
+
+  describe('onBidWon', function () {
+    // existence test
+    it('onBidWon function should be defined', function () {
+      expect(spec.onBidWon).to.exist.and.to.be.a('function');
+    });
+
+    it('It should invoke onBidWon, resolving the eventType and domain', function () {
+      const bid = {
+        bidder: 'inmobi',
+        width: 300,
+        height: 250,
+        adId: '330a22bdea4cac1',
+        mediaType: 'banner',
+        cpm: 0.28,
+        ad: 'inmobiAd',
+        requestId: '418b37f85e772c1',
+        adUnitCode: 'div-gpt-ad-1460505748561-01',
+        size: '350x250',
+        adserverTargeting: {
+          hb_bidder: 'prebid',
+          hb_adid: '330a22bdea4cac1',
+          hb_pb: '0.20',
+          hb_size: '350x250'
+        },
+        meta: {
+          loggingPercentage: 100
+        }
+      };
+      spec.onBidWon(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onBidWon`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid.meta
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(1);
+      const fetchArgs = fetchStub.getCall(0).args;
+      /*
+                     index  fetch parameter
+                     0 ->   URL
+                     1 ->   options (method, headers, body, etc.)
+                  */
+      expect(fetchArgs[0]).to.equal(expectedUrl);
+      const actualPayload = fetchArgs[1]?.body;
+      expect(actualPayload).to.equal(expectedPayload);
+      expect(fetchArgs[1]).to.deep.include({
+        method: 'POST',
+        credentials: 'include',
+        keepalive: true,
+      });
+      expect(fetchArgs[1]?.headers).to.deep.equal({
+        'Content-Type': 'text/plain',
+      });
+    });
+
+    it('onBidWon should not be called when loggingPercentage is set to 0', function () {
+      const bid = {
+        bidder: 'inmobi',
+        width: 300,
+        height: 250,
+        adId: '330a22bdea4cac1',
+        mediaType: 'banner',
+        cpm: 0.28,
+        ad: 'inmobiAd',
+        requestId: '418b37f85e772c1',
+        adUnitCode: 'div-gpt-ad-1460505748561-01',
+        size: '350x250',
+        adserverTargeting: {
+          hb_bidder: 'prebid',
+          hb_adid: '330a22bdea4cac1',
+          hb_pb: '0.20',
+          hb_size: '350x250'
+        },
+        meta: {
+          loggingPercentage: 0
+        }
+      };
+      spec.onBidWon(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onBidWon`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid.meta
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+
+    it('onBidWon should not be called if the bid data is null', function () {
+      // Call onBidWon with null data
+      spec.onBidWon(null);
+      // Assert that ajax was not called since bid data is null
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+  });
+
+  describe('onBidderError', function () {
+    it('onBidderError function should be defined', function () {
+      expect(spec.onBidderError).to.exist.and.to.be.a('function');
+    });
+
+    it('onBidderError should not be called if the bid data is null', function () {
+      // Call onBidError with null data
+      spec.onBidderError(null);
+      // Assert that ajax was not called since bid data is null
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+
+    it('onBidderError should be called with the eventType', function () {
+      const bid = {
+        error: 'error', // Assuming this will be a mock or reference to an actual XMLHttpRequest object
+        bidderRequest: {
+          auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917',
+          auctionStart: 1579746300522,
+          bidder: 'inmobi',
+          bidderRequestId: '15246a574e859f'
+        }
+      };
+      spec.onBidderError(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onBidderError`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(1);
+      const fetchArgs = fetchStub.getCall(0).args;
+      /*
+                     index  fetch parameter
+                     0 ->   URL
+                     1 ->   options (method, headers, body, etc.)
+                  */
+      expect(fetchArgs[0]).to.equal(expectedUrl);
+      const actualPayload = fetchArgs[1]?.body;
+      expect(actualPayload).to.equal(expectedPayload);
+      expect(fetchArgs[1]).to.deep.include({
+        method: 'POST',
+        credentials: 'include',
+        keepalive: true,
+      });
+      expect(fetchArgs[1]?.headers).to.deep.equal({
+        'Content-Type': 'text/plain',
+      });
+    });
+  });
+
+  describe('onAdRenderSucceeded', function () {
+    // existence test
+    it('onAdRenderSucceeded function should be defined', function () {
+      expect(spec.onAdRenderSucceeded).to.exist.and.to.be.a('function');
+    });
+
+    it('should invoke onAdRenderSucceeded, resolving the eventType and domain', function () {
+      const bid = {
+        bidder: 'inmobi',
+        width: 300,
+        height: 250,
+        adId: '330a22bdea4cac1',
+        mediaType: 'banner',
+        cpm: 0.28,
+        ad: 'inmobiAd',
+        requestId: '418b37f85e772c1',
+        adUnitCode: 'div-gpt-ad-1460505748561-01',
+        size: '350x250',
+        adserverTargeting: {
+          hb_bidder: 'prebid',
+          hb_adid: '330a22bdea4cac1',
+          hb_pb: '0.20',
+          hb_size: '350x250'
+        },
+        meta: {
+          loggingPercentage: 100
+        }
+      };
+      spec.onAdRenderSucceeded(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onAdRenderSucceeded`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid.meta
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(1);
+      const fetchArgs = fetchStub.getCall(0).args;
+      /*
+                     index  fetch parameter
+                     0 ->   URL
+                     1 ->   options (method, headers, body, etc.)
+                  */
+      expect(fetchArgs[0]).to.equal(expectedUrl);
+      const actualPayload = fetchArgs[1]?.body;
+      expect(actualPayload).to.equal(expectedPayload);
+      expect(fetchArgs[1]).to.deep.include({
+        method: 'POST',
+        credentials: 'include',
+        keepalive: true,
+      });
+      expect(fetchArgs[1]?.headers).to.deep.equal({
+        'Content-Type': 'text/plain',
+      });
+    });
+
+    it('onAdRenderSucceeded should not be called when loggingPercentage is 0', function () {
+      const bid = {
+        bidder: 'inmobi',
+        width: 300,
+        height: 250,
+        adId: '330a22bdea4cac1',
+        mediaType: 'banner',
+        cpm: 0.28,
+        ad: 'inmobiAd',
+        requestId: '418b37f85e772c1',
+        adUnitCode: 'div-gpt-ad-1460505748561-01',
+        size: '350x250',
+        adserverTargeting: {
+          hb_bidder: 'prebid',
+          hb_adid: '330a22bdea4cac1',
+          hb_pb: '0.20',
+          hb_size: '350x250'
+        },
+        meta: {
+          loggingPercentage: 0
+        }
+      };
+      spec.onAdRenderSucceeded(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onAdRenderSucceeded`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid.meta
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+
+    it('onAdRenderSucceeded should not be called if the bid data is null', function () {
+      // Call onAdRenderSucceeded with null data
+      spec.onAdRenderSucceeded(null);
+      // Assert that ajax was not called since bid data is null
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+  });
+
+  describe('onTimeout', function () {
+    // existence test
+    it('onTimeout function should be defined', function () {
+      expect(spec.onTimeout).to.exist.and.to.be.a('function');
+    });
+
+    it('should invoke onTimeout, resolving the eventType and domain', function () {
+      const bid = [
+        {
+          bidder: 'inmobi',
+          bidId: '51ef8751f9aead1',
+          adUnitCode: 'div-gpt-ad-14605057481561-0',
+          timeout: 3000,
+          auctionId: '18fd8b8b0bd7517'
+        }
+      ];
+      spec.onTimeout(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onTimeout`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(1);
+      const fetchArgs = fetchStub.getCall(0).args;
+      /*
+                     index  fetch parameter
+                     0 ->   URL
+                     1 ->   options (method, headers, body, etc.)
+                  */
+      expect(fetchArgs[0]).to.equal(expectedUrl);
+      const actualPayload = fetchArgs[1]?.body;
+      expect(actualPayload).to.equal(expectedPayload);
+      expect(fetchArgs[1]).to.deep.include({
+        method: 'POST',
+        credentials: 'include',
+        keepalive: true,
+      });
+      expect(fetchArgs[1]?.headers).to.deep.equal({
+        'Content-Type': 'text/plain',
+      });
+    });
+
+    it('onTimeout should not be called if the bid data is null', function () {
+      // Call onTimeout with null data
+      spec.onTimeout(null);
+      // Assert that ajax was not called since bid data is null
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+  });
+
+  describe('onSetTargeting', function () {
+    // existence test
+    it('The onSetTargeting function should be defined', function () {
+      expect(spec.onSetTargeting).to.exist.and.to.be.a('function');
+    });
+
+    it('should invoke onSetTargeting, resolving the eventType and domain', function () {
+      const bid = {
+        bidder: 'inmobi',
+        width: 300,
+        height: 250,
+        adId: '330a22bdea4cac1',
+        mediaType: 'banner',
+        cpm: 0.28,
+        ad: 'inmobiAd',
+        requestId: '418b37f85e7721c',
+        adUnitCode: 'div-gpt-ad-1460505748561-01',
+        size: '350x250',
+        adserverTargeting: {
+          hb_bidder: 'prebid',
+          hb_adid: '330a22bdea4cac1',
+          hb_pb: '0.20',
+          hb_size: '350x250'
+        },
+        meta: {
+          loggingPercentage: 100
+        }
+      };
+      spec.onSetTargeting(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onSetTargeting`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid.meta
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(1);
+      const fetchArgs = fetchStub.getCall(0).args;
+      /*
+                     index  fetch parameter
+                     0 ->   URL
+                     1 ->   options (method, headers, body, etc.)
+                  */
+      expect(fetchArgs[0]).to.equal(expectedUrl);
+      const actualPayload = fetchArgs[1]?.body;
+      expect(actualPayload).to.equal(expectedPayload);
+      expect(fetchArgs[1]).to.deep.include({
+        method: 'POST',
+        credentials: 'include',
+        keepalive: true,
+      });
+      expect(fetchArgs[1]?.headers).to.deep.equal({
+        'Content-Type': 'text/plain',
+      });
+    });
+
+    it('onSetTargeting should not be called when loggingPercentage is 0', function () {
+      const bid = {
+        bidder: 'inmobi',
+        width: 300,
+        height: 250,
+        adId: '330a22bdea4cac1',
+        mediaType: 'banner',
+        cpm: 0.28,
+        ad: 'inmobiAd',
+        requestId: '418b37f85e7721c',
+        adUnitCode: 'div-gpt-ad-1460505748561-01',
+        size: '350x250',
+        adserverTargeting: {
+          hb_bidder: 'prebid',
+          hb_adid: '330a22bdea4cac1',
+          hb_pb: '0.20',
+          hb_size: '350x250'
+        },
+        meta: {
+          loggingPercentage: 0
+        }
+      };
+      spec.onSetTargeting(bid);
+      // expected url and payload
+      const expectedUrl = `${EVENT_ENDPOINT}/report/onSetTargeting`;
+      const expectedPayload = JSON.stringify({
+        domain: location.hostname,
+        eventPayload: bid.meta
+      });
+      // assert statements
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+
+    it('onSetTargeting should not be called if the bid data is null', function () {
+      // Call onSetTargeting with null data
+      spec.onSetTargeting(null);
+      // Assert that ajax was not called since bid data is null
+      expect(fetchStub.callCount).to.be.equal(0);
+    });
+  });
+
+  describe('isBidRequestValid', function () {
+    it('should return false when an invalid bid is provided', function () {
+      const bid = {
+        bidder: 'inmobi',
+      };
+      const isValid = spec.isBidRequestValid(bid);
+      expect(isValid).to.equal(false);
+    });
+
+    it('should return true when the bid contains a PLC', function () {
+      const bid = {
+        bidder: 'inmobi',
+        params: {
+          plc: '123a',
+        },
+      };
+      const isValid = spec.isBidRequestValid(bid);
+      expect(isValid).to.equal(true);
+    });
+  });
+
+  describe('buildRequests', function () {
+    before(() => {
+      hook.ready();
+    })
+    afterEach(function () {
+      config.resetConfig();
+    });
+    const bidderRequest = {
+      refererInfo: {
+        page: 'inmobi',
+        topmostLocation: 'inmobi'
+      },
+      timeout: 3000,
+      gdprConsent: {
+        gdprApplies: true,
+        consentString: 'consentDataString',
+        vendorData: {
+          vendorConsents: {
+            '333': 1
+          },
+        },
+        apiVersion: 1,
+      },
+    };
+
+    it('request should build with correct plc', function () {
+      const bidRequests = [
+        {
+          bidId: 'bidId',
+          bidder: 'inmobi',
+          adUnitCode: 'bid-12',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123'
+          }
+        },
+      ];
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const ortbRequest = request.data;
+      expect(ortbRequest.imp[0].ext.bidder.plc).to.deep.equal('123');
+    });
+
+    it('request should build with correct imp', function () {
+      const expectedMetric = {
+        url: 'https://inmobi.com'
+      }
+      const bidRequests = [{
+        bidId: 'bidId',
+        bidder: 'inmobi',
+        adUnitCode: 'impId',
+        mediaTypes: {
+          banner: {
+            sizes: [[320, 50]]
+          }
+        },
+        ortb2Imp: {
+          instl: 1,
+          metric: expectedMetric,
+          ext: {
+            gpid: 'gpid_inmobi'
+          },
+          rwdd: 1
+        },
+        params: {
+          plc: '123ai',
+          bidfloor: 4.66,
+          bidfloorcur: 'USD'
+        }
+      }];
+
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.imp).to.have.lengthOf(1);
+      expect(ortbRequest.imp[0].id).to.deep.equal('bidId');
+      expect(ortbRequest.imp[0].tagid).to.deep.equal('impId');
+      expect(ortbRequest.imp[0].instl).to.equal(1);
+      expect(ortbRequest.imp[0].bidfloor).to.equal(4.66);
+      expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD');
+      expect(ortbRequest.imp[0].metric).to.deep.equal(expectedMetric);
+      expect(ortbRequest.imp[0].secure).to.equal(0);
+      expect(ortbRequest.imp[0].ext.gpid).to.equal('gpid_inmobi');
+      expect(ortbRequest.imp[0].rwdd).to.equal(1);
+    });
+
+    it('request should build with proper site data', function () {
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '1234a',
+          },
+        },
+      ];
+      const ortb2 = {
+        site: {
+          name: 'raapchikgames.com',
+          domain: 'raapchikgames.com',
+          keywords: 'test1, test2',
+          cat: ['IAB2'],
+          pagecat: ['IAB3'],
+          sectioncat: ['IAB4'],
+          page: 'https://raapchikgames.com',
+          ref: 'inmobi.com',
+          privacypolicy: 1,
+          content: {
+            url: 'https://raapchikgames.com/games1'
+          }
+        }
+      };
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data;
+      expect(ortbRequest.site.domain).to.equal('raapchikgames.com');
+      expect(ortbRequest.site.publisher.domain).to.equal('inmobi');
+      expect(ortbRequest.site.page).to.equal('https://raapchikgames.com');
+      expect(ortbRequest.site.name).to.equal('raapchikgames.com');
+      expect(ortbRequest.site.keywords).to.equal('test1, test2');
+      expect(ortbRequest.site.cat).to.deep.equal(['IAB2']);
+      expect(ortbRequest.site.pagecat).to.deep.equal(['IAB3']);
+      expect(ortbRequest.site.sectioncat).to.deep.equal(['IAB4']);
+      expect(ortbRequest.site.ref).to.equal('inmobi.com');
+      expect(ortbRequest.site.privacypolicy).to.equal(1);
+      expect(ortbRequest.site.content.url).to.equal('https://raapchikgames.com/games1')
+    });
+
+    it('request should build with proper device data', function () {
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '1234a',
+          },
+        },
+      ];
+      const ortb2 = {
+        device: {
+          dnt: 0,
+          ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
+          ip: '195.199.250.144',
+          h: 919,
+          w: 1920,
+          language: 'hu',
+          lmt: 1,
+          js: 1,
+          connectiontype: 0,
+          hwv: '5S',
+          model: 'iphone',
+          mccmnc: '310-005',
+          geo: {
+            lat: 40.0964439,
+            lon: -75.3009142
+          }
+        }
+      };
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data;
+      expect(ortbRequest.device.dnt).to.equal(0);
+      expect(ortbRequest.device.lmt).to.equal(1);
+      expect(ortbRequest.device.js).to.equal(1);
+      expect(ortbRequest.device.connectiontype).to.equal(0);
+      expect(ortbRequest.device.ua).to.equal('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36');
+      expect(ortbRequest.device.ip).to.equal('195.199.250.144');
+      expect(ortbRequest.device.h).to.equal(919);
+      expect(ortbRequest.device.w).to.equal(1920);
+      expect(ortbRequest.device.language).to.deep.equal('hu');
+      expect(ortbRequest.device.hwv).to.deep.equal('5S');
+      expect(ortbRequest.device.model).to.deep.equal('iphone');
+      expect(ortbRequest.device.mccmnc).to.deep.equal('310-005');
+      expect(ortbRequest.device.geo.lat).to.deep.equal(40.0964439);
+      expect(ortbRequest.device.geo.lon).to.deep.equal(-75.3009142);
+    });
+
+    it('should properly build a request with source object', function () {
+      const expectedSchain = { id: 'prebid' };
+      const ortb2 = {
+        source: {
+          pchain: 'inmobi',
+          schain: expectedSchain
+        }
+      };
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          bidId: 'bidId',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '124',
+          },
+        },
+      ];
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data;
+      expect(ortbRequest.source.ext.schain).to.deep.equal(expectedSchain);
+      expect(ortbRequest.source.pchain).to.equal('inmobi');
+    });
+
+    it('should properly user object', function () {
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123'
+          }
+        },
+      ];
+      const br = {
+        ...bidderRequest,
+        ortb2: {
+          user: {
+            yob: 2002,
+            keyowrds: 'test test',
+            gender: 'M',
+            customdata: 'test no',
+            geo: {
+              lat: 40.0964439,
+              lon: -75.3009142
+            },
+            ext: {
+              eids: [
+                {
+                  source: 'inmobi.com',
+                  uids: [{
+                    id: 'iid',
+                    atype: 1
+                  }]
+                }
+              ]
+            }
+          }
+        }
+      }
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br));
+      const ortbRequest = request.data;
+      expect(ortbRequest.user.yob).to.deep.equal(2002);
+      expect(ortbRequest.user.keyowrds).to.deep.equal('test test');
+      expect(ortbRequest.user.gender).to.deep.equal('M');
+      expect(ortbRequest.user.customdata).to.deep.equal('test no');
+      expect(ortbRequest.user.geo.lat).to.deep.equal(40.0964439);
+      expect(ortbRequest.user.geo.lon).to.deep.equal(-75.3009142);
+      expect(ortbRequest.user.ext.eids).to.deep.equal([
+        {
+          source: 'inmobi.com',
+          uids: [{
+            id: 'iid',
+            atype: 1
+          }]
+        }
+      ]);
+    });
+
+    it('should properly build a request regs object', function () {
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '1234a',
+          },
+        },
+      ];
+      const ortb2 = {
+        regs: {
+          coppa: 1,
+          gpp: 'gpp_consent_string',
+          gpp_sid: [0, 1, 2],
+          us_privacy: 'yes us privacy applied'
+        }
+      };
+
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data;
+      expect(ortbRequest.regs.coppa).to.equal(1);
+      expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string');
+      expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]);
+      expect(ortbRequest.regs.ext.us_privacy).to.deep.equal('yes us privacy applied');
+    });
+
+    it('gdpr test', function () {
+      // using privacy params from global bidder Request
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '1234a',
+          },
+        },
+      ];
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.regs.ext.gdpr).to.deep.equal(1);
+      expect(ortbRequest.user.ext.consent).to.equal('consentDataString');
+    });
+
+    it('should properly set tmax if available', function () {
+      // using tmax from global bidder Request
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'bid-1',
+          transactionId: 'trans-1',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123'
+          }
+        },
+      ];
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const ortbRequest = request.data;
+      expect(ortbRequest.tmax).to.equal(bidderRequest.timeout);
+    });
+
+    it('should properly build a request with bcat field', function () {
+      const bcat = ['IAB1', 'IAB2'];
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123a',
+          },
+        },
+      ];
+      const bidderRequest = {
+        ortb2: {
+          bcat
+        }
+      };
+
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.bcat).to.deep.equal(bcat);
+    });
+
+    it('should properly build a request with badv field', function () {
+      const badv = ['ford.com'];
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '12ea',
+          },
+        },
+      ];
+      const bidderRequest = {
+        ortb2: {
+          badv
+        }
+      };
+
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.badv).to.deep.equal(badv);
+    });
+
+    it('should properly build a request with bapp field', function () {
+      const bapp = ['raapchik.com'];
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123a',
+          },
+        },
+      ];
+      const bidderRequest = {
+        ortb2: {
+          bapp
+        }
+      };
+
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.bapp).to.deep.equal(bapp);
+    });
+
+    it('banner request test', function () {
+      const bidderRequest = {};
+      const bidRequests = [
+        {
+          bidId: 'bidId',
+          bidder: 'inmobi',
+          adUnitCode: 'bid-12',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]],
+              pos: 1,
+              topframe: 0,
+            }
+          },
+          params: {
+            plc: '123'
+          },
+          ortb2Imp: {
+            banner: {
+              api: [1, 2],
+              mimes: ['image/jpg', 'image/gif']
+            }
+          }
+        },
+      ];
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const ortbRequest = request.data;
+      expect(ortbRequest.imp[0].banner).not.to.be.null;
+      expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320);
+      expect(ortbRequest.imp[0].banner.format[0].h).to.equal(50);
+      expect(ortbRequest.imp[0].banner.pos).to.equal(1);
+      expect(ortbRequest.imp[0].banner.topframe).to.equal(0);
+      expect(ortbRequest.imp[0].banner.api).to.deep.equal([1, 2]);
+      expect(ortbRequest.imp[0].banner.mimes).to.deep.equal(['image/jpg', 'image/gif']);
+    });
+
+    it('banner request test with sizes > 1', function () {
+      const bidderRequest = {};
+      const bidRequests = [
+        {
+          bidId: 'bidId',
+          bidder: 'inmobi',
+          adUnitCode: 'bid-12',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50], [720, 50]]
+            }
+          },
+          params: {
+            plc: '123'
+          }
+        },
+      ];
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const ortbRequest = request.data;
+      expect(ortbRequest.imp[0].banner).not.to.be.null;
+      expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320);
+      expect(ortbRequest.imp[0].banner.format[0].h).to.equal(50);
+      expect(ortbRequest.imp[0].banner.format[1].w).to.equal(720);
+      expect(ortbRequest.imp[0].banner.format[1].h).to.equal(50);
+    });
+
+    if (FEATURES.VIDEO) {
+      it('video request test', function () {
+        const bidRequests = [
+          {
+            bidder: 'inmobi',
+            adUnitCode: 'bid-123',
+            sizes: [[640, 360]],
+            mediaTypes: {
+              video: {
+                context: 'inbanner',
+                playerSize: [640, 360],
+                mimes: ['video/mp4', 'video/x-flv'],
+                maxduration: 30,
+                api: [1, 2],
+                protocols: [2, 3],
+                plcmt: 3,
+                w: 640,
+                h: 360,
+                linearity: 1,
+                skipmin: 30,
+                skipafter: 30,
+                minbitrate: 10000,
+                maxbitrate: 48000,
+                delivery: [1, 2, 3],
+                pos: 1,
+                playbackend: 1,
+                adPodDurationSec: 30,
+                durationRangeSec: [1, 30],
+                skip: 1,
+                minduration: 5,
+                startdelay: 5,
+                playbackmethod: [1, 3],
+                placement: 2
+              }
+            },
+            params: {
+              plc: '123'
+            },
+          },
+        ];
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const ortbRequest = request.data;
+        expect(ortbRequest.imp).to.have.lengthOf(1);
+        expect(ortbRequest.imp[0].video.skip).to.equal(1);
+        expect(ortbRequest.imp[0].video.minduration).to.equal(5);
+        expect(ortbRequest.imp[0].video.startdelay).to.equal(5);
+        expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]);
+        expect(ortbRequest.imp[0].video.placement).to.equal(2);
+        expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']);
+        expect(ortbRequest.imp[0].video.maxduration).to.equal(30);
+        expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2]);
+        expect(ortbRequest.imp[0].video.protocols).to.deep.equal([2, 3]);
+        expect(ortbRequest.imp[0].video.plcmt).to.equal(3);
+        expect(ortbRequest.imp[0].video.w).to.equal(640);
+        expect(ortbRequest.imp[0].video.h).to.equal(360);
+        expect(ortbRequest.imp[0].video.linearity).to.equal(1);
+        expect(ortbRequest.imp[0].video.skipmin).to.equal(30);
+        expect(ortbRequest.imp[0].video.skipafter).to.equal(30);
+        expect(ortbRequest.imp[0].video.minbitrate).to.equal(10000);
+        expect(ortbRequest.imp[0].video.maxbitrate).to.equal(48000);
+        expect(ortbRequest.imp[0].video.delivery).to.deep.equal([1, 2, 3]);
+        expect(ortbRequest.imp[0].video.pos).to.equal(1);
+        expect(ortbRequest.imp[0].video.playbackend).to.equal(1);
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('video request with player size > 1 ', function () {
+        const bidRequests = [
+          {
+            bidder: 'inmobi',
+            adUnitCode: 'bid-123',
+            sizes: [[640, 360], [480, 320]],
+            mediaTypes: {
+              video: {
+                context: 'inbanner',
+                playerSize: [[640, 360], [480, 320]],
+                mimes: ['video/mp4', 'video/x-flv'],
+                maxduration: 30,
+                api: [1, 2],
+                protocols: [2, 3],
+                plcmt: 3,
+                w: 640,
+                h: 360,
+                linearity: 1,
+                skipmin: 30,
+                skipafter: 30,
+                minbitrate: 10000,
+                maxbitrate: 48000,
+                delivery: [1, 2, 3],
+                pos: 1,
+                playbackend: 1,
+                adPodDurationSec: 30,
+                durationRangeSec: [1, 30],
+                skip: 1,
+                minduration: 5,
+                startdelay: 5,
+                playbackmethod: [1, 3],
+                placement: 2
+              }
+            },
+            params: {
+              plc: '123'
+            },
+          },
+        ];
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const ortbRequest = request.data;
+        expect(ortbRequest.imp).to.have.lengthOf(1);
+        expect(ortbRequest.imp[0].video.w).to.be.equal(640);
+        expect(ortbRequest.imp[0].video.h).to.be.equal(360);
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('video request test when skip is 0', function () {
+        const bidRequests = [
+          {
+            bidder: 'inmobi',
+            adUnitCode: 'bid-123',
+            sizes: [[640, 360]],
+            mediaTypes: {
+              video: {
+                context: 'inbanner',
+                playerSize: [640, 360],
+                mimes: ['video/mp4', 'video/x-flv'],
+                maxduration: 30,
+                api: [1, 2],
+                protocols: [2, 3],
+                plcmt: 3,
+                w: 640,
+                h: 360,
+                linearity: 1,
+                skipmin: 30,
+                skipafter: 30,
+                minbitrate: 10000,
+                maxbitrate: 48000,
+                delivery: [1, 2, 3],
+                pos: 1,
+                playbackend: 1,
+                adPodDurationSec: 30,
+                durationRangeSec: [1, 30],
+                skip: 0,
+                minduration: 5,
+                startdelay: 5,
+                playbackmethod: [1, 3],
+                placement: 2
+              }
+            },
+            params: {
+              plc: '123'
+            },
+          },
+        ];
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const ortbRequest = request.data;
+        expect(ortbRequest.imp).to.have.lengthOf(1);
+        expect(ortbRequest.imp[0].video.skip).to.equal(0);
+      });
+    }
+
+    if (FEATURES.NATIVE) {
+      it('native request test without assests', function () {
+        const bidRequests = [
+          {
+            mediaTypes: {
+              native: {}
+            },
+            params: {
+              'plc': '123a'
+            }
+          },
+        ];
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const ortbRequest = request.data;
+        expect(ortbRequest.imp[0].native).to.be.undefined;
+      });
+    }
+
+    if (FEATURES.NATIVE) {
+      it('native request with assets', function () {
+        const assets = [{
+          required: 1,
+          id: 1,
+          img: {
+            type: 3,
+            wmin: 100,
+            hmin: 100,
+          }
+        },
+        {
+          required: 1,
+          id: 2,
+          title: {
+            len: 140,
+          }
+        },
+        {
+          required: 1,
+          id: 3,
+          data: {
+            type: 1,
+          }
+        }];
+        const bidRequests = [
+          {
+            mediaTypes: {
+              native: {}
+            },
+            nativeOrtbRequest: {
+              assets: assets
+            },
+            params: {}
+          },
+        ];
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const ortbRequest = request.data;
+
+        expect(ortbRequest.imp[0].native.request).to.not.be.null;
+        const nativeRequest = JSON.parse(ortbRequest.imp[0].native.request);
+        expect(nativeRequest).to.have.property('assets');
+        expect(nativeRequest.assets).to.deep.equal(assets);
+      });
+    }
+
+    it('should properly build a request when coppa flag is true', function () {
+      const bidRequests = [];
+      const bidderRequest = {};
+      config.setConfig({ coppa: true });
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.regs.coppa).to.equal(1);
+    });
+
+    it('should properly build a request when coppa flag is false', function () {
+      const bidRequests = [];
+      const bidderRequest = {};
+      config.setConfig({ coppa: false });
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.regs.coppa).to.equal(0);
+    });
+
+    it('should properly build a request when coppa flag is not defined', function () {
+      const bidRequests = [];
+      const bidderRequest = {};
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.regs?.coppa).to.be.undefined;
+    });
+
+    it('build a banner request with bidFloor', function () {
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50], [720, 90]]
+            }
+          },
+          params: {
+            plc: '123a',
+            bidfloor: 1,
+            bidfloorcur: 'USD'
+          }
+        }
+      ];
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1);
+      expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD');
+    });
+
+    it('build a banner request with getFloor', function () {
+      const bidRequests = [
+        {
+          bidder: 'inmobi',
+          adUnitCode: 'impId',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123a'
+          },
+          getFloor: inputParams => {
+            return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' };
+          }
+        }
+      ];
+      const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+      expect(ortbRequest.imp[0].bidfloor).equal(1.23);
+      expect(ortbRequest.imp[0].bidfloorcur).equal('USD');
+    });
+
+    if (FEATURES.VIDEO) {
+      it('build a video request with bidFloor', function () {
+        const bidRequests = [
+          {
+            bidder: 'inmobi',
+            adUnitCode: 'impId',
+            mediaTypes: {
+              video: {
+                playerSize: [[480, 320], [720, 480]]
+              }
+            },
+            params: {
+              plc: '123a',
+              bidfloor: 1,
+              bidfloorcur: 'USD'
+            }
+          }
+        ];
+        const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+        expect(ortbRequest.imp[0].bidfloor).to.equal(1);
+        expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD');
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('build a video request with getFloor', function () {
+        const bidRequests = [
+          {
+            bidder: 'inmobi',
+            adUnitCode: 'impId',
+            mediaTypes: {
+              video: {
+                playerSize: [[480, 320], [720, 480]]
+              }
+            },
+            params: {
+              plc: '123a'
+            },
+            getFloor: inputParams => {
+              return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' };
+            }
+          }
+        ];
+        const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+        expect(ortbRequest.imp[0].bidfloor).to.equal(1.23);
+        expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD');
+      });
+    }
+
+    if (FEATURES.NATIVE && FEATURES.VIDEO) {
+      it('build a mutli format request with getFloor', function () {
+        const assets = [{
+          required: 1,
+          id: 1,
+          img: {
+            type: 3,
+            wmin: 100,
+            hmin: 100,
+          }
+        },
+        {
+          required: 1,
+          id: 2,
+          title: {
+            len: 140,
+          }
+        },
+        {
+          required: 1,
+          id: 3,
+          data: {
+            type: 1,
+          }
+        }];
+        const bidRequests = [
+          {
+            bidder: 'inmobi',
+            adUnitCode: 'impId',
+            mediaTypes: {
+              banner: {
+                sizes: [[320, 50], [720, 90]]
+              },
+              video: {
+                playerSize: [640, 480],
+              },
+              native: {
+
+              }
+            },
+            nativeOrtbRequest: {
+              assets: assets
+            },
+            params: {
+              plc: '12456'
+            },
+            getFloor: inputParams => {
+              return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' };
+            }
+          },
+        ];
+        const bidderRequest = {};
+        const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+        expect(ortbRequest.imp[0].banner).not.to.be.undefined;
+        expect(ortbRequest.imp[0].video).not.to.be.undefined;
+        expect(ortbRequest.imp[0].native.request).not.to.be.undefined;
+        expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1.23);
+        expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD');
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('build a multi imp request', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          mediaTypes: {
+            video: {
+              context: 'inbanner',
+              playerSize: [640, 360],
+              mimes: ['video/mp4', 'video/x-flv'],
+              maxduration: 30,
+              api: [1, 2],
+              protocols: [2, 3],
+              plcmt: 3,
+              w: 640,
+              h: 360,
+              linearity: 1,
+              skipmin: 30,
+              skipafter: 30,
+              minbitrate: 10000,
+              maxbitrate: 48000,
+              delivery: [1, 2, 3],
+              pos: 1,
+              playbackend: 1,
+              adPodDurationSec: 30,
+              durationRangeSec: [1, 30],
+              skip: 1,
+              minduration: 5,
+              startdelay: 5,
+              playbackmethod: [1, 3],
+              placement: 2
+            }
+          },
+          params: {
+            plc: '123'
+          },
+        }, {
+          adUnitCode: 'impId',
+          bidId: 'bidId2',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123',
+          }
+        }];
+        const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+        expect(ortbRequest.imp).to.have.lengthOf(2);
+        expect(ortbRequest.imp[0].video.skip).to.equal(1);
+        expect(ortbRequest.imp[0].video.minduration).to.equal(5);
+        expect(ortbRequest.imp[0].video.startdelay).to.equal(5);
+        expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]);
+        expect(ortbRequest.imp[0].video.placement).to.equal(2);
+        // banner test
+        expect(ortbRequest.imp[1].banner).not.to.be.undefined;
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('build a multi format request', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          mediaTypes: {
+            video: {
+              context: 'inbanner',
+              playerSize: [640, 360],
+              mimes: ['video/mp4', 'video/x-flv'],
+              maxduration: 30,
+              api: [1, 2],
+              protocols: [2, 3],
+              plcmt: 3,
+              w: 640,
+              h: 360,
+              linearity: 1,
+              skipmin: 30,
+              skipafter: 30,
+              minbitrate: 10000,
+              maxbitrate: 48000,
+              delivery: [1, 2, 3],
+              pos: 1,
+              playbackend: 1,
+              adPodDurationSec: 30,
+              durationRangeSec: [1, 30],
+              skip: 1,
+              minduration: 5,
+              startdelay: 5,
+              playbackmethod: [1, 3],
+              placement: 2
+            },
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123'
+          },
+        }];
+        const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data;
+        expect(ortbRequest.imp).to.have.lengthOf(1);
+        expect(ortbRequest.imp[0].video.skip).to.equal(1);
+        expect(ortbRequest.imp[0].video.minduration).to.equal(5);
+        expect(ortbRequest.imp[0].video.startdelay).to.equal(5);
+        expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]);
+        expect(ortbRequest.imp[0].video.placement).to.equal(2);
+        // banner test
+        expect(ortbRequest.imp[0].banner).not.to.be.undefined;
+      });
+    }
+  });
+
+  describe('getUserSyncs', function () {
+    const syncEndPoint = 'https://sync.inmobi.com/prebidjs?';
+
+    it('should return empty if iframe disabled and pixelEnabled is false', function () {
+      const res = spec.getUserSyncs({});
+      expect(res).to.deep.equal([]);
+    });
+
+    it('should return user sync if iframe enabled is true', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true });
+      expect(res).to.deep.equal([{ type: 'iframe', url: syncEndPoint }]);
+    });
+
+    it('should return user sync if iframe enabled is true and gdpr not present', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false });
+      expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}` }]);
+    });
+
+    it('should return user sync if iframe enabled is true and gdpr = 1 and gdpr consent present', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' });
+      expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}gdpr=1&gdpr_consent=GDPR_CONSENT` }]);
+    });
+
+    it('should return user sync if iframe enabled is true and gdpr = 1 , gdpr consent present and usp_consent present', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }, 'USP_CONSENT');
+      expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` }]);
+    });
+
+    it('should return user sync if iframe enabled is true  usp_consent present and gppConsent present', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: undefined }, 'USP_CONSENT', { gppString: 'GPP_STRING', applicableSections: [32, 51] });
+      expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51` }]);
+    });
+
+    const responses = [{
+      body: {
+        ext: {
+          prebidjs: {
+            urls: [
+              {
+                url: 'https://sync.inmobi.com/prebidjs'
+              },
+              {
+                url: 'https://eus.rubiconproject.com/usync.html?p=test'
+              }
+            ]
+          }
+        }
+      }
+    }];
+
+    it('should return urls from response when iframe enabled is false and pixel enabled', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses);
+      expect(res).to.deep.equal([
+        { type: 'image', url: 'https://sync.inmobi.com/prebidjs' },
+        { type: 'image', url: 'https://eus.rubiconproject.com/usync.html?p=test' }
+      ])
+    });
+
+    it('should return urls from response when iframe enabled is false and pixel enabled and empty responses', function () {
+      const responses = [];
+      const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses);
+      expect(res).to.deep.equal([
+        { type: 'image', url: `${syncEndPoint}` }
+      ])
+    });
+
+    it('should return urls from response when iframe enabled is false and pixel enabled and no response', function () {
+      const responses = undefined;
+      const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses);
+      expect(res).to.deep.equal([
+        { type: 'image', url: `${syncEndPoint}` }
+      ])
+    });
+
+    it('should return urls from response when iframe enabled is false and all consent parameters present', function () {
+      const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses, { gdprApplies: true, consentString: 'GDPR_CONSENT' }, 'USP_CONSENT', { gppString: 'GPP_STRING', applicableSections: [32, 51] });
+      expect(res).to.deep.equal([
+        { type: 'image', url: 'https://sync.inmobi.com/prebidjs?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51' },
+        { type: 'image', url: 'https://eus.rubiconproject.com/usync.html?p=test&gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51' }
+      ])
+    });
+  });
+
+  describe('interpretResponse', function () {
+    const bidderRequest = {
+      refererInfo: {
+        page: 'https://raapchikgames.com',
+        topmostLocation: 'https://raapchikgames.com'
+      },
+      timeout: 3000,
+      gdprConsent: {
+        gdprApplies: true,
+        consentString: 'consentDataString',
+        vendorData: {
+          vendorConsents: {
+            '333': 1
+          },
+        },
+        apiVersion: 1,
+      },
+    };
+    function mockResponse(winningBidId, mediaType) {
+      return {
+        id: '95d08af8-2d50-4d75-a411-8ecd9224970e',
+        cur: 'USD',
+        seatbid: [
+          {
+            bid: [
+              {
+                id: '20dd72ed-930f-1000-e56f-07c37a793f30',
+                impid: winningBidId,
+                price: 1.1645,
+                adomain: ['advertiserDomain.sandbox.inmobi.com'],
+                crid: '88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301',
+                w: 320,
+                h: 50,
+                adm: 'test-ad',
+                mtype: mediaType,
+                nurl: 'https://et-l.w.inmobi.com/c.asm/',
+                api: 3,
+                cat: [],
+                ext: {
+                  loggingPercentage: 100,
+                  prebid: {
+                    meta: {
+                      advertiserDomains: ['advertiserDomain.sandbox.inmobi.com'],
+                      networkName: 'inmobi'
+                    }
+                  }
+                }
+              }
+            ]
+          }
+        ],
+        ext: {
+          prebidjs: {
+            urls: [
+              { url: 'https://sync.inmobi.com/prebidjs' },
+              { url: 'https://eus.rubiconproject.com/usync.html?p=test' }
+            ]
+          }
+        }
+      };
+    };
+
+    function mockResponseNative(winningBidId, mediaType) {
+      return {
+        id: '95d08af8-2d50-4d75-a411-8ecd9224970e',
+        cur: 'USD',
+        seatbid: [
+          {
+            bid: [
+              {
+                id: '20dd72ed-930f-1000-e56f-07c37a793f30',
+                impid: winningBidId,
+                price: 1.1645,
+                adomain: ['advertiserDomain.sandbox.inmobi.com'],
+                crid: '88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301',
+                w: 320,
+                h: 50,
+                adm: '{"native":{"ver":"1.2","assets":[{"img":{"w":100,"h":100,"type":3,"url":"https://supply.inmobicdn.net/sandbox-prod-assets/Native_testAd.png"},"id":1,"required":1},{"id":2,"title":{"len":140,"text":"Native-Title-InMobi-Sandbox"},"required":1},{"data":{"type":1,"value":""},"id":3,"required":1},{"data":{"type":2,"value":"InMobi native test - Subtitle"},"id":4,"required":0},{"img":{"w":20,"h":20,"type":1,"url":"https://supply.inmobicdn.net/sandbox-prod-assets/inmobi-Logo-150x150.png"},"id":5,"required":0}],"link":{"clicktrackers":["https://c-eus.w.inmobi.com/"],"url":"https://www.inmobi.com"},"eventtrackers":[{"method":1,"event":1,"url":"https://et-eus.w.inmobi.com/"}]}}',
+                mtype: mediaType,
+                nurl: 'https://et-l.w.inmobi.com/c.asm/',
+                api: 3,
+                cat: [],
+                ext: {
+                  loggingPercentage: 100,
+                  prebid: {
+                    meta: {
+                      advertiserDomains: ['advertiserDomain.sandbox.inmobi.com'],
+                      networkName: 'inmobi'
+                    }
+                  }
+                }
+              }
+            ]
+          }
+        ],
+        ext: {
+          prebidjs: {
+            urls: [
+              { url: 'https://sync.inmobi.com/prebidjs' },
+              { url: 'https://eus.rubiconproject.com/usync.html?p=test' }
+            ]
+          }
+        }
+      };
+    };
+
+    it('returns an empty array when bid response is empty', function () {
+      const bidRequests = [];
+      const response = {};
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const bids = spec.interpretResponse(response, request);
+      expect(bids).to.have.lengthOf(0);
+    });
+
+    it('should return an empty array when there is no bid response', function () {
+      const bidRequests = [];
+      const response = { seatbid: [] };
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const bids = spec.interpretResponse({ body: response }, request);
+      expect(bids).to.have.lengthOf(0);
+    });
+
+    it('return banner response', function () {
+      const bidRequests = [{
+        adUnitCode: 'impId',
+        bidId: 'bidId',
+        mediaTypes: {
+          banner: {
+            sizes: [[320, 50]]
+          }
+        },
+        params: {
+          plc: '124a',
+        }
+      }];
+      const response = mockResponse('bidId', 1);
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const bids = spec.interpretResponse({ body: response }, request);
+      expect(bids).to.have.length(1);
+      expect(bids[0].currency).to.deep.equal('USD');
+      expect(bids[0].mediaType).to.deep.equal('banner');
+      expect(bids[0].requestId).to.deep.equal('bidId');
+      expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30');
+      expect(bids[0].cpm).to.equal(1.1645);
+      expect(bids[0].width).to.equal(320);
+      expect(bids[0].height).to.equal(50);
+      expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+      expect(bids[0].meta.loggingPercentage).to.equal(100);
+      expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+      expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+    });
+
+    it('bid response when banner wins among two ad units', function () {
+      const bidRequests = [{
+        adUnitCode: 'impId',
+        bidId: 'bidId',
+        mediaTypes: {
+          video: {
+            context: 'instream',
+            mimes: ['video/mpeg'],
+            playerSize: [640, 320],
+            protocols: [5, 6],
+            maxduration: 30,
+            api: [1, 2]
+          }
+        },
+        params: {
+          plc: '123',
+        },
+      }, {
+        adUnitCode: 'impId',
+        bidId: 'bidId2',
+        mediaTypes: {
+          banner: {
+            sizes: [[320, 50]]
+          }
+        },
+        params: {
+          plc: '123',
+        }
+      }];
+      const response = mockResponse('bidId2', 1);
+      const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+      const bids = spec.interpretResponse({ body: response }, request);
+      expect(bids[0].currency).to.deep.equal('USD');
+      expect(bids[0].mediaType).to.deep.equal('banner');
+      expect(bids[0].requestId).to.deep.equal('bidId2');
+      expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30');
+      expect(bids[0].cpm).to.equal(1.1645);
+      expect(bids[0].width).to.equal(320);
+      expect(bids[0].height).to.equal(50);
+      expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+      expect(bids[0].meta.loggingPercentage).to.equal(100);
+      expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+      expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+    });
+
+    if (FEATURES.VIDEO) {
+      it('return instream video response', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          mediaTypes: {
+            video: {
+              context: 'instream',
+              mimes: ['video/mpeg'],
+              playerSize: [640, 320],
+              protocols: [5, 6],
+              maxduration: 30,
+              api: [1, 2]
+            }
+          },
+          params: {
+            plc: '123a1',
+          },
+        }];
+        const response = mockResponse('bidId', 2);
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const bids = spec.interpretResponse({ body: response }, request);
+        expect(bids).to.have.lengthOf(1);
+        expect(bids[0].mediaType).to.equal(VIDEO);
+        expect(bids[0].currency).to.deep.equal('USD');
+        expect(bids[0].requestId).to.deep.equal('bidId');
+        expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30');
+        expect(bids[0].cpm).to.equal(1.1645);
+        expect(bids[0].playerWidth).to.equal(640);
+        expect(bids[0].playerHeight).to.equal(320);
+        expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+        expect(bids[0].meta.loggingPercentage).to.equal(100);
+        expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+        expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+        expect(bids[0].vastUrl).to.equal('https://et-l.w.inmobi.com/c.asm/');
+        expect(bids[0].vastXml).to.equal('test-ad');
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('return video outstream response', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          mediaTypes: {
+            video: {
+              context: 'outstream',
+              mimes: ['video/mpeg'],
+              playerSize: [640, 320],
+              protocols: [5, 6],
+              maxduration: 30,
+              api: [1, 2]
+            }
+          },
+          params: {
+            plc: '123a1',
+          },
+        }];
+        const response = mockResponse('bidId', 2);
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const bids = spec.interpretResponse({ body: response }, request);
+        expect(bids).to.have.lengthOf(1);
+        expect(bids[0].mediaType).to.equal(VIDEO);
+        expect(bids[0].currency).to.deep.equal('USD');
+        expect(bids[0].requestId).to.deep.equal('bidId');
+        expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30');
+        expect(bids[0].cpm).to.equal(1.1645);
+        expect(bids[0].playerWidth).to.equal(640);
+        expect(bids[0].playerHeight).to.equal(320);
+        expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+        expect(bids[0].meta.loggingPercentage).to.equal(100);
+        expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+        expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+        expect(bids[0].vastUrl).to.equal('https://et-l.w.inmobi.com/c.asm/');
+        expect(bids[0].vastXml).to.equal('test-ad');
+      });
+    }
+
+    if (FEATURES.VIDEO) {
+      it('bid response when video wins among two ad units', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          mediaTypes: {
+            video: {
+              context: 'instream',
+              mimes: ['video/mpeg'],
+              playerSize: [640, 320],
+              protocols: [5, 6],
+              maxduration: 30,
+              api: [1, 2]
+            }
+          },
+          params: {
+            plc: '123',
+          },
+        }, {
+          adUnitCode: 'impId',
+          bidId: 'bidId2',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123',
+          }
+        }];
+        const response = mockResponse('bidId', 2);
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const bids = spec.interpretResponse({ body: response }, request);
+        expect(bids).to.have.lengthOf(1);
+        expect(bids[0].mediaType).to.equal(VIDEO);
+        expect(bids[0].currency).to.deep.equal('USD');
+        expect(bids[0].requestId).to.deep.equal('bidId');
+        expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30');
+        expect(bids[0].cpm).to.equal(1.1645);
+        expect(bids[0].playerWidth).to.equal(640);
+        expect(bids[0].playerHeight).to.equal(320);
+        expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+        expect(bids[0].meta.loggingPercentage).to.equal(100);
+        expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+        expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+        expect(bids[0].vastUrl).to.equal('https://et-l.w.inmobi.com/c.asm/');
+        expect(bids[0].vastXml).to.equal('test-ad');
+      });
+    }
+
+    if (FEATURES.NATIVE) {
+      it('should correctly parse a native bid response', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          params: {
+            plc: '123',
+          },
+          native: true,
+          bidder: 'inmobi',
+          mediaTypes: {
+            native: {
+              image: {
+                required: true,
+                sizes: [120, 60],
+                sendId: true,
+                sendTargetingKeys: false
+              }
+            }
+          }
+        }];
+        const response = mockResponseNative('bidId', 4);
+        const expectedAdmNativeOrtb = JSON.parse(response.seatbid[0].bid[0].adm).native;
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const bids = spec.interpretResponse({ body: response }, request);
+
+        expect(bids).to.have.lengthOf(1);
+        expect(bids[0].mediaType).to.equal(NATIVE);
+        // testing
+        expect(bids[0].requestId).to.deep.equal('bidId');
+        expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30')
+        expect(bids[0].cpm).to.deep.equal(1.1645);
+        expect(bids[0].currency).to.deep.equal('USD');
+        expect(bids[0].width).to.deep.equal(320);
+        expect(bids[0].height).to.deep.equal(50);
+        expect(bids[0].ad).to.equal(undefined);
+        expect(bids[0].native.ortb).not.to.be.null;
+        expect(bids[0].native.ortb).to.deep.equal(expectedAdmNativeOrtb);
+        expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+        expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+        expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+      });
+    }
+
+    if (FEATURES.NATIVE) {
+      it('should correctly parse a native bid response when there are two ad units', function () {
+        const bidRequests = [{
+          adUnitCode: 'impId',
+          bidId: 'bidId',
+          params: {
+            plc: '123',
+          },
+          native: true,
+          bidder: 'inmobi',
+          mediaTypes: {
+            native: {
+              image: {
+                required: true,
+                sizes: [120, 60],
+                sendId: true,
+                sendTargetingKeys: false
+              }
+            }
+          }
+        },
+        {
+          adUnitCode: 'impId',
+          bidId: 'bidId2',
+          mediaTypes: {
+            banner: {
+              sizes: [[320, 50]]
+            }
+          },
+          params: {
+            plc: '123',
+          }
+        }];
+        const response = mockResponseNative('bidId', 4);
+        const expectedAdmNativeOrtb = JSON.parse(response.seatbid[0].bid[0].adm).native;
+        const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest));
+        const bids = spec.interpretResponse({ body: response }, request);
+        expect(bids).to.have.lengthOf(1);
+        expect(bids[0].mediaType).to.equal(NATIVE);
+        // testing
+        expect(bids[0].requestId).to.deep.equal('bidId');
+        expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30')
+        expect(bids[0].cpm).to.deep.equal(1.1645);
+        expect(bids[0].currency).to.deep.equal('USD');
+        expect(bids[0].width).to.deep.equal(320);
+        expect(bids[0].height).to.deep.equal(50);
+        expect(bids[0].ad).to.equal(undefined);
+        expect(bids[0].native.ortb).not.to.be.null;
+        expect(bids[0].native.ortb).to.deep.equal(expectedAdmNativeOrtb);
+        expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301');
+        expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com');
+        expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi');
+      });
+    }
+  });
+});

From a60d6679b355c37bc6d3ad8bcaf5b2155163f005 Mon Sep 17 00:00:00 2001
From: pm-nitin-shirsat <107102698+pm-nitin-shirsat@users.noreply.github.com>
Date: Thu, 21 Nov 2024 20:58:50 +0530
Subject: [PATCH 0689/1097] PubMatic Bid Adapter : support for InBannerVideo
 (IBV) Field in Bid Response with meta.mediaType (#12484)

Support for InBannerVideo (IBV) Field in Bid Response with meta.mediaType
---
 modules/pubmaticBidAdapter.js                | 23 ++++-
 test/spec/modules/pubmaticBidAdapter_spec.js | 88 +++++++++++++++++++-
 2 files changed, 105 insertions(+), 6 deletions(-)

diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index a03091f1f4a..79a68a9b87b 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -859,6 +859,18 @@ function _handleEids(payload, validBidRequests) {
   }
 }
 
+// Setting IBV & meta.mediaType field into the bid response
+export function setIBVField(bid, newBid) {
+  if (bid?.ext?.ibv) {
+    newBid.ext = newBid.ext || {};
+    newBid.ext['ibv'] = bid.ext.ibv;
+
+    // Overriding the mediaType field in meta with the `video` value if bid.ext.ibv is present
+    newBid.meta = newBid.meta || {};
+    newBid.meta.mediaType = VIDEO;
+  }
+}
+
 function _checkMediaType(bid, newBid) {
   // Create a regex here to check the strings
   if (bid.ext && bid.ext['bidtype'] != undefined) {
@@ -1008,7 +1020,7 @@ function isNonEmptyArray(test) {
  * @param {*} bid : bids
  */
 export function prepareMetaObject(br, bid, seat) {
-  br.meta = {};
+  br.meta = br.meta || {};
 
   if (bid.ext && bid.ext.dspid) {
     br.meta.networkId = bid.ext.dspid;
@@ -1047,6 +1059,11 @@ export function prepareMetaObject(br, bid, seat) {
   if (bid.ext && bid.ext.dsa && Object.keys(bid.ext.dsa).length) {
     br.meta.dsa = bid.ext.dsa;
   }
+
+  // Initializing meta.mediaType field to the actual bidType returned by the bidder
+  if (br.mediaType) {
+    br.meta.mediaType = br.mediaType;
+  }
 }
 
 export const spec = {
@@ -1401,12 +1418,12 @@ export const spec = {
                   }
                 });
               }
+              prepareMetaObject(newBid, bid, seatbidder.seat);
+              setIBVField(bid, newBid);
               if (bid.ext && bid.ext.deal_channel) {
                 newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null;
               }
 
-              prepareMetaObject(newBid, bid, seatbidder.seat);
-
               // adserverTargeting
               if (seatbidder.ext && seatbidder.ext.buyid) {
                 newBid.adserverTargeting = {
diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js
index 71b22e25272..6489d8ef0d5 100644
--- a/test/spec/modules/pubmaticBidAdapter_spec.js
+++ b/test/spec/modules/pubmaticBidAdapter_spec.js
@@ -1,5 +1,5 @@
 import { expect } from 'chai';
-import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType } from 'modules/pubmaticBidAdapter.js';
+import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField } from 'modules/pubmaticBidAdapter.js';
 import * as utils from 'src/utils.js';
 import { config } from 'src/config.js';
 import { createEidsArray } from 'modules/userId/eids.js';
@@ -3579,6 +3579,33 @@ describe('PubMatic adapter', function () {
         expect(response[0].renderer).to.not.exist;
       });
 
+      it('should set ibv field in bid.ext when bid.ext.ibv exists', function() {
+        let request = spec.buildRequests(bidRequests, {
+          auctionId: 'new-auction-id'
+        });
+
+        let copyOfBidResponse = utils.deepClone(bannerBidResponse);
+        let bidExt = utils.deepClone(copyOfBidResponse.body.seatbid[0].bid[0].ext);
+        copyOfBidResponse.body.seatbid[0].bid[0].ext = Object.assign(bidExt, {
+          ibv: true
+        });
+
+        let response = spec.interpretResponse(copyOfBidResponse, request);
+        expect(response[0].ext.ibv).to.equal(true);
+        expect(response[0].meta.mediaType).to.equal('video');
+      });
+
+      it('should not set ibv field when bid.ext does not exist ', function() {
+        let request = spec.buildRequests(bidRequests, {
+          auctionId: 'new-auction-id'
+        });
+
+        let response = spec.interpretResponse(bannerBidResponse, request);
+        expect(response[0].ext).to.not.exist;
+        expect(response[0].meta).to.exist;
+        expect(response[0].meta.mediaType).to.equal('banner');
+      });
+
       if (FEATURES.VIDEO) {
         it('should check for valid video mediaType in case of multiformat request', function() {
           let request = spec.buildRequests(videoBidRequests, {
@@ -3878,10 +3905,12 @@ describe('PubMatic adapter', function () {
             // dchain: 'dc',
             // demandSource: 'ds',
             // secondaryCatIds: ['secondaryCatIds']
-          }
+          },
         };
 
-        const br = {};
+        const br = {
+          mediaType: 'video'
+        };
         prepareMetaObject(br, bid, null);
         expect(br.meta.networkId).to.equal(6); // dspid
         expect(br.meta.buyerId).to.equal('12'); // adid
@@ -3900,6 +3929,7 @@ describe('PubMatic adapter', function () {
         expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain
         expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain
         expect(br.meta.dsa).to.equal(dsa); // dsa
+        expect(br.meta.mediaType).to.equal('video'); // mediaType
       });
 
       it('Should be empty, when ext and adomain is absent in bid object', function () {
@@ -4176,6 +4206,58 @@ describe('PubMatic adapter', function () {
         expect(data.imp[0]['banner']['battr']).to.equal(undefined);
       });
     });
+
+    describe('setIBVField', function() {
+      it('should set ibv field in newBid.ext when bid.ext.ibv exists', function() {
+        const bid = {
+          ext: {
+            ibv: true
+          }
+        };
+        const newBid = {};
+        setIBVField(bid, newBid);
+        expect(newBid.ext).to.exist;
+        expect(newBid.ext.ibv).to.equal(true);
+        expect(newBid.meta).to.exist;
+        expect(newBid.meta.mediaType).to.equal('video');
+      });
+
+      it('should not set ibv field when bid.ext.ibv does not exist', function() {
+        const bid = {
+          ext: {}
+        };
+        const newBid = {};
+        setIBVField(bid, newBid);
+        expect(newBid.ext).to.not.exist;
+        expect(newBid.meta).to.not.exist;
+      });
+
+      it('should not set ibv field when bid.ext does not exist', function() {
+        const bid = {};
+        const newBid = {};
+        setIBVField(bid, newBid);
+        expect(newBid.ext).to.not.exist;
+        expect(newBid.meta).to.not.exist;
+      });
+
+      it('should preserve existing newBid.ext properties', function() {
+        const bid = {
+          ext: {
+            ibv: true
+          }
+        };
+        const newBid = {
+          ext: {
+            existingProp: 'should remain'
+          }
+        };
+        setIBVField(bid, newBid);
+        expect(newBid.ext.existingProp).to.equal('should remain');
+        expect(newBid.ext.ibv).to.equal(true);
+        expect(newBid.meta).to.exist;
+        expect(newBid.meta.mediaType).to.equal('video');
+      });
+    });
   });
 
   if (FEATURES.VIDEO) {

From e11a5111b2be931e29f64b3ac1c1a6d529dd3f7e Mon Sep 17 00:00:00 2001
From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com>
Date: Thu, 21 Nov 2024 21:50:45 +0530
Subject: [PATCH 0690/1097] PubMatic Bid Adapter: Updated default TTL and added
 mediaType based TTL (#12487)

* setting TTL as per bid response or mediatype

* added test cases for TTL

* setting default ttl as failsafe

* added a test case for default ttl

* Using 360 as default ttl

* updated test cases with defualt ttl value

---------

Co-authored-by: pm-azhar-mulla 
---
 modules/pubmaticBidAdapter.js                | 15 +++++-
 test/spec/modules/pubmaticBidAdapter_spec.js | 51 ++++++++++++++++++--
 2 files changed, 62 insertions(+), 4 deletions(-)

diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index 79a68a9b87b..8952d8d1852 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -22,6 +22,7 @@ const AUCTION_TYPE = 1;
 const UNDEFINED = undefined;
 const DEFAULT_WIDTH = 0;
 const DEFAULT_HEIGHT = 0;
+const DEFAULT_TTL = 360;
 const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html';
 const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com)
 const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application
@@ -140,6 +141,12 @@ const MEDIATYPE = [
   NATIVE
 ]
 
+const MEDIATYPE_TTL = {
+  'banner': 360,
+  'video': 1800,
+  'native': 1800
+};
+
 let publisherId = 0;
 let isInvalidNativeRequest = false;
 let biddersList = ['pubmatic'];
@@ -859,6 +866,11 @@ function _handleEids(payload, validBidRequests) {
   }
 }
 
+export function setTTL(bid, newBid) {
+  let ttl = MEDIATYPE_TTL[newBid?.mediaType] || DEFAULT_TTL;
+  newBid.ttl = bid.exp || ttl;
+}
+
 // Setting IBV & meta.mediaType field into the bid response
 export function setIBVField(bid, newBid) {
   if (bid?.ext?.ibv) {
@@ -1390,7 +1402,7 @@ export const spec = {
                 dealId: bid.dealid,
                 currency: respCur,
                 netRevenue: NET_REVENUE,
-                ttl: 300,
+                ttl: DEFAULT_TTL,
                 referrer: parsedReferrer,
                 ad: bid.adm,
                 pm_seat: seatbidder.seat || null,
@@ -1401,6 +1413,7 @@ export const spec = {
                 parsedRequest.imp.forEach(req => {
                   if (bid.impid === req.id) {
                     _checkMediaType(bid, newBid);
+                    setTTL(bid, newBid);
                     switch (newBid.mediaType) {
                       case BANNER:
                         break;
diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js
index 6489d8ef0d5..834db255e01 100644
--- a/test/spec/modules/pubmaticBidAdapter_spec.js
+++ b/test/spec/modules/pubmaticBidAdapter_spec.js
@@ -1,5 +1,5 @@
 import { expect } from 'chai';
-import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField } from 'modules/pubmaticBidAdapter.js';
+import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField, setTTL } from 'modules/pubmaticBidAdapter.js';
 import * as utils from 'src/utils.js';
 import { config } from 'src/config.js';
 import { createEidsArray } from 'modules/userId/eids.js';
@@ -3463,7 +3463,7 @@ describe('PubMatic adapter', function () {
         expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid);
         expect(response[0].currency).to.equal('USD');
         expect(response[0].netRevenue).to.equal(true);
-        expect(response[0].ttl).to.equal(300);
+        expect(response[0].ttl).to.equal(360);
         expect(response[0].meta.networkId).to.equal(123);
         expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987');
         expect(response[0].meta.buyerId).to.equal('seat-id');
@@ -3488,7 +3488,7 @@ describe('PubMatic adapter', function () {
         expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid);
         expect(response[1].currency).to.equal('USD');
         expect(response[1].netRevenue).to.equal(true);
-        expect(response[1].ttl).to.equal(300);
+        expect(response[1].ttl).to.equal(360);
         expect(response[1].meta.networkId).to.equal(422);
         expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789');
         expect(response[1].meta.buyerId).to.equal(832);
@@ -4383,4 +4383,49 @@ describe('PubMatic adapter', function () {
       expect(response[0].bidderCode).to.equal('groupm');
     });
   });
+
+  describe('setTTL', function() {
+    it('should set ttl field in newBid.ttl when bid.exp exists', function() {
+      const bid = {
+        exp: 200
+      };
+      const newBid = {};
+      setTTL(bid, newBid);
+      expect(newBid.ttl).to.equal(200);
+    });
+
+    it('should set ttl as 360 mediatype banner', function() {
+      const bid = {};
+      const newBid = {
+        mediaType: 'banner'
+      };
+      setTTL(bid, newBid);
+      expect(newBid.ttl).to.equal(360);
+    });
+
+    it('should set ttl as 1800 mediatype video', function() {
+      const bid = {};
+      const newBid = {
+        mediaType: 'video'
+      };
+      setTTL(bid, newBid);
+      expect(newBid.ttl).to.equal(1800);
+    });
+
+    it('should set ttl as 1800 mediatype native', function() {
+      const bid = {};
+      const newBid = {
+        mediaType: 'native'
+      };
+      setTTL(bid, newBid);
+      expect(newBid.ttl).to.equal(1800);
+    });
+
+    it('should set ttl as 360 as default if all condition fails', function() {
+      const bid = {};
+      const newBid = {};
+      setTTL(bid, newBid);
+      expect(newBid.ttl).to.equal(360);
+    });
+  });
 });

From bdc2edc0936ec6a109a26015bb4d316dc3eca829 Mon Sep 17 00:00:00 2001
From: "Prebid.js automated release" 
Date: Thu, 21 Nov 2024 17:37:53 +0000
Subject: [PATCH 0691/1097] Prebid 9.20.0 release

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 50319bec3c7..f6ea2859cf1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "prebid.js",
-  "version": "9.20.0-pre",
+  "version": "9.20.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "prebid.js",
-      "version": "9.20.0-pre",
+      "version": "9.20.0",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index 151f324f5da..91db2ef29c4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "prebid.js",
-  "version": "9.20.0-pre",
+  "version": "9.20.0",
   "description": "Header Bidding Management Library",
   "main": "src/prebid.public.js",
   "exports": {

From a0d075aea76a2af2ec56df40db143335cbcd3c2b Mon Sep 17 00:00:00 2001
From: "Prebid.js automated release" 
Date: Thu, 21 Nov 2024 17:37:53 +0000
Subject: [PATCH 0692/1097] Increment version to 9.21.0-pre

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f6ea2859cf1..d781c075fa5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "prebid.js",
-  "version": "9.20.0",
+  "version": "9.21.0-pre",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "prebid.js",
-      "version": "9.20.0",
+      "version": "9.21.0-pre",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index 91db2ef29c4..3e1a7b6ece0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "prebid.js",
-  "version": "9.20.0",
+  "version": "9.21.0-pre",
   "description": "Header Bidding Management Library",
   "main": "src/prebid.public.js",
   "exports": {

From 48997e53e5cbf6fc244f4aee4057a7e8248f6942 Mon Sep 17 00:00:00 2001
From: rrochwick <65189775+rrochwick@users.noreply.github.com>
Date: Thu, 21 Nov 2024 16:57:47 -0500
Subject: [PATCH 0693/1097] Qortex RTD Module: support messaging dispatch &
 receive + rate limits (#12392)

* Qortex RTD module messaging dispatch & receive

* Added additional JSDoc comments for readability

* Rate limiting feature into QortexRTD content lookup

* Updated test case to catch 429 status code

* Check for bid enrichment toggle at external message received
---
 modules/qortexRtdProvider.js                | 132 ++++++++++++++------
 test/spec/modules/qortexRtdProvider_spec.js |  73 +++++++++--
 2 files changed, 158 insertions(+), 47 deletions(-)

diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js
index 3505f64789a..b856c3aa9ee 100644
--- a/modules/qortexRtdProvider.js
+++ b/modules/qortexRtdProvider.js
@@ -7,8 +7,11 @@ import { EVENTS } from '../src/constants.js';
 import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
 
 const DEFAULT_API_URL = 'https://demand.qortex.ai';
+const LOOKUP_RATE_LIMIT = 5000;
+const qortexSessionInfo = {};
 
-const qortexSessionInfo = {}
+let lookupRateLimitTimeout;
+let lookupAttemptDelay;
 
 /**
  * Init if module configuration is valid
@@ -93,8 +96,16 @@ function onAuctionEndEvent (data, config, t) {
  * @returns {Promise} ortb Content object
  */
 export function getContext () {
-  if (!qortexSessionInfo.currentSiteContext) {
-    const pageUrlObject = { pageUrl: qortexSessionInfo.indexData?.pageUrl ?? '' }
+  if (qortexSessionInfo.currentSiteContext) {
+    logMessage('Adding Content object from existing context data');
+    return new Promise((resolve, reject) => resolve(qortexSessionInfo.currentSiteContext));
+  } else if (lookupRateLimitTimeout !== null) {
+    logMessage(`Content lookup attempted during rate limit waiting period of ${LOOKUP_RATE_LIMIT}ms.`);
+    lookupAttemptDelay = true;
+    return new Promise((resolve, reject) => reject(new Error(429)));
+  } else {
+    lookupRateLimitTimeout = setTimeout(resetRateLimitTimeout, LOOKUP_RATE_LIMIT);
+    const pageUrlObject = { pageUrl: document.location.href ?? '' }
     logMessage('Requesting new context data');
     return new Promise((resolve, reject) => {
       const callbacks = {
@@ -114,9 +125,20 @@ export function getContext () {
       }
       ajax(qortexSessionInfo.contextUrl, callbacks, JSON.stringify(pageUrlObject), {contentType: 'application/json'})
     })
-  } else {
-    logMessage('Adding Content object from existing context data');
-    return new Promise((resolve, reject) => resolve(qortexSessionInfo.currentSiteContext));
+  }
+}
+
+/**
+ * Resets rate limit timeout for preventing overactive page content lookup
+ * Will re-trigger requestContextData if page lookups had been previously delayed
+ */
+export function resetRateLimitTimeout() {
+  lookupRateLimitTimeout = null;
+  if (lookupAttemptDelay) {
+    lookupAttemptDelay = false;
+    if (!qortexSessionInfo.currentSiteContext) {
+      requestContextData();
+    }
   }
 }
 
@@ -144,26 +166,22 @@ export function getGroupConfig () {
  * @returns {Promise}
  */
 export function sendAnalyticsEvent(eventType, subType, data) {
-  if (qortexSessionInfo.analyticsUrl !== null) {
-    if (shouldSendAnalytics(data)) {
-      const analtyicsEventObject = generateAnalyticsEventObject(eventType, subType, data)
-      logMessage('Sending qortex analytics event');
-      return new Promise((resolve, reject) => {
-        const callbacks = {
-          success() {
-            resolve();
-          },
-          error(e, x) {
-            reject(new Error('Returned error status code: ' + x.status));
-          }
+  if (shouldSendAnalytics(data)) {
+    const analtyicsEventObject = generateAnalyticsEventObject(eventType, subType, data)
+    logMessage('Sending qortex analytics event');
+    return new Promise((resolve, reject) => {
+      const callbacks = {
+        success() {
+          resolve();
+        },
+        error(e, x) {
+          reject(new Error('Returned error status code: ' + x.status));
         }
-        ajax(qortexSessionInfo.analyticsUrl, callbacks, JSON.stringify(analtyicsEventObject), {contentType: 'application/json'})
-      })
-    } else {
-      return new Promise((resolve, reject) => reject(new Error('Current request did not meet analytics percentage threshold, cancelling sending event')));
-    }
+      }
+      ajax(qortexSessionInfo.analyticsUrl, callbacks, JSON.stringify(analtyicsEventObject), {contentType: 'application/json'})
+    })
   } else {
-    return new Promise((resolve, reject) => reject(new Error('Analytics host not initialized')));
+    return new Promise((resolve, reject) => reject(new Error('Current request did not meet analytics percentage threshold, cancelling sending event')));
   }
 }
 
@@ -265,22 +283,37 @@ export function loadScriptTag(config) {
   loadExternalScript(src, MODULE_TYPE_RTD, code, undefined, undefined, attr);
 }
 
+/**
+ * Request contextual data about page (after checking for allow) and begin listening for postMessages from publisher
+ */
 export function initializeBidEnrichment() {
   if (shouldAllowBidEnrichment()) {
-    getContext()
-      .then(contextData => {
-        if (qortexSessionInfo.pageAnalysisData.contextRetrieved) {
-          logMessage('Contextual record Received from Qortex API')
-          setContextData(contextData)
-        } else {
-          logWarn('Contexual record is not yet complete at this time')
-        }
-      })
-      .catch((e) => {
-        logWarn('Returned error status code: ' + e.message)
-      })
+    requestContextData()
   }
+  addEventListener('message', windowPostMessageReceived);
 }
+
+/**
+ * Call Qortex api for available contextual information about current environment
+ */
+export function requestContextData() {
+  getContext()
+    .then(contextData => {
+      if (qortexSessionInfo.pageAnalysisData.contextRetrieved) {
+        logMessage('Contextual record Received from Qortex API')
+        setContextData(contextData)
+      } else {
+        logWarn('Contexual record is not yet complete at this time')
+      }
+    })
+    .catch((e) => {
+      logWarn('Returned error status code: ' + e.message)
+      if (e.message == 404) {
+        postBidEnrichmentMessage('NO-CONTEXT')
+      }
+    })
+}
+
 /**
  * Helper function to set initial values when they are obtained by init
  * @param {Object} config module config obtained during init
@@ -302,6 +335,8 @@ export function initializeModuleData(config) {
   qortexSessionInfo.groupConfigUrl = `${qortexUrlBase}/api/v1/prebid/group/configs/${groupId}/${windowUrl}`;
   qortexSessionInfo.contextUrl = `${qortexUrlBase}/api/v1/prebid/${groupId}/page/lookup`;
   qortexSessionInfo.analyticsUrl = generateAnalyticsHostUrl(qortexUrlBase);
+  lookupRateLimitTimeout = null;
+  lookupAttemptDelay = false;
   return qortexSessionInfo;
 }
 
@@ -370,6 +405,31 @@ function shouldAllowBidEnrichment() {
   return true
 }
 
+/**
+ * Passes message out to external page through postMessage method
+ * @param {string} msg message string to be passed to CX-BID-ENRICH target on current page
+ */
+function postBidEnrichmentMessage(msg) {
+  window.postMessage({
+    target: 'CX-BID-ENRICH',
+    message: msg
+  }, window.location.protocol + '//' + window.location.host);
+  logMessage('Message post :: Qortex contextuality has been not been indexed.')
+}
+
+/**
+ * Receives messages passed through postMessage method to QORTEX-PREBIDJS-RTD-MODULE on current page
+ * @param {Object} evt data object holding Event information
+ */
+export function windowPostMessageReceived(evt) {
+  const data = evt.data;
+  if (typeof data.target !== 'undefined' && data.target === 'QORTEX-PREBIDJS-RTD-MODULE') {
+    if (data.message === 'CX-BID-ENRICH-INITIALIZED' && shouldAllowBidEnrichment()) {
+      requestContextData()
+    }
+  }
+}
+
 export const qortexSubmodule = {
   name: 'qortex',
   init,
diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js
index c1ae25e6104..f7a2669a6e7 100644
--- a/test/spec/modules/qortexRtdProvider_spec.js
+++ b/test/spec/modules/qortexRtdProvider_spec.js
@@ -15,7 +15,9 @@ import {
   setGroupConfigData,
   saveContextAdded,
   initializeBidEnrichment,
-  getContextAddedEntry
+  getContextAddedEntry,
+  windowPostMessageReceived,
+  resetRateLimitTimeout
 } from '../../../modules/qortexRtdProvider';
 import {server} from '../../mocks/xhr.js';
 import { cloneDeep } from 'lodash';
@@ -27,12 +29,10 @@ describe('qortexRtdProvider', () => {
 
   const defaultApiHost = 'https://demand.qortex.ai';
   const defaultGroupId = 'test';
-
   const validBidderArray = ['qortex', 'test'];
   const validTagConfig = {
     videoContainer: 'my-video-container'
   }
-
   const validModuleConfig = {
     params: {
       groupId: defaultGroupId,
@@ -58,7 +58,6 @@ describe('qortexRtdProvider', () => {
   const emptyModuleConfig = {
     params: {}
   }
-
   const validImpressionEvent = {
     detail: {
       uid: 'uid123',
@@ -76,17 +75,19 @@ describe('qortexRtdProvider', () => {
       type: 'qx-impression'
     }
   }
+  const validQortexPostMessage = {
+    target: 'QORTEX-PREBIDJS-RTD-MODULE',
+    message: 'CX-BID-ENRICH-INITIALIZED'
+  }
   const invalidTypeQortexEvent = {
     detail: {
       type: 'invalid-type'
     }
   }
-
   const responseHeaders = {
     'content-type': 'application/json',
     'access-control-allow-origin': '*'
   };
-
   const contextResponseObj = {
     content: {
       id: '123456',
@@ -98,7 +99,6 @@ describe('qortexRtdProvider', () => {
     }
   }
   const contextResponse = JSON.stringify(contextResponseObj);
-
   const validGroupConfigResponseObj = {
     groupId: defaultGroupId,
     active: true,
@@ -107,7 +107,6 @@ describe('qortexRtdProvider', () => {
     prebidReportingPercentage: 100
   }
   const validGroupConfigResponse = JSON.stringify(validGroupConfigResponseObj);
-
   const inactiveGroupConfigResponseObj = {
     groupId: defaultGroupId,
     active: false,
@@ -115,7 +114,6 @@ describe('qortexRtdProvider', () => {
     PrebidReportingPercentage: 100
   }
   const inactiveGroupConfigResponse = JSON.stringify(inactiveGroupConfigResponseObj);
-
   const noEnrichmentGroupConfigResponseObj = {
     groupId: defaultGroupId,
     active: true,
@@ -123,7 +121,6 @@ describe('qortexRtdProvider', () => {
     prebidBidEnrichmentPercentage: 0,
     prebidReportingPercentage: 100
   }
-
   const reqBidsConfig = {
     auctionId: '1234',
     adUnits: [{
@@ -275,11 +272,13 @@ describe('qortexRtdProvider', () => {
       setGroupConfigData(validGroupConfigResponseObj);
       callbackSpy = sinon.spy();
       server.reset();
+      global.lookupRateLimitTimeout = null;
     })
 
     afterEach(() => {
       initializeModuleData(emptyModuleConfig);
       callbackSpy.resetHistory();
+      global.lookupRateLimitTimeout = null;
     })
 
     it('will call callback immediately if no adunits', () => {
@@ -340,6 +339,17 @@ describe('qortexRtdProvider', () => {
       }, 200)
     })
 
+    it('Logs warning for rate limit', (done) => {
+      saveContextAdded(reqBidsConfig);
+      const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'};
+      module.onAuctionEndEvent(testData);
+      server.requests[0].respond(429, responseHeaders, JSON.stringify({}));
+      setTimeout(() => {
+        expect(logWarnSpy.calledWith('Returned error status code: 429')).to.be.eql(true);
+        done();
+      }, 200)
+    })
+
     it('will not request context if prebid disable toggle is true', (done) => {
       initializeModuleData(bidEnrichmentDisabledModuleConfig);
       const cb = function () {
@@ -427,6 +437,26 @@ describe('qortexRtdProvider', () => {
         done();
       });
     })
+
+    it('request content object after elapsed rate limit timeout if a second content lookup was delayed', (done) => {
+      const ctx = getContext();
+      server.requests[0].respond(202, responseHeaders, JSON.stringify({}))
+      ctx.then(response => {
+        expect(response).to.be.null;
+        expect(logMessageSpy.calledWith('Requesting new context data')).to.be.true;
+        expect(server.requests.length).to.be.eql(1);
+        expect(logWarnSpy.called).to.be.false;
+      });
+      setTimeout(() => {
+        const ctx2 = getContext();
+        ctx2.catch(e => {
+          expect(e.message).to.equal('429');
+          expect(logMessageSpy.calledWith('Content lookup attempted during rate limit waiting period of 5000ms.')).to.be.true;
+          resetRateLimitTimeout();
+          done();
+        })
+      }, 200)
+    })
   })
 
   describe('addContextToRequests', () => {
@@ -603,7 +633,6 @@ describe('qortexRtdProvider', () => {
     })
 
     afterEach(() => {
-      initializeModuleData(emptyModuleConfig);
       setGroupConfigData(null);
       setContextData(null);
       server.reset();
@@ -626,5 +655,27 @@ describe('qortexRtdProvider', () => {
         done();
       }, 250)
     })
+
+    it('processes incoming qortex component "initialize" message', () => {
+      postMessage(validQortexPostMessage);
+    })
+
+    it('will catch and log error and fire callback', (done) => {
+      initializeBidEnrichment();
+      server.requests[0].respond(404, responseHeaders, JSON.stringify({}));
+      setTimeout(() => {
+        expect(logWarnSpy.calledWith('Returned error status code: 404')).to.be.eql(true);
+        done();
+      }, 250)
+    })
+
+    it('Logs warning for network error', (done) => {
+      initializeBidEnrichment();
+      server.requests[0].respond(500, responseHeaders, JSON.stringify({}));
+      setTimeout(() => {
+        expect(logWarnSpy.calledWith('Returned error status code: 500')).to.be.eql(true);
+        done();
+      }, 200)
+    })
   })
 })

From 8a95ab264f70bcfe026850c80d519f7addcd08a2 Mon Sep 17 00:00:00 2001
From: Philip Watson 
Date: Sat, 23 Nov 2024 01:35:09 +1300
Subject: [PATCH 0694/1097] StroeerCore Bid Adapter: Add campaignType property
 to the bid's meta object (#12488)

---
 modules/stroeerCoreBidAdapter.js                |  3 ++-
 test/spec/modules/stroeerCoreBidAdapter_spec.js | 10 ++++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js
index fedebc72053..5630cfe006f 100644
--- a/modules/stroeerCoreBidAdapter.js
+++ b/modules/stroeerCoreBidAdapter.js
@@ -116,7 +116,8 @@ export const spec = {
           creativeId: '',
           meta: {
             advertiserDomains: bidResponse.adomain,
-            dsa: bidResponse.dsa
+            dsa: bidResponse.dsa,
+            campaignType: bidResponse.campaignType,
           },
           mediaType,
         };
diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js
index 89ebf485ca0..d186b0d5cd0 100644
--- a/test/spec/modules/stroeerCoreBidAdapter_spec.js
+++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js
@@ -1022,6 +1022,16 @@ describe('stroeerCore bid adapter', function () {
       assert.deepPropertyVal(result[0].meta, 'dsa', dsaResponse);
       assert.propertyVal(result[1].meta, 'dsa', undefined);
     });
+
+    it('should add campaignType to meta object', () => {
+      const response = buildBidderResponse();
+      response.bids[1] = Object.assign(response.bids[1], {campaignType: 'RTB'});
+
+      const result = spec.interpretResponse({body: response});
+
+      assert.propertyVal(result[0].meta, 'campaignType', undefined);
+      assert.propertyVal(result[1].meta, 'campaignType', 'RTB');
+    });
   });
 
   describe('get user syncs entry point', () => {

From 540165272bc7ded7c3788c9f4e03b1e42bd1f31f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michel=20Chr=C3=A9tien?= 
Date: Fri, 22 Nov 2024 16:11:39 +0100
Subject: [PATCH 0695/1097] AdagioAnalyticsAdapter: stop trying to read sizes
 of sizeless ad-units (#12489)

---
 modules/adagioAnalyticsAdapter.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js
index 94319aa6108..410accc946a 100644
--- a/modules/adagioAnalyticsAdapter.js
+++ b/modules/adagioAnalyticsAdapter.js
@@ -231,7 +231,7 @@ function handlerAuctionInit(event) {
       mediaTypeKey => mediaTypeKey
     ).map(mediaType => getMediaTypeAlias(mediaType)).sort();
     const bannerSizes = removeDuplicates(
-      mediaTypes.filter(mediaType => mediaType.hasOwnProperty(BANNER))
+      mediaTypes.filter(mediaType => mediaType.hasOwnProperty(BANNER) && mediaType[BANNER].hasOwnProperty('sizes'))
         .map(mediaType => mediaType[BANNER].sizes.map(size => size.join('x')))
         .flat(),
       bannerSize => bannerSize

From 337c0aabcf7db2bf0f35f8d4fc2465a85f849e98 Mon Sep 17 00:00:00 2001
From: Yohan Boutin 
Date: Fri, 22 Nov 2024 19:04:07 +0100
Subject: [PATCH 0696/1097] allow video outstream on any placement except
 instream (#12491)

---
 modules/seedtagBidAdapter.js                |  43 ++---
 test/spec/modules/seedtagBidAdapter_spec.js | 195 ++++++--------------
 2 files changed, 80 insertions(+), 158 deletions(-)

diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js
index d649999c438..4cde02c690e 100644
--- a/modules/seedtagBidAdapter.js
+++ b/modules/seedtagBidAdapter.js
@@ -18,12 +18,6 @@ const BIDDER_CODE = 'seedtag';
 const SEEDTAG_ALIAS = 'st';
 const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid';
 const SEEDTAG_SSP_ONTIMEOUT_ENDPOINT = 'https://s.seedtag.com/se/hb/timeout';
-const ALLOWED_DISPLAY_PLACEMENTS = [
-  'inScreen',
-  'inImage',
-  'inArticle',
-  'inBanner',
-];
 
 // Global Vendor List Id
 // https://iabeurope.eu/vendor-list-tcf-v2-0/
@@ -95,8 +89,7 @@ function hasMandatoryDisplayParams(bid) {
   const p = bid.params;
   return (
     !!p.publisherId &&
-    !!p.adUnitId &&
-    ALLOWED_DISPLAY_PLACEMENTS.indexOf(p.placement) > -1
+    !!p.adUnitId
   );
 }
 
@@ -111,19 +104,7 @@ function hasMandatoryVideoParams(bid) {
     isArray(videoParams.playerSize) &&
     videoParams.playerSize.length > 0;
 
-  switch (bid.params.placement) {
-    // instream accept only video format
-    case 'inStream':
-      return isValid && (videoParams.context === 'instream' || videoParams.context === 'outstream');
-    // outstream accept banner/native/video format
-    default:
-      return (
-        isValid &&
-        videoParams.context === 'outstream' &&
-        hasBannerMediaType(bid) &&
-        hasMandatoryDisplayParams(bid)
-      );
-  }
+  return isValid
 }
 
 function buildBidRequest(validBidRequest) {
@@ -172,6 +153,10 @@ function getVideoParams(validBidRequest) {
   return videoParams;
 }
 
+function isVideoOutstream(validBidRequest) {
+  return getVideoParams(validBidRequest).context === 'outstream';
+}
+
 function buildBidResponse(seedtagBid) {
   const mediaType = mapMediaType(seedtagBid.mediaType);
   const bid = {
@@ -286,9 +271,19 @@ export const spec = {
    * @return boolean True if this is a valid bid, and false otherwise.
    */
   isBidRequestValid(bid) {
-    return hasVideoMediaType(bid)
-      ? hasMandatoryVideoParams(bid)
-      : hasMandatoryDisplayParams(bid);
+    const hasVideo = hasVideoMediaType(bid);
+    const hasBanner = hasBannerMediaType(bid);
+
+    // when accept both mediatype but it must be outstream
+    if (hasVideo && hasBanner) {
+      return hasMandatoryVideoParams(bid) && isVideoOutstream(bid) && hasMandatoryDisplayParams(bid);
+    } else if (hasVideo) {
+      return hasMandatoryVideoParams(bid);
+    } else if (hasBanner) {
+      return hasMandatoryDisplayParams(bid);
+    } else {
+      return false;
+    }
   },
 
   /**
diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js
index 4032d2a280c..db19d71f23f 100644
--- a/test/spec/modules/seedtagBidAdapter_spec.js
+++ b/test/spec/modules/seedtagBidAdapter_spec.js
@@ -49,15 +49,13 @@ function createInStreamSlotConfig(mediaType) {
   return getSlotConfigs(mediaType, {
     publisherId: PUBLISHER_ID,
     adUnitId: ADUNIT_ID,
-    placement: 'inStream',
   });
 }
 
-const createBannerSlotConfig = (placement, mediatypes) => {
+const createBannerSlotConfig = (mediatypes) => {
   return getSlotConfigs(mediatypes || { banner: {} }, {
     publisherId: PUBLISHER_ID,
     adUnitId: ADUNIT_ID,
-    placement,
   });
 };
 
@@ -72,64 +70,69 @@ describe('Seedtag Adapter', function () {
   describe('isBidRequestValid method', function () {
     describe('returns true', function () {
       describe('when banner slot config has all mandatory params', () => {
-        const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle'];
-        placements.forEach((placement) => {
-          it(placement + 'should be valid', function () {
+        it('should be valid', function () {
+          const isBidRequestValid = spec.isBidRequestValid(
+            createBannerSlotConfig()
+          );
+          expect(isBidRequestValid).to.equal(true);
+        });
+
+        it('should be valid when has display and video mediatypes, and video context is outstream',
+          function () {
             const isBidRequestValid = spec.isBidRequestValid(
-              createBannerSlotConfig(placement)
+              createBannerSlotConfig({
+                banner: {},
+                video: {
+                  context: 'outstream',
+                  playerSize: [[600, 200]],
+                },
+              })
             );
             expect(isBidRequestValid).to.equal(true);
-          });
-
-          it(
-            placement +
-              ' should be valid when has display and video mediatypes, and video context is outstream',
-            function () {
-              const isBidRequestValid = spec.isBidRequestValid(
-                createBannerSlotConfig(placement, {
-                  banner: {},
-                  video: {
-                    context: 'outstream',
-                    playerSize: [[600, 200]],
-                  },
-                })
-              );
-              expect(isBidRequestValid).to.equal(true);
-            }
-          );
+          }
+        );
 
-          it(
-            placement +
-              ' shouldn\'t be valid when has only video mediatypes, and video context is outstream',
-            function () {
-              const isBidRequestValid = spec.isBidRequestValid(
-                createBannerSlotConfig(placement, {
-                  video: {
-                    context: 'outstream',
-                    playerSize: [[600, 200]],
-                  },
-                })
-              );
-              expect(isBidRequestValid).to.equal(false);
-            }
-          );
-          it(
-            placement +
-              " shouldn't be valid when has display and video mediatypes, and video context is instream",
-            function () {
-              const isBidRequestValid = spec.isBidRequestValid(
-                createBannerSlotConfig(placement, {
-                  banner: {},
-                  video: {
-                    context: 'instream',
-                    playerSize: [[600, 200]],
-                  },
-                })
-              );
-              expect(isBidRequestValid).to.equal(false);
-            }
-          );
-        });
+        it('should be valid when has only video mediatypes, and video context is outstream',
+          function () {
+            const isBidRequestValid = spec.isBidRequestValid(
+              createBannerSlotConfig({
+                video: {
+                  context: 'outstream',
+                  playerSize: [[600, 200]],
+                },
+              })
+            );
+            expect(isBidRequestValid).to.equal(true);
+          }
+        );
+        it('should be valid when has display and video mediatypes, and video context is instream',
+          function () {
+            const isBidRequestValid = spec.isBidRequestValid(
+              createBannerSlotConfig({
+                banner: {},
+                video: {
+                  context: 'instream',
+                  playerSize: [[600, 200]],
+                },
+              })
+            );
+            expect(isBidRequestValid).to.equal(false);
+          }
+        );
+        it("shouldn't be valid when has display and video mediatypes, and video context is instream",
+          function () {
+            const isBidRequestValid = spec.isBidRequestValid(
+              createBannerSlotConfig({
+                banner: {},
+                video: {
+                  context: 'instream',
+                  playerSize: [[600, 200]],
+                },
+              })
+            );
+            expect(isBidRequestValid).to.equal(false);
+          }
+        );
       });
       describe('when video slot has all mandatory params', function () {
         it('should return true, when video context is instream', function () {
@@ -142,7 +145,7 @@ describe('Seedtag Adapter', function () {
           const isBidRequestValid = spec.isBidRequestValid(slotConfig);
           expect(isBidRequestValid).to.equal(true);
         });
-        it('should return true, when video context is instream and mediatype is video and banner', function () {
+        it('should return false, when video context is instream and mediatype is video and banner', function () {
           const slotConfig = createInStreamSlotConfig({
             video: {
               context: 'instream',
@@ -151,61 +154,8 @@ describe('Seedtag Adapter', function () {
             banner: {},
           });
           const isBidRequestValid = spec.isBidRequestValid(slotConfig);
-          expect(isBidRequestValid).to.equal(true);
-        });
-        it('should return false, when video context is instream, but placement is not inStream', function () {
-          const slotConfig = getSlotConfigs(
-            {
-              video: {
-                context: 'instream',
-                playerSize: [[600, 200]],
-              },
-            },
-            {
-              publisherId: PUBLISHER_ID,
-              adUnitId: ADUNIT_ID,
-              placement: 'inBanner',
-            }
-          );
-          const isBidRequestValid = spec.isBidRequestValid(slotConfig);
           expect(isBidRequestValid).to.equal(false);
         });
-
-        it('should return true when placement is inStream and video context is outstream', function () {
-          const slotConfig = getSlotConfigs(
-            {
-              video: {
-                context: 'instream',
-                playerSize: [[600, 200]],
-              },
-            },
-            {
-              publisherId: PUBLISHER_ID,
-              adUnitId: ADUNIT_ID,
-              placement: 'inStream',
-            }
-          );
-          const isBidRequestValid = spec.isBidRequestValid(slotConfig);
-          expect(isBidRequestValid).to.equal(true);
-        });
-
-        it('should return true when placement is inStream and video context is instream', function () {
-          const slotConfig = getSlotConfigs(
-            {
-              video: {
-                context: 'outstream',
-                playerSize: [[600, 200]],
-              },
-            },
-            {
-              publisherId: PUBLISHER_ID,
-              adUnitId: ADUNIT_ID,
-              placement: 'inStream',
-            }
-          );
-          const isBidRequestValid = spec.isBidRequestValid(slotConfig);
-          expect(isBidRequestValid).to.equal(true);
-        });
       });
     });
     describe('returns false', function () {
@@ -217,7 +167,6 @@ describe('Seedtag Adapter', function () {
           const isBidRequestValid = spec.isBidRequestValid(
             createSlotConfig({
               adUnitId: ADUNIT_ID,
-              placement: 'inBanner',
             })
           );
           expect(isBidRequestValid).to.equal(false);
@@ -226,26 +175,6 @@ describe('Seedtag Adapter', function () {
           const isBidRequestValid = spec.isBidRequestValid(
             createSlotConfig({
               publisherId: PUBLISHER_ID,
-              placement: 'inBanner',
-            })
-          );
-          expect(isBidRequestValid).to.equal(false);
-        });
-        it('does not have the placement.', function () {
-          const isBidRequestValid = spec.isBidRequestValid(
-            createSlotConfig({
-              publisherId: PUBLISHER_ID,
-              adUnitId: ADUNIT_ID,
-            })
-          );
-          expect(isBidRequestValid).to.equal(false);
-        });
-        it('does not have a the correct placement.', function () {
-          const isBidRequestValid = spec.isBidRequestValid(
-            createSlotConfig({
-              publisherId: PUBLISHER_ID,
-              adUnitId: ADUNIT_ID,
-              placement: 'another_thing',
             })
           );
           expect(isBidRequestValid).to.equal(false);
@@ -293,12 +222,10 @@ describe('Seedtag Adapter', function () {
     const mandatoryDisplayParams = {
       publisherId: PUBLISHER_ID,
       adUnitId: ADUNIT_ID,
-      placement: 'inBanner',
     };
     const mandatoryVideoParams = {
       publisherId: PUBLISHER_ID,
       adUnitId: ADUNIT_ID,
-      placement: 'inStream',
     };
     const validBidRequests = [
       getSlotConfigs({ banner: {} }, mandatoryDisplayParams),

From 31ae262e415f68ad453d57bcb9dae7f57b61cf44 Mon Sep 17 00:00:00 2001
From: dmytro-po 
Date: Fri, 22 Nov 2024 21:21:38 +0200
Subject: [PATCH 0697/1097] IntentIq ID & Analytics Modules : support
 domainName parameter (#12434)

* update intentIqAnalyticsAdapter.js && intentIqIdSystem.js

* fix lint issues

* fix tests

* move info

* resolve issues

* update storeFirstPartyData

* remove unused code

* update defineEmptyDataAndFireCallback

* update fix lint

* update reportExternalWin

* small fixes

* update test && add docs

* AGT-347: Support domain name

* AGT-347: Support domain name

* AGT-374: Support domainName to vrref

* AGT-374: tests in progress

* AGT-374: Remove duplicate encoded in getRelevantRefferer and fix tests

* AGT-374: Add test domainName, changes in documentation

* AGT-374: Change js version value

* AGT-374: Remove extra coma

* Remove unused method

---------

Co-authored-by: dlepetynskyi 
Co-authored-by: DimaIntentIQ 
Co-authored-by: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com>
---
 .../intentIqConstants/intentIqConstants.js    |  2 +-
 .../detectBrowserUtils.js                     |  0
 libraries/intentIqUtils/getRefferer.js        | 64 ++++++++++++++
 modules/intentIqAnalyticsAdapter.js           | 40 ++++-----
 modules/intentIqIdSystem.js                   |  6 +-
 modules/intentIqIdSystem.md                   |  5 +-
 src/events.js                                 |  1 -
 .../modules/intentIqAnalyticsAdapter_spec.js  | 86 +++++++++++++++++--
 test/spec/modules/intentIqIdSystem_spec.js    |  2 +-
 9 files changed, 168 insertions(+), 38 deletions(-)
 rename libraries/{detectBrowserUtils => intentIqUtils}/detectBrowserUtils.js (100%)
 create mode 100644 libraries/intentIqUtils/getRefferer.js

diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js
index 46c466e936f..56f014be4c1 100644
--- a/libraries/intentIqConstants/intentIqConstants.js
+++ b/libraries/intentIqConstants/intentIqConstants.js
@@ -7,4 +7,4 @@ export const OPT_OUT = 'O';
 export const BLACK_LIST = 'L';
 export const CLIENT_HINTS_KEY = '_iiq_ch';
 export const EMPTY = 'EMPTY'
-export const VERSION = 0.21
+export const VERSION = 0.22
diff --git a/libraries/detectBrowserUtils/detectBrowserUtils.js b/libraries/intentIqUtils/detectBrowserUtils.js
similarity index 100%
rename from libraries/detectBrowserUtils/detectBrowserUtils.js
rename to libraries/intentIqUtils/detectBrowserUtils.js
diff --git a/libraries/intentIqUtils/getRefferer.js b/libraries/intentIqUtils/getRefferer.js
new file mode 100644
index 00000000000..39fde70ac24
--- /dev/null
+++ b/libraries/intentIqUtils/getRefferer.js
@@ -0,0 +1,64 @@
+import { getWindowTop, logError, getWindowLocation, getWindowSelf } from '../../src/utils.js';
+
+/**
+ * Determines if the script is running inside an iframe and retrieves the URL.
+ * @return {string} The encoded vrref value representing the relevant URL.
+ */
+export function getReferrer() {
+  try {
+    if (getWindowSelf() === getWindowTop()) {
+      return encodeURIComponent(getWindowLocation().href);
+    } else {
+      return encodeURIComponent(getWindowTop().location.href);
+    }
+  } catch (error) {
+    logError(`Error accessing location: ${error}`);
+    return '';
+  }
+}
+
+/**
+ * Appends `vrref` and `fui` parameters to the provided URL.
+ * If the referrer URL is available, it appends `vrref` with the relevant referrer value based on the domain.
+ * Otherwise, it appends `fui=1`. If a domain name is provided, it may also append `vrref` with the domain.
+ * @param {string} url - The URL to append parameters to.
+ * @param {string} domainName - The domain name used to determine the relevant referrer.
+ * @return {string} The modified URL with appended `vrref` or `fui` parameters.
+ */
+export function appendVrrefAndFui(url, domainName) {
+  const fullUrl = getReferrer();
+  if (fullUrl) {
+    return (url += '&vrref=' + getRelevantRefferer(domainName, fullUrl));
+  }
+  url += '&fui=1'; // Full Url Issue
+  url += '&vrref=' + encodeURIComponent(domainName || '');
+  return url;
+}
+
+/**
+ * Get the relevant referrer based on full URL and domain
+ * @param {string} domainName The domain name to compare
+ * @param {string} fullUrl The full URL to analyze
+ * @return {string} The relevant referrer
+ */
+export function getRelevantRefferer(domainName, fullUrl) {
+  if (domainName && isDomainIncluded(fullUrl, domainName)) {
+    return fullUrl;
+  }
+  return domainName ? encodeURIComponent(domainName) : fullUrl;
+}
+
+/**
+ * Checks if the provided domain name is included in the full URL.
+ * @param {string} fullUrl - The full URL to check.
+ * @param {string} domainName - The domain name to search for within the URL.
+ * @return {boolean} `True` if the domain name is found in the URL, `false` otherwise.
+ */
+export function isDomainIncluded(fullUrl, domainName) {
+  try {
+    return fullUrl.includes(domainName);
+  } catch (error) {
+    logError(`Invalid URL provided: ${error}`);
+    return false;
+  }
+}
diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js
index 7962080a138..a3113b7b089 100644
--- a/modules/intentIqAnalyticsAdapter.js
+++ b/modules/intentIqAnalyticsAdapter.js
@@ -1,4 +1,4 @@
-import {getWindowLocation, getWindowSelf, getWindowTop, logError, logInfo} from '../src/utils.js';
+import {logError, logInfo} from '../src/utils.js';
 import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
 import adapterManager from '../src/adapterManager.js';
 import {ajax} from '../src/ajax.js';
@@ -6,7 +6,8 @@ import {getStorageManager} from '../src/storageManager.js';
 import {config} from '../src/config.js';
 import {EVENTS} from '../src/constants.js';
 import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js';
-import {detectBrowser} from '../libraries/detectBrowserUtils/detectBrowserUtils.js';
+import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
+import {appendVrrefAndFui, getReferrer} from '../libraries/intentIqUtils/getRefferer.js';
 import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js';
 
 const MODULE_NAME = 'iiqAnalytics'
@@ -61,7 +62,8 @@ let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl, analyticsT
     dataInLs: null,
     eidl: null,
     lsIdsInitialized: false,
-    manualWinReportEnabled: false
+    manualWinReportEnabled: false,
+    domainName: null
   },
   track({eventType, args}) {
     switch (eventType) {
@@ -114,7 +116,8 @@ function initLsValues() {
       iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner;
     }
     iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : '';
-    iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqArr[0].params.manualWinReportEnabled || false
+    iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqArr[0].params.manualWinReportEnabled || false;
+    iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqArr[0].params.domainName || '';
   }
 }
 
@@ -258,32 +261,21 @@ function getDefaultDataObject() {
 }
 
 function constructFullUrl(data) {
-  let report = []
-  data = btoa(JSON.stringify(data))
-  report.push(data)
-  return defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner +
+  let report = [];
+  data = btoa(JSON.stringify(data));
+  report.push(data);
+
+  let url = defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner +
     '&mct=1' +
-    ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid)
+    ((iiqAnalyticsAnalyticsAdapter.initOptions?.fpid)
       ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') +
     '&agid=' + REPORTER_ID +
     '&jsver=' + VERSION +
-    '&vrref=' + getReferrer() +
     '&source=pbjs' +
     '&payload=' + JSON.stringify(report) +
-    '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints
-}
-
-export function getReferrer() {
-  try {
-    if (getWindowSelf() === getWindowTop()) {
-      return encodeURIComponent(getWindowLocation().href);
-    } else {
-      return encodeURIComponent(getWindowTop().location.href);
-    }
-  } catch (error) {
-    logError(`Error accessing location: ${error}`);
-    return '';
-  }
+    '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints;
+  url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName);
+  return url;
 }
 
 iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics;
diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js
index 069a955477b..2a57f602973 100644
--- a/modules/intentIqIdSystem.js
+++ b/modules/intentIqIdSystem.js
@@ -13,7 +13,8 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js';
 import {gppDataHandler, uspDataHandler} from '../src/consentHandler.js';
 import AES from 'crypto-js/aes.js';
 import Utf8 from 'crypto-js/enc-utf8.js';
-import {detectBrowser} from '../libraries/detectBrowserUtils/detectBrowserUtils.js';
+import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
+import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js';
 import {
   FIRST_PARTY_KEY,
   WITH_IIQ, WITHOUT_IIQ,
@@ -383,6 +384,9 @@ export const intentIqIdSubmodule = {
     url += VERSION ? '&jsver=' + VERSION : '';
     url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : '';
 
+    // Add vrref and fui to the URL
+    url = appendVrrefAndFui(url, configParams.domainName);
+
     const storeFirstPartyData = () => {
       partnerData.eidl = runtimeEids?.eids?.length || -1
       storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage);
diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md
index 089fbeef509..34fd495d625 100644
--- a/modules/intentIqIdSystem.md
+++ b/modules/intentIqIdSystem.md
@@ -42,7 +42,7 @@ Please find below list of paramters that could be used in configuring Intent IQ
 | params.timeoutInMillis         | Optional | Number   | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500.                                                                                                                                                                                                       | `450`                                         |
 | params.browserBlackList        | Optional |  String  | This is the name of a browser that can be added to a blacklist.                                                                                                                                                                                                                                                                           | `"chrome"`                                    |
 | params.manualWinReportEnabled  | Optional | Boolean  | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true`|
-
+| params.domainName              | Optional | String   | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages.                     | `"currentDomain.com"`                         |
 
 ### Configuration example
 
@@ -56,7 +56,8 @@ pbjs.setConfig({
                 timeoutInMillis: 500,
                 browserBlackList: "chrome",
                 callback: (data, group) => window.pbjs.requestBids(),
-                manualWinReportEnabled: true
+                manualWinReportEnabled: true,
+                domainName: "currentDomain.com"
             },
             storage: {
                 type: "html5",
diff --git a/src/events.js b/src/events.js
index 279acc27b9a..38e7f633d16 100644
--- a/src/events.js
+++ b/src/events.js
@@ -161,7 +161,6 @@ const _public = (function () {
     return eventsFired.toArray().map(val => Object.assign({}, val))
   };
 
-  window.prebidEvents = _public
   return _public;
 }());
 
diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js
index 2391c550da2..5ba0686af0b 100644
--- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js
+++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js
@@ -1,15 +1,16 @@
 import { expect } from 'chai';
 import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js';
 import * as utils from 'src/utils.js';
-import * as detectBrowserUtils from '../../../libraries/detectBrowserUtils/detectBrowserUtils';
 import { server } from 'test/mocks/xhr.js';
 import { config } from 'src/config.js';
 import { EVENTS } from 'src/constants.js';
 import * as events from 'src/events.js';
 import { getStorageManager } from 'src/storageManager.js';
 import sinon from 'sinon';
-import { REPORTER_ID, getReferrer, preparePayload } from '../../../modules/intentIqAnalyticsAdapter';
+import { REPORTER_ID, preparePayload } from '../../../modules/intentIqAnalyticsAdapter';
 import {FIRST_PARTY_KEY, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js';
+import * as detectBrowserUtils from '../../../libraries/intentIqUtils/detectBrowserUtils.js';
+import {getReferrer, appendVrrefAndFui} from '../../../libraries/intentIqUtils/getRefferer.js';
 
 const partner = 10;
 const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}';
@@ -89,7 +90,8 @@ describe('IntentIQ tests all', function () {
       dataInLs: null,
       eidl: null,
       lsIdsInitialized: false,
-      manualWinReportEnabled: false
+      manualWinReportEnabled: false,
+      domainName: null
     };
     if (iiqAnalyticsAnalyticsAdapter.track.restore) {
       iiqAnalyticsAnalyticsAdapter.track.restore();
@@ -115,13 +117,16 @@ describe('IntentIQ tests all', function () {
 
   it('IIQ Analytical Adapter bid win report', function () {
     localStorage.setItem(FIRST_PARTY_KEY, defaultData);
+    getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({href: 'http://localhost:9876/'});
+    const expectedVrref = encodeURIComponent(getWindowLocationStub().href);
 
     events.emit(EVENTS.BID_WON, wonRequest);
 
     expect(server.requests.length).to.be.above(0);
     const request = server.requests[0];
     expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1');
-    expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`);
+    expect(request.url).to.contain(`&jsver=${version}`);
+    expect(request.url).to.contain(`&vrref=${expectedVrref}`);
     expect(request.url).to.contain('&payload=');
     expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344');
   });
@@ -132,13 +137,15 @@ describe('IntentIQ tests all', function () {
 
   it('should handle BID_WON event with group configuration from local storage', function () {
     localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}');
+    const expectedVrref = encodeURIComponent('http://localhost:9876/');
 
     events.emit(EVENTS.BID_WON, wonRequest);
 
     expect(server.requests.length).to.be.above(0);
     const request = server.requests[0];
     expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1');
-    expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`);
+    expect(request.url).to.contain(`&jsver=${version}`);
+    expect(request.url).to.contain(`&vrref=${expectedVrref}`);
     expect(request.url).to.contain('iiqid=testpcid');
   });
 
@@ -153,9 +160,11 @@ describe('IntentIQ tests all', function () {
     const dataToSend = preparePayload(wonRequest);
     const base64String = btoa(JSON.stringify(dataToSend));
     const payload = `[%22${base64String}%22]`;
-    expect(request.url).to.equal(
-      `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}&uh=`
+    const expectedUrl = appendVrrefAndFui(
+      `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&source=pbjs&payload=${payload}&uh=`,
+      iiqAnalyticsAnalyticsAdapter.initOptions.domainName
     );
+    expect(request.url).to.equal(expectedUrl);
     expect(dataToSend.pcid).to.equal(defaultDataObj.pcid)
   });
 
@@ -250,8 +259,69 @@ describe('IntentIQ tests all', function () {
     expect(server.requests.length).to.be.above(0);
     const request = server.requests[0];
     expect(request.url).to.contain(`https://reports.intentiq.com/report?pid=${partner}&mct=1`);
-    expect(request.url).to.contain(`&jsver=${version}&vrref=${encodeURIComponent('http://localhost:9876/')}`);
+    expect(request.url).to.contain(`&jsver=${version}`);
+    expect(request.url).to.contain(`&vrref=${encodeURIComponent('http://localhost:9876/')}`);
     expect(request.url).to.contain('&payload=');
     expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344');
   });
+
+  const testCasesVrref = [
+    {
+      description: 'domainName matches window.top.location.href',
+      getWindowSelf: {},
+      getWindowTop: { location: { href: 'http://example.com/page' } },
+      getWindowLocation: { href: 'http://example.com/page' },
+      domainName: 'example.com',
+      expectedVrref: encodeURIComponent('http://example.com/page'),
+      shouldContainFui: false
+    },
+    {
+      description: 'domainName does not match window.top.location.href',
+      getWindowSelf: {},
+      getWindowTop: { location: { href: 'http://anotherdomain.com/page' } },
+      getWindowLocation: { href: 'http://anotherdomain.com/page' },
+      domainName: 'example.com',
+      expectedVrref: encodeURIComponent('example.com'),
+      shouldContainFui: false
+    },
+    {
+      description: 'domainName is missing, only fui=1 is returned',
+      getWindowSelf: {},
+      getWindowTop: { location: { href: '' } },
+      getWindowLocation: { href: '' },
+      domainName: null,
+      expectedVrref: '',
+      shouldContainFui: true
+    },
+    {
+      description: 'domainName is missing',
+      getWindowSelf: {},
+      getWindowTop: { location: { href: 'http://example.com/page' } },
+      getWindowLocation: { href: 'http://example.com/page' },
+      domainName: null,
+      expectedVrref: encodeURIComponent('http://example.com/page'),
+      shouldContainFui: false
+    },
+  ];
+
+  testCasesVrref.forEach(({ description, getWindowSelf, getWindowTop, getWindowLocation, domainName, expectedVrref, shouldContainFui }) => {
+    it(`should append correct vrref when ${description}`, function () {
+      getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(getWindowSelf);
+      getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns(getWindowTop);
+      getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns(getWindowLocation);
+
+      const url = 'https://reports.intentiq.com/report?pid=10';
+      const modifiedUrl = appendVrrefAndFui(url, domainName);
+      const urlObj = new URL(modifiedUrl);
+
+      const vrref = encodeURIComponent(urlObj.searchParams.get('vrref') || '');
+      const fui = urlObj.searchParams.get('fui');
+
+      expect(vrref).to.equal(expectedVrref);
+      expect(urlObj.searchParams.has('fui')).to.equal(shouldContainFui);
+      if (shouldContainFui) {
+        expect(fui).to.equal('1');
+      }
+    });
+  });
 });
diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js
index cc3495aeb11..b2ce17470ff 100644
--- a/test/spec/modules/intentIqIdSystem_spec.js
+++ b/test/spec/modules/intentIqIdSystem_spec.js
@@ -5,7 +5,7 @@ import { server } from 'test/mocks/xhr.js';
 import { decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem';
 import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler';
 import { clearAllCookies } from '../../helpers/cookies';
-import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/detectBrowserUtils/detectBrowserUtils';
+import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils';
 import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY} from '../../../libraries/intentIqConstants/intentIqConstants.js';
 
 const partner = 10;

From 9361e17108c948014b737fe30ef812eb2aad1ae0 Mon Sep 17 00:00:00 2001
From: Eugene Dorfman 
Date: Sat, 23 Nov 2024 12:56:46 +0100
Subject: [PATCH 0698/1097] Userid module: propagate ortb2.user.ext.eids to
 userIdsAsEids even if no UserId submodules (#12477)

* userId module: temp fix empty bid.userIdAsEids

* userId module: fix empty bid.userIdAsEids

* userId module: fix empty bid.userIdAsEids

* userId module: fix empty bid.userIdAsEids

* userId module: fix tests

* userId module: prevent re-adding addUserIdsHook hook

* userId module: mode adding addUserIdsHook, added test

---------

Co-authored-by: Konstantin Mikhalyov 
Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com>
---
 modules/userId/index.js          | 30 ++++++++++++++---
 test/spec/modules/userId_spec.js | 56 ++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/modules/userId/index.js b/modules/userId/index.js
index d0299427603..2a9d1f27072 100644
--- a/modules/userId/index.js
+++ b/modules/userId/index.js
@@ -165,9 +165,6 @@ export const dep = {
   isAllowed: isActivityAllowed
 }
 
-/** @type {boolean} */
-let addedUserIdHook = false;
-
 /** @type {SubmoduleContainer[]} */
 let submodules = [];
 
@@ -693,6 +690,25 @@ export const startAuctionHook = timedAuctionHook('userId', function requestBidsH
   });
 });
 
+/**
+ * Append user id data from config to bids to be accessed in adapters when there are no submodules.
+ * @param {function} fn required; The next function in the chain, used by hook.js
+ * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids.
+ */
+export const addUserIdsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) {
+  addIdData(reqBidsConfigObj);
+  // calling fn allows prebid to continue processing
+  fn.call(this, reqBidsConfigObj);
+});
+
+/**
+ * Is startAuctionHook added
+ * @returns {boolean}
+ */
+function addedStartAuctionHook() {
+  return !!startAuction.getHooks({hook: startAuctionHook}).length;
+}
+
 /**
  * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well.
  * Simple use case will be passing these UserIds to A9 wrapper solution
@@ -1110,11 +1126,11 @@ function updateSubmodules() {
     .forEach((sm) => submodules.push(sm));
 
   if (submodules.length) {
-    if (!addedUserIdHook) {
+    if (!addedStartAuctionHook()) {
+      startAuction.getHooks({hook: addUserIdsHook}).remove();
       startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd
       adapterManager.callDataDeletionRequest.before(requestDataDeletion);
       coreGetPPID.after((next) => next(getPPID()));
-      addedUserIdHook = true;
     }
     logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name));
   }
@@ -1221,6 +1237,10 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) {
   (getGlobal()).refreshUserIds = normalizePromise(refreshUserIds);
   (getGlobal()).getUserIdsAsync = normalizePromise(getUserIdsAsync);
   (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource;
+  if (!addedStartAuctionHook()) {
+    // Add ortb2.user.ext.eids even if 0 submodules are added
+    startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd
+  }
 }
 
 // init config update listener to start the application
diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js
index 6ebe533e260..c4f333e56ac 100644
--- a/test/spec/modules/userId_spec.js
+++ b/test/spec/modules/userId_spec.js
@@ -8,6 +8,7 @@ import {
   init,
   PBJS_USER_ID_OPTOUT_NAME,
   startAuctionHook,
+  addUserIdsHook,
   requestDataDeletion,
   setStoredValue,
   setSubmoduleRegistry,
@@ -27,6 +28,7 @@ import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js';
 import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js';
 import * as mockGpt from '../integration/faker/googletag.js';
 import 'src/prebid.js';
+import {startAuction} from 'src/prebid';
 import {hook} from '../../../src/hook.js';
 import {mockGdprConsent} from '../../helpers/consentData.js';
 import {getPPID} from '../../../src/adserver.js';
@@ -175,6 +177,8 @@ describe('User ID', function () {
   afterEach(() => {
     sandbox.restore();
     config.resetConfig();
+    startAuction.getHooks({hook: startAuctionHook}).remove();
+    startAuction.getHooks({hook: addUserIdsHook}).remove();
   });
 
   after(() => {
@@ -2423,6 +2427,58 @@ describe('User ID', function () {
         })
       })
     });
+
+    describe('submodules not added', () => {
+      const eid = {
+        source: 'example.com',
+        uids: [{id: '1234', atype: 3}]
+      };
+      let adUnits;
+      let startAuctionStub;
+      function saHook(fn, ...args) {
+        return startAuctionStub(...args);
+      }
+      beforeEach(() => {
+        adUnits = [{code: 'au1', bids: [{bidder: 'sampleBidder'}]}];
+        startAuctionStub = sinon.stub();
+        startAuction.before(saHook);
+        config.resetConfig();
+      });
+      afterEach(() => {
+        startAuction.getHooks({hook: saHook}).remove();
+      })
+
+      it('addUserIdsHook', function (done) {
+        addUserIdsHook(function () {
+          adUnits.forEach(unit => {
+            unit.bids.forEach(bid => {
+              expect(bid).to.have.deep.nested.property('userIdAsEids.0.source');
+              expect(bid).to.have.deep.nested.property('userIdAsEids.0.uids.0.id');
+              expect(bid.userIdAsEids[0].source).to.equal('example.com');
+              expect(bid.userIdAsEids[0].uids[0].id).to.equal('1234');
+            });
+          });
+          done();
+        }, {
+          adUnits,
+          ortb2Fragments: {
+            global: {user: {ext: {eids: [eid]}}},
+            bidder: {}
+          }
+        });
+      });
+
+      it('should add userIdAsEids and merge ortb2.user.ext.eids even if no User ID submodules', () => {
+        init(config);
+        config.setConfig({
+          ortb2: {user: {ext: {eids: [eid]}}}
+        })
+        expect(startAuction.getHooks({hook: startAuctionHook}).length).equal(0);
+        expect(startAuction.getHooks({hook: addUserIdsHook}).length).equal(1);
+        $$PREBID_GLOBAL$$.requestBids({adUnits});
+        sinon.assert.calledWith(startAuctionStub, sinon.match.hasNested('adUnits[0].bids[0].userIdAsEids[0]', eid));
+      });
+    });
   });
 
   describe('handles config with ESP configuration in user sync object', function() {

From e3e9ce0fb4ff068f6a3749f14c13a9da3f8c2c4e Mon Sep 17 00:00:00 2001
From: andreasgreen 
Date: Sat, 23 Nov 2024 13:07:10 +0100
Subject: [PATCH 0699/1097] Bidtheatre Bidder Adapter: initial release (#12485)

---
 modules/bidtheatreBidAdapter.js               | 117 ++++++++
 modules/bidtheatreBidAdapter.md               | 111 ++++++++
 .../spec/modules/bidtheatreBidAdapter_spec.js | 266 ++++++++++++++++++
 3 files changed, 494 insertions(+)
 create mode 100644 modules/bidtheatreBidAdapter.js
 create mode 100644 modules/bidtheatreBidAdapter.md
 create mode 100644 test/spec/modules/bidtheatreBidAdapter_spec.js

diff --git a/modules/bidtheatreBidAdapter.js b/modules/bidtheatreBidAdapter.js
new file mode 100644
index 00000000000..8fb3dc2fd3b
--- /dev/null
+++ b/modules/bidtheatreBidAdapter.js
@@ -0,0 +1,117 @@
+import { ortbConverter } from '../libraries/ortbConverter/converter.js'
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { deepSetValue, logError, replaceAuctionPrice } from '../src/utils.js';
+import { getStorageManager } from '../src/storageManager.js';
+
+const GVLID = 30;
+export const BIDDER_CODE = 'bidtheatre';
+export const ENDPOINT_URL = 'https://prebidjs-bids.bidtheatre.net/prebidjsbid';
+const METHOD = 'POST';
+const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO];
+export const DEFAULT_CURRENCY = 'USD';
+const BIDTHEATRE_COOKIE_NAME = '__kuid';
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
+
+const converter = ortbConverter({
+  context: {
+    netRevenue: true,
+    ttl: 120,
+    currency: DEFAULT_CURRENCY
+  }
+});
+
+export const spec = {
+  code: BIDDER_CODE,
+  supportedMediaTypes: SUPPORTED_MEDIA_TYPES,
+  gvlid: GVLID,
+  isBidRequestValid: function (bidRequest) {
+    const isValid = bidRequest &&
+                    bidRequest.params &&
+                    typeof bidRequest.params.publisherId === 'string' &&
+                    bidRequest.params.publisherId.trim().length === 36
+
+    if (!isValid) {
+      logError('Bidtheatre Header Bidding Publisher ID not provided or in incorrect format');
+    }
+
+    return isValid;
+  },
+  getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
+    const seenUrls = new Set();
+    const syncs = [];
+
+    if (syncOptions.pixelEnabled && serverResponses) {
+      serverResponses.forEach(response => {
+        if (response.body && response.body.seatbid) {
+          response.body.seatbid.forEach(seatbid => {
+            if (seatbid.bid) {
+              seatbid.bid.forEach(bid => {
+                const urls = bid.ext && bid.ext.usersync_urls;
+                if (Array.isArray(urls)) {
+                  urls.forEach(url => {
+                    if (!seenUrls.has(url)) {
+                      seenUrls.add(url);
+                      syncs.push({
+                        type: 'image',
+                        url: url
+                      });
+                    }
+                  });
+                }
+              });
+            }
+          });
+        }
+      });
+    }
+    return syncs;
+  },
+  buildRequests(bidRequests, bidderRequest) {
+    const data = converter.toORTB({bidRequests, bidderRequest});
+
+    const cookieValue = storage.getCookie(BIDTHEATRE_COOKIE_NAME);
+    if (cookieValue) {
+      deepSetValue(data, 'user.buyeruid', cookieValue);
+    }
+
+    data.imp.forEach((impObj, index) => {
+      let publisherId = bidRequests[index].params.publisherId;
+
+      if (publisherId) {
+        deepSetValue(impObj, 'ext.bidder.publisherId', publisherId);
+      }
+    });
+
+    return [{
+      method: METHOD,
+      url: ENDPOINT_URL,
+      data
+    }]
+  },
+  interpretResponse(response, request) {
+    if (!response || !response.body || !response.body.seatbid) {
+      return [];
+    }
+
+    const macroReplacedSeatbid = response.body.seatbid.map(seatbidItem => {
+      const macroReplacedBid = seatbidItem.bid.map((bidItem) => ({
+        ...bidItem,
+        adm: replaceAuctionPrice(bidItem.adm, bidItem.price),
+        nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price)
+      }));
+      return { ...seatbidItem, bid: macroReplacedBid };
+    });
+
+    const macroReplacedResponseBody = { ...response.body, seatbid: macroReplacedSeatbid };
+    const bids = converter.fromORTB({response: macroReplacedResponseBody, request: request.data}).bids;
+    return bids;
+  },
+  onTimeout: function(timeoutData) {},
+  onBidWon: function(bid) {},
+  onSetTargeting: function(bid) {},
+  // onBidderError: function({ error, bidderRequest }) {},
+  onAdRenderSucceeded: function(bid) {}
+}
+
+registerBidder(spec);
diff --git a/modules/bidtheatreBidAdapter.md b/modules/bidtheatreBidAdapter.md
new file mode 100644
index 00000000000..7f9301596aa
--- /dev/null
+++ b/modules/bidtheatreBidAdapter.md
@@ -0,0 +1,111 @@
+# Overview
+
+```
+Module Name : Bidtheatre Bidder Adapter
+Module Type : Bidder Adapter
+Maintainer  : operations@bidtheatre.com
+```
+
+# Description
+
+Module that connects to Bidtheatre's demand sources
+
+About us: https://www.bidtheatre.com
+
+The Bidtheatre Bidding adapter requires manual set up before use. Please contact us at [operations@bidtheatre.com](mailto:operations@bidtheatre.com) if you would like to access Bidtheatre demand.
+
+# Bid params
+| Name          | Scope    | Description                            | Example                              |
+|:--------------| :------- |:---------------------------------------|:-------------------------------------|
+| `publisherId` | required | Manually set up publisher ID | `73b20b3a-12a0-4869-b54e-8d42b55786ee`|
+
+In addition to the required bid param above, Bidtheatre will also enforce the following requirements
+- All ad slots on a page must belong to the same publisher ID
+- The publisher must provide either a client IP and/or explicit geo data in the request
+
+# Test Parameters
+
+## Banner
+
+```javascript
+var displayAdUnits = [
+    {
+        code: 'test-banner',
+        mediaTypes: {
+            banner: {
+                sizes: [[980,240]]
+            }
+        },
+        bids: [
+            {
+                bidder: 'bidtheatre',
+                params: {
+                    publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee'
+                }
+            }
+        ]
+    }
+];
+```
+
+## Video
+
+```javascript
+var videoAdUnits = [
+    {
+        code: 'test-video',
+        mediaTypes: {
+            video: {
+                playerSize: [[1280, 720]]
+            }
+        },
+        bids: [
+            {
+                bidder: 'bidtheatre',
+                params: {
+                    publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee'
+                }
+            }
+        ]
+    }
+];
+```
+
+## Multiformat
+
+```javascript
+var adUnits = [
+    {
+        code: 'test-banner',
+        mediaTypes: {
+            banner: {
+                sizes: [[980,240]]
+            }
+        },
+        bids: [
+            {
+                bidder: 'bidtheatre',
+                params: {
+                    publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee'
+                }
+            }
+        ]
+    },
+    {
+        code: 'test-video',
+        mediaTypes: {
+            video: {
+                playerSize: [[1280, 720]]
+            }
+        },
+        bids: [
+            {
+                bidder: 'bidtheatre',
+                params: {
+                    publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee'
+                }
+            }
+        ]
+    }
+];
+```
diff --git a/test/spec/modules/bidtheatreBidAdapter_spec.js b/test/spec/modules/bidtheatreBidAdapter_spec.js
new file mode 100644
index 00000000000..4842c43d1f0
--- /dev/null
+++ b/test/spec/modules/bidtheatreBidAdapter_spec.js
@@ -0,0 +1,266 @@
+import { expect } from 'chai';
+import { spec, ENDPOINT_URL, BIDDER_CODE, DEFAULT_CURRENCY } from 'modules/bidtheatreBidAdapter.js';
+import { BANNER, VIDEO } from 'src/mediaTypes.js';
+import { deepClone } from 'src/utils.js';
+
+const VALID_PUBLISHER_ID = '73b20b3a-12a0-4869-b54e-8d42b55786ee';
+const STATIC_IMP_ID = '3263e5dec855c5';
+const BID_PRICE = 5.112871170043945;
+const AUCTION_PRICE_MACRO = '${AUCTION_PRICE}';
+
+const BANNER_BID_REQUEST = [
+  {
+    'bidder': BIDDER_CODE,
+    'params': {
+      'publisherId': VALID_PUBLISHER_ID
+    },
+    'bidId': STATIC_IMP_ID,
+    'mediaTypes': {
+      'banner': {
+        'sizes': [
+          [
+            980,
+            240
+          ]
+        ]
+      }
+    },
+    'sizes': [
+      [
+        980,
+        240
+      ]
+    ]
+  }
+];
+
+const BANNER_BIDDER_REQUEST = {'bidderCode': BIDDER_CODE, 'bids': BANNER_BID_REQUEST};
+
+const BANNER_BID_RESPONSE = {
+  'cur': 'USD',
+  'seatbid': [
+    {
+      'seat': '5',
+      'bid': [
+        {
+          'ext': {
+            'usersync_urls': [
+              'https://match.adsby.bidtheatre.com/usersync?gdpr=1&gdpr_consent=CONSENT_STRING'
+            ]
+          },
+          'crid': '1915538',
+          'h': 240,
+          'adm': "",
+          'mtype': 1,
+          'adid': '1915538',
+          'adomain': [
+            'bidtheatre.com'
+          ],
+          'price': BID_PRICE,
+          'cat': [
+            'IAB3-1'
+          ],
+          'w': 980,
+          'id': STATIC_IMP_ID,
+          'impid': STATIC_IMP_ID,
+          'cid': 'c154375'
+        }
+      ]
+    }
+  ]
+};
+
+const VIDEO_BID_REQUEST = [
+  {
+    'bidder': BIDDER_CODE,
+    'params': {
+      'publisherId': VALID_PUBLISHER_ID
+    },
+    'bidId': STATIC_IMP_ID,
+    'mediaTypes': {
+      'video': {
+        'playerSize': [
+          [
+            1280,
+            720
+          ]
+        ],
+        'context': 'instream'
+      }
+    },
+    'sizes': [[1280, 720]]
+  }
+];
+
+const VIDEO_BIDDER_REQUEST = {'bidderCode': BIDDER_CODE, 'bids': VIDEO_BID_REQUEST};
+
+const VIDEO_BID_RESPONSE = {
+  'cur': 'USD',
+  'seatbid': [
+    {
+      'seat': '5',
+      'bid': [
+        {
+          'ext': {
+            'usersync_urls': [
+              'https://match.adsby.bidtheatre.com/usersync?gdpr=0&gdpr_consent='
+            ]
+          },
+          'crid': '1922926',
+          'h': 720,
+          'mtype': 2,
+          'nurl': 'https://adsby.bidtheatre.com/video?z=27025;a=1922926;ex=36;es=http%3A%2F%2F127.0.0.1%3A8080;eb=3672319;xs=940400838;so=1;tag=unspec_1280_720;kuid=05914b22-88cb-4c5d-9f7c-f133fdf9669a;wp=${AUCTION_PRICE};su=127.0.0.1%3A8080;iab=vast2;dealId=;ma=eyJjZCI6ZmFsc2UsInN0IjozLCJtbGF0Ijo1OS4yNywibW9yZyI6InRlbGlhIG5ldHdvcmsgc2VydmljZXMiLCJtbHNjb3JlIjowLjg2MDcwMDU0NzY5NTE1OTksIm16aXAiOiIxMjggMzUiLCJiaXAiOiI4MS4yMjcuODIuMjgiLCJhZ2lkIjozNTYyNzAyLCJtbG1vZGVsIjoibWFzdGVyX21sX2Nsa181MzYiLCJ1YSI6Ik1vemlsbGFcLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdFwvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lXC8xMzAuMC4wLjAgU2FmYXJpXC81MzcuMzYiLCJtbG9uIjoxOC4xMywibXJlZ2lvbiI6ImFiIiwiZHQiOjEsIm1jaXR5Ijoic2thcnBuYWNrIiwicGFnZXVybCI6Imh0dHA6XC9cLzEyNy4wLjAuMTo4MDgwXC92aWRlby5odG1sP3BianNfZGVidWc9dHJ1ZSIsImltcGlkIjoieDM2X2FzeC1iLXMyXzQxNDMzMTA0MTIyMzUyNTU3NDgiLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMxNTA3NTY5Njg3fQ%3D%3D;cd=0;cb0=;impId=x36_asx-b-s2_4143310412235255748;gdpr=1;gdpr_consent=CP-S4UAP-S4UACGABBENAzEsAP_gAEPgAAAAKKtV_H__bW1r8X73aft0eY1P9_j77sQxBhfJE-4FzLvW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2D-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v0_F_rE2_eT1l_tevp7D9-ct87_XW-9_fff79Ll9-goqAWYaFRAHWBISEGgYRQIAVBWEBFAgAAABIGiAgBMGBTsDAJdYSIAQAoABggBAACjIAEAAAEACEQAQAFAgAAgECgABAAgEAgAIGAAEAFgIBAACA6BCmBBAoFgAkZkRCmBCFAkEBLZUIJAECCuEIRZ4AEAiJgoAAAAACsAAQFgsDiSQEqEggS4g2gAAIAEAghAqEEnJgACBI2WoPBE2jK0gDQ04SAAAAA.YAAAAAAAAAAA',
+          'adid': '1922926',
+          'adomain': [
+            'bidtheatre.com'
+          ],
+          'price': BID_PRICE,
+          'cat': [
+            'IAB3-1'
+          ],
+          'w': 1280,
+          'id': STATIC_IMP_ID,
+          'impid': STATIC_IMP_ID,
+          'cid': 'c154375'
+        }
+      ]
+    }
+  ]
+}
+
+describe('BidtheatreAdapter', function () {
+  describe('isBidRequestValid', function () {
+    let bid = {
+      'bidder': BIDDER_CODE,
+      'params': {
+        'publisherId': VALID_PUBLISHER_ID
+      },
+      'sizes': [[980, 240]]
+    };
+
+    it('should return true when required param found and of correct type', function () {
+      expect(spec.isBidRequestValid(bid)).to.equal(true);
+    });
+
+    it('should return false when required param is not passed', function () {
+      let invalidBid = Object.assign({}, bid);
+      delete invalidBid.params;
+      invalidBid.params = {
+
+      };
+      expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+    });
+
+    it('should return false when required param of incorrect data type', function () {
+      let invalidBid = Object.assign({}, bid);
+      delete invalidBid.params;
+      invalidBid.params = {
+        'publisherId': 12345
+      };
+      expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+    });
+
+    it('should return false when required param of incorrect length', function () {
+      let invalidBid = Object.assign({}, bid);
+      delete invalidBid.params;
+      invalidBid.params = {
+        'publisherId': '73b20b3a-12a0-4869-b54e-8d42b55786e'
+      };
+      expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+    });
+  });
+
+  describe('buildRequests', function () {
+    it('should include correct http method, correct url and existing data', function () {
+      const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST);
+      expect(request[0].method).to.equal('POST');
+      expect(request[0].url).to.equal(ENDPOINT_URL);
+      expect(request[0].data).to.exist;
+    });
+
+    it('should include required bid param in request', function () {
+      const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST);
+      const data = request[0].data;
+      expect(data.imp[0].ext.bidder.publisherId).to.equal(VALID_PUBLISHER_ID);
+    });
+
+    it('should include imp array in request', function () {
+      const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST);
+      const data = request[0].data;
+      expect(data).to.have.property('imp').that.is.an('array').with.lengthOf.at.least(1);
+      expect(data.imp[0]).to.be.an('object');
+    });
+  });
+
+  describe('interpretResponse', function () {
+    it('should have exactly one bid in banner response', function () {
+      const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST);
+      const bids = spec.interpretResponse({body: BANNER_BID_RESPONSE}, request[0]);
+      expect(bids).to.be.an('array').with.lengthOf(1);
+      expect(bids[0]).to.be.an('object');
+    });
+
+    it('should have currency set to USD in banner response', function () {
+      const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST);
+      const bids = spec.interpretResponse({body: BANNER_BID_RESPONSE}, request[0]);
+      expect(bids[0].currency).to.be.a('string').and.to.equal(DEFAULT_CURRENCY);
+    });
+
+    it('should have ad in response and auction price macros replaced in banner response', function () {
+      const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST);
+      const bids = spec.interpretResponse({body: BANNER_BID_RESPONSE}, request[0]);
+      const ad = bids[0].ad;
+      expect(ad).to.exist;
+      expect(ad).to.be.a('string');
+      expect(ad).to.include('&wp=' + BID_PRICE + '&');
+      expect(ad).to.not.include(AUCTION_PRICE_MACRO);
+    });
+
+    if (FEATURES.VIDEO) {
+      it('should have exactly one bid in video response', function () {
+        const request = spec.buildRequests(VIDEO_BID_REQUEST, VIDEO_BIDDER_REQUEST);
+        const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request[0]);
+        expect(bids).to.be.an('array').with.lengthOf(1);
+        expect(bids[0]).to.be.an('object');
+      });
+
+      it('should have currency set to USD in video response', function () {
+        const request = spec.buildRequests(VIDEO_BID_REQUEST, VIDEO_BIDDER_REQUEST);
+        const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request[0]);
+        expect(bids[0].currency).to.be.a('string').and.to.equal(DEFAULT_CURRENCY);
+      });
+
+      it('should have vastUrl in response and auction price macros replaced in video response', function () {
+        const request = spec.buildRequests(VIDEO_BID_REQUEST, VIDEO_BIDDER_REQUEST);
+        const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request[0]);
+        const vastUrl = bids[0].vastUrl;
+        expect(vastUrl).to.exist;
+        expect(vastUrl).to.be.a('string');
+        expect(vastUrl).to.include(';wp=' + BID_PRICE + ';');
+        expect(vastUrl).to.not.include(AUCTION_PRICE_MACRO);
+      });
+    }
+  });
+
+  describe('getUserSyncs', function () {
+    const bidResponse = deepClone(BANNER_BID_RESPONSE);
+    const bidResponseSyncURL = bidResponse.seatbid[0].bid[0].ext.usersync_urls[0];
+
+    const gdprConsent = {
+      gdprApplies: true,
+      consentString: 'CONSENT_STRING'
+    };
+
+    it('should return empty when pixel is disallowed', function () {
+      expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse, gdprConsent)).to.be.empty;
+    });
+
+    it('should return empty when no server response is present', function () {
+      expect(spec.getUserSyncs({ pixelEnabled: true }, [], gdprConsent)).to.be.empty;
+    });
+
+    it('should return usersync url when pixel is allowed and present in bid response', function () {
+      expect(spec.getUserSyncs({ pixelEnabled: true }, [{body: bidResponse}], gdprConsent)).to.deep.equal([{ type: 'image', url: bidResponseSyncURL }]);
+    });
+  });
+});

From b3577cc162af109ff52c74ade0d07a1eb645be4a Mon Sep 17 00:00:00 2001
From: Dejan Grbavcic 
Date: Sun, 24 Nov 2024 14:20:07 +0100
Subject: [PATCH 0700/1097] TargetVideo Bid Adapter : user sync and response
 changes (#12461)

* TargetVideo bid adapter

* TargetVideo bid adapter

* TargetVideo bid adapter

* TargetVideo Bid Adapter: Add GDPR/USP support

* TargetVideo Bid Adapter: Add GDPR/USP support tests

* TargetVideo Bid Adapter: Updating margin rule

* Add Brid bid adapter

* Brid adapter requested changes

* BridBidAdapter: switching to plcmt

* Brid Bid Adapter: getUserSyncs method and interpretResponse updates

* Adding missing semicolon

* TargetVideo Bid Adapter : user sync and response changes

* TargetVideo Bid Adapter : removing duplicate code
---
 libraries/targetVideoUtils/bidderUtils.js     | 37 +++++++++++++++++-
 libraries/targetVideoUtils/constants.js       |  2 +-
 modules/bridBidAdapter.js                     | 38 ++-----------------
 modules/targetVideoBidAdapter.js              | 18 +++++++--
 .../modules/targetVideoBidAdapter_spec.js     | 20 ++++++++++
 5 files changed, 76 insertions(+), 39 deletions(-)

diff --git a/libraries/targetVideoUtils/bidderUtils.js b/libraries/targetVideoUtils/bidderUtils.js
index f18540818cb..cf106566944 100644
--- a/libraries/targetVideoUtils/bidderUtils.js
+++ b/libraries/targetVideoUtils/bidderUtils.js
@@ -1,6 +1,7 @@
+import {SYNC_URL} from './constants.js';
 import {VIDEO} from '../../src/mediaTypes.js';
 import {getRefererInfo} from '../../src/refererDetection.js';
-import {createTrackPixelHtml, deepAccess, getBidRequest} from '../../src/utils.js';
+import {createTrackPixelHtml, deepAccess, getBidRequest, formatQS} from '../../src/utils.js';
 
 export function getSizes(request) {
   let sizes = request.sizes;
@@ -167,6 +168,40 @@ export function getAd(bid) {
   return {ad, adUrl, vastXml, vastUrl};
 }
 
+export function getSyncResponse(syncOptions, gdprConsent, uspConsent, gppConsent, endpoint) {
+  const params = {
+    endpoint
+  };
+
+  // Attaching GDPR Consent Params in UserSync url
+  if (gdprConsent) {
+    params.gdpr = (gdprConsent.gdprApplies ? 1 : 0);
+    params.gdpr_consent = encodeURIComponent(gdprConsent.consentString || '');
+  }
+
+  // CCPA
+  if (uspConsent && typeof uspConsent === 'string') {
+    params.us_privacy = encodeURIComponent(uspConsent);
+  }
+
+  // GPP Consent
+  if (gppConsent?.gppString && gppConsent?.applicableSections?.length) {
+    params.gpp = encodeURIComponent(gppConsent.gppString);
+    params.gpp_sid = encodeURIComponent(gppConsent?.applicableSections?.join(','));
+  }
+
+  const queryParams = Object.keys(params).length > 0 ? formatQS(params) : '';
+  let response = [];
+  if (syncOptions.iframeEnabled) {
+    response = [{
+      type: 'iframe',
+      url: SYNC_URL + 'load-cookie.html?' + queryParams
+    }];
+  }
+
+  return response;
+}
+
 export function getSiteObj() {
   const refInfo = (getRefererInfo && getRefererInfo()) || {};
 
diff --git a/libraries/targetVideoUtils/constants.js b/libraries/targetVideoUtils/constants.js
index 1f47846eba4..33076b71e7d 100644
--- a/libraries/targetVideoUtils/constants.js
+++ b/libraries/targetVideoUtils/constants.js
@@ -9,7 +9,7 @@ const VIDEO_ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction';
 const SYNC_URL = 'https://bppb.link/static/';
 const VIDEO_PARAMS = [
   'api', 'linearity', 'maxduration', 'mimes', 'minduration',
-  'plcmt', 'playbackmethod', 'protocols', 'startdelay'
+  'plcmt', 'playbackmethod', 'protocols', 'startdelay', 'placement'
 ];
 
 export {
diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js
index 74f5e66b90b..c822f4d5c80 100644
--- a/modules/bridBidAdapter.js
+++ b/modules/bridBidAdapter.js
@@ -1,8 +1,8 @@
-import {_each, deepAccess, formatQS, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js';
+import {_each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js';
 import {VIDEO} from '../src/mediaTypes.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {getAd, getSiteObj} from '../libraries/targetVideoUtils/bidderUtils.js'
-import {GVLID, SOURCE, SYNC_URL, TIME_TO_LIVE, VIDEO_ENDPOINT_URL, VIDEO_PARAMS} from '../libraries/targetVideoUtils/constants.js';
+import {getAd, getSiteObj, getSyncResponse} from '../libraries/targetVideoUtils/bidderUtils.js'
+import {GVLID, SOURCE, TIME_TO_LIVE, VIDEO_ENDPOINT_URL, VIDEO_PARAMS} from '../libraries/targetVideoUtils/constants.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -178,37 +178,7 @@ export const spec = {
    * Return an array containing an object with the sync type and the constructed URL.
    */
   getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => {
-    const params = {
-      endpoint: 'brid'
-    };
-
-    // Attaching GDPR Consent Params in UserSync url
-    if (gdprConsent) {
-      params.gdpr = (gdprConsent.gdprApplies ? 1 : 0);
-      params.gdpr_consent = encodeURIComponent(gdprConsent.consentString || '');
-    }
-
-    // CCPA
-    if (uspConsent && typeof uspConsent === 'string') {
-      params.us_privacy = encodeURIComponent(uspConsent);
-    }
-
-    // GPP Consent
-    if (gppConsent?.gppString && gppConsent?.applicableSections?.length) {
-      params.gpp = encodeURIComponent(gppConsent.gppString);
-      params.gpp_sid = encodeURIComponent(gppConsent?.applicableSections?.join(','));
-    }
-
-    const queryParams = Object.keys(params).length > 0 ? formatQS(params) : '';
-    let response = [];
-    if (syncOptions.iframeEnabled) {
-      response = [{
-        type: 'iframe',
-        url: SYNC_URL + 'load-cookie.html?' + queryParams
-      }];
-    }
-
-    return response;
+    return getSyncResponse(syncOptions, gdprConsent, uspConsent, gppConsent, 'brid');
   }
 
 }
diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js
index fd5d79d08b7..b01e3dddab3 100644
--- a/modules/targetVideoBidAdapter.js
+++ b/modules/targetVideoBidAdapter.js
@@ -1,7 +1,7 @@
 import {_each, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {formatRequest, getRtbBid, getSiteObj, videoBid, bannerBid, createVideoTag} from '../libraries/targetVideoUtils/bidderUtils.js';
+import {formatRequest, getRtbBid, getSiteObj, getSyncResponse, videoBid, bannerBid, createVideoTag} from '../libraries/targetVideoUtils/bidderUtils.js';
 import {SOURCE, GVLID, BIDDER_CODE, VIDEO_PARAMS, BANNER_ENDPOINT_URL, VIDEO_ENDPOINT_URL, MARGIN, TIME_TO_LIVE} from '../libraries/targetVideoUtils/constants.js';
 
 /**
@@ -167,14 +167,26 @@ export const spec = {
         _each(resp.bid, (bid) => {
           const requestId = bidRequest.bidId;
           const params = bidRequest.params;
-
-          bids.push(videoBid(bid, requestId, currency, params, TIME_TO_LIVE));
+          const vBid = videoBid(bid, requestId, currency, params, TIME_TO_LIVE);
+          if (bids.length == 0 || bids[0].cpm < vBid.cpm) {
+            bids[0] = vBid;
+          }
         });
       });
     }
 
     return bids;
+  },
+
+  /**
+   * Determine the user sync type (either 'iframe' or 'image') based on syncOptions.
+   * Construct the sync URL by appending required query parameters such as gdpr, ccpa, and coppa consents.
+   * Return an array containing an object with the sync type and the constructed URL.
+   */
+  getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => {
+    return getSyncResponse(syncOptions, gdprConsent, uspConsent, gppConsent, 'targetvideo');
   }
+
 }
 
 registerBidder(spec);
diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js
index 442d7e7ef0b..61df5413862 100644
--- a/test/spec/modules/targetVideoBidAdapter_spec.js
+++ b/test/spec/modules/targetVideoBidAdapter_spec.js
@@ -1,4 +1,5 @@
 import { spec } from '../../../modules/targetVideoBidAdapter.js'
+import { SYNC_URL } from '../../../libraries/targetVideoUtils/constants.js';
 
 describe('TargetVideo Bid Adapter', function() {
   const bidder = 'targetVideo';
@@ -237,4 +238,23 @@ describe('TargetVideo Bid Adapter', function() {
     expect(payload.regs.ext.us_privacy).to.equal(uspConsentString);
     expect(payload.regs.ext.gdpr).to.equal(1);
   });
+
+  it('Test userSync have only one object and it should have a property type=iframe', function () {
+    let userSync = spec.getUserSyncs({ iframeEnabled: true });
+    expect(userSync).to.be.an('array');
+    expect(userSync.length).to.be.equal(1);
+    expect(userSync[0]).to.have.property('type');
+    expect(userSync[0].type).to.be.equal('iframe');
+  });
+
+  it('Test userSync valid sync url for iframe', function () {
+    let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, {}, {consentString: 'anyString'});
+    expect(userSync.url).to.contain(SYNC_URL + 'load-cookie.html?endpoint=targetvideo&gdpr=0&gdpr_consent=anyString');
+    expect(userSync.type).to.be.equal('iframe');
+  });
+
+  it('Test userSyncs iframeEnabled=false', function () {
+    let userSyncs = spec.getUserSyncs({iframeEnabled: false});
+    expect(userSyncs).to.have.lengthOf(0);
+  });
 });

From 8832e848e057b6ec77ebdb52ee708a44d25f0995 Mon Sep 17 00:00:00 2001
From: Denis Logachov 
Date: Tue, 26 Nov 2024 19:03:30 +0200
Subject: [PATCH 0701/1097] Adkernel: add OppaMedia alias (#12506)

---
 modules/adkernelBidAdapter.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index 22ff06966dc..ac211cc9de9 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -100,7 +100,8 @@ export const spec = {
     {code: 'global_sun'},
     {code: 'rxnetwork'},
     {code: 'revbid'},
-    {code: 'spinx', gvlid: 1308}
+    {code: 'spinx', gvlid: 1308},
+    {code: 'oppamedia'}
   ],
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 

From 894f60b380bf9d03bf9716904896858c2442a894 Mon Sep 17 00:00:00 2001
From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com>
Date: Tue, 26 Nov 2024 13:11:13 -0500
Subject: [PATCH 0702/1097] Contxtful RTD Module: support ui events (#12398)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: ui events

* doc: wording

* doc: revised to re-run ci

* test: removed os-specific test

---------

Co-authored-by: Sébastien Rufiange 
---
 modules/contxtfulRtdProvider.js               | 151 +++++++---
 modules/contxtfulRtdProvider.md               |   4 +-
 .../spec/modules/contxtfulRtdProvider_spec.js | 259 ++++++++++++------
 3 files changed, 289 insertions(+), 125 deletions(-)

diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js
index a0d11328427..ff54526abfb 100644
--- a/modules/contxtfulRtdProvider.js
+++ b/modules/contxtfulRtdProvider.js
@@ -27,7 +27,7 @@ const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io';
 
 const storageManager = getStorageManager({
   moduleType: MODULE_TYPE_RTD,
-  moduleName: MODULE_NAME
+  moduleName: MODULE_NAME,
 });
 
 let rxApi = null;
@@ -43,14 +43,11 @@ function getRxEngineReceptivity(requester) {
 }
 
 function getItemFromSessionStorage(key) {
-  let value = null;
   try {
-    // Use the Storage Manager
-    value = storageManager.getDataFromSessionStorage(key, null);
+    return storageManager.getDataFromSessionStorage(key);
   } catch (error) {
+    logError(MODULE, error);
   }
-
-  return value;
 }
 
 function loadSessionReceptivity(requester) {
@@ -102,6 +99,9 @@ function init(config) {
 
   try {
     initCustomer(config);
+
+    observeLastCursorPosition();
+
     return true;
   } catch (error) {
     logError(MODULE, error);
@@ -170,6 +170,9 @@ function addConnectorEventListener(tagId, prebidConfig) {
       }
       config['prebid'] = prebidConfig || {};
       rxApi = await rxApiBuilder(config);
+
+      // Remove listener now that we can use rxApi.
+      removeListeners();
     }
   );
 }
@@ -189,8 +192,11 @@ function getTargetingData(adUnits, config, _userConsent) {
     logInfo(MODULE, 'getTargetingData');
 
     const requester = config?.params?.customer;
-    const rx = getRxEngineReceptivity(requester) ||
-      loadSessionReceptivity(requester) || {};
+    const rx =
+      getRxEngineReceptivity(requester) ||
+      loadSessionReceptivity(requester) ||
+      {};
+
     if (isEmpty(rx)) {
       return {};
     }
@@ -215,9 +221,10 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) {
   function onReturn() {
     if (isFirstBidRequestCall) {
       isFirstBidRequestCall = false;
-    };
+    }
     onDone();
   }
+
   logInfo(MODULE, 'getBidRequestData');
   const bidders = config?.params?.bidders || [];
   if (isEmpty(bidders) || !isArray(bidders)) {
@@ -225,46 +232,31 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) {
     return;
   }
 
-  let fromApiBatched = () => rxApi?.receptivityBatched?.(bidders);
-  let fromApiSingle = () => prepareBatch(bidders, getRxEngineReceptivity);
-  let fromStorage = () => prepareBatch(bidders, loadSessionReceptivity);
-
-  function tryMethods(methods) {
-    for (let method of methods) {
-      try {
-        let batch = method();
-        if (!isEmpty(batch)) {
-          return batch;
-        }
-      } catch (error) { }
-    }
-    return {};
+  let fromApi = rxApi?.receptivityBatched?.(bidders) || {};
+  let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`));
+
+  let sources = [fromStorage, fromApi];
+  if (isFirstBidRequestCall) {
+    sources.reverse();
   }
-  let rxBatch = {};
-  try {
-    if (isFirstBidRequestCall) {
-      rxBatch = tryMethods([fromStorage, fromApiBatched, fromApiSingle]);
-    } else {
-      rxBatch = tryMethods([fromApiBatched, fromApiSingle, fromStorage])
-    }
-  } catch (error) { }
 
+  let rxBatch = Object.assign(...sources);
+
+  let singlePointEvents;
   if (isEmpty(rxBatch)) {
-    onReturn();
-    return;
+    singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() }));
   }
 
   bidders
-    .map((bidderCode) => ({ bidderCode, rx: rxBatch[bidderCode] }))
-    .filter(({ rx }) => !isEmpty(rx))
-    .forEach(({ bidderCode, rx }) => {
+    .forEach(bidderCode => {
       const ortb2 = {
         user: {
           data: [
             {
               name: MODULE_NAME,
               ext: {
-                rx,
+                rx: rxBatch[bidderCode],
+                events: singlePointEvents,
                 params: {
                   ev: config.params?.version,
                   ci: config.params?.customer,
@@ -274,13 +266,96 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) {
           ],
         },
       };
+
       mergeDeep(reqBidsConfigObj.ortb2Fragments?.bidder, {
         [bidderCode]: ortb2,
       });
     });
 
   onReturn();
-};
+}
+
+function getUiEvents() {
+  return {
+    position: lastCursorPosition,
+    screen: getScreen(),
+  };
+}
+
+function getScreen() {
+  function getInnerSize() {
+    let w = window?.innerWidth;
+    let h = window?.innerHeight;
+
+    if (w && h) {
+      return [w, h];
+    }
+  }
+
+  function getDocumentSize() {
+    let body = window?.document?.body;
+    let w = body.clientWidth;
+    let h = body.clientHeight;
+
+    if (w && h) {
+      return [w, h];
+    }
+  }
+
+  // If we cannot access or cast the window dimensions, we get None.
+  // If we cannot collect the size from the window we try to use the root document dimensions
+  let [width, height] = getInnerSize() || getDocumentSize() || [0, 0];
+  let topLeft = { x: window.scrollX, y: window.scrollY };
+
+  return {
+    topLeft,
+    width,
+    height,
+    timestampMs: performance.now(),
+  };
+}
+
+let lastCursorPosition;
+
+function observeLastCursorPosition() {
+  function pointerEventToPosition(event) {
+    lastCursorPosition = {
+      x: event.clientX,
+      y: event.clientY,
+      timestampMs: performance.now()
+    };
+  }
+
+  function touchEventToPosition(event) {
+    let touch = event.touches.item(0);
+    if (!touch) {
+      return;
+    }
+
+    lastCursorPosition = {
+      x: touch.clientX,
+      y: touch.clientY,
+      timestampMs: performance.now()
+    };
+  }
+
+  addListener('pointermove', pointerEventToPosition);
+  addListener('touchmove', touchEventToPosition);
+}
+
+let listeners = {};
+function addListener(name, listener) {
+  listeners[name] = listener;
+
+  window.addEventListener(name, listener);
+}
+
+function removeListeners() {
+  for (const name in listeners) {
+    window.removeEventListener(name, listeners[name]);
+    delete listeners[name];
+  }
+}
 
 export const contxtfulSubmodule = {
   name: MODULE_NAME,
diff --git a/modules/contxtfulRtdProvider.md b/modules/contxtfulRtdProvider.md
index 622b353c27a..de2376e782d 100644
--- a/modules/contxtfulRtdProvider.md
+++ b/modules/contxtfulRtdProvider.md
@@ -8,7 +8,7 @@
 
 The Contxtful RTD module offers a unique feature—Receptivity. Receptivity is an efficiency metric, enabling the qualification of any instant in a session in real time based on attention. The core idea is straightforward: the likelihood of an ad’s success increases when it grabs attention and is presented in the right context at the right time.
 
-To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please contact [contact@contxtful.com](mailto:contact@contxtful.com).
+To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please reach out to  [contact@contxtful.com](mailto:contact@contxtful.com).
 
 ## Build Instructions
 
@@ -72,7 +72,7 @@ pbjs.setConfig({
 | `customer`          | `String` | Required | Your unique customer identifier.           |
 | `hostname`          | `String` | Optional | Target URL for CONTXTFUL external JavaScript file. Default is "api.receptivity.io". Changing default behaviour is not recommended. Please reach out to contact@contxtful.com if you experience issues. |
 | `adServerTargeting` | `Boolean`| Optional | Enables the `getTargetingData` to inject targeting value in ad units. Setting to true enables the feature, false disables the feature. Default is true      |
-| `bidders`           | `Array`  | Optional | Setting this array enables Receptivity in the `ortb2` object through `getBidRequestData` for all the listed `bidders`. Default is `[]` (an empty array). RECOMMENDED : Add all the bidders active like this `["bidderCode1", "bidderCode", "..."]` |
+| `bidders`           | `Array`  | Optional | Setting this array enables Receptivity in the `ortb2` object through `getBidRequestData` for all the listed `bidders`. Default is `[]` (an empty array). RECOMMENDED : Add all the active bidders like this `["bidderCode1", "bidderCode", "..."]` |
 
 ## Usage: Injection in Ad Servers
 
diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js
index ad79b051393..e31ef554da0 100644
--- a/test/spec/modules/contxtfulRtdProvider_spec.js
+++ b/test/spec/modules/contxtfulRtdProvider_spec.js
@@ -1,7 +1,12 @@
 import { contxtfulSubmodule, extractParameters } from '../../../modules/contxtfulRtdProvider.js';
 import { expect } from 'chai';
 import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js';
+import { getStorageManager } from '../../../src/storageManager.js';
+import { MODULE_TYPE_UID } from '../../../src/activities/modules.js';
 import * as events from '../../../src/events';
+import Sinon from 'sinon';
+
+const MODULE_NAME = 'contxtful';
 
 const VERSION = 'v1';
 const CUSTOMER = 'CUSTOMER';
@@ -10,7 +15,7 @@ const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/preb
 const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_from_session_storage' };
 const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' };
 
-const RX_API_MOCK = { receptivity: sinon.stub(), };
+const RX_API_MOCK = { receptivity: sinon.stub(), receptivityBatched: sinon.stub() };
 const RX_CONNECTOR_MOCK = {
   fetchConfig: sinon.stub(),
   rxApiBuilder: sinon.stub(),
@@ -19,13 +24,6 @@ const RX_CONNECTOR_MOCK = {
 const TIMEOUT = 10;
 const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: {[CUSTOMER]: RX_CONNECTOR_MOCK}, bubbles: true });
 
-function writeToStorage(requester, timeDiff) {
-  let rx = RX_FROM_SESSION_STORAGE;
-  let exp = new Date().getTime() + timeDiff;
-  let item = { rx, exp, };
-  sessionStorage.setItem(requester, JSON.stringify(item),);
-}
-
 function buildInitConfig(version, customer) {
   return {
     name: 'contxtful',
@@ -44,12 +42,17 @@ describe('contxtfulRtdProvider', function () {
   let loadExternalScriptTag;
   let eventsEmitSpy;
 
+  const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME });
+
   beforeEach(() => {
     loadExternalScriptTag = document.createElement('script');
     loadExternalScriptStub.callsFake((_url, _moduleName) => loadExternalScriptTag);
 
     RX_API_MOCK.receptivity.reset();
-    RX_API_MOCK.receptivity.callsFake((tagId) => RX_FROM_API);
+    RX_API_MOCK.receptivity.callsFake(() => RX_FROM_API);
+
+    RX_API_MOCK.receptivityBatched.reset();
+    RX_API_MOCK.receptivityBatched.callsFake((bidders) => bidders.reduce((accumulator, bidder) => { accumulator[bidder] = RX_FROM_API; return accumulator; }, {}));
 
     RX_CONNECTOR_MOCK.fetchConfig.reset();
     RX_CONNECTOR_MOCK.fetchConfig.callsFake((tagId) => new Promise((resolve, reject) => resolve({ tag_id: tagId })));
@@ -249,7 +252,7 @@ describe('contxtfulRtdProvider', function () {
 
         setTimeout(() => {
           let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config);
-          expect(targetingData, description).to.deep.equal(expected);
+          expect(targetingData, description).to.deep.equal(expected, description);
           done();
         }, TIMEOUT);
       });
@@ -322,22 +325,18 @@ describe('contxtfulRtdProvider', function () {
     ];
 
     theories.forEach(([adUnits, expected, _description]) => {
-      // TODO: commented out because of rule violations
-      /*
       it('uses non-expired info from session storage and adds receptivity to the ad units using session storage', function (done) {
-        let config = buildInitConfig(VERSION, CUSTOMER);
         // Simulate that there was a write to sessionStorage in the past.
-        writeToStorage(config.params.customer, +100);
+        storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE}))
+
+        let config = buildInitConfig(VERSION, CUSTOMER);
         contxtfulSubmodule.init(config);
 
-        setTimeout(() => {
-          expect(contxtfulSubmodule.getTargetingData(adUnits, config)).to.deep.equal(
-            expected
-          );
-          done();
-        }, TIMEOUT);
+        let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config);
+        expect(targetingData).to.deep.equal(expected);
+
+        done();
       });
-       */
     });
   });
 
@@ -360,13 +359,15 @@ describe('contxtfulRtdProvider', function () {
 
     theories.forEach(([adUnits, expected, _description]) => {
       it('ignores expired info from session storage and does not forward the info to ad units', function (done) {
-        let config = buildInitConfig(VERSION, CUSTOMER);
         // Simulate that there was a write to sessionStorage in the past.
-        writeToStorage(config.params.customer, -100);
+        storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({exp: new Date().getTime() - 100, rx: RX_FROM_SESSION_STORAGE}));
+
+        let config = buildInitConfig(VERSION, CUSTOMER);
         contxtfulSubmodule.init(config);
-        expect(contxtfulSubmodule.getTargetingData(adUnits, config)).to.deep.equal(
-          expected
-        );
+
+        let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config);
+        expect(targetingData).to.deep.equal(expected);
+
         done();
       });
     });
@@ -428,42 +429,39 @@ describe('contxtfulRtdProvider', function () {
         },
       };
 
-      let expectedOrtb2 = {
-        user: {
-          data: [
-            {
-              name: 'contxtful',
-              ext: {
-                rx: RX_FROM_API,
-                params: {
-                  ev: config.params?.version,
-                  ci: config.params?.customer,
-                },
-              },
-            },
-          ],
+      let expectedData = {
+        name: 'contxtful',
+        ext: {
+          rx: RX_FROM_API,
+          params: {
+            ev: config.params?.version,
+            ci: config.params?.customer,
+          },
         },
       };
 
       setTimeout(() => {
-        const onDone = () => undefined;
-        contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDone, config);
-        let actualOrtb2 = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]];
-        expect(actualOrtb2).to.deep.equal(expectedOrtb2);
+        const onDoneSpy = sinon.spy();
+        contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config);
+
+        let data = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0];
+
+        expect(data.name).to.deep.equal(expectedData.name);
+        expect(data.ext.rx).to.deep.equal(expectedData.ext.rx);
+        expect(data.ext.params).to.deep.equal(expectedData.ext.params);
         done();
       }, TIMEOUT);
     });
   });
 
   describe('getBidRequestData', function () {
-    // TODO: commented out because of rule violations
-    /*
     it('uses non-expired info from session storage and adds receptivity to the reqBidsConfigObj', function (done) {
       let config = buildInitConfig(VERSION, CUSTOMER);
+
       // Simulate that there was a write to sessionStorage in the past.
-      writeToStorage(config.params.bidders[0], +100);
+      let bidder = config.params.bidders[0];
 
-      contxtfulSubmodule.init(config);
+      storage.setDataInSessionStorage(`${config.params.customer}_${bidder}`, JSON.stringify({exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE}));
 
       let reqBidsConfigObj = {
         ortb2Fragments: {
@@ -472,33 +470,26 @@ describe('contxtfulRtdProvider', function () {
         },
       };
 
-      let expectedOrtb2 = {
-        user: {
-          data: [
-            {
-              name: 'contxtful',
-              ext: {
-                rx: RX_FROM_SESSION_STORAGE,
-                params: {
-                  ev: config.params?.version,
-                  ci: config.params?.customer,
-                },
-              },
-            },
-          ],
-        },
-      };
+      contxtfulSubmodule.init(config);
 
       // Since the RX_CONNECTOR_IS_READY_EVENT event was not dispatched, the RX engine is not loaded.
+      contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, () => {}, config);
+
       setTimeout(() => {
-        const noOp = () => undefined;
-        contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, noOp, buildInitConfig(VERSION, CUSTOMER));
-        let actualOrtb2 = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]];
-        expect(actualOrtb2).to.deep.equal(expectedOrtb2);
+        let ortb2BidderFragment = reqBidsConfigObj.ortb2Fragments.bidder[bidder];
+        let userData = ortb2BidderFragment.user.data;
+        let contxtfulData = userData[0];
+
+        expect(contxtfulData.name).to.be.equal('contxtful');
+        expect(contxtfulData.ext.rx).to.deep.equal(RX_FROM_SESSION_STORAGE);
+        expect(contxtfulData.ext.params).to.deep.equal({
+          ev: config.params.version,
+          ci: config.params.customer,
+        });
+
         done();
       }, TIMEOUT);
     });
-     */
   });
 
   describe('getBidRequestData', function () {
@@ -520,7 +511,7 @@ describe('contxtfulRtdProvider', function () {
         const onDoneSpy = sinon.spy();
         contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config);
         expect(onDoneSpy.callCount).to.equal(1);
-        expect(RX_API_MOCK.receptivity.callCount).to.equal(1);
+        expect(RX_API_MOCK.receptivityBatched.callCount).to.equal(1);
         done();
       }, TIMEOUT);
     });
@@ -539,29 +530,127 @@ describe('contxtfulRtdProvider', function () {
         },
       };
 
-      let ortb2 = {
-        user: {
-          data: [
-            {
-              name: 'contxtful',
-              ext: {
-                rx: RX_FROM_API,
-                params: {
-                  ev: config.params?.version,
-                  ci: config.params?.customer,
-                },
-              },
-            },
-          ],
+      let expectedData = {
+        name: 'contxtful',
+        ext: {
+          rx: RX_FROM_API,
+          params: {
+            ev: config.params?.version,
+            ci: config.params?.customer,
+          },
         },
       };
 
       setTimeout(() => {
         const onDoneSpy = sinon.spy();
         contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config);
-        expect(reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]).to.deep.equal(ortb2);
+
+        let data = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0];
+
+        expect(data.name).to.deep.equal(expectedData.name);
+        expect(data.ext.rx).to.deep.equal(expectedData.ext.rx);
+        expect(data.ext.params).to.deep.equal(expectedData.ext.params);
         done();
       }, TIMEOUT);
     });
+
+    describe('before rxApi is loaded', function () {
+      const moveEventTheories = [
+        [
+          new PointerEvent('pointermove', { clientX: 1, clientY: 2 }),
+          { x: 1, y: 2 },
+          'pointer move',
+        ]
+      ];
+
+      moveEventTheories.forEach(([event, expected, _description]) => {
+        it('adds move event', function (done) {
+          let config = buildInitConfig(VERSION, CUSTOMER);
+          contxtfulSubmodule.init(config);
+
+          window.dispatchEvent(event);
+
+          let reqBidsConfigObj = {
+            ortb2Fragments: {
+              global: {},
+              bidder: {},
+            },
+          };
+
+          setTimeout(() => {
+            const onDoneSpy = sinon.spy();
+            contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config);
+
+            let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext;
+
+            let events = JSON.parse(atob(ext.events));
+
+            expect(events.ui.position.x).to.be.deep.equal(expected.x);
+            expect(events.ui.position.y).to.be.deep.equal(expected.y);
+            expect(Sinon.match.number.test(events.ui.position.timestampMs)).to.be.true;
+            done();
+          }, TIMEOUT);
+        });
+      });
+
+      it('adds screen event', function (done) {
+        let config = buildInitConfig(VERSION, CUSTOMER);
+        contxtfulSubmodule.init(config);
+
+        // Cannot change the window size from JS
+        // So we take the current size as expectation
+        const width = window.innerWidth;
+        const height = window.innerHeight;
+
+        let reqBidsConfigObj = {
+          ortb2Fragments: {
+            global: {},
+            bidder: {},
+          },
+        };
+
+        setTimeout(() => {
+          const onDoneSpy = sinon.spy();
+          contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config);
+
+          let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext;
+
+          let events = JSON.parse(atob(ext.events));
+
+          expect(events.ui.screen.topLeft).to.be.deep.equal({ x: 0, y: 0 }, 'screen top left');
+          expect(events.ui.screen.width).to.be.deep.equal(width, 'screen width');
+          expect(events.ui.screen.height).to.be.deep.equal(height, 'screen height');
+          expect(Sinon.match.number.test(events.ui.screen.timestampMs), 'screen timestamp').to.be.true;
+          done();
+        }, TIMEOUT);
+      });
+    })
   });
+
+  describe('after rxApi is loaded', function () {
+    it('does not add event', function (done) {
+      let config = buildInitConfig(VERSION, CUSTOMER);
+      contxtfulSubmodule.init(config);
+      window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT);
+
+      let reqBidsConfigObj = {
+        ortb2Fragments: {
+          global: {},
+          bidder: {},
+        },
+      };
+
+      setTimeout(() => {
+        const onDoneSpy = sinon.spy();
+        contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config);
+
+        let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext;
+
+        let events = ext.events;
+
+        expect(events).to.be.undefined;
+        done();
+      }, TIMEOUT);
+    });
+  })
 });

From 8f0bb737a83876d559b9be3bdc0a8feabb30ce16 Mon Sep 17 00:00:00 2001
From: Mikhail Malkov 
Date: Wed, 27 Nov 2024 01:09:08 +0300
Subject: [PATCH 0703/1097] nextMillennium Bid Adapter: Fixed a bug when there
 were several bids with the same adUnit.code (#12505)

* nextMillenniumBidAdaper: Fixed a bug when there were several bids with the same adUnit.code

* nextMillenniumBidAdaper: Fixed a bug when there were several bids with the same adUnit.code
---
 modules/nextMillenniumBidAdapter.js           | 15 +++----
 .../modules/nextMillenniumBidAdapter_spec.js  | 39 +++++++------------
 2 files changed, 20 insertions(+), 34 deletions(-)

diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js
index 8c794fdaf03..8cf20848675 100644
--- a/modules/nextMillenniumBidAdapter.js
+++ b/modules/nextMillenniumBidAdapter.js
@@ -21,7 +21,7 @@ import {config} from '../src/config.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {getRefererInfo} from '../src/refererDetection.js';
 
-const NM_VERSION = '4.2.0';
+const NM_VERSION = '4.2.1';
 const PBJS_VERSION = 'v$prebid.version$';
 const GVLID = 1060;
 const BIDDER_CODE = 'nextMillennium';
@@ -87,7 +87,6 @@ export const spec = {
 
   buildRequests: function(validBidRequests, bidderRequest) {
     const requests = [];
-    const bidIds = {};
     window.nmmRefreshCounts = window.nmmRefreshCounts || {};
     const site = getSiteObj();
     const device = getDeviceObj();
@@ -121,8 +120,6 @@ export const spec = {
       if (i === 0) postBody.cur = cur;
       postBody.imp.push(getImp(bid, id, mediaTypes));
       postBody.ext.next_mil_imps.push(getExtNextMilImp(bid));
-
-      bidIds[bid.adUnitCode] = bid.bidId;
     });
 
     this.getUrlPixelMetric(EVENTS.BID_REQUESTED, validBidRequests);
@@ -135,21 +132,19 @@ export const spec = {
         contentType: 'text/plain',
         withCredentials: true,
       },
-
-      bidIds,
     });
 
     return requests;
   },
 
-  interpretResponse: function(serverResponse, bidRequest) {
+  interpretResponse: function(serverResponse) {
     const response = serverResponse.body;
     const bidResponses = [];
 
     const bids = [];
     _each(response.seatbid, (resp) => {
       _each(resp.bid, (bid) => {
-        const requestId = bidRequest.bidIds[bid.impid];
+        const requestId = bid.impid;
 
         const {ad, adUrl, vastUrl, vastXml} = getAd(bid);
 
@@ -271,7 +266,7 @@ export const spec = {
 function getExtNextMilImp(bid) {
   if (typeof window?.nmmRefreshCounts[bid.adUnitCode] === 'number') ++window.nmmRefreshCounts[bid.adUnitCode];
   const nextMilImp = {
-    impId: bid.adUnitCode,
+    impId: bid.bidId,
     nextMillennium: {
       nm_version: NM_VERSION,
       pbjs_version: PBJS_VERSION,
@@ -286,7 +281,7 @@ function getExtNextMilImp(bid) {
 export function getImp(bid, id, mediaTypes) {
   const {banner, video} = mediaTypes;
   const imp = {
-    id: bid.adUnitCode,
+    id: bid.bidId,
     ext: {
       prebid: {
         storedrequest: {
diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js
index 5cf51af9948..a5c5f5f714d 100644
--- a/test/spec/modules/nextMillenniumBidAdapter_spec.js
+++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js
@@ -20,6 +20,7 @@ describe('nextMillenniumBidAdapterTests', () => {
           bid: {
             mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}},
             adUnitCode: 'test-banner-1',
+            bidId: 'e36ea395f67f',
           },
 
           mediaTypes: {
@@ -32,7 +33,7 @@ describe('nextMillenniumBidAdapterTests', () => {
         },
 
         expected: {
-          id: 'test-banner-1',
+          id: 'e36ea395f67f',
           bidfloorcur: 'EUR',
           bidfloor: 1.11,
           ext: {prebid: {storedrequest: {id: '123'}}},
@@ -48,6 +49,7 @@ describe('nextMillenniumBidAdapterTests', () => {
           bid: {
             mediaTypes: {video: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}},
             adUnitCode: 'test-video-1',
+            bidId: 'e36ea395f67f',
           },
 
           mediaTypes: {
@@ -59,7 +61,7 @@ describe('nextMillenniumBidAdapterTests', () => {
         },
 
         expected: {
-          id: 'test-video-1',
+          id: 'e36ea395f67f',
           bidfloorcur: 'USD',
           ext: {prebid: {storedrequest: {id: '234'}}},
           video: {
@@ -80,7 +82,7 @@ describe('nextMillenniumBidAdapterTests', () => {
           postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}},
           bid: {
             mediaTypes: {video: {w: 640, h: 480}},
-            adUnitCode: 'test-video-2',
+            bidId: 'e36ea395f67f',
           },
 
           mediaTypes: {
@@ -92,7 +94,7 @@ describe('nextMillenniumBidAdapterTests', () => {
         },
 
         expected: {
-          id: 'test-video-2',
+          id: 'e36ea395f67f',
           bidfloorcur: 'USD',
           ext: {prebid: {storedrequest: {id: '234'}}},
           video: {w: 640, h: 480, mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript']},
@@ -789,17 +791,17 @@ describe('nextMillenniumBidAdapterTests', () => {
   describe('Check ext.next_mil_imps', function() {
     const expectedNextMilImps = [
       {
-        impId: 'nmi-test-0',
+        impId: 'bid1234',
         nextMillennium: {refresh_count: 1},
       },
 
       {
-        impId: 'nmi-test-1',
+        impId: 'bid1235',
         nextMillennium: {refresh_count: 1},
       },
 
       {
-        impId: 'nmi-test-2',
+        impId: 'bid1236',
         nextMillennium: {refresh_count: 1},
       },
     ];
@@ -1055,7 +1057,6 @@ describe('nextMillenniumBidAdapterTests', () => {
 
         expected: {
           id: 'mock-uuid',
-          bidIds: {'test-div': 'bid1234', 'test-div-2': 'bid1235'},
           impSize: 2,
           requestSize: 1,
           domain: 'example.com',
@@ -1068,7 +1069,6 @@ describe('nextMillenniumBidAdapterTests', () => {
       it(title, () => {
         const request = spec.buildRequests(bidRequests, bidderRequest);
         expect(request.length).to.equal(expected.requestSize);
-        expect(request[0].bidIds).to.deep.equal(expected.bidIds);
 
         const requestData = JSON.parse(request[0].data);
         expect(requestData.id).to.equal(expected.id);
@@ -1090,7 +1090,7 @@ describe('nextMillenniumBidAdapterTests', () => {
                 bid: [
                   {
                     id: '7457329903666272789-0',
-                    impid: 'ad-unit-0',
+                    impid: '700ce0a43f72',
                     price: 0.5,
                     adm: 'Hello! It\'s a test ad!',
                     adid: '96846035-0',
@@ -1101,7 +1101,7 @@ describe('nextMillenniumBidAdapterTests', () => {
 
                   {
                     id: '7457329903666272789-1',
-                    impid: 'ad-unit-1',
+                    impid: '700ce0a43f73',
                     price: 0.7,
                     adm: 'https://some_vast_host.com/vast.xml',
                     adid: '96846035-1',
@@ -1113,7 +1113,7 @@ describe('nextMillenniumBidAdapterTests', () => {
 
                   {
                     id: '7457329903666272789-2',
-                    impid: 'ad-unit-3',
+                    impid: '700ce0a43f74',
                     price: 1.0,
                     adm: '',
                     adid: '96846035-3',
@@ -1129,19 +1129,10 @@ describe('nextMillenniumBidAdapterTests', () => {
           },
         },
 
-        bidRequest: {
-          bidIds: {
-            'ad-unit-0': 'bid-id-0',
-            'ad-unit-1': 'bid-id-1',
-            'ad-unit-2': 'bid-id-2',
-            'ad-unit-3': 'bid-id-3',
-          },
-        },
-
         expected: [
           {
             title: 'banner',
-            requestId: 'bid-id-0',
+            requestId: '700ce0a43f72',
             creativeId: '96846035-0',
             ad: 'Hello! It\'s a test ad!',
             vastUrl: undefined,
@@ -1154,7 +1145,7 @@ describe('nextMillenniumBidAdapterTests', () => {
 
           {
             title: 'video - vastUrl',
-            requestId: 'bid-id-1',
+            requestId: '700ce0a43f73',
             creativeId: '96846035-1',
             ad: undefined,
             vastUrl: 'https://some_vast_host.com/vast.xml',
@@ -1167,7 +1158,7 @@ describe('nextMillenniumBidAdapterTests', () => {
 
           {
             title: 'video - vastXml',
-            requestId: 'bid-id-3',
+            requestId: '700ce0a43f74',
             creativeId: '96846035-3',
             ad: undefined,
             vastUrl: undefined,

From b319bcc529930373a72a8d87c36f09c2601bd444 Mon Sep 17 00:00:00 2001
From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com>
Date: Tue, 26 Nov 2024 17:14:21 -0500
Subject: [PATCH 0704/1097] Contxtful RTD Module: added defer param (#12499)

* feat: defer param

* fix: added typecheck
---
 modules/contxtfulRtdProvider.js | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js
index ff54526abfb..55623c00591 100644
--- a/modules/contxtfulRtdProvider.js
+++ b/modules/contxtfulRtdProvider.js
@@ -23,7 +23,8 @@ import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
 const MODULE_NAME = 'contxtful';
 const MODULE = `${MODULE_NAME}RtdProvider`;
 
-const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io';
+const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io';
+const CONTXTFUL_DEFER_DEFAULT = 0;
 
 const storageManager = getStorageManager({
   moduleType: MODULE_TYPE_RTD,
@@ -128,9 +129,10 @@ export function extractParameters(config) {
     throw Error(`${MODULE}: params.customer should be a non-empty string`);
   }
 
-  const hostname = config?.params?.hostname || CONTXTFUL_RECEPTIVITY_DOMAIN;
+  const hostname = config?.params?.hostname || CONTXTFUL_HOSTNAME_DEFAULT;
+  const defer = config?.params?.defer || CONTXTFUL_DEFER_DEFAULT;
 
-  return { version, customer, hostname };
+  return { version, customer, hostname, defer };
 }
 
 /**
@@ -139,7 +141,7 @@ export function extractParameters(config) {
  * @param { String } config
  */
 function initCustomer(config) {
-  const { version, customer, hostname } = extractParameters(config);
+  const { version, customer, hostname, defer } = extractParameters(config);
   const CONNECTOR_URL = buildUrl({
     protocol: 'https',
     host: hostname,
@@ -147,7 +149,14 @@ function initCustomer(config) {
   });
 
   addConnectorEventListener(customer, config);
-  loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME);
+
+  const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME);
+  // Optionally defer the loading of the script
+  if (Number.isFinite(defer) && defer > 0) {
+    setTimeout(loadScript, defer);
+  } else {
+    loadScript();
+  }
 }
 
 /**

From c5da2ad942ba30b968820af15e0b48e31836b71b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Manuel=20Alfonso=20Rodr=C3=ADguez?= 
Date: Tue, 26 Nov 2024 17:57:41 -0500
Subject: [PATCH 0705/1097] Utiq ID module: add netID support (#12494)

* Utiq ID module: Add netID support

* Utiq ID module: Refactor netId test for readability

---------

Co-authored-by: manuel 
---
 modules/utiqIdSystem.js                | 11 ++++++++
 test/spec/modules/utiqIdSystem_spec.js | 39 ++++++++++++++++++++++++++
 2 files changed, 50 insertions(+)

diff --git a/modules/utiqIdSystem.js b/modules/utiqIdSystem.js
index d6b8aae44a3..b5da87627a5 100644
--- a/modules/utiqIdSystem.js
+++ b/modules/utiqIdSystem.js
@@ -28,6 +28,17 @@ function getUtiqFromStorage() {
     storage.getDataFromLocalStorage('utiqPass')
   );
 
+  const netIdAdtechpass = storage.getDataFromLocalStorage('netid_utiq_adtechpass');
+
+  if (netIdAdtechpass) {
+    logInfo(
+      `${LOG_PREFIX}: Local storage netid_utiq_adtechpass: ${netIdAdtechpass}`
+    );
+    return {
+      utiq: netIdAdtechpass,
+    }
+  }
+
   if (
     utiqPassStorage &&
     utiqPassStorage.connectId &&
diff --git a/test/spec/modules/utiqIdSystem_spec.js b/test/spec/modules/utiqIdSystem_spec.js
index 62754d39fa3..ef8e4efc5c5 100644
--- a/test/spec/modules/utiqIdSystem_spec.js
+++ b/test/spec/modules/utiqIdSystem_spec.js
@@ -4,6 +4,7 @@ import { storage } from 'modules/utiqIdSystem.js';
 
 describe('utiqIdSystem', () => {
   const utiqPassKey = 'utiqPass';
+  const netIdKey = 'netid_utiq_adtechpass';
 
   const getStorageData = (idGraph) => {
     if (!idGraph) {
@@ -185,4 +186,42 @@ describe('utiqIdSystem', () => {
       });
     });
   });
+
+  describe('utiq getUtiqFromStorage', () => {
+    afterEach(() => {
+      storage.removeDataFromLocalStorage(utiqPassKey);
+    });
+
+    it(`correctly set utiqPassKey as adtechpass utiq value for ${netIdKey} empty`, (done) => {
+      // given
+      storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData({
+        'domain': 'TEST DOMAIN',
+        'atid': 'TEST ATID',
+      }))); // setting idGraph
+      storage.setDataInLocalStorage(netIdKey, ''); // setting an empty value
+
+      // when
+      const response = utiqIdSubmodule.getId();
+
+      // then
+      expect(response.id.utiq).to.be.equal('TEST ATID');
+      done();
+    });
+
+    it(`correctly set netIdAdtechpass as adtechpass utiq value for ${netIdKey} settled`, (done) => {
+      // given
+      storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData({
+        'domain': 'TEST DOMAIN',
+        'atid': 'TEST ATID',
+      }))); // setting idGraph
+      storage.setDataInLocalStorage(netIdKey, 'testNetIdValue'); // setting a correct value
+
+      // when
+      const response = utiqIdSubmodule.getId();
+
+      // then
+      expect(response.id.utiq).to.be.equal('testNetIdValue');
+      done();
+    });
+  });
 });

From 99e888cf53180e5ae2f707fa6b44881a91e54893 Mon Sep 17 00:00:00 2001
From: Copper6SSP 
Date: Wed, 27 Nov 2024 04:00:57 +0200
Subject: [PATCH 0706/1097] Copper6ssp Bid Adapter: add gvl_id (#12498)

* release adapter Copper6SSP

* removed code duplication

* add gvl_id
---
 modules/copper6sspBidAdapter.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/modules/copper6sspBidAdapter.js b/modules/copper6sspBidAdapter.js
index 335b3b3d144..e05ed241cc6 100644
--- a/modules/copper6sspBidAdapter.js
+++ b/modules/copper6sspBidAdapter.js
@@ -5,9 +5,11 @@ import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } fro
 const BIDDER_CODE = 'copper6ssp';
 const AD_URL = 'https://endpoint.copper6.com/pbjs';
 const SYNC_URL = 'https://сsync.copper6.com';
+const GVLID = 1356;
 
 export const spec = {
   code: BIDDER_CODE,
+  gvlid: GVLID,
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 
   isBidRequestValid: isBidRequestValid(),

From 36f60c13fecdadc0a540b82040c483fe215e68fd Mon Sep 17 00:00:00 2001
From: Demetrio Girardi 
Date: Tue, 26 Nov 2024 18:01:35 -0800
Subject: [PATCH 0707/1097] vastTrackers: make request and auction info
 available to VAST trackers (#12468)

---
 libraries/vastTrackers/vastTrackers.js   | 46 +++++++++++++-----
 test/spec/libraries/vastTrackers_spec.js | 60 ++++++++++++++++++------
 2 files changed, 80 insertions(+), 26 deletions(-)

diff --git a/libraries/vastTrackers/vastTrackers.js b/libraries/vastTrackers/vastTrackers.js
index f414a65a18c..b8fc829a89a 100644
--- a/libraries/vastTrackers/vastTrackers.js
+++ b/libraries/vastTrackers/vastTrackers.js
@@ -4,27 +4,47 @@ import {logError} from '../../src/utils.js';
 import {isActivityAllowed} from '../../src/activities/rules.js';
 import {ACTIVITY_REPORT_ANALYTICS} from '../../src/activities/activities.js';
 import {activityParams} from '../../src/activities/activityParams.js';
+import {auctionManager} from '../../src/auctionManager.js';
 
 const vastTrackers = [];
+let enabled = false;
 
 export function reset() {
   vastTrackers.length = 0;
 }
 
-export function addTrackersToResponse(next, adUnitcode, bidResponse, reject) {
-  if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) {
-    const vastTrackers = getVastTrackers(bidResponse);
-    if (vastTrackers) {
-      bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml);
-      const impTrackers = vastTrackers.get('impressions');
-      if (impTrackers) {
-        bidResponse.vastImpUrl = [].concat([...impTrackers]).concat(bidResponse.vastImpUrl).filter(t => t);
+export function enable() {
+  if (!enabled) {
+    addBidResponse.before(addTrackersToResponse);
+    enabled = true;
+  }
+}
+
+export function disable() {
+  if (enabled) {
+    addBidResponse.getHooks({hook: addTrackersToResponse}).remove();
+    enabled = false;
+  }
+}
+
+export function responseHook({index = auctionManager.index} = {}) {
+  return function addTrackersToResponse(next, adUnitcode, bidResponse, reject) {
+    if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) {
+      const vastTrackers = getVastTrackers(bidResponse, {index});
+      if (vastTrackers) {
+        bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml);
+        const impTrackers = vastTrackers.get('impressions');
+        if (impTrackers) {
+          bidResponse.vastImpUrl = [].concat([...impTrackers]).concat(bidResponse.vastImpUrl).filter(t => t);
+        }
       }
     }
+    next(adUnitcode, bidResponse, reject);
   }
-  next(adUnitcode, bidResponse, reject);
 }
-addBidResponse.before(addTrackersToResponse);
+
+const addTrackersToResponse = responseHook();
+enable();
 
 export function registerVastTrackers(moduleType, moduleName, trackerFn) {
   if (typeof trackerFn === 'function') {
@@ -54,7 +74,7 @@ export function insertVastTrackers(trackers, vastXml) {
   return vastXml;
 }
 
-export function getVastTrackers(bid) {
+export function getVastTrackers(bid, {index = auctionManager.index}) {
   let trackers = [];
   vastTrackers.filter(
     ({
@@ -63,7 +83,9 @@ export function getVastTrackers(bid) {
       trackerFn
     }) => isActivityAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(moduleType, moduleName))
   ).forEach(({trackerFn}) => {
-    let trackersToAdd = trackerFn(bid);
+    const auction = index.getAuction(bid).getProperties();
+    const bidRequest = index.getBidRequest(bid);
+    let trackersToAdd = trackerFn(bid, {auction, bidRequest});
     trackersToAdd.forEach(trackerToAdd => {
       if (isValidVastTracker(trackers, trackerToAdd)) {
         trackers.push(trackerToAdd);
diff --git a/test/spec/libraries/vastTrackers_spec.js b/test/spec/libraries/vastTrackers_spec.js
index c336eec0321..3e8a8456e9c 100644
--- a/test/spec/libraries/vastTrackers_spec.js
+++ b/test/spec/libraries/vastTrackers_spec.js
@@ -4,38 +4,74 @@ import {
   getVastTrackers,
   insertVastTrackers,
   registerVastTrackers,
-  reset
+  reset, responseHook,
+  disable
 } from 'libraries/vastTrackers/vastTrackers.js';
 import {MODULE_TYPE_ANALYTICS} from '../../../src/activities/modules.js';
+import {AuctionIndex} from '../../../src/auctionIndex.js';
 
 describe('vast trackers', () => {
+  let sandbox, tracker, auction, bid, bidRequest, index;
   beforeEach(() => {
-    registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) {
+    bid = {
+      requestId: 'bid',
+      cpm: 1.0,
+      auctionId: 'aid',
+      mediaType: 'video',
+    }
+    bidRequest = {
+      auctionId: 'aid',
+      bidId: 'bid',
+    }
+    auction = {
+      getAuctionId() {
+        return 'aid';
+      },
+      getProperties() {
+        return {auction: 'props'};
+      },
+      getBidRequests() {
+        return [{bids: [bidRequest]}]
+      }
+    };
+    sandbox = sinon.sandbox.create();
+    index = new AuctionIndex(() => [auction]);
+    tracker = sinon.stub().callsFake(function (bidResponse) {
       return [
         {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`}
       ];
     });
+    registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', tracker);
   })
   afterEach(() => {
     reset();
+    sandbox.restore();
   });
 
-  it('insert into tracker list', function() {
-    const trackers = getVastTrackers({'cpm': 1.0});
+  after(disable);
+
+  it('insert into tracker list', function () {
+    const trackers = getVastTrackers(bid, {index});
     expect(trackers).to.be.a('map');
     expect(trackers.get('impressions')).to.exists;
     expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true;
   });
 
-  it('insert trackers in vastXml', function() {
-    const trackers = getVastTrackers({'cpm': 1.0});
+  it('insert trackers in vastXml', function () {
+    const trackers = getVastTrackers(bid, {index});
     let vastXml = '';
     vastXml = insertVastTrackers(trackers, vastXml);
     expect(vastXml).to.equal('');
   });
 
-  it('test addImpUrlToTrackers', function() {
-    const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers({'cpm': 1.0}));
+  it('should pass request and auction properties to trackerFn', () => {
+    const bid = {requestId: 'bid', auctionId: 'aid'};
+    getVastTrackers(bid, {index});
+    sinon.assert.calledWith(tracker, bid, sinon.match({auction: auction.getProperties(), bidRequest}))
+  })
+
+  it('test addImpUrlToTrackers', function () {
+    const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers(bid, {index}));
     expect(trackers).to.be.a('map');
     expect(trackers.get('impressions')).to.exists;
     expect(trackers.get('impressions').has('imptracker.com')).to.be.true;
@@ -43,12 +79,8 @@ describe('vast trackers', () => {
 
   if (FEATURES.VIDEO) {
     it('should add trackers to bid response', () => {
-      const bidResponse = {
-        mediaType: 'video',
-        cpm: 1
-      }
-      addTrackersToResponse(sinon.stub(), 'au', bidResponse);
-      expect(bidResponse.vastImpUrl).to.eql([
+      responseHook({index})(sinon.stub(), 'au', bid);
+      expect(bid.vastImpUrl).to.eql([
         'https://vasttracking.mydomain.com/vast?cpm=1'
       ])
     });

From 80565144db66d240a7ea6de05cfe8bde7fc0d244 Mon Sep 17 00:00:00 2001
From: dmytro-po 
Date: Wed, 27 Nov 2024 14:35:35 +0200
Subject: [PATCH 0708/1097] IntentIq ID & Analytics Modules : CMP values and
 browser detection bug fix (#12511)

* update intentIqAnalyticsAdapter.js && intentIqIdSystem.js

* fix lint issues

* fix tests

* move info

* resolve issues

* update storeFirstPartyData

* remove unused code

* update defineEmptyDataAndFireCallback

* update fix lint

* update reportExternalWin

* small fixes

* update test && add docs

* AGT-347: Support domain name

* AGT-347: Support domain name

* AGT-374: Support domainName to vrref

* AGT-374: tests in progress

* AGT-374: Remove duplicate encoded in getRelevantRefferer and fix tests

* AGT-374: Add test domainName, changes in documentation

* AGT-374: Change js version value

* AGT-374: Remove extra coma

* Remove unused method

* AGT-384: gpp string value

* AGT-384: GPC value, browserDetector fix

* AGT-384: Remove gpc logic

* AGT-384: Reduce getGppValue method

* AGT-384: Gpp tests and prevent send ids from LS in group B

* AGT-384: Change empty array of eids

* AGT-384: Some fixes after review

---------

Co-authored-by: dlepetynskyi 
Co-authored-by: DimaIntentIQ 
Co-authored-by: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com>
---
 .../intentIqConstants/intentIqConstants.js    |   2 +-
 libraries/intentIqUtils/detectBrowserUtils.js |  10 ++
 libraries/intentIqUtils/getGppValue.js        |  16 +++
 modules/intentIqAnalyticsAdapter.js           |   6 +-
 modules/intentIqIdSystem.js                   |  51 +++-----
 test/spec/modules/intentIqIdSystem_spec.js    | 118 ++++++++----------
 6 files changed, 99 insertions(+), 104 deletions(-)
 create mode 100644 libraries/intentIqUtils/getGppValue.js

diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js
index 56f014be4c1..af7f7722213 100644
--- a/libraries/intentIqConstants/intentIqConstants.js
+++ b/libraries/intentIqConstants/intentIqConstants.js
@@ -7,4 +7,4 @@ export const OPT_OUT = 'O';
 export const BLACK_LIST = 'L';
 export const CLIENT_HINTS_KEY = '_iiq_ch';
 export const EMPTY = 'EMPTY'
-export const VERSION = 0.22
+export const VERSION = 0.23
diff --git a/libraries/intentIqUtils/detectBrowserUtils.js b/libraries/intentIqUtils/detectBrowserUtils.js
index 0606388a346..c7004c77ae9 100644
--- a/libraries/intentIqUtils/detectBrowserUtils.js
+++ b/libraries/intentIqUtils/detectBrowserUtils.js
@@ -32,6 +32,16 @@ export function detectBrowserFromUserAgent(userAgent) {
     ie: /MSIE|Trident/,
   };
 
+  // Check for Edge first
+  if (browserRegexPatterns.edge.test(userAgent)) {
+    return 'edge';
+  }
+
+  // Check for Opera next
+  if (browserRegexPatterns.opera.test(userAgent)) {
+    return 'opera';
+  }
+
   // Check for Chrome first to avoid confusion with Safari
   if (browserRegexPatterns.chrome.test(userAgent)) {
     return 'chrome';
diff --git a/libraries/intentIqUtils/getGppValue.js b/libraries/intentIqUtils/getGppValue.js
new file mode 100644
index 00000000000..9c538b4f753
--- /dev/null
+++ b/libraries/intentIqUtils/getGppValue.js
@@ -0,0 +1,16 @@
+import {gppDataHandler} from '../../src/consentHandler.js';
+
+/**
+ * Retrieves the GPP string value and additional GPP-related information.
+ * This function extracts the GPP data, encodes it, and determines specific GPP flags such as GPI and applicable sections.
+ * @return {Object} An object containing:
+ * - `gppString` (string): The encoded GPP string value.
+ * - `gpi` (number): An indicator representing whether GPP consent is available (0 if available, 1 if not).
+ */
+export function getGppValue() {
+  const gppData = gppDataHandler.getConsentData();
+  const gppString = gppData?.gppString || '';
+  const gpi = gppString ? 0 : 1;
+
+  return { gppString, gpi };
+}
diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js
index a3113b7b089..1779152c5f0 100644
--- a/modules/intentIqAnalyticsAdapter.js
+++ b/modules/intentIqAnalyticsAdapter.js
@@ -8,6 +8,7 @@ import {EVENTS} from '../src/constants.js';
 import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js';
 import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
 import {appendVrrefAndFui, getReferrer} from '../libraries/intentIqUtils/getRefferer.js';
+import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js';
 import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js';
 
 const MODULE_NAME = 'iiqAnalytics'
@@ -264,6 +265,7 @@ function constructFullUrl(data) {
   let report = [];
   data = btoa(JSON.stringify(data));
   report.push(data);
+  const gppData = getGppValue();
 
   let url = defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner +
     '&mct=1' +
@@ -273,7 +275,9 @@ function constructFullUrl(data) {
     '&jsver=' + VERSION +
     '&source=pbjs' +
     '&payload=' + JSON.stringify(report) +
-    '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints;
+    '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints +
+    (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : '');
+
   url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName);
   return url;
 }
diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js
index 2a57f602973..c854b6c4746 100644
--- a/modules/intentIqIdSystem.js
+++ b/modules/intentIqIdSystem.js
@@ -10,11 +10,12 @@ import {ajax} from '../src/ajax.js';
 import {submodule} from '../src/hook.js'
 import {getStorageManager} from '../src/storageManager.js';
 import {MODULE_TYPE_UID} from '../src/activities/modules.js';
-import {gppDataHandler, uspDataHandler} from '../src/consentHandler.js';
+import {uspDataHandler} from '../src/consentHandler.js';
 import AES from 'crypto-js/aes.js';
 import Utf8 from 'crypto-js/enc-utf8.js';
 import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js';
 import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js';
+import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js';
 import {
   FIRST_PARTY_KEY,
   WITH_IIQ, WITHOUT_IIQ,
@@ -158,22 +159,6 @@ function tryParse(data) {
   }
 }
 
-/**
- * Convert GPP data to an object
- * @param {Object} data
- * @return {string} The JSON string representation of the input data.
- */
-export function handleGPPData(data = {}) {
-  if (Array.isArray(data)) {
-    let obj = {};
-    for (const element of data) {
-      obj = Object.assign(obj, element);
-    }
-    return JSON.stringify(obj);
-  }
-  return JSON.stringify(data);
-}
-
 /**
  * Processes raw client hints data into a structured format.
  * @param {object} clientHints - Raw client hints data
@@ -231,17 +216,19 @@ export const intentIqIdSubmodule = {
   getId(config) {
     const configParams = (config?.params) || {};
     let decryptedData, callbackTimeoutID;
-    let callbackFired = false
-    let runtimeEids = {}
+    let callbackFired = false;
+    let runtimeEids = { eids: [] };
 
     const allowedStorage = defineStorageType(config.enabledStorageTypes);
 
     let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage));
+    const isGroupB = firstPartyData?.group === WITHOUT_IIQ;
 
     const firePartnerCallback = () => {
       if (configParams.callback && !callbackFired) {
         callbackFired = true;
         if (callbackTimeoutID) clearTimeout(callbackTimeoutID);
+        if (isGroupB) runtimeEids = { eids: [] };
         configParams.callback(runtimeEids, firstPartyData?.group || NOT_YET_DEFINED);
       }
     }
@@ -276,22 +263,14 @@ export const intentIqIdSubmodule = {
     // Get consent information
     const cmpData = {};
     const uspData = uspDataHandler.getConsentData();
-    const gppData = gppDataHandler.getConsentData();
+    const gppData = getGppValue();
 
     if (uspData) {
       cmpData.us_privacy = uspData;
     }
 
-    if (gppData) {
-      cmpData.gpp = '';
-      cmpData.gpi = 1;
-
-      if (gppData.parsedSections && 'usnat' in gppData.parsedSections) {
-        cmpData.gpp = handleGPPData(gppData.parsedSections['usnat']);
-        cmpData.gpi = 0
-      }
-      cmpData.gpp_sid = gppData.applicableSections;
-    }
+    cmpData.gpp = gppData.gppString;
+    cmpData.gpi = gppData.gpi;
 
     // Read client hints from storage
     let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage);
@@ -345,9 +324,9 @@ export const intentIqIdSubmodule = {
       }
     }
 
-    if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_value !== cmpData.gpp) {
+    if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_string_value !== cmpData.gpp) {
       firstPartyData.uspapi_value = cmpData.us_privacy;
-      firstPartyData.gpp_value = cmpData.gpp;
+      firstPartyData.gpp_string_value = cmpData.gpp;
       firstPartyData.isOptedOut = false
       firstPartyData.cttl = 0
       shouldCallServer = true;
@@ -364,8 +343,9 @@ export const intentIqIdSubmodule = {
     }
 
     if (!shouldCallServer) {
+      if (isGroupB) runtimeEids = { eids: [] };
       firePartnerCallback();
-      return {id: runtimeEids?.eids || []};
+      return { id: runtimeEids.eids };
     }
 
     // use protocol relative urls for http or https
@@ -378,7 +358,7 @@ export const intentIqIdSubmodule = {
     url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : '';
     url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : '';
     url += cmpData.us_privacy ? '&pa=' + encodeURIComponent(cmpData.us_privacy) : '';
-    url += cmpData.gpp ? '&gpv=' + encodeURIComponent(cmpData.gpp) : '';
+    url += cmpData.gpp ? '&gpp=' + encodeURIComponent(cmpData.gpp) : '';
     url += cmpData.gpi ? '&gpi=' + cmpData.gpi : '';
     url += clientHints ? '&uh=' + encodeURIComponent(clientHints) : '';
     url += VERSION ? '&jsver=' + VERSION : '';
@@ -402,8 +382,7 @@ export const intentIqIdSubmodule = {
             partnerData.date = Date.now();
             firstPartyData.date = Date.now();
             const defineEmptyDataAndFireCallback = () => {
-              respJson.data = partnerData.data = runtimeEids = {};
-              partnerData.data = ''
+              respJson.data = partnerData.data = runtimeEids = { eids: [] };
               storeFirstPartyData()
               firePartnerCallback()
               callback(runtimeEids)
diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js
index b2ce17470ff..b95c3603e3a 100644
--- a/test/spec/modules/intentIqIdSystem_spec.js
+++ b/test/spec/modules/intentIqIdSystem_spec.js
@@ -2,7 +2,8 @@ import { expect } from 'chai';
 import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js';
 import * as utils from 'src/utils.js';
 import { server } from 'test/mocks/xhr.js';
-import { decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem';
+import { decryptData, handleClientHints, readData } from '../../../modules/intentIqIdSystem';
+import {getGppValue} from '../../../libraries/intentIqUtils/getGppValue.js';
 import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler';
 import { clearAllCookies } from '../../helpers/cookies';
 import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils';
@@ -252,7 +253,7 @@ describe('IntentIQ tests', function () {
       JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false })
     );
     expect(callBackSpy.calledOnce).to.be.true;
-    expect(callBackSpy.args[0][0]).to.deep.equal({});
+    expect(callBackSpy.args[0][0]).to.deep.equal({eids: []});
   });
 
   it('send addition parameters if were found in localstorage', function () {
@@ -306,36 +307,39 @@ describe('IntentIQ tests', function () {
     expect(logErrorStub.called).to.be.true;
   });
 
-  describe('handleGPPData', function () {
-    it('should convert array of objects to a single JSON string', function () {
-      const input = [
-        { key1: 'value1' },
-        { key2: 'value2' }
-      ];
-      const expectedOutput = JSON.stringify({ key1: 'value1', key2: 'value2' });
-      const result = handleGPPData(input);
-      expect(result).to.equal(expectedOutput);
-    });
-
-    it('should convert a single object to a JSON string', function () {
-      const input = { key1: 'value1', key2: 'value2' };
-      const expectedOutput = JSON.stringify(input);
-      const result = handleGPPData(input);
-      expect(result).to.equal(expectedOutput);
-    });
-
-    it('should handle empty object', function () {
-      const input = {};
-      const expectedOutput = JSON.stringify(input);
-      const result = handleGPPData(input);
-      expect(result).to.equal(expectedOutput);
-    });
-
-    it('should handle empty array', function () {
-      const input = [];
-      const expectedOutput = JSON.stringify({});
-      const result = handleGPPData(input);
-      expect(result).to.equal(expectedOutput);
+  describe('getGppValue', function () {
+    const testCases = [
+      {
+        description: 'should return gppString and gpi=0 when GPP data exists',
+        input: { gppString: '{"key1":"value1","key2":"value2"}' },
+        expectedOutput: { gppString: '{"key1":"value1","key2":"value2"}', gpi: 0 }
+      },
+      {
+        description: 'should return empty gppString and gpi=1 when GPP data does not exist',
+        input: null,
+        expectedOutput: { gppString: '', gpi: 1 }
+      },
+      {
+        description: 'should return empty gppString and gpi=1 when gppString is not set',
+        input: {},
+        expectedOutput: { gppString: '', gpi: 1 }
+      },
+      {
+        description: 'should handle GPP data with empty string',
+        input: { gppString: '' },
+        expectedOutput: { gppString: '', gpi: 1 }
+      }
+    ];
+
+    testCases.forEach(({ description, input, expectedOutput }) => {
+      it(description, function () {
+        sinon.stub(gppDataHandler, 'getConsentData').returns(input);
+
+        const result = getGppValue();
+        expect(result).to.deep.equal(expectedOutput);
+
+        gppDataHandler.getConsentData.restore();
+      });
     });
   });
 
@@ -431,54 +435,36 @@ describe('IntentIQ tests', function () {
       expect(firstPartyData.uspapi_value).to.equal(uspData);
     });
 
-    it('should set cmpData.gpp and cmpData.gpp_sid if gppData exists and has parsedSections with usnat', function () {
-      const gppData = {
-        parsedSections: {
-          usnat: { key1: 'value1', key2: 'value2' }
-        },
-        applicableSections: ['usnat']
+    it('should create a request with gpp data if gppData exists and has gppString', function () {
+      const mockGppValue = {
+        gppString: '{"key1":"value1","key2":"value2"}',
+        gpi: 0
       };
-      gppDataHandlerStub.returns(gppData);
 
-      let callBackSpy = sinon.spy();
-      let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
-      submoduleCallback(callBackSpy);
-      let request = server.requests[0];
-      expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid=');
-      request.respond(
-        200,
-        responseHeader,
-        JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: false })
-      );
-      expect(callBackSpy.calledOnce).to.be.true;
-
-      const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY));
-      expect(firstPartyData.gpp_value).to.equal(JSON.stringify({ key1: 'value1', key2: 'value2' }));
-    });
-
-    it('should handle gppData without usnat in parsedSections', function () {
-      const gppData = {
-        parsedSections: {
-          euconsent: { key1: 'value1' }
-        },
-        applicableSections: ['euconsent']
+      const mockConfig = {
+        params: { partner: partner },
+        enabledStorageTypes: ['localStorage']
       };
-      gppDataHandlerStub.returns(gppData);
+
+      gppDataHandlerStub.returns(mockGppValue);
 
       let callBackSpy = sinon.spy();
-      let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback;
+      let submoduleCallback = intentIqIdSubmodule.getId(mockConfig).callback;
       submoduleCallback(callBackSpy);
+
       let request = server.requests[0];
-      expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid=');
+
       request.respond(
         200,
         responseHeader,
-        JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: true })
+        JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true })
       );
+
+      expect(request.url).to.contain(`&gpp=${encodeURIComponent(mockGppValue.gppString)}`);
       expect(callBackSpy.calledOnce).to.be.true;
 
       const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY));
-      expect(firstPartyData.gpp_value).to.equal('');
+      expect(firstPartyData.gpp_string_value).to.equal(mockGppValue.gppString);
     });
   });
 

From 789d5008d3527ffe4922c9c5085ff66a57a031db Mon Sep 17 00:00:00 2001
From: Mike Marcus 
Date: Wed, 27 Nov 2024 08:16:07 -0500
Subject: [PATCH 0709/1097] Updates the Lotame User ID module to honor a
 publisher-supplied storage configuration object (#12403)

---
 modules/lotamePanoramaIdSystem.js             | 115 ++++++++-------
 modules/lotamePanoramaIdSystem.md             |  31 +++-
 .../modules/lotamePanoramaIdSystem_spec.js    | 133 ------------------
 3 files changed, 89 insertions(+), 190 deletions(-)

diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js
index 64d631c2469..3be7b261723 100644
--- a/modules/lotamePanoramaIdSystem.js
+++ b/modules/lotamePanoramaIdSystem.js
@@ -11,13 +11,11 @@ import {
   isBoolean,
   buildUrl,
   isEmpty,
-  isArray,
-  isEmptyStr
+  isArray
 } from '../src/utils.js';
 import { ajax } from '../src/ajax.js';
 import { submodule } from '../src/hook.js';
 import {getStorageManager} from '../src/storageManager.js';
-import { uspDataHandler } from '../src/adapterManager.js';
 import {MODULE_TYPE_UID} from '../src/activities/modules.js';
 
 /**
@@ -38,16 +36,24 @@ const MISSING_CORE_CONSENT = 111;
 const GVLID = 95;
 const ID_HOST = 'id.crwdcntrl.net';
 const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net';
+const DO_NOT_HONOR_CONFIG = false;
 
 export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
 let cookieDomain;
+let appliedConfig = {
+  name: 'lotamePanoramaId',
+  storage: {
+    type: 'cookie&html5',
+    name: 'panoramaId'
+  }
+};
 
 /**
  * Set the Lotame First Party Profile ID in the first party namespace
  * @param {String} profileId
  */
 function setProfileId(profileId) {
-  if (storage.cookiesAreEnabled()) {
+  if (cookiesAreEnabled()) {
     let expirationDate = new Date(timestamp() + NINE_MONTHS_MS).toUTCString();
     storage.setCookie(
       KEY_PROFILE,
@@ -58,7 +64,7 @@ function setProfileId(profileId) {
       undefined
     );
   }
-  if (storage.hasLocalStorage()) {
+  if (localStorageIsEnabled()) {
     storage.setDataInLocalStorage(KEY_PROFILE, profileId, undefined);
   }
 }
@@ -68,10 +74,10 @@ function setProfileId(profileId) {
  */
 function getProfileId() {
   let profileId;
-  if (storage.cookiesAreEnabled()) {
+  if (cookiesAreEnabled(DO_NOT_HONOR_CONFIG)) {
     profileId = storage.getCookie(KEY_PROFILE, undefined);
   }
-  if (!profileId && storage.hasLocalStorage()) {
+  if (!profileId && localStorageIsEnabled(DO_NOT_HONOR_CONFIG)) {
     profileId = storage.getDataFromLocalStorage(KEY_PROFILE, undefined);
   }
   return profileId;
@@ -83,21 +89,11 @@ function getProfileId() {
  */
 function getFromStorage(key) {
   let value = null;
-  if (storage.cookiesAreEnabled()) {
+  if (cookiesAreEnabled(DO_NOT_HONOR_CONFIG)) {
     value = storage.getCookie(key, undefined);
   }
-  if (storage.hasLocalStorage() && value === null) {
-    const storedValueExp = storage.getDataFromLocalStorage(
-      `${key}_exp`, undefined
-    );
-
-    if (storedValueExp === '' || storedValueExp === null) {
-      value = storage.getDataFromLocalStorage(key, undefined);
-    } else if (storedValueExp) {
-      if ((new Date(parseInt(storedValueExp, 10))).getTime() - Date.now() > 0) {
-        value = storage.getDataFromLocalStorage(key, undefined);
-      }
-    }
+  if (value === null && localStorageIsEnabled(DO_NOT_HONOR_CONFIG)) {
+    value = storage.getDataFromLocalStorage(key, undefined);
   }
   return value;
 }
@@ -115,7 +111,7 @@ function saveLotameCache(
 ) {
   if (key && value) {
     let expirationDate = new Date(expirationTimestamp).toUTCString();
-    if (storage.cookiesAreEnabled()) {
+    if (cookiesAreEnabled()) {
       storage.setCookie(
         key,
         value,
@@ -125,12 +121,7 @@ function saveLotameCache(
         undefined
       );
     }
-    if (storage.hasLocalStorage()) {
-      storage.setDataInLocalStorage(
-        `${key}_exp`,
-        String(expirationTimestamp),
-        undefined
-      );
+    if (localStorageIsEnabled()) {
       storage.setDataInLocalStorage(key, value, undefined);
     }
   }
@@ -172,7 +163,7 @@ function getLotameLocalCache(clientId = undefined) {
  */
 function clearLotameCache(key) {
   if (key) {
-    if (storage.cookiesAreEnabled()) {
+    if (cookiesAreEnabled(DO_NOT_HONOR_CONFIG)) {
       let expirationDate = new Date(0).toUTCString();
       storage.setCookie(
         key,
@@ -183,11 +174,50 @@ function clearLotameCache(key) {
         undefined
       );
     }
-    if (storage.hasLocalStorage()) {
+    if (localStorageIsEnabled(DO_NOT_HONOR_CONFIG)) {
       storage.removeDataFromLocalStorage(key, undefined);
     }
   }
 }
+/**
+ * @param {boolean} honorConfig - false to override for reading or deleting old cookies
+ * @returns {boolean} for whether we can write the cookie
+ */
+function cookiesAreEnabled(honorConfig = true) {
+  if (honorConfig) {
+    return storage.cookiesAreEnabled() && appliedConfig.storage.type.includes('cookie');
+  }
+  return storage.cookiesAreEnabled();
+}
+/**
+ * @param {boolean} honorConfig - false to override for reading or deleting old stored items
+ * @returns {boolean} for whether we can write the cookie
+ */
+function localStorageIsEnabled(honorConfig = true) {
+  if (honorConfig) {
+    return storage.hasLocalStorage() && appliedConfig.storage.type.includes('html5');
+  }
+  return storage.hasLocalStorage();
+}
+/**
+ * @param {SubmoduleConfig} config
+ * @returns {null|string} - string error if it finds one, null otherwise.
+ */
+function checkConfigHasErrorsAndReport(config) {
+  let error = null;
+  if (typeof config.storage !== 'undefined') {
+    Object.assign(appliedConfig.storage, appliedConfig.storage, config.storage);
+    const READABLE_MODULE_NAME = 'Lotame ID module';
+    const PERMITTED_STORAGE_TYPES = ['cookie', 'html5', 'cookie&html5'];
+    if (typeof config.storage.name !== 'undefined' && config.storage.name !== KEY_ID) {
+      logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is expected to be "${KEY_ID}", actual is "${config.storage.name}"`);
+      error = true;
+    } else if (config.storage.type !== 'undefined' && !PERMITTED_STORAGE_TYPES.includes(config.storage.type)) {
+      logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is expected to be one of "${PERMITTED_STORAGE_TYPES.join(', ')}", actual is "${config.storage.type}"`);
+    }
+  }
+  return error;
+}
 /** @type {Submodule} */
 export const lotamePanoramaIdSubmodule = {
   /**
@@ -222,6 +252,9 @@ export const lotamePanoramaIdSubmodule = {
    * @returns {IdResponse|undefined}
    */
   getId(config, consentData, cacheIdObj) {
+    if (checkConfigHasErrorsAndReport(config)) {
+      return;
+    }
     cookieDomain = lotamePanoramaIdSubmodule.findRootDomain();
     const configParams = (config && config.params) || {};
     const clientId = configParams.clientId;
@@ -249,18 +282,6 @@ export const lotamePanoramaIdSubmodule = {
 
     const storedUserId = getProfileId();
 
-    // Add CCPA Consent data handling
-    const usp = uspDataHandler.getConsentData();
-
-    let usPrivacy;
-    if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) {
-      usPrivacy = usp;
-    }
-    if (!usPrivacy) {
-      // fallback to 1st party cookie
-      usPrivacy = getFromStorage('us_privacy');
-    }
-
     const getRequestHost = function() {
       if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
         return ID_HOST_COOKIELESS;
@@ -281,22 +302,10 @@ export const lotamePanoramaIdSubmodule = {
         }
         consentString = consentData.consentString;
       }
-      // If no consent string, try to read it from 1st party cookies
-      if (!consentString) {
-        consentString = getFromStorage('eupubconsent-v2');
-      }
-      if (!consentString) {
-        consentString = getFromStorage('euconsent-v2');
-      }
       if (consentString) {
         queryParams.gdpr_consent = consentString;
       }
 
-      // Add usPrivacy to the url
-      if (usPrivacy) {
-        queryParams.us_privacy = usPrivacy;
-      }
-
       // Add clientId to the url
       if (hasCustomClientId) {
         queryParams.c = clientId;
diff --git a/modules/lotamePanoramaIdSystem.md b/modules/lotamePanoramaIdSystem.md
index e960f4b5695..1fbc3f561c7 100644
--- a/modules/lotamePanoramaIdSystem.md
+++ b/modules/lotamePanoramaIdSystem.md
@@ -17,9 +17,32 @@ Retrieve the Lotame Panorama Id
     pbjs.setConfig({                
         usersync: {
             userIds: [
-            {
-                name: 'lotamePanoramaId' // The only parameter that is needed
-            }],
+                {
+                    name: 'lotamePanoramaId',
+                    storage: {
+                        name: 'panoramaId',
+                        type: 'cookie&html5',
+                        expires: 7
+                    }
+                }
+            ],
         }
     });
-```            
\ No newline at end of file
+```
+
+| Parameters under `userSync.userIds[]` | Scope | Type | Description | Example |
+| ---| --- | --- | --- | --- |
+| name | Required | String | Name for the Lotame ID submodule | `"lotamePanoramaId"` |
+| storage | Optional | Object | Configures how to cache User IDs locally in the browser | See [storage settings](#storage-settings) |
+
+
+### Storage Settings
+
+The following settings are available for the `storage` property in the `userSync.userIds[]` object. Please note that inclusion of the `storage` property is optional, but if provided, all three attributes listed below *must* be specified:
+
+| Param name | Scope | Type | Description | Example   |
+| --- | --- | --- | --- | --- |
+| name | Required | String| Name of the cookie or localStorage where the user ID will be stored; *must* be `"panoramaId"` | `"panoramaId"` |
+| type | Required | String | `"cookie&html5"` (preferred)  or `"cookie"` or `"html5"` | `"cookie&html5"` |
+| expires | Required | Number | How long (in days) the user ID information will be stored. Lotame recommends `7`. | `7` |
+
diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js
index fbd4ba7c000..27efef9df50 100644
--- a/test/spec/modules/lotamePanoramaIdSystem_spec.js
+++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js
@@ -2,7 +2,6 @@ import {
   lotamePanoramaIdSubmodule,
   storage,
 } from 'modules/lotamePanoramaIdSystem.js';
-import { uspDataHandler } from 'src/adapterManager.js';
 import * as utils from 'src/utils.js';
 import { server } from 'test/mocks/xhr.js';
 import sinon from 'sinon';
@@ -19,7 +18,6 @@ describe('LotameId', function() {
   let setLocalStorageStub;
   let removeFromLocalStorageStub;
   let timeStampStub;
-  let uspConsentDataStub;
   let requestHost;
 
   const nowTimestamp = new Date().getTime();
@@ -34,7 +32,6 @@ describe('LotameId', function() {
       'removeDataFromLocalStorage'
     );
     timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp);
-    uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData');
     if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
       requestHost = 'https://c.ltmsphrcl.net/id';
     } else {
@@ -50,7 +47,6 @@ describe('LotameId', function() {
     setLocalStorageStub.restore();
     removeFromLocalStorageStub.restore();
     timeStampStub.restore();
-    uspConsentDataStub.restore();
   });
 
   describe('caching initial data received from the remote server', function () {
@@ -451,70 +447,6 @@ describe('LotameId', function() {
     });
   });
 
-  describe('when gdpr applies and falls back to eupubconsent cookie', function () {
-    let request;
-    let callBackSpy = sinon.spy();
-    let consentData = {
-      gdprApplies: true,
-      consentString: undefined
-    };
-
-    beforeEach(function () {
-      getCookieStub
-        .withArgs('eupubconsent-v2')
-        .returns('consentGiven');
-
-      let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback;
-      submoduleCallback(callBackSpy);
-
-      // the contents of the response don't matter for this
-      request = server.requests[0];
-      request.respond(200, responseHeader, '');
-    });
-
-    it('should call the remote server when getId is called', function () {
-      expect(callBackSpy.calledOnce).to.be.true;
-    });
-
-    it('should pass the gdpr consent string back', function() {
-      expect(request.url).to.be.eq(
-        `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven`
-      );
-    });
-  });
-
-  describe('when gdpr applies and falls back to euconsent cookie', function () {
-    let request;
-    let callBackSpy = sinon.spy();
-    let consentData = {
-      gdprApplies: true,
-      consentString: undefined
-    };
-
-    beforeEach(function () {
-      getCookieStub
-        .withArgs('euconsent-v2')
-        .returns('consentGiven');
-
-      let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback;
-      submoduleCallback(callBackSpy);
-
-      // the contents of the response don't matter for this
-      request = server.requests[0];
-      request.respond(200, responseHeader, '');
-    });
-
-    it('should call the remote server when getId is called', function () {
-      expect(callBackSpy.calledOnce).to.be.true;
-    });
-
-    it('should pass the gdpr consent string back', function() {
-      expect(request.url).to.be.eq(
-        `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven`
-      );
-    });
-  });
-
   describe('when gdpr applies but no consent string is available', function () {
     let request;
     let callBackSpy = sinon.spy();
@@ -543,64 +475,6 @@ describe('LotameId', function() {
     });
   });
 
-  describe('when no consentData and falls back to eupubconsent cookie', function () {
-    let request;
-    let callBackSpy = sinon.spy();
-    let consentData;
-
-    beforeEach(function () {
-      getCookieStub
-        .withArgs('eupubconsent-v2')
-        .returns('consentGiven');
-
-      let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback;
-      submoduleCallback(callBackSpy);
-
-      // the contents of the response don't matter for this
-      request = server.requests[0];
-      request.respond(200, responseHeader, '');
-    });
-
-    it('should call the remote server when getId is called', function () {
-      expect(callBackSpy.calledOnce).to.be.true;
-    });
-
-    it('should pass the gdpr consent string back', function() {
-      expect(request.url).to.be.eq(
-        `${requestHost}?gdpr_consent=consentGiven`
-      );
-    });
-  });
-
-  describe('when no consentData and falls back to euconsent cookie', function () {
-    let request;
-    let callBackSpy = sinon.spy();
-    let consentData;
-
-    beforeEach(function () {
-      getCookieStub
-        .withArgs('euconsent-v2')
-        .returns('consentGiven');
-
-      let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback;
-      submoduleCallback(callBackSpy);
-
-      // the contents of the response don't matter for this
-      request = server.requests[0];
-      request.respond(200, responseHeader, '');
-    });
-
-    it('should call the remote server when getId is called', function () {
-      expect(callBackSpy.calledOnce).to.be.true;
-    });
-
-    it('should pass the gdpr consent string back', function() {
-      expect(request.url).to.be.eq(
-        `${requestHost}?gdpr_consent=consentGiven`
-      );
-    });
-  });
-
   describe('when no consentData and no cookies', function () {
     let request;
     let callBackSpy = sinon.spy();
@@ -809,7 +683,6 @@ describe('LotameId', function() {
         let callBackSpy = sinon.spy();
 
         beforeEach(function () {
-          uspConsentDataStub.returns('1NNN');
           let submoduleCallback = lotamePanoramaIdSubmodule.getId(
             {
               params: {
@@ -840,12 +713,6 @@ describe('LotameId', function() {
           expect(callBackSpy.calledOnce).to.be.true;
         });
 
-        it('should pass the usp consent string and client id back', function () {
-          expect(request.url).to.be.eq(
-            `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234`
-          );
-        });
-
         it('should NOT set an expiry for the client', function () {
           sinon.assert.neverCalledWith(
             setCookieStub,

From c8dc64c90367609d1b56793affdd8644a070b289 Mon Sep 17 00:00:00 2001
From: "Prebid.js automated release" 
Date: Wed, 27 Nov 2024 16:07:53 +0000
Subject: [PATCH 0710/1097] Prebid 9.21.0 release

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index d781c075fa5..9f61ecfe819 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "prebid.js",
-  "version": "9.21.0-pre",
+  "version": "9.21.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "prebid.js",
-      "version": "9.21.0-pre",
+      "version": "9.21.0",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index 3e1a7b6ece0..05c3d40c46c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "prebid.js",
-  "version": "9.21.0-pre",
+  "version": "9.21.0",
   "description": "Header Bidding Management Library",
   "main": "src/prebid.public.js",
   "exports": {

From 15f7e93e25d356e78cdea11d5b94188516913874 Mon Sep 17 00:00:00 2001
From: "Prebid.js automated release" 
Date: Wed, 27 Nov 2024 16:07:53 +0000
Subject: [PATCH 0711/1097] Increment version to 9.22.0-pre

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 9f61ecfe819..133c5a7a632 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "prebid.js",
-  "version": "9.21.0",
+  "version": "9.22.0-pre",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "prebid.js",
-      "version": "9.21.0",
+      "version": "9.22.0-pre",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index 05c3d40c46c..fa5b569bc6b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "prebid.js",
-  "version": "9.21.0",
+  "version": "9.22.0-pre",
   "description": "Header Bidding Management Library",
   "main": "src/prebid.public.js",
   "exports": {

From 957d1fad5a1fba54ea3d90e709183b8e2ee0706a Mon Sep 17 00:00:00 2001
From: Denis Logachov 
Date: Fri, 29 Nov 2024 15:44:29 +0200
Subject: [PATCH 0712/1097] Adkernel: add Pixelpluses alias (#12520)

---
 modules/adkernelBidAdapter.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index ac211cc9de9..414d1ca28e9 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -101,7 +101,8 @@ export const spec = {
     {code: 'rxnetwork'},
     {code: 'revbid'},
     {code: 'spinx', gvlid: 1308},
-    {code: 'oppamedia'}
+    {code: 'oppamedia'},
+    {code: 'pixelpluses', gvlid: 1209}
   ],
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
 

From f7e44cc0a63788711301ec5abcf673f7925453a2 Mon Sep 17 00:00:00 2001
From: "Adserver.Online" <61009237+adserver-online@users.noreply.github.com>
Date: Sun, 1 Dec 2024 00:13:01 +0200
Subject: [PATCH 0713/1097] Add kuantyx alias (#12523)

Co-authored-by: dev 
---
 modules/asoBidAdapter.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js
index 43215c05d5b..08612757de1 100644
--- a/modules/asoBidAdapter.js
+++ b/modules/asoBidAdapter.js
@@ -17,7 +17,8 @@ export const spec = {
   supportedMediaTypes: [BANNER, VIDEO, NATIVE],
   aliases: [
     {code: 'bcmint'},
-    {code: 'bidgency'}
+    {code: 'bidgency'},
+    {code: 'kuantyx'}
   ],
 
   isBidRequestValid: bid => {

From f7e8034b45c661a0d2f242584341894932a2b8ad Mon Sep 17 00:00:00 2001
From: Ariel <155993606+arielmtk@users.noreply.github.com>
Date: Mon, 2 Dec 2024 14:54:40 -0300
Subject: [PATCH 0714/1097] Mobian Bid Adapter : push context data to GAM
 (#12389)

* Push context data to GAM

* Update browsi to set gpt key values

* fix browsi

* Revamps module to make it configurable

* Revamps module and tests, adds config

* Adds more config and documentation

* Updates mock emotion

---------

Co-authored-by: Demetrio Girardi 
---
 libraries/gptUtils/gptUtils.js              |  12 +
 modules/browsiRtdProvider.js                |  10 +-
 modules/mobianRtdProvider.js                | 241 ++++++++----
 modules/mobianRtdProvider.md                |  34 +-
 test/spec/modules/mobianRtdProvider_spec.js | 393 +++++++++-----------
 5 files changed, 406 insertions(+), 284 deletions(-)

diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js
index 68ce29ef168..923d207c0d9 100644
--- a/libraries/gptUtils/gptUtils.js
+++ b/libraries/gptUtils/gptUtils.js
@@ -11,6 +11,18 @@ export function isSlotMatchingAdUnitCode(adUnitCode) {
   return (slot) => compareCodeAndSlot(slot, adUnitCode);
 }
 
+/**
+ * @summary Export a k-v pair to GAM
+ */
+export function setKeyValue(key, value) {
+  if (!key || typeof key !== 'string') return false;
+  window.googletag = window.googletag || {cmd: []};
+  window.googletag.cmd = window.googletag.cmd || [];
+  window.googletag.cmd.push(() => {
+    window.googletag.pubads().setTargeting(key, value);
+  });
+}
+
 /**
  * @summary Uses the adUnit's code in order to find a matching gpt slot object on the page
  */
diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js
index 8f5fea80ffa..6ac19b39c79 100644
--- a/modules/browsiRtdProvider.js
+++ b/modules/browsiRtdProvider.js
@@ -26,6 +26,7 @@ import {getGlobal} from '../src/prebidGlobal.js';
 import * as events from '../src/events.js';
 import {EVENTS} from '../src/constants.js';
 import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
+import {setKeyValue as setGptKeyValue} from '../libraries/gptUtils/gptUtils.js';
 
 /**
  * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
@@ -67,14 +68,7 @@ export function addBrowsiTag(data) {
   return script;
 }
 
-export function setKeyValue(key) {
-  if (!key || typeof key !== 'string') return false;
-  window.googletag = window.googletag || {cmd: []};
-  window.googletag.cmd = window.googletag.cmd || [];
-  window.googletag.cmd.push(() => {
-    window.googletag.pubads().setTargeting(key, RANDOM.toString());
-  });
-}
+export const setKeyValue = (key) => setGptKeyValue(key, RANDOM.toString());
 
 export function sendPageviewEvent(eventType) {
   if (eventType === 'PAGEVIEW') {
diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js
index 3b9d2632246..02f2d1b83cf 100644
--- a/modules/mobianRtdProvider.js
+++ b/modules/mobianRtdProvider.js
@@ -4,85 +4,200 @@
  */
 import { submodule } from '../src/hook.js';
 import { ajaxBuilder } from '../src/ajax.js';
-import { deepSetValue, safeJSONParse } from '../src/utils.js';
+import { safeJSONParse, logMessage as _logMessage } from '../src/utils.js';
+import { setKeyValue } from '../libraries/gptUtils/gptUtils.js';
 
 /**
  * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
  */
 
+/**
+ * @typedef {Object} MobianConfig
+ * @property {MobianConfigParams} params
+ */
+
+/**
+ * @typedef {Object} MobianConfigParams
+ * @property {string} [prefix] - Optional prefix for targeting keys (default: 'mobian')
+ * @property {boolean} [publisherTargeting] - Optional boolean to enable targeting for publishers (default: false)
+ * @property {boolean} [advertiserTargeting] - Optional boolean to enable targeting for advertisers (default: false)
+ */
+
+/**
+ * @typedef {Object} MobianContextData
+ * @property {Object} apValues
+ * @property {string[]} categories
+ * @property {string[]} emotions
+ * @property {string[]} genres
+ * @property {string} risk
+ * @property {string} sentiment
+ * @property {string[]} themes
+ * @property {string[]} tones
+ */
+
 export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async';
 
-/** @type {RtdSubmodule} */
-export const mobianBrandSafetySubmodule = {
-  name: 'mobianBrandSafety',
-  init: init,
-  getBidRequestData: getBidRequestData
+export const CONTEXT_KEYS = [
+  'apValues',
+  'categories',
+  'emotions',
+  'genres',
+  'risk',
+  'sentiment',
+  'themes',
+  'tones'
+];
+
+const AP_KEYS = ['a0', 'a1', 'p0', 'p1'];
+
+const logMessage = (...args) => {
+  _logMessage('Mobian', ...args);
 };
 
-function init() {
-  return true;
+function makeMemoizedFetch() {
+  let cachedResponse = null;
+  return async function () {
+    if (cachedResponse) {
+      return Promise.resolve(cachedResponse);
+    }
+    try {
+      const response = await fetchContextData();
+      cachedResponse = makeDataFromResponse(response);
+      return cachedResponse;
+    } catch (error) {
+      logMessage('error', error);
+      return Promise.resolve({});
+    }
+  }
 }
 
-function getBidRequestData(bidReqConfig, callback, config) {
-  const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global;
-  const pageUrl = encodeURIComponent(getPageUrl());
+export const getContextData = makeMemoizedFetch();
+
+export async function fetchContextData() {
+  const pageUrl = encodeURIComponent(window.location.href);
   const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`;
+  const request = ajaxBuilder();
+
+  return new Promise((resolve, reject) => {
+    request(requestUrl, { success: resolve, error: reject });
+  });
+}
+
+export function getConfig(config) {
+  const [advertiserTargeting, publisherTargeting] = ['advertiserTargeting', 'publisherTargeting'].map((key) => {
+    const value = config?.params?.[key];
+    if (!value) {
+      return [];
+    } else if (value === true) {
+      return CONTEXT_KEYS;
+    } else if (Array.isArray(value) && value.length) {
+      return value.filter((key) => CONTEXT_KEYS.includes(key));
+    }
+    return [];
+  });
+
+  const prefix = config?.params?.prefix || 'mobian';
+  return { advertiserTargeting, prefix, publisherTargeting };
+}
+
+/**
+ * @param {MobianConfigParams} parsedConfig
+ * @param {MobianContextData} contextData
+ * @returns {function}
+ */
+export function setTargeting(parsedConfig, contextData) {
+  const { publisherTargeting, prefix } = parsedConfig;
+  logMessage('context', contextData);
+
+  CONTEXT_KEYS.forEach((key) => {
+    if (!publisherTargeting.includes(key)) return;
 
-  const ajax = ajaxBuilder();
-
-  return new Promise((resolve) => {
-    ajax(requestUrl, {
-      success: function(responseData) {
-        let response = safeJSONParse(responseData);
-        if (!response || !response.meta.has_results) {
-          resolve({});
-          callback();
-          return;
-        }
-
-        const results = response.results;
-        const mobianRisk = results.mobianRisk || 'unknown';
-        const contentCategories = results.mobianContentCategories || [];
-        const sentiment = results.mobianSentiment || 'unknown';
-        const emotions = results.mobianEmotions || [];
-        const themes = results.mobianThemes || [];
-        const tones = results.mobianTones || [];
-        const genres = results.mobianGenres || [];
-        const apValues = results.ap || {};
-
-        const risk = {
-          risk: mobianRisk,
-          contentCategories: contentCategories,
-          sentiment: sentiment,
-          emotions: emotions,
-          themes: themes,
-          tones: tones,
-          genres: genres,
-          apValues: apValues,
-        };
-
-        deepSetValue(ortb2Site, 'ext.data.mobianRisk', mobianRisk);
-        deepSetValue(ortb2Site, 'ext.data.mobianContentCategories', contentCategories);
-        deepSetValue(ortb2Site, 'ext.data.mobianSentiment', sentiment);
-        deepSetValue(ortb2Site, 'ext.data.mobianEmotions', emotions);
-        deepSetValue(ortb2Site, 'ext.data.mobianThemes', themes);
-        deepSetValue(ortb2Site, 'ext.data.mobianTones', tones);
-        deepSetValue(ortb2Site, 'ext.data.mobianGenres', genres);
-        deepSetValue(ortb2Site, 'ext.data.apValues', apValues);
-
-        resolve(risk);
-        callback();
-      },
-      error: function () {
-        resolve({});
-        callback();
-      }
-    });
+    if (key === 'apValues') {
+      AP_KEYS.forEach((apKey) => {
+        if (!contextData[key]?.[apKey]?.length) return;
+        logMessage(`${prefix}_ap_${apKey}`, contextData[key][apKey]);
+        setKeyValue(`${prefix}_ap_${apKey}`, contextData[key][apKey]);
+      });
+      return;
+    }
+
+    if (contextData[key]?.length) {
+      logMessage(`${prefix}_${key}`, contextData[key]);
+      setKeyValue(`${prefix}_${key}`, contextData[key]);
+    }
   });
 }
 
-function getPageUrl() {
-  return window.location.href;
+export function makeDataFromResponse(contextData) {
+  const data = typeof contextData === 'string' ? safeJSONParse(contextData) : contextData;
+  const results = data.results;
+  if (!results) {
+    return {};
+  }
+  return {
+    apValues: results.ap || {},
+    categories: results.mobianContentCategories,
+    emotions: results.mobianEmotions,
+    genres: results.mobianGenres,
+    risk: results.mobianRisk || 'unknown',
+    sentiment: results.mobianSentiment || 'unknown',
+    themes: results.mobianThemes,
+    tones: results.mobianTones,
+  };
+}
+
+export function extendBidRequestConfig(bidReqConfig, contextData) {
+  logMessage('extendBidRequestConfig', bidReqConfig, contextData);
+  const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global;
+
+  ortb2Site.ext = ortb2Site.ext || {};
+  ortb2Site.ext.data = {
+    ...(ortb2Site.ext.data || {}),
+    ...contextData
+  };
+
+  return bidReqConfig;
 }
 
+/**
+ * @param {MobianConfig} config
+ * @returns {boolean}
+ */
+function init(config) {
+  logMessage('init', config);
+
+  const parsedConfig = getConfig(config);
+
+  if (parsedConfig.publisherTargeting.length) {
+    getContextData().then((contextData) => setTargeting(parsedConfig, contextData));
+  }
+
+  return true;
+}
+
+function getBidRequestData(bidReqConfig, callback, config) {
+  logMessage('getBidRequestData', bidReqConfig);
+
+  const { advertiserTargeting } = getConfig(config);
+
+  if (!advertiserTargeting.length) {
+    callback();
+    return;
+  }
+
+  getContextData()
+    .then((contextData) => {
+      extendBidRequestConfig(bidReqConfig, contextData);
+    })
+    .catch(() => {})
+    .finally(() => callback());
+}
+
+/** @type {RtdSubmodule} */
+export const mobianBrandSafetySubmodule = {
+  name: 'mobianBrandSafety',
+  init: init,
+  getBidRequestData: getBidRequestData
+};
+
 submodule('realTimeData', mobianBrandSafetySubmodule);
diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md
index e7475eb40fb..e974e56dcdd 100644
--- a/modules/mobianRtdProvider.md
+++ b/modules/mobianRtdProvider.md
@@ -1,11 +1,41 @@
-# Overview
+# Mobian Rtd Provider
+
+## Overview
 
 Module Name: Mobian Rtd Provider
 Module Type: Rtd Provider
 Maintainer: rich.rodriguez@themobian.com
 
-# Description
+## Description
 
 RTD provider for themobian Brand Safety determinations. Publishers
 should use this to get Mobian's GARM Risk evaluations for
 a URL. 
+
+## Configuration
+
+```js
+pbjs.setConfig({
+  realTimeData: {
+    dataProviders: [{
+      name: 'mobianBrandSafety',
+      params: {
+        // Prefix for the targeting keys (default: 'mobian')
+        prefix: 'mobian',
+        
+        // Enable targeting keys for advertiser data
+        advertiserTargeting: true,
+        // Or set it as an array to pick specific targeting keys:
+        // advertiserTargeting: ['genres', 'emotions', 'themes'],
+        // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones'
+
+        // Enable targeting keys for publisher data
+        publisherTargeting: true,
+        // Or set it as an array to pick specific targeting keys:
+        // publisherTargeting: ['tones', 'risk'],
+        // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones'
+      }
+    }]
+  }
+});
+```
diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js
index 088e5559a17..796a79e4e1c 100644
--- a/test/spec/modules/mobianRtdProvider_spec.js
+++ b/test/spec/modules/mobianRtdProvider_spec.js
@@ -1,11 +1,49 @@
 import { expect } from 'chai';
 import sinon from 'sinon';
-import { mobianBrandSafetySubmodule, MOBIAN_URL } from 'modules/mobianRtdProvider.js';
 import * as ajax from 'src/ajax.js';
+import * as gptUtils from 'libraries/gptUtils/gptUtils.js';
+import {
+  CONTEXT_KEYS,
+  extendBidRequestConfig,
+  fetchContextData,
+  getConfig,
+  getContextData,
+  makeDataFromResponse,
+  setTargeting,
+} from 'modules/mobianRtdProvider.js';
 
 describe('Mobian RTD Submodule', function () {
   let ajaxStub;
   let bidReqConfig;
+  let setKeyValueSpy;
+
+  const mockResponse = JSON.stringify({
+    meta: {
+      url: 'https://example.com',
+      has_results: true
+    },
+    results: {
+      ap: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] },
+      mobianContentCategories: [],
+      mobianEmotions: ['affection'],
+      mobianGenres: [],
+      mobianRisk: 'low',
+      mobianSentiment: 'positive',
+      mobianThemes: [],
+      mobianTones: [],
+    }
+  });
+
+  const mockContextData = {
+    apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] },
+    categories: [],
+    emotions: ['affection'],
+    genres: [],
+    risk: 'low',
+    sentiment: 'positive',
+    themes: [],
+    tones: [],
+  }
 
   beforeEach(function () {
     bidReqConfig = {
@@ -19,255 +57,188 @@ describe('Mobian RTD Submodule', function () {
         }
       }
     };
+
+    setKeyValueSpy = sinon.spy(gptUtils, 'setKeyValue');
   });
 
   afterEach(function () {
     ajaxStub.restore();
+    setKeyValueSpy.restore();
   });
 
-  it('should set key-value pairs when server responds with valid data', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success(JSON.stringify({
-        meta: {
-          url: 'https://example.com',
-          has_results: true
-        },
-        results: {
-          mobianRisk: 'low',
-          mobianSentiment: 'positive',
-          mobianContentCategories: [],
-          mobianEmotions: ['joy'],
-          mobianThemes: [],
-          mobianTones: [],
-          mobianGenres: [],
-          ap: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }
-        }
-      }));
-    });
-
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({
-        risk: 'low',
-        contentCategories: [],
-        sentiment: 'positive',
-        emotions: ['joy'],
-        themes: [],
-        tones: [],
-        genres: [],
-        apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }
-      });
-      expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({
-        mobianRisk: 'low',
-        mobianContentCategories: [],
-        mobianSentiment: 'positive',
-        mobianEmotions: ['joy'],
-        mobianThemes: [],
-        mobianTones: [],
-        mobianGenres: [],
-        apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }
+  describe('fetchContextData', function () {
+    it('should return fetched context data', async function () {
+      ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
+        callbacks.success(mockResponse);
       });
-    });
-  });
 
-  it('should handle response with content categories, multiple emotions, and ap values', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success(JSON.stringify({
-        meta: {
-          url: 'https://example.com',
-          has_results: true
-        },
-        results: {
-          mobianRisk: 'medium',
-          mobianSentiment: 'negative',
-          mobianContentCategories: ['arms', 'crime'],
-          mobianEmotions: ['anger', 'fear'],
-          mobianThemes: ['conflict', 'international relations'],
-          mobianTones: ['factual', 'serious'],
-          mobianGenres: ['news', 'political_analysis'],
-          ap: { a0: [100], a1: [200, 300], p0: [400, 500], p1: [600] }
-        }
-      }));
+      const contextData = await fetchContextData();
+      expect(contextData).to.deep.equal(mockResponse);
     });
+  });
 
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({
-        risk: 'medium',
-        contentCategories: ['arms', 'crime'],
-        sentiment: 'negative',
-        emotions: ['anger', 'fear'],
-        themes: ['conflict', 'international relations'],
-        tones: ['factual', 'serious'],
-        genres: ['news', 'political_analysis'],
-        apValues: { a0: [100], a1: [200, 300], p0: [400, 500], p1: [600] }
-      });
-      expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({
-        mobianRisk: 'medium',
-        mobianContentCategories: ['arms', 'crime'],
-        mobianSentiment: 'negative',
-        mobianEmotions: ['anger', 'fear'],
-        mobianThemes: ['conflict', 'international relations'],
-        mobianTones: ['factual', 'serious'],
-        mobianGenres: ['news', 'political_analysis'],
-        apValues: { a0: [100], a1: [200, 300], p0: [400, 500], p1: [600] }
+  describe('makeDataFromResponse', function () {
+    it('should format context data response', async function () {
+      ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
+        callbacks.success(mockResponse);
       });
+
+      const data = makeDataFromResponse(mockResponse);
+      expect(data).to.deep.equal(mockContextData);
     });
   });
 
-  it('should return empty object when server responds with has_results: false', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success(JSON.stringify({
-        meta: {
-          url: 'https://example.com',
-          has_results: false
-        },
-        results: {}
-      }));
-    });
+  describe('getContextData', function () {
+    it('should return formatted context data', async function () {
+      ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
+        callbacks.success(mockResponse);
+      });
 
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({});
-      expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.not.have.any.keys(
-        'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions', 'mobianThemes', 'mobianTones', 'mobianGenres', 'apValues'
-      );
+      const data = await getContextData();
+      expect(data).to.deep.equal(mockContextData);
     });
   });
 
-  it('should return empty object when server response is not valid JSON', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success('unexpected output not even of the right type');
-    });
-    const originalConfig = JSON.parse(JSON.stringify(bidReqConfig));
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({});
-      // Check that bidReqConfig hasn't been modified
-      expect(bidReqConfig).to.deep.equal(originalConfig);
+  describe('setTargeting', function () {
+    it('should set targeting key-value pairs as per config', function () {
+      const parsedConfig = {
+        prefix: 'mobian',
+        publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'],
+      };
+      setTargeting(parsedConfig, mockContextData);
+
+      expect(setKeyValueSpy.callCount).to.equal(6);
+      expect(setKeyValueSpy.calledWith('mobian_ap_a1', [2313, 12])).to.equal(true);
+      expect(setKeyValueSpy.calledWith('mobian_ap_p0', [1231231, 212])).to.equal(true);
+      expect(setKeyValueSpy.calledWith('mobian_ap_p1', [231, 419])).to.equal(true);
+      expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true);
+      expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true);
+      expect(setKeyValueSpy.calledWith('mobian_sentiment', 'positive')).to.equal(true);
+
+      expect(setKeyValueSpy.calledWith('mobian_ap_a0')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_themes')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_tones')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_genres')).to.equal(false);
+    });
+
+    it('should not set key-value pairs if context data is empty', function () {
+      const parsedConfig = {
+        prefix: 'mobian',
+        publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'],
+      };
+      setTargeting(parsedConfig, {});
+
+      expect(setKeyValueSpy.callCount).to.equal(0);
+    });
+
+    it('should only set key-value pairs for the keys specified in config', function () {
+      const parsedConfig = {
+        prefix: 'mobian',
+        publisherTargeting: ['emotions', 'risk'],
+      };
+
+      setTargeting(parsedConfig, mockContextData);
+
+      expect(setKeyValueSpy.callCount).to.equal(2);
+      expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true);
+      expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true);
+
+      expect(setKeyValueSpy.calledWith('mobian_ap_a0')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_ap_a1')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_ap_p0')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_ap_p1')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_themes')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_tones')).to.equal(false);
+      expect(setKeyValueSpy.calledWith('mobian_genres')).to.equal(false);
     });
   });
 
-  it('should handle error response', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.error();
+  describe('extendBidRequestConfig', function () {
+    it('should extend bid request config with context data', function () {
+      const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData);
+      expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData);
     });
 
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({});
-    });
-  });
+    it('should not override existing data', function () {
+      bidReqConfig.ortb2Fragments.global.site.ext.data = {
+        existing: 'data'
+      };
 
-  it('should use default values when fields are missing in the response', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success(JSON.stringify({
-        meta: {
-          url: 'https://example.com',
-          has_results: true
-        },
-        results: {
-          mobianRisk: 'high'
-          // Missing other fields
-        }
-      }));
+      const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData);
+      expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal({
+        existing: 'data',
+        ...mockContextData
+      });
     });
 
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({
-        risk: 'high',
-        contentCategories: [],
-        sentiment: 'unknown',
-        emotions: [],
-        themes: [],
-        tones: [],
-        genres: [],
-        apValues: {}
-      });
-      expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({
-        mobianRisk: 'high',
-        mobianContentCategories: [],
-        mobianSentiment: 'unknown',
-        mobianEmotions: [],
-        mobianThemes: [],
-        mobianTones: [],
-        mobianGenres: [],
-        apValues: {}
-      });
+    it('should create data object if missing', function () {
+      delete bidReqConfig.ortb2Fragments.global.site.ext.data;
+      const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData);
+      expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData);
     });
   });
 
-  it('should handle response with only ap values', function () {
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success(JSON.stringify({
-        meta: {
-          url: 'https://example.com',
-          has_results: true
-        },
-        results: {
-          ap: { a0: [1, 2], a1: [3, 4], p0: [5, 6], p1: [7, 8] }
+  describe('getConfig', function () {
+    it('should return config with correct keys', function () {
+      const config = getConfig({
+        name: 'mobianBrandSafety',
+        params: {
+          prefix: 'mobiantest',
+          publisherTargeting: ['apValues'],
+          advertiserTargeting: ['emotions'],
         }
-      }));
+      });
+      expect(config).to.deep.equal({
+        prefix: 'mobiantest',
+        publisherTargeting: ['apValues'],
+        advertiserTargeting: ['emotions'],
+      });
     });
 
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => {
-      expect(result).to.deep.equal({
-        risk: 'unknown',
-        contentCategories: [],
-        sentiment: 'unknown',
-        emotions: [],
-        themes: [],
-        tones: [],
-        genres: [],
-        apValues: { a0: [1, 2], a1: [3, 4], p0: [5, 6], p1: [7, 8] }
+    it('should set default values for configs not set', function () {
+      const config = getConfig({
+        name: 'mobianBrandSafety',
+        params: {
+          publisherTargeting: ['apValues'],
+        }
       });
-      expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({
-        mobianRisk: 'unknown',
-        mobianContentCategories: [],
-        mobianSentiment: 'unknown',
-        mobianEmotions: [],
-        mobianThemes: [],
-        mobianTones: [],
-        mobianGenres: [],
-        apValues: { a0: [1, 2], a1: [3, 4], p0: [5, 6], p1: [7, 8] }
+      expect(config).to.deep.equal({
+        prefix: 'mobian',
+        publisherTargeting: ['apValues'],
+        advertiserTargeting: [],
       });
     });
-  });
 
-  it('should set key-value pairs when ext object is missing', function () {
-    bidReqConfig = {
-      ortb2Fragments: {
-        global: {
-          site: {}
-        }
-      }
-    };
+    it('should set default values if not provided', function () {
+      const config = getConfig({});
+      expect(config).to.deep.equal({
+        prefix: 'mobian',
+        publisherTargeting: [],
+        advertiserTargeting: [],
+      });
+    });
 
-    ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
-      callbacks.success(JSON.stringify({
-        meta: {
-          url: 'https://example.com',
-          has_results: true
-        },
-        results: {
-          mobianRisk: 'low',
-          mobianSentiment: 'positive',
-          mobianContentCategories: [],
-          mobianEmotions: ['joy'],
-          mobianThemes: [],
-          mobianTones: [],
-          mobianGenres: [],
-          ap: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }
-        }
-      }));
+    it('should set default values if no config is provided', function () {
+      const config = getConfig();
+      expect(config).to.deep.equal({
+        prefix: 'mobian',
+        publisherTargeting: [],
+        advertiserTargeting: [],
+      });
     });
 
-    return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, () => {}, {}).then(() => {
-      expect(bidReqConfig.ortb2Fragments.global.site.ext).to.exist;
-      expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({
-        mobianRisk: 'low',
-        mobianContentCategories: [],
-        mobianSentiment: 'positive',
-        mobianEmotions: ['joy'],
-        mobianThemes: [],
-        mobianTones: [],
-        mobianGenres: [],
-        apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }
+    it('should set all tarteging values if value is true', function () {
+      const config = getConfig({
+        name: 'mobianBrandSafety',
+        params: {
+          publisherTargeting: true,
+          advertiserTargeting: true,
+        }
+      });
+      expect(config).to.deep.equal({
+        prefix: 'mobian',
+        publisherTargeting: CONTEXT_KEYS,
+        advertiserTargeting: CONTEXT_KEYS,
       });
     });
   });

From cafb7f7638d2b01b6bfb0b40fca2560106cf282c Mon Sep 17 00:00:00 2001
From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com>
Date: Mon, 2 Dec 2024 20:21:56 +0200
Subject: [PATCH 0715/1097] AGT-388: Add missed params and return old params
 (#12524)

Co-authored-by: dmytro-po 
---
 .../intentIqConstants/intentIqConstants.js    |  2 +-
 modules/intentIqAnalyticsAdapter.js           | 65 ++++++++++++++-----
 modules/intentIqIdSystem.js                   | 16 +++++
 3 files changed, 64 insertions(+), 19 deletions(-)

diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js
index af7f7722213..ed9856fc213 100644
--- a/libraries/intentIqConstants/intentIqConstants.js
+++ b/libraries/intentIqConstants/intentIqConstants.js
@@ -7,4 +7,4 @@ export const OPT_OUT = 'O';
 export const BLACK_LIST = 'L';
 export const CLIENT_HINTS_KEY = '_iiq_ch';
 export const EMPTY = 'EMPTY'
-export const VERSION = 0.23
+export const VERSION = 0.24
diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js
index 1779152c5f0..1cf270117b7 100644
--- a/modules/intentIqAnalyticsAdapter.js
+++ b/modules/intentIqAnalyticsAdapter.js
@@ -51,7 +51,8 @@ const PARAMS_NAMES = {
   isInBrowserBlacklist: 'inbbl',
   prebidVersion: 'pbjsver',
   partnerId: 'partnerId',
-  firstPartyId: 'pcid'
+  firstPartyId: 'pcid',
+  placementId: 'placementId'
 };
 
 let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl, analyticsType}), {
@@ -138,6 +139,10 @@ function initReadLsIds() {
       iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause
       iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data;
       iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1;
+      iiqAnalyticsAnalyticsAdapter.initOptions.ct = pData.ct || null;
+      iiqAnalyticsAnalyticsAdapter.initOptions.siteId = pData.siteId || null;
+      iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll = pData.wsrvcll || false;
+      iiqAnalyticsAnalyticsAdapter.initOptions.rrtt = pData.rrtt || null;
     }
 
     iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints
@@ -198,13 +203,18 @@ export function preparePayload(data) {
   result[PARAMS_NAMES.referrer] = getReferrer();
   result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause;
   result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup;
+  result[PARAMS_NAMES.clientType] = iiqAnalyticsAnalyticsAdapter.initOptions.ct;
+  result[PARAMS_NAMES.siteId] = iiqAnalyticsAnalyticsAdapter.initOptions.siteId;
+  result[PARAMS_NAMES.wasServerCalled] = iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll;
+  result[PARAMS_NAMES.requestRtt] = iiqAnalyticsAnalyticsAdapter.initOptions.rrtt;
 
   result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup == 'A';
 
   result[PARAMS_NAMES.agentId] = REPORTER_ID;
-  if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid)
+  if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid);
+  if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pid) result[PARAMS_NAMES.profile] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pid)
 
-  fillPrebidEventData(data, result);
+  prepareData(data, result);
 
   fillEidsData(result);
 
@@ -218,27 +228,46 @@ function fillEidsData(result) {
   }
 }
 
-function fillPrebidEventData(eventData, result) {
-  if (eventData.bidderCode) {
-    result.bidderCode = eventData.bidderCode;
+function prepareData (data, result) {
+  if (data.bidderCode) {
+    result.bidderCode = data.bidderCode;
   }
-  if (eventData.cpm) {
-    result.cpm = eventData.cpm;
+  if (data.cpm) {
+    result.cpm = data.cpm;
   }
-  if (eventData.currency) {
-    result.currency = eventData.currency;
+  if (data.currency) {
+    result.currency = data.currency;
   }
-  if (eventData.originalCpm) {
-    result.originalCpm = eventData.originalCpm;
+  if (data.originalCpm) {
+    result.originalCpm = data.originalCpm;
   }
-  if (eventData.originalCurrency) {
-    result.originalCurrency = eventData.originalCurrency;
+  if (data.originalCurrency) {
+    result.originalCurrency = data.originalCurrency;
   }
-  if (eventData.status) {
-    result.status = eventData.status;
+  if (data.status) {
+    result.status = data.status;
   }
-  if (eventData.auctionId) {
-    result.prebidAuctionId = eventData.auctionId;
+  if (data.auctionId) {
+    result.prebidAuctionId = data.auctionId;
+  }
+  if (data.placementId) {
+    result.placementId = data.placementId;
+  } else {
+    // Simplified placementId determination
+    let placeIdFound = false;
+    if (data.params && Array.isArray(data.params)) {
+      for (let i = 0; i < data.params.length; i++) {
+        const param = data.params[i];
+        if (param.placementId) {
+          result.placementId = param.placementId;
+          placeIdFound = true;
+          break;
+        }
+      }
+    }
+    if (!placeIdFound && data.adUnitCode) {
+      result.placementId = data.adUnitCode;
+    }
   }
 
   result.biddingPlatformId = 1;
diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js
index c854b6c4746..4a7d0f47b18 100644
--- a/modules/intentIqIdSystem.js
+++ b/modules/intentIqIdSystem.js
@@ -315,6 +315,11 @@ export const intentIqIdSubmodule = {
     const savedData = tryParse(readData(FIRST_PARTY_DATA_KEY, allowedStorage))
     if (savedData) {
       partnerData = savedData;
+
+      if (partnerData.wsrvcll) {
+        partnerData.wsrvcll = false;
+        storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage);
+      }
     }
 
     if (partnerData.data) {
@@ -434,6 +439,14 @@ export const intentIqIdSubmodule = {
               partnerData.data = respJson.data;
             }
 
+            if ('ct' in respJson) {
+              partnerData.ct = respJson.ct;
+            }
+
+            if ('sid' in respJson) {
+              partnerData.siteId = respJson.sid;
+            }
+
             if (rrttStrtTime && rrttStrtTime > 0) {
               partnerData.rrtt = Date.now() - rrttStrtTime;
             }
@@ -459,7 +472,10 @@ export const intentIqIdSubmodule = {
           callback(runtimeEids);
         }
       };
+      rrttStrtTime = Date.now();
 
+      partnerData.wsrvcll = true;
+      storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage);
       ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true});
     };
     const respObj = {callback: resp};

From 0b8d77451a759d448e6730dc6942cbfa2f6c7243 Mon Sep 17 00:00:00 2001
From: Hiroaki Kubota 
Date: Tue, 3 Dec 2024 03:23:57 +0900
Subject: [PATCH 0716/1097] Refactor craftBidAdapter (#12517)

---
 libraries/interpretResponseUtils/index.js | 22 +++++++++++++++
 modules/craftBidAdapter.js                | 34 ++++++-----------------
 2 files changed, 31 insertions(+), 25 deletions(-)
 create mode 100644 libraries/interpretResponseUtils/index.js

diff --git a/libraries/interpretResponseUtils/index.js b/libraries/interpretResponseUtils/index.js
new file mode 100644
index 00000000000..6d081e4c272
--- /dev/null
+++ b/libraries/interpretResponseUtils/index.js
@@ -0,0 +1,22 @@
+import {logError} from '../../src/utils.js';
+
+export function interpretResponseUtil(serverResponse, {bidderRequest}, eachBidCallback) {
+  const bids = [];
+  if (!serverResponse.body || serverResponse.body.error) {
+    let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`;
+    if (serverResponse.body && serverResponse.body.error) { errorMessage += `: ${serverResponse.body.error}`; }
+    logError(errorMessage);
+    return bids;
+  }
+  (serverResponse.body.tags || []).forEach(serverBid => {
+    try {
+      const bid = eachBidCallback(serverBid);
+      if (bid) {
+        bids.push(bid);
+      }
+    } catch (e) {
+      // Do nothing
+    }
+  });
+  return bids;
+}
diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js
index f8d216f0838..3161534a441 100644
--- a/modules/craftBidAdapter.js
+++ b/modules/craftBidAdapter.js
@@ -1,4 +1,4 @@
-import {getBidRequest, logError} from '../src/utils.js';
+import {getBidRequest} from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import {find, includes} from '../src/polyfill.js';
@@ -7,6 +7,7 @@ import {ajax} from '../src/ajax.js';
 import {hasPurpose1Consent} from '../src/utils/gdpr.js';
 import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
 import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js';
+import {interpretResponseUtil} from '../libraries/interpretResponseUtils/index.js';
 
 const BIDDER_CODE = 'craft';
 const URL_BASE = 'https://gacraft.jp/prebid-v3';
@@ -68,31 +69,14 @@ export const spec = {
 
   interpretResponse: function(serverResponse, {bidderRequest}) {
     try {
-      serverResponse = serverResponse.body;
-      const bids = [];
-      if (!serverResponse) {
-        return [];
-      }
-      if (serverResponse.error) {
-        let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`;
-        if (serverResponse.error) {
-          errorMessage += `: ${serverResponse.error}`;
+      const bids = interpretResponseUtil(serverResponse, {bidderRequest}, serverBid => {
+        const rtbBid = getRtbBid(serverBid);
+        if (rtbBid && rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) {
+          const bid = newBid(serverBid, rtbBid, bidderRequest);
+          bid.mediaType = parseMediaType(rtbBid);
+          return bid;
         }
-        logError(errorMessage);
-        return bids;
-      }
-      if (serverResponse.tags) {
-        serverResponse.tags.forEach(serverBid => {
-          const rtbBid = getRtbBid(serverBid);
-          if (rtbBid) {
-            if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) {
-              const bid = newBid(serverBid, rtbBid, bidderRequest);
-              bid.mediaType = parseMediaType(rtbBid);
-              bids.push(bid);
-            }
-          }
-        });
-      }
+      });
       return bids;
     } catch (e) {
       return [];

From 7facccbe88e5972f6b83b7f11d96e344a2728d71 Mon Sep 17 00:00:00 2001
From: rrochwick <65189775+rrochwick@users.noreply.github.com>
Date: Tue, 3 Dec 2024 10:35:30 -0500
Subject: [PATCH 0717/1097] Qortex RTD module : code removal & cleanup (#12515)

* Qortex RTD module code removal & cleanup

* Add additional information to jsdocs

* JSDocs update for setters
---
 modules/qortexRtdProvider.js                | 311 ++++--------------
 test/spec/modules/qortexRtdProvider_spec.js | 329 ++------------------
 2 files changed, 96 insertions(+), 544 deletions(-)

diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js
index b856c3aa9ee..1ee8f8cf5d0 100644
--- a/modules/qortexRtdProvider.js
+++ b/modules/qortexRtdProvider.js
@@ -1,17 +1,21 @@
 import { submodule } from '../src/hook.js';
-import { ajax } from '../src/ajax.js';
 import { logWarn, mergeDeep, logMessage, generateUUID } from '../src/utils.js';
 import { loadExternalScript } from '../src/adloader.js';
 import * as events from '../src/events.js';
 import { EVENTS } from '../src/constants.js';
 import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
 
-const DEFAULT_API_URL = 'https://demand.qortex.ai';
-const LOOKUP_RATE_LIMIT = 5000;
 const qortexSessionInfo = {};
-
-let lookupRateLimitTimeout;
-let lookupAttemptDelay;
+const QX_IN_MESSAGE = {
+  BID_ENRICH_INITIALIZED: 'CX-BID-ENRICH-INITIALIZED',
+  DISPATCH_CONTEXT: 'DISPATCH-CONTEXT'
+}
+const QX_OUT_MESSAGE = {
+  AUCTION_END: 'AUCTION-END',
+  NO_CONTEXT: 'NO-CONTEXT',
+  RTD_INITIALIZED: 'RTD-INITIALIZED',
+  REQUEST_CONTEXT: 'REQUEST-CONTEXT'
+}
 
 /**
  * Init if module configuration is valid
@@ -25,25 +29,7 @@ function init (config) {
   } else {
     initializeModuleData(config);
     if (config?.params?.enableBidEnrichment) {
-      logMessage('Requesting Qortex group configuration')
-      getGroupConfig()
-        .then(groupConfig => {
-          logMessage(['Received response for qortex group config', groupConfig])
-          if (groupConfig?.active === true && groupConfig?.prebidBidEnrichment === true) {
-            setGroupConfigData(groupConfig);
-            initializeBidEnrichment();
-          } else {
-            logWarn('Group config is not configured for qortex bid enrichment')
-            setGroupConfigData(groupConfig);
-          }
-        })
-        .catch((e) => {
-          const errorStatus = e.message;
-          logWarn('Returned error status code: ' + errorStatus);
-          if (errorStatus == 404) {
-            logWarn('No Group Config found');
-          }
-        });
+      initializeBidEnrichment();
     } else {
       logWarn('Bid Enrichment Function has been disabled in module configuration')
     }
@@ -61,20 +47,11 @@ function init (config) {
  */
 function getBidRequestData (reqBidsConfig, callback) {
   if (reqBidsConfig?.adUnits?.length > 0 && shouldAllowBidEnrichment()) {
-    getContext()
-      .then(contextData => {
-        setContextData(contextData)
-        addContextToRequests(reqBidsConfig)
-        callback();
-      })
-      .catch(e => {
-        logWarn('Returned error status code: ' + e.message);
-        callback();
-      });
+    addContextToRequests(reqBidsConfig);
   } else {
-    logWarn('Module function is paused due to configuration \n Module Config: ' + JSON.stringify(reqBidsConfig) + `\n Group Config: ${JSON.stringify(qortexSessionInfo.groupConfig) ?? 'NO GROUP CONFIG'}`)
-    callback();
+    logWarn('Module function is paused due to configuration \n Module Config: ' + JSON.stringify(reqBidsConfig));
   }
+  callback();
 }
 
 /**
@@ -82,136 +59,13 @@ function getBidRequestData (reqBidsConfig, callback) {
  * @param {Object} data Auction end object
  */
 function onAuctionEndEvent (data, config, t) {
+  logMessage('Auction ended: ', JSON.stringify(data));
   if (shouldAllowBidEnrichment()) {
-    sendAnalyticsEvent('AUCTION', 'AUCTION_END', attachContextAnalytics(data))
-      .then(result => {
-        logMessage('Qortex analytics event sent')
-      })
-      .catch(e => logWarn(e.message))
-  }
-}
-
-/**
- * determines whether to send a request to context api and does so if necessary
- * @returns {Promise} ortb Content object
- */
-export function getContext () {
-  if (qortexSessionInfo.currentSiteContext) {
-    logMessage('Adding Content object from existing context data');
-    return new Promise((resolve, reject) => resolve(qortexSessionInfo.currentSiteContext));
-  } else if (lookupRateLimitTimeout !== null) {
-    logMessage(`Content lookup attempted during rate limit waiting period of ${LOOKUP_RATE_LIMIT}ms.`);
-    lookupAttemptDelay = true;
-    return new Promise((resolve, reject) => reject(new Error(429)));
-  } else {
-    lookupRateLimitTimeout = setTimeout(resetRateLimitTimeout, LOOKUP_RATE_LIMIT);
-    const pageUrlObject = { pageUrl: document.location.href ?? '' }
-    logMessage('Requesting new context data');
-    return new Promise((resolve, reject) => {
-      const callbacks = {
-        success(text, data) {
-          const responseStatus = data.status;
-          let result = null;
-          if (responseStatus === 200) {
-            qortexSessionInfo.pageAnalysisData.contextRetrieved = true
-            result = JSON.parse(data.response)?.content;
-          }
-          resolve(result);
-        },
-        error(e, x) {
-          const responseStatus = x.status;
-          reject(new Error(responseStatus));
-        }
-      }
-      ajax(qortexSessionInfo.contextUrl, callbacks, JSON.stringify(pageUrlObject), {contentType: 'application/json'})
-    })
-  }
-}
-
-/**
- * Resets rate limit timeout for preventing overactive page content lookup
- * Will re-trigger requestContextData if page lookups had been previously delayed
- */
-export function resetRateLimitTimeout() {
-  lookupRateLimitTimeout = null;
-  if (lookupAttemptDelay) {
-    lookupAttemptDelay = false;
-    if (!qortexSessionInfo.currentSiteContext) {
-      requestContextData();
-    }
-  }
-}
-
-/**
- * Requests Qortex group configuration using group id
- * @returns {Promise} Qortex group configuration
- */
-export function getGroupConfig () {
-  return new Promise((resolve, reject) => {
-    const callbacks = {
-      success(text, data) {
-        const result = data.status === 200 ? JSON.parse(data.response) : null;
-        resolve(result);
-      },
-      error(e, x) {
-        reject(new Error(x.status));
-      }
+    if (!qortexSessionInfo.auctionsEnded) {
+      qortexSessionInfo.auctionsEnded = [];
     }
-    ajax(qortexSessionInfo.groupConfigUrl, callbacks)
-  })
-}
-
-/**
- * Sends analytics events to Qortex
- * @returns {Promise}
- */
-export function sendAnalyticsEvent(eventType, subType, data) {
-  if (shouldSendAnalytics(data)) {
-    const analtyicsEventObject = generateAnalyticsEventObject(eventType, subType, data)
-    logMessage('Sending qortex analytics event');
-    return new Promise((resolve, reject) => {
-      const callbacks = {
-        success() {
-          resolve();
-        },
-        error(e, x) {
-          reject(new Error('Returned error status code: ' + x.status));
-        }
-      }
-      ajax(qortexSessionInfo.analyticsUrl, callbacks, JSON.stringify(analtyicsEventObject), {contentType: 'application/json'})
-    })
-  } else {
-    return new Promise((resolve, reject) => reject(new Error('Current request did not meet analytics percentage threshold, cancelling sending event')));
-  }
-}
-
-/**
- * Creates analytics object for Qortex
- * @returns {Object} analytics object
- */
-export function generateAnalyticsEventObject(eventType, subType, data) {
-  return {
-    sessionId: qortexSessionInfo.sessionId,
-    groupId: qortexSessionInfo.groupId,
-    eventType: eventType,
-    subType: subType,
-    eventOriginSource: 'RTD',
-    data: data
-  }
-}
-
-/**
- * Determines API host for Qortex
- * @param qortexUrlBase api url from config or default
- * @returns {string} Qortex analytics host url
- */
-export function generateAnalyticsHostUrl(qortexUrlBase) {
-  if (qortexUrlBase === DEFAULT_API_URL) {
-    return 'https://events.qortex.ai/api/v1/player-event';
-  } else if (qortexUrlBase.includes('stg-demand')) {
-    return 'https://stg-events.qortex.ai/api/v1/player-event';
-  } else {
-    return 'https://dev-events.qortex.ai/api/v1/player-event';
+    qortexSessionInfo.auctionsEnded.push(JSON.stringify(data));
+    postBidEnrichmentMessage(QX_OUT_MESSAGE.AUCTION_END, JSON.stringify(data));
   }
 }
 
@@ -222,20 +76,17 @@ export function generateAnalyticsHostUrl(qortexUrlBase) {
 export function addContextToRequests (reqBidsConfig) {
   if (qortexSessionInfo.currentSiteContext === null) {
     logWarn('No context data received at this time');
+    requestContextData();
   } else {
     if (checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidBidEnrichmentPercentage)) {
       const fragment = { site: {content: qortexSessionInfo.currentSiteContext} }
       if (qortexSessionInfo.bidderArray?.length > 0) {
-        qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment}))
-        saveContextAdded(reqBidsConfig);
+        qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment}));
       } else if (!qortexSessionInfo.bidderArray) {
         mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment);
-        saveContextAdded(reqBidsConfig);
       } else {
         logWarn('Config contains an empty bidders array, unable to determine which bids to enrich');
       }
-    } else {
-      saveContextAdded(reqBidsConfig, true);
     }
   }
 }
@@ -294,24 +145,14 @@ export function initializeBidEnrichment() {
 }
 
 /**
- * Call Qortex api for available contextual information about current environment
+ * Call Qortex code on page for available contextual information about current environment
  */
 export function requestContextData() {
-  getContext()
-    .then(contextData => {
-      if (qortexSessionInfo.pageAnalysisData.contextRetrieved) {
-        logMessage('Contextual record Received from Qortex API')
-        setContextData(contextData)
-      } else {
-        logWarn('Contexual record is not yet complete at this time')
-      }
-    })
-    .catch((e) => {
-      logWarn('Returned error status code: ' + e.message)
-      if (e.message == 404) {
-        postBidEnrichmentMessage('NO-CONTEXT')
-      }
-    })
+  if (qortexSessionInfo.currentSiteContext) {
+    logMessage('Context data already retrieved.');
+  } else {
+    postBidEnrichmentMessage(QX_OUT_MESSAGE.REQUEST_CONTEXT);
+  }
 }
 
 /**
@@ -319,88 +160,57 @@ export function requestContextData() {
  * @param {Object} config module config obtained during init
  */
 export function initializeModuleData(config) {
-  const {apiUrl, groupId, bidders, enableBidEnrichment} = config.params;
-  const qortexUrlBase = apiUrl || DEFAULT_API_URL;
-  const windowUrl = window.top.location.host;
+  const {groupId, bidders, enableBidEnrichment} = config.params;
   qortexSessionInfo.bidEnrichmentDisabled = enableBidEnrichment !== null ? !enableBidEnrichment : true;
   qortexSessionInfo.bidderArray = bidders;
   qortexSessionInfo.impressionIds = new Set();
   qortexSessionInfo.currentSiteContext = null;
-  qortexSessionInfo.pageAnalysisData = {
-    contextRetrieved: false,
-    contextAdded: {}
-  };
   qortexSessionInfo.sessionId = generateSessionId();
   qortexSessionInfo.groupId = groupId;
-  qortexSessionInfo.groupConfigUrl = `${qortexUrlBase}/api/v1/prebid/group/configs/${groupId}/${windowUrl}`;
-  qortexSessionInfo.contextUrl = `${qortexUrlBase}/api/v1/prebid/${groupId}/page/lookup`;
-  qortexSessionInfo.analyticsUrl = generateAnalyticsHostUrl(qortexUrlBase);
-  lookupRateLimitTimeout = null;
-  lookupAttemptDelay = false;
   return qortexSessionInfo;
 }
 
-export function saveContextAdded(reqBids, skipped = false) {
-  const id = reqBids.auctionId;
-  const contextBidders = qortexSessionInfo.bidderArray ?? Array.from(new Set(reqBids.adUnits.flatMap(adunit => adunit.bids.map(bid => bid.bidder))))
-  qortexSessionInfo.pageAnalysisData.contextAdded[id] = {
-    bidders: contextBidders,
-    contextSkipped: skipped
-  };
-}
-
+/**
+ * Allows setting of contextual data
+ */
 export function setContextData(value) {
   qortexSessionInfo.currentSiteContext = value
 }
 
+/**
+ * Allows setting of group configuration data
+ */
 export function setGroupConfigData(value) {
   qortexSessionInfo.groupConfig = value
 }
 
-export function getContextAddedEntry (id) {
-  return qortexSessionInfo?.pageAnalysisData?.contextAdded[id]
-}
-
+/**
+ * Unique id generator creating an identifier through datetime and random number
+ * @returns {string}
+ */
 function generateSessionId() {
   const randomInt = window.crypto.getRandomValues(new Uint32Array(1));
   const currentDateTime = Math.floor(Date.now() / 1000);
   return 'QX' + randomInt.toString() + 'X' + currentDateTime.toString()
 }
 
-function attachContextAnalytics (data) {
-  const contextAddedEntry = getContextAddedEntry(data.auctionId);
-  if (contextAddedEntry) {
-    data.qortexContext = qortexSessionInfo.currentSiteContext ?? {};
-    data.qortexContextBidders = contextAddedEntry?.bidders;
-    data.qortexContextSkipped = contextAddedEntry?.contextSkipped;
-    return data;
-  } else {
-    logMessage(`Auction ${data.auctionId} did not interact with qortex bid enrichment`)
-    return null;
-  }
-}
-
+/**
+ * Check for a random value to be above given percentage threshold
+ * @param {number} percentageValue 0-100 number for percentage check.
+ * @returns {Boolean}
+ */
 function checkPercentageOutcome(percentageValue) {
-  const analyticsPercentage = percentageValue ?? 0;
-  const randomInt = Math.random().toFixed(5) * 100;
-  return analyticsPercentage > randomInt;
-}
-
-function shouldSendAnalytics(data) {
-  if (data) {
-    return checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidReportingPercentage)
-  } else {
-    return false;
-  }
+  return (percentageValue ?? 0) > (Math.random() * 100);
 }
 
+/**
+ * Check for allowing functionality of bid enrichment capabilities.
+ * @returns {Boolean}
+ */
 function shouldAllowBidEnrichment() {
   if (qortexSessionInfo.bidEnrichmentDisabled) {
     logWarn('Bid enrichment disabled at prebid config')
     return false;
-  } else if (!qortexSessionInfo.groupConfig?.prebidBidEnrichment) {
-    logWarn('Bid enrichment disabled at group config')
-    return false;
   }
   return true
 }
@@ -408,13 +218,15 @@ function shouldAllowBidEnrichment() {
 /**
  * Passes message out to external page through postMessage method
  * @param {string} msg message string to be passed to CX-BID-ENRICH target on current page
+ * @param {Object} data optional parameter object with additional data to send with post
  */
-function postBidEnrichmentMessage(msg) {
+function postBidEnrichmentMessage(msg, data = null) {
   window.postMessage({
     target: 'CX-BID-ENRICH',
-    message: msg
+    message: msg,
+    params: data
   }, window.location.protocol + '//' + window.location.host);
-  logMessage('Message post :: Qortex contextuality has been not been indexed.')
+  logMessage('Dispatching window postMessage: ' + msg);
 }
 
 /**
@@ -424,8 +236,21 @@ function postBidEnrichmentMessage(msg) {
 export function windowPostMessageReceived(evt) {
   const data = evt.data;
   if (typeof data.target !== 'undefined' && data.target === 'QORTEX-PREBIDJS-RTD-MODULE') {
-    if (data.message === 'CX-BID-ENRICH-INITIALIZED' && shouldAllowBidEnrichment()) {
-      requestContextData()
+    if (shouldAllowBidEnrichment()) {
+      if (data.message === QX_IN_MESSAGE.BID_ENRICH_INITIALIZED) {
+        if (Boolean(data.params) && Boolean(data.params?.groupConfig)) {
+          setGroupConfigData(data.params.groupConfig);
+        }
+        postBidEnrichmentMessage(QX_OUT_MESSAGE.RTD_INITIALIZED);
+        if (qortexSessionInfo?.auctionsEnded?.length > 0) {
+          qortexSessionInfo.auctionsEnded.forEach(data => postBidEnrichmentMessage(QX_OUT_MESSAGE.AUCTION_END, data));
+        }
+        requestContextData();
+      } else if (data.message === QX_IN_MESSAGE.DISPATCH_CONTEXT) {
+        if (data.params?.context) {
+          setContextData(data.params.context);
+        }
+      }
     }
   }
 }
diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js
index f7a2669a6e7..b5006e45d56 100644
--- a/test/spec/modules/qortexRtdProvider_spec.js
+++ b/test/spec/modules/qortexRtdProvider_spec.js
@@ -4,20 +4,13 @@ import { EVENTS } from '../../../src/constants.js';
 import {loadExternalScript} from 'src/adloader.js';
 import {
   qortexSubmodule as module,
-  getContext,
-  getGroupConfig,
-  generateAnalyticsEventObject,
-  generateAnalyticsHostUrl,
   addContextToRequests,
   setContextData,
   loadScriptTag,
   initializeModuleData,
   setGroupConfigData,
-  saveContextAdded,
-  initializeBidEnrichment,
-  getContextAddedEntry,
-  windowPostMessageReceived,
-  resetRateLimitTimeout
+  requestContextData,
+  windowPostMessageReceived
 } from '../../../modules/qortexRtdProvider';
 import {server} from '../../mocks/xhr.js';
 import { cloneDeep } from 'lodash';
@@ -75,9 +68,15 @@ describe('qortexRtdProvider', () => {
       type: 'qx-impression'
     }
   }
-  const validQortexPostMessage = {
+  const QortexPostMessageInitialized = {
     target: 'QORTEX-PREBIDJS-RTD-MODULE',
-    message: 'CX-BID-ENRICH-INITIALIZED'
+    message: 'CX-BID-ENRICH-INITIALIZED',
+    params: {groupConfig: {data: true}}
+  }
+  const QortexPostMessageContext = {
+    target: 'QORTEX-PREBIDJS-RTD-MODULE',
+    message: 'DISPATCH-CONTEXT',
+    params: {context: {data: true}}
   }
   const invalidTypeQortexEvent = {
     detail: {
@@ -148,37 +147,6 @@ describe('qortexRtdProvider', () => {
   })
 
   describe('init', () => {
-    it('returns true for valid config object', (done) => {
-      const result = module.init(validModuleConfig);
-      expect(server.requests.length).to.be.eql(1)
-      const groupConfigReq = server.requests[0];
-      groupConfigReq.respond(200, responseHeaders, validGroupConfigResponse);
-      setTimeout(() => {
-        expect(result).to.be.true;
-        done()
-      }, 500)
-    })
-
-    it('logs warning when group config does not pass setup conditions', (done) => {
-      const result = module.init(validModuleConfig);
-      expect(server.requests.length).to.be.eql(1)
-      const groupConfigReq = server.requests[0];
-      groupConfigReq.respond(200, responseHeaders, inactiveGroupConfigResponse);
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Group config is not configured for qortex bid enrichment')).to.be.true;
-        done()
-      }, 500)
-    })
-
-    it('logs warning when group config request errors', (done) => {
-      const result = module.init(validModuleConfig);
-      server.requests[0].respond(404, responseHeaders, inactiveGroupConfigResponse);
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('No Group Config found')).to.be.true;
-        done()
-      }, 500)
-    })
-
     it('will not initialize bid enrichment if it is disabled', () => {
       module.init(bidEnrichmentDisabledModuleConfig);
       expect(logWarnSpy.calledWith('Bid Enrichment Function has been disabled in module configuration')).to.be.true;
@@ -272,13 +240,11 @@ describe('qortexRtdProvider', () => {
       setGroupConfigData(validGroupConfigResponseObj);
       callbackSpy = sinon.spy();
       server.reset();
-      global.lookupRateLimitTimeout = null;
     })
 
     afterEach(() => {
       initializeModuleData(emptyModuleConfig);
       callbackSpy.resetHistory();
-      global.lookupRateLimitTimeout = null;
     })
 
     it('will call callback immediately if no adunits', () => {
@@ -288,74 +254,23 @@ describe('qortexRtdProvider', () => {
       expect(logWarnSpy.calledOnce).to.be.true;
     })
 
-    it('will call callback if getContext does not throw', (done) => {
-      const cb = function () {
-        expect(logWarnSpy.calledOnce).to.be.false;
-        done();
-      }
-      module.getBidRequestData(reqBidsConfig, cb);
-      server.requests[0].respond(200, responseHeaders, contextResponse);
-    })
-
-    it('will log message call callback if context data has already been collected', (done) => {
-      setContextData(contextResponseObj);
-      module.getBidRequestData(reqBidsConfig, callbackSpy);
-      setTimeout(() => {
-        expect(server.requests.length).to.be.eql(0);
-        expect(logMessageSpy.calledWith('Adding Content object from existing context data')).to.be.true;
-        done();
-      }, 250)
-    })
-
-    it('will catch and log error and fire callback', (done) => {
-      module.getBidRequestData(reqBidsConfig, callbackSpy);
-      server.requests[0].respond(404, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Returned error status code: 404')).to.be.eql(true);
-        expect(callbackSpy.calledOnce).to.be.true;
-        done();
-      }, 250)
-    })
-
-    it('will not request context if group config toggle is false', (done) => {
-      setGroupConfigData(inactiveGroupConfigResponseObj);
+    it('will not request context if prebid disable toggle is true', (done) => {
+      initializeModuleData(bidEnrichmentDisabledModuleConfig);
       const cb = function () {
         expect(server.requests.length).to.be.eql(0);
         expect(logWarnSpy.called).to.be.true;
-        expect(logWarnSpy.calledWith('Bid enrichment disabled at group config')).to.be.true;
+        expect(logWarnSpy.calledWith('Bid enrichment disabled at prebid config')).to.be.true;
         done();
       }
       module.getBidRequestData(reqBidsConfig, cb);
     })
 
-    it('Logs warning for network error', (done) => {
-      saveContextAdded(reqBidsConfig);
-      const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'};
-      module.onAuctionEndEvent(testData);
-      server.requests[0].respond(500, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Returned error status code: 500')).to.be.eql(true);
-        done();
-      }, 200)
-    })
-
-    it('Logs warning for rate limit', (done) => {
-      saveContextAdded(reqBidsConfig);
-      const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'};
-      module.onAuctionEndEvent(testData);
-      server.requests[0].respond(429, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Returned error status code: 429')).to.be.eql(true);
-        done();
-      }, 200)
-    })
-
-    it('will not request context if prebid disable toggle is true', (done) => {
-      initializeModuleData(bidEnrichmentDisabledModuleConfig);
+    it('will request to add context when ad units present and enabled', (done) => {
       const cb = function () {
+        setContextData(null);
         expect(server.requests.length).to.be.eql(0);
         expect(logWarnSpy.called).to.be.true;
-        expect(logWarnSpy.calledWith('Bid enrichment disabled at prebid config')).to.be.true;
+        expect(logWarnSpy.calledWith('No context data received at this time')).to.be.true;
         done();
       }
       module.getBidRequestData(reqBidsConfig, cb);
@@ -373,89 +288,24 @@ describe('qortexRtdProvider', () => {
       setGroupConfigData(null);
     })
 
-    it('Properly sends analytics event with valid config', (done) => {
-      saveContextAdded(reqBidsConfig);
+    it('Properly sends analytics event with valid config', () => {
       const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'};
       module.onAuctionEndEvent(testData);
-      const request = server.requests[0];
-      expect(request.url).to.be.eql('https://events.qortex.ai/api/v1/player-event');
-      server.requests[0].respond(200, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logMessageSpy.calledWith('Qortex analytics event sent')).to.be.true
-        done();
-      }, 200)
-    })
-
-    it('Logs warning for rejected analytics request', (done) => {
-      const invalidPercentageConfig = cloneDeep(validGroupConfigResponseObj);
-      invalidPercentageConfig.prebidReportingPercentage = -1;
-      setGroupConfigData(invalidPercentageConfig);
-      const testData = {data: 'data'};
-      module.onAuctionEndEvent(testData);
-      expect(server.requests.length).to.be.eql(0);
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Current request did not meet analytics percentage threshold, cancelling sending event')).to.be.true
-        done();
-      }, 200)
     })
   })
 
-  describe('getContext', () => {
-    beforeEach(() => {
-      initializeModuleData(validModuleConfig);
-    })
-
-    afterEach(() => {
-      initializeModuleData(emptyModuleConfig);
-    })
-
-    it('returns a promise', () => {
-      const result = getContext();
-      expect(result).to.be.a('promise');
+  describe('requestContextData', () => {
+    before(() => {
+      setContextData({data: true});
     })
 
-    it('uses request url generated from initialize function in config and resolves to content object data', (done) => {
-      let requestUrl = `${validModuleConfig.params.apiUrl}/api/v1/prebid/${validModuleConfig.params.groupId}/page/lookup`;
-      const ctx = getContext()
-      const request = server.requests[0]
-      request.respond(200, responseHeaders, contextResponse);
-      ctx.then(response => {
-        expect(server.requests.length).to.be.eql(1);
-        expect(request.url).to.be.eql(requestUrl);
-        expect(response).to.be.eql(contextResponseObj.content);
-        done();
-      });
+    after(() => {
+      setContextData(null);
     })
 
-    it('returns null when necessary', (done) => {
-      const ctx = getContext()
-      server.requests[0].respond(202, responseHeaders, JSON.stringify({}))
-      ctx.then(response => {
-        expect(response).to.be.null;
-        expect(server.requests.length).to.be.eql(1);
-        expect(logWarnSpy.called).to.be.false;
-        done();
-      });
-    })
-
-    it('request content object after elapsed rate limit timeout if a second content lookup was delayed', (done) => {
-      const ctx = getContext();
-      server.requests[0].respond(202, responseHeaders, JSON.stringify({}))
-      ctx.then(response => {
-        expect(response).to.be.null;
-        expect(logMessageSpy.calledWith('Requesting new context data')).to.be.true;
-        expect(server.requests.length).to.be.eql(1);
-        expect(logWarnSpy.called).to.be.false;
-      });
-      setTimeout(() => {
-        const ctx2 = getContext();
-        ctx2.catch(e => {
-          expect(e.message).to.equal('429');
-          expect(logMessageSpy.calledWith('Content lookup attempted during rate limit waiting period of 5000ms.')).to.be.true;
-          resetRateLimitTimeout();
-          done();
-        })
-      }, 200)
+    it('Will log properly when context data already available', () => {
+      requestContextData();
+      expect(logMessageSpy.calledWith('Context data already retrieved.')).to.be.true;
     })
   })
 
@@ -490,16 +340,6 @@ describe('qortexRtdProvider', () => {
       expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({});
     })
 
-    it('saves context added entry with skipped flag if valid request does not meet threshold', () => {
-      initializeModuleData(validModuleConfig);
-      setContextData(contextResponseObj.content);
-      setGroupConfigData(noEnrichmentGroupConfigResponseObj);
-      addContextToRequests(reqBidsConfig);
-      const contextAdded = getContextAddedEntry(reqBidsConfig.auctionId);
-      expect(contextAdded).to.not.be.null;
-      expect(contextAdded.contextSkipped).to.eql(true);
-    })
-
     it('adds site.content only to global ortb2 when bidders array is omitted', () => {
       const omittedBidderArrayConfig = cloneDeep(validModuleConfig);
       delete omittedBidderArrayConfig.params.bidders;
@@ -545,137 +385,24 @@ describe('qortexRtdProvider', () => {
     })
   })
 
-  describe('generateAnalyticsEventObject', () => {
-    let qortexSessionInfo;
-    beforeEach(() => {
-      qortexSessionInfo = initializeModuleData(validModuleConfig);
-      setGroupConfigData(validGroupConfigResponseObj);
-    })
-
-    afterEach(() => {
-      initializeModuleData(emptyModuleConfig);
-      setGroupConfigData(null);
-    })
-
-    it('returns expected object', () => {
-      const testEventType = 'TEST';
-      const testSubType = 'TEST_SUBTYPE';
-      const testData = {data: 'data'};
-
-      const result = generateAnalyticsEventObject(testEventType, testSubType, testData);
-
-      expect(result.sessionId).to.be.eql(qortexSessionInfo.sessionId);
-      expect(result.groupId).to.be.eql(qortexSessionInfo.groupId);
-      expect(result.eventType).to.be.eql(testEventType);
-      expect(result.subType).to.be.eql(testSubType);
-      expect(result.eventOriginSource).to.be.eql('RTD');
-      expect(result.data).to.be.eql(testData);
-    })
-  })
-
-  describe('generateAnalyticsHostUrl', () => {
-    it('will use qortex analytics host when appropriate', () => {
-      const hostUrl = generateAnalyticsHostUrl(defaultApiHost);
-      expect(hostUrl).to.be.eql('https://events.qortex.ai/api/v1/player-event');
-    })
-
-    it('will use qortex stage analytics host when appropriate', () => {
-      const hostUrl = generateAnalyticsHostUrl('https://stg-demand.qortex.ai');
-      expect(hostUrl).to.be.eql('https://stg-events.qortex.ai/api/v1/player-event');
-    })
-
-    it('will default to dev analytics host when appropriate', () => {
-      const hostUrl = generateAnalyticsHostUrl('https://dev-demand.qortex.ai');
-      expect(hostUrl).to.be.eql('https://dev-events.qortex.ai/api/v1/player-event');
-    })
-  })
-
-  describe('getGroupConfig', () => {
-    let sessionInfo;
-
-    beforeEach(() => {
-      sessionInfo = initializeModuleData(validModuleConfig);
-    })
-
-    afterEach(() => {
-      initializeModuleData(emptyModuleConfig);
-      setGroupConfigData(null);
-      setContextData(null);
-      server.reset();
-    })
-
-    it('returns a promise', () => {
-      const result = getGroupConfig();
-      expect(result).to.be.a('promise');
-    })
-
-    it('processes group config response in valid conditions', (done) => {
-      const result = getGroupConfig();
-      const request = server.requests[0]
-      request.respond(200, responseHeaders, validGroupConfigResponse);
-      result.then(response => {
-        expect(request.url).to.be.eql(sessionInfo.groupConfigUrl);
-        expect(response.groupId).to.be.eql(validGroupConfigResponseObj.groupId);
-        expect(response.active).to.be.eql(validGroupConfigResponseObj.active);
-        expect(response.prebidBidEnrichment).to.be.eql(validGroupConfigResponseObj.prebidBidEnrichment);
-        expect(response.prebidReportingPercentage).to.be.eql(validGroupConfigResponseObj.prebidReportingPercentage);
-        done();
-      })
-    })
-  })
-
   describe('initializeBidEnrichment', () => {
     beforeEach(() => {
       initializeModuleData(validModuleConfig);
       setGroupConfigData(validGroupConfigResponseObj);
       setContextData(null);
-      server.reset();
     })
 
     afterEach(() => {
       setGroupConfigData(null);
       setContextData(null);
-      server.reset();
-    })
-
-    it('sets context data if applicable', (done) => {
-      initializeBidEnrichment();
-      server.requests[0].respond(200, responseHeaders, contextResponse);
-      setTimeout(() => {
-        expect(logMessageSpy.calledWith('Contextual record Received from Qortex API')).to.be.true;
-        done()
-      }, 250)
-    })
-
-    it('logs warning if no record has been made', (done) => {
-      initializeBidEnrichment();
-      server.requests[0].respond(202, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Contexual record is not yet complete at this time')).to.be.true;
-        done();
-      }, 250)
     })
 
     it('processes incoming qortex component "initialize" message', () => {
-      postMessage(validQortexPostMessage);
-    })
-
-    it('will catch and log error and fire callback', (done) => {
-      initializeBidEnrichment();
-      server.requests[0].respond(404, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Returned error status code: 404')).to.be.eql(true);
-        done();
-      }, 250)
+      windowPostMessageReceived({data: QortexPostMessageInitialized})
     })
 
-    it('Logs warning for network error', (done) => {
-      initializeBidEnrichment();
-      server.requests[0].respond(500, responseHeaders, JSON.stringify({}));
-      setTimeout(() => {
-        expect(logWarnSpy.calledWith('Returned error status code: 500')).to.be.eql(true);
-        done();
-      }, 200)
+    it('processes incoming qortex component "context" message', () => {
+      windowPostMessageReceived({data: QortexPostMessageContext})
     })
   })
 })

From d0144407debf2104a2e79cff1ecf80e4e871ed71 Mon Sep 17 00:00:00 2001
From: mkomorski 
Date: Tue, 3 Dec 2024 16:36:37 +0100
Subject: [PATCH 0718/1097] Price floors module : accept null floors (#12295)

* 10432 Accept null floors

* lint fix

* Adding tests for floor provider case

* null-safe getFloor in adapters

* Update kargoBidAdapter.js

---------

Co-authored-by: Marcin Komorski 
Co-authored-by: Patrick McCann 
---
 libraries/advangUtils/index.js           |  2 +-
 libraries/currencyUtils/floor.js         |  2 +-
 libraries/dspxUtils/bidderUtils.js       |  2 +-
 libraries/precisoUtils/bidUtilsCommon.js |  2 +-
 libraries/riseUtils/index.js             |  5 +-
 libraries/teqblazeUtils/bidderUtils.js   |  2 +-
 libraries/vidazooUtils/bidderUtils.js    |  2 +-
 libraries/xeUtils/bidderUtils.js         |  4 +-
 modules/33acrossBidAdapter.js            |  2 +-
 modules/adagioBidAdapter.js              |  2 +-
 modules/adfBidAdapter.js                 |  4 +-
 modules/admixerBidAdapter.js             |  2 +-
 modules/adotBidAdapter.js                |  2 +-
 modules/adriverBidAdapter.js             |  8 +--
 modules/adtrgtmeBidAdapter.js            |  2 +-
 modules/adyoulikeBidAdapter.js           |  2 +-
 modules/amxBidAdapter.js                 |  2 +-
 modules/appushBidAdapter.js              |  2 +-
 modules/audiencerunBidAdapter.js         |  2 +-
 modules/axonixBidAdapter.js              |  2 +-
 modules/beopBidAdapter.js                |  3 +-
 modules/brightMountainMediaBidAdapter.js |  4 +-
 modules/c1xBidAdapter.js                 |  2 +-
 modules/carodaBidAdapter.js              |  4 +-
 modules/connatixBidAdapter.js            |  2 +-
 modules/connectadBidAdapter.js           |  2 +-
 modules/dailyhuntBidAdapter.js           |  2 +-
 modules/deltaprojectsBidAdapter.js       |  2 +-
 modules/dianomiBidAdapter.js             |  4 +-
 modules/eplanningBidAdapter.js           |  2 +-
 modules/freewheel-sspBidAdapter.js       |  2 +-
 modules/getintentBidAdapter.js           |  2 +-
 modules/gridBidAdapter.js                |  5 +-
 modules/gumgumBidAdapter.js              |  2 +-
 modules/impactifyBidAdapter.js           |  4 +-
 modules/kargoBidAdapter.js               |  4 +-
 modules/koblerBidAdapter.js              |  2 +-
 modules/kubientBidAdapter.js             |  4 +-
 modules/kueezBidAdapter.js               |  2 +-
 modules/lemmaDigitalBidAdapter.js        |  2 +-
 modules/luponmediaBidAdapter.js          |  3 +-
 modules/marsmediaBidAdapter.js           |  4 +-
 modules/mediakeysBidAdapter.js           |  3 +-
 modules/medianetBidAdapter.js            |  2 +-
 modules/mediasniperBidAdapter.js         |  3 +-
 modules/missenaBidAdapter.js             |  2 +-
 modules/nativoBidAdapter.js              |  4 +-
 modules/nextMillenniumBidAdapter.js      |  4 +-
 modules/nobidBidAdapter.js               |  2 +-
 modules/oguryBidAdapter.js               |  4 +-
 modules/onetagBidAdapter.js              |  2 +-
 modules/optidigitalBidAdapter.js         |  4 +-
 modules/outbrainBidAdapter.js            |  4 +-
 modules/ozoneBidAdapter.js               |  6 +-
 modules/priceFloors.js                   |  8 ++-
 modules/pubgeniusBidAdapter.js           |  2 +-
 modules/pubmaticBidAdapter.js            |  2 +-
 modules/pubwiseBidAdapter.js             |  4 +-
 modules/readpeakBidAdapter.js            |  2 +-
 modules/resetdigitalBidAdapter.js        |  4 +-
 modules/revcontentBidAdapter.js          |  2 +-
 modules/rtbhouseBidAdapter.js            |  2 +-
 modules/rubiconBidAdapter.js             |  5 +-
 modules/seedtagBidAdapter.js             |  2 +-
 modules/sharethroughBidAdapter.js        |  4 +-
 modules/silverpushBidAdapter.js          |  2 +-
 modules/sovrnBidAdapter.js               |  2 +-
 modules/sspBCBidAdapter.js               |  6 +-
 modules/stroeerCoreBidAdapter.js         |  4 +-
 modules/taboolaBidAdapter.js             |  4 +-
 modules/tripleliftBidAdapter.js          |  2 +-
 modules/ucfunnelBidAdapter.js            |  2 +-
 modules/unrulyBidAdapter.js              |  2 +-
 modules/videobyteBidAdapter.js           |  4 +-
 modules/vidoomyBidAdapter.js             |  4 +-
 modules/yahooAdsBidAdapter.js            |  2 +-
 modules/yieldlabBidAdapter.js            |  2 +-
 test/spec/modules/priceFloors_spec.js    | 84 ++++++++++++++++++++++++
 78 files changed, 204 insertions(+), 109 deletions(-)

diff --git a/libraries/advangUtils/index.js b/libraries/advangUtils/index.js
index 08c8e43cea3..f815f389ed6 100644
--- a/libraries/advangUtils/index.js
+++ b/libraries/advangUtils/index.js
@@ -14,7 +14,7 @@ export function isVideoBid(bid) {
 
 export function getBannerBidFloor(bid) {
   let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {};
-  return floorInfo.floor || getBannerBidParam(bid, 'bidfloor');
+  return floorInfo?.floor || getBannerBidParam(bid, 'bidfloor');
 }
 
 export function getVideoBidFloor(bid) {
diff --git a/libraries/currencyUtils/floor.js b/libraries/currencyUtils/floor.js
index 0bd52172a41..ee41bad01da 100644
--- a/libraries/currencyUtils/floor.js
+++ b/libraries/currencyUtils/floor.js
@@ -16,7 +16,7 @@ export function getBidFloor(bid) {
       mediaType: '*',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (_) {
     return 0;
   }
diff --git a/libraries/dspxUtils/bidderUtils.js b/libraries/dspxUtils/bidderUtils.js
index f19e2bfc29a..612a20f6865 100644
--- a/libraries/dspxUtils/bidderUtils.js
+++ b/libraries/dspxUtils/bidderUtils.js
@@ -225,7 +225,7 @@ export function getBidFloor(bid) {
       mediaType: '*',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (_) {
     return 0
   }
diff --git a/libraries/precisoUtils/bidUtilsCommon.js b/libraries/precisoUtils/bidUtilsCommon.js
index 74cf00b8450..a8ea97efcaf 100644
--- a/libraries/precisoUtils/bidUtilsCommon.js
+++ b/libraries/precisoUtils/bidUtilsCommon.js
@@ -37,7 +37,7 @@ export function getBidFloor(bid) {
       mediaType: '*',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (_) {
     return 0
   }
diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js
index 25c7183d552..2bc337b9c55 100644
--- a/libraries/riseUtils/index.js
+++ b/libraries/riseUtils/index.js
@@ -5,7 +5,8 @@ import {
   isEmpty,
   contains,
   isInteger,
-  getBidIdParameter
+  getBidIdParameter,
+  isPlainObject
 } from '../../src/utils.js';
 import { BANNER, VIDEO } from '../../src/mediaTypes.js';
 import {config} from '../../src/config.js';
@@ -19,7 +20,7 @@ export function getFloor(bid, mediaType) {
     mediaType: mediaType,
     size: '*'
   });
-  return floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0;
+  return isPlainObject(floorResult) && floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0;
 }
 
 export function getSizesArray(bid, mediaType) {
diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js
index 6186e526eb8..f9484ebe5d1 100644
--- a/libraries/teqblazeUtils/bidderUtils.js
+++ b/libraries/teqblazeUtils/bidderUtils.js
@@ -29,7 +29,7 @@ const getBidFloor = (bid) => {
       size: '*',
     });
 
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (err) {
     return 0;
   }
diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js
index 5c3409f4780..df947142a4c 100644
--- a/libraries/vidazooUtils/bidderUtils.js
+++ b/libraries/vidazooUtils/bidderUtils.js
@@ -255,7 +255,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder
       size: '*'
     });
 
-    if (floorInfo.currency === 'USD') {
+    if (floorInfo?.currency === 'USD') {
       bidFloor = floorInfo.floor;
     }
   }
diff --git a/libraries/xeUtils/bidderUtils.js b/libraries/xeUtils/bidderUtils.js
index abbd14db6a9..f03c6323d76 100644
--- a/libraries/xeUtils/bidderUtils.js
+++ b/libraries/xeUtils/bidderUtils.js
@@ -1,4 +1,4 @@
-import {deepAccess, getBidIdParameter, isFn, logError, isArray, parseSizesInput} from '../../src/utils.js';
+import {deepAccess, getBidIdParameter, isFn, logError, isArray, parseSizesInput, isPlainObject} from '../../src/utils.js';
 import {getAdUnitSizes} from '../sizeUtils/sizeUtils.js';
 import {findIndex} from '../../src/polyfill.js';
 
@@ -13,7 +13,7 @@ export function getBidFloor(bid, currency = 'USD') {
     size: '*'
   });
 
-  if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === currency) {
+  if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === currency) {
     return floor.floor;
   }
 
diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js
index ae002d79d58..555d9c8a291 100644
--- a/modules/33acrossBidAdapter.js
+++ b/modules/33acrossBidAdapter.js
@@ -535,7 +535,7 @@ function _getBidFloors(bidRequest, size, mediaType) {
     size: [ size.w, size.h ]
   });
 
-  if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) {
+  if (!isNaN(bidFloors?.floor) && (bidFloors?.currency === CURRENCY)) {
     return bidFloors.floor;
   }
 }
diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js
index cb125d4446e..d44c7e249d6 100644
--- a/modules/adagioBidAdapter.js
+++ b/modules/adagioBidAdapter.js
@@ -313,7 +313,7 @@ function _getFloors(bidRequest) {
     floors.push(cleanObj({
       mt: mediaType,
       s: isArray(size) ? `${size[0]}x${size[1]}` : undefined,
-      f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : undefined
+      f: (!isNaN(info?.floor) && info?.currency === CURRENCY) ? info?.floor : undefined
     }));
   }
 
diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js
index 925c0b3500e..b74c3efc628 100644
--- a/modules/adfBidAdapter.js
+++ b/modules/adfBidAdapter.js
@@ -75,8 +75,8 @@ export const spec = {
         mediaType: '*'
       }) : {};
 
-      const bidfloor = floorInfo.floor;
-      const bidfloorcur = floorInfo.currency;
+      const bidfloor = floorInfo?.floor;
+      const bidfloorcur = floorInfo?.currency;
       const { mid, inv, mname } = bid.params;
       const impExtData = bid.ortb2Imp?.ext?.data;
 
diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js
index 4deeaee5206..1570a36c5f0 100644
--- a/modules/admixerBidAdapter.js
+++ b/modules/admixerBidAdapter.js
@@ -133,7 +133,7 @@ function getBidFloor(bid) {
       mediaType: '*',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (_) {
     return 0;
   }
diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js
index 5167eeb8244..18c7e9265bf 100644
--- a/modules/adotBidAdapter.js
+++ b/modules/adotBidAdapter.js
@@ -641,7 +641,7 @@ function getFloor(adUnit, size, mediaType, currency) {
 
   const floorResult = adUnit.getFloor({ currency, mediaType, size });
 
-  return floorResult.currency === currency ? floorResult.floor : 0;
+  return floorResult?.currency === currency ? floorResult?.floor : 0;
 }
 
 /**
diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js
index 5bce315f572..7ac8e4fb728 100644
--- a/modules/adriverBidAdapter.js
+++ b/modules/adriverBidAdapter.js
@@ -1,5 +1,5 @@
 // ADRIVER BID ADAPTER for Prebid 1.13
-import {logInfo, getWindowLocation, _each, getBidIdParameter} from '../src/utils.js';
+import {logInfo, getWindowLocation, _each, getBidIdParameter, isPlainObject} from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { getStorageManager } from '../src/storageManager.js';
 
@@ -188,12 +188,12 @@ function _getFloor(bid, currencyPar, sizes) {
       size: isSize ? sizes : '*'
     });
 
-    if (typeof floorInfo === 'object' &&
-      !isNaN(parseFloat(floorInfo.floor))) {
+    if (isPlainObject(floorInfo) &&
+      !isNaN(parseFloat(floorInfo?.floor))) {
       floor = floorInfo.floor;
     }
 
-    if (typeof floorInfo === 'object' && floorInfo.currency) {
+    if (isPlainObject(floorInfo) && floorInfo.currency) {
       currencyResult = floorInfo.currency;
     }
   }
diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js
index c342c2625be..18cbd1e6ae7 100644
--- a/modules/adtrgtmeBidAdapter.js
+++ b/modules/adtrgtmeBidAdapter.js
@@ -90,7 +90,7 @@ function getFloorModuleData(bid) {
     mediaType: BANNER,
     size: '*'
   };
-  return (isFn(bid.getFloor)) ? bid.getFloor(getFloorRequestObject) : false;
+  return (isFn(bid.getFloor)) ? (bid.getFloor(getFloorRequestObject) || {}) : false;
 };
 
 function generateOpenRtbObject(bidderRequest, bid) {
diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js
index 146e1d3b24a..fce5d1ae000 100644
--- a/modules/adyoulikeBidAdapter.js
+++ b/modules/adyoulikeBidAdapter.js
@@ -255,7 +255,7 @@ function getFloor(bidRequest, size, mediaType) {
     size: [ size.width, size.height ]
   });
 
-  if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) {
+  if (!isNaN(bidFloors?.floor) && (bidFloors?.currency === CURRENCY)) {
     return bidFloors.floor;
   }
 }
diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js
index df260958104..508c4d6a0c7 100644
--- a/modules/amxBidAdapter.js
+++ b/modules/amxBidAdapter.js
@@ -142,7 +142,7 @@ function getFloor(bid) {
       size: '*',
       bidRequest: bid,
     });
-    return floor.floor;
+    return floor?.floor;
   } catch (e) {
     logError('call to getFloor failed: ', e);
     return DEFAULT_MIN_FLOOR;
diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js
index 67557aed10c..ec742120582 100644
--- a/modules/appushBidAdapter.js
+++ b/modules/appushBidAdapter.js
@@ -85,7 +85,7 @@ function getBidFloor(bid) {
       mediaType: '*',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (err) {
     logError(err);
     return 0;
diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js
index 92a4343b3ed..2e3125e8aa0 100644
--- a/modules/audiencerunBidAdapter.js
+++ b/modules/audiencerunBidAdapter.js
@@ -47,7 +47,7 @@ function getBidFloor(bid) {
       mediaType: BANNER,
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (_) {
     return 0;
   }
diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js
index b1ccef155de..2eefb617636 100644
--- a/modules/axonixBidAdapter.js
+++ b/modules/axonixBidAdapter.js
@@ -21,7 +21,7 @@ function getBidFloor(bidRequest) {
     });
   }
 
-  return floorInfo.floor || 0;
+  return floorInfo?.floor || 0;
 }
 
 function getPageUrl(bidRequest, bidderRequest) {
diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js
index 5237f3d7573..103820651fa 100644
--- a/modules/beopBidAdapter.js
+++ b/modules/beopBidAdapter.js
@@ -3,6 +3,7 @@ import {
   deepAccess, getBidIdParameter,
   getValue,
   isArray,
+  isPlainObject,
   logInfo,
   logWarn,
   triggerPixel
@@ -147,7 +148,7 @@ function beOpRequestSlotsMaker(bid) {
   let floor;
   if (typeof bid.getFloor === 'function') {
     const floorInfo = bid.getFloor({currency: publisherCurrency, mediaType: 'banner', size: [1, 1]});
-    if (typeof floorInfo === 'object' && floorInfo.currency === publisherCurrency && !isNaN(parseFloat(floorInfo.floor))) {
+    if (isPlainObject(floorInfo) && floorInfo.currency === publisherCurrency && !isNaN(parseFloat(floorInfo.floor))) {
       floor = parseFloat(floorInfo.floor);
     }
   }
diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js
index 98b97286631..5e5b062889d 100644
--- a/modules/brightMountainMediaBidAdapter.js
+++ b/modules/brightMountainMediaBidAdapter.js
@@ -1,4 +1,4 @@
-import { generateUUID, deepAccess, logWarn, deepSetValue } from '../src/utils.js';
+import { generateUUID, deepAccess, logWarn, deepSetValue, isPlainObject } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import { config } from '../src/config.js';
@@ -224,7 +224,7 @@ function getFloor(bid, size) {
       size: size,
     });
 
-    if (typeof floorInfo === 'object' && floorInfo.currency === 'USD') {
+    if (isPlainObject(floorInfo) && floorInfo.currency === 'USD') {
       return parseFloat(floorInfo.floor);
     }
   }
diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js
index 79ba8cf499d..d1b51dcb27d 100644
--- a/modules/c1xBidAdapter.js
+++ b/modules/c1xBidAdapter.js
@@ -185,7 +185,7 @@ function getBidFloor(bidRequest) {
   }
 
   let floor =
-    floorInfo.floor ||
+    floorInfo?.floor ||
     bidRequest.params.bidfloor ||
     bidRequest.params.floorPriceMap ||
     0;
diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js
index 8ec260213b6..a2370a13942 100644
--- a/modules/carodaBidAdapter.js
+++ b/modules/carodaBidAdapter.js
@@ -184,8 +184,8 @@ function getImps (validBidRequests, common) {
     const floorInfo = bid.getFloor
       ? bid.getFloor({ currency: common.currency || 'EUR' })
       : {};
-    const bidfloor = floorInfo.floor;
-    const bidfloorcur = floorInfo.currency;
+    const bidfloor = floorInfo?.floor;
+    const bidfloorcur = floorInfo?.currency;
     const { ctok, placementId } = bid.params;
     const imp = {
       bid_id: bid.bidId,
diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js
index aeadd2d1cd9..ea02b49d8ed 100644
--- a/modules/connatixBidAdapter.js
+++ b/modules/connatixBidAdapter.js
@@ -57,7 +57,7 @@ export function getBidFloor(bid) {
       mediaType: '*',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (err) {
     logError(err);
     return 0;
diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js
index 6789c937afb..982bff22585 100644
--- a/modules/connectadBidAdapter.js
+++ b/modules/connectadBidAdapter.js
@@ -243,7 +243,7 @@ function getBidFloor(bidRequest) {
     });
   }
 
-  let floor = floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.floorprice || 0;
+  let floor = floorInfo?.floor || bidRequest.params.bidfloor || bidRequest.params.floorprice || 0;
 
   return floor;
 }
diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js
index f96e07b71bf..fd7a5c137a7 100644
--- a/modules/dailyhuntBidAdapter.js
+++ b/modules/dailyhuntBidAdapter.js
@@ -129,7 +129,7 @@ const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validB
 // get bidFloor Function for different creatives
 function getBidFloor(bid, creative) {
   let floorInfo = typeof (bid.getFloor) == 'function' ? bid.getFloor({ currency: 'USD', mediaType: creative, size: '*' }) : {};
-  return Math.floor(floorInfo.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0));
+  return Math.floor(floorInfo?.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0));
 }
 
 const createOrtbImpObj = (bid) => {
diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js
index 15e94a5bc36..bf5489322c9 100644
--- a/modules/deltaprojectsBidAdapter.js
+++ b/modules/deltaprojectsBidAdapter.js
@@ -229,7 +229,7 @@ export function getBidFloor(bid, mediaType, size, currency) {
   if (isFn(bid.getFloor)) {
     const bidFloorCurrency = currency || 'USD';
     const bidFloor = bid.getFloor({currency: bidFloorCurrency, mediaType: mediaType, size: size});
-    if (isNumber(bidFloor.floor)) {
+    if (isNumber(bidFloor?.floor)) {
       return bidFloor;
     }
   }
diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js
index 3ae8b4d6b80..777878b5207 100644
--- a/modules/dianomiBidAdapter.js
+++ b/modules/dianomiBidAdapter.js
@@ -128,8 +128,8 @@ export const spec = {
           currency: currency || 'USD',
         })
         : {};
-      const bidfloor = floorInfo.floor;
-      const bidfloorcur = floorInfo.currency;
+      const bidfloor = floorInfo?.floor;
+      const bidfloorcur = floorInfo?.currency;
       const { smartadId } = bid.params;
 
       const imp = {
diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js
index d57804c04e6..1fe5cb09c10 100644
--- a/modules/eplanningBidAdapter.js
+++ b/modules/eplanningBidAdapter.js
@@ -274,7 +274,7 @@ function getFloorStr(bid) {
       currency: DOLLAR_CODE,
       mediaType: '*',
       size: '*'
-    });
+    }) || {};
 
     if (bidFloor.floor) {
       return '|' + encodeURIComponent(bidFloor.floor);
diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js
index 0ac848bf62a..fc85edc483b 100644
--- a/modules/freewheel-sspBidAdapter.js
+++ b/modules/freewheel-sspBidAdapter.js
@@ -231,7 +231,7 @@ function getBidFloor(bid, config) {
       mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video',
       size: '*',
     });
-    return bidFloor.floor;
+    return bidFloor?.floor;
   } catch (e) {
     return -1;
   }
diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js
index a8888893333..67a0e1e91be 100644
--- a/modules/getintentBidAdapter.js
+++ b/modules/getintentBidAdapter.js
@@ -152,7 +152,7 @@ function getBidFloor(bidRequest, currency) {
       currency: currency || DEFAULT_CURRENCY,
       mediaType: bidRequest.mediaType,
       size: bidRequest.sizes || '*'
-    });
+    }) || {};
   }
 
   return {
diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js
index 8d10a0f18d2..4f3dfb94747 100644
--- a/modules/gridBidAdapter.js
+++ b/modules/gridBidAdapter.js
@@ -7,7 +7,8 @@ import {
   mergeDeep,
   logWarn,
   isNumber,
-  isStr
+  isStr,
+  isPlainObject
 } from '../src/utils.js';
 import { ajax } from '../src/ajax.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
@@ -508,7 +509,7 @@ function _getFloor (mediaTypes, bid) {
       size: bid.sizes.map(([w, h]) => ({w, h}))
     });
 
-    if (typeof floorInfo === 'object' &&
+    if (isPlainObject(floorInfo) &&
       floorInfo.currency === 'USD' &&
       !isNaN(parseFloat(floorInfo.floor))) {
       floor = Math.max(floor, parseFloat(floorInfo.floor));
diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js
index dbfdbef2e91..2911522c89b 100644
--- a/modules/gumgumBidAdapter.js
+++ b/modules/gumgumBidAdapter.js
@@ -233,7 +233,7 @@ function _getFloor(mediaTypes, staticBidFloor, bid) {
     const { currency, floor } = bid.getFloor({
       mediaType: curMediaType,
       size: '*'
-    });
+    }) || {};
     floor && (bidFloor.floor = floor);
     currency && (bidFloor.currency = currency);
 
diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js
index 79522f4a089..fcd9360d973 100644
--- a/modules/impactifyBidAdapter.js
+++ b/modules/impactifyBidAdapter.js
@@ -1,6 +1,6 @@
 'use strict';
 
-import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js';
+import { deepAccess, deepSetValue, generateUUID, isPlainObject } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { config } from '../src/config.js';
 import { ajax } from '../src/ajax.js';
@@ -98,7 +98,7 @@ const helpers = {
       mediaType: '*',
       size: '*'
     });
-    if (typeof floorInfo === 'object' && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
+    if (isPlainObject(floorInfo) && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
       return parseFloat(floorInfo.floor);
     }
     return null;
diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js
index 46ad7dfec71..9416e6a0411 100644
--- a/modules/kargoBidAdapter.js
+++ b/modules/kargoBidAdapter.js
@@ -1,4 +1,4 @@
-import { _each, isEmpty, buildUrl, deepAccess, pick, logError } from '../src/utils.js';
+import { _each, isEmpty, buildUrl, deepAccess, pick, logError, isPlainObject } from '../src/utils.js';
 import { config } from '../src/config.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { getStorageManager } from '../src/storageManager.js';
@@ -526,7 +526,7 @@ function getImpression(bid) {
       } catch (e) {
         logError('Kargo: getFloor threw an error: ', e);
       }
-      imp.floor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined;
+      imp.floor = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined;
     }
   }
 
diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js
index 3ef40c8a921..54e70686fcc 100644
--- a/modules/koblerBidAdapter.js
+++ b/modules/koblerBidAdapter.js
@@ -163,7 +163,7 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) {
 function buildOpenRtbImpObject(validBidRequest) {
   const sizes = getSizes(validBidRequest);
   const mainSize = sizes[0];
-  const floorInfo = getFloorInfo(validBidRequest, mainSize);
+  const floorInfo = getFloorInfo(validBidRequest, mainSize) || {};
 
   return {
     id: validBidRequest.bidId,
diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js
index 57cbe6acd07..8ccfa4ab059 100644
--- a/modules/kubientBidAdapter.js
+++ b/modules/kubientBidAdapter.js
@@ -1,4 +1,4 @@
-import { isArray, deepAccess } from '../src/utils.js';
+import { isArray, deepAccess, isPlainObject } from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import { config } from '../src/config.js';
@@ -33,7 +33,7 @@ export const spec = {
         const mediaType = (Object.keys(bid.mediaTypes).length == 1) ? Object.keys(bid.mediaTypes)[0] : '*';
         const sizes = bid.sizes || '*';
         const floorInfo = bid.getFloor({currency: 'USD', mediaType: mediaType, size: sizes});
-        if (typeof floorInfo === 'object' && floorInfo.currency === 'USD') {
+        if (isPlainObject(floorInfo) && floorInfo.currency === 'USD') {
           let floor = parseFloat(floorInfo.floor)
           if (!isNaN(floor) && floor > 0) {
             adSlot.floor = parseFloat(floorInfo.floor);
diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js
index 63a01bfea02..f11d71f3318 100644
--- a/modules/kueezBidAdapter.js
+++ b/modules/kueezBidAdapter.js
@@ -155,7 +155,7 @@ function getFloorPrice(bid, mediaType) {
       currency: MAIN_CURRENCY,
       mediaType: mediaType,
       size: '*'
-    });
+    }) || {};
     floor = floorResult.currency === MAIN_CURRENCY && floorResult.floor ? floorResult.floor : 0;
   }
 
diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js
index dde7c25d9b9..f2d677f14db 100644
--- a/modules/lemmaDigitalBidAdapter.js
+++ b/modules/lemmaDigitalBidAdapter.js
@@ -295,7 +295,7 @@ export var spec = {
       [BANNER, VIDEO].forEach(mediaType => {
         if (impObj.hasOwnProperty(mediaType)) {
           let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' });
-          if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) {
+          if (utils.isPlainObject(floorInfo) && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) {
             let mediaTypeFloor = parseFloat(floorInfo.floor);
             bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor));
           }
diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js
index 447257f97da..63435437967 100755
--- a/modules/luponmediaBidAdapter.js
+++ b/modules/luponmediaBidAdapter.js
@@ -6,6 +6,7 @@ import {
   isArray,
   isEmpty,
   isFn,
+  isPlainObject,
   logError,
   logMessage,
   logWarn,
@@ -320,7 +321,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) {
     } catch (e) {
       logError('LuponMedia: getFloor threw an error: ', e);
     }
-    bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined;
+    bidFloor = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined;
   } else {
     bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor'));
   }
diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js
index 82a25af60d1..1d322a23a77 100644
--- a/modules/marsmediaBidAdapter.js
+++ b/modules/marsmediaBidAdapter.js
@@ -1,6 +1,6 @@
 
 'use strict';
-import { deepAccess, getDNT, parseSizesInput, isArray, getWindowTop, deepSetValue, triggerPixel, getWindowSelf } from '../src/utils.js';
+import { deepAccess, getDNT, parseSizesInput, isArray, getWindowTop, deepSetValue, triggerPixel, getWindowSelf, isPlainObject } from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import {config} from '../src/config.js';
@@ -341,7 +341,7 @@ function MarsmediaAdapter() {
         size: '*'
       });
 
-      if (typeof floorInfo === 'object' &&
+      if (isPlainObject(floorInfo) &&
         floorInfo.currency === 'USD' &&
         !isNaN(parseFloat(floorInfo.floor))) {
         floor = floorInfo.floor;
diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js
index 987b2689f6b..efe9eb90256 100644
--- a/modules/mediakeysBidAdapter.js
+++ b/modules/mediakeysBidAdapter.js
@@ -11,6 +11,7 @@ import {
   isFn,
   isInteger,
   isNumber,
+  isPlainObject,
   isStr,
   logError,
   logWarn,
@@ -139,7 +140,7 @@ function getFloor(bid, mediaType, size = '*') {
     size
   })
 
-  return (!isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) ? floor.floor : false
+  return (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) ? floor.floor : false
 }
 
 /**
diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js
index 0c43b04f53c..5adf3f743a7 100644
--- a/modules/medianetBidAdapter.js
+++ b/modules/medianetBidAdapter.js
@@ -291,7 +291,7 @@ function getBidFloorByType(bidRequest) {
   return floorInfo;
 }
 function setFloorInfo(bidRequest, mediaType, size, floorInfo) {
-  let floor = bidRequest.getFloor({currency: 'USD', mediaType: mediaType, size: size});
+  let floor = bidRequest.getFloor({currency: 'USD', mediaType: mediaType, size: size}) || {};
   if (size.length > 1) floor.size = size;
   floor.mediaType = mediaType;
   floorInfo.push(floor);
diff --git a/modules/mediasniperBidAdapter.js b/modules/mediasniperBidAdapter.js
index 5cf0ceaba18..796a15e1778 100644
--- a/modules/mediasniperBidAdapter.js
+++ b/modules/mediasniperBidAdapter.js
@@ -7,6 +7,7 @@ import {
   isEmpty,
   isFn,
   isNumber,
+  isPlainObject,
   isStr,
   logError,
   logMessage,
@@ -262,7 +263,7 @@ function getFloor(bid, mediaType, size = '*') {
     size,
   });
 
-  return !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY
+  return isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY
     ? floor.floor
     : false;
 }
diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js
index 0069bf052ba..e621e478ce7 100644
--- a/modules/missenaBidAdapter.js
+++ b/modules/missenaBidAdapter.js
@@ -41,7 +41,7 @@ function getFloor(bidRequest) {
     mediaType: BANNER,
   });
 
-  if (!isNaN(bidFloors.floor)) {
+  if (!isNaN(bidFloors?.floor)) {
     return bidFloors;
   }
 }
diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js
index c945619c667..bdd0fa7e054 100644
--- a/modules/nativoBidAdapter.js
+++ b/modules/nativoBidAdapter.js
@@ -609,7 +609,7 @@ export function parseFloorPriceData(bidRequest) {
         size,
       })
       // Save the data and track the sizes
-      mediaTypeFloorPriceData[sizeToString(size)] = priceFloorData.floor
+      mediaTypeFloorPriceData[sizeToString(size)] = priceFloorData?.floor
       sizeOptions.add(size)
     })
     bidRequestFloorPriceData[mediaType] = mediaTypeFloorPriceData
@@ -617,7 +617,7 @@ export function parseFloorPriceData(bidRequest) {
     // Get floor price of current media type with a wildcard size
     const sizeWildcardFloor = getSizeWildcardPrice(bidRequest, mediaType)
     // Save the wildcard floor price if it was retrieved successfully
-    if (sizeWildcardFloor.floor > 0) {
+    if (sizeWildcardFloor?.floor > 0) {
       mediaTypeFloorPriceData['*'] = sizeWildcardFloor.floor
     }
   })
diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js
index 8cf20848675..112f0d68504 100644
--- a/modules/nextMillenniumBidAdapter.js
+++ b/modules/nextMillenniumBidAdapter.js
@@ -419,8 +419,8 @@ function getCurrency(bid = {}) {
 
     if (typeof bid.getFloor === 'function') {
       let floorInfo = bid.getFloor({currency, mediaType, size: '*'});
-      mediaTypes[mediaType].bidfloorcur = floorInfo.currency;
-      mediaTypes[mediaType].bidfloor = floorInfo.floor;
+      mediaTypes[mediaType].bidfloorcur = floorInfo?.currency;
+      mediaTypes[mediaType].bidfloor = floorInfo?.floor;
     } else {
       mediaTypes[mediaType].bidfloorcur = currency;
     };
diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js
index 4865ea3bc9c..7cacac819c1 100644
--- a/modules/nobidBidAdapter.js
+++ b/modules/nobidBidAdapter.js
@@ -231,7 +231,7 @@ function nobidBuildRequests(bids, bidderRequest) {
     return adunits;
   }
   function getFloor (bid) {
-    if (bid && typeof bid.getFloor === 'function' && bid.getFloor().floor) {
+    if (bid && typeof bid.getFloor === 'function' && bid.getFloor()?.floor) {
       return bid.getFloor().floor;
     }
     return null;
diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js
index c33fccdbac9..ecf512f11d3 100644
--- a/modules/oguryBidAdapter.js
+++ b/modules/oguryBidAdapter.js
@@ -1,7 +1,7 @@
 'use strict';
 
 import {BANNER} from '../src/mediaTypes.js';
-import {getWindowSelf, getWindowTop, isFn, logWarn, deepAccess} from '../src/utils.js';
+import {getWindowSelf, getWindowTop, isFn, logWarn, deepAccess, isPlainObject} from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {ajax} from '../src/ajax.js';
 import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js';
@@ -211,7 +211,7 @@ function getFloor(bid) {
     mediaType: 'banner',
     size: '*'
   });
-  return floorResult.currency === 'USD' ? floorResult.floor : 0;
+  return (isPlainObject(floorResult) && floorResult.currency === 'USD') ? floorResult.floor : 0;
 }
 
 function getWindowContext() {
diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js
index 8ddcb2c3980..8bf326b4848 100644
--- a/modules/onetagBidAdapter.js
+++ b/modules/onetagBidAdapter.js
@@ -411,7 +411,7 @@ function getBidFloor(bidRequest, mediaType, sizes) {
         currency: 'EUR',
         mediaType: mediaType || '*',
         size: [size.width, size.height]
-      });
+      }) || {};
       floor.size = deepClone(size);
       if (!floor.floor) { floor.floor = null; }
       priceFloors.push(floor);
diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js
index 396131fd8aa..f8dea0f5a92 100755
--- a/modules/optidigitalBidAdapter.js
+++ b/modules/optidigitalBidAdapter.js
@@ -1,6 +1,6 @@
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER} from '../src/mediaTypes.js';
-import {deepAccess, parseSizesInput} from '../src/utils.js';
+import {deepAccess, isPlainObject, parseSizesInput} from '../src/utils.js';
 import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js';
 
 /**
@@ -247,7 +247,7 @@ function _getFloor (bid, sizes, currency) {
         mediaType: 'banner',
         size: size
       });
-      if (typeof floorInfo === 'object' && floorInfo.currency === CUR && !isNaN(parseFloat(floorInfo.floor))) {
+      if (isPlainObject(floorInfo) && floorInfo.currency === CUR && !isNaN(parseFloat(floorInfo.floor))) {
         floor = parseFloat(floorInfo.floor);
       }
     } catch (err) {}
diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js
index 5ce514a46d7..f626b2485d1 100644
--- a/modules/outbrainBidAdapter.js
+++ b/modules/outbrainBidAdapter.js
@@ -5,7 +5,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import { getStorageManager } from '../src/storageManager.js';
 import {OUTSTREAM} from '../src/video.js';
-import {_map, deepAccess, deepSetValue, logWarn, replaceAuctionPrice, setOnAny, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js';
+import {_map, deepAccess, deepSetValue, logWarn, replaceAuctionPrice, setOnAny, parseGPTSingleSizeArrayToRtbSize, isPlainObject} from '../src/utils.js';
 import {ajax} from '../src/ajax.js';
 import {config} from '../src/config.js';
 import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
@@ -353,7 +353,7 @@ function _getFloor(bid, type) {
     mediaType: type,
     size: '*'
   });
-  if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
+  if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
     return parseFloat(floorInfo.floor);
   }
   return null;
diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js
index d3ba7fc0792..d554c0afeda 100644
--- a/modules/ozoneBidAdapter.js
+++ b/modules/ozoneBidAdapter.js
@@ -494,13 +494,13 @@ export const spec = {
     logInfo('getFloorObjectForAuction mediaTypesSizes : ', mediaTypesSizes);
     let ret = {};
     if (mediaTypesSizes.banner) {
-      ret.banner = bidRequestRef.getFloor({mediaType: 'banner', currency: 'USD', size: mediaTypesSizes.banner});
+      ret.banner = bidRequestRef.getFloor({mediaType: 'banner', currency: 'USD', size: mediaTypesSizes.banner}) || {};
     }
     if (mediaTypesSizes.video) {
-      ret.video = bidRequestRef.getFloor({mediaType: 'video', currency: 'USD', size: mediaTypesSizes.video});
+      ret.video = bidRequestRef.getFloor({mediaType: 'video', currency: 'USD', size: mediaTypesSizes.video}) || {};
     }
     if (mediaTypesSizes.native) {
-      ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native});
+      ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native}) || {};
     }
     logInfo('getFloorObjectForAuction returning : ', deepClone(ret));
     return ret;
diff --git a/modules/priceFloors.js b/modules/priceFloors.js
index 5df8f938c3d..54353a15c4e 100644
--- a/modules/priceFloors.js
+++ b/modules/priceFloors.js
@@ -270,6 +270,10 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size:
     }
   }
 
+  if (floorInfo.floorRuleValue === null) {
+    return null;
+  }
+
   if (floorInfo.matchingFloor) {
     return {
       floor: roundUp(floorInfo.matchingFloor, 4),
@@ -467,7 +471,7 @@ function isValidRule(key, floor, numFields, delimiter) {
   if (typeof key !== 'string' || key.split(delimiter).length !== numFields) {
     return false;
   }
-  return typeof floor === 'number';
+  return typeof floor === 'number' || floor === null;
 }
 
 function validateRules(floorsData, numFields, delimiter) {
@@ -827,7 +831,7 @@ export function setOrtbImpBidFloor(imp, bidRequest, context) {
         currency: context.currency || config.getConfig('currency.adServerCurrency') || 'USD',
         mediaType: context.mediaType || '*',
         size: '*'
-      }));
+      }) || {});
     } catch (e) {
       logWarn('Cannot compute floor for bid', bidRequest);
       return;
diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js
index 617123746e5..19260e65e60 100644
--- a/modules/pubgeniusBidAdapter.js
+++ b/modules/pubgeniusBidAdapter.js
@@ -194,7 +194,7 @@ function buildImp(bid) {
       mediaType: bid.mediaTypes.banner ? 'banner' : 'video',
       size: '*',
       currency: 'USD',
-    });
+    }) || {};
 
     if (floor) {
       imp.bidfloor = floor;
diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index 8952d8d1852..ab83cfdf88a 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -833,7 +833,7 @@ function _addFloorFromFloorModule(impObj, bid) {
         sizesArray.forEach(size => {
           let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: size });
           logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, ' and size:', size, ' is: currency', floorInfo.currency, 'floor', floorInfo.floor);
-          if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) {
+          if (isPlainObject(floorInfo) && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) {
             let mediaTypeFloor = parseFloat(floorInfo.floor);
             logInfo(LOG_WARN_PREFIX, 'floor from floor module:', mediaTypeFloor, 'previous floor value', bidFloor, 'Min:', Math.min(mediaTypeFloor, bidFloor));
             if (bidFloor === -1) {
diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js
index 639a39d4636..df31dde127b 100644
--- a/modules/pubwiseBidAdapter.js
+++ b/modules/pubwiseBidAdapter.js
@@ -1,5 +1,5 @@
 
-import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError } from '../src/utils.js';
+import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError, isPlainObject } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
 import { config } from '../src/config.js';
@@ -667,7 +667,7 @@ function _addFloorFromFloorModule(impObj, bid) {
     [BANNER, VIDEO, NATIVE].forEach(mediaType => {
       if (impObj.hasOwnProperty(mediaType)) {
         let floorInfo = bid.getFloor({ currency: impObj.bidFloorCur, mediaType: mediaType, size: '*' });
-        if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) {
+        if (isPlainObject(floorInfo) && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) {
           let mediaTypeFloor = parseFloat(floorInfo.floor);
           bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor))
         }
diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js
index 4ff51aeb43e..da3153c0b68 100644
--- a/modules/readpeakBidAdapter.js
+++ b/modules/readpeakBidAdapter.js
@@ -136,7 +136,7 @@ function impression(slot) {
       mediaType: 'native',
       size: '\*'
     });
-    bidFloorFromModule = floorInfo.currency === 'USD' ? floorInfo.floor : undefined;
+    bidFloorFromModule = floorInfo?.currency === 'USD' ? floorInfo?.floor : undefined;
   }
   const imp = {
     id: slot.bidId,
diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js
index 1fe4b4d750c..4f1ebafdf9c 100644
--- a/modules/resetdigitalBidAdapter.js
+++ b/modules/resetdigitalBidAdapter.js
@@ -1,4 +1,4 @@
-import { timestamp, deepAccess, isStr, deepClone } from '../src/utils.js';
+import { timestamp, deepAccess, isStr, deepClone, isPlainObject } from '../src/utils.js';
 import { getOrigin } from '../libraries/getOrigin/index.js';
 import { config } from '../src/config.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
@@ -92,7 +92,7 @@ export const spec = {
           size: '*',
         });
         if (
-          typeof floorInfo === 'object' &&
+          isPlainObject(floorInfo) &&
           floorInfo.currency === CURRENCY &&
           !isNaN(parseFloat(floorInfo.floor))
         ) {
diff --git a/modules/revcontentBidAdapter.js b/modules/revcontentBidAdapter.js
index f1d5521f780..ce04e3aa822 100644
--- a/modules/revcontentBidAdapter.js
+++ b/modules/revcontentBidAdapter.js
@@ -207,7 +207,7 @@ function buildImp(bid, id) {
       currency: 'USD',
       mediaType: '*',
       size: '*'
-    }).floor;
+    })?.floor;
   } else {
     bidfloor = deepAccess(bid, `params.bidfloor`) || 0.1;
   }
diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js
index bcc076f1781..74a4df14f6f 100644
--- a/modules/rtbhouseBidAdapter.js
+++ b/modules/rtbhouseBidAdapter.js
@@ -215,7 +215,7 @@ function applyFloor(slot) {
   if (typeof slot.getFloor === 'function') {
     Object.keys(slot.mediaTypes).forEach(type => {
       if (includes(SUPPORTED_MEDIA_TYPES, type)) {
-        floors.push(slot.getFloor({ currency: DEFAULT_CURRENCY_ARR[0], mediaType: type, size: slot.sizes || '*' }).floor);
+        floors.push(slot.getFloor({ currency: DEFAULT_CURRENCY_ARR[0], mediaType: type, size: slot.sizes || '*' })?.floor);
       }
     });
   }
diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index 7bcc7bbd69b..411e194b1ee 100644
--- a/modules/rubiconBidAdapter.js
+++ b/modules/rubiconBidAdapter.js
@@ -18,7 +18,8 @@ import {
   mergeDeep,
   parseSizesInput,
   pick,
-  _each
+  _each,
+  isPlainObject
 } from '../src/utils.js';
 import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js';
 import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js';
@@ -520,7 +521,7 @@ export const spec = {
       } catch (e) {
         logError('Rubicon: getFloor threw an error: ', e);
       }
-      data['rp_hard_floor'] = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined;
+      data['rp_hard_floor'] = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined;
     }
 
     // Send multiformat data if requested
diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js
index 4cde02c690e..ad579f1cdbf 100644
--- a/modules/seedtagBidAdapter.js
+++ b/modules/seedtagBidAdapter.js
@@ -47,7 +47,7 @@ function getBidFloor(bidRequest) {
     });
   }
 
-  return floorInfo.floor;
+  return floorInfo?.floor;
 }
 
 const getConnectionType = () => {
diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js
index 4096d0320e9..7144370dc9c 100644
--- a/modules/sharethroughBidAdapter.js
+++ b/modules/sharethroughBidAdapter.js
@@ -1,7 +1,7 @@
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { config } from '../src/config.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { deepAccess, generateUUID, inIframe, logWarn, mergeDeep } from '../src/utils.js';
+import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js';
 
 const VERSION = '4.3.0';
 const BIDDER_CODE = 'sharethrough';
@@ -285,7 +285,7 @@ function getBidRequestFloor(bid) {
       mediaType: bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner',
       size: bid.sizes.map((size) => ({ w: size[0], h: size[1] })),
     });
-    if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
+    if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
       floor = parseFloat(floorInfo.floor);
     }
   }
diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js
index 1d5662f88eb..593e613d603 100644
--- a/modules/silverpushBidAdapter.js
+++ b/modules/silverpushBidAdapter.js
@@ -259,7 +259,7 @@ function getBidFloor(bid) {
     mediaType: '*',
     size: '*',
   });
-  return bidFloor.floor;
+  return bidFloor?.floor;
 }
 
 function _renderer(bid) {
diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js
index 5478f9aa6e5..d4e53c00472 100644
--- a/modules/sovrnBidAdapter.js
+++ b/modules/sovrnBidAdapter.js
@@ -373,7 +373,7 @@ function _getBidFloors(bid) {
     mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video',
     size: '*'
   }) : {}
-  const floorModuleValue = parseFloat(floorInfo.floor)
+  const floorModuleValue = parseFloat(floorInfo?.floor)
   if (!isNaN(floorModuleValue)) {
     return floorModuleValue
   }
diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js
index e05c9db6303..ba9af3314b0 100644
--- a/modules/sspBCBidAdapter.js
+++ b/modules/sspBCBidAdapter.js
@@ -261,18 +261,18 @@ const getHighestFloor = (slot) => {
           mediaType: 'banner',
           size: next,
           currency
-        });
+        }) || {};
         return prev > currentFloor ? prev : currentFloor;
       }, 0);
     }
 
     const { floor: nativeFloor = 0 } = slot.getFloor({
       mediaType: 'native', currency
-    });
+    }) || {};
 
     const { floor: videoFloor = 0 } = slot.getFloor({
       mediaType: 'video', currency
-    });
+    }) || {};
 
     result.floor = Math.max(bannerFloor, nativeFloor, videoFloor);
   }
diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js
index 5630cfe006f..5a492a8e5e0 100644
--- a/modules/stroeerCoreBidAdapter.js
+++ b/modules/stroeerCoreBidAdapter.js
@@ -253,14 +253,14 @@ const createFloorPriceObject = (mediaType, sizes, bidRequest) => {
     currency: 'EUR',
     mediaType: mediaType,
     size: '*'
-  });
+  }) || {};
 
   const sizeFloors = sizes.map(size => {
     const floor = bidRequest.getFloor({
       currency: 'EUR',
       mediaType: mediaType,
       size: [size[0], size[1]]
-    });
+    }) || {};
     return {...floor, size};
   });
 
diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js
index 5fa7f2c8b8e..da7559ef144 100644
--- a/modules/taboolaBidAdapter.js
+++ b/modules/taboolaBidAdapter.js
@@ -3,7 +3,7 @@
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER} from '../src/mediaTypes.js';
 import {config} from '../src/config.js';
-import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse} from '../src/utils.js';
+import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject} from '../src/utils.js';
 import {getStorageManager} from '../src/storageManager.js';
 import {ajax} from '../src/ajax.js';
 import {ortbConverter} from '../libraries/ortbConverter/converter.js';
@@ -338,7 +338,7 @@ function fillTaboolaImpData(bid, imp) {
       currency: CURRENCY,
       size: '*'
     });
-    if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
+    if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) {
       imp.bidfloor = parseFloat(floorInfo.floor);
       imp.bidfloorcur = CURRENCY;
     }
diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js
index a665de6140f..0c8bd330e11 100644
--- a/modules/tripleliftBidAdapter.js
+++ b/modules/tripleliftBidAdapter.js
@@ -255,7 +255,7 @@ function _getFloor (bid) {
         mediaType: _isVideoBidRequest(bid) ? 'video' : 'banner',
         size: '*'
       });
-      if (typeof floorInfo === 'object' &&
+      if (utils.isPlainObject(floorInfo) &&
       floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
         floor = parseFloat(floorInfo.floor);
       }
diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js
index 19b933a8666..d5017db0705 100644
--- a/modules/ucfunnelBidAdapter.js
+++ b/modules/ucfunnelBidAdapter.js
@@ -235,7 +235,7 @@ function getFloor(bid, size, mediaTypes) {
       mediaType: getMediaType(mediaTypes),
       size: (size) ? [ size[0], size[1] ] : '*',
     });
-    if (bidFloor.currency === CURRENCY) {
+    if (bidFloor?.currency === CURRENCY) {
       return bidFloor.floor;
     }
   }
diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js
index 39d77c81b57..53e6629d8cc 100644
--- a/modules/unrulyBidAdapter.js
+++ b/modules/unrulyBidAdapter.js
@@ -31,7 +31,7 @@ const addBidFloorInfo = (validBid) => {
         currency: 'USD',
         mediaType: key,
         size: '*'
-      }).floor || 0;
+      })?.floor || 0;
     } else {
       floor = validBid.params.floor || 0;
     }
diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js
index b62474d0c25..c34d3ecb097 100644
--- a/modules/videobyteBidAdapter.js
+++ b/modules/videobyteBidAdapter.js
@@ -214,8 +214,8 @@ function buildRequestData(bidRequest, bidderRequest) {
         id: '1',
         video: video,
         secure: isSecure() ? 1 : 0,
-        bidfloor: floorData.floor,
-        bidfloorcur: floorData.currency
+        bidfloor: floorData?.floor,
+        bidfloorcur: floorData?.currency
       }
     ],
     site: {
diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js
index c9ac9fae0f4..9f341b42ff5 100644
--- a/modules/vidoomyBidAdapter.js
+++ b/modules/vidoomyBidAdapter.js
@@ -1,4 +1,4 @@
-import {deepAccess, logError, parseSizesInput} from '../src/utils.js';
+import {deepAccess, isPlainObject, logError, parseSizesInput} from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {config} from '../src/config.js';
@@ -88,7 +88,7 @@ function getBidFloor(bid, mediaType, sizes, bidfloor) {
   var size = sizes && sizes.length > 0 ? sizes[0] : '*';
   if (typeof bid.getFloor === 'function') {
     const floorInfo = bid.getFloor({currency: 'USD', mediaType, size});
-    if (typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
+    if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
       floor = Math.max(bidfloor, parseFloat(floorInfo.floor));
     }
   }
diff --git a/modules/yahooAdsBidAdapter.js b/modules/yahooAdsBidAdapter.js
index eec5420ec6c..7622a5b7587 100644
--- a/modules/yahooAdsBidAdapter.js
+++ b/modules/yahooAdsBidAdapter.js
@@ -346,7 +346,7 @@ function appendImpObject(bid, openRtbObject) {
     const impObject = {
       id: bid.bidId,
       secure: isSecure(bid),
-      bidfloor: getFloorModuleData(bid).floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor')
+      bidfloor: getFloorModuleData(bid)?.floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor')
     };
 
     if (bid.mediaTypes.banner && (typeof mediaTypeMode === 'undefined' || mediaTypeMode === BANNER || mediaTypeMode === '*')) {
diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js
index 67a34ae4870..9be01096084 100644
--- a/modules/yieldlabBidAdapter.js
+++ b/modules/yieldlabBidAdapter.js
@@ -555,7 +555,7 @@ function getBidFloor(bid, sizes) {
     mediaType: mediaType !== undefined && spec.supportedMediaTypes.includes(mediaType) ? mediaType : '*',
     size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN),
   });
-  if (floor.currency === CURRENCY_CODE) {
+  if (floor?.currency === CURRENCY_CODE) {
     return (floor.floor * 100).toFixed(0);
   }
   return undefined;
diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js
index ceb3446b570..41f1609f74f 100644
--- a/test/spec/modules/priceFloors_spec.js
+++ b/test/spec/modules/priceFloors_spec.js
@@ -2401,3 +2401,87 @@ describe('the price floors module', function () {
     })
   });
 });
+
+describe('setting null as rule value', () => {
+  const nullFloorData = {
+    modelVersion: 'basic model',
+    modelWeight: 10,
+    modelTimestamp: 1606772895,
+    currency: 'USD',
+    schema: {
+      delimiter: '|',
+      fields: ['mediaType', 'size']
+    },
+    values: {
+      'banner|600x300': null,
+    }
+  };
+
+  const basicBidRequest = {
+    bidder: 'rubicon',
+    adUnitCode: 'test_div_1',
+    auctionId: '1234-56-789',
+    transactionId: 'tr_test_div_1',
+    adUnitId: 'tr_test_div_1',
+  };
+
+  it('should validate for null values', function () {
+    let data = utils.deepClone(nullFloorData);
+    data.floorsSchemaVersion = 1;
+    expect(isFloorsDataValid(data)).to.to.equal(true);
+  });
+
+  it('getFloor should not return numeric value if null set as value', function () {
+    const bidRequest = { ...basicBidRequest, getFloor };
+    const basicFloorConfig = {
+      enabled: true,
+      auctionDelay: 0,
+      endpoint: {},
+      enforcement: {
+        enforceJS: true,
+        enforcePBS: false,
+        floorDeals: false,
+        bidAdjustment: true
+      },
+      data: nullFloorData
+    }
+    _floorDataForAuction[bidRequest.auctionId] = basicFloorConfig;
+
+    let inputParams = {mediaType: 'banner', size: [600, 300]};
+    expect(bidRequest.getFloor(inputParams)).to.deep.equal(null);
+  })
+
+  it('getFloor should not return numeric value if null set as value - external floor provider', function () {
+    const basicFloorConfig = {
+      enabled: true,
+      auctionDelay: 0,
+      endpoint: {},
+      enforcement: {
+        enforceJS: true,
+        enforcePBS: false,
+        floorDeals: false,
+        bidAdjustment: true
+      },
+      data: nullFloorData
+    }
+    server.respondWith(JSON.stringify(nullFloorData));
+    let exposedAdUnits;
+
+    handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', endpoint: {url: 'http://www.fakefloorprovider.json/'}});
+
+    const adUnits = [{
+      cod: 'test_div_1',
+      mediaTypes: {banner: { sizes: [[600, 300]] }, native: {}},
+      bids: [{bidder: 'someBidder', adUnitCode: 'test_div_1'}, {bidder: 'someOtherBidder', adUnitCode: 'test_div_1'}]
+    }];
+
+    requestBidsHook(config => exposedAdUnits = config.adUnits, {
+      auctionId: basicBidRequest.auctionId,
+      adUnits
+    });
+
+    let inputParams = {mediaType: 'banner', size: [600, 300]};
+
+    expect(exposedAdUnits[0].bids[0].getFloor(inputParams)).to.deep.equal(null);
+  });
+})

From 0175c366bee715bf26d13ce17f640b7e82c3f72f Mon Sep 17 00:00:00 2001
From: vivekyadav15 
Date: Tue, 3 Dec 2024 21:34:46 +0530
Subject: [PATCH 0719/1097] Medianet Analytics Adapter: ADD bid properties in
 logs and small fix in bidTimeoutHandler (#12526)

---
 modules/medianetAnalyticsAdapter.js           | 116 +++++++++++++++---
 .../modules/medianetAnalyticsAdapter_spec.js  |  18 +--
 2 files changed, 105 insertions(+), 29 deletions(-)

diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js
index e8d73e77d5f..1b4ccc45c88 100644
--- a/modules/medianetAnalyticsAdapter.js
+++ b/modules/medianetAnalyticsAdapter.js
@@ -8,7 +8,8 @@ import {
   logError,
   logInfo,
   triggerPixel,
-  uniques
+  uniques,
+  deepSetValue
 } from '../src/utils.js';
 import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
 import adapterManager from '../src/adapterManager.js';
@@ -18,6 +19,7 @@ import {getRefererInfo} from '../src/refererDetection.js';
 import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js';
 import {includes} from '../src/polyfill.js';
 import {getGlobal} from '../src/prebidGlobal.js';
+import {convertCurrency} from '../libraries/currencyUtils/currency.js';
 
 const analyticsType = 'endpoint';
 const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client';
@@ -112,6 +114,7 @@ class Configure {
       pbv: PREBID_VERSION,
       pbav: ANALYTICS_VERSION,
       flt: 1,
+      enableDbf: 1
     }
   }
 
@@ -303,21 +306,17 @@ class BidWrapper {
     this.bidObjs.push(bidObj);
   }
 
-  getAdSlotBids(adSlot) {
-    const bidResponses = this.getAdSlotBidObjs(adSlot);
-    return bidResponses.map((bid) => bid.getLoggingData());
+  getAdSlotBidRequests(adSlot) {
+    return this.bidReqs.filter((bid) => bid.adUnitCode === adSlot);
   }
 
-  getAdSlotBidObjs(adSlot) {
-    const bidResponses = this.bidObjs
-      .filter((bid) => bid.adUnitCode === adSlot);
-    const remResponses = this.bidReqs.filter(bid => !bid.used && bid.adUnitCode === adSlot);
-    return [...bidResponses, ...remResponses];
+  getAdSlotBidResponses(adSlot) {
+    return this.bidObjs.filter((bid) => bid.adUnitCode === adSlot);
   }
 }
 
 class Bid {
-  constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes) {
+  constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, resSizes) {
     this.bidId = bidId;
     this.bidder = bidder;
     this.src = src;
@@ -348,6 +347,14 @@ class Bid {
     this.used = false;
     this.originalRequestId = bidId;
     this.requestId = undefined;
+    this.advUrl = undefined;
+    this.latestAcid = undefined;
+    this.originalCurrency = undefined;
+    this.currMul = undefined;
+    this.inCurrMul = undefined;
+    this.res_mtype = undefined;
+    this.res_sizes = resSizes;
+    this.req_mtype = mediaType;
   }
 
   get size() {
@@ -374,7 +381,7 @@ class Bid {
       cbdp: this.dfpbd,
       dfpbd: this.dfpbd,
       szs: this.allMediaTypeSizes.join('|'),
-      size: this.size,
+      size: (this.res_sizes || [this.size]).join('|'),
       mtype: this.mediaType,
       dId: this.dealId,
       winner: this.winner,
@@ -389,6 +396,13 @@ class Bid {
       flrrule: this.floorRule,
       ext: JSON.stringify(this.ext),
       rtime: this.serverLatencyMillis,
+      advurl: this.advUrl,
+      lacid: this.latestAcid,
+      icurr: this.originalCurrency,
+      imul: this.inCurrMul,
+      omul: this.currMul,
+      res_mtype: this.res_mtype,
+      req_mtype: this.req_mtype
     }
   }
 }
@@ -454,11 +468,12 @@ class Auction {
     return this.bidWrapper.findBidObj(key, value)
   }
 
-  getAdSlotBids(adSlot) {
-    return this.bidWrapper.getAdSlotBids(adSlot);
+  getAdSlotBidRequests(adSlot) {
+    return this.bidWrapper.getAdSlotBidRequests(adSlot);
   }
-  getAdSlotBidObjs(adSlot) {
-    return this.bidWrapper.getAdSlotBidObjs(adSlot);
+
+  getAdSlotBidResponses(adSlot) {
+    return this.bidWrapper.getAdSlotBidResponses(adSlot);
   }
 
   _mergeFieldsToLog(objParams) {
@@ -558,8 +573,8 @@ function _getSizes(mediaTypes, sizes) {
 }
 
 function bidResponseHandler(bid) {
-  const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder } = bid;
-  const {originalCpm, creativeId, adId, currency} = bid;
+  const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder, meta } = bid;
+  let {originalCpm, creativeId, adId, currency, originalCurrency} = bid;
 
   if (!(auctions[auctionId] instanceof Auction)) {
     return;
@@ -575,13 +590,26 @@ function bidResponseHandler(bid) {
     bidObj = {};
     isBidOverridden = false;
   }
+  currency = currency ? currency.toUpperCase() : '';
+  originalCurrency = originalCurrency ? originalCurrency.toUpperCase() : currency;
   Object.assign(bidObj, bidReq,
     { cpm, width, height, mediaType, timeToRespond, dealId, creativeId, originalRequestId, requestId },
-    { adId, currency }
+    { adId, currency, originalCurrency }
   );
   bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue');
   bidObj.floorRule = deepAccess(bid, 'floorData.floorRule');
   bidObj.originalCpm = originalCpm || cpm;
+  bidObj.advUrl = meta && meta.advertiserDomains && meta.advertiserDomains.join(',');
+  bidObj.currMul = 1;
+  bidObj.inCurrMul = 1;
+  if (bidObj.originalCurrency !== 'USD') {
+    bidObj.originalCpm = exchangeCurrency(bidObj.originalCpm, bidObj.originalCurrency, 'USD');
+    bidObj.inCurrMul = exchangeCurrency(1, 'USD', bidObj.originalCurrency)
+  }
+  if (bidObj.currency !== 'USD') {
+    bidObj.cpm = exchangeCurrency(bidObj.cpm, bidObj.currency, 'USD');
+    bidObj.currMul = exchangeCurrency(1, 'USD', bidObj.currency)
+  }
   let dfpbd = deepAccess(bid, 'adserverTargeting.hb_pb');
   if (!dfpbd) {
     let priceGranularity = getPriceGranularity(bid);
@@ -606,9 +634,19 @@ function bidResponseHandler(bid) {
   if (typeof bid.serverResponseTimeMs !== 'undefined') {
     bidObj.serverLatencyMillis = bid.serverResponseTimeMs;
   }
+  bidObj.res_mtype = mediaType;
   !isBidOverridden && auctions[auctionId].addBidObj(bidObj);
 }
 
+function exchangeCurrency(price, fromCurrency, toCurrency) {
+  try {
+    return convertCurrency(price, fromCurrency, toCurrency, false).toFixed(4)
+  } catch (e) {
+    logError(`Media.net Analytics Adapter: Could not convert ${fromCurrency} to ${toCurrency} for price ${price}`);
+  }
+  return price;
+}
+
 function noBidResponseHandler({ auctionId, bidId }) {
   if (!(auctions[auctionId] instanceof Auction)) {
     return;
@@ -630,7 +668,7 @@ function bidTimeoutHandler(timedOutBids) {
     if (!(auctions[auctionId] instanceof Auction)) {
       return;
     }
-    const bidReq = auctions[auctionId].findReqBid('bidId', bidId);
+    const bidReq = auctions[auctionId].findReqBid(bidId);
     if (!(bidReq instanceof Bid) || bidReq.used) {
       return;
     }
@@ -710,6 +748,7 @@ function bidWonHandler(bid) {
     }).send();
     return;
   }
+  bidObj.latestAcid = bid.latestTargetedAuctionId;
   auctions[auctionId].bidWonTime = Date.now();
   bidObj.winner = 1;
   sendEvent(auctionId, adUnitCode, LOG_TYPE.RA, bidObj.adId);
@@ -749,6 +788,43 @@ function getCommonLoggingData(acid, adtag) {
   return Object.assign(commonParams, adunitParams, auctionParams);
 }
 
+function getResponseSizeMap(acid, adtag) {
+  const responses = auctions[acid].getAdSlotBidResponses(adtag);
+  const receivedResponse = {};
+  // Set true in map for success bids
+  responses.filter((bid) => bid.size !== '')
+    .forEach((bid) => deepSetValue(receivedResponse, `${bid.bidId}.${bid.size}`, true));
+
+  // For non-success bids:
+  // 1) set bid.res_sizes = (sizes for which no successful bid received)
+  // 2) set true in map
+  responses.filter((bid) => bid.size === '').forEach((bid) => {
+    bid.res_sizes = bid.allMediaTypeSizes.filter((size) => !deepAccess(receivedResponse, `${bid.bidId}.${size}`));
+    bid.allMediaTypeSizes.forEach((size) => deepSetValue(receivedResponse, `${bid.bidId}.${size}`, true));
+  });
+  return receivedResponse;
+}
+
+function getDummyBids(acid, adtag, receivedResponse) {
+  const emptyBids = [];
+  auctions[acid].getAdSlotBidRequests(adtag).forEach(({ bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, status }) => {
+    const emptySizes = allMediaTypeSizes.filter((size) => !deepAccess(receivedResponse, `${bidId}.${size}`));
+    if (bidder !== DUMMY_BIDDER && emptySizes.length > 0) {
+      const bid = new Bid(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, emptySizes);
+      bid.status = status === BID_SUCCESS ? BID_NOBID : status;
+      emptyBids.push(bid);
+    }
+  });
+  return emptyBids;
+}
+
+function getLoggingBids(acid, adtag) {
+  const receivedResponse = getResponseSizeMap(acid, adtag);
+  const dummyBids = getDummyBids(acid, adtag, receivedResponse);
+
+  return [...auctions[acid].getAdSlotBidResponses(adtag), ...dummyBids];
+}
+
 function fireAuctionLog(acid, adtag, logType, adId) {
   let commonParams = getCommonLoggingData(acid, adtag);
   commonParams.lgtp = logType;
@@ -766,7 +842,7 @@ function fireAuctionLog(acid, adtag, logType, adId) {
     bidParams = [winLogData];
     commonParams.lper = 1;
   } else {
-    bidParams = auctions[acid].getAdSlotBids(adtag).map(({winner, ...restParams}) => restParams);
+    bidParams = getLoggingBids(acid, adtag).map((bid) => bid.getLoggingData())
     delete commonParams.wts;
   }
   let mnetPresent = bidParams.filter(b => b.pvnm === MEDIANET_BIDDER_CODE).length > 0;
diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js
index 7c3cf88dace..8a298acae80 100644
--- a/test/spec/modules/medianetAnalyticsAdapter_spec.js
+++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js
@@ -210,7 +210,7 @@ describe('Media.net Analytics Adapter', function() {
       expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'bidder1', 'bidder1', 'medianet']);
       expect(noBidLog.mtype).to.have.ordered.members([banner, banner, banner, banner]);
       expect(noBidLog.status).to.have.ordered.members(['1', '2', '2', '2']);
-      expect(noBidLog.size).to.have.ordered.members(['', '', '', '']);
+      expect(noBidLog.size).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']);
       expect(noBidLog.szs).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']);
     });
 
@@ -225,7 +225,7 @@ describe('Media.net Analytics Adapter', function() {
     it('should have winner log in standard auction', function() {
       medianetAnalytics.clearlogsQueue();
       performStandardAuctionWithWinner();
-      let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner);
+      let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1');
       medianetAnalytics.clearlogsQueue();
 
       expect(winnerLog.length).to.equal(1);
@@ -234,7 +234,7 @@ describe('Media.net Analytics Adapter', function() {
     it('should have correct values in winner log', function() {
       medianetAnalytics.clearlogsQueue();
       performStandardAuctionWithWinner();
-      let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner);
+      let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1');
       medianetAnalytics.clearlogsQueue();
 
       expect(winnerLog[0]).to.include({
@@ -258,7 +258,7 @@ describe('Media.net Analytics Adapter', function() {
     it('should have correct bid floor data in winner log', function() {
       medianetAnalytics.clearlogsQueue();
       performAuctionWithFloorConfig();
-      let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner);
+      let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1');
       medianetAnalytics.clearlogsQueue();
 
       expect(winnerLog[0]).to.include({
@@ -309,32 +309,32 @@ describe('Media.net Analytics Adapter', function() {
 
     it('should pick winning bid if multibids with same request id', function() {
       performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM);
-      let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0];
+      let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0];
       expect(winningBid.adid).equals('3e6e4bce5c8fb3');
       medianetAnalytics.clearlogsQueue();
 
       const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_DIFF_CPM).reverse();
       performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray);
-      winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0];
+      winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0];
       expect(winningBid.adid).equals('3e6e4bce5c8fb3');
     });
 
     it('should pick winning bid if multibids with same request id and same time to respond', function() {
       performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM_SAME_TIME);
-      let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0];
+      let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0];
       expect(winningBid.adid).equals('3e6e4bce5c8fb3');
       medianetAnalytics.clearlogsQueue();
     });
 
     it('should pick winning bid if multibids with same request id and equal cpm', function() {
       performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM);
-      let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0];
+      let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0];
       expect(winningBid.adid).equals('3e6e4bce5c8fb3');
       medianetAnalytics.clearlogsQueue();
 
       const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_EQUAL_CPM).reverse();
       performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray);
-      winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0];
+      winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0];
       expect(winningBid.adid).equals('3e6e4bce5c8fb3');
     });
 

From dfd19325c119eb8114829a7938a299dead0dc003 Mon Sep 17 00:00:00 2001
From: Demetrio Girardi 
Date: Tue, 3 Dec 2024 08:05:21 -0800
Subject: [PATCH 0720/1097] Core: fix bug where adRenderSucceeded event
 payloads are sometimes missing adId (#12530)

---
 src/adRendering.js              |  4 ++--
 test/spec/unit/pbjs_api_spec.js | 12 +++++++++++-
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/adRendering.js b/src/adRendering.js
index 4b1f2960428..dfed547ee39 100644
--- a/src/adRendering.js
+++ b/src/adRendering.js
@@ -255,7 +255,7 @@ export function renderAdDirect(doc, adId, options) {
     if (adData.ad) {
       doc.write(adData.ad);
       doc.close();
-      emitAdRenderSucceeded({doc, bid, adId: bid.adId});
+      emitAdRenderSucceeded({doc, bid, id: bid.adId});
     } else {
       getCreativeRenderer(bid)
         .then(render => render(adData, {
@@ -263,7 +263,7 @@ export function renderAdDirect(doc, adId, options) {
           mkFrame: createIframe,
         }, doc.defaultView))
         .then(
-          () => emitAdRenderSucceeded({doc, bid, adId: bid.adId}),
+          () => emitAdRenderSucceeded({doc, bid, id: bid.adId}),
           (e) => {
             fail(e?.reason || AD_RENDER_FAILED_REASON.EXCEPTION, e?.message)
             e?.stack && logError(e);
diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js
index ac8978907a9..e56f3256569 100644
--- a/test/spec/unit/pbjs_api_spec.js
+++ b/test/spec/unit/pbjs_api_spec.js
@@ -28,7 +28,7 @@ import {generateUUID} from '../../../src/utils.js';
 import {getCreativeRenderer} from '../../../src/creativeRenderers.js';
 import {BID_STATUS, EVENTS, GRANULARITY_OPTIONS, PB_LOCATOR, TARGETING_KEYS} from 'src/constants.js';
 import {getBidToRender} from '../../../src/adRendering.js';
-import { setBattrForAdUnit } from '../../../src/prebid.js';
+import {setBattrForAdUnit} from '../../../src/prebid.js';
 
 var assert = require('chai').assert;
 var expect = require('chai').expect;
@@ -1319,6 +1319,16 @@ describe('Unit: Prebid Module', function () {
       });
     });
 
+    it('should emit AD_RENDER_SUCCEEDED', () => {
+      sandbox.stub(events, 'emit');
+      pushBidResponseToAuction({
+        ad: ""
+      });
+      return renderAd(document, bidId).then(() => {
+        sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_SUCCEEDED, sinon.match({adId: bidId}));
+      });
+    });
+
     it('should not render videos', function () {
       pushBidResponseToAuction({
         mediatype: 'video'

From a8dccf5e68053f2589f5207f90c4802eb4dba979 Mon Sep 17 00:00:00 2001
From: Gena 
Date: Tue, 3 Dec 2024 17:08:59 +0100
Subject: [PATCH 0721/1097] Add adtarget gvlid (#12531)

---
 modules/adtargetBidAdapter.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js
index a1dec5a420f..0faf740ac54 100644
--- a/modules/adtargetBidAdapter.js
+++ b/modules/adtargetBidAdapter.js
@@ -12,6 +12,7 @@ const syncsCache = {};
 
 export const spec = {
   code: BIDDER_CODE,
+  gvlid: 779,
   supportedMediaTypes: [VIDEO, BANNER],
   isBidRequestValid: function (bid) {
     return !!deepAccess(bid, 'params.aid');

From a7fb4add867dc58becd9c89bdba12ab7e9961686 Mon Sep 17 00:00:00 2001
From: mkomorski 
Date: Tue, 3 Dec 2024 17:18:45 +0100
Subject: [PATCH 0722/1097] Currency Module: Adding auction delay handling
 (#12364)

* Delay auction param on currency module

* hookConfig change

* test improvement

* review fixes

* introducing timeoutQueue

* fix

---------

Co-authored-by: Marcin Komorski 
---
 libraries/timeoutQueue/timeoutQueue.js | 22 ++++++++++
 modules/currency.js                    | 26 +++++++++++-
 modules/priceFloors.js                 | 37 +++++------------
 test/spec/modules/currency_spec.js     | 56 +++++++++++++++++++++++++-
 4 files changed, 110 insertions(+), 31 deletions(-)
 create mode 100644 libraries/timeoutQueue/timeoutQueue.js

diff --git a/libraries/timeoutQueue/timeoutQueue.js b/libraries/timeoutQueue/timeoutQueue.js
new file mode 100644
index 00000000000..5046eed150b
--- /dev/null
+++ b/libraries/timeoutQueue/timeoutQueue.js
@@ -0,0 +1,22 @@
+export function timeoutQueue() {
+  const queue = [];
+  return {
+    submit(timeout, onResume, onTimeout) {
+      const item = [
+        onResume,
+        setTimeout(() => {
+          queue.splice(queue.indexOf(item), 1);
+          onTimeout();
+        }, timeout)
+      ];
+      queue.push(item);
+    },
+    resume() {
+      while (queue.length) {
+        const [onResume, timerId] = queue.shift();
+        clearTimeout(timerId);
+        onResume();
+      }
+    }
+  }
+}
diff --git a/modules/currency.js b/modules/currency.js
index 8ac2b8cbead..d040dc2cf49 100644
--- a/modules/currency.js
+++ b/modules/currency.js
@@ -6,11 +6,13 @@ import {config} from '../src/config.js';
 import {getHook} from '../src/hook.js';
 import {defer} from '../src/utils/promise.js';
 import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js';
-import {timedBidResponseHook} from '../src/utils/perfMetrics.js';
+import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js';
 import {on as onEvent, off as offEvent} from '../src/events.js';
+import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js';
 
 const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$';
 const CURRENCY_RATE_PRECISION = 4;
+const MODULE_NAME = 'currency';
 
 let ratesURL;
 let bidResponseQueue = [];
@@ -26,6 +28,9 @@ let defaultRates;
 
 export let responseReady = defer();
 
+const delayedAuctions = timeoutQueue();
+let auctionDelay = 0;
+
 /**
  * Configuration function for currency
  * @param {object} config
@@ -77,6 +82,7 @@ export function setConfig(config) {
   }
 
   if (typeof config.adServerCurrency === 'string') {
+    auctionDelay = config.auctionDelay;
     logInfo('enabling currency support', arguments);
 
     adServerCurrency = config.adServerCurrency;
@@ -106,6 +112,7 @@ export function setConfig(config) {
     initCurrency();
   } else {
     // currency support is disabled, setting defaults
+    auctionDelay = 0;
     logInfo('disabling currency support');
     resetCurrency();
   }
@@ -137,6 +144,7 @@ function loadRates() {
             conversionCache = {};
             currencyRatesLoaded = true;
             processBidResponseQueue();
+            delayedAuctions.resume();
           } catch (e) {
             errorSettingsRates('Failed to parse currencyRates response: ' + response);
           }
@@ -145,6 +153,7 @@ function loadRates() {
           errorSettingsRates(...args);
           currencyRatesLoaded = true;
           processBidResponseQueue();
+          delayedAuctions.resume();
           needToCallForCurrencyFile = true;
         }
       }
@@ -162,6 +171,7 @@ function initCurrency() {
     getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency);
     getHook('addBidResponse').before(addBidResponseHook, 100);
     getHook('responsesReady').before(responsesReadyHook);
+    getHook('requestBids').before(requestBidsHook, 50);
     onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout);
     onEvent(EVENTS.AUCTION_INIT, loadRates);
     loadRates();
@@ -172,6 +182,7 @@ function resetCurrency() {
   if (currencySupportEnabled) {
     getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove();
     getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove();
+    getHook('requestBids').getHooks({hook: requestBidsHook}).remove();
     offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout);
     offEvent(EVENTS.AUCTION_INIT, loadRates);
     delete getGlobal().convertCurrency;
@@ -335,3 +346,16 @@ export function setOrtbCurrency(ortbRequest, bidderRequest, context) {
 }
 
 registerOrtbProcessor({type: REQUEST, name: 'currency', fn: setOrtbCurrency});
+
+export const requestBidsHook = timedAuctionHook('currency', function requestBidsHook(fn, reqBidsConfigObj) {
+  const continueAuction = ((that) => () => fn.call(that, reqBidsConfigObj))(this);
+
+  if (!currencyRatesLoaded && auctionDelay > 0) {
+    delayedAuctions.submit(auctionDelay, continueAuction, () => {
+      logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction ${reqBidsConfigObj.auctionId}`)
+      continueAuction();
+    });
+  } else {
+    continueAuction();
+  }
+});
diff --git a/modules/priceFloors.js b/modules/priceFloors.js
index 54353a15c4e..d14a82af360 100644
--- a/modules/priceFloors.js
+++ b/modules/priceFloors.js
@@ -3,7 +3,6 @@ import {
   deepAccess,
   deepClone,
   deepSetValue,
-  generateUUID,
   getParameterByName,
   isNumber,
   logError,
@@ -13,7 +12,8 @@ import {
   parseGPTSingleSizeArray,
   parseUrl,
   pick,
-  deepEqual
+  deepEqual,
+  generateUUID
 } from '../src/utils.js';
 import {getGlobal} from '../src/prebidGlobal.js';
 import {config} from '../src/config.js';
@@ -30,6 +30,7 @@ import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.j
 import {adjustCpm} from '../src/utils/cpm.js';
 import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
 import {convertCurrency} from '../libraries/currencyUtils/currency.js';
+import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js';
 
 export const FLOOR_SKIPPED_REASON = {
   NOT_FOUND: 'not_found',
@@ -72,7 +73,7 @@ let _floorsConfig = {};
 /**
  * @summary If a auction is to be delayed by an ongoing fetch we hold it here until it can be resumed
  */
-let _delayedAuctions = [];
+const _delayedAuctions = timeoutQueue();
 
 /**
  * @summary Each auction can have differing floors data depending on execution time or per adunit setup
@@ -440,17 +441,11 @@ export function createFloorsDataForAuction(adUnits, auctionId) {
  * @summary This is the function which will be called to exit our module and continue the auction.
  */
 export function continueAuction(hookConfig) {
-  // only run if hasExited
   if (!hookConfig.hasExited) {
-    // if this current auction is still fetching, remove it from the _delayedAuctions
-    _delayedAuctions = _delayedAuctions.filter(auctionConfig => auctionConfig.timer !== hookConfig.timer);
-
     // We need to know the auctionId at this time. So we will use the passed in one or generate and set it ourselves
     hookConfig.reqBidsConfigObj.auctionId = hookConfig.reqBidsConfigObj.auctionId || generateUUID();
-
     // now we do what we need to with adUnits and save the data object to be used for getFloor and enforcement calls
     _floorDataForAuction[hookConfig.reqBidsConfigObj.auctionId] = createFloorsDataForAuction(hookConfig.reqBidsConfigObj.adUnits || getGlobal().adUnits, hookConfig.reqBidsConfigObj.auctionId);
-
     hookConfig.nextFn.apply(hookConfig.context, [hookConfig.reqBidsConfigObj]);
     hookConfig.hasExited = true;
   }
@@ -581,36 +576,22 @@ export const requestBidsHook = timedAuctionHook('priceFloors', function requestB
     reqBidsConfigObj,
     context: this,
     nextFn: fn,
-    haveExited: false,
+    hasExited: false,
     timer: null
   };
 
   // If auction delay > 0 AND we are fetching -> Then wait until it finishes
   if (_floorsConfig.auctionDelay > 0 && fetching) {
-    hookConfig.timer = setTimeout(() => {
+    _delayedAuctions.submit(_floorsConfig.auctionDelay, () => continueAuction(hookConfig), () => {
       logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction`);
       _floorsConfig.fetchStatus = 'timeout';
       continueAuction(hookConfig);
-    }, _floorsConfig.auctionDelay);
-    _delayedAuctions.push(hookConfig);
+    });
   } else {
     continueAuction(hookConfig);
   }
 });
 
-/**
- * @summary If an auction was queued to be delayed (waiting for a fetch) then this function will resume
- * those delayed auctions when delay is hit or success return or fail return
- */
-function resumeDelayedAuctions() {
-  _delayedAuctions.forEach(auctionConfig => {
-    // clear the timeout
-    clearTimeout(auctionConfig.timer);
-    continueAuction(auctionConfig);
-  });
-  _delayedAuctions = [];
-}
-
 /**
  * This function handles the ajax response which comes from the user set URL to fetch floors data from
  * @param {object} fetchResponse The floors data response which came back from the url configured in config.floors
@@ -635,7 +616,7 @@ export function handleFetchResponse(fetchResponse) {
   }
 
   // if any auctions are waiting for fetch to finish, we need to continue them!
-  resumeDelayedAuctions();
+  _delayedAuctions.resume();
 }
 
 function handleFetchError(status) {
@@ -644,7 +625,7 @@ function handleFetchError(status) {
   logError(`${MODULE_NAME}: Fetch errored with: `, status);
 
   // if any auctions are waiting for fetch to finish, we need to continue them!
-  resumeDelayedAuctions();
+  _delayedAuctions.resume();
 }
 
 /**
diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js
index e96867f4e84..149a4380036 100644
--- a/test/spec/modules/currency_spec.js
+++ b/test/spec/modules/currency_spec.js
@@ -3,7 +3,7 @@ import {
   getCurrencyRates
 } from 'test/fixtures/fixtures.js';
 
-import { getGlobal } from 'src/prebidGlobal.js';
+import {getGlobal} from 'src/prebidGlobal.js';
 
 import {
   setConfig,
@@ -13,9 +13,11 @@ import {
   responseReady
 } from 'modules/currency.js';
 import {createBid} from '../../../src/bidfactory.js';
-import { EVENTS, STATUS, REJECTION_REASON } from '../../../src/constants.js';
+import * as utils from 'src/utils.js';
+import {EVENTS, STATUS, REJECTION_REASON} from '../../../src/constants.js';
 import {server} from '../../mocks/xhr.js';
 import * as events from 'src/events.js';
+import {requestBidsHook} from '../../../modules/currency.js';
 
 var assert = require('chai').assert;
 var expect = require('chai').expect;
@@ -522,4 +524,54 @@ describe('currency', function () {
       expect(innerBid.currency).to.equal('CNY');
     });
   });
+
+  describe('auctionDelay param', () => {
+    const continueAuction = sinon.stub();
+    let logWarnSpy;
+
+    beforeEach(function() {
+      sandbox = sinon.sandbox.create();
+      clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z
+      logWarnSpy = sinon.spy(utils, 'logWarn');
+    });
+
+    afterEach(function () {
+      clock.runAll();
+      sandbox.restore();
+      clock.restore();
+      utils.logWarn.restore();
+      continueAuction.resetHistory();
+    });
+
+    it('should delay auction start when auctionDelay set in module config', () => {
+      setConfig({auctionDelay: 2000, adServerCurrency: 'USD'});
+      const reqBidsConfigObj = {
+        auctionId: '128937'
+      };
+      requestBidsHook(continueAuction, reqBidsConfigObj);
+      clock.tick(1000);
+      expect(continueAuction.notCalled).to.be.true;
+    });
+
+    it('should start auction when auctionDelay time passed', () => {
+      setConfig({auctionDelay: 2000, adServerCurrency: 'USD'});
+      const reqBidsConfigObj = {
+        auctionId: '128937'
+      };
+      requestBidsHook(continueAuction, reqBidsConfigObj);
+      clock.tick(3000);
+      expect(logWarnSpy.calledOnce).to.equal(true);
+      expect(continueAuction.calledOnce).to.be.true;
+    });
+
+    it('should run auction if rates were fetched before auctionDelay time', () => {
+      setConfig({auctionDelay: 3000, adServerCurrency: 'USD'});
+      const reqBidsConfigObj = {
+        auctionId: '128937'
+      };
+      fakeCurrencyFileServer.respond();
+      requestBidsHook(continueAuction, reqBidsConfigObj);
+      expect(continueAuction.calledOnce).to.be.true;
+    });
+  });
 });

From 6398c684a3148a9a1092764ce361ae8f43192174 Mon Sep 17 00:00:00 2001
From: SebRobert 
Date: Tue, 3 Dec 2024 17:28:51 +0100
Subject: [PATCH 0723/1097] BeOpAdapter - First Party Cookie read and set (#16)
 (#12486)

---
 modules/beopBidAdapter.js                | 19 ++++++++++++++++++-
 test/spec/modules/beopBidAdapter_spec.js | 12 ++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js
index 103820651fa..5b9e57628c6 100644
--- a/modules/beopBidAdapter.js
+++ b/modules/beopBidAdapter.js
@@ -1,6 +1,6 @@
 import {
   buildUrl,
-  deepAccess, getBidIdParameter,
+  deepAccess, generateUUID, getBidIdParameter,
   getValue,
   isArray,
   isPlainObject,
@@ -10,6 +10,7 @@ import {
 } from '../src/utils.js';
 import {getRefererInfo} from '../src/refererDetection.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
+import { getStorageManager } from '../src/storageManager.js';
 import {config} from '../src/config.js';
 import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js';
 
@@ -21,9 +22,11 @@ import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js';
 
 const BIDDER_CODE = 'beop';
 const ENDPOINT_URL = 'https://hb.beop.io/bid';
+const COOKIE_NAME = 'beopid';
 const TCF_VENDOR_ID = 666;
 
 const validIdRegExp = /^[0-9a-fA-F]{24}$/
+const storage = getStorageManager({bidderCode: BIDDER_CODE});
 
 export const spec = {
   code: BIDDER_CODE,
@@ -64,6 +67,19 @@ export const spec = {
     const kwdsFromRequest = firstSlot.kwds;
     let keywords = getAllOrtbKeywords(bidderRequest.ortb2, kwdsFromRequest);
 
+    let beopid = '';
+    if (storage.cookiesAreEnabled) {
+      beopid = storage.getCookie(COOKIE_NAME, undefined);
+      if (!beopid) {
+        beopid = generateUUID();
+        let expirationDate = new Date();
+        expirationDate.setTime(expirationDate.getTime() + 86400 * 183 * 1000);
+        storage.setCookie(COOKIE_NAME, beopid, expirationDate.toUTCString());
+      }
+    } else {
+      storage.setCookie(COOKIE_NAME, '', 0);
+    }
+
     const payloadObject = {
       at: new Date().toString(),
       nid: firstSlot.nid,
@@ -74,6 +90,7 @@ export const spec = {
       lang: (window.navigator.language || window.navigator.languages[0]),
       kwds: keywords,
       dbg: false,
+      fg: beopid,
       slts: slots,
       is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'),
       gdpr_applies: gdpr ? gdpr.gdprApplies : false,
diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js
index d044e71ceb7..0a752761531 100644
--- a/test/spec/modules/beopBidAdapter_spec.js
+++ b/test/spec/modules/beopBidAdapter_spec.js
@@ -327,4 +327,16 @@ describe('BeOp Bid Adapter tests', () => {
       expect(payload.eids[0].source).to.equal('provider.com');
     });
   })
+
+  describe('Ensure first party cookie is well managed', function () {
+    let bidRequests = [];
+
+    it(`should generate a new uuid`, function () {
+      let bid = Object.assign({}, validBid);
+      bidRequests.push(bid);
+      const request = spec.buildRequests(bidRequests, {});
+      const payload = JSON.parse(request.data);
+      expect(payload.fg).to.exist;
+    })
+  })
 });

From 1475988daff3880f94f792c264c7492f7276c1bc Mon Sep 17 00:00:00 2001
From: Demetrio Girardi 
Date: Tue, 3 Dec 2024 08:59:43 -0800
Subject: [PATCH 0724/1097] Core: fix bug where queue is processed before
 processQueue is called (#12528)

---
 src/prebid.js                   |  9 ++++-----
 test/spec/unit/pbjs_api_spec.js | 20 ++++++++++++++++----
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/src/prebid.js b/src/prebid.js
index 9a77f6d3bd3..2a9b88cd47d 100644
--- a/src/prebid.js
+++ b/src/prebid.js
@@ -967,12 +967,12 @@ pbjsInstance.que.push(() => listenMessagesFromCreative());
  * by prebid once it's done loading. If it runs after prebid loads, then this monkey-patch causes their
  * function to execute immediately.
  *
- * @memberof pbjs
  * @param  {function} command A function which takes no arguments. This is guaranteed to run exactly once, and only after
  *                            the Prebid script has been fully loaded.
  * @alias module:pbjs.cmd.push
+ * @alias module:pbjs.que.push
  */
-pbjsInstance.cmd.push = function (command) {
+function quePush(command) {
   if (typeof command === 'function') {
     try {
       command.call();
@@ -982,9 +982,7 @@ pbjsInstance.cmd.push = function (command) {
   } else {
     logError('Commands written into $$PREBID_GLOBAL$$.cmd.push must be wrapped in a function');
   }
-};
-
-pbjsInstance.que.push = pbjsInstance.cmd.push;
+}
 
 function processQueue(queue) {
   queue.forEach(function (cmd) {
@@ -1003,6 +1001,7 @@ function processQueue(queue) {
  * @alias module:pbjs.processQueue
  */
 pbjsInstance.processQueue = function () {
+  pbjsInstance.que.push = pbjsInstance.cmd.push = quePush;
   insertLocatorFrame();
   hook.ready();
   processQueue(pbjsInstance.que);
diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js
index e56f3256569..e1f5b3b5b88 100644
--- a/test/spec/unit/pbjs_api_spec.js
+++ b/test/spec/unit/pbjs_api_spec.js
@@ -237,9 +237,21 @@ describe('Unit: Prebid Module', function () {
     getBidToRender.getHooks({hook: getBidToRenderHook}).remove();
   });
 
-  it('should insert a locator frame on the page', () => {
-    $$PREBID_GLOBAL$$.processQueue();
-    expect(window.frames[PB_LOCATOR]).to.exist;
+  describe('processQueue', () => {
+    it('should insert a locator frame on the page', () => {
+      $$PREBID_GLOBAL$$.processQueue();
+      expect(window.frames[PB_LOCATOR]).to.exist;
+    });
+
+    ['cmd', 'que'].forEach(prop => {
+      it(`should patch ${prop}.push`, () => {
+        $$PREBID_GLOBAL$$[prop].push = false;
+        $$PREBID_GLOBAL$$.processQueue();
+        let ran = false;
+        $$PREBID_GLOBAL$$[prop].push(() => { ran = true; });
+        expect(ran).to.be.true;
+      })
+    })
   })
 
   describe('and global adUnits', () => {
@@ -262,10 +274,10 @@ describe('Unit: Prebid Module', function () {
 
     beforeEach(() => {
       $$PREBID_GLOBAL$$.requestBids.before(deferringHook, 99);
-      $$PREBID_GLOBAL$$.adUnits.splice(0, $$PREBID_GLOBAL$$.adUnits.length, ...startingAdUnits);
       hookRan = new Promise((resolve) => {
         done = resolve;
       });
+      $$PREBID_GLOBAL$$.adUnits.splice(0, $$PREBID_GLOBAL$$.adUnits.length, ...startingAdUnits);
     });
 
     afterEach(() => {

From 72b5262e22b220eb064629365e01f3ce408ce371 Mon Sep 17 00:00:00 2001
From: Rich Audience 
Date: Wed, 4 Dec 2024 12:39:21 +0100
Subject: [PATCH 0725/1097] richAudience Bid Adapter : update functionality of
 bid param:  keywords (#12537)

* Update richaudienceBidAdapter.md

Update maintainer e-mail to integrations@richaudience.com

* Add richaudienceBidAdapter.js file

* Add richaudienceBidAdapter_spec.js

* Update richaudienceBidAdapter.js

* RichaudienceBidAdapter add compability with DSA

* RichaudienceBidAdapter add compability with DSA

* RichaudienceBidAdapter add compability with DSA

* Richaudience Bid Adapter: update adomain

* Richaudience Bid Adapter: update adomain test

* (fix)richAudienceBidAdapter change functionality of bid param:  keywords

---------

Co-authored-by: Patrick McCann 
Co-authored-by: sergigimenez 
---
 modules/richaudienceBidAdapter.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js
index b88e9474c28..cfb0f97fafd 100644
--- a/modules/richaudienceBidAdapter.js
+++ b/modules/richaudienceBidAdapter.js
@@ -3,7 +3,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {config} from '../src/config.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {Renderer} from '../src/Renderer.js';
-import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js';
 
 const BIDDER_CODE = 'richaudience';
 let REFERER = '';
@@ -53,7 +52,7 @@ export const spec = {
         videoData: raiGetVideoInfo(bid),
         scr_rsl: raiGetResolution(),
         cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null),
-        kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','),
+        kws: bid.params.keywords,
         schain: bid.schain,
         gpid: raiSetPbAdSlot(bid),
         dsa: setDSA(bid),

From 189a7cecf611560f159b68d590bb6346ede77df6 Mon Sep 17 00:00:00 2001
From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com>
Date: Wed, 4 Dec 2024 19:57:07 +0530
Subject: [PATCH 0726/1097] Prebid Core: Added TTL validation for suppressing
 expired ads (#12532)

* Added TTL Validation for Suppressing Expired Ads

* resolved linting issues

---------

Co-authored-by: pm-azhar-mulla 
---
 src/adRendering.js                 | 11 ++++++++++-
 src/config.js                      |  4 ++--
 src/constants.js                   |  1 +
 test/spec/config_spec.js           | 17 +++++++++++++++++
 test/spec/unit/adRendering_spec.js | 21 +++++++++++++++++++++
 5 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/src/adRendering.js b/src/adRendering.js
index dfed547ee39..130ee74278d 100644
--- a/src/adRendering.js
+++ b/src/adRendering.js
@@ -20,8 +20,9 @@ import {fireNativeTrackers} from './native.js';
 import {GreedyPromise} from './utils/promise.js';
 import adapterManager from './adapterManager.js';
 import {useMetrics} from './utils/perfMetrics.js';
+import {filters} from './targeting.js';
 
-const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS;
+const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON, EXPIRED_RENDER } = EVENTS;
 const { EXCEPTION } = AD_RENDER_FAILED_REASON;
 
 export const getBidToRender = hook('sync', function (adId, forRender = true, override = GreedyPromise.resolve()) {
@@ -185,6 +186,14 @@ export function handleRender({renderFn, resizeFn, adId, options, bidResponse, do
         return;
       }
     }
+    if (!filters.isBidNotExpired(bidResponse)) {
+      logWarn(`Ad id ${adId} has been expired`);
+      events.emit(EXPIRED_RENDER, bidResponse);
+      if (deepAccess(config.getConfig('auctionOptions'), 'suppressExpiredRender')) {
+        return;
+      }
+    }
+
     try {
       doRender({renderFn, resizeFn, bidResponse, options, doc});
     } catch (e) {
diff --git a/src/config.js b/src/config.js
index 53f53f99529..88affc40faf 100644
--- a/src/config.js
+++ b/src/config.js
@@ -179,7 +179,7 @@ function attachProperties(config, useDefaultValues = true) {
     }
 
     for (let k of Object.keys(val)) {
-      if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') {
+      if (k !== 'secondaryBidders' && k !== 'suppressStaleRender' && k !== 'suppressExpiredRender') {
         logWarn(`Auction Options given an incorrect param: ${k}`)
         return false
       }
@@ -191,7 +191,7 @@ function attachProperties(config, useDefaultValues = true) {
           logWarn(`Auction Options ${k} must be only string`);
           return false
         }
-      } else if (k === 'suppressStaleRender') {
+      } else if (k === 'suppressStaleRender' || k === 'suppressExpiredRender') {
         if (!isBoolean(val[k])) {
           logWarn(`Auction Options ${k} must be of type boolean`);
           return false;
diff --git a/src/constants.js b/src/constants.js
index 095ee648b7e..1a17cf3eec0 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -40,6 +40,7 @@ export const EVENTS = {
   AUCTION_DEBUG: 'auctionDebug',
   BID_VIEWABLE: 'bidViewable',
   STALE_RENDER: 'staleRender',
+  EXPIRED_RENDER: 'expiredRender',
   BILLABLE_EVENT: 'billableEvent',
   BID_ACCEPTED: 'bidAccepted',
   RUN_PAAPI_AUCTION: 'paapiRunAuction',
diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js
index b54f207e6a9..9aa4f42da48 100644
--- a/test/spec/config_spec.js
+++ b/test/spec/config_spec.js
@@ -345,6 +345,14 @@ describe('config API', function () {
     expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig);
   });
 
+  it('sets auctionOptions suppressExpiredRender', function () {
+    const auctionOptionsConfig = {
+      'suppressExpiredRender': true
+    }
+    setConfig({ auctionOptions: auctionOptionsConfig });
+    expect(getConfig('auctionOptions')).to.eql(auctionOptionsConfig);
+  });
+
   it('should log warning for the wrong value passed to auctionOptions', function () {
     setConfig({ auctionOptions: '' });
     expect(logWarnSpy.calledOnce).to.equal(true);
@@ -370,6 +378,15 @@ describe('config API', function () {
     assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged');
   });
 
+  it('should log warning for invalid auctionOptions suppress expired render', function () {
+    setConfig({ auctionOptions: {
+      'suppressExpiredRender': 'test',
+    }});
+    expect(logWarnSpy.calledOnce).to.equal(true);
+    const warning = 'Auction Options suppressExpiredRender must be of type boolean';
+    assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged');
+  });
+
   it('should log warning for invalid properties to auctionOptions', function () {
     setConfig({ auctionOptions: {
       'testing': true
diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js
index bb9ba9215cf..373dba95b3a 100644
--- a/test/spec/unit/adRendering_spec.js
+++ b/test/spec/unit/adRendering_spec.js
@@ -16,6 +16,7 @@ import {config} from 'src/config.js';
 import {VIDEO} from '../../../src/mediaTypes.js';
 import {auctionManager} from '../../../src/auctionManager.js';
 import adapterManager from '../../../src/adapterManager.js';
+import {filters} from 'src/targeting.js';
 
 describe('adRendering', () => {
   let sandbox;
@@ -309,6 +310,26 @@ describe('adRendering', () => {
           sinon.assert.notCalled(doRenderStub);
         })
       });
+
+      describe('when bid has already expired', () => {
+        let isBidNotExpiredStub = sinon.stub(filters, 'isBidNotExpired');
+        beforeEach(() => {
+          isBidNotExpiredStub.returns(false);
+        });
+        afterEach(() => {
+          isBidNotExpiredStub.restore();
+        })
+        it('should emit EXPIRED_RENDER', () => {
+          handleRender({adId, bidResponse});
+          sinon.assert.calledWith(events.emit, EVENTS.EXPIRED_RENDER, bidResponse);
+          sinon.assert.called(doRenderStub);
+        });
+        it('should skip rendering if suppressExpiredRender', () => {
+          config.setConfig({auctionOptions: {suppressExpiredRender: true}});
+          handleRender({adId, bidResponse});
+          sinon.assert.notCalled(doRenderStub);
+        })
+      });
     })
   })
 

From 8b00a5ddeb3a49f13762911ab9958448920a66b7 Mon Sep 17 00:00:00 2001
From: "Prebid.js automated release" 
Date: Wed, 4 Dec 2024 15:05:13 +0000
Subject: [PATCH 0727/1097] Prebid 9.22.0 release

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 133c5a7a632..416fbe4a3b2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "prebid.js",
-  "version": "9.22.0-pre",
+  "version": "9.22.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "prebid.js",
-      "version": "9.22.0-pre",
+      "version": "9.22.0",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index fa5b569bc6b..1a6dc9fd003 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "prebid.js",
-  "version": "9.22.0-pre",
+  "version": "9.22.0",
   "description": "Header Bidding Management Library",
   "main": "src/prebid.public.js",
   "exports": {

From a9958be99d7c9d431fe3d22929daf834009f3480 Mon Sep 17 00:00:00 2001
From: "Prebid.js automated release" 
Date: Wed, 4 Dec 2024 15:05:14 +0000
Subject: [PATCH 0728/1097] Increment version to 9.23.0-pre

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 416fbe4a3b2..f69353f8360 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "prebid.js",
-  "version": "9.22.0",
+  "version": "9.23.0-pre",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "prebid.js",
-      "version": "9.22.0",
+      "version": "9.23.0-pre",
       "license": "Apache-2.0",
       "dependencies": {
         "@babel/core": "^7.25.2",
diff --git a/package.json b/package.json
index 1a6dc9fd003..2b6d3cae7aa 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "prebid.js",
-  "version": "9.22.0",
+  "version": "9.23.0-pre",
   "description": "Header Bidding Management Library",
   "main": "src/prebid.public.js",
   "exports": {

From 5e1a3c2baf76db76de29d469f2499a4c431308c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?=
 <88041828+krzysztofequativ@users.noreply.github.com>
Date: Wed, 4 Dec 2024 21:01:12 +0100
Subject: [PATCH 0729/1097] Equativ Bid Adapter: add support for video media
 type (#12514)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* add support of dsa

* restore topics

* DSA fix for UT

* drafy of adapter

* fixes after dev test

* make world simpler

* fix prev commit

* return empty userSyncs array by default

* adjustments

* apply prettier

* unit tests for Equativ adapter

* add dsp user sync

* add readme

* body can be undef

* support additional br params

* remove user sync

* do not send dt param

* handle floors and network id

* handle empty media types

* get min floor

* fix desc for u.t.

* better name for u.t.

* add u.t. for not supported media type

* improve currency u.t.

* SADR-6484: initial video setup for new PBJS adapter

* SADR-6484: Adding logging requirement missed earlier

* SADR-6484: handle ext.rewarded prop for video with new oRTBConverter

* SADR-6484: test revision + not sending bid requests where video obj is empty

* refactoring and u.t.

* rename variable

* revert changes rel. to test endpoint

* revert changes rel. to test endpoint

* split imp[0] into seperate requests and fix u.t.

---------

Co-authored-by: Elżbieta SZPONDER 
Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com>
Co-authored-by: janzych-smart 
Co-authored-by: Jeff Mahoney 
Co-authored-by: Jeff Mahoney 
---
 modules/equativBidAdapter.js                |  91 +++---
 test/spec/modules/equativBidAdapter_spec.js | 344 +++++++++++++++-----
 2 files changed, 319 insertions(+), 116 deletions(-)

diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js
index 7f59e46920e..a53597a9074 100644
--- a/modules/equativBidAdapter.js
+++ b/modules/equativBidAdapter.js
@@ -1,9 +1,10 @@
-import { BANNER } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'
+import { getStorageManager } from '../src/storageManager.js';
 import { ortbConverter } from '../libraries/ortbConverter/converter.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { getStorageManager } from '../src/storageManager.js';
-import { deepAccess, deepSetValue, mergeDeep } from '../src/utils.js';
+import { deepAccess, deepSetValue, logError, logWarn, mergeDeep } from '../src/utils.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
@@ -13,14 +14,26 @@ import { deepAccess, deepSetValue, mergeDeep } from '../src/utils.js';
 const BIDDER_CODE = 'equativ';
 const COOKIE_SYNC_ORIGIN = 'https://apps.smartadserver.com';
 const COOKIE_SYNC_URL = `${COOKIE_SYNC_ORIGIN}/diff/templates/asset/csync.html`;
+const LOG_PREFIX = 'Equativ:';
 const PID_COOKIE_NAME = 'eqt_pid';
 
+/**
+ * Evaluates a bid request for validity. Returns false if the
+ * request contains a video media type with no properties, true
+ * otherwise.
+ * @param {*} bidReq A bid request object to evaluate
+ * @returns boolean
+ */
+function isValid(bidReq) {
+  return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}');
+}
+
 export const storage = getStorageManager({ bidderCode: BIDDER_CODE });
 
 export const spec = {
   code: BIDDER_CODE,
   gvlid: 45,
-  supportedMediaTypes: [BANNER],
+  supportedMediaTypes: [BANNER, VIDEO],
 
   /**
    * @param bidRequests
@@ -28,11 +41,23 @@ export const spec = {
    * @returns {ServerRequest[]}
    */
   buildRequests: (bidRequests, bidderRequest) => {
-    return {
-      data: converter.toORTB({ bidderRequest, bidRequests }),
-      method: 'POST',
-      url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169'
-    };
+    if (bidRequests.filter(isValid).length === 0) {
+      logError(`${LOG_PREFIX} No useful bid requests to process. No request will be sent.`, bidRequests);
+      return undefined;
+    }
+
+    const requests = [];
+
+    bidRequests.forEach(bid => {
+      const data = converter.toORTB({bidRequests: [bid], bidderRequest});
+      requests.push({
+        data,
+        method: 'POST',
+        url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169',
+      })
+    });
+
+    return requests;
   },
 
   /**
@@ -89,32 +114,23 @@ export const converter = ortbConverter({
 
   imp(buildImp, bidRequest, context) {
     const imp = buildImp(bidRequest, context);
+    const mediaType = deepAccess(bidRequest, 'mediaTypes.video') ? VIDEO : BANNER;
     const { siteId, pageId, formatId } = bidRequest.params;
 
     delete imp.dt;
 
-    imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest);
-    imp.secure = bidRequest.ortb2Imp?.secure ?? 1;
-    imp.tagid = bidRequest.adUnitCode;
-
-    if (siteId || pageId || formatId) {
-      const bidder = {};
-
-      if (siteId) {
-        bidder.siteId = siteId;
-      }
+    imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest, config.getConfig('currency.adServerCurrency'), mediaType);
+    imp.secure = 1;
 
-      if (pageId) {
-        bidder.pageId = pageId;
-      }
+    imp.tagid = bidRequest.adUnitCode;
 
-      if (formatId) {
-        bidder.formatId = formatId;
-      }
+    if (!deepAccess(bidRequest, 'ortb2Imp.rwdd') && deepAccess(bidRequest, 'mediaTypes.video.ext.rewarded')) {
+      mergeDeep(imp, { rwdd: bidRequest.mediaTypes.video.ext.rewarded });
+    }
 
-      mergeDeep(imp, {
-        ext: { bidder },
-      });
+    const bidder = { ...(siteId && { siteId }), ...(pageId && { pageId }), ...(formatId && { formatId }) };
+    if (Object.keys(bidder).length) {
+      mergeDeep(imp.ext, { bidder });
     }
 
     return imp;
@@ -124,14 +140,15 @@ export const converter = ortbConverter({
     const bid = context.bidRequests[0];
     const req = buildRequest(imps, bidderRequest, context);
 
-    if (deepAccess(bid, 'ortb2.site.publisher')) {
-      deepSetValue(req, 'site.publisher.id', bid.ortb2.site.publisher.id || bid.params.networkId);
-    } else if (deepAccess(bid, 'ortb2.app.publisher')) {
-      deepSetValue(req, 'app.publisher.id', bid.ortb2.app.publisher.id || bid.params.networkId);
-    } else if (deepAccess(bid, 'ortb2.dooh.publisher')) {
-      deepSetValue(req, 'dooh.publisher.id', bid.ortb2.dooh.publisher.id || bid.params.networkId);
-    } else {
-      deepSetValue(req, 'site.publisher.id', bid.params.networkId);
+    let env = ['ortb2.site.publisher', 'ortb2.app.publisher', 'ortb2.dooh.publisher'].find(propPath => deepAccess(bid, propPath)) || 'ortb2.site.publisher';
+    deepSetValue(req, env.replace('ortb2.', '') + '.id', deepAccess(bid, env + '.id') || bid.params.networkId);
+
+    if (deepAccess(bid, 'mediaTypes.video')) {
+      ['mimes', 'placement'].forEach(prop => {
+        if (!bid.mediaTypes.video[prop]) {
+          logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request`, bid);
+        }
+      });
     }
 
     const pid = storage.getCookie(PID_COOKIE_NAME);
@@ -140,7 +157,7 @@ export const converter = ortbConverter({
     }
 
     return req;
-  },
+  }
 });
 
 registerBidder(spec);
diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js
index c52507a000b..9f767a3cd4e 100644
--- a/test/spec/modules/equativBidAdapter_spec.js
+++ b/test/spec/modules/equativBidAdapter_spec.js
@@ -1,9 +1,20 @@
 import { BANNER } from 'src/mediaTypes.js';
 import { getBidFloor } from 'libraries/equativUtils/equativUtils.js'
 import { converter, spec, storage } from 'modules/equativBidAdapter.js';
+import * as utils from '../../../src/utils.js';
 
 describe('Equativ bid adapter tests', () => {
-  const DEFAULT_BID_REQUESTS = [
+  let sandBox;
+
+  beforeEach(() => {
+    sandBox = sinon.createSandbox();
+    sandBox.stub(utils, 'logError');
+    sandBox.stub(utils, 'logWarn');
+  });
+
+  afterEach(() => sandBox.restore());
+
+  const DEFAULT_BANNER_BID_REQUESTS = [
     {
       adUnitCode: 'eqtv_42',
       bidId: 'abcd1234',
@@ -25,12 +36,56 @@ describe('Equativ bid adapter tests', () => {
           tid: 'zsfgzzg',
         },
       },
-    },
+    }
+  ];
+
+  const DEFAULT_VIDEO_BID_REQUESTS = [
+    {
+      adUnitCode: 'eqtv_43',
+      bidId: 'efgh5678',
+      mediaTypes: {
+        video: {
+          context: 'instream',
+          playerSize: [[640, 480]],
+          pos: 3,
+          skip: 1,
+          linearity: 1,
+          minduration: 10,
+          maxduration: 30,
+          minbitrate: 300,
+          maxbitrate: 600,
+          w: 640,
+          h: 480,
+          playbackmethod: [1],
+          api: [3],
+          mimes: ['video/x-flv', 'video/mp4'],
+          // protocols: [2, 3], // used in older adapter ... including as comment for reference
+          startdelay: 42,
+          battr: [13, 14],
+          placement: 1,
+        },
+      },
+      bidder: 'equativ',
+      params: {
+        networkId: 111,
+      },
+      requestId: 'abcd1234',
+      ortb2Imp: {
+        ext: {
+          tid: 'zsgzgzz',
+        },
+      },
+    }
   ];
 
-  const DEFAULT_BIDDER_REQUEST = {
+  const DEFAULT_BANNER_BIDDER_REQUEST = {
+    bidderCode: 'equativ',
+    bids: DEFAULT_BANNER_BID_REQUESTS,
+  };
+
+  const DEFAULT_VIDEO_BIDDER_REQUEST = {
     bidderCode: 'equativ',
-    bids: DEFAULT_BID_REQUESTS,
+    bids: DEFAULT_VIDEO_BID_REQUESTS,
   };
 
   const SAMPLE_RESPONSE = {
@@ -62,25 +117,18 @@ describe('Equativ bid adapter tests', () => {
     },
   };
 
-  // const RESPONSE_WITH_DSP_PIXELS = {
-  //   ...SAMPLE_RESPONSE,
-  //   body: {
-  //     dspPixels: ['1st-pixel', '2nd-pixel', '3rd-pixel']
-  //   }
-  // };
-
   describe('buildRequests', () => {
-    it('should build correct request using ORTB converter', () => {
+    it('should build correct requests using ORTB converter', () => {
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
       );
       const dataFromConverter = converter.toORTB({
-        bidderRequest: DEFAULT_BIDDER_REQUEST,
-        bidRequests: DEFAULT_BID_REQUESTS,
+        bidderRequest: DEFAULT_BANNER_BIDDER_REQUEST,
+        bidRequests: DEFAULT_BANNER_BID_REQUESTS,
       });
-      expect(request).to.deep.equal({
-        data: { ...dataFromConverter, id: request.data.id },
+      expect(request[0]).to.deep.equal({
+        data: { ...dataFromConverter, id: request[0].data.id },
         method: 'POST',
         url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169',
       });
@@ -88,10 +136,10 @@ describe('Equativ bid adapter tests', () => {
 
     it('should add ext.bidder to imp object when siteId is defined', () => {
       const bidRequests = [
-        { ...DEFAULT_BID_REQUESTS[0], params: { siteId: 123 } },
+        { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { siteId: 123 } },
       ];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.imp[0].ext.bidder).to.deep.equal({
         siteId: 123,
       });
@@ -99,10 +147,10 @@ describe('Equativ bid adapter tests', () => {
 
     it('should add ext.bidder to imp object when pageId is defined', () => {
       const bidRequests = [
-        { ...DEFAULT_BID_REQUESTS[0], params: { pageId: 123 } },
+        { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { pageId: 123 } },
       ];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.imp[0].ext.bidder).to.deep.equal({
         pageId: 123,
       });
@@ -110,33 +158,33 @@ describe('Equativ bid adapter tests', () => {
 
     it('should add ext.bidder to imp object when formatId is defined', () => {
       const bidRequests = [
-        { ...DEFAULT_BID_REQUESTS[0], params: { formatId: 123 } },
+        { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { formatId: 123 } },
       ];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.imp[0].ext.bidder).to.deep.equal({
         formatId: 123,
       });
     });
 
     it('should not add ext.bidder to imp object when siteId, pageId, formatId are not defined', () => {
-      const bidRequests = [{ ...DEFAULT_BID_REQUESTS[0], params: {} }];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidRequests = [{ ...DEFAULT_BANNER_BID_REQUESTS[0], params: {} }];
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.imp[0].ext.bidder).to.be.undefined;
     });
 
     it('should add site.publisher.id param', () => {
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
       expect(request.data.site.publisher.id).to.equal(111);
     });
 
     it('should pass ortb2.site.publisher.id', () => {
       const bidRequests = [{
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         ortb2: {
           site: {
             publisher: {
@@ -146,28 +194,28 @@ describe('Equativ bid adapter tests', () => {
         }
       }];
       delete bidRequests[0].params;
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.site.publisher.id).to.equal(98);
     });
 
     it('should pass networkId as site.publisher.id', () => {
       const bidRequests = [{
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         ortb2: {
           site: {
             publisher: {}
           }
         }
       }];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.site.publisher.id).to.equal(111);
     });
 
     it('should pass ortb2.app.publisher.id', () => {
       const bidRequests = [{
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         ortb2: {
           app: {
             publisher: {
@@ -177,28 +225,28 @@ describe('Equativ bid adapter tests', () => {
         }
       }];
       delete bidRequests[0].params;
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.app.publisher.id).to.equal(27);
     });
 
     it('should pass networkId as app.publisher.id', () => {
       const bidRequests = [{
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         ortb2: {
           app: {
             publisher: {}
           }
         }
       }];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.app.publisher.id).to.equal(111);
     });
 
     it('should pass ortb2.dooh.publisher.id', () => {
       const bidRequests = [{
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         ortb2: {
           dooh: {
             publisher: {
@@ -208,55 +256,55 @@ describe('Equativ bid adapter tests', () => {
         }
       }];
       delete bidRequests[0].params;
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.dooh.publisher.id).to.equal(35);
     });
 
     it('should pass networkId as dooh.publisher.id', () => {
       const bidRequests = [{
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         ortb2: {
           dooh: {
             publisher: {}
           }
         }
       }];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.dooh.publisher.id).to.equal(111);
     });
 
     it('should send default floor of 0.0', () => {
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
       expect(request.data.imp[0]).to.have.property('bidfloor').that.eq(0.0);
     });
 
     it('should send secure connection', () => {
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
       expect(request.data.imp[0]).to.have.property('secure').that.eq(1);
     });
 
     it('should have tagid', () => {
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
-      expect(request.data.imp[0]).to.have.property('tagid').that.eq(DEFAULT_BID_REQUESTS[0].adUnitCode);
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
+      expect(request.data.imp[0]).to.have.property('tagid').that.eq(DEFAULT_BANNER_BID_REQUESTS[0].adUnitCode);
     });
 
     it('should remove dt', () => {
       const bidRequests = [
-        { ...DEFAULT_BID_REQUESTS[0], ortb2Imp: { dt: 1728377558235 } }
+        { ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2Imp: { dt: 1728377558235 } }
       ];
-      const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
-      const request = spec.buildRequests(bidRequests, bidderRequest);
+      const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests };
+      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
       expect(request.data.imp[0]).to.not.have.property('dt');
     });
 
@@ -268,9 +316,9 @@ describe('Equativ bid adapter tests', () => {
       getCookieStub.callsFake(cookieName => cookieData[cookieName]);
 
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
 
       expect(request.data.user).to.have.property('buyeruid').that.eq(cookieData['eqt_pid']);
 
@@ -282,9 +330,9 @@ describe('Equativ bid adapter tests', () => {
       getCookieStub.callsFake(() => null);
 
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
 
       expect(request.data).to.not.have.property('user');
 
@@ -296,25 +344,163 @@ describe('Equativ bid adapter tests', () => {
       getCookieStub.callsFake(() => undefined);
 
       const bidRequest = {
-        ...DEFAULT_BIDDER_REQUEST,
+        ...DEFAULT_BANNER_BIDDER_REQUEST,
         ortb2: {
           user: {
             buyeruid: 'buyeruid-provided-by-publisher'
           }
         }
       };
-      const request = spec.buildRequests([ DEFAULT_BID_REQUESTS[0] ], bidRequest);
+      const request = spec.buildRequests([ DEFAULT_BANNER_BID_REQUESTS[0] ], bidRequest)[0];
 
       expect(request.data.user.buyeruid).to.deep.eq(bidRequest.ortb2.user.buyeruid);
 
       getCookieStub.restore();
     });
+
+    it('should build a video request properly under normal circumstances', () => {
+      // ASSEMBLE
+      if (FEATURES.VIDEO) {
+        // ACT
+        const request = spec.buildRequests(DEFAULT_VIDEO_BID_REQUESTS, {})[0].data;
+
+        // ASSERT
+        expect(request.imp[0]).to.have.property('video');
+
+        const videoObj = request.imp[0].video;
+
+        expect(videoObj).to.have.property('api').and.to.deep.equal([3]);
+        expect(videoObj).to.have.property('battr').and.to.deep.equal([13, 14]);
+        expect(videoObj).to.have.property('linearity').and.to.equal(1);
+        expect(videoObj).to.have.property('mimes').and.to.deep.equal(['video/x-flv', 'video/mp4']);
+        expect(videoObj).to.have.property('minbitrate').and.to.equal(300);
+        expect(videoObj).to.have.property('maxbitrate').and.to.equal(600);
+        expect(videoObj).to.have.property('minduration').and.to.equal(10);
+        expect(videoObj).to.have.property('maxduration').and.to.equal(30);
+        expect(videoObj).to.have.property('placement').and.to.equal(1);
+        expect(videoObj).to.have.property('playbackmethod').and.to.deep.equal([1]);
+        expect(videoObj).to.have.property('pos').and.to.equal(3);
+        expect(videoObj).to.have.property('skip').and.to.equal(1);
+        expect(videoObj).to.have.property('startdelay').and.to.equal(42);
+        expect(videoObj).to.have.property('w').and.to.equal(640);
+        expect(videoObj).to.have.property('h').and.to.equal(480);
+        expect(videoObj).not.to.have.property('ext');
+      }
+    });
+
+    it('should read and pass ortb2Imp.rwdd', () => {
+      // ASSEMBLE
+      if (FEATURES.VIDEO) {
+        const bidRequestsWithOrtb2ImpRwdd = [
+          {
+            ...DEFAULT_VIDEO_BID_REQUESTS[0],
+            ortb2Imp: {
+              rwdd: 1
+            }
+          }
+        ];
+        // ACT
+        const request = spec.buildRequests(bidRequestsWithOrtb2ImpRwdd, {})[0].data;
+
+        // ASSERT
+        expect(request.imp[0]).to.have.property('rwdd').and.to.equal(1);
+      }
+    });
+
+    it('should read mediaTypes.video.ext.rewarded and pass as rwdd', () => {
+      // ASSEMBLE
+      if (FEATURES.VIDEO) {
+        const bidRequestsWithExtReworded = [
+          {
+            ...DEFAULT_VIDEO_BID_REQUESTS[0],
+            mediaTypes: {
+              video: {
+                ext: {
+                  rewarded: 1
+                }
+              }
+            }
+          }
+        ];
+        // ACT
+        const request = spec.buildRequests(bidRequestsWithExtReworded, {})[0].data;
+
+        // ASSERT
+        expect(request.imp[0]).to.have.property('rwdd').and.to.equal(1);
+      }
+    });
+
+    it('should prioritize ortb2Imp.rwdd over mediaTypes.video.ext.rewarded', () => {
+      // ASSEMBLE
+      if (FEATURES.VIDEO) {
+        const bidRequestsWithBothRewordedParams = [
+          {
+            ...DEFAULT_VIDEO_BID_REQUESTS[0],
+            mediaTypes: {
+              video: {
+                ext: {
+                  rewarded: 1
+                }
+              }
+            },
+            ortb2Imp: {
+              rwdd: 2
+            }
+          }
+        ];
+        // ACT
+        const request = spec.buildRequests(bidRequestsWithBothRewordedParams, {})[0].data;
+
+        // ASSERT
+        expect(request.imp[0]).to.have.property('rwdd').and.to.equal(2);
+      }
+    });
+
+    it('should warn about missing required properties for video requests', () => {
+      // ASSEMBLE
+      const missingRequiredVideoRequest = DEFAULT_VIDEO_BID_REQUESTS[0];
+
+      // removing required properties
+      delete missingRequiredVideoRequest.mediaTypes.video.mimes;
+      delete missingRequiredVideoRequest.mediaTypes.video.placement;
+
+      const bidRequests = [ missingRequiredVideoRequest ];
+      const bidderRequest = { ...DEFAULT_VIDEO_BIDDER_REQUEST, bids: bidRequests };
+
+      // ACT
+      spec.buildRequests(bidRequests, bidderRequest);
+
+      // ASSERT
+      expect(utils.logWarn.callCount).to.equal(2);
+      expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('"mimes" is missing'));
+      expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"placement" is missing'));
+    });
+
+    it('should not send a video request when it has an empty body and no other impressions with any media types are defined', () => {
+      // ASSEMBLE
+      const emptyVideoRequest = {
+        ...DEFAULT_VIDEO_BID_REQUESTS[0],
+        mediaTypes: {
+          video: {}
+        }
+      };
+      const bidRequests = [ emptyVideoRequest ];
+      const bidderRequest = { ...DEFAULT_VIDEO_BIDDER_REQUEST, bids: bidRequests };
+
+      // ACT
+      const request = spec.buildRequests(bidRequests, bidderRequest);
+
+      // ASSERT
+      expect(utils.logError.calledOnce).to.equal(true);
+      expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request'));
+      expect(request).to.be.undefined;
+    });
   });
 
   describe('getBidFloor', () => {
     it('should return floor of 0.0 if floor module not available', () => {
       const bid = {
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         getFloor: false,
       };
       expect(getBidFloor(bid)).to.deep.eq(0.0);
@@ -330,7 +516,7 @@ describe('Equativ bid adapter tests', () => {
 
     it('should return proper min floor', () => {
       const bid = {
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         getFloor: data => {
           if (data.size[0] === 300 && data.size[1] === 250) {
             return { floor: 1.13 };
@@ -346,7 +532,7 @@ describe('Equativ bid adapter tests', () => {
 
     it('should return global media type floor if no rule for size', () => {
       const bid = {
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         getFloor: data => {
           if (data.size[0] === 728 && data.size[1] === 90) {
             return { floor: 1.13 };
@@ -362,7 +548,7 @@ describe('Equativ bid adapter tests', () => {
 
     it('should return floor of 0 if no rule for size', () => {
       const bid = {
-        ...DEFAULT_BID_REQUESTS[0],
+        ...DEFAULT_BANNER_BID_REQUESTS[0],
         getFloor: data => {
           if (data.size[0] === 728 && data.size[1] === 90) {
             return { floor: 1.13 };
@@ -466,9 +652,9 @@ describe('Equativ bid adapter tests', () => {
   describe('interpretResponse', () => {
     it('should return data returned by ORTB converter', () => {
       const request = spec.buildRequests(
-        DEFAULT_BID_REQUESTS,
-        DEFAULT_BIDDER_REQUEST
-      );
+        DEFAULT_BANNER_BID_REQUESTS,
+        DEFAULT_BANNER_BIDDER_REQUEST
+      )[0];
       const bids = spec.interpretResponse(SAMPLE_RESPONSE, request);
       expect(bids).to.deep.equal(
         converter.fromORTB({

From 840f5abf7e4438559b756376f1dea5976085b003 Mon Sep 17 00:00:00 2001
From: Demetrio Girardi 
Date: Wed, 4 Dec 2024 12:16:11 -0800
Subject: [PATCH 0730/1097] Core: remove individual bids from cache when
 minBidCacheTTL is set (#12535)

---
 src/auction.js                   |  19 ++-
 src/auctionManager.js            |  24 +---
 src/bidTTL.js                    |  24 ++--
 src/targeting.js                 |   4 +-
 test/spec/auctionmanager_spec.js | 195 ++++++++++++++++++++-----------
 5 files changed, 163 insertions(+), 103 deletions(-)

diff --git a/src/auction.js b/src/auction.js
index 759397275d5..9cc6dd48401 100644
--- a/src/auction.js
+++ b/src/auction.js
@@ -98,6 +98,8 @@ import {defer, GreedyPromise} from './utils/promise.js';
 import {useMetrics} from './utils/perfMetrics.js';
 import {adjustCpm} from './utils/cpm.js';
 import {getGlobal} from './prebidGlobal.js';
+import {ttlCollection} from './utils/ttlCollection.js';
+import {getMinBidCacheTTL, onMinBidCacheTTLChange} from './bidTTL.js';
 
 const { syncUsers } = userSync;
 
@@ -153,7 +155,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
   let _bidsRejected = [];
   let _callback = callback;
   let _bidderRequests = [];
-  let _bidsReceived = [];
+  let _bidsReceived = ttlCollection({
+    startTime: (bid) => bid.responseTimestamp,
+    ttl: (bid) => getMinBidCacheTTL() == null ? null : Math.max(getMinBidCacheTTL(), bid.ttl) * 1000
+  });
   let _noBids = [];
   let _winningBids = [];
   let _auctionStart;
@@ -162,8 +167,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
   let _auctionStatus;
   let _nonBids = [];
 
+  onMinBidCacheTTLChange(() => _bidsReceived.refresh());
+
   function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests); }
-  function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); }
+  function addBidReceived(bid) { _bidsReceived.add(bid); }
   function addBidRejected(bidsRejected) { _bidsRejected = _bidsRejected.concat(bidsRejected); }
   function addNoBid(noBid) { _noBids = _noBids.concat(noBid); }
   function addNonBids(seatnonbids) { _nonBids = _nonBids.concat(seatnonbids); }
@@ -179,7 +186,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
       labels: _labels,
       bidderRequests: _bidderRequests,
       noBids: _noBids,
-      bidsReceived: _bidsReceived,
+      bidsReceived: _bidsReceived.toArray(),
       bidsRejected: _bidsRejected,
       winningBids: _winningBids,
       timeout: _timeout,
@@ -219,7 +226,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
       bidsBackCallback(_adUnits, function () {
         try {
           if (_callback != null) {
-            const bids = _bidsReceived
+            const bids = _bidsReceived.toArray()
               .filter(bid => _adUnitCodes.includes(bid.adUnitCode))
               .reduce(groupByPlacement, {});
             _callback.apply(pbjsInstance, [bids, timedOut, _auctionId]);
@@ -246,7 +253,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
   function auctionDone() {
     config.resetBidder();
     // when all bidders have called done callback atleast once it means auction is complete
-    logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived);
+    logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived.toArray());
     _auctionStatus = AUCTION_COMPLETED;
     executeCallback(false);
   }
@@ -404,7 +411,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
     getAdUnits: () => _adUnits,
     getAdUnitCodes: () => _adUnitCodes,
     getBidRequests: () => _bidderRequests,
-    getBidsReceived: () => _bidsReceived,
+    getBidsReceived: () => _bidsReceived.toArray(),
     getNoBids: () => _noBids,
     getNonBids: () => _nonBids,
     getFPD: () => ortb2Fragments,
diff --git a/src/auctionManager.js b/src/auctionManager.js
index 27a2c2beaf2..ab2947b96b4 100644
--- a/src/auctionManager.js
+++ b/src/auctionManager.js
@@ -26,10 +26,7 @@ import {AuctionIndex} from './auctionIndex.js';
 import { BID_STATUS, JSON_MAPPING } from './constants.js';
 import {useMetrics} from './utils/perfMetrics.js';
 import {ttlCollection} from './utils/ttlCollection.js';
-import {getTTL, onTTLBufferChange} from './bidTTL.js';
-import {config} from './config.js';
-
-const CACHE_TTL_SETTING = 'minBidCacheTTL';
+import {getMinBidCacheTTL, onMinBidCacheTTLChange} from './bidTTL.js';
 
 /**
  * Creates new instance of auctionManager. There will only be one instance of auctionManager but
@@ -38,27 +35,14 @@ const CACHE_TTL_SETTING = 'minBidCacheTTL';
  * @returns {AuctionManager} auctionManagerInstance
  */
 export function newAuctionManager() {
-  let minCacheTTL = null;
-
   const _auctions = ttlCollection({
     startTime: (au) => au.end.then(() => au.getAuctionEnd()),
-    ttl: (au) => minCacheTTL == null ? null : au.end.then(() => {
-      return Math.max(minCacheTTL, ...au.getBidsReceived().map(getTTL)) * 1000
+    ttl: (au) => getMinBidCacheTTL() == null ? null : au.end.then(() => {
+      return Math.max(getMinBidCacheTTL(), ...au.getBidsReceived().map(bid => bid.ttl)) * 1000
     }),
   });
 
-  onTTLBufferChange(() => {
-    if (minCacheTTL != null) _auctions.refresh();
-  })
-
-  config.getConfig(CACHE_TTL_SETTING, (cfg) => {
-    const prev = minCacheTTL;
-    minCacheTTL = cfg?.[CACHE_TTL_SETTING];
-    minCacheTTL = typeof minCacheTTL === 'number' ? minCacheTTL : null;
-    if (prev !== minCacheTTL) {
-      _auctions.refresh();
-    }
-  })
+  onMinBidCacheTTLChange(() => _auctions.refresh());
 
   const auctionManager = {
     onExpiry: _auctions.onExpiry
diff --git a/src/bidTTL.js b/src/bidTTL.js
index 55ba0c026b0..d685c4aa4f0 100644
--- a/src/bidTTL.js
+++ b/src/bidTTL.js
@@ -1,25 +1,35 @@
 import {config} from './config.js';
 import {logError} from './utils.js';
+const CACHE_TTL_SETTING = 'minBidCacheTTL';
 let TTL_BUFFER = 1;
-
+let minCacheTTL = null;
 const listeners = [];
 
 config.getConfig('ttlBuffer', (cfg) => {
   if (typeof cfg.ttlBuffer === 'number') {
-    const prev = TTL_BUFFER;
     TTL_BUFFER = cfg.ttlBuffer;
-    if (prev !== TTL_BUFFER) {
-      listeners.forEach(l => l(TTL_BUFFER))
-    }
   } else {
     logError('Invalid value for ttlBuffer', cfg.ttlBuffer);
   }
 })
 
-export function getTTL(bid) {
+export function getBufferedTTL(bid) {
   return bid.ttl - (bid.hasOwnProperty('ttlBuffer') ? bid.ttlBuffer : TTL_BUFFER);
 }
 
-export function onTTLBufferChange(listener) {
+export function getMinBidCacheTTL() {
+  return minCacheTTL;
+}
+
+config.getConfig(CACHE_TTL_SETTING, (cfg) => {
+  const prev = minCacheTTL;
+  minCacheTTL = cfg?.[CACHE_TTL_SETTING];
+  minCacheTTL = typeof minCacheTTL === 'number' ? minCacheTTL : null;
+  if (prev !== minCacheTTL) {
+    listeners.forEach(l => l(minCacheTTL))
+  }
+})
+
+export function onMinBidCacheTTLChange(listener) {
   listeners.push(listener);
 }
diff --git a/src/targeting.js b/src/targeting.js
index aecc29787c7..55783c9632c 100644
--- a/src/targeting.js
+++ b/src/targeting.js
@@ -1,5 +1,5 @@
 import { auctionManager } from './auctionManager.js';
-import { getTTL } from './bidTTL.js';
+import { getBufferedTTL } from './bidTTL.js';
 import { bidderSettings } from './bidderSettings.js';
 import { config } from './config.js';
 import {
@@ -48,7 +48,7 @@ export const TARGETING_KEYS_ARR = Object.keys(TARGETING_KEYS).map(
 );
 
 // return unexpired bids
-const isBidNotExpired = (bid) => (bid.responseTimestamp + getTTL(bid) * 1000) > timestamp();
+const isBidNotExpired = (bid) => (bid.responseTimestamp + getBufferedTTL(bid) * 1000) > timestamp();
 
 // return bids whose status is not set. Winning bids can only have a status of `rendered`.
 const isUnusedBid = (bid) => bid && ((bid.status && !includes([BID_STATUS.RENDERED], bid.status)) || !bid.status);
diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js
index afc90fae342..72a8940e4d8 100644
--- a/test/spec/auctionmanager_spec.js
+++ b/test/spec/auctionmanager_spec.js
@@ -29,6 +29,7 @@ import { setConfig as setCurrencyConfig } from '../../modules/currency.js';
 import { REJECTION_REASON } from '../../src/constants.js';
 import { setDocumentHidden } from './unit/utils/focusTimeout_spec.js';
 import {sandbox} from 'sinon';
+import {getMinBidCacheTTL, onMinBidCacheTTLChange} from '../../src/bidTTL.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -849,7 +850,23 @@ describe('auctionmanager.js', function () {
       await auction.requestsDone;
     })
 
-    describe('stale auctions', () => {
+    describe('setConfig(minBidCacheTTL)', () => {
+      it('should update getMinBidCacheTTL', () => {
+        expect(getMinBidCacheTTL()).to.eql(null);
+        config.setConfig({minBidCacheTTL: 123});
+        expect(getMinBidCacheTTL()).to.eql(123);
+      });
+
+      it('should run listeners registered with onMinBidCacheTTLChange', () => {
+        config.setConfig({minBidCacheTTL: 1});
+        let newTTL = null;
+        onMinBidCacheTTLChange((ttl) => { newTTL = ttl; });
+        config.setConfig({minBidCacheTTL: 2});
+        expect(newTTL).to.eql(2);
+      })
+    })
+
+    describe('minBidCacheTTL', () => {
       let clock, auction;
       beforeEach(() => {
         clock = sinon.useFakeTimers();
@@ -861,79 +878,121 @@ describe('auctionmanager.js', function () {
         config.resetConfig();
       });
 
-      it('are dropped after their last bid becomes stale (if minBidCacheTTL is set)', () => {
-        config.setConfig({
-          minBidCacheTTL: 0
-        });
-        bids = [
-          {
-            adUnitCode: ADUNIT_CODE,
-            adUnitId: ADUNIT_CODE,
-            ttl: 10
-          }, {
-            adUnitCode: ADUNIT_CODE,
-            adUnitId: ADUNIT_CODE,
-            ttl: 100
-          }
-        ];
-        auction.callBids();
-        return auction.end.then(() => {
-          clock.tick(50 * 1000);
-          expect(auctionManager.getBidsReceived().length).to.equal(2);
-          clock.tick(56 * 1000);
-          expect(auctionManager.getBidsReceived()).to.eql([]);
+      describe('individual bids', () => {
+        beforeEach(() => {
+          bids = [
+            {
+              adUnitCode: ADUNIT_CODE,
+              adUnitId: ADUNIT_CODE,
+              ttl: 10
+            }, {
+              adUnitCode: ADUNIT_CODE,
+              adUnitId: ADUNIT_CODE,
+              ttl: 100
+            }
+          ];
+        })
+        it('are dropped when stale (if minBidCacheTTL is set)', () => {
+          config.setConfig({
+            minBidCacheTTL: 30
+          });
+          auction.callBids();
+          return auction.end.then(() => {
+            clock.tick(20 * 1000);
+            expect(auctionManager.getBidsReceived().length).to.equal(2);
+            clock.tick(50 * 1000);
+            expect(auctionManager.getBidsReceived().length).to.equal(1);
+          });
         });
-      });
 
-      it('are dropped after `minBidCacheTTL` seconds if they had no bid', () => {
-        auction.callBids();
-        config.setConfig({
-          minBidCacheTTL: 2
-        });
-        return auction.end.then(() => {
-          expect(auctionManager.getNoBids().length).to.eql(1);
-          clock.tick(10 * 10000);
-          expect(auctionManager.getNoBids().length).to.eql(0);
+        it('pick up updates to minBidCacheTTL that happen during bid lifetime', () => {
+          auction.callBids();
+          return auction.end.then(() => {
+            clock.tick(10 * 1000);
+            config.setConfig({
+              minBidCacheTTL: 20
+            })
+            clock.tick(20 * 1000);
+            expect(auctionManager.getBidsReceived().length).to.equal(1);
+          });
         })
-      });
+      })
 
-      it('are not dropped after `minBidCacheTTL` seconds if the page was hidden', () => {
-        auction.callBids();
-        config.setConfig({
-          minBidCacheTTL: 10
+      describe('stale auctions', () => {
+        it('are dropped after their last bid becomes stale (if minBidCacheTTL is set)', () => {
+          config.setConfig({
+            minBidCacheTTL: 90
+          });
+          bids = [
+            {
+              adUnitCode: ADUNIT_CODE,
+              adUnitId: ADUNIT_CODE,
+              ttl: 10
+            }, {
+              adUnitCode: ADUNIT_CODE,
+              adUnitId: ADUNIT_CODE,
+              ttl: 100
+            }
+          ];
+          auction.callBids();
+          return auction.end.then(() => {
+            clock.tick(50 * 1000);
+            expect(auctionManager.getBidsReceived().length).to.equal(2);
+            clock.tick(56 * 1000);
+            expect(auctionManager.getBidsReceived()).to.eql([]);
+          });
         });
-        return auction.end.then(() => {
-          expect(auctionManager.getNoBids().length).to.eql(1);
-          setDocumentHidden(true);
-          clock.tick(10 * 10000);
-          setDocumentHidden(false);
-          expect(auctionManager.getNoBids().length).to.eql(1);
-        })
-      });
 
-      Object.entries({
-        'bids': {
-          bd: [{
-            adUnitCode: ADUNIT_CODE,
-            adUnitId: ADUNIT_CODE,
-            ttl: 10
-          }],
-          entries: () => auctionManager.getBidsReceived()
-        },
-        'no bids': {
-          bd: [],
-          entries: () => auctionManager.getNoBids()
-        }
-      }).forEach(([t, {bd, entries}]) => {
-        it(`with ${t} are never dropped if minBidCacheTTL is not set`, () => {
-          bids = bd;
+        it('are dropped after `minBidCacheTTL` seconds if they had no bid', () => {
           auction.callBids();
+          config.setConfig({
+            minBidCacheTTL: 2
+          });
           return auction.end.then(() => {
-            clock.tick(100 * 1000);
-            expect(entries().length > 0).to.be.true;
+            expect(auctionManager.getNoBids().length).to.eql(1);
+            clock.tick(10 * 10000);
+            expect(auctionManager.getNoBids().length).to.eql(0);
           })
-        })
-      });
+        });
+
+        it('are not dropped after `minBidCacheTTL` seconds if the page was hidden', () => {
+          auction.callBids();
+          config.setConfig({
+            minBidCacheTTL: 10
+          });
+          return auction.end.then(() => {
+            expect(auctionManager.getNoBids().length).to.eql(1);
+            setDocumentHidden(true);
+            clock.tick(10 * 10000);
+            setDocumentHidden(false);
+            expect(auctionManager.getNoBids().length).to.eql(1);
+          })
+        });
+
+        Object.entries({
+          'bids': {
+            bd: [{
+              adUnitCode: ADUNIT_CODE,
+              adUnitId: ADUNIT_CODE,
+              ttl: 10
+            }],
+            entries: () => auctionManager.getBidsReceived()
+          },
+          'no bids': {
+            bd: [],
+            entries: () => auctionManager.getNoBids()
+          }
+        }).forEach(([t, {bd, entries}]) => {
+          it(`with ${t} are never dropped if minBidCacheTTL is not set`, () => {
+            bids = bd;
+            auction.callBids();
+            return auction.end.then(() => {
+              clock.tick(100 * 1000);
+              expect(entries().length > 0).to.be.true;
+            })
+          })
+        });
+      })
     })
   });
 
@@ -1443,10 +1502,10 @@ describe('auctionmanager.js', function () {
 
     it('should not alter bid requestID', function () {
       auction.callBids();
-
-      const addedBid2 = auction.getBidsReceived().pop();
+      const bidsReceived = auction.getBidsReceived();
+      const addedBid2 = bidsReceived.pop();
       assert.equal(addedBid2.requestId, bids1[0].requestId);
-      const addedBid1 = auction.getBidsReceived().pop();
+      const addedBid1 = bidsReceived.pop();
       assert.equal(addedBid1.requestId, bids[0].requestId);
     });
 

From 7d7e5403c15b43098f0694bed35b53bd74486849 Mon Sep 17 00:00:00 2001
From: mkomorski 
Date: Wed, 4 Dec 2024 21:49:51 +0100
Subject: [PATCH 0731/1097] Various bid adapters: Currency config cleanup
 (#12102)

* cleanup currency poc

* update

* update

* update

* update

* fix

* investigation

* review fixes

* Do not change ortbConverter priority

* Update admaticBidAdapter.js

* Update missenaBidAdapter.js

* Update missenaBidAdapter.js

* Update admaticBidAdapter.js

* lint fix

---------

Co-authored-by: Marcin Komorski 
Co-authored-by: Marcin Komorski 
Co-authored-by: Demetrio Girardi 
Co-authored-by: Patrick McCann 
---
 libraries/ortb2Utils/currency.js              |   3 +
 modules/adfBidAdapter.js                      |   3 +-
 modules/adgenerationBidAdapter.js             |  27 +--
 modules/admaticBidAdapter.js                  |  14 +-
 modules/adotBidAdapter.js                     |  17 +-
 modules/audiencerunBidAdapter.js              |   9 +-
 modules/beopBidAdapter.js                     |  14 +-
 modules/carodaBidAdapter.js                   |   5 +-
 modules/cointrafficBidAdapter.js              |   3 +-
 modules/currency.js                           |  14 +-
 modules/deltaprojectsBidAdapter.js            |  12 +-
 modules/dianomiBidAdapter.js                  |   3 +-
 modules/dsp_genieeBidAdapter.js               |   3 +-
 modules/finativeBidAdapter.js                 |   4 +-
 modules/gmosspBidAdapter.js                   |  17 +-
 modules/koblerBidAdapter.js                   |  31 +++-
 modules/livewrappedBidAdapter.js              |   3 +-
 modules/mgidBidAdapter.js                     |   3 +-
 modules/missenaBidAdapter.js                  |   4 +-
 modules/nexx360BidAdapter.js                  |   4 +-
 modules/richaudienceBidAdapter.js             |   3 +-
 modules/silverpushBidAdapter.js               |  11 +-
 modules/smartadserverBidAdapter.js            |   3 +-
 modules/smilewantedBidAdapter.js              |   4 +-
 modules/sspBCBidAdapter.js                    |   6 +-
 modules/sublimeBidAdapter.js                  |   3 +-
 modules/visxBidAdapter.js                     |   4 +-
 modules/voxBidAdapter.js                      |  10 +-
 modules/yandexBidAdapter.js                   |   4 +-
 test/spec/modules/adfBidAdapter_spec.js       |  65 +++----
 .../modules/adgenerationBidAdapter_spec.js    |  54 +++---
 test/spec/modules/beopBidAdapter_spec.js      |  35 ++--
 test/spec/modules/carodaBidAdapter_spec.js    |  26 ++-
 test/spec/modules/currency_spec.js            |  14 ++
 test/spec/modules/dianomiBidAdapter_spec.js   |  30 ++--
 .../spec/modules/dsp_genieeBidAdapter_spec.js |  17 +-
 test/spec/modules/koblerBidAdapter_spec.js    |  41 +++--
 .../modules/livewrappedBidAdapter_spec.js     |  49 ++----
 .../modules/smartadserverBidAdapter_spec.js   | 164 +++++++++---------
 test/spec/modules/visxBidAdapter_spec.js      |  73 ++------
 test/spec/modules/voxBidAdapter_spec.js       |  22 ++-
 test/spec/modules/yandexBidAdapter_spec.js    |  23 +--
 42 files changed, 454 insertions(+), 400 deletions(-)
 create mode 100644 libraries/ortb2Utils/currency.js

diff --git a/libraries/ortb2Utils/currency.js b/libraries/ortb2Utils/currency.js
new file mode 100644
index 00000000000..5c2c6b7956d
--- /dev/null
+++ b/libraries/ortb2Utils/currency.js
@@ -0,0 +1,3 @@
+export function getCurrencyFromBidderRequest(bidderRequest) {
+  return bidderRequest?.ortb2?.ext?.prebid?.adServerCurrency;
+}
diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js
index b74c3efc628..dc2cab498ea 100644
--- a/modules/adfBidAdapter.js
+++ b/modules/adfBidAdapter.js
@@ -6,6 +6,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import {deepAccess, deepClone, deepSetValue, mergeDeep, parseSizesInput, setOnAny} from '../src/utils.js';
 import {config} from '../src/config.js';
 import {Renderer} from '../src/Renderer.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const { getConfig } = config;
 
@@ -60,7 +61,7 @@ export const spec = {
     const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net';
     const tid = bidderRequest.ortb2?.source?.tid;
     const test = setOnAny(validBidRequests, 'params.test');
-    const currency = getConfig('currency.adServerCurrency');
+    const currency = getCurrencyFromBidderRequest(bidderRequest);
     const cur = currency && [ currency ];
     const eids = setOnAny(validBidRequests, 'userIdAsEids');
     const schain = setOnAny(validBidRequests, 'schain');
diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js
index 16375d92194..3ef9e495ea2 100644
--- a/modules/adgenerationBidAdapter.js
+++ b/modules/adgenerationBidAdapter.js
@@ -1,10 +1,10 @@
-import {deepAccess, getBidIdParameter} from '../src/utils.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER, NATIVE} from '../src/mediaTypes.js';
-import {config} from '../src/config.js';
-import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
-import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js';
-import {escapeUnsafeChars} from '../libraries/htmlEscape/htmlEscape.js';
+import { escapeUnsafeChars } from '../libraries/htmlEscape/htmlEscape.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE } from '../src/mediaTypes.js';
+import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
+import { deepAccess, getBidIdParameter } from '../src/utils.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -61,7 +61,7 @@ export const spec = {
       data = tryAppendQueryString(data, 't', 'json3');
       data = tryAppendQueryString(data, 'transactionid', validReq.ortb2Imp?.ext?.tid);
       data = tryAppendQueryString(data, 'sizes', getSizes(validReq));
-      data = tryAppendQueryString(data, 'currency', getCurrencyType());
+      data = tryAppendQueryString(data, 'currency', getCurrencyType(bidderRequest));
       data = tryAppendQueryString(data, 'pbver', '$prebid.version$');
       data = tryAppendQueryString(data, 'sdkname', 'prebidjs');
       data = tryAppendQueryString(data, 'adapterver', ADGENE_PREBID_VERSION);
@@ -94,7 +94,8 @@ export const spec = {
         method: 'GET',
         url: url,
         data: data,
-        bidRequest: validBidRequests[i]
+        bidRequest: validBidRequests[i],
+        bidderRequest
       });
     }
     return serverRequests;
@@ -119,7 +120,7 @@ export const spec = {
       height: body.h ? body.h : 1,
       creativeId: body.creativeid || '',
       dealId: body.dealid || '',
-      currency: getCurrencyType(),
+      currency: getCurrencyFromBidderRequest(bidRequests.bidderRequest),
       netRevenue: true,
       ttl: body.ttl || 10,
     };
@@ -304,9 +305,9 @@ function getSizes(validReq) {
 /**
  * @return {?string} USD or JPY
  */
-function getCurrencyType() {
-  if (config.getConfig('currency.adServerCurrency') && config.getConfig('currency.adServerCurrency').toUpperCase() === 'USD') return 'USD';
-  return 'JPY';
+function getCurrencyType(bidderRequest) {
+  const adServerCurrency = getCurrencyFromBidderRequest(bidderRequest) || ''
+  return adServerCurrency.toUpperCase() === 'USD' ? 'USD' : 'JPY'
 }
 
 /**
diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js
index ed38cc975e8..2a3ec5499e6 100644
--- a/modules/admaticBidAdapter.js
+++ b/modules/admaticBidAdapter.js
@@ -1,8 +1,8 @@
-import {getValue, formatQS, logError, deepAccess, isArray, getBidIdParameter} from '../src/utils.js';
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { config } from '../src/config.js';
-import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 import { Renderer } from '../src/Renderer.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { deepAccess, formatQS, getBidIdParameter, getValue, isArray, logError } from '../src/utils.js';
 import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js';
 import { interpretNativeAd } from '../libraries/precisoUtils/bidNativeUtils.js';
 
@@ -56,6 +56,7 @@ export const spec = {
     const ortb = bidderRequest.ortb2;
     const networkId = getValue(validBidRequests[0].params, 'networkId');
     let host = getValue(validBidRequests[0].params, 'host');
+    const currency = getCurrencyFromBidderRequest(bidderRequest) || 'TRY';
     const bidderName = validBidRequests[0].bidder;
 
     const payload = {
@@ -84,10 +85,7 @@ export const spec = {
       tmax: parseInt(tmax)
     };
 
-    if (config.getConfig('currency.adServerCurrency')) {
-      payload.ext.cur = config.getConfig('currency.adServerCurrency');
-    }
-
+    payload.ext.cur = currency;
     if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
       const consentStr = (bidderRequest.gdprConsent.consentString)
         ? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : '';
diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js
index 18c7e9265bf..3924537061c 100644
--- a/modules/adotBidAdapter.js
+++ b/modules/adotBidAdapter.js
@@ -1,11 +1,12 @@
-import {Renderer} from '../src/Renderer.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
-import {isArray, isBoolean, isFn, isPlainObject, isStr, logError, replaceAuctionPrice} from '../src/utils.js';
-import {find} from '../src/polyfill.js';
-import {config} from '../src/config.js';
-import {OUTSTREAM} from '../src/video.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+import { Renderer } from '../src/Renderer.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
 import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
+import { find } from '../src/polyfill.js';
+import { isArray, isBoolean, isFn, isPlainObject, isStr, logError, replaceAuctionPrice } from '../src/utils.js';
+import { OUTSTREAM } from '../src/video.js';
 import { NATIVE_ASSETS_IDS as NATIVE_ID_MAPPING, NATIVE_ASSETS as NATIVE_PLACEMENTS } from '../libraries/braveUtils/nativeAssets.js';
 
 /**
@@ -310,7 +311,7 @@ function buildImpFromAdUnit(adUnit, bidderRequest) {
   if (!mediaType) return null;
 
   const media = IMP_BUILDER[mediaType](mediaTypes[mediaType], bidderRequest, adUnit)
-  const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY;
+  const currency = getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY;
   const bidfloor = getMainFloor(adUnit, media.format, mediaType, currency);
 
   return {
diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js
index 2e3125e8aa0..df3bbda6a53 100644
--- a/modules/audiencerunBidAdapter.js
+++ b/modules/audiencerunBidAdapter.js
@@ -1,3 +1,7 @@
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { BANNER } from '../src/mediaTypes.js';
 import {
   _each,
   deepAccess,
@@ -8,9 +12,6 @@ import {
   logError,
   triggerPixel,
 } from '../src/utils.js';
-import {config} from '../src/config.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER} from '../src/mediaTypes.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -136,7 +137,7 @@ export const spec = {
       referer: deepAccess(bidderRequest, 'refererInfo.topmostLocation'),
       // TODO: please do not send internal data structures over the network
       refererInfo: deepAccess(bidderRequest, 'refererInfo.legacy'),
-      currencyCode: config.getConfig('currency.adServerCurrency'),
+      currencyCode: getCurrencyFromBidderRequest(bidderRequest),
       timeout: config.getConfig('bidderTimeout'),
       bids,
     };
diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js
index 5b9e57628c6..a24579af9a9 100644
--- a/modules/beopBidAdapter.js
+++ b/modules/beopBidAdapter.js
@@ -1,3 +1,7 @@
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { getRefererInfo } from '../src/refererDetection.js';
 import {
   buildUrl,
   deepAccess, generateUUID, getBidIdParameter,
@@ -8,11 +12,7 @@ import {
   logWarn,
   triggerPixel
 } from '../src/utils.js';
-import {getRefererInfo} from '../src/refererDetection.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
 import { getStorageManager } from '../src/storageManager.js';
-import {config} from '../src/config.js';
-import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
@@ -56,7 +56,7 @@ export const spec = {
    * @return ServerRequest Info describing the request to the BeOp's server
    */
   buildRequests: function(validBidRequests, bidderRequest) {
-    const slots = validBidRequests.map(beOpRequestSlotsMaker);
+    const slots = validBidRequests.map((bid) => beOpRequestSlotsMaker(bid, bidderRequest));
     const firstPartyData = bidderRequest.ortb2 || {};
     const psegs = firstPartyData.user?.ext?.permutive || firstPartyData.user?.ext?.data?.permutive || [];
     const userBpSegs = firstPartyData.user?.ext?.bpsegs || firstPartyData.user?.ext?.data?.bpsegs || [];
@@ -159,9 +159,9 @@ function buildTrackingParams(data, info, value) {
   };
 }
 
-function beOpRequestSlotsMaker(bid) {
+function beOpRequestSlotsMaker(bid, bidderRequest) {
   const bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes');
-  const publisherCurrency = config.getConfig('currency.adServerCurrency') || getValue(bid.params, 'currency') || 'EUR';
+  const publisherCurrency = getCurrencyFromBidderRequest(bidderRequest) || getValue(bid.params, 'currency') || 'EUR';
   let floor;
   if (typeof bid.getFloor === 'function') {
     const floorInfo = bid.getFloor({currency: publisherCurrency, mediaType: 'banner', size: [1, 1]});
diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js
index a2370a13942..ebe4686630f 100644
--- a/modules/carodaBidAdapter.js
+++ b/modules/carodaBidAdapter.js
@@ -1,7 +1,9 @@
 // jshint esversion: 6, es3: false, node: true
 'use strict'
 
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import {
   deepAccess,
@@ -11,7 +13,6 @@ import {
   sizeTupleToRtbSize,
   sizesToSizeTuples
 } from '../src/utils.js';
-import { config } from '../src/config.js';
 
 const { getConfig } = config;
 
@@ -45,7 +46,7 @@ export const spec = {
       getFirstWithKey(validBidRequests, 'params.priceType') ||
       'net';
     const test = getFirstWithKey(validBidRequests, 'params.test');
-    const currency = getConfig('currency.adServerCurrency');
+    const currency = getCurrencyFromBidderRequest(bidderRequest);
     const eids = getFirstWithKey(validBidRequests, 'userIdAsEids');
     const schain = getFirstWithKey(validBidRequests, 'schain');
     const request = {
diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js
index 3b90529b6cc..c626d1f56aa 100644
--- a/modules/cointrafficBidAdapter.js
+++ b/modules/cointrafficBidAdapter.js
@@ -2,6 +2,7 @@ import { parseSizesInput, logError, isEmpty } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { BANNER } from '../src/mediaTypes.js'
 import { config } from '../src/config.js'
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -45,7 +46,7 @@ export const spec = {
       const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes);
       const currency =
         config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) ||
-        config.getConfig('currency.adServerCurrency') ||
+        getCurrencyFromBidderRequest(bidderRequest) ||
         DEFAULT_CURRENCY;
 
       if (ALLOWED_CURRENCIES.indexOf(currency) === -1) {
diff --git a/modules/currency.js b/modules/currency.js
index d040dc2cf49..b149a1934c3 100644
--- a/modules/currency.js
+++ b/modules/currency.js
@@ -1,4 +1,4 @@
-import {logError, logInfo, logMessage, logWarn} from '../src/utils.js';
+import {deepSetValue, logError, logInfo, logMessage, logWarn} from '../src/utils.js';
 import {getGlobal} from '../src/prebidGlobal.js';
 import { EVENTS, REJECTION_REASON } from '../src/constants.js';
 import {ajax} from '../src/ajax.js';
@@ -8,6 +8,7 @@ import {defer} from '../src/utils/promise.js';
 import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js';
 import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js';
 import {on as onEvent, off as offEvent} from '../src/events.js';
+import { enrichFPD } from '../src/fpd/enrichment.js';
 import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js';
 
 const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$';
@@ -171,6 +172,7 @@ function initCurrency() {
     getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency);
     getHook('addBidResponse').before(addBidResponseHook, 100);
     getHook('responsesReady').before(responsesReadyHook);
+    enrichFPD.before(enrichFPDHook);
     getHook('requestBids').before(requestBidsHook, 50);
     onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout);
     onEvent(EVENTS.AUCTION_INIT, loadRates);
@@ -178,10 +180,11 @@ function initCurrency() {
   }
 }
 
-function resetCurrency() {
+export function resetCurrency() {
   if (currencySupportEnabled) {
     getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove();
     getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove();
+    enrichFPD.getHooks({hook: enrichFPDHook}).remove();
     getHook('requestBids').getHooks({hook: requestBidsHook}).remove();
     offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout);
     offEvent(EVENTS.AUCTION_INIT, loadRates);
@@ -347,6 +350,13 @@ export function setOrtbCurrency(ortbRequest, bidderRequest, context) {
 
 registerOrtbProcessor({type: REQUEST, name: 'currency', fn: setOrtbCurrency});
 
+function enrichFPDHook(next, fpd) {
+  return next(fpd.then(ortb2 => {
+    deepSetValue(ortb2, 'ext.prebid.adServerCurrency', adServerCurrency);
+    return ortb2;
+  }))
+}
+
 export const requestBidsHook = timedAuctionHook('currency', function requestBidsHook(fn, reqBidsConfigObj) {
   const continueAuction = ((that) => () => fn.call(that, reqBidsConfigObj))(this);
 
diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js
index bf5489322c9..2111643b344 100644
--- a/modules/deltaprojectsBidAdapter.js
+++ b/modules/deltaprojectsBidAdapter.js
@@ -1,5 +1,6 @@
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER} from '../src/mediaTypes.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
 import {
   _each,
   _map,
@@ -11,7 +12,6 @@ import {
   logWarn,
   setOnAny
 } from '../src/utils.js';
-import {config} from '../src/config.js';
 
 export const BIDDER_CODE = 'deltaprojects';
 export const BIDDER_ENDPOINT_URL = 'https://d5p.de17a.com/dogfight/prebid';
@@ -74,7 +74,7 @@ function buildRequests(validBidRequests, bidderRequest) {
 
   // build bid specific
   return validBidRequests.map(validBidRequest => {
-    const openRTBRequest = buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs);
+    const openRTBRequest = buildOpenRTBRequest(validBidRequest, bidderRequest, id, site, device, user, tmax, regs);
     return {
       method: 'POST',
       url: BIDDER_ENDPOINT_URL,
@@ -85,9 +85,9 @@ function buildRequests(validBidRequests, bidderRequest) {
   });
 }
 
-function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs) {
+function buildOpenRTBRequest(validBidRequest, bidderRequest, id, site, device, user, tmax, regs) {
   // build cur
-  const currency = config.getConfig('currency.adServerCurrency') || deepAccess(validBidRequest, 'params.currency');
+  const currency = getCurrencyFromBidderRequest(bidderRequest) || deepAccess(validBidRequest, 'params.currency');
   const cur = currency && [currency];
 
   // build impression
diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js
index 777878b5207..3e90a76cf9e 100644
--- a/modules/dianomiBidAdapter.js
+++ b/modules/dianomiBidAdapter.js
@@ -15,6 +15,7 @@ import {
 import { config } from '../src/config.js';
 import { Renderer } from '../src/Renderer.js';
 import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js';
 
 const { getConfig } = config;
@@ -115,7 +116,7 @@ export const spec = {
       setOnAny(validBidRequests, 'params.priceType') ||
       'net';
     const tid = bidderRequest.ortb2?.source?.tid;
-    const currency = getConfig('currency.adServerCurrency');
+    const currency = getCurrencyFromBidderRequest(bidderRequest);
     const cur = currency && [currency];
     const eids = setOnAny(validBidRequests, 'userIdAsEids');
     const schain = setOnAny(validBidRequests, 'schain');
diff --git a/modules/dsp_genieeBidAdapter.js b/modules/dsp_genieeBidAdapter.js
index 57aafd47fc8..f97c13379f3 100644
--- a/modules/dsp_genieeBidAdapter.js
+++ b/modules/dsp_genieeBidAdapter.js
@@ -3,6 +3,7 @@ import { BANNER } from '../src/mediaTypes.js';
 import { ortbConverter } from '../libraries/ortbConverter/converter.js';
 import { deepAccess, deepSetValue } from '../src/utils.js';
 import { config } from '../src/config.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -71,7 +72,7 @@ export const spec = {
     if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || // gdpr
             USPConsent(bidderRequest.uspConsent) || // usp
             config.getConfig('coppa') || // coppa
-            invalidCurrency(config.getConfig('currency.adServerCurrency')) // currency validation
+            invalidCurrency(getCurrencyFromBidderRequest(bidderRequest)) // currency validation
     ) {
       return {
         method: 'GET',
diff --git a/modules/finativeBidAdapter.js b/modules/finativeBidAdapter.js
index e7613bf9cce..0cdcae15e61 100644
--- a/modules/finativeBidAdapter.js
+++ b/modules/finativeBidAdapter.js
@@ -4,8 +4,8 @@
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {NATIVE} from '../src/mediaTypes.js';
 import {_map, deepSetValue, isEmpty, setOnAny} from '../src/utils.js';
-import {config} from '../src/config.js';
 import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const BIDDER_CODE = 'finative';
 const DEFAULT_CUR = 'EUR';
@@ -64,7 +64,7 @@ export const spec = {
     validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
     const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net';
     const tid = bidderRequest.ortb2?.source?.tid;
-    const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR];
+    const cur = [getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CUR];
     let url = bidderRequest.refererInfo.referer;
 
     const imp = validBidRequests.map((bid, id) => {
diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js
index d7af51f7312..e0a5861f40c 100644
--- a/modules/gmosspBidAdapter.js
+++ b/modules/gmosspBidAdapter.js
@@ -1,3 +1,7 @@
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
 import {
   createTrackPixelHtml,
   deepAccess,
@@ -7,10 +11,6 @@ import {
   isEmpty,
   logError
 } from '../src/utils.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {config} from '../src/config.js';
-import {BANNER} from '../src/mediaTypes.js';
-import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -50,7 +50,7 @@ export const spec = {
     const bidRequests = [];
 
     const urlInfo = getUrlInfo(bidderRequest.refererInfo);
-    const cur = getCurrencyType();
+    const cur = getCurrencyType(bidderRequest);
     const dnt = getDNT() ? '1' : '0';
 
     for (let i = 0; i < validBidRequests.length; i++) {
@@ -156,11 +156,8 @@ export const spec = {
 
 };
 
-function getCurrencyType() {
-  if (config.getConfig('currency.adServerCurrency')) {
-    return config.getConfig('currency.adServerCurrency');
-  }
-  return 'JPY';
+function getCurrencyType(bidderRequest) {
+  return getCurrencyFromBidderRequest(bidderRequest) || 'JPY';
 }
 
 function getUrlInfo(refererInfo) {
diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js
index 54e70686fcc..9cde2e96d4e 100644
--- a/modules/koblerBidAdapter.js
+++ b/modules/koblerBidAdapter.js
@@ -7,10 +7,22 @@ import {
   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';
 import {getRefererInfo} from '../src/refererDetection.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
+
+const additionalData = new WeakMap();
+
+export function setAdditionalData(obj, key, value) {
+  const prevValue = additionalData.get(obj) || {};
+  additionalData.set(obj, { ...prevValue, [key]: value });
+}
+
+export function getAdditionalData(obj, key) {
+  const data = additionalData.get(obj) || {};
+  return data[key];
+}
 
 const BIDDER_CODE = 'kobler';
 const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call';
@@ -36,17 +48,18 @@ export const buildRequests = function (validBidRequests, bidderRequest) {
     data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest),
     options: {
       contentType: 'application/json'
-    }
+    },
+    bidderRequest
   };
 };
 
-export const interpretResponse = function (serverResponse) {
+export const interpretResponse = function (serverResponse, request) {
   const res = serverResponse.body;
   const bids = []
   if (res) {
     res.seatbid.forEach(sb => {
       sb.bid.forEach(b => {
-        bids.push({
+        const bid = {
           requestId: b.impid,
           cpm: b.price,
           currency: res.cur,
@@ -61,20 +74,24 @@ export const interpretResponse = function (serverResponse) {
           meta: {
             advertiserDomains: b.adomain
           }
-        })
+        }
+        setAdditionalData(bid, 'adServerCurrency', getCurrencyFromBidderRequest(request.bidderRequest));
+        bids.push(bid);
       })
     });
   }
+
   return bids;
 };
 
 export const onBidWon = function (bid) {
+  const adServerCurrency = getAdditionalData(bid, 'adServerCurrency');
   // 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 publisherCurrency = bid.currency || adServerCurrency || SUPPORTED_CURRENCY;
   const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0);
-  const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY;
+  const adServerPriceCurrency = adServerCurrency || SUPPORTED_CURRENCY;
   if (isStr(bid.nurl) && bid.nurl !== '') {
     const winNotificationUrl = replaceAuctionPrice(bid.nurl, publisherPrice)
       .replace(/\${AUCTION_PRICE_CURRENCY}/g, publisherCurrency)
diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js
index cfbd2b5b3b5..203996c9fb5 100644
--- a/modules/livewrappedBidAdapter.js
+++ b/modules/livewrappedBidAdapter.js
@@ -4,6 +4,7 @@ import {config} from '../src/config.js';
 import {find} from '../src/polyfill.js';
 import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import {getStorageManager} from '../src/storageManager.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -69,7 +70,7 @@ export const spec = {
     bidUrl = bidUrl ? bidUrl.params.bidUrl : URL;
     url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest));
     test = test ? test.params.test : undefined;
-    const currency = config.getConfig('currency.adServerCurrency') || 'USD';
+    const currency = getCurrencyFromBidderRequest(bidderRequest) || 'USD';
     var adRequests = bidRequests.map(b => bidToAdRequest(b, currency));
     const adRequestsContainFloors = adRequests.some(r => r.flr !== undefined);
 
diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js
index a384b9d676c..9e2ea06df69 100644
--- a/modules/mgidBidAdapter.js
+++ b/modules/mgidBidAdapter.js
@@ -22,6 +22,7 @@ import {config} from '../src/config.js';
 import { getStorageManager } from '../src/storageManager.js';
 import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
 import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js'
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -158,7 +159,7 @@ export const spec = {
     if (isStr(muid) && muid.length > 0) {
       url += '?muid=' + muid;
     }
-    const cur = setOnAny(validBidRequests, 'params.currency') || setOnAny(validBidRequests, 'params.cur') || config.getConfig('currency.adServerCurrency') || DEFAULT_CUR;
+    const cur = setOnAny(validBidRequests, 'params.currency') || setOnAny(validBidRequests, 'params.cur') || getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CUR;
     const secure = window.location.protocol === 'https:' ? 1 : 0;
     let imp = [];
     validBidRequests.forEach(bid => {
diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js
index e621e478ce7..ac69fcc9965 100644
--- a/modules/missenaBidAdapter.js
+++ b/modules/missenaBidAdapter.js
@@ -7,10 +7,10 @@ import {
   safeJSONParse,
   triggerPixel,
 } from '../src/utils.js';
-import { config } from '../src/config.js';
 import { BANNER } from '../src/mediaTypes.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { getStorageManager } from '../src/storageManager.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js';
 
 /**
@@ -82,7 +82,7 @@ function toPayload(bidRequest, bidderRequest) {
   const bidFloor = getFloor(bidRequest);
   payload.floor = bidFloor?.floor;
   payload.floor_currency = bidFloor?.currency;
-  payload.currency = config.getConfig('currency.adServerCurrency');
+  payload.currency = getCurrencyFromBidderRequest(bidderRequest);
   payload.schain = bidRequest.schain;
   payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0;
   payload.autoplay = isAutoplayEnabled() === true ? 1 : 0;
diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js
index 9a76b24f223..933c0378f03 100644
--- a/modules/nexx360BidAdapter.js
+++ b/modules/nexx360BidAdapter.js
@@ -1,4 +1,3 @@
-import {config} from '../src/config.js';
 import { deepAccess, deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js';
 import {Renderer} from '../src/Renderer.js';
 import {getStorageManager} from '../src/storageManager.js';
@@ -7,6 +6,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import {getGlobal} from '../src/prebidGlobal.js';
 import {ortbConverter} from '../libraries/ortbConverter/converter.js'
 import { INSTREAM, OUTSTREAM } from '../src/video.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -132,7 +132,7 @@ const converter = ortbConverter({
     deepSetValue(request, 'ext.source', 'prebid.js');
     deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID);
     deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION);
-    deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']);
+    deepSetValue(request, 'cur', [getCurrencyFromBidderRequest(bidderRequest) || 'USD']);
     if (!request.user) request.user = {};
     if (getAmxId()) {
       if (!request.user.ext) request.user.ext = {};
diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js
index cfb0f97fafd..135d2e94b65 100644
--- a/modules/richaudienceBidAdapter.js
+++ b/modules/richaudienceBidAdapter.js
@@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {config} from '../src/config.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {Renderer} from '../src/Renderer.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const BIDDER_CODE = 'richaudience';
 let REFERER = '';
@@ -35,7 +36,7 @@ export const spec = {
         ifa: bid.params.ifa,
         pid: bid.params.pid,
         supplyType: bid.params.supplyType,
-        currencyCode: config.getConfig('currency.adServerCurrency'),
+        currencyCode: getCurrencyFromBidderRequest(bidderRequest),
         auctionId: bid.auctionId,
         bidId: bid.bidId,
         BidRequestsCount: bid.bidRequestsCount,
diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js
index 593e613d603..70c0e475cc4 100644
--- a/modules/silverpushBidAdapter.js
+++ b/modules/silverpushBidAdapter.js
@@ -1,4 +1,3 @@
-import { config } from '../src/config.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import * as utils from '../src/utils.js';
 import { mergeDeep } from '../src/utils.js';
@@ -6,6 +5,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import { ortbConverter } from '../libraries/ortbConverter/converter.js';
 import { Renderer } from '../src/Renderer.js';
 import { ajax } from '../src/ajax.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const BIDDER_CODE = 'silverpush';
 const bidderConfig = 'sp_pb_ortb';
@@ -65,7 +65,7 @@ export const CONVERTER = ortbConverter({
       imp = buildBannerImp(bidRequest, imp);
     }
 
-    const bidFloor = getBidFloor(bidRequest);
+    const bidFloor = getBidFloor(bidRequest, bidRequest.bidderRequest);
 
     utils.deepSetValue(imp, 'bidfloor', bidFloor);
 
@@ -216,7 +216,8 @@ function createRequest(bidRequests, bidderRequest, mediaType) {
   return {
     method: 'POST',
     url: REQUEST_URL,
-    data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } })
+    data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }),
+    bidderRequest
   }
 }
 
@@ -247,8 +248,8 @@ function buildVideoOutstreamResponse(bidResponse, context) {
   return {...bidResponse};
 }
 
-function getBidFloor(bid) {
-  const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY;
+function getBidFloor(bid, bidderRequest) {
+  const currency = getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY;
 
   if (typeof bid.getFloor !== 'function') {
     return utils.deepAccess(bid, 'params.bidFloor', 0.05);
diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js
index e0ddf0a66e6..e3cdb3dc5bf 100644
--- a/modules/smartadserverBidAdapter.js
+++ b/modules/smartadserverBidAdapter.js
@@ -11,6 +11,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import { config } from '../src/config.js';
 import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'
 import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -180,7 +181,7 @@ export const spec = {
    */
   buildRequests: function (validBidRequests, bidderRequest) {
     // use bidderRequest.bids[] to get bidder-dependent request info
-    const adServerCurrency = config.getConfig('currency.adServerCurrency');
+    const adServerCurrency = getCurrencyFromBidderRequest(bidderRequest);
     const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data'));
     const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data'));
 
diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js
index 7f83dc025cb..a78c60f8308 100644
--- a/modules/smilewantedBidAdapter.js
+++ b/modules/smilewantedBidAdapter.js
@@ -1,11 +1,11 @@
 import {deepAccess, deepClone, isArray, isFn, isPlainObject, logError, logWarn} from '../src/utils.js';
 import {Renderer} from '../src/Renderer.js';
-import {config} from '../src/config.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import {INSTREAM, OUTSTREAM} from '../src/video.js';
 import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js'
 import {convertOrtbRequestToProprietaryNative, toOrtbNativeRequest, toLegacyResponse} from '../src/native.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const BIDDER_CODE = 'smilewanted';
 
@@ -66,7 +66,7 @@ export const spec = {
     return validBidRequests.map(bid => {
       const payload = {
         zoneId: bid.params.zoneId,
-        currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR',
+        currencyCode: getCurrencyFromBidderRequest(bidderRequest) || 'EUR',
         tagId: bid.adUnitCode,
         sizes: bid.sizes.map(size => ({
           w: size[0],
diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js
index ba9af3314b0..1227b6fdc76 100644
--- a/modules/sspBCBidAdapter.js
+++ b/modules/sspBCBidAdapter.js
@@ -1,10 +1,10 @@
 import { deepAccess, getWindowTop, isArray, logInfo, logWarn } from '../src/utils.js';
 import { ajax } from '../src/ajax.js';
-import { config } from '../src/config.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
 import { includes as strIncludes } from '../src/polyfill.js';
 import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const BIDDER_CODE = 'sspBC';
 const BIDDER_URL = 'https://ssp.wp.pl/bidder/';
@@ -284,7 +284,7 @@ const getHighestFloor = (slot) => {
  * Get currency (either default or adserver)
  * @returns {string} currency name
  */
-const getCurrency = () => config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY;
+const getCurrency = (bidderRequest) => getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY;
 
 /**
  * Get value for first occurence of key within the collection
@@ -617,7 +617,7 @@ const spec = {
         content: { language: getContentLanguage() },
       },
       imp: validBidRequests.map(slot => mapImpression(slot)),
-      cur: [getCurrency()],
+      cur: [getCurrency(bidderRequest)],
       tmax,
       user: {},
       regs,
diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js
index a29265ce9cd..eaae877ba0e 100644
--- a/modules/sublimeBidAdapter.js
+++ b/modules/sublimeBidAdapter.js
@@ -1,6 +1,7 @@
 import { logInfo, generateUUID, formatQS, triggerPixel, deepAccess } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { config } from '../src/config.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -152,7 +153,7 @@ function buildRequests(validBidRequests, bidderRequest) {
     pbav: SUBLIME_VERSION,
     // Current Prebid params
     prebidVersion: '$prebid.version$',
-    currencyCode: config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY,
+    currencyCode: getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY,
     timeout: (typeof bidderRequest === 'object' && !!bidderRequest) ? bidderRequest.timeout : config.getConfig('bidderTimeout'),
   };
 
diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js
index 217cab48cee..510106845db 100644
--- a/modules/visxBidAdapter.js
+++ b/modules/visxBidAdapter.js
@@ -6,6 +6,7 @@ import {INSTREAM as VIDEO_INSTREAM} from '../src/video.js';
 import {getStorageManager} from '../src/storageManager.js';
 import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
 import { getBidFromResponse } from '../libraries/processResponse/index.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 const BIDDER_CODE = 'visx';
 const GVLID = 154;
@@ -57,9 +58,8 @@ export const spec = {
     const bids = validBidRequests || [];
     const currency =
       config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) ||
-      config.getConfig('currency.adServerCurrency') ||
+      getCurrencyFromBidderRequest(bidderRequest) ||
       DEFAULT_CUR;
-
     let request;
     let reqId;
     let payloadSchain;
diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js
index da72b975717..b0cfdabee9f 100644
--- a/modules/voxBidAdapter.js
+++ b/modules/voxBidAdapter.js
@@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {find} from '../src/polyfill.js';
 import {Renderer} from '../src/Renderer.js';
-import {config} from '../src/config.js';
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -12,17 +12,15 @@ import {config} from '../src/config.js';
  * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests
  */
 
-const { getConfig } = config;
-
 const BIDDER_CODE = 'vox';
 const SSP_ENDPOINT = 'https://ssp.hybrid.ai/auction/prebid';
 const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js';
 const TTL = 60;
 const GVLID = 206;
 
-function buildBidRequests(validBidRequests) {
+function buildBidRequests(validBidRequests, bidderRequest) {
   return _map(validBidRequests, function(bid) {
-    const currency = getConfig('currency.adServerCurrency');
+    const currency = getCurrencyFromBidderRequest(bidderRequest);
     const floorInfo = bid.getFloor ? bid.getFloor({
       currency: currency || 'USD'
     }) : {};
@@ -218,7 +216,7 @@ export const spec = {
       // TODO: is 'page' the right value here?
       url: bidderRequest.refererInfo.page,
       cmp: !!bidderRequest.gdprConsent,
-      bidRequests: buildBidRequests(validBidRequests)
+      bidRequests: buildBidRequests(validBidRequests, bidderRequest)
     };
 
     if (payload.cmp) {
diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js
index 97bd793d633..eed0902e9dc 100644
--- a/modules/yandexBidAdapter.js
+++ b/modules/yandexBidAdapter.js
@@ -1,5 +1,5 @@
+import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { config } from '../src/config.js';
 import { BANNER, NATIVE } from '../src/mediaTypes.js';
 import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
 import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel, logInfo } from '../src/utils.js';
@@ -128,7 +128,7 @@ export const spec = {
       timeout = bidderRequest.timeout;
     }
 
-    const adServerCurrency = config.getConfig('currency.adServerCurrency');
+    const adServerCurrency = getCurrencyFromBidderRequest(bidderRequest);
 
     return validBidRequests.map((bidRequest) => {
       const { params } = bidRequest;
diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js
index 6f4395d548f..14abb33ba68 100644
--- a/test/spec/modules/adfBidAdapter_spec.js
+++ b/test/spec/modules/adfBidAdapter_spec.js
@@ -3,7 +3,8 @@
 import { assert } from 'chai';
 import { spec } from 'modules/adfBidAdapter.js';
 import { config } from 'src/config.js';
-import { createEidsArray } from 'modules/userId/eids.js';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
 
 describe('Adf adapter', function () {
   let bids = [];
@@ -332,12 +333,15 @@ describe('Adf adapter', function () {
     });
 
     it('should send currency if defined', function () {
-      config.setConfig({ currency: { adServerCurrency: 'EUR' } });
       let validBidRequests = [{ params: {} }];
       let refererInfo = { page: 'page' };
-      let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data);
-
-      assert.deepEqual(request.cur, [ 'EUR' ]);
+      const bidderRequest = { refererInfo };
+      setCurrencyConfig({ adServerCurrency: 'EUR' })
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        let request = JSON.parse(spec.buildRequests(validBidRequests, res).data);
+        assert.deepEqual(request.cur, [ 'EUR' ]);
+        setCurrencyConfig({});
+      });
     });
 
     it('should pass supply chain object', function () {
@@ -480,12 +484,14 @@ describe('Adf adapter', function () {
         });
 
         it('should request floor price in adserver currency', function () {
-          config.setConfig({ currency: { adServerCurrency: 'DKK' } });
+          setCurrencyConfig({ adServerCurrency: 'DKK' })
           const validBidRequests = [ getBidWithFloor() ];
-          let imp = getRequestImps(validBidRequests)[0];
-
-          assert.equal(imp.bidfloor, undefined);
-          assert.equal(imp.bidfloorcur, 'DKK');
+          return addFPDToBidderRequest(validBidRequests[0]).then(res => {
+            const imp = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' }, ...res }).data).imp[0];
+            assert.equal(imp.bidfloor, undefined);
+            assert.equal(imp.bidfloorcur, 'DKK');
+            setCurrencyConfig({});
+          });
         });
 
         it('should add correct floor values', function () {
@@ -505,30 +511,29 @@ describe('Adf adapter', function () {
             playerSize: [ 100, 200 ]
           } };
           const expectedFloors = [ 1, 1.3, 0.5 ];
-          config.setConfig({ currency: { adServerCurrency: 'DKK' } });
+          setCurrencyConfig({ adServerCurrency: 'DKK' });
           let validBidRequests = expectedFloors.map(getBidWithFloorTest);
-          getRequestImps(validBidRequests);
-          assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
+          return addFPDToBidderRequest(validBidRequests[0]).then(res => {
+            getRequestImps(validBidRequests, res);
+            assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' })
+            mediaTypes = { banner: {
+              sizes: [ [100, 200], [300, 400] ]
+            }};
+            getRequestImps(validBidRequests, res);
 
-          mediaTypes = { banner: {
-            sizes: [ [100, 200], [300, 400] ]
-          }};
-          validBidRequests = expectedFloors.map(getBidWithFloorTest);
-          getRequestImps(validBidRequests);
+            assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
 
-          assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
+            mediaTypes = { native: {} };
+            getRequestImps(validBidRequests, res);
 
-          mediaTypes = { native: {} };
-          validBidRequests = expectedFloors.map(getBidWithFloorTest);
-          getRequestImps(validBidRequests);
+            assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
 
-          assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
+            mediaTypes = {};
+            getRequestImps(validBidRequests, res);
 
-          mediaTypes = {};
-          validBidRequests = expectedFloors.map(getBidWithFloorTest);
-          getRequestImps(validBidRequests);
-
-          assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
+            assert.deepEqual(result, { currency: 'DKK', size: '*', mediaType: '*' });
+            setCurrencyConfig({});
+          });
 
           function getBidWithFloorTest(floor) {
             return {
@@ -913,8 +918,8 @@ describe('Adf adapter', function () {
       });
     });
 
-    function getRequestImps(validBidRequests) {
-      return JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp;
+    function getRequestImps(validBidRequests, enriched = {}) {
+      return JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' }, ...enriched }).data).imp;
     }
   });
 
diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js
index 7a95d4272fb..c0bb40a1bf2 100644
--- a/test/spec/modules/adgenerationBidAdapter_spec.js
+++ b/test/spec/modules/adgenerationBidAdapter_spec.js
@@ -4,6 +4,8 @@ import {newBidder} from 'src/adapters/bidderFactory.js';
 import {NATIVE} from 'src/mediaTypes.js';
 import {config} from 'src/config.js';
 import prebid from '../../../package.json';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 describe('AdgenerationAdapter', function () {
   const adapter = newBidder(spec);
@@ -248,19 +250,20 @@ describe('AdgenerationAdapter', function () {
       config.resetConfig();
     });
     it('allows setConfig to set bidder currency for USD', function () {
-      config.setConfig({
-        currency: {
-          adServerCurrency: 'USD'
-        }
+      setCurrencyConfig({ adServerCurrency: 'USD' });
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        const bidRequest = spec.buildRequests(bidRequests, res)[0];
+        expect(bidRequest.data).to.equal(data.bannerUSD);
+        setCurrencyConfig({});
       });
-      const request = spec.buildRequests(bidRequests, bidderRequest)[0];
-      expect(request.data).to.equal(data.bannerUSD);
-      config.resetConfig();
     });
   });
   describe('interpretResponse', function () {
     const bidRequests = {
       banner: {
+        bidderRequest: {
+          ortb2: {ext: {prebid: {adServerCurrency: 'JPY'}}}
+        },
         bidRequest: {
           bidder: 'adg',
           params: {
@@ -275,6 +278,9 @@ describe('AdgenerationAdapter', function () {
         },
       },
       native: {
+        bidderRequest: {
+          ortb2: {ext: {prebid: {adServerCurrency: 'JPY'}}}
+        },
         bidRequest: {
           bidder: 'adg',
           params: {
@@ -312,6 +318,9 @@ describe('AdgenerationAdapter', function () {
         },
       },
       upperBillboard: {
+        bidderRequest: {
+          ortb2: {ext: {prebid: {adServerCurrency: 'JPY'}}}
+        },
         bidRequest: {
           bidder: 'adg',
           params: {
@@ -916,21 +925,26 @@ describe('AdgenerationAdapter', function () {
     });
 
     it('handles ADGBrowserM responses', function () {
-      config.setConfig({
-        currency: {
-          adServerCurrency: 'JPY'
+      setCurrencyConfig({ adServerCurrency: 'JPY' });
+      const bidderRequest = {
+        refererInfo: {
+          page: 'https://example.com'
         }
+      };
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        spec.buildRequests(bidRequests, res)[0];
+        const result = spec.interpretResponse({body: serverResponse.normal.upperBillboard}, { ...bidRequests.upperBillboard, bidderRequest: res })[0];
+        expect(result.requestId).to.equal(bidResponses.normal.upperBillboard.requestId);
+        expect(result.width).to.equal(bidResponses.normal.upperBillboard.width);
+        expect(result.height).to.equal(bidResponses.normal.upperBillboard.height);
+        expect(result.creativeId).to.equal(bidResponses.normal.upperBillboard.creativeId);
+        expect(result.dealId).to.equal(bidResponses.normal.upperBillboard.dealId);
+        expect(result.currency).to.equal(bidResponses.normal.upperBillboard.currency);
+        expect(result.netRevenue).to.equal(bidResponses.normal.upperBillboard.netRevenue);
+        expect(result.ttl).to.equal(bidResponses.normal.upperBillboard.ttl);
+        expect(result.ad).to.equal(bidResponses.normal.upperBillboard.ad);
+        setCurrencyConfig({});
       });
-      const result = spec.interpretResponse({body: serverResponse.normal.upperBillboard}, bidRequests.upperBillboard)[0];
-      expect(result.requestId).to.equal(bidResponses.normal.upperBillboard.requestId);
-      expect(result.width).to.equal(bidResponses.normal.upperBillboard.width);
-      expect(result.height).to.equal(bidResponses.normal.upperBillboard.height);
-      expect(result.creativeId).to.equal(bidResponses.normal.upperBillboard.creativeId);
-      expect(result.dealId).to.equal(bidResponses.normal.upperBillboard.dealId);
-      expect(result.currency).to.equal(bidResponses.normal.upperBillboard.currency);
-      expect(result.netRevenue).to.equal(bidResponses.normal.upperBillboard.netRevenue);
-      expect(result.ttl).to.equal(bidResponses.normal.upperBillboard.ttl);
-      expect(result.ad).to.equal(bidResponses.normal.upperBillboard.ad);
     });
 
     it('handles banner responses for empty adomain', function () {
diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js
index 0a752761531..3f06cd04910 100644
--- a/test/spec/modules/beopBidAdapter_spec.js
+++ b/test/spec/modules/beopBidAdapter_spec.js
@@ -2,6 +2,8 @@ import { expect } from 'chai';
 import { spec } from 'modules/beopBidAdapter.js';
 import { newBidder } from 'src/adapters/bidderFactory.js';
 import { config } from 'src/config.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 const utils = require('src/utils');
 
 const ENDPOINT = 'https://hb.beop.io/bid';
@@ -92,18 +94,27 @@ describe('BeOp Bid Adapter tests', () => {
     bidRequests.push(validBid);
 
     it('should build the request', function () {
-      config.setConfig({'currency': {'adServerCurrency': 'USD'}});
-      const request = spec.buildRequests(bidRequests, {});
-      const payload = JSON.parse(request.data);
-      const url = request.url;
-      expect(url).to.equal(ENDPOINT);
-      expect(payload.pid).to.exist;
-      expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad');
-      expect(payload.gdpr_applies).to.exist;
-      expect(payload.gdpr_applies).to.equals(false);
-      expect(payload.slts[0].name).to.exist;
-      expect(payload.slts[0].name).to.equal('bellow-article');
-      expect(payload.slts[0].flr).to.equal(10);
+      const bidderRequest = {
+        refererInfo: {
+          page: 'https://example.com'
+        }
+      };
+      setCurrencyConfig({ adServerCurrency: 'USD' })
+
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        const request = spec.buildRequests(bidRequests, res);
+        const payload = JSON.parse(request.data);
+        const url = request.url;
+        expect(url).to.equal(ENDPOINT);
+        expect(payload.pid).to.exist;
+        expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad');
+        expect(payload.gdpr_applies).to.exist;
+        expect(payload.gdpr_applies).to.equals(false);
+        expect(payload.slts[0].name).to.exist;
+        expect(payload.slts[0].name).to.equal('bellow-article');
+        expect(payload.slts[0].flr).to.equal(10);
+        setCurrencyConfig({});
+      });
     });
 
     it('should call the endpoint with GDPR consent and pageURL info if found', function () {
diff --git a/test/spec/modules/carodaBidAdapter_spec.js b/test/spec/modules/carodaBidAdapter_spec.js
index bf4557d7a8a..780c81ebe9f 100644
--- a/test/spec/modules/carodaBidAdapter_spec.js
+++ b/test/spec/modules/carodaBidAdapter_spec.js
@@ -3,6 +3,8 @@ import { assert } from 'chai';
 import { spec } from 'modules/carodaBidAdapter.js';
 import { config } from 'src/config.js';
 import { createEidsArray } from 'modules/userId/eids.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 describe('Caroda adapter', function () {
   let bids = [];
@@ -185,12 +187,14 @@ describe('Caroda adapter', function () {
     });
 
     it('should send currency if defined', function () {
-      config.setConfig({ currency: { adServerCurrency: 'EUR' } });
+      setCurrencyConfig({ adServerCurrency: 'EUR' });
       let validBidRequests = [{ params: {} }];
-      let refererInfo = { page: 'page' };
-      let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo })[0].data);
-
-      assert.deepEqual(request.currency, 'EUR');
+      const bidderRequest = { refererInfo: { page: 'page' } };
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        let request = JSON.parse(spec.buildRequests(validBidRequests, res)[0].data);
+        assert.deepEqual(request.currency, 'EUR');
+        setCurrencyConfig({});
+      });
     });
 
     it('should pass extended ids', function () {
@@ -301,11 +305,15 @@ describe('Caroda adapter', function () {
         });
 
         it('should request floor price in adserver currency', function () {
-          config.setConfig({ currency: { adServerCurrency: 'DKK' } });
           const validBidRequests = [ getBidWithFloor() ];
-          const imp = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } })[0].data);
-          assert.equal(imp.bidfloor, undefined);
-          assert.equal(imp.bidfloorcur, 'DKK');
+          setCurrencyConfig({ adServerCurrency: 'DKK' });
+          const bidderRequest = { refererInfo: { page: 'page' } };
+          return addFPDToBidderRequest(bidderRequest).then(res => {
+            const imp = JSON.parse(spec.buildRequests(validBidRequests, res)[0].data);
+            assert.equal(imp.bidfloor, undefined);
+            assert.equal(imp.bidfloorcur, 'DKK');
+            setCurrencyConfig({});
+          });
         });
 
         it('should add correct floor values', function () {
diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js
index 149a4380036..8685bc1266f 100644
--- a/test/spec/modules/currency_spec.js
+++ b/test/spec/modules/currency_spec.js
@@ -17,6 +17,7 @@ import * as utils from 'src/utils.js';
 import {EVENTS, STATUS, REJECTION_REASON} from '../../../src/constants.js';
 import {server} from '../../mocks/xhr.js';
 import * as events from 'src/events.js';
+import { enrichFPD } from '../../../src/fpd/enrichment.js';
 import {requestBidsHook} from '../../../modules/currency.js';
 
 var assert = require('chai').assert;
@@ -525,6 +526,19 @@ describe('currency', function () {
     });
   });
 
+  describe('enrichFpd', function() {
+    function fpd(ortb2 = {}) {
+      return enrichFPD(Promise.resolve(ortb2));
+    }
+    it('should set adServerCurrency on ortb', function () {
+      fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates()));
+      setConfig({ adServerCurrency: 'EUR' });
+      return fpd({}).then((ortb) => {
+        expect(ortb.ext.prebid.adServerCurrency).to.eql('EUR')
+      })
+    })
+  });
+
   describe('auctionDelay param', () => {
     const continueAuction = sinon.stub();
     let logWarnSpy;
diff --git a/test/spec/modules/dianomiBidAdapter_spec.js b/test/spec/modules/dianomiBidAdapter_spec.js
index b1ba5f60540..ef9283d3dad 100644
--- a/test/spec/modules/dianomiBidAdapter_spec.js
+++ b/test/spec/modules/dianomiBidAdapter_spec.js
@@ -3,6 +3,8 @@ import { assert } from 'chai';
 import { spec } from 'modules/dianomiBidAdapter.js';
 import { config } from 'src/config.js';
 import { createEidsArray } from 'modules/userId/eids.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 describe('Dianomi adapter', () => {
   let bids = [];
@@ -267,12 +269,14 @@ describe('Dianomi adapter', () => {
     });
 
     it('should send currency if defined', () => {
-      config.setConfig({ currency: { adServerCurrency: 'EUR' } });
+      setCurrencyConfig({ adServerCurrency: 'EUR' })
       let validBidRequests = [{ params: { smartadId: 1234 } }];
       let refererInfo = { page: 'page' };
-      let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data);
-
-      assert.deepEqual(request.cur, ['EUR']);
+      return addFPDToBidderRequest({ refererInfo }).then(res => {
+        let request = JSON.parse(spec.buildRequests(validBidRequests, res).data);
+        assert.deepEqual(request.cur, ['EUR']);
+        setCurrencyConfig({});
+      });
     });
 
     it('should pass supply chain object', () => {
@@ -394,12 +398,18 @@ describe('Dianomi adapter', () => {
         });
 
         it('should request floor price in adserver currency', () => {
-          config.setConfig({ currency: { adServerCurrency: 'GBP' } });
-          const validBidRequests = [getBidWithFloor()];
-          let imp = getRequestImps(validBidRequests)[0];
-
-          assert.equal(imp.bidfloor, undefined);
-          assert.equal(imp.bidfloorcur, 'GBP');
+          setCurrencyConfig({ adServerCurrency: 'GBP' })
+          let validBidRequests = [getBidWithFloor()];
+          let refererInfo = { page: 'page' };
+          return addFPDToBidderRequest({ refererInfo }).then(res => {
+            let imp = JSON.parse(
+              spec.buildRequests(validBidRequests, res).data
+            ).imp[0];
+
+            assert.equal(imp.bidfloor, undefined);
+            assert.equal(imp.bidfloorcur, 'GBP');
+            setCurrencyConfig({});
+          });
         });
 
         it('should add correct floor values', () => {
diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js
index 576fd9e404b..b708acffc0b 100644
--- a/test/spec/modules/dsp_genieeBidAdapter_spec.js
+++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js
@@ -1,6 +1,8 @@
 import { expect } from 'chai';
 import { spec } from 'modules/dsp_genieeBidAdapter.js';
 import { config } from 'src/config';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 describe('Geniee adapter tests', () => {
   const validBidderRequest = {
@@ -74,13 +76,16 @@ describe('Geniee adapter tests', () => {
       config.resetConfig();
     });
     it('uncomfortable (currency)', () => {
-      config.setConfig({ currency: { adServerCurrency: 'TWD' } });
-      const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest);
-      expect(request).deep.equal({
-        method: 'GET',
-        url: 'https://rt.gsspat.jp/prebid_uncomfortable',
+      setCurrencyConfig({ adServerCurrency: 'TWD' });
+      return addFPDToBidderRequest(validBidderRequest).then(res => {
+        const request = spec.buildRequests(validBidderRequest.bids, res);
+        expect(request).deep.equal({
+          method: 'GET',
+          url: 'https://rt.gsspat.jp/prebid_uncomfortable',
+        });
+        setCurrencyConfig({});
+        config.resetConfig();
       });
-      config.resetConfig();
     });
   });
   describe('interpretResponse function test', () => {
diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js
index 74c0a1f5967..47b89bb5956 100644
--- a/test/spec/modules/koblerBidAdapter_spec.js
+++ b/test/spec/modules/koblerBidAdapter_spec.js
@@ -4,6 +4,8 @@ import {newBidder} from 'src/adapters/bidderFactory.js';
 import {config} from 'src/config.js';
 import * as utils from 'src/utils.js';
 import {getRefererInfo} from 'src/refererDetection.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 function createBidderRequest(auctionId, timeout, pageUrl, addGdprConsent) {
   const gdprConsent = addGdprConsent ? {
@@ -598,7 +600,7 @@ describe('KoblerAdapter', function () {
           cur: 'USD'
         }
       };
-      const bids = spec.interpretResponse(responseWithTwoBids)
+      const bids = spec.interpretResponse(responseWithTwoBids, {})
 
       const expectedBids = [
         {
@@ -665,25 +667,30 @@ describe('KoblerAdapter', function () {
     });
 
     it('Should trigger pixel with replaced nurl if nurl is not empty', function () {
-      config.setConfig({
-        'currency': {
-          'adServerCurrency': 'NOK'
-        }
-      });
-      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}',
-        adserverTargeting: {
+      setCurrencyConfig({ adServerCurrency: 'NOK' });
+      let validBidRequests = [{ params: {} }];
+      let refererInfo = { page: 'page' };
+      const bidderRequest = { refererInfo };
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        JSON.parse(spec.buildRequests(validBidRequests, res).data);
+        const bids = spec.interpretResponse({ body: { seatbid: [{ bid: [{
+          originalCpm: 1.532,
+          price: 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}',
+        }]}]}}, { bidderRequest: res });
+        const bidToWon = bids[0];
+        bidToWon.adserverTargeting = {
           hb_pb: 8
         }
-      });
+        spec.onBidWon(bidToWon);
 
-      expect(utils.triggerPixel.callCount).to.be.equal(1);
-      expect(utils.triggerPixel.firstCall.args[0]).to.be.equal(
-        'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=8.341&sp_cur=NOK&asp=8&asp_cur=NOK'
-      );
+        expect(utils.triggerPixel.callCount).to.be.equal(1);
+        expect(utils.triggerPixel.firstCall.args[0]).to.be.equal(
+          'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=8.341&sp_cur=NOK&asp=8&asp_cur=NOK'
+        );
+        setCurrencyConfig({});
+      });
     });
   });
 
diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js
index 5ab00859d81..7a3cd26dbe7 100644
--- a/test/spec/modules/livewrappedBidAdapter_spec.js
+++ b/test/spec/modules/livewrappedBidAdapter_spec.js
@@ -3,6 +3,8 @@ import {spec, storage} from 'modules/livewrappedBidAdapter.js';
 import {config} from 'src/config.js';
 import * as utils from 'src/utils.js';
 import { NATIVE, VIDEO } from 'src/mediaTypes.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 describe('Livewrapped adapter tests', function () {
   let sandbox,
@@ -1178,50 +1180,21 @@ describe('Livewrapped adapter tests', function () {
       sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
       sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true);
 
-      let origGetConfig = config.getConfig;
-      sandbox.stub(config, 'getConfig').callsFake(function (key) {
-        if (key === 'currency.adServerCurrency') {
-          return 'EUR';
-        }
-        return origGetConfig.apply(config, arguments);
-      });
-
+      setCurrencyConfig({ adServerCurrency: 'EUR' });
       let testbidRequest = clone(bidderRequest);
       let bids = testbidRequest.bids.map(b => {
         b.getFloor = function () { return { floor: 10, currency: 'EUR' }; }
         return b;
       });
-      let result = spec.buildRequests(bids, testbidRequest);
-      let data = JSON.parse(result.data);
-
-      expect(result.url).to.equal('https://lwadm.com/ad');
-
-      let expectedQuery = {
-        auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
-        publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
-        userId: 'user id',
-        url: 'https://www.domain.com',
-        seats: {'dsp': ['seat 1']},
-        version: '1.4',
-        width: 100,
-        height: 100,
-        cookieSupport: true,
-        flrCur: 'EUR',
-        adRequests: [{
-          adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
-          callerAdUnitId: 'panorama_d_1',
-          bidId: '2ffb201a808da7',
-          rtbData: {
-            ext: {
-              tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D'
-            },
-          },
-          formats: [{width: 980, height: 240}, {width: 980, height: 120}],
-          flr: 10
-        }]
-      };
 
-      expect(data).to.deep.equal(expectedQuery);
+      return addFPDToBidderRequest(testbidRequest).then(res => {
+        let result = spec.buildRequests(bids, res);
+        let data = JSON.parse(result.data);
+        expect(result.url).to.equal('https://lwadm.com/ad');
+        expect(data.adRequests[0].flr).to.eql(10)
+        expect(data.flrCur).to.eql('EUR')
+        setCurrencyConfig({});
+      });
     });
 
     it('getFloor returns valid floor - default currency', function() {
diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js
index 0935b6c8404..f6993b433ad 100644
--- a/test/spec/modules/smartadserverBidAdapter_spec.js
+++ b/test/spec/modules/smartadserverBidAdapter_spec.js
@@ -4,6 +4,8 @@ import { config } from 'src/config.js';
 import { deepClone } from 'src/utils.js';
 import { getBidFloor } from 'libraries/equativUtils/equativUtils.js'
 import { spec } from 'modules/smartadserverBidAdapter.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 // Default params with optional ones
 describe('Smart bid adapter tests', function () {
@@ -249,10 +251,8 @@ describe('Smart bid adapter tests', function () {
   ];
 
   it('Verify build request', function () {
+    setCurrencyConfig({ adServerCurrency: 'EUR' });
     config.setConfig({
-      'currency': {
-        'adServerCurrency': 'EUR'
-      },
       ortb2: {
         'user': {
           'data': sellerDefinedAudience
@@ -265,30 +265,32 @@ describe('Smart bid adapter tests', function () {
       }
     });
 
-    const request = spec.buildRequests(DEFAULT_PARAMS);
-    expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1');
-    expect(request[0]).to.have.property('method').and.to.equal('POST');
-    const requestContent = JSON.parse(request[0].data);
-
-    expect(requestContent).to.have.property('siteid').and.to.equal('1234');
-    expect(requestContent).to.have.property('pageid').and.to.equal('5678');
-    expect(requestContent).to.have.property('formatid').and.to.equal('90');
-    expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR');
-    expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42);
-    expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid');
-    expect(requestContent).to.have.property('tagId').and.to.equal('sas_42');
-    expect(requestContent).to.have.property('sizes');
-    expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300);
-    expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250);
-    expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300);
-    expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200);
-    expect(requestContent).to.not.have.property('pageDomain');
-    expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined;
-    expect(requestContent).to.have.property('buid').and.to.equal('7569');
-    expect(requestContent).to.have.property('appname').and.to.equal('Mozilla');
-    expect(requestContent).to.have.property('ckid').and.to.equal(42);
-    expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience);
-    expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext);
+    return addFPDToBidderRequest(DEFAULT_PARAMS[0]).then(res => {
+      const request = spec.buildRequests(DEFAULT_PARAMS, res);
+      expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1');
+      expect(request[0]).to.have.property('method').and.to.equal('POST');
+      const requestContent = JSON.parse(request[0].data);
+      expect(requestContent).to.have.property('siteid').and.to.equal('1234');
+      expect(requestContent).to.have.property('pageid').and.to.equal('5678');
+      expect(requestContent).to.have.property('formatid').and.to.equal('90');
+      expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR');
+      expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42);
+      expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid');
+      expect(requestContent).to.have.property('tagId').and.to.equal('sas_42');
+      expect(requestContent).to.have.property('sizes');
+      expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300);
+      expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250);
+      expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300);
+      expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200);
+      expect(requestContent).to.not.have.property('pageDomain');
+      expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined;
+      expect(requestContent).to.have.property('buid').and.to.equal('7569');
+      expect(requestContent).to.have.property('appname').and.to.equal('Mozilla');
+      expect(requestContent).to.have.property('ckid').and.to.equal(42);
+      expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience);
+      expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext);
+      setCurrencyConfig({});
+    });
   });
 
   it('Verify parse response with no ad', function () {
@@ -648,10 +650,8 @@ describe('Smart bid adapter tests', function () {
     };
 
     it('Verify instream video build request', function () {
+      setCurrencyConfig({ adServerCurrency: 'EUR' });
       config.setConfig({
-        'currency': {
-          'adServerCurrency': 'EUR'
-        },
         ortb2: {
           'user': {
             'data': sellerDefinedAudience
@@ -663,29 +663,33 @@ describe('Smart bid adapter tests', function () {
           }
         }
       });
-      const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS);
-      expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1');
-      expect(request[0]).to.have.property('method').and.to.equal('POST');
-      const requestContent = JSON.parse(request[0].data);
-      expect(requestContent).to.have.property('siteid').and.to.equal('1234');
-      expect(requestContent).to.have.property('pageid').and.to.equal('5678');
-      expect(requestContent).to.have.property('formatid').and.to.equal('90');
-      expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR');
-      expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42);
-      expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid');
-      expect(requestContent).to.have.property('tagId').and.to.equal('sas_42');
-      expect(requestContent).to.not.have.property('pageDomain');
-      expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined;
-      expect(requestContent).to.have.property('buid').and.to.equal('7569');
-      expect(requestContent).to.have.property('appname').and.to.equal('Mozilla');
-      expect(requestContent).to.have.property('ckid').and.to.equal(42);
-      expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience);
-      expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext);
-      expect(requestContent).to.have.property('isVideo').and.to.equal(true);
-      expect(requestContent).to.have.property('videoData');
-      expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6);
-      expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(640);
-      expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(480);
+
+      return addFPDToBidderRequest(INSTREAM_DEFAULT_PARAMS[0]).then(res => {
+        const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS, res);
+        expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1');
+        expect(request[0]).to.have.property('method').and.to.equal('POST');
+        const requestContent = JSON.parse(request[0].data);
+        expect(requestContent).to.have.property('siteid').and.to.equal('1234');
+        expect(requestContent).to.have.property('pageid').and.to.equal('5678');
+        expect(requestContent).to.have.property('formatid').and.to.equal('90');
+        expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR');
+        expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42);
+        expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid');
+        expect(requestContent).to.have.property('tagId').and.to.equal('sas_42');
+        expect(requestContent).to.not.have.property('pageDomain');
+        expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined;
+        expect(requestContent).to.have.property('buid').and.to.equal('7569');
+        expect(requestContent).to.have.property('appname').and.to.equal('Mozilla');
+        expect(requestContent).to.have.property('ckid').and.to.equal(42);
+        expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience);
+        expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext);
+        expect(requestContent).to.have.property('isVideo').and.to.equal(true);
+        expect(requestContent).to.have.property('videoData');
+        expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6);
+        expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(640);
+        expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(480);
+        setCurrencyConfig({});
+      });
     });
 
     it('Verify instream parse response', function () {
@@ -989,10 +993,8 @@ describe('Smart bid adapter tests', function () {
     };
 
     it('Verify outstream video build request', function () {
+      setCurrencyConfig({ adServerCurrency: 'EUR' });
       config.setConfig({
-        'currency': {
-          'adServerCurrency': 'EUR'
-        },
         ortb2: {
           'user': {
             'data': sellerDefinedAudience
@@ -1004,29 +1006,33 @@ describe('Smart bid adapter tests', function () {
           }
         }
       });
-      const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS);
-      expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1');
-      expect(request[0]).to.have.property('method').and.to.equal('POST');
-      const requestContent = JSON.parse(request[0].data);
-      expect(requestContent).to.have.property('siteid').and.to.equal('1234');
-      expect(requestContent).to.have.property('pageid').and.to.equal('5678');
-      expect(requestContent).to.have.property('formatid').and.to.equal('91');
-      expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR');
-      expect(requestContent).to.have.property('bidfloor').and.to.equal(0.43);
-      expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid-outstream');
-      expect(requestContent).to.have.property('tagId').and.to.equal('sas_43');
-      expect(requestContent).to.not.have.property('pageDomain');
-      expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined;
-      expect(requestContent).to.have.property('buid').and.to.equal('7579');
-      expect(requestContent).to.have.property('appname').and.to.equal('Mozilla');
-      expect(requestContent).to.have.property('ckid').and.to.equal(43);
-      expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience);
-      expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext);
-      expect(requestContent).to.have.property('isVideo').and.to.equal(false);
-      expect(requestContent).to.have.property('videoData');
-      expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7);
-      expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(800);
-      expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(600);
+
+      return addFPDToBidderRequest(OUTSTREAM_DEFAULT_PARAMS[0]).then(res => {
+        const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS, res);
+        expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1');
+        expect(request[0]).to.have.property('method').and.to.equal('POST');
+        const requestContent = JSON.parse(request[0].data);
+        expect(requestContent).to.have.property('siteid').and.to.equal('1234');
+        expect(requestContent).to.have.property('pageid').and.to.equal('5678');
+        expect(requestContent).to.have.property('formatid').and.to.equal('91');
+        expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR');
+        expect(requestContent).to.have.property('bidfloor').and.to.equal(0.43);
+        expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid-outstream');
+        expect(requestContent).to.have.property('tagId').and.to.equal('sas_43');
+        expect(requestContent).to.not.have.property('pageDomain');
+        expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined;
+        expect(requestContent).to.have.property('buid').and.to.equal('7579');
+        expect(requestContent).to.have.property('appname').and.to.equal('Mozilla');
+        expect(requestContent).to.have.property('ckid').and.to.equal(43);
+        expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience);
+        expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext);
+        expect(requestContent).to.have.property('isVideo').and.to.equal(false);
+        expect(requestContent).to.have.property('videoData');
+        expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7);
+        expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(800);
+        expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(600);
+        setCurrencyConfig({});
+      });
     });
 
     it('Verify outstream parse response', function () {
diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js
index db928e4d802..923ff7e86b2 100755
--- a/test/spec/modules/visxBidAdapter_spec.js
+++ b/test/spec/modules/visxBidAdapter_spec.js
@@ -5,6 +5,8 @@ import { newBidder } from 'src/adapters/bidderFactory.js';
 import * as utils from 'src/utils.js';
 import { makeSlot } from '../integration/faker/googletag.js';
 import { mergeDeep } from '../../../src/utils.js';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency.js';
+import { addFPDToBidderRequest } from '../../helpers/fpd.js';
 
 describe('VisxAdapter', function () {
   const adapter = newBidder(spec);
@@ -355,8 +357,7 @@ describe('VisxAdapter', function () {
     });
 
     it('should add currency from currency.bidderCurrencyDefault', function () {
-      const getConfigStub = sinon.stub(config, 'getConfig').callsFake(
-        arg => arg === 'currency.bidderCurrencyDefault.visx' ? 'GBP' : 'USD');
+      config.setConfig({currency: {bidderCurrencyDefault: {visx: 'GBP'}}})
       const request = spec.buildRequests(bidRequests, bidderRequest);
       const payload = parseRequest(request.url);
       expect(payload).to.be.an('object');
@@ -410,66 +411,22 @@ describe('VisxAdapter', function () {
         }
       });
 
-      getConfigStub.restore();
+      config.resetConfig();
     });
 
     it('should add currency from currency.adServerCurrency', function () {
-      const getConfigStub = sinon.stub(config, 'getConfig').callsFake(
-        arg => arg === 'currency.bidderCurrencyDefault.visx' ? '' : 'USD');
-      const request = spec.buildRequests(bidRequests, bidderRequest);
-      const payload = parseRequest(request.url);
-      expect(payload).to.be.an('object');
-      expect(payload).to.have.property('auids', '903535,903535,903536,903537');
-
-      const postData = request.data;
-      expect(postData).to.be.an('object');
-      expect(postData).to.deep.equal({
-        'id': '22edbae2733bf6',
-        'imp': expectedFullImps,
-        'tmax': 3000,
-        'cur': ['USD'],
-        'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}},
-        'site': {
-          'domain': 'localhost:9999',
-          'publisher': {
-            'domain': 'localhost:9999'
-          },
-          'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html'
-        },
-        'device': {
-          'w': 1259,
-          'h': 934,
-          'dnt': 0,
-          'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
-          'language': 'tr',
-          'sua': {
-            'source': 1,
-            'platform': {
-              'brand': 'macOS'
-            },
-            'browsers': [
-              {
-                'brand': 'Chromium',
-                'version': [ '124' ]
-              },
-              {
-                'brand': 'Google Chrome',
-                'version': [ '124' ]
-              },
-              {
-                'brand': 'Not-A.Brand',
-                'version': [ '99' ]
-              }
-            ],
-            'mobile': 0
-          },
-          'ext': {
-            'cdep': 'treatment_1.1'
-          }
-        },
+      setCurrencyConfig({ adServerCurrency: 'USD' })
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        const request = spec.buildRequests(bidRequests, res);
+        const payload = parseRequest(request.url);
+        expect(payload).to.be.an('object');
+        expect(payload).to.have.property('auids', '903535,903535,903536,903537');
+
+        const postData = request.data;
+        expect(postData).to.be.an('object');
+        expect(postData.cur).to.deep.equal(['USD']);
+        setCurrencyConfig({})
       });
-
-      getConfigStub.restore();
     });
 
     it('if gdprConsent is present payload must have gdpr params', function () {
diff --git a/test/spec/modules/voxBidAdapter_spec.js b/test/spec/modules/voxBidAdapter_spec.js
index 5f4ada06c65..65752837b23 100644
--- a/test/spec/modules/voxBidAdapter_spec.js
+++ b/test/spec/modules/voxBidAdapter_spec.js
@@ -1,6 +1,7 @@
 import { expect } from 'chai'
 import { spec } from 'modules/voxBidAdapter.js'
-import {config} from 'src/config.js'
+import { setConfig as setCurrencyConfig } from '../../../modules/currency'
+import { addFPDToBidderRequest } from '../../helpers/fpd'
 
 function getSlotConfigs(mediaTypes, params) {
   return {
@@ -247,14 +248,17 @@ describe('VOX Adapter', function() {
       })
 
       it('should request floor price in adserver currency', function () {
-        const configCurrency = 'DKK'
-        config.setConfig({ currency: { adServerCurrency: configCurrency } })
-        const request = spec.buildRequests([ getBidWithFloor() ], bidderRequest)
-        const data = JSON.parse(request.data)
-        data.bidRequests.forEach(bid => {
-          expect(bid.floorInfo.currency).to.equal(configCurrency)
-        })
-      })
+        const configCurrency = 'DKK';
+        setCurrencyConfig({ adServerCurrency: configCurrency });
+        return addFPDToBidderRequest(bidderRequest).then(res => {
+          const request = spec.buildRequests([ getBidWithFloor() ], res)
+          const data = JSON.parse(request.data)
+          data.bidRequests.forEach(bid => {
+            expect(bid.floorInfo.currency).to.equal(configCurrency)
+          })
+          setCurrencyConfig({});
+        });
+      });
 
       function getBidWithFloor(floor) {
         return {
diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js
index 140be4121ec..e6b2f5cc1f0 100644
--- a/test/spec/modules/yandexBidAdapter_spec.js
+++ b/test/spec/modules/yandexBidAdapter_spec.js
@@ -1,8 +1,9 @@
 import { assert, expect } from 'chai';
 import { NATIVE_ASSETS, spec } from 'modules/yandexBidAdapter.js';
 import * as utils from 'src/utils.js';
-import { config } from '../../../src/config';
+import { setConfig as setCurrencyConfig } from '../../../modules/currency';
 import { BANNER, NATIVE } from '../../../src/mediaTypes';
+import { addFPDToBidderRequest } from '../../helpers/fpd';
 
 describe('Yandex adapter', function () {
   describe('isBidRequestValid', function () {
@@ -125,19 +126,21 @@ describe('Yandex adapter', function () {
     });
 
     it('should send currency if defined', function () {
-      config.setConfig({
-        currency: {
-          adServerCurrency: 'USD'
-        }
+      setCurrencyConfig({
+        adServerCurrency: 'USD'
       });
 
       const bannerRequest = getBidRequest();
-      const requests = spec.buildRequests([bannerRequest], bidderRequest);
-      const { url } = requests[0];
-      const parsedRequestUrl = utils.parseUrl(url);
-      const { search: query } = parsedRequestUrl
 
-      expect(query['ssp-cur']).to.equal('USD');
+      return addFPDToBidderRequest(bidderRequest).then(res => {
+        const requests = spec.buildRequests([bannerRequest], res);
+        const { url } = requests[0];
+        const parsedRequestUrl = utils.parseUrl(url);
+        const { search: query } = parsedRequestUrl
+
+        expect(query['ssp-cur']).to.equal('USD');
+        setCurrencyConfig({});
+      });
     });
 
     it('should send eids and ortb2 user data if defined', function() {

From 438522e96097a1ee7b4ac303a538d685cc63d95a Mon Sep 17 00:00:00 2001
From: Gena 
Date: Wed, 4 Dec 2024 22:01:59 +0100
Subject: [PATCH 0732/1097] Bidmatic Bid Adapter: add syncing  (#12541)

* Add bidmatic syncs

* Add bidmatic syncs
---
 modules/bidmaticBidAdapter.js                | 38 +++++++++++++++++-
 modules/bidmaticBidAdapter.md                |  6 +++
 test/spec/modules/bidmaticBidAdapter_spec.js | 42 +++++++++++++++++++-
 3 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/modules/bidmaticBidAdapter.js b/modules/bidmaticBidAdapter.js
index 8c22d70c632..6c88a3f1932 100644
--- a/modules/bidmaticBidAdapter.js
+++ b/modules/bidmaticBidAdapter.js
@@ -3,9 +3,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import { replaceAuctionPrice, isNumber, deepAccess, isFn } from '../src/utils.js';
 
-export const END_POINT = 'https://adapter.bidmatic.io/ortb-client';
+const HOST = 'https://adapter.bidmatic.io';
 const BIDDER_CODE = 'bidmatic';
 const DEFAULT_CURRENCY = 'USD';
+export const SYNC_URL = `${HOST}/sync.html`;
+export const END_POINT = `${HOST}/ortb-client`;
 
 export const converter = ortbConverter({
   context: {
@@ -59,6 +61,33 @@ export const converter = ortbConverter({
   }
 });
 
+const PROCESSED_SOURCES = {};
+
+export function createUserSyncs(processedSources, syncOptions, gdprConsent, uspConsent, gppConsent) {
+  if (syncOptions?.iframeEnabled) {
+    return Object.entries(processedSources)
+      .filter(([_, syncMade]) => syncMade === 0)
+      .map(([sourceId]) => {
+        processedSources[sourceId] = 1
+
+        let url = `${SYNC_URL}?aid=${sourceId}`
+        if (gdprConsent && gdprConsent.gdprApplies) {
+          url += `&gdpr=${+(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`
+        }
+        if (uspConsent) {
+          url += `&usp=${uspConsent}`;
+        }
+        if (gppConsent) {
+          url += `&gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections?.toString()}`
+        }
+        return {
+          type: 'iframe',
+          url
+        };
+      })
+  }
+}
+
 export const spec = {
   code: BIDDER_CODE,
   supportedMediaTypes: [BANNER, VIDEO],
@@ -66,7 +95,9 @@ export const spec = {
   isBidRequestValid: function (bid) {
     return isNumber(deepAccess(bid, 'params.source'))
   },
-
+  getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) {
+    return createUserSyncs(PROCESSED_SOURCES, syncOptions, gdprConsent, uspConsent, gppConsent);
+  },
   buildRequests: function (validBidRequests, bidderRequest) {
     const requestsBySource = validBidRequests.reduce((acc, bidRequest) => {
       acc[bidRequest.params.source] = acc[bidRequest.params.source] || [];
@@ -75,6 +106,9 @@ export const spec = {
     }, {});
 
     return Object.entries(requestsBySource).map(([source, bidRequests]) => {
+      if (!PROCESSED_SOURCES[source]) {
+        PROCESSED_SOURCES[source] = 0;
+      }
       const data = converter.toORTB({ bidRequests, bidderRequest });
       const url = new URL(END_POINT);
       url.searchParams.append('source', source);
diff --git a/modules/bidmaticBidAdapter.md b/modules/bidmaticBidAdapter.md
index d248b386ea3..242d7d9e77b 100644
--- a/modules/bidmaticBidAdapter.md
+++ b/modules/bidmaticBidAdapter.md
@@ -24,3 +24,9 @@ var adUnits = [{
     }]
 }]
 ```
+
+
+# Testing 
+```gulp test-only --file=./test/spec/modules/bidmaticBidAdapter_spec.js```
+```gulp test-coverage --file=./test/spec/modules/bidmaticBidAdapter_spec.js```
+```gulp view-coverage```
diff --git a/test/spec/modules/bidmaticBidAdapter_spec.js b/test/spec/modules/bidmaticBidAdapter_spec.js
index 4b213c6dcc4..225c2a6dce6 100644
--- a/test/spec/modules/bidmaticBidAdapter_spec.js
+++ b/test/spec/modules/bidmaticBidAdapter_spec.js
@@ -1,5 +1,5 @@
 import { expect } from 'chai';
-import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js';
+import { END_POINT, SYNC_URL, spec, createUserSyncs } from 'modules/bidmaticBidAdapter.js';
 import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils';
 
 const expectedImp = {
@@ -165,6 +165,46 @@ describe('Bidmatic Bid Adapter', () => {
     });
   })
 
+  describe('syncs creation', () => {
+    const syncOptions = { iframeEnabled: true };
+
+    it('should not operate without syncs enabled', () => {
+      const syncs = createUserSyncs({});
+      expect(syncs).to.eq(undefined);
+    });
+
+    it('should call uniq and unused sources only', () => {
+      const sources = { 111: 0, 222: 0, 333: 1 }
+      const syncs = createUserSyncs(sources, syncOptions);
+
+      expect(syncs.length).to.eq(2);
+
+      expect(syncs[0].type).to.eq('iframe');
+      expect(syncs[0].url).to.eq(`${SYNC_URL}?aid=111`);
+      expect(syncs[1].type).to.eq('iframe');
+      expect(syncs[1].url).to.eq(`${SYNC_URL}?aid=222`);
+
+      expect(sources[111]).to.eq(1);
+      expect(sources[222]).to.eq(1);
+
+      const syncs2 = createUserSyncs(sources, syncOptions);
+      expect(syncs2.length).to.eq(0);
+    });
+
+    it('should add consent info', () => {
+      const [{ url: syncUrl }] = createUserSyncs(
+        { 111: 0 },
+        syncOptions,
+        { gdprApplies: true, consentString: '111' },
+        'yyy',
+        { gppString: '222', applicableSections: [1, 2] });
+
+      expect(syncUrl.includes('gdpr=1&gdpr_consent=111')).to.eq(true);
+      expect(syncUrl.includes('usp=yyy')).to.eq(true);
+      expect(syncUrl.includes('gpp=222&gpp_sid=1,2')).to.eq(true);
+    });
+  })
+
   describe('response interpreter', () => {
     const SERVER_RESPONSE = {
       'body': {

From ec420b6a1e247367c0d68891aa238f9ae3e591cb Mon Sep 17 00:00:00 2001
From: Petre Damoc 
Date: Wed, 4 Dec 2024 23:41:11 +0200
Subject: [PATCH 0733/1097] Missena Bid Adapter : refactor getUserSyncs, send
 screen size (#12497)

* Missena Bid Adapter : refactor getUserSyncs

* send screen
---
 modules/missenaBidAdapter.js                | 30 +++++++++++----------
 test/spec/modules/missenaBidAdapter_spec.js | 16 +++++++----
 2 files changed, 27 insertions(+), 19 deletions(-)

diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js
index ac69fcc9965..cd5650e73c2 100644
--- a/modules/missenaBidAdapter.js
+++ b/modules/missenaBidAdapter.js
@@ -86,6 +86,7 @@ function toPayload(bidRequest, bidderRequest) {
   payload.schain = bidRequest.schain;
   payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0;
   payload.autoplay = isAutoplayEnabled() === true ? 1 : 0;
+  payload.screen = { height: screen.height, width: screen.width };
 
   return {
     method: 'POST',
@@ -130,6 +131,8 @@ export const spec = {
       return [];
     }
 
+    this.msnaApiKey = validBidRequests[0]?.params.apiKey;
+
     return validBidRequests.map((bidRequest) =>
       toPayload(bidRequest, bidderRequest),
     );
@@ -154,26 +157,25 @@ export const spec = {
   getUserSyncs: function (
     syncOptions,
     serverResponses,
-    gdprConsent,
+    gdprConsent = {},
     uspConsent,
   ) {
-    if (!syncOptions.iframeEnabled) {
+    if (!syncOptions.iframeEnabled || !this.msnaApiKey) {
       return [];
     }
 
-    let gdprParams = '';
-    if (
-      gdprConsent &&
-      'gdprApplies' in gdprConsent &&
-      typeof gdprConsent.gdprApplies === 'boolean'
-    ) {
-      gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${
-        gdprConsent.consentString
-      }`;
+    const url = new URL('https://sync.missena.io/iframe');
+    url.searchParams.append('t', this.msnaApiKey);
+
+    if (typeof gdprConsent.gdprApplies === 'boolean') {
+      url.searchParams.append('gdpr', Number(gdprConsent.gdprApplies));
+      url.searchParams.append('gdpr_consent', gdprConsent.consentString);
+    }
+    if (uspConsent) {
+      url.searchParams.append('us_privacy', uspConsent);
     }
-    return [
-      { type: 'iframe', url: 'https://sync.missena.io/iframe' + gdprParams },
-    ];
+
+    return [{ type: 'iframe', url: url.href }];
   },
   /**
    * Register bidder specific code, which will execute if bidder timed out after an auction
diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js
index 6a143fa49ef..afd96091d84 100644
--- a/test/spec/modules/missenaBidAdapter_spec.js
+++ b/test/spec/modules/missenaBidAdapter_spec.js
@@ -7,6 +7,7 @@ import * as autoplay from 'libraries/autoplayDetection/autoplay.js';
 const REFERRER = 'https://referer';
 const REFERRER2 = 'https://referer2';
 const COOKIE_DEPRECATION_LABEL = 'test';
+const API_KEY = 'PA-XXXXXX';
 
 describe('Missena Adapter', function () {
   $$PREBID_GLOBAL$$.bidderSettings = {
@@ -30,7 +31,7 @@ describe('Missena Adapter', function () {
       },
     },
     params: {
-      apiKey: 'PA-34745704',
+      apiKey: API_KEY,
       placement: 'sticky',
       formats: ['sticky-banner'],
     },
@@ -57,7 +58,7 @@ describe('Missena Adapter', function () {
     sizes: [[1, 1]],
     mediaTypes: { banner: { sizes: [[1, 1]] } },
     params: {
-      apiKey: 'PA-34745704',
+      apiKey: API_KEY,
       placement: 'sticky',
       formats: ['sticky-banner'],
     },
@@ -172,6 +173,11 @@ describe('Missena Adapter', function () {
       expect(payload.ik).to.equal(window.msna_ik);
     });
 
+    it('should send screen', function () {
+      expect(payload.screen.width).to.equal(screen.width);
+      expect(payload.screen.height).to.equal(screen.height);
+    });
+
     getDataFromLocalStorageStub.restore();
     getDataFromLocalStorageStub = sinon.stub(
       storage,
@@ -299,7 +305,7 @@ describe('Missena Adapter', function () {
 
       expect(userSync.length).to.be.equal(1);
       expect(userSync[0].type).to.be.equal('iframe');
-      expect(userSync[0].url).to.be.equal(syncFrameUrl);
+      expect(userSync[0].url).to.be.equal(`${syncFrameUrl}?t=${API_KEY}`);
     });
 
     it('should return empty array when iframeEnabled is false', function () {
@@ -312,7 +318,7 @@ describe('Missena Adapter', function () {
         gdprApplies: true,
         consentString,
       });
-      const expectedUrl = `${syncFrameUrl}?gdpr=1&gdpr_consent=${consentString}`;
+      const expectedUrl = `${syncFrameUrl}?t=${API_KEY}&gdpr=1&gdpr_consent=${consentString}`;
       expect(userSync.length).to.be.equal(1);
       expect(userSync[0].type).to.be.equal('iframe');
       expect(userSync[0].url).to.be.equal(expectedUrl);
@@ -322,7 +328,7 @@ describe('Missena Adapter', function () {
         gdprApplies: false,
         consentString,
       });
-      const expectedUrl = `${syncFrameUrl}?gdpr=0&gdpr_consent=${consentString}`;
+      const expectedUrl = `${syncFrameUrl}?t=${API_KEY}&gdpr=0&gdpr_consent=${consentString}`;
       expect(userSync.length).to.be.equal(1);
       expect(userSync[0].type).to.be.equal('iframe');
       expect(userSync[0].url).to.be.equal(expectedUrl);

From 08467c8b757e96d28f2c6e11f9fde8491b5098b8 Mon Sep 17 00:00:00 2001
From: Demetrio Girardi 
Date: Thu, 5 Dec 2024 06:19:33 -0800
Subject: [PATCH 0734/1097] Core: support adAuctionHeaders (#12542)

---
 src/ajax.js                      | 12 ++++++---
 test/spec/unit/core/ajax_spec.js | 44 +++++++++++++++++---------------
 2 files changed, 31 insertions(+), 25 deletions(-)

diff --git a/src/ajax.js b/src/ajax.js
index 7f9857ad18d..0178f95eadf 100644
--- a/src/ajax.js
+++ b/src/ajax.js
@@ -47,10 +47,14 @@ export function toFetchRequest(url, data, options = {}) {
   if (options.withCredentials) {
     rqOpts.credentials = 'include';
   }
-  if (options.browsingTopics && isSecureContext) {
-    // the Request constructor will throw an exception if the browser supports topics
-    // but we're not in a secure context
-    rqOpts.browsingTopics = true;
+  if (isSecureContext) {
+    ['browsingTopics', 'adAuctionHeaders'].forEach(opt => {
+      // the Request constructor will throw an exception if the browser supports topics/fledge
+      // but we're not in a secure context
+      if (options[opt]) {
+        rqOpts[opt] = true;
+      }
+    })
   }
   if (options.keepalive) {
     rqOpts.keepalive = true;
diff --git a/test/spec/unit/core/ajax_spec.js b/test/spec/unit/core/ajax_spec.js
index 8140123d9fc..a7ecf88d14d 100644
--- a/test/spec/unit/core/ajax_spec.js
+++ b/test/spec/unit/core/ajax_spec.js
@@ -185,28 +185,30 @@ describe('toFetchRequest', () => {
     });
   });
 
-  describe('browsingTopics', () => {
-    Object.entries({
-      'browsingTopics = true': [{browsingTopics: true}, true],
-      'browsingTopics = false': [{browsingTopics: false}, false],
-      'browsingTopics is undef': [{}, false]
-    }).forEach(([t, [opts, shouldBeSet]]) => {
-      describe(`when options has ${t}`, () => {
-        const sandbox = sinon.createSandbox();
-        afterEach(() => {
-          sandbox.restore();
-        });
+  describe('chrome options', () => {
+    ['browsingTopics', 'adAuctionHeaders'].forEach(option => {
+      Object.entries({
+        [`${option} = true`]: [{[option]: true}, true],
+        [`${option} = false`]: [{[option]: false}, false],
+        [`${option} undef`]: [{}, false]
+      }).forEach(([t, [opts, shouldBeSet]]) => {
+        describe(`when options has ${t}`, () => {
+          const sandbox = sinon.createSandbox();
+          afterEach(() => {
+            sandbox.restore();
+          });
 
-        it(`should ${!shouldBeSet ? 'not ' : ''}be set when in a secure context`, () => {
-          sandbox.stub(window, 'isSecureContext').get(() => true);
-          toFetchRequest(EXAMPLE_URL, null, opts);
-          sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {browsingTopics: shouldBeSet ? true : undefined});
-        });
-        it(`should not be set when not in a secure context`, () => {
-          sandbox.stub(window, 'isSecureContext').get(() => false);
-          toFetchRequest(EXAMPLE_URL, null, opts);
-          sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {browsingTopics: undefined});
-        });
+          it(`should ${!shouldBeSet ? 'not ' : ''}be set when in a secure context`, () => {
+            sandbox.stub(window, 'isSecureContext').get(() => true);
+            toFetchRequest(EXAMPLE_URL, null, opts);
+            sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {[option]: shouldBeSet ? true : undefined});
+          });
+          it(`should not be set when not in a secure context`, () => {
+            sandbox.stub(window, 'isSecureContext').get(() => false);
+            toFetchRequest(EXAMPLE_URL, null, opts);
+            sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {[option]: undefined});
+          });
+        })
       })
     })
   })

From 6a2c4cd6bd304fec9cf658ab810bef66c6973f0b Mon Sep 17 00:00:00 2001
From: Keith Doggett 
Date: Thu, 5 Dec 2024 15:42:56 -0500
Subject: [PATCH 0735/1097] Goldfish Ads RTD Adapter: Fix Description Typo
 (#12550)

* Add goldfishAdsRtdProvider Module

* Update description and change instances of GoldfishAds to Goldfish Ads

* update names

* Fix typo in goldfishAdsRtdProvider.md
---
 modules/goldfishAdsRtdProvider.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/modules/goldfishAdsRtdProvider.md b/modules/goldfishAdsRtdProvider.md
index 4625c9a7988..9a6fd939bd1 100755
--- a/modules/goldfishAdsRtdProvider.md
+++ b/modules/goldfishAdsRtdProvider.md
@@ -8,7 +8,7 @@
 
 ## Description
 
-This RTD module provides access to the Goldfish Ads Geograph, which leverages geographic and temporal data on a privcay-first platform. This module works without using cookies, PII, emails, or device IDs across all website traffic, including unauthenticated users, and adds audience data into bid requests to increase scale and yields.
+This RTD module provides access to the Goldfish Ads Geograph, which leverages geographic and temporal data on a privacy-first platform. This module works without using cookies, PII, emails, or device IDs across all website traffic, including unauthenticated users, and adds audience data into bid requests to increase scale and yields.
 
 ## Usage
 

From 272d67c857ac5b970157cae3d205469723f1a757 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 6 Dec 2024 04:12:38 -0700
Subject: [PATCH 0736/1097] Bump path-to-regexp and express (#12552)

Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.12 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together.


Updates `path-to-regexp` from 0.1.10 to 0.1.12
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v0.1.12)

Updates `express` from 4.21.1 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package-lock.json | 32 ++++++++++++++++++--------------
 1 file changed, 18 insertions(+), 14 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f69353f8360..c3012d1d9a4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11337,9 +11337,9 @@
       }
     },
     "node_modules/express": {
-      "version": "4.21.1",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
-      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+      "version": "4.21.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+      "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
@@ -11360,7 +11360,7 @@
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.10",
+        "path-to-regexp": "0.1.12",
         "proxy-addr": "~2.0.7",
         "qs": "6.13.0",
         "range-parser": "~1.2.1",
@@ -11375,6 +11375,10 @@
       },
       "engines": {
         "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
       }
     },
     "node_modules/express/node_modules/debug": {
@@ -21597,9 +21601,9 @@
       "dev": true
     },
     "node_modules/path-to-regexp": {
-      "version": "0.1.10",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
-      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
     },
     "node_modules/path-type": {
       "version": "1.1.0",
@@ -36414,9 +36418,9 @@
       }
     },
     "express": {
-      "version": "4.21.1",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
-      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+      "version": "4.21.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+      "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
       "requires": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
@@ -36437,7 +36441,7 @@
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.10",
+        "path-to-regexp": "0.1.12",
         "proxy-addr": "~2.0.7",
         "qs": "6.13.0",
         "range-parser": "~1.2.1",
@@ -44256,9 +44260,9 @@
       }
     },
     "path-to-regexp": {
-      "version": "0.1.10",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
-      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
     },
     "path-type": {
       "version": "1.1.0",

From f9327ffb6eaf98014ffc2aada2eef58209f9d70c Mon Sep 17 00:00:00 2001
From: Graham Higgins 
Date: Fri, 6 Dec 2024 06:19:40 -0500
Subject: [PATCH 0737/1097] Core & multiple modules: replace `deepAccess` with
 optional chaining (#12544)

* Replace `deepAccess` with optional chaining

Closes #12543

* Revert changes containing variable lookups that may dynamically contain "."

* Use optional chaining

Adding these changes back after review discovered the missing optional chain which was causing test failure in a previous commit.

* revert bidderSettings & add test case

---------

Co-authored-by: Demetrio Girardi 
---
 libraries/appnexusUtils/anKeywords.js         |  2 +-
 libraries/fpdUtils/pageInfo.js                |  4 +--
 libraries/ortbConverter/processors/banner.js  |  3 +-
 libraries/ortbConverter/processors/video.js   |  6 ++--
 modules/criteoBidAdapter.js                   | 34 +++++++++----------
 modules/dfpAdServerVideo.js                   |  7 ++--
 modules/prebidServerBidAdapter/index.js       |  5 ++-
 .../prebidServerBidAdapter/ortbConverter.js   |  6 ++--
 modules/taboolaBidAdapter.js                  |  4 +--
 modules/teadsBidAdapter.js                    | 26 +++++++-------
 modules/userId/index.js                       |  3 +-
 src/Renderer.js                               |  6 ++--
 src/adRendering.js                            |  5 ++-
 src/adapterManager.js                         |  3 +-
 src/auction.js                                | 24 ++++++-------
 src/native.js                                 | 24 ++++++-------
 src/prebid.js                                 |  3 +-
 src/utils/gdpr.js                             |  4 +--
 src/video.js                                  |  8 ++---
 test/spec/unit/core/bidderSettings_spec.js    |  7 ++++
 20 files changed, 86 insertions(+), 98 deletions(-)

diff --git a/libraries/appnexusUtils/anKeywords.js b/libraries/appnexusUtils/anKeywords.js
index a6fa8d7a21e..4a0da18024e 100644
--- a/libraries/appnexusUtils/anKeywords.js
+++ b/libraries/appnexusUtils/anKeywords.js
@@ -136,7 +136,7 @@ export function getANMapFromOrtbSegments(ortb2) {
     let ortbSegsArrObj = deepAccess(ortb2, path) || [];
     ortbSegsArrObj.forEach(segObj => {
       // only read segment data from known sources
-      const segtax = ORTB_SEGTAX_KEY_MAP[deepAccess(segObj, 'ext.segtax')];
+      const segtax = ORTB_SEGTAX_KEY_MAP[segObj?.ext?.segtax];
       if (segtax) {
         segObj.segment.forEach(seg => {
           // if source was in multiple locations of ortb or had multiple segments in same area, stack them together into an array
diff --git a/libraries/fpdUtils/pageInfo.js b/libraries/fpdUtils/pageInfo.js
index dcf172d96c8..8e02134e070 100644
--- a/libraries/fpdUtils/pageInfo.js
+++ b/libraries/fpdUtils/pageInfo.js
@@ -1,5 +1,3 @@
-import * as utils from '../../src/utils.js';
-
 /**
  * get page title
  * @returns {string}
@@ -67,7 +65,7 @@ export function getReferrer(bidRequest = {}, bidderRequest = {}) {
   if (bidRequest.params && bidRequest.params.referrer) {
     pageUrl = bidRequest.params.referrer;
   } else {
-    pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.page');
+    pageUrl = bidderRequest?.refererInfo?.page;
   }
   return pageUrl;
 }
diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js
index fca9598022b..877a8b9081b 100644
--- a/libraries/ortbConverter/processors/banner.js
+++ b/libraries/ortbConverter/processors/banner.js
@@ -1,6 +1,5 @@
 import {
   createTrackPixelHtml,
-  deepAccess,
   inIframe,
   mergeDeep,
   sizesToSizeTuples,
@@ -15,7 +14,7 @@ import {BANNER} from '../../../src/mediaTypes.js';
 export function fillBannerImp(imp, bidRequest, context) {
   if (context.mediaType && context.mediaType !== BANNER) return;
 
-  const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner');
+  const bannerParams = bidRequest?.mediaTypes?.banner;
   if (bannerParams) {
     const banner = {
       topframe: inIframe() === true ? 0 : 1
diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js
index 85ab1cd6ecf..3bb4e69e24d 100644
--- a/libraries/ortbConverter/processors/video.js
+++ b/libraries/ortbConverter/processors/video.js
@@ -1,4 +1,4 @@
-import {deepAccess, isEmpty, logWarn, mergeDeep, sizesToSizeTuples, sizeTupleToRtbSize} from '../../../src/utils.js';
+import {isEmpty, logWarn, mergeDeep, sizesToSizeTuples, sizeTupleToRtbSize} from '../../../src/utils.js';
 import {VIDEO} from '../../../src/mediaTypes.js';
 
 import {ORTB_VIDEO_PARAMS} from '../../../src/video.js';
@@ -6,7 +6,7 @@ import {ORTB_VIDEO_PARAMS} from '../../../src/video.js';
 export function fillVideoImp(imp, bidRequest, context) {
   if (context.mediaType && context.mediaType !== VIDEO) return;
 
-  const videoParams = deepAccess(bidRequest, 'mediaTypes.video');
+  const videoParams = bidRequest?.mediaTypes?.video;
   if (!isEmpty(videoParams)) {
     const video = Object.fromEntries(
       // Parameters that share the same name & semantics between pbjs adUnits and imp.video
@@ -27,7 +27,7 @@ export function fillVideoImp(imp, bidRequest, context) {
 
 export function fillVideoResponse(bidResponse, seatbid, context) {
   if (bidResponse.mediaType === VIDEO) {
-    if (deepAccess(context.imp, 'video.w') && deepAccess(context.imp, 'video.h')) {
+    if (context?.imp?.video?.w && context?.imp?.video?.h) {
       [bidResponse.playerWidth, bidResponse.playerHeight] = [context.imp.video.w, context.imp.video.h];
     }
 
diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js
index d44f947dd3e..b01e7361e3f 100644
--- a/modules/criteoBidAdapter.js
+++ b/modules/criteoBidAdapter.js
@@ -1,4 +1,4 @@
-import {deepAccess, deepSetValue, isArray, logError, logWarn, parseUrl, triggerPixel} from '../src/utils.js';
+import {deepSetValue, isArray, logError, logWarn, parseUrl, triggerPixel} from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
 import {getStorageManager} from '../src/storageManager.js';
@@ -92,7 +92,7 @@ function imp(buildImp, bidRequest, context) {
     }
     deepSetValue(imp, 'video.ext', {
       context: bidRequest.mediaTypes.video.context,
-      playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize),
+      playersizes: parseSizes(bidRequest?.mediaTypes?.video?.playerSize, parseSize),
       plcmt: bidRequest.mediaTypes.video.plcmt,
       poddur: bidRequest.mediaTypes.video.adPodDurationSec,
       rqddurs: bidRequest.mediaTypes.video.durationRangeSec,
@@ -156,7 +156,7 @@ function request(buildRequest, imps, bidderRequest, context) {
  * @returns {*}
  */
 function bidResponse(buildBidResponse, bid, context) {
-  context.mediaType = deepAccess(bid, 'ext.mediatype');
+  context.mediaType = bid?.ext?.mediatype;
   if (context.mediaType === NATIVE && typeof bid.adm_native !== 'undefined') {
     bid.adm = bid.adm_native;
     delete bid.adm_native;
@@ -165,22 +165,22 @@ function bidResponse(buildBidResponse, bid, context) {
   let bidResponse = buildBidResponse(bid, context);
   const {bidRequest} = context;
 
-  bidResponse.currency = deepAccess(bid, 'ext.cur')
+  bidResponse.currency = bid?.ext?.cur;
 
-  if (typeof deepAccess(bid, 'ext.meta') !== 'undefined') {
+  if (typeof bid?.ext?.meta !== 'undefined') {
     deepSetValue(bidResponse, 'meta', {
       ...bidResponse.meta,
       ...bid.ext.meta
     });
   }
-  if (typeof deepAccess(bid, 'ext.paf.content_id') !== 'undefined') {
+  if (typeof bid?.ext?.paf?.content_id !== 'undefined') {
     deepSetValue(bidResponse, 'meta.paf.content_id', bid.ext.paf.content_id)
   }
 
   if (bidResponse.mediaType === VIDEO) {
     bidResponse.vastUrl = bid.ext?.displayurl;
     // if outstream video, add a default render for it.
-    if (deepAccess(bidRequest, 'mediaTypes.video.context') === OUTSTREAM) {
+    if (bidRequest?.mediaTypes?.video?.context === OUTSTREAM) {
       bidResponse.renderer = createOutstreamVideoRenderer(bid);
     }
   }
@@ -200,9 +200,9 @@ function bidResponse(buildBidResponse, bid, context) {
 function response(buildResponse, bidResponses, ortbResponse, context) {
   let response = buildResponse(bidResponses, ortbResponse, context);
 
-  const pafTransmission = deepAccess(ortbResponse, 'ext.paf.transmission');
+  const pafTransmission = ortbResponse?.ext?.paf?.transmission;
   response.bids.forEach(bid => {
-    if (typeof pafTransmission !== 'undefined' && typeof deepAccess(bid, 'meta.paf.content_id') !== 'undefined') {
+    if (typeof pafTransmission !== 'undefined' && typeof bid?.meta?.paf?.content_id !== 'undefined') {
       deepSetValue(bid, 'meta.paf.transmission', pafTransmission);
     } else {
       delete bid.meta.paf;
@@ -362,7 +362,7 @@ export const spec = {
         // We support native request without assets requirements because we can fill them later on.
         // This is a trick to fool oRTB converter isOpenRTBBidRequestValid(ortb) fn because it needs
         // nativeOrtbRequest.assets to be non-empty.
-        if (deepAccess(bidRequest, 'nativeOrtbRequest.assets') == null) {
+        if (bidRequest?.nativeOrtbRequest?.assets == null) {
           logWarn(LOG_PREFIX + 'native asset requirements are missing');
           deepSetValue(bidRequest, 'nativeOrtbRequest.assets', [{}]);
         }
@@ -391,7 +391,7 @@ export const spec = {
     const interpretedResponse = CONVERTER.fromORTB({response: response.body, request: request.data});
     const bids = interpretedResponse.bids || [];
 
-    const fledgeAuctionConfigs = deepAccess(response.body, 'ext.igi')?.filter(igi => isArray(igi?.igs))
+    const fledgeAuctionConfigs = response.body?.ext?.igi?.filter(igi => isArray(igi?.igs))
       .flatMap(igi => igi.igs);
     if (fledgeAuctionConfigs?.length) {
       return {
@@ -548,11 +548,11 @@ function parseSize(size) {
 }
 
 function hasVideoMediaType(bidRequest) {
-  return deepAccess(bidRequest, 'mediaTypes.video') !== undefined;
+  return bidRequest?.mediaTypes?.video !== undefined;
 }
 
 function hasNativeMediaType(bidRequest) {
-  return deepAccess(bidRequest, 'mediaTypes.native') !== undefined;
+  return bidRequest?.mediaTypes?.native !== undefined;
 }
 
 function hasValidVideoMediaType(bidRequest) {
@@ -562,12 +562,12 @@ function hasValidVideoMediaType(bidRequest) {
 
   requiredMediaTypesParams.forEach(function (param) {
     if (param === 'placement') {
-      if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined && deepAccess(bidRequest, 'mediaTypes.video.plcmt') === undefined && deepAccess(bidRequest, 'params.video.plcmt') === undefined) {
+      if (bidRequest?.mediaTypes?.video?.[param] === undefined && bidRequest?.params?.video?.[param] === undefined && bidRequest?.mediaTypes?.video?.plcmt === undefined && bidRequest?.params?.video?.plcmt === undefined) {
         isValid = false;
         logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' or mediaTypes.video.plcmt is required');
       }
     } else {
-      if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) {
+      if (bidRequest?.mediaTypes?.video?.[param] === undefined && bidRequest?.params?.video?.[param] === undefined) {
         isValid = false;
         logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required');
       }
@@ -604,13 +604,13 @@ function getFloors(bidRequest) {
     if (getFloor) {
       if (bidRequest.mediaTypes?.banner) {
         floors.banner = {};
-        const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'))
+        const bannerSizes = parseSizes(bidRequest?.mediaTypes?.banner?.sizes)
         bannerSizes.forEach(bannerSize => floors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER }));
       }
 
       if (bidRequest.mediaTypes?.video) {
         floors.video = {};
-        const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'))
+        const videoSizes = parseSizes(bidRequest?.mediaTypes?.video?.playerSize)
         videoSizes.forEach(videoSize => floors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO }));
       }
 
diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js
index 3e1a716d8e7..87ead9fe980 100644
--- a/modules/dfpAdServerVideo.js
+++ b/modules/dfpAdServerVideo.js
@@ -14,7 +14,6 @@ import { getRefererInfo } from '../src/refererDetection.js';
 import { targeting } from '../src/targeting.js';
 import {
   buildUrl,
-  deepAccess,
   formatQS,
   isEmpty,
   isNumber,
@@ -90,7 +89,7 @@ export function buildDfpVideoUrl(options) {
 
   const derivedParams = {
     correlator: Date.now(),
-    sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'),
+    sz: parseSizesInput(adUnit?.mediaTypes?.video?.playerSize).join('|'),
     url: encodeURIComponent(location.href),
   };
 
@@ -208,7 +207,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) {
  * @return {string | undefined} The encoded vast url if it exists, or undefined
  */
 function getDescriptionUrl(bid, components, prop) {
-  return deepAccess(components, `${prop}.description_url`) || encodeURIComponent(dep.ri().page);
+  return components?.[prop]?.description_url || encodeURIComponent(dep.ri().page);
 }
 
 /**
@@ -240,7 +239,7 @@ function getCustParams(bid, options, urlCustParams) {
   events.emit(EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet});
 
   // merge the prebid + publisher targeting sets
-  const publisherTargetingSet = deepAccess(options, 'params.cust_params');
+  const publisherTargetingSet = options?.params?.cust_params;
   const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet);
   let encodedParams = encodeURIComponent(formatQS(targetingSet));
   if (urlCustParams) {
diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js
index 33ddb9847fa..673e92f709b 100644
--- a/modules/prebidServerBidAdapter/index.js
+++ b/modules/prebidServerBidAdapter/index.js
@@ -1,6 +1,5 @@
 import Adapter from '../../src/adapter.js';
 import {
-  deepAccess,
   deepClone,
   flatten,
   generateUUID,
@@ -450,7 +449,7 @@ export function PrebidServer() {
 
   /* Prebid executes this function when the page asks to send out bid requests */
   baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) {
-    const adapterMetrics = s2sBidRequest.metrics = useMetrics(deepAccess(bidRequests, '0.metrics'))
+    const adapterMetrics = s2sBidRequest.metrics = useMetrics(bidRequests?.[0]?.metrics)
       .newMetrics()
       .renameWith((n) => [`adapter.s2s.${n}`, `adapters.s2s.${s2sBidRequest.s2sConfig.defaultVendor}.${n}`])
     done = adapterMetrics.startTiming('total').stopBefore(done);
@@ -568,7 +567,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques
   const requestJson = request && JSON.stringify(request);
   logInfo('BidRequest: ' + requestJson);
   const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent);
-  const customHeaders = deepAccess(s2sBidRequest, 's2sConfig.customHeaders', {});
+  const customHeaders = s2sBidRequest?.s2sConfig?.customHeaders ?? {};
   if (request && requestJson && endpointUrl) {
     const networkDone = s2sBidRequest.metrics.startTiming('net');
     ajax(
diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js
index 67cc5a372cf..9de7d3f05c9 100644
--- a/modules/prebidServerBidAdapter/ortbConverter.js
+++ b/modules/prebidServerBidAdapter/ortbConverter.js
@@ -1,5 +1,5 @@
 import {ortbConverter} from '../../libraries/ortbConverter/converter.js';
-import {deepAccess, deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js';
+import {deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js';
 import {config} from '../../src/config.js';
 import {S2S, STATUS} from '../../src/constants.js';
 import {createBid} from '../../src/bidfactory.js';
@@ -183,7 +183,7 @@ const PBS_CONVERTER = ortbConverter({
       },
       sourceExtSchain(orig, ortbRequest, proxyBidderRequest, context) {
         // pass schains in ext.prebid.schains
-        let chains = (deepAccess(ortbRequest, 'ext.prebid.schains') || []);
+        let chains = ortbRequest?.ext?.prebid?.schains || [];
         const chainBidders = new Set(chains.flatMap((item) => item.bidders));
 
         chains = Object.values(
@@ -192,7 +192,7 @@ const PBS_CONVERTER = ortbConverter({
               .filter((req) => !chainBidders.has(req.bidderCode)) // schain defined in s2sConfig.extPrebid takes precedence
               .map((req) => ({
                 bidders: [req.bidderCode],
-                schain: deepAccess(req, 'bids.0.schain')
+                schain: req?.bids?.[0]?.schain
               })))
             .filter(({bidders, schain}) => bidders?.length > 0 && schain)
             .reduce((chains, {bidders, schain}) => {
diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js
index da7559ef144..7d1c58bcdcf 100644
--- a/modules/taboolaBidAdapter.js
+++ b/modules/taboolaBidAdapter.js
@@ -3,7 +3,7 @@
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER} from '../src/mediaTypes.js';
 import {config} from '../src/config.js';
-import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject} from '../src/utils.js';
+import {deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject} from '../src/utils.js';
 import {getStorageManager} from '../src/storageManager.js';
 import {ajax} from '../src/ajax.js';
 import {ortbConverter} from '../libraries/ortbConverter/converter.js';
@@ -347,7 +347,7 @@ function fillTaboolaImpData(bid, imp) {
     imp.bidfloor = bidfloor;
     imp.bidfloorcur = bidfloorcur;
   }
-  deepSetValue(imp, 'ext.gpid', deepAccess(bid, 'ortb2Imp.ext.gpid'));
+  deepSetValue(imp, 'ext.gpid', bid?.ortb2Imp?.ext?.gpid);
 }
 
 function getBanners(bid, pos) {
diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js
index d7b9ef9be2b..cbf7a989e91 100644
--- a/modules/teadsBidAdapter.js
+++ b/modules/teadsBidAdapter.js
@@ -1,4 +1,4 @@
-import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParameter} from '../src/utils.js';
+import {logError, deepAccess, parseSizesInput, isArray, getBidIdParameter} from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {getStorageManager} from '../src/storageManager.js';
 import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js';
@@ -34,8 +34,8 @@ export const spec = {
   isBidRequestValid: function(bid) {
     let isValid = false;
     if (typeof bid.params !== 'undefined') {
-      let isValidPlacementId = _validateId(getValue(bid.params, 'placementId'));
-      let isValidPageId = _validateId(getValue(bid.params, 'pageId'));
+      let isValidPlacementId = _validateId(bid.params.placementId);
+      let isValidPageId = _validateId(bid.params.pageId);
       isValid = isValidPlacementId && isValidPageId;
     }
 
@@ -113,12 +113,12 @@ export const spec = {
       payload.us_privacy = bidderRequest.uspConsent;
     }
 
-    const userAgentClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua');
+    const userAgentClientHints = firstBidRequest?.ortb2?.device?.sua;
     if (userAgentClientHints) {
       payload.userAgentClientHints = userAgentClientHints;
     }
 
-    const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa');
+    const dsa = bidderRequest?.ortb2?.regs?.ext?.dsa;
     if (dsa) {
       payload.dsa = dsa;
     }
@@ -287,10 +287,10 @@ function findGdprStatus(gdprApplies, gdprData) {
 
 function buildRequestObject(bid) {
   const reqObj = {};
-  let placementId = getValue(bid.params, 'placementId');
-  let pageId = getValue(bid.params, 'pageId');
-  const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid');
-  const videoPlcmt = deepAccess(bid, 'mediaTypes.video.plcmt');
+  let placementId = bid.params.placementId;
+  let pageId = bid.params.pageId;
+  const gpid = bid?.ortb2Imp?.ext?.gpid;
+  const videoPlcmt = bid?.mediaTypes?.video?.plcmt;
 
   reqObj.sizes = getSizes(bid);
   reqObj.bidId = getBidIdParameter('bidId', bid);
@@ -309,9 +309,9 @@ function getSizes(bid) {
 }
 
 function concatSizes(bid) {
-  let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize');
-  let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes');
-  let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes');
+  let playerSize = bid?.mediaTypes?.video?.playerSize;
+  let videoSizes = bid?.mediaTypes?.video?.sizes;
+  let bannerSizes = bid?.mediaTypes?.banner?.sizes;
 
   if (isArray(bannerSizes) || isArray(playerSize) || isArray(videoSizes)) {
     let mediaTypesSizes = [bannerSizes, videoSizes, playerSize];
@@ -343,7 +343,7 @@ function _validateId(id) {
  * @returns `{} | {firstPartyCookieTeadsId: string}`
  */
 function getFirstPartyTeadsIdParameter(validBidRequests) {
-  const firstPartyTeadsIdFromUserIdModule = deepAccess(validBidRequests, '0.userId.teadsId');
+  const firstPartyTeadsIdFromUserIdModule = validBidRequests?.[0]?.userId?.teadsId;
 
   if (firstPartyTeadsIdFromUserIdModule) {
     return {firstPartyCookieTeadsId: firstPartyTeadsIdFromUserIdModule};
diff --git a/modules/userId/index.js b/modules/userId/index.js
index 2a9d1f27072..69988e00f3b 100644
--- a/modules/userId/index.js
+++ b/modules/userId/index.js
@@ -131,7 +131,6 @@ import {
   STORAGE_TYPE_LOCALSTORAGE
 } from '../../src/storageManager.js';
 import {
-  deepAccess,
   deepSetValue,
   delayExecution,
   isArray,
@@ -659,7 +658,7 @@ let initIdSystem;
 function getPPID(eids = getUserIdsAsEids() || []) {
   // userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com
   const matchingUserId = ppidSource && eids.find(userID => userID.source === ppidSource);
-  if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') {
+  if (matchingUserId && typeof matchingUserId?.uids?.[0]?.id === 'string') {
     const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, '');
     if (ppidValue.length >= 32 && ppidValue.length <= 150) {
       return ppidValue;
diff --git a/src/Renderer.js b/src/Renderer.js
index 912259206c4..772d8d93655 100644
--- a/src/Renderer.js
+++ b/src/Renderer.js
@@ -1,6 +1,6 @@
 import { loadExternalScript } from './adloader.js';
 import {
-  logError, logWarn, logMessage, deepAccess
+  logError, logWarn, logMessage
 } from './utils.js';
 import {find} from './polyfill.js';
 import {getGlobal} from './prebidGlobal.js';
@@ -144,11 +144,11 @@ function isRendererPreferredFromAdUnit(adUnitCode) {
   }
 
   // renderer defined at adUnit level
-  const adUnitRenderer = deepAccess(adUnit, 'renderer');
+  const adUnitRenderer = adUnit?.renderer;
   const hasValidAdUnitRenderer = !!(adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render);
 
   // renderer defined at adUnit.mediaTypes level
-  const mediaTypeRenderer = deepAccess(adUnit, 'mediaTypes.video.renderer');
+  const mediaTypeRenderer = adUnit?.mediaTypes?.video?.renderer;
   const hasValidMediaTypeRenderer = !!(mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render)
 
   return !!(
diff --git a/src/adRendering.js b/src/adRendering.js
index 130ee74278d..502721dea38 100644
--- a/src/adRendering.js
+++ b/src/adRendering.js
@@ -1,7 +1,6 @@
 import {
   createIframe,
   createInvisibleIframe,
-  deepAccess,
   inIframe,
   insertElement,
   logError,
@@ -182,14 +181,14 @@ export function handleRender({renderFn, resizeFn, adId, options, bidResponse, do
     if (bidResponse.status === BID_STATUS.RENDERED) {
       logWarn(`Ad id ${adId} has been rendered before`);
       events.emit(STALE_RENDER, bidResponse);
-      if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) {
+      if (config.getConfig('auctionOptions')?.suppressStaleRender) {
         return;
       }
     }
     if (!filters.isBidNotExpired(bidResponse)) {
       logWarn(`Ad id ${adId} has been expired`);
       events.emit(EXPIRED_RENDER, bidResponse);
-      if (deepAccess(config.getConfig('auctionOptions'), 'suppressExpiredRender')) {
+      if (config.getConfig('auctionOptions')?.suppressExpiredRender) {
         return;
       }
     }
diff --git a/src/adapterManager.js b/src/adapterManager.js
index c39ef039af3..fb396d8d794 100644
--- a/src/adapterManager.js
+++ b/src/adapterManager.js
@@ -1,7 +1,6 @@
 /** @module adaptermanger */
 
 import {
-  deepAccess,
   deepClone,
   flatten,
   generateUUID,
@@ -128,7 +127,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics}
           adUnitCode: adUnit.code,
           transactionId: adUnit.transactionId,
           adUnitId: adUnit.adUnitId,
-          sizes: deepAccess(mediaTypes, 'banner.sizes') || deepAccess(mediaTypes, 'video.playerSize') || [],
+          sizes: mediaTypes?.banner?.sizes || mediaTypes?.video?.playerSize || [],
           bidId: bid.bid_id || getUniqueIdentifierStr(),
           bidderRequestId,
           auctionId,
diff --git a/src/auction.js b/src/auction.js
index 9cc6dd48401..3945e0bfe53 100644
--- a/src/auction.js
+++ b/src/auction.js
@@ -66,9 +66,7 @@
  */
 
 import {
-  deepAccess,
   generateUUID,
-  getValue,
   isEmpty,
   isEmptyStr,
   isFn,
@@ -566,13 +564,12 @@ export function addBidToAuction(auctionInstance, bidResponse) {
 function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = auctionManager.index} = {}) {
   let addBid = true;
 
-  const videoMediaType = deepAccess(
-    index.getMediaTypes({
-      requestId: bidResponse.originalRequestId || bidResponse.requestId,
-      adUnitId: bidResponse.adUnitId
-    }), 'video');
-  const context = videoMediaType && deepAccess(videoMediaType, 'context');
-  const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey');
+  const videoMediaType = index.getMediaTypes({
+    requestId: bidResponse.originalRequestId || bidResponse.requestId,
+    adUnitId: bidResponse.adUnitId
+  })?.video;
+  const context = videoMediaType && videoMediaType?.context;
+  const useCacheKey = videoMediaType && videoMediaType?.useCacheKey;
 
   if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) {
     if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) {
@@ -691,7 +688,7 @@ function setupBidTargeting(bidObject) {
 export function getMediaTypeGranularity(mediaType, mediaTypes, mediaTypePriceGranularity) {
   if (mediaType && mediaTypePriceGranularity) {
     if (FEATURES.VIDEO && mediaType === VIDEO) {
-      const context = deepAccess(mediaTypes, `${VIDEO}.context`, 'instream');
+      const context = mediaTypes?.[VIDEO]?.context ?? 'instream';
       if (mediaTypePriceGranularity[`${VIDEO}-${context}`]) {
         return mediaTypePriceGranularity[`${VIDEO}-${context}`];
       }
@@ -764,7 +761,7 @@ export const getAdvertiserDomain = () => {
  */
 export const getDSP = () => {
   return (bid) => {
-    return (bid.meta && (bid.meta.networkId || bid.meta.networkName)) ? deepAccess(bid, 'meta.networkName') || deepAccess(bid, 'meta.networkId') : '';
+    return (bid.meta && (bid.meta.networkId || bid.meta.networkName)) ? bid?.meta?.networkName || bid?.meta?.networkId : '';
   }
 }
 
@@ -787,7 +784,7 @@ function createKeyVal(key, value) {
         return value(bidResponse, bidReq);
       }
       : function (bidResponse) {
-        return getValue(bidResponse, value);
+        return bidResponse[value];
       }
   };
 }
@@ -836,8 +833,7 @@ export function getStandardBidderSettings(mediaType, bidderCode) {
 
       if (typeof find(adserverTargeting, targetingKeyVal => targetingKeyVal.key === TARGETING_KEYS.CACHE_HOST) === 'undefined') {
         adserverTargeting.push(createKeyVal(TARGETING_KEYS.CACHE_HOST, function(bidResponse) {
-          return deepAccess(bidResponse, `adserverTargeting.${TARGETING_KEYS.CACHE_HOST}`)
-            ? bidResponse.adserverTargeting[TARGETING_KEYS.CACHE_HOST] : urlInfo.hostname;
+          return bidResponse?.adserverTargeting?.[TARGETING_KEYS.CACHE_HOST] || urlInfo.hostname;
         }));
       }
     }
diff --git a/src/native.js b/src/native.js
index a641beb71d5..19833406451 100644
--- a/src/native.js
+++ b/src/native.js
@@ -1,5 +1,4 @@
 import {
-  deepAccess,
   deepClone, getDefinedParams,
   insertHtmlIntoIframe,
   isArray,
@@ -129,7 +128,7 @@ export function processNativeAdUnitParams(params) {
 export function decorateAdUnitsWithNativeParams(adUnits) {
   adUnits.forEach(adUnit => {
     const nativeParams =
-      adUnit.nativeParams || deepAccess(adUnit, 'mediaTypes.native');
+      adUnit.nativeParams || adUnit?.mediaTypes?.native;
     if (nativeParams) {
       adUnit.nativeParams = processNativeAdUnitParams(nativeParams);
     }
@@ -213,7 +212,7 @@ function typeIsSupported(type) {
  */
 export const nativeAdUnit = adUnit => {
   const mediaType = adUnit.mediaType === 'native';
-  const mediaTypes = deepAccess(adUnit, 'mediaTypes.native');
+  const mediaTypes = adUnit?.mediaTypes?.native;
   return mediaType || mediaTypes;
 }
 export const nativeBidder = bid => includes(nativeAdapters, bid.bidder);
@@ -236,7 +235,7 @@ export function nativeBidIsValid(bid, {index = auctionManager.index} = {}) {
 }
 
 export function isNativeOpenRTBBidValid(bidORTB, bidRequestORTB) {
-  if (!deepAccess(bidORTB, 'link.url')) {
+  if (!bidORTB?.link?.url) {
     logError(`native response doesn't have 'link' property. Ortb response: `, bidORTB);
     return false;
   }
@@ -364,10 +363,7 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) {
   let keyValues = {};
   const adUnit = index.getAdUnit(bid);
 
-  const globalSendTargetingKeys = adUnit?.nativeParams?.ortb == null && deepAccess(
-    adUnit,
-    `nativeParams.sendTargetingKeys`
-  ) !== false;
+  const globalSendTargetingKeys = adUnit?.nativeParams?.ortb == null && adUnit?.nativeParams?.sendTargetingKeys !== false;
 
   const nativeKeys = getNativeKeys(adUnit);
 
@@ -376,15 +372,15 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) {
 
   Object.keys(flatBidNativeKeys).forEach(asset => {
     const key = nativeKeys[asset];
-    let value = getAssetValue(bid.native[asset]) || getAssetValue(deepAccess(bid, `native.ext.${asset}`));
+    let value = getAssetValue(bid.native[asset]) || getAssetValue(bid?.native?.ext?.[asset]);
 
     if (asset === 'adTemplate' || !key || !value) {
       return;
     }
 
-    let sendPlaceholder = deepAccess(adUnit, `nativeParams.${asset}.sendId`);
+    let sendPlaceholder = adUnit?.nativeParams?.[asset]?.sendId;
     if (typeof sendPlaceholder !== 'boolean') {
-      sendPlaceholder = deepAccess(adUnit, `nativeParams.ext.${asset}.sendId`);
+      sendPlaceholder = adUnit?.nativeParams?.ext?.[asset]?.sendId;
     }
 
     if (sendPlaceholder) {
@@ -392,9 +388,9 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) {
       value = placeholder;
     }
 
-    let assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.${asset}.sendTargetingKeys`);
+    let assetSendTargetingKeys = adUnit?.nativeParams?.[asset]?.sendTargetingKeys;
     if (typeof assetSendTargetingKeys !== 'boolean') {
-      assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.ext.${asset}.sendTargetingKeys`);
+      assetSendTargetingKeys = adUnit?.nativeParams?.ext?.[asset]?.sendTargetingKeys;
     }
 
     const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys;
@@ -482,7 +478,7 @@ function getAssetValue(value) {
 function getNativeKeys(adUnit) {
   const extraNativeKeys = {}
 
-  if (deepAccess(adUnit, 'nativeParams.ext')) {
+  if (adUnit?.nativeParams?.ext) {
     Object.keys(adUnit.nativeParams.ext).forEach(extKey => {
       extraNativeKeys[extKey] = `hb_native_${extKey}`;
     })
diff --git a/src/prebid.js b/src/prebid.js
index 2a9b88cd47d..37d37acfee7 100644
--- a/src/prebid.js
+++ b/src/prebid.js
@@ -2,7 +2,6 @@
 
 import {getGlobal} from './prebidGlobal.js';
 import {
-  deepAccess,
   deepClone,
   deepSetValue,
   flatten,
@@ -213,7 +212,7 @@ function validateNativeMediaType(adUnit) {
 }
 
 function validateAdUnitPos(adUnit, mediaType) {
-  let pos = deepAccess(adUnit, `mediaTypes.${mediaType}.pos`);
+  let pos = adUnit?.mediaTypes?.[mediaType]?.pos;
 
   if (!isNumber(pos) || isNaN(pos) || !isFinite(pos)) {
     let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`;
diff --git a/src/utils/gdpr.js b/src/utils/gdpr.js
index 19c7126b7d7..6151861a67b 100644
--- a/src/utils/gdpr.js
+++ b/src/utils/gdpr.js
@@ -1,5 +1,3 @@
-import {deepAccess} from '../utils.js';
-
 /**
  * Check if GDPR purpose 1 consent was given.
  *
@@ -8,7 +6,7 @@ import {deepAccess} from '../utils.js';
  */
 export function hasPurpose1Consent(gdprConsent) {
   if (gdprConsent?.gdprApplies) {
-    return deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true;
+    return gdprConsent?.vendorData?.purpose?.consents?.[1] === true;
   }
   return true;
 }
diff --git a/src/video.js b/src/video.js
index 9be9adec4c5..a859fcb17bd 100644
--- a/src/video.js
+++ b/src/video.js
@@ -1,4 +1,4 @@
-import {deepAccess, isArrayOfNums, isInteger, isNumber, isPlainObject, isStr, logError, logWarn} from './utils.js';
+import {isArrayOfNums, isInteger, isNumber, isPlainObject, isStr, logError, logWarn} from './utils.js';
 import {config} from '../src/config.js';
 import {hook} from './hook.js';
 import {auctionManager} from './auctionManager.js';
@@ -107,9 +107,9 @@ export function validateOrtbVideoFields(adUnit, onInvalidParam) {
  * @return {Boolean} If object is valid
  */
 export function isValidVideoBid(bid, {index = auctionManager.index} = {}) {
-  const videoMediaType = deepAccess(index.getMediaTypes(bid), 'video');
-  const context = videoMediaType && deepAccess(videoMediaType, 'context');
-  const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey');
+  const videoMediaType = index.getMediaTypes(bid)?.video;
+  const context = videoMediaType && videoMediaType?.context;
+  const useCacheKey = videoMediaType && videoMediaType?.useCacheKey;
   const adUnit = index.getAdUnit(bid);
 
   // if context not defined assume default 'instream' for video bids
diff --git a/test/spec/unit/core/bidderSettings_spec.js b/test/spec/unit/core/bidderSettings_spec.js
index ece18040d1e..7e6446b456b 100644
--- a/test/spec/unit/core/bidderSettings_spec.js
+++ b/test/spec/unit/core/bidderSettings_spec.js
@@ -19,6 +19,13 @@ describe('ScopedSettings', () => {
       expect(settings.get('scope', 'key')).to.equal('value');
     });
 
+    it('can retrieve nested settings', () => {
+      data = {
+        scope: {outer: {key: 'value'}}
+      }
+      expect(settings.get('scope', 'outer.key')).to.equal('value');
+    })
+
     it('should fallback to fallback scope', () => {
       data = {
         fallback: {

From 0ca1fee9ade63a839e94b54874af141c03e3a6fa Mon Sep 17 00:00:00 2001
From: pm-priyanka-deshmane
 <107103300+pm-priyanka-deshmane@users.noreply.github.com>
Date: Fri, 6 Dec 2024 16:59:06 +0530
Subject: [PATCH 0738/1097] targeting keys issue when sendAllBids is true
 (#12518)

---
 src/targeting.js                      | 10 +++---
 test/spec/unit/core/targeting_spec.js | 45 +++++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 6 deletions(-)

diff --git a/src/targeting.js b/src/targeting.js
index 55783c9632c..1903524984b 100644
--- a/src/targeting.js
+++ b/src/targeting.js
@@ -207,9 +207,7 @@ export function newTargeting(auctionManager) {
     });
   };
 
-  function addBidToTargeting(bids, bidderLevelTargetingEnabled = false, deals = false) {
-    if (!bidderLevelTargetingEnabled) return [];
-
+  function addBidToTargeting(bids, enableSendAllBids = false, deals = false) {
     const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice();
     const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys');
 
@@ -218,7 +216,7 @@ export function newTargeting(auctionManager) {
       : standardKeys;
 
     return bids.reduce((result, bid) => {
-      if ((!deals || bid.dealId)) {
+      if (enableSendAllBids || (deals && bid.dealId)) {
         const targetingValue = getTargetingMap(bid, standardKeys.filter(
           key => typeof bid.adserverTargeting[key] !== 'undefined' &&
           (deals || allowedSendAllBidTargeting.indexOf(key) !== -1)));
@@ -233,8 +231,8 @@ export function newTargeting(auctionManager) {
 
   function getBidderTargeting(bids) {
     const alwaysIncludeDeals = config.getConfig('targetingControls.alwaysIncludeDeals');
-    const bidderLevelTargetingEnabled = config.getConfig('enableSendAllBids') || alwaysIncludeDeals;
-    return addBidToTargeting(bids, bidderLevelTargetingEnabled, alwaysIncludeDeals);
+    const enableSendAllBids = config.getConfig('enableSendAllBids');
+    return addBidToTargeting(bids, enableSendAllBids, alwaysIncludeDeals);
   }
 
   /**
diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js
index f3d0c6df442..2296379ca43 100644
--- a/test/spec/unit/core/targeting_spec.js
+++ b/test/spec/unit/core/targeting_spec.js
@@ -861,6 +861,51 @@ describe('targeting tests', function () {
       });
     });
 
+    describe('targetingControls.alwaysIncludeDeals with enableSendAllBids', function () {
+      beforeEach(function() {
+        enableSendAllBids = true;
+      });
+
+      it('includes bids w/o deal when enableSendAllBids and alwaysIncludeDeals set to true', function () {
+        config.setConfig({
+          enableSendAllBids: true,
+          targetingControls: {
+            alwaysIncludeDeals: true
+          }
+        });
+
+        let bid5 = utils.deepClone(bid1);
+        bid5.adserverTargeting = {
+          hb_pb: '3.0',
+          hb_adid: '111111',
+          hb_bidder: 'pubmatic',
+          foobar: '300x250'
+        };
+        bid5.bidder = bid5.bidderCode = 'pubmatic';
+        bid5.cpm = 3.0; // winning bid!
+        delete bid5.dealId; // no deal with winner
+        bidsReceived.push(bid5);
+
+        const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']);
+
+        // Pubmatic wins but no deal. But enableSendAllBids is true.
+        // So Pubmatic is passed through
+        expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({
+          'hb_bidder': 'pubmatic',
+          'hb_adid': '111111',
+          'hb_pb': '3.0',
+          'foobar': '300x250',
+          'hb_pb_pubmatic': '3.0',
+          'hb_adid_pubmatic': '111111',
+          'hb_bidder_pubmatic': 'pubmatic',
+          'hb_deal_rubicon': '1234',
+          'hb_pb_rubicon': '0.53',
+          'hb_adid_rubicon': '148018fe5e',
+          'hb_bidder_rubicon': 'rubicon'
+        });
+      });
+    });
+
     it('selects the top bid when enableSendAllBids true', function () {
       enableSendAllBids = true;
       let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']);

From 06024e8ca670e172d34ca537214113b20d454faa Mon Sep 17 00:00:00 2001
From: Evgenii Novikov 
Date: Fri, 6 Dec 2024 13:37:20 +0100
Subject: [PATCH 0739/1097] Yandex Metrica Analytics: updated links to the
 interface (#12548)

---
 modules/yandexAnalyticsAdapter.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/yandexAnalyticsAdapter.md b/modules/yandexAnalyticsAdapter.md
index 43460550471..0863cc1dfb6 100644
--- a/modules/yandexAnalyticsAdapter.md
+++ b/modules/yandexAnalyticsAdapter.md
@@ -20,7 +20,7 @@ Disclosure: The adapter utilizes the Metrica Tag build based on [github.com/yand
 
 2. **Insert Counter Initialization Code:**
 
-   Retrieve the counter initialization code from the Yandex Metrica settings page at `https://metrica.yandex.com/settings?id={counterId}`, where `{counterId}` is your counter ID, and embed it into your website's HTML.
+   Retrieve the counter initialization code from the Yandex Metrica settings page at [metrica.yandex.com/r/settings](https://metrica.yandex.com/r/settings), and embed it into your website's HTML.
 
 3. **Initialize the Adapter in Prebid.js:**
 
@@ -43,4 +43,4 @@ Disclosure: The adapter utilizes the Metrica Tag build based on [github.com/yand
 
 ## Accessing Analytics Data
 
-You can view the collected analytics data in the Yandex Metrica dashboard. Navigate to [metrika.yandex.com/dashboard](https://metrika.yandex.com/dashboard) and look for the Prebid Analytics section to analyze your data.
+You can view the collected analytics data in the Yandex Metrica dashboard. Navigate to [metrika.yandex.com/r/stat/prebid_events](https://metrika.yandex.com/r/stat/prebid_events) to analyze your data.

From 80364ce42af5db937c164a91689dc3c89c8161b4 Mon Sep 17 00:00:00 2001
From: wojciech-bialy-wpm
 <67895844+wojciech-bialy-wpm@users.noreply.github.com>
Date: Fri, 6 Dec 2024 21:06:09 +0100
Subject: [PATCH 0740/1097] sspBC Bid Adapter : added support for transactionID
 and update tests (#12547)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Update tests for sspBC adapter

Update tests for sspBC adapter:
- change userSync test (due to tcf param appended in v4.6)
- add tests for onBidWon and onTimeout

* [sspbc-adapter] 5.3 updates: content-type for notifications

* [sspbc-adapter] pass CTA to native bid

* [sspbc-5.3] keep pbsize for detected adunits

* [maintenance] - remove old test for sspBc bid adaptor

* [sspbc-5.3] increment adaptor ver

* [sspbc-adapter] maintenance update to sspBCBidAdapter

* remove yarn.lock

* Delete package-lock.json

* remove package-lock.jsonfrom pull request

* [sspbc-adapter] send pageViewId in request

* [sspbc-adapter] update pageViewId test

* [sspbc-adapter] add viewabiility tracker to native ads

* [sspbc-adapter] add support for bid.admNative property

* [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request)

* [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative

* [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response

* [sspbc-adapter] jstracker data fix

* [sspbc-adapter] jstracker data fix

* [sspbc-adapter] send tagid in notifications

* [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync

* update remote repo

* cleanup of grupawp/prebid master branch

* update sspBC adapter to v 5.9

* update tests for sspBC bid adapter

* [sspbc-adapter] add support for topicsFPD module

* [sspbc-adapter] change topic segment ids to int

* sspbc adapter -> update to v6

* [sspbc-adapter] update to v6.1 - add image sync, transactionId & schain support, improve test coverage

* [sspbc-adapter] fix typos

---------

Co-authored-by: Wojciech Biały 
Co-authored-by: decemberWP <155962474+decemberWP@users.noreply.github.com>
---
 modules/sspBCBidAdapter.js                | 41 ++++++++++++++++++-----
 test/spec/modules/sspBCBidAdapter_spec.js | 35 ++++++++++++++++---
 2 files changed, 63 insertions(+), 13 deletions(-)

diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js
index 1227b6fdc76..03a9f5a1da9 100644
--- a/modules/sspBCBidAdapter.js
+++ b/modules/sspBCBidAdapter.js
@@ -8,11 +8,12 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j
 
 const BIDDER_CODE = 'sspBC';
 const BIDDER_URL = 'https://ssp.wp.pl/bidder/';
-const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync';
+const SYNC_URL_IFRAME = 'https://ssp.wp.pl/bidder/usersync';
+const SYNC_URL_IMAGE = 'https://ssp.wp.pl/v1/sync/pixel';
 const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify';
 const GVLID = 676;
 const TMAX = 450;
-const BIDDER_VERSION = '6.00';
+const BIDDER_VERSION = '6.10';
 const DEFAULT_CURRENCY = 'PLN';
 const W = window;
 const { navigator } = W;
@@ -70,7 +71,20 @@ const getContentLanguage = () => {
     const topWindow = getWindowTop();
     return topWindow.document.body.parentNode.lang;
   } catch (err) {
-    logWarn('Could not read language form top-level html', err);
+    logWarn('Could not read language from top-level html', err);
+  }
+};
+
+/**
+ * Get host name of the top level html object
+ * @returns {string} host name
+ */
+const getTopHost = () => {
+  try {
+    const topWindow = getWindowTop();
+    return topWindow.location.host;
+  } catch (err) {
+    logWarn('Could not read host from top-level window', err);
   }
 };
 
@@ -604,7 +618,9 @@ const spec = {
     const pbver = '$prebid.version$';
     const testMode = setOnAny(validBidRequests, 'params.test') ? 1 : undefined;
     const ref = bidderRequest.refererInfo.ref;
-    const { regs = {} } = ortb2 || {};
+    const { source = {}, regs = {} } = ortb2 || {};
+
+    source.schain = setOnAny(validBidRequests, 'schain');
 
     const payload = {
       id: bidderRequest.bidderRequestId,
@@ -621,6 +637,7 @@ const spec = {
       tmax,
       user: {},
       regs,
+      source,
       device: {
         language: getBrowserLanguage(),
         w: screen.width,
@@ -764,14 +781,22 @@ const spec = {
 
     return fledgeAuctionConfigs.length ? { bids, fledgeAuctionConfigs } : bids;
   },
-  getUserSyncs(syncOptions) {
+
+  getUserSyncs(syncOptions, _, gdprConsent = {}) {
+    const {iframeEnabled, pixelEnabled} = syncOptions;
+    const {gdprApplies, consentString = ''} = gdprConsent;
     let mySyncs = [];
-    if (syncOptions.iframeEnabled) {
+    if (iframeEnabled) {
       mySyncs.push({
         type: 'iframe',
-        url: `${SYNC_URL}?tcf=2&pvid=${pageView.id}&sn=${pageView.sn}`,
+        url: `${SYNC_URL_IFRAME}?tcf=2&pvid=${pageView.id}&sn=${pageView.sn}`,
       });
-    };
+    } else if (pixelEnabled) {
+      mySyncs.push({
+        type: 'image',
+        url: `${SYNC_URL_IMAGE}?inver=0&platform=wpartner&host=${getTopHost() || ''}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}`,
+      });
+    }
     return mySyncs;
   },
 
diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js
index 623faab5f1e..ceaad85faac 100644
--- a/test/spec/modules/sspBCBidAdapter_spec.js
+++ b/test/spec/modules/sspBCBidAdapter_spec.js
@@ -4,7 +4,8 @@ import * as utils from 'src/utils.js';
 
 const BIDDER_CODE = 'sspBC';
 const BIDDER_URL = 'https://ssp.wp.pl/bidder/';
-const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync';
+const SYNC_URL_IFRAME = 'https://ssp.wp.pl/bidder/usersync';
+const SYNC_URL_IMAGE = 'https://ssp.wp.pl/v1/sync/pixel';
 
 describe('SSPBC adapter', function () {
   function prepareTestData() {
@@ -649,6 +650,25 @@ describe('SSPBC adapter', function () {
       expect(extAssets1).to.have.property('pbsize').that.equals('750x200_1')
       expect(extAssets2).to.have.property('pbsize').that.equals('750x200_1')
     });
+
+    it('should send supply chain data', function () {
+      const supplyChain = {
+        ver: '1.0',
+        complete: 1,
+        nodes: [
+          {
+            asi: 'first-seller.com',
+            sid: '00001',
+            hp: 1
+          },
+        ]
+      }
+      const bidWithSupplyChain = Object.assign(bids[0], { schain: supplyChain });
+      const requestWithSupplyChain = spec.buildRequests([bidWithSupplyChain], bidRequest);
+      const payloadWithSupplyChain = requestWithSupplyChain ? JSON.parse(requestWithSupplyChain.data) : { site: false, imp: false };
+
+      expect(payloadWithSupplyChain.source).to.have.property('schain').that.has.keys('ver', 'complete', 'nodes');
+    });
   });
 
   describe('interpretResponse', function () {
@@ -741,13 +761,18 @@ describe('SSPBC adapter', function () {
     let syncResultImage = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true });
     let syncResultNone = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false });
 
-    it('should provide correct url, if frame sync is allowed', function () {
+    it('should provide correct iframe url, if frame sync is allowed', function () {
       expect(syncResultAll).to.have.length(1);
-      expect(syncResultAll[0].url).to.have.string(SYNC_URL);
+      expect(syncResultAll[0].url).to.have.string(SYNC_URL_IFRAME);
+    });
+
+    it('should provide correct image url, if image sync is allowed', function () {
+      expect(syncResultImage).to.have.length(1);
+      expect(syncResultImage[0].url).to.have.string(SYNC_URL_IMAGE);
     });
 
-    it('should send no syncs, if frame sync is not allowed', function () {
-      expect(syncResultImage).to.have.length(0);
+    it('should send no syncs, if no sync is allowed', function () {
+      expect(syncResultNone).to.have.length(0);
       expect(syncResultNone).to.have.length(0);
     });
   });

From 05d9c7485c22a940f61f41c07b59f2456370b057 Mon Sep 17 00:00:00 2001
From: mkomorski 
Date: Mon, 9 Dec 2024 14:34:00 +0100
Subject: [PATCH 0741/1097] Core: Sync between ortb2Imp and mediaTypes (#12423)

* ortb2 <-> mediaTypes

* native & banner params

* syncOrtb2

* improvements

* improvement

* lint fix

* type check

* instance of Map check

* get rid of entries

* removing native from syncOrtb2

* Update test/spec/banner_spec.js

Co-authored-by: Demetrio Girardi 

* Update src/prebid.js

Co-authored-by: Demetrio Girardi 

* Update prebid.js

* video test fix

---------

Co-authored-by: Marcin Komorski 
Co-authored-by: Patrick McCann 
Co-authored-by: Demetrio Girardi 
---
 src/banner.js                   |  21 ++++
 src/prebid.js                   |  47 ++++++--
 test/spec/banner_spec.js        | 182 ++++++++++++++++++++++++++++
 test/spec/native_spec.js        |   2 +-
 test/spec/unit/pbjs_api_spec.js |  67 -----------
 test/spec/video_spec.js         | 204 ++++++++++++++++++++++++++++++++
 6 files changed, 442 insertions(+), 81 deletions(-)
 create mode 100644 src/banner.js
 create mode 100644 test/spec/banner_spec.js

diff --git a/src/banner.js b/src/banner.js
new file mode 100644
index 00000000000..25da06b6669
--- /dev/null
+++ b/src/banner.js
@@ -0,0 +1,21 @@
+import { isArrayOfNums, isInteger, isStr } from './utils.js';
+
+/**
+ * List of OpenRTB 2.x banner object properties with simple validators.
+ * Not included: `ext`
+ * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md
+ */
+export const ORTB_BANNER_PARAMS = new Map([
+  [ 'format', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'object') ],
+  [ 'w', isInteger ],
+  [ 'h', isInteger ],
+  [ 'btype', isArrayOfNums ],
+  [ 'battr', isArrayOfNums ],
+  [ 'pos', isInteger ],
+  [ 'mimes', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string') ],
+  [ 'topframe', value => [1, 0].includes(value) ],
+  [ 'expdir', isArrayOfNums ],
+  [ 'api', isArrayOfNums ],
+  [ 'id', isStr ],
+  [ 'vcm', value => [1, 0].includes(value) ]
+]);
diff --git a/src/prebid.js b/src/prebid.js
index 37d37acfee7..6da2504dca1 100644
--- a/src/prebid.js
+++ b/src/prebid.js
@@ -46,7 +46,9 @@ import {
   renderIfDeferred
 } from './adRendering.js';
 import {getHighestCpm} from './utils/reducers.js';
-import {fillVideoDefaults, validateOrtbVideoFields} from './video.js';
+import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js';
+import { ORTB_BANNER_PARAMS } from './banner.js';
+import { BANNER, VIDEO } from './mediaTypes.js';
 
 const pbjsInstance = getGlobal();
 const { triggerUserSyncs } = userSync;
@@ -105,20 +107,40 @@ function validateSizes(sizes, targLength) {
   return cleanSizes;
 }
 
-export function setBattrForAdUnit(adUnit, mediaType) {
-  const ortb2Imp = adUnit.ortb2Imp || {};
-  const mediaTypes = adUnit.mediaTypes || {};
+// synchronize fields between mediaTypes[mediaType] and ortb2Imp[mediaType]
+export function syncOrtb2(adUnit, mediaType) {
+  const ortb2Imp = deepAccess(adUnit, `ortb2Imp.${mediaType}`);
+  const mediaTypes = deepAccess(adUnit, `mediaTypes.${mediaType}`);
 
-  if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) {
-    logWarn(`Ad unit ${adUnit.code} specifies conflicting ortb2Imp.${mediaType}.battr and mediaTypes.${mediaType}.battr, the latter will be ignored`, adUnit);
+  if (!ortb2Imp && !mediaTypes) {
+    // omitting sync due to not present mediaType
+    return;
   }
 
-  const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr;
+  const fields = {
+    [VIDEO]: FEATURES.VIDEO && ORTB_VIDEO_PARAMS,
+    [BANNER]: ORTB_BANNER_PARAMS
+  }[mediaType];
 
-  if (battr != null) {
-    deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr);
-    deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr);
+  if (!fields) {
+    return;
   }
+
+  [...fields].forEach(([key, validator]) => {
+    const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`);
+    const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`);
+
+    if (mediaTypesFieldValue == undefined && ortbFieldValue == undefined) {
+      // omitting the params if it's not defined on either of sides
+    } else if (mediaTypesFieldValue == undefined) {
+      deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue);
+    } else if (ortbFieldValue == undefined) {
+      deepSetValue(adUnit, `ortb2Imp.${mediaType}.${key}`, mediaTypesFieldValue);
+    } else {
+      logWarn(`adUnit ${adUnit.code}: specifies conflicting ortb2Imp.${mediaType}.${key} and mediaTypes.${mediaType}.${key}, the latter will be ignored`, adUnit);
+      deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue);
+    }
+  });
 }
 
 function validateBannerMediaType(adUnit) {
@@ -133,7 +155,7 @@ function validateBannerMediaType(adUnit) {
     logError('Detected a mediaTypes.banner object without a proper sizes field.  Please ensure the sizes are listed like: [[300, 250], ...].  Removing invalid mediaTypes.banner object from request.');
     delete validatedAdUnit.mediaTypes.banner
   }
-  setBattrForAdUnit(validatedAdUnit, 'banner');
+  syncOrtb2(validatedAdUnit, 'banner')
   return validatedAdUnit;
 }
 
@@ -157,7 +179,7 @@ function validateVideoMediaType(adUnit) {
     }
   }
   validateOrtbVideoFields(validatedAdUnit);
-  setBattrForAdUnit(validatedAdUnit, 'video');
+  syncOrtb2(validatedAdUnit, 'video');
   return validatedAdUnit;
 }
 
@@ -207,7 +229,6 @@ function validateNativeMediaType(adUnit) {
     logError('Please use an array of sizes for native.icon.sizes field.  Removing invalid mediaTypes.native.icon.sizes property from request.');
     delete validatedAdUnit.mediaTypes.native.icon.sizes;
   }
-  setBattrForAdUnit(validatedAdUnit, 'native');
   return validatedAdUnit;
 }
 
diff --git a/test/spec/banner_spec.js b/test/spec/banner_spec.js
new file mode 100644
index 00000000000..80273686ff3
--- /dev/null
+++ b/test/spec/banner_spec.js
@@ -0,0 +1,182 @@
+import * as utils from '../../src/utils.js';
+import { syncOrtb2 } from '../../src/prebid.js';
+
+describe('banner', () => {
+  describe('syncOrtb2', () => {
+    let logWarnSpy;
+
+    beforeEach(function () {
+      logWarnSpy = sinon.spy(utils, 'logWarn');
+    });
+
+    afterEach(function () {
+      utils.logWarn.restore();
+    });
+
+    it('should properly sync fields if both present', () => {
+      const adUnit = {
+        mediaTypes: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [1, 2, 34], // should be overwritten with value from ortb2Imp
+            battr: [3, 4],
+            maxduration: 'omitted_value' // should be omitted during copying - not part of banner obj spec
+          }
+        },
+        ortb2Imp: {
+          banner: {
+            request: '{payload: true}',
+            pos: 5,
+            btype: [999, 999],
+            vcm: 0,
+            foobar: 'omitted_value' // should be omitted during copying - not part of banner obj spec
+          }
+        }
+      };
+
+      const expected = {
+        mediaTypes: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+            maxduration: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            request: '{payload: true}',
+            pos: 5,
+            btype: [999, 999],
+            battr: [3, 4],
+            vcm: 0,
+            foobar: 'omitted_value'
+          }
+        }
+      };
+
+      syncOrtb2(adUnit, 'banner');
+      expect(adUnit).to.deep.eql(expected);
+
+      assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting btype');
+    });
+
+    it('should omit sync if mediaType not present on adUnit', () => {
+      const adUnit = {
+        mediaTypes: {
+          video: {
+            fieldToOmit: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          video: {
+            fieldToOmit2: 'omitted_value'
+          }
+        }
+      };
+
+      syncOrtb2(adUnit, 'banner');
+
+      expect(adUnit.ortb2Imp.banner).to.be.undefined;
+      expect(adUnit.mediaTypes.banner).to.be.undefined;
+    });
+
+    it('should properly sync if mediaTypes is not present on any of side', () => {
+      const adUnit = {
+        mediaTypes: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+            maxduration: 'omitted_value'
+          }
+        },
+      };
+
+      const expected1 = {
+        mediaTypes: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+            maxduration: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+          }
+        }
+      };
+
+      syncOrtb2(adUnit, 'banner');
+      expect(adUnit).to.deep.eql(expected1);
+
+      const adUnit2 = {
+        mediaTypes: {},
+        ortb2Imp: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+            maxduration: 'omitted_value'
+          }
+        }
+      };
+
+      const expected2 = {
+        mediaTypes: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+          }
+        },
+        ortb2Imp: {
+          banner: {
+            format: [{w: 100, h: 100}],
+            btype: [999, 999],
+            pos: 5,
+            battr: [3, 4],
+            vcm: 0,
+            maxduration: 'omitted_value'
+          }
+        }
+      };
+
+      syncOrtb2(adUnit2, 'banner');
+      expect(adUnit2).to.deep.eql(expected2);
+    });
+
+    it('should not create empty banner object on ortb2Imp if there was nothing to copy', () => {
+      const adUnit2 = {
+        mediaTypes: {
+          banner: {
+            noOrtbBannerField1: 'value',
+            noOrtbBannerField2: 'value'
+          }
+        },
+        ortb2Imp: {
+          // lack of banner field
+        }
+      };
+      syncOrtb2(adUnit2, 'banner');
+      expect(adUnit2.ortb2Imp.banner).to.be.undefined
+    });
+  });
+})
diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js
index 877306c1c04..01214cdb3ae 100644
--- a/test/spec/native_spec.js
+++ b/test/spec/native_spec.js
@@ -22,7 +22,7 @@ import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '..
 import {auctionManager} from '../../src/auctionManager.js';
 import {getRenderingData} from '../../src/adRendering.js';
 import {getCreativeRendererSource} from '../../src/creativeRenderers.js';
-import {deepClone, deepSetValue} from '../../src/utils.js';
+import {deepSetValue} from '../../src/utils.js';
 const utils = require('src/utils');
 
 const bid = {
diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js
index e1f5b3b5b88..0a9de8e976c 100644
--- a/test/spec/unit/pbjs_api_spec.js
+++ b/test/spec/unit/pbjs_api_spec.js
@@ -3786,71 +3786,4 @@ describe('Unit: Prebid Module', function () {
       expect(auctionManager.getBidsReceived().length).to.equal(0);
     });
   });
-
-  describe('setBattrForAdUnit', () => {
-    it('should set copy battr to both places', () => {
-      const adUnit = {
-        ortb2Imp: {
-          video: {
-            battr: 'banned attribute'
-          }
-        },
-        mediaTypes: {
-          video: {}
-        }
-      }
-
-      setBattrForAdUnit(adUnit, 'video');
-
-      expect(adUnit.mediaTypes.video.battr).to.deep.equal('banned attribute');
-      expect(adUnit.ortb2Imp.video.battr).to.deep.equal('banned attribute');
-
-      const adUnit2 = {
-        mediaTypes: {
-          video: {
-            battr: 'banned attribute'
-          }
-        },
-        ortb2Imp: {
-          video: {}
-        }
-      }
-
-      setBattrForAdUnit(adUnit2, 'video');
-
-      expect(adUnit2.ortb2Imp.video.battr).to.deep.equal('banned attribute');
-      expect(adUnit2.mediaTypes.video.battr).to.deep.equal('banned attribute');
-    })
-
-    it('should log warn if both are specified and differ from eachother', () => {
-      let spyLogWarn = sinon.spy(utils, 'logWarn');
-      const adUnit = {
-        mediaTypes: {
-          native: {
-            battr: 'banned attribute'
-          }
-        },
-        ortb2Imp: {
-          native: {
-            battr: 'banned attribute 2'
-          }
-        }
-      }
-      setBattrForAdUnit(adUnit, 'native');
-      sinon.assert.calledOnce(spyLogWarn);
-      spyLogWarn.resetHistory();
-      utils.logWarn.restore();
-    })
-
-    it('should not copy for undefined battr', () => {
-      const adUnit = {
-        mediaTypes: {
-          native: {}
-        }
-      }
-      setBattrForAdUnit(adUnit, 'native');
-      expect(adUnit.mediaTypes.native).to.deep.equal({});
-      expect(adUnit.mediaTypes.ortb2Imp).to.not.exist;
-    })
-  })
 });
diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js
index 18955771049..0d2a32659e9 100644
--- a/test/spec/video_spec.js
+++ b/test/spec/video_spec.js
@@ -2,6 +2,7 @@ import {fillVideoDefaults, isValidVideoBid, validateOrtbVideoFields} from 'src/v
 import {hook} from '../../src/hook.js';
 import {stubAuctionIndex} from '../helpers/indexStub.js';
 import * as utils from '../../src/utils.js';
+import { syncOrtb2 } from '../../src/prebid.js';
 
 describe('video.js', function () {
   let sandbox;
@@ -274,4 +275,207 @@ describe('video.js', function () {
       expect(valid).to.equal(false);
     });
   })
+
+  describe('syncOrtb2', () => {
+    if (!FEATURES.VIDEO) {
+      return;
+    }
+
+    let logWarnSpy;
+
+    beforeEach(function () {
+      logWarnSpy = sinon.spy(utils, 'logWarn');
+    });
+
+    afterEach(function () {
+      utils.logWarn.restore();
+    });
+
+    it('should properly sync fields if both present', () => {
+      const adUnit = {
+        mediaTypes: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5, // should be overwritten with value from ortb2Imp
+            w: 100,
+            h: 200,
+            foo: 'omitted_value' // should be omitted during copying - not part of video obj spec
+          }
+        },
+        ortb2Imp: {
+          video: {
+            minbitrate: 10,
+            maxbitrate: 50,
+            delivery: [1, 2, 3, 4],
+            linearity: 10,
+            bar: 'omitted_value' // should be omitted during copying - not part of video obj spec
+          }
+        }
+      };
+
+      const expected = {
+        mediaTypes: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            w: 100,
+            h: 200,
+            minbitrate: 10,
+            maxbitrate: 50,
+            delivery: [1, 2, 3, 4],
+            linearity: 10,
+            foo: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            w: 100,
+            h: 200,
+            minbitrate: 10,
+            maxbitrate: 50,
+            delivery: [1, 2, 3, 4],
+            linearity: 10,
+            bar: 'omitted_value'
+          }
+        }
+      };
+
+      syncOrtb2(adUnit, 'video');
+      expect(adUnit).to.deep.eql(expected);
+
+      assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting linearity');
+    });
+
+    it('should omit sync if video mediaType not present on adUnit', () => {
+      const adUnit = {
+        mediaTypes: {
+          native: {
+            fieldToOmit: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          native: {
+            fieldToOmit2: 'omitted_value'
+          }
+        }
+      };
+
+      syncOrtb2(adUnit, 'video');
+
+      expect(adUnit.mediaTypes.video).to.be.undefined;
+      expect(adUnit.ortb2Imp.video).to.be.undefined;
+    });
+
+    it('should properly sync if mediaTypes is not present on any of side', () => {
+      const adUnit = {
+        mediaTypes: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5,
+            w: 100,
+            h: 200,
+            foo: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          // lack of video field
+        }
+      };
+
+      const expected1 = {
+        mediaTypes: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5,
+            w: 100,
+            h: 200,
+            foo: 'omitted_value'
+          }
+        },
+        ortb2Imp: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5,
+            w: 100,
+            h: 200,
+          }
+        }
+      };
+
+      syncOrtb2(adUnit, 'video');
+      expect(adUnit).to.deep.eql(expected1);
+
+      const adUnit2 = {
+        mediaTypes: {
+          // lack of video field
+        },
+        ortb2Imp: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5,
+            w: 100,
+            h: 200,
+            foo: 'omitted_value'
+          }
+        }
+      };
+
+      const expected2 = {
+        mediaTypes: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5,
+            w: 100,
+            h: 200,
+          }
+        },
+        ortb2Imp: {
+          video: {
+            minduration: 500,
+            maxduration: 1000,
+            protocols: [1, 2, 3],
+            linearity: 5,
+            w: 100,
+            h: 200,
+            foo: 'omitted_value',
+          }
+        }
+      };
+
+      syncOrtb2(adUnit2, 'video');
+      expect(adUnit2).to.deep.eql(expected2);
+    });
+
+    it('should not create empty video object on ortb2Imp if there was nothing to copy', () => {
+      const adUnit2 = {
+        mediaTypes: {
+          video: {
+            noOrtbVideoField1: 'value',
+            noOrtbVideoField2: 'value'
+          }
+        },
+        ortb2Imp: {
+          // lack of video field
+        }
+      };
+      syncOrtb2(adUnit2, 'video');
+      expect(adUnit2.ortb2Imp.video).to.be.undefined
+    });
+  });
 });

From e364847ff66034a230dbbad89d65555fcefb3007 Mon Sep 17 00:00:00 2001
From: Chris Huie 
Date: Mon, 9 Dec 2024 07:40:33 -0700
Subject: [PATCH 0742/1097] Fix deep access (#12558)

---
 src/prebid.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/prebid.js b/src/prebid.js
index 6da2504dca1..e745445295e 100644
--- a/src/prebid.js
+++ b/src/prebid.js
@@ -2,6 +2,7 @@
 
 import {getGlobal} from './prebidGlobal.js';
 import {
+  deepAccess,
   deepClone,
   deepSetValue,
   flatten,

From 7aa0638c8a8a73fea956c1da403e12003e9ca0d0 Mon Sep 17 00:00:00 2001
From: Hiroaki Kubota 
Date: Tue, 10 Dec 2024 02:38:47 +0900
Subject: [PATCH 0743/1097] craftBidAdapter: Fix netRevenue (#12536)

---
 modules/craftBidAdapter.js                | 2 +-
 test/spec/modules/craftBidAdapter_spec.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js
index 3161534a441..3e24a68b946 100644
--- a/modules/craftBidAdapter.js
+++ b/modules/craftBidAdapter.js
@@ -120,7 +120,7 @@ function newBid(serverBid, rtbBid, bidderRequest) {
     ad: rtbBid.rtb.banner.content,
     ttl: TTL,
     creativeId: rtbBid.creative_id,
-    netRevenue: false, // ???
+    netRevenue: true,
     dealId: rtbBid.deal_id,
     meta: null,
     _adUnitCode: bidRequest.adUnitCode,
diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js
index aeb17f37161..d74477c5b8b 100644
--- a/test/spec/modules/craftBidAdapter_spec.js
+++ b/test/spec/modules/craftBidAdapter_spec.js
@@ -158,7 +158,7 @@ describe('craftAdapter', function () {
         height: 250,
         mediaType: 'banner',
         meta: null,
-        netRevenue: false,
+        netRevenue: true,
         requestId: '0396fae4eb5f47',
         ttl: 360,
         width: 300,

From df356f50d7ad8a89d55b97e867f2300ec7a552c2 Mon Sep 17 00:00:00 2001
From: Kotaro Shikata 
Date: Tue, 10 Dec 2024 02:59:17 +0900
Subject: [PATCH 0744/1097] Unicorn Bid Adapter : fix net revenue (#12509)

* fix net revenue

* fix spec
---
 modules/unicornBidAdapter.js                | 2 +-
 test/spec/modules/unicornBidAdapter_spec.js | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js
index 43eb943f6d5..ac81e124b66 100644
--- a/modules/unicornBidAdapter.js
+++ b/modules/unicornBidAdapter.js
@@ -135,7 +135,7 @@ const interpretResponse = (serverResponse, request) => {
           ad: b.adm,
           ttl: 1000,
           creativeId: b.crid,
-          netRevenue: false,
+          netRevenue: true,
           currency: res.cur
         }
 
diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js
index bd9175dac1e..ffde4451bdb 100644
--- a/test/spec/modules/unicornBidAdapter_spec.js
+++ b/test/spec/modules/unicornBidAdapter_spec.js
@@ -462,7 +462,7 @@ const interpretedBids = [
     ad: '
test
', ttl: 1000, creativeId: 'ABCDE', - netRevenue: false, + netRevenue: true, currency: 'JPY' }, { requestId: '31e2b28ced2475', @@ -472,7 +472,7 @@ const interpretedBids = [ ad: '
test
', ttl: 1000, creativeId: 'abcde', - netRevenue: false, + netRevenue: true, currency: 'JPY' }, { requestId: '40a333e047a9bd', @@ -482,7 +482,7 @@ const interpretedBids = [ ad: '
test
', ttl: 1000, creativeId: 'XYZXYZ', - netRevenue: false, + netRevenue: true, currency: 'JPY' } ]; From 39e6418bb05501dbe1180bda44865c6b0ba938f2 Mon Sep 17 00:00:00 2001 From: CompassSSP <95415988+CompassSSP@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:23:07 +0200 Subject: [PATCH 0745/1097] Compass Bid Adapter : add gvlid (#12561) * add Compass Adapter * fix * fix * fix * add endpointId * fix * add gpp info --- modules/compassBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index f5d98312a63..1a4cc3faeaf 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -3,11 +3,13 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'compass'; +const GVLID = 883; const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; const SYNC_URL = 'https://sa-cs.deliverimp.com'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(), From 84a227874cab9d281b50eae540f574f141a2e2ac Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Wed, 11 Dec 2024 14:46:10 +0300 Subject: [PATCH 0746/1097] AdMatic Bid Adapter: adt sync url updated (#12565) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 2a3ec5499e6..0287ff3de33 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -137,7 +137,7 @@ export const spec = { SYNC_URL = 'https://static.cdn.admatic.de/admaticde/sync.html'; break; case 'adt': - SYNC_URL = 'https://static.cdn.adtarget.org/adt/sync.html'; + SYNC_URL = 'https://static.cdn.adtarget.biz/adt/sync.html'; break; default: SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; From 0b1c521f8adc502dd772f730aed81cf50c0624f0 Mon Sep 17 00:00:00 2001 From: anand-nexverse Date: Wed, 11 Dec 2024 19:51:09 +0530 Subject: [PATCH 0747/1097] Nexverse Bid Adapter : initial relese (#12297) * Add nexverseBidAdapter implementation and tests * Updated Nexverse adapter to support OpenRTB 2.5, handle 204 responses, and added device and connection type detection * Resolved conflicts and merged master into nexverse-bid-adapter * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * * Removed bidfloor from Adunit * Fixed issues of 1.Category 2.Domain 3.Attributes * Added Nexverse Demo html for testing with sample bid * Handled Height and width issue * * Added test cases for bid param checks * fixed bidFloor issue * * Added testcases for utils functions ' * * removed duplicate function and used from available library * * removed unwated logging * * added logger in try catch * fixed linter error * * fixed linter issue * fixed the getOsVersion missing function issue * fixed the getOsVersion missing function issue * removed hardcoded cpm value * Added comment for is debug param --------- Co-authored-by: yogeshverse --- integrationExamples/gpt/nexverse.html | 126 ++++++++++ libraries/nexverseUtils/index.js | 130 ++++++++++ modules/nexverseBidAdapter.js | 247 +++++++++++++++++++ modules/nexverseBidAdapter.md | 41 +++ test/spec/modules/nexverseBidAdapter_spec.js | 203 +++++++++++++++ 5 files changed, 747 insertions(+) create mode 100644 integrationExamples/gpt/nexverse.html create mode 100644 libraries/nexverseUtils/index.js create mode 100644 modules/nexverseBidAdapter.js create mode 100644 modules/nexverseBidAdapter.md create mode 100644 test/spec/modules/nexverseBidAdapter_spec.js diff --git a/integrationExamples/gpt/nexverse.html b/integrationExamples/gpt/nexverse.html new file mode 100644 index 00000000000..498cf2bd2f3 --- /dev/null +++ b/integrationExamples/gpt/nexverse.html @@ -0,0 +1,126 @@ + + + + + + NexVerse Prebid.Js Demo + + + + + + + + + + +

Nexverse Prebid.js Test

+
Div-1
+
+ +
+ +
Div-2
+
+ +
+ + + \ No newline at end of file diff --git a/libraries/nexverseUtils/index.js b/libraries/nexverseUtils/index.js new file mode 100644 index 00000000000..c9e286c221d --- /dev/null +++ b/libraries/nexverseUtils/index.js @@ -0,0 +1,130 @@ +import { logError, logInfo, logWarn, generateUUID } from '../../src/utils.js'; + +const LOG_WARN_PREFIX = '[Nexverse warn]: '; +const LOG_ERROR_PREFIX = '[Nexverse error]: '; +const LOG_INFO_PREFIX = '[Nexverse info]: '; +const NEXVERSE_USER_COOKIE_KEY = 'user_nexverse'; + +/** + * Determines the device model (if possible). + * @returns {string} The device model or a fallback message if not identifiable. + */ +export function getDeviceModel() { + const ua = navigator.userAgent; + if (/iPhone/i.test(ua)) { + return 'iPhone'; + } else if (/iPad/i.test(ua)) { + return 'iPad'; + } else if (/Android/i.test(ua)) { + const match = ua.match(/Android.*;\s([a-zA-Z0-9\s]+)\sBuild/); + return match ? match[1].trim() : 'Unknown Android Device'; + } else if (/Windows Phone/i.test(ua)) { + return 'Windows Phone'; + } else if (/Macintosh/i.test(ua)) { + return 'Mac'; + } else if (/Linux/i.test(ua)) { + return 'Linux'; + } else if (/Windows/i.test(ua)) { + return 'Windows PC'; + } + return ''; +} + +/** + * Prepapre the endpoint URL based on passed bid request. + * @param {string} bidderEndPoint - Bidder End Point. + * @param {object} bid - Bid details. + * @returns {string} The Endpoint URL with required parameters. + */ +export function buildEndpointUrl(bidderEndPoint, bid) { + const { uid, pubId, pubEpid } = bid.params; + const isDebug = bid.isDebug; + let endPoint = `${bidderEndPoint}?uid=${encodeURIComponent(uid)}&pub_id=${encodeURIComponent(pubId)}&pub_epid=${encodeURIComponent(pubEpid)}`; + if (isDebug) { + endPoint = `${endPoint}&test=1`; + } + return endPoint; +} +/** + * Validates the bid request to ensure all required parameters are present. + * @param {Object} bid - The bid request object. + * @returns {boolean} True if the bid request is valid, false otherwise. + */ +export function isBidRequestValid(bid) { + const isValid = !!( + bid.params && + bid.params.uid && bid.params.uid.trim() && + bid.params.pubId && bid.params.pubId.trim() && + bid.params.pubEpid && bid.params.pubEpid.trim() + ); + if (!isValid) { + logError(`${LOG_ERROR_PREFIX} Missing required bid parameters.`); + } + + return isValid; +} + +/** + * Parses the native response from the server into Prebid's native format. + * + * @param {string} adm - The adm field from the bid response (JSON string). + * @returns {Object} The parsed native response object. + */ +export function parseNativeResponse(adm) { + try { + const admObj = JSON.parse(adm); + return admObj.native; + } catch (e) { + printLog('error', `Error parsing native response: `, e) + logError(`${LOG_ERROR_PREFIX} Error parsing native response: `, e); + return {}; + } +} + +/** + * Parses the native response from the server into Prebid's native format. + * @param {type} type - Type of log. default is info + * @param {args} args - Log data. + */ +export function printLog(type, ...args) { + // Determine the prefix based on the log type + const prefixes = { + error: LOG_ERROR_PREFIX, + warning: LOG_WARN_PREFIX, // Assuming warning uses the same prefix as error + info: LOG_INFO_PREFIX + }; + + // Construct the log message by joining all arguments into a single string + const logMessage = args + .map(arg => (arg instanceof Error ? `${arg.name}: ${arg.message}` : arg)) + .join(' '); // Join all arguments into a single string with a space separator + // Add prefix and punctuation (for info type) + const formattedMessage = `${prefixes[type] || LOG_INFO_PREFIX} ${logMessage}${type === 'info' ? '.' : ''}`; + // Map the log type to its corresponding log function + const logFunctions = { + error: logError, + warning: logWarn, + info: logInfo + }; + + // Call the appropriate log function (defaulting to logInfo) + (logFunctions[type] || logInfo)(formattedMessage); +} +/** + * Get or Create Uid for First Party Cookie + */ +export const getUid = (storage) => { + let nexverseUid = storage.getCookie(NEXVERSE_USER_COOKIE_KEY); + if (!nexverseUid) { + nexverseUid = generateUUID(); + } + try { + const expirationInMs = 60 * 60 * 24 * 1000; // 1 day in milliseconds + const expirationTime = new Date(Date.now() + expirationInMs); // Set expiration time + // Set the cookie with the expiration date + storage.setCookie(NEXVERSE_USER_COOKIE_KEY, nexverseUid, expirationTime.toUTCString()); + } catch (e) { + printLog('error', `Failed to set UID cookie: ${e.message}`); + } + return nexverseUid; +}; diff --git a/modules/nexverseBidAdapter.js b/modules/nexverseBidAdapter.js new file mode 100644 index 00000000000..5ff6aa10bc5 --- /dev/null +++ b/modules/nexverseBidAdapter.js @@ -0,0 +1,247 @@ +/* eslint-disable camelcase */ + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { isArray } from '../src/utils.js'; +import {getConnectionType} from '../libraries/connectionInfo/connectionUtils.js' +import { getDeviceType, getOS } from '../libraries/userAgentUtils/index.js'; +import { getDeviceModel, buildEndpointUrl, isBidRequestValid, parseNativeResponse, printLog, getUid } from '../libraries/nexverseUtils/index.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; +import { getOsVersion } from '../libraries/advangUtils/index.js'; + +const BIDDER_CODE = 'nexverse'; +const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai/'; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; +const DEFAULT_CURRENCY = 'USD'; +const BID_TTL = 300; +const DEFAULT_LANG = 'en'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: BIDDER_CODE}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid, + /** + * Builds the OpenRTB server request from the list of valid bid requests. + * + * @param {Array} validBidRequests - Array of valid bid requests. + * @param {Object} bidderRequest - The bidder request object containing additional data. + * @returns {Array} Array of server requests to be sent to the endpoint. + */ + buildRequests(validBidRequests, bidderRequest) { + const requests = validBidRequests.map((bid) => { + // Build the endpoint URL with query parameters + const endpointUrl = buildEndpointUrl(BIDDER_ENDPOINT, bid); + + // Build the OpenRTB payload + const payload = buildOpenRtbRequest(bid, bidderRequest); + + if (!payload) { + printLog('error', 'Payload could not be built.'); + return null; // Skip this bid + } + + // Return the server request + return { + method: 'POST', + url: endpointUrl, + data: JSON.stringify(payload), + bidRequest: bid, + }; + }); + + return requests.filter((request) => request !== null); // Remove null entries + }, + + /** + * Interprets the server's response and extracts bid information. + * + * @param {Object} serverResponse - The response from the server. + * @param {Object} request - The original server request. + * @returns {Array} Array of bids to be passed to the auction. + */ + interpretResponse(serverResponse, request) { + if (serverResponse && serverResponse.status === 204) { + printLog('info', 'No ad available (204 response).'); + return []; + } + + const bidResponses = []; + const response = serverResponse.body; + + if (!response || !response.seatbid || !isArray(response.seatbid)) { + printLog('warning', 'No valid bids in the response.'); + return bidResponses; + } + + response.seatbid.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + const bidResponse = { + requestId: bid.impid, + cpm: bid.price, + currency: response.cur || DEFAULT_CURRENCY, + width: bid.width || 0, + height: bid.height || 0, + creativeId: bid.crid || bid.id, + ttl: BID_TTL, + netRevenue: true, + meta: {}, + }; + // Determine media type and assign the ad content + if (bid.ext && bid.ext.mediaType) { + bidResponse.mediaType = bid.ext.mediaType; + } else if (bid.adm && bid.adm.indexOf(' ({ w: size[0], h: size[1] })), // List of size objects + w: bid.sizes[0][0], + h: bid.sizes[0][1], + }, + secure: window.location.protocol === 'https:' ? 1 : 0, // Indicates whether the request is secure (HTTPS) + }); + } + if (bid.mediaTypes.video) { + imp.push({ + id: bid.bidId, + video: { + w: bid.sizes[0][0], + h: bid.sizes[0][1], + mimes: bid.mediaTypes.video.mimes || ['video/mp4'], // Default to video/mp4 if not specified + protocols: bid.mediaTypes.video.protocols || [2, 3, 5, 6], // RTB video ad serving protocols + maxduration: bid.mediaTypes.video.maxduration || 30, + linearity: bid.mediaTypes.video.linearity || 1, + playbackmethod: bid.mediaTypes.video.playbackmethod || [2], + }, + secure: window.location.protocol === 'https:' ? 1 : 0, // Indicates whether the request is secure (HTTPS) + }); + } + if (bid.mediaTypes.native) { + imp.push({ + id: bid.bidId, + native: { + request: JSON.stringify(bid.mediaTypes.native), // Convert native request to JSON string + }, + secure: window.location.protocol === 'https:' ? 1 : 0, // Indicates whether the request is secure (HTTPS) + }); + } + + // Construct the OpenRTB request object + const openRtbRequest = { + id: bidderRequest.auctionId, + imp: imp, + site: { + page: bidderRequest.refererInfo.page, + domain: bidderRequest.refererInfo.domain, + ref: bidderRequest.refererInfo.ref || '', // Referrer URL + }, + device: { + ua: navigator.userAgent, + devicetype: getDeviceType(), // 1 = Mobile/Tablet, 2 = Desktop + os: getOS(), + osv: getOsVersion(), + make: navigator.vendor || '', + model: getDeviceModel(), + connectiontype: getConnectionType(), // Include connection type + geo: { + lat: bid.params.geoLat || 0, + lon: bid.params.geoLon || 0, + }, + language: navigator.language || DEFAULT_LANG, + dnt: navigator.doNotTrack === '1' ? 1 : 0, // Do Not Track flag + }, + user: { + id: getUid(storage), + buyeruid: bidderRequest.userId || '', // User ID or Buyer ID + ext: { + consent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : null, // GDPR consent string + }, + }, + regs: { + ext: { + gdpr: bidderRequest.gdprConsent ? (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) : 0, + }, + }, + ext: { + prebid: { + auctiontimestamp: bidderRequest.auctionStart, + }, + }, + }; + + // Add app object if the request comes from a mobile app + if (bidderRequest.app) { + openRtbRequest.app = { + id: bidderRequest.app.id, + name: bidderRequest.app.name, + bundle: bidderRequest.app.bundle, + domain: bidderRequest.app.domain, + storeurl: bidderRequest.app.storeUrl, + cat: bidderRequest.app.cat || [], + }; + } + // Add additional fields related to GDPR, US Privacy, CCPA + if (bidderRequest.uspConsent) { + openRtbRequest.regs.ext.us_privacy = bidderRequest.uspConsent; + } + return openRtbRequest; +} + +registerBidder(spec); diff --git a/modules/nexverseBidAdapter.md b/modules/nexverseBidAdapter.md new file mode 100644 index 00000000000..1de5dda01e9 --- /dev/null +++ b/modules/nexverseBidAdapter.md @@ -0,0 +1,41 @@ +# Nexverse Bid Adapter + +## Overview +The Nexverse Bid Adapter enables publishers to connect with the Nexverse Real-Time Bidding (RTB) platform. This adapter supports multiple ad formats, including Banner, Video, and Native ads. By integrating this adapter, publishers can send bid requests to Nexverse’s marketplace and receive high-quality ads in response. + +- **Module name**: Nexverse +- **Module type**: Bidder Adapter +- **Supported Media Types**: Banner, Video, Native +- **Maintainer**: anand.kumar@nexverse.ai + +## Bidder Parameters +To correctly configure the Nexverse Bid Adapter, the following parameters are required: + +| Param Name | Scope | Type | Description | +|--------------|----------|--------|-----------------------------------------------------| +| `uid` | required | string | Unique User ID assigned by Nexverse for the publisher | +| `pubId` | required | string | The unique ID for the publisher | +| `pubEpid` | required | string | The unique endpoint ID for the publisher | + +### Example Configuration +The following is an example configuration for a Nexverse bid request using Prebid.js: + +```javascript +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'nexverse', + params: { + uid: '12345', + pubId: '54321', + pubEpid: 'abcde' + }, + isDebug: false // Optional, i.e True for debug mode + }] +}]; +``` diff --git a/test/spec/modules/nexverseBidAdapter_spec.js b/test/spec/modules/nexverseBidAdapter_spec.js new file mode 100644 index 00000000000..fb648a154f7 --- /dev/null +++ b/test/spec/modules/nexverseBidAdapter_spec.js @@ -0,0 +1,203 @@ +import { expect } from 'chai'; +import { spec } from 'modules/nexverseBidAdapter.js'; +import { getDeviceModel, buildEndpointUrl, parseNativeResponse } from '../../../libraries/nexverseUtils/index.js'; +import { getOsVersion } from '../../../libraries/advangUtils/index.js'; + +const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai/'; + +describe('nexverseBidAdapterTests', () => { + describe('isBidRequestValid', function () { + let sbid = { + 'adUnitCode': 'div', + 'bidder': 'nexverse', + 'params': { + 'uid': '77d4a2eb3d209ce6c7691dc79fcab358', + 'pubId': '24051' + }, + }; + + it('should not accept bid without required params', function () { + let isValid = spec.isBidRequestValid(sbid); + expect(isValid).to.equal(false); + }); + + it('should return false when params are not passed', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.params = {uid: '', pubId: '', pubEpid: ''}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.adUnitCode = ''; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {uid: '77d4a2eb3d209ce6c7691dc79fcab358', pubId: '24051'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return true when valid params are passed as nums', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {uid: '77d4a2eb3d209ce6c7691dc79fcab358', pubId: '24051', pubEpid: '34561'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('getDeviceModel', () => { + it('should return "iPhone" for iPhone userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', configurable: true }); + expect(getDeviceModel()).to.equal('iPhone'); + }); + + it('should return "iPad" for iPad userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)', configurable: true }); + expect(getDeviceModel()).to.equal('iPad'); + }); + + it('should return the Android device name for Android userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QQ2A.200305.003) AppleWebKit/537.36', configurable: true }); + expect(getDeviceModel()).to.equal('Pixel 3'); + }); + + it('should return "Unknown Android Device" if device name is missing in Android userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10;) AppleWebKit/537.36', configurable: true }); + expect(getDeviceModel()).to.equal('Unknown Android Device'); + }); + + it('should return "Mac" for Mac userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)', configurable: true }); + expect(getDeviceModel()).to.equal('Mac'); + }); + + it('should return "Linux" for Linux userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (X11; Linux x86_64)', configurable: true }); + expect(getDeviceModel()).to.equal('Linux'); + }); + + it('should return "Windows PC" for Windows userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', configurable: true }); + expect(getDeviceModel()).to.equal('Windows PC'); + }); + + it('should return "Unknown Device" for an unrecognized userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Unknown OS)', configurable: true }); + expect(getDeviceModel()).to.equal(''); + }); + }); + + describe('buildEndpointUrl', () => { + it('should construct the URL with uid, pubId, and pubEpid', function () { + const bid = { + params: { + uid: '12345', + pubId: '67890', + pubEpid: 'abcdef' + }, + isDebug: false + }; + const expectedUrl = `${BIDDER_ENDPOINT}?uid=12345&pub_id=67890&pub_epid=abcdef`; + expect(buildEndpointUrl(BIDDER_ENDPOINT, bid)).to.equal(expectedUrl); + }); + }); + + describe('buildEndpointUrl', () => { + it('should construct the test URL with uid, pubId, and pubEpid', function () { + const bid = { + params: { + uid: '12345', + pubId: '67890', + pubEpid: 'abcdef' + }, + isDebug: true + }; + const expectedUrl = `${BIDDER_ENDPOINT}?uid=12345&pub_id=67890&pub_epid=abcdef&test=1`; + expect(buildEndpointUrl(BIDDER_ENDPOINT, bid)).to.equal(expectedUrl); + }); + }); + + describe('parseNativeResponse', () => { + it('should parse and return the empty json object from a invalid JSON string', function () { + const adm = 'Nexverse test ad'; + const result = parseNativeResponse(adm); + expect(result).to.deep.equal({}); + }); + it('should parse and return the native object from a valid JSON string', function () { + const adm = '{"native": "sample native ad"}'; // JSON string + const result = parseNativeResponse(adm); + expect(result).to.deep.equal('sample native ad'); + }); + }); + + describe('getOsVersion', () => { + it('should detect Android OS', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QQ2A.200305.003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36', configurable: true }); + expect(getOsVersion()).to.equal('Android'); + }); + it('should detect iOS', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1', + configurable: true, + }); + expect(getOsVersion()).to.equal('iOS'); + }); + it('should detect Mac OS X', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Mac OS X'); + }); + it('should detect Windows 10', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Windows 10'); + }); + it('should detect Linux', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Linux'); + }); + it('should detect Windows 7', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Windows 7'); + }); + it('should detect Search Bot', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', + configurable: true, + }); + expect(getOsVersion()).to.equal('Search Bot'); + }); + it('should return unknown for an unrecognized user agent', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Unknown OS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('unknown'); + }); + }); +}); From 528519ddc5d1d9bffa56e133c271f480c817df8e Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:23:31 +0100 Subject: [PATCH 0748/1097] Ensure the correct winning bid is recorded if an adapter is returning multiple bids. (#12568) --- modules/livewrappedAnalyticsAdapter.js | 12 +++- .../livewrappedAnalyticsAdapter_spec.js | 61 ++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 413147b4fa4..ec8fea42bac 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -77,6 +77,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE logInfo('LIVEWRAPPED_BID_RESPONSE:', args); let bidResponse = cache.auctions[args.auctionId].bids[args.requestId]; + if (bidResponse.cpm > args.cpm) break; // For now we only store the highest bid bidResponse.isBid = args.getStatusCode() === STATUS.GOOD; bidResponse.width = args.width; bidResponse.height = args.height; @@ -84,7 +85,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE bidResponse.originalCpm = prebidGlobal.convertCurrency(args.originalCpm, args.originalCurrency, args.currency); bidResponse.ttr = args.timeToRespond; bidResponse.readyToSend = 1; - bidResponse.mediaType = args.mediaType == 'native' ? 2 : (args.mediaType == 'video' ? 4 : 1); + bidResponse.mediaType = getMediaTypeEnum(args.mediaType); bidResponse.floorData = args.floorData; bidResponse.meta = args.meta; @@ -115,6 +116,11 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE logInfo('LIVEWRAPPED_BID_WON:', args); let wonBid = cache.auctions[args.auctionId].bids[args.requestId]; wonBid.won = true; + wonBid.width = args.width; + wonBid.height = args.height; + wonBid.cpm = args.cpm; + wonBid.originalCpm = prebidGlobal.convertCurrency(args.originalCpm, args.originalCurrency, args.currency); + wonBid.mediaType = getMediaTypeEnum(args.mediaType); wonBid.floorData = args.floorData; wonBid.rUp = args.rUp; wonBid.meta = args.meta; @@ -185,6 +191,10 @@ livewrappedAnalyticsAdapter.sendEvents = function() { ajax(initOptions.endpoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); }; +function getMediaTypeEnum(mediaType) { + return mediaType == 'native' ? 2 : (mediaType == 'video' ? 4 : 1); +} + function getSentRequests() { var sentRequests = []; var gdpr = []; diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index 1e5f089ca53..4b7f7bc2bac 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -59,6 +59,24 @@ const BID2 = Object.assign({}, BID1, { dealId: undefined }); +const BID2_2 = Object.assign({}, BID2, { + width: 320, + height: 320, + cpm: 10.0, + originalCpm: 20.0, + currency: 'USD', + originalCurrency: 'FOO', + timeToRespond: 300, + bidId: '3ecff0db240758', + requestId: '3ecff0db240757', + adId: '3ecff0db240758', + mediaType: 'video', + meta: { + data: 'value2_2' + }, + dealId: 'deal2_2' +}); + const BID3 = { bidId: '4ecff0db240757', requestId: '4ecff0db240757', @@ -99,7 +117,8 @@ const MOCK = { }, BID_RESPONSE: [ BID1, - BID2 + BID2, + BID2_2 ], AUCTION_END: { }, @@ -281,6 +300,7 @@ function performStandardAuction() { events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[2]); events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.SET_TARGETING); @@ -643,5 +663,44 @@ describe('Livewrapped analytics adapter', function () { expect(message.ext).to.not.equal(null); expect(message.ext.testparam).to.equal(123); }); + + it('should forward the correct winning bid from a multi-bid response', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[2]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, Object.assign({}, BID2_2, { + 'status': 'rendered', + 'requestId': '3ecff0db240757' + })); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0]).to.deep.equal({ + timeStamp: 1519149562216, + adUnit: 'box_d_1', + adUnitId: 'adunitid', + bidder: 'livewrapped', + width: 320, + height: 320, + cpm: 10.0, + orgCpm: 200, + mediaType: 4, + dealId: 'deal2_2', + gdpr: 0, + auctionId: 0, + meta: { + data: 'value2_2' + } + }); + }); }); }); From b4d6dd415ba216315b7ccf78c8fd713575a6f2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20Chr=C3=A9tien?= Date: Wed, 11 Dec 2024 15:30:13 +0100 Subject: [PATCH 0749/1097] Adagio rtd provider: fix traffic going outside the ab test (#12563) * AdagioRtdProvider: isolate AbTest logic from localstorage adagio.session object * AdagioRtdProvider: only pass AbTest data if for current session * AdagioRtdProvider: change session lastActivityTime to session to simplify logic of session users --- modules/adagioRtdProvider.js | 81 +++++++++++++++------ test/spec/modules/adagioRtdProvider_spec.js | 74 +------------------ 2 files changed, 64 insertions(+), 91 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index 2260ae8d31a..ee81e1337dd 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -37,7 +37,7 @@ const SUBMODULE_NAME = 'adagio'; const ADAGIO_BIDDER_CODE = 'adagio'; const GVLID = 617; const SCRIPT_URL = 'https://script.4dex.io/a/latest/adagio.js'; -const SESS_DURATION = 30 * 60 * 1000; +const LATEST_ABTEST_VERSION = 2; export const PLACEMENT_SOURCES = { ORTB: 'ortb', // implicit default, not used atm. ADUNITCODE: 'code', @@ -66,42 +66,47 @@ const _SESSION = (function() { return { init: () => { // helper function to determine if the session is new. - const isNewSession = (lastActivity) => { - const now = Date.now(); - return (!isNumber(lastActivity) || (now - lastActivity) > SESS_DURATION); + const isNewSession = (expiry) => { + return (!isNumber(expiry) || Date.now() > expiry); }; storage.getDataFromLocalStorage('adagio', (storageValue) => { // session can be an empty object - const { rnd, new: isNew = false, vwSmplg, vwSmplgNxt, lastActivityTime, id, testName, testVersion, initiator, pages } = _internal.getSessionFromLocalStorage(storageValue); + const { rnd, vwSmplg, vwSmplgNxt, expiry, lastActivityTime, id, pages, testName: legacyTestName, testVersion: legacyTestVersion } = _internal.getSessionFromLocalStorage(storageValue); - // isNew can be `true` if the session has been initialized by the A/B test snippet (external) - const isNewSess = (initiator === 'snippet') ? isNew : isNewSession(lastActivityTime); + const isNewSess = isNewSession(expiry); + + // if lastActivityTime is defined it means that the website is using the original version of the snippet + const v = !lastActivityTime ? LATEST_ABTEST_VERSION : undefined; data.session = { + v, rnd, pages: pages || 1, new: isNewSess, // legacy: `new` was used but the choosen name is not good. // Don't use values if they are not defined. ...(vwSmplg !== undefined && { vwSmplg }), ...(vwSmplgNxt !== undefined && { vwSmplgNxt }), - ...(lastActivityTime !== undefined && { lastActivityTime }), + ...(expiry !== undefined && { expiry }), + ...(lastActivityTime !== undefined && { lastActivityTime }), // legacy: used by older version of the snippet ...(id !== undefined && { id }), - ...(testName !== undefined && { testName }), - ...(testVersion !== undefined && { testVersion }), - ...(initiator !== undefined && { initiator }), }; - // `initiator` is a pseudo flag used to know if the session has been initialized by the A/B test snippet (external). - // If the AB Test snippet has not been used, then `initiator` value is `adgjs` or `undefined`. - // The check on `testName` is used to ensure that the A/B test values are removed. - if (initiator !== 'snippet' && (isNewSess || testName)) { + if (isNewSess) { data.session.new = true; data.session.id = generateUUID(); data.session.rnd = Math.random(); - // Ensure that the A/B test values are removed. - delete data.session.testName; - delete data.session.testVersion; + } + + const { testName, testVersion, expiry: abTestExpiry, sessionId } = _internal.getAbTestFromLocalStorage(storageValue); + if (v === LATEST_ABTEST_VERSION) { + if (abTestExpiry && abTestExpiry > Date.now() && (!sessionId || sessionId === data.session.id)) { // if AbTest didn't set a session id, it's probably because it's a new one and it didn't retrieve it yet, assume it's okay to get test Name and Version. + data.session.testName = testName; + data.session.testVersion = testVersion; + } + } else { + data.session.testName = legacyTestName; + data.session.testVersion = legacyTestVersion; } _internal.getAdagioNs().queue.push({ @@ -196,13 +201,35 @@ export const _internal = { rnd: Math.random() }; - const obj = JSON.parse(storageValue, function(name, value) { + const obj = this.getObjFromStorageValue(storageValue); + + return (!obj || !obj.session) ? _default : obj.session; + }, + + /** + * Returns the abTest data from the localStorage. + * + * @param {string} storageValue - The value stored in the localStorage. + * @returns {AbTest} + */ + getAbTestFromLocalStorage: function(storageValue) { + const obj = this.getObjFromStorageValue(storageValue); + + return (!obj || !obj.abTest) ? {} : obj.abTest; + }, + + /** + * Returns the parsed data from the localStorage. + * + * @param {string} storageValue - The value stored in the localStorage. + * @returns {Object} + */ + getObjFromStorageValue: function(storageValue) { + return JSON.parse(storageValue, function(name, value) { if (name.charAt(0) !== '_' || name === '') { return value; } }); - - return (!obj || !obj.session) ? _default : obj.session; } }; @@ -681,12 +708,24 @@ function registerEventsForAdServers(config) { /** * @typedef {Object} Session + * @property {string} id - uuid of the session. * @property {boolean} new - True if the session is new. * @property {number} rnd - Random number used to determine if the session is new. * @property {number} vwSmplg - View sampling rate. * @property {number} vwSmplgNxt - Next view sampling rate. + * @property {number} expiry - Timestamp after which session should be considered expired. * @property {number} lastActivityTime - Last activity time. * @property {number} pages - current number of pages seen. + * @property {string} testName - The test name defined by the publisher. Legacy only present for websites with older abTest snippet. + * @property {string} testVersion - 'clt', 'srv'. Legacy only present for websites with older abTest snippet. + */ + +/** + * @typedef {Object} AbTest + * @property {string} testName - The test name defined by the publisher. + * @property {string} testVersion - 'clt', 'srv'. + * @property {string} sessionId - uuid of the session. + * @property {number} expiry - Timestamp after which session should be considered expired. */ /** diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index 64c006fbc3f..7b9fec595b1 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -117,12 +117,13 @@ describe('Adagio Rtd Provider', function () { describe('store session data in localStorage', function () { const session = { - lastActivityTime: 1714116520700, + expiry: 1714116530700, id: 'uid-1234', rnd: 0.5697, vwSmplg: 0.1, vwSmplgNxt: 0.1, - pages: 1 + pages: 1, + v: 2 }; it('store new session data for further usage', function () { @@ -138,6 +139,7 @@ describe('Adagio Rtd Provider', function () { const expected = { session: { + v: 2, new: true, id: utils.generateUUID(), rnd: Math.random(), @@ -203,74 +205,6 @@ describe('Adagio Rtd Provider', function () { }).calledOnce).to.be.true; }); }); - - describe('store session data in localStorage when used with external AB Test snippet', function () { - const sessionWithABTest = { - lastActivityTime: 1714116520700, - id: 'uid-1234', - rnd: 0.5697, - vwSmplg: 0.1, - vwSmplgNxt: 0.1, - testName: 'adg-test', - testVersion: 'srv', - initiator: 'snippet', - pages: 1 - }; - - it('store new session data instancied by the AB Test snippet for further usage', function () { - const sessionWithNewFlag = { ...sessionWithABTest, new: true }; - const storageValue = JSON.stringify({session: sessionWithNewFlag}); - sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); - sandbox.stub(Date, 'now').returns(1714116520710); - sandbox.stub(Math, 'random').returns(0.8); - - const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') - - adagioRtdSubmodule.init(config); - - const expected = { - session: { - ...sessionWithNewFlag - } - } - - expect(spy.withArgs({ - action: 'session', - ts: Date.now(), - data: expected, - }).calledOnce).to.be.true; - }); - - it('store new session data after removing AB Test props when initiator is not the snippet', function () { - const sessionWithNewFlag = { ...sessionWithABTest, new: false, initiator: 'adgjs' }; - const storageValue = JSON.stringify({session: sessionWithNewFlag}); - sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); - sandbox.stub(Date, 'now').returns(1714116520710); - sandbox.stub(Math, 'random').returns(0.8); - sandbox.stub(utils, 'generateUUID').returns('uid-5678'); - - const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') - - adagioRtdSubmodule.init(config); - - const expected = { - session: { - ...sessionWithNewFlag, - new: true, - id: utils.generateUUID(), - rnd: Math.random(), - } - } - delete expected.session.testName; - delete expected.session.testVersion; - - expect(spy.withArgs({ - action: 'session', - ts: Date.now(), - data: expected, - }).calledOnce).to.be.true; - }); - }); }); describe('submodule `getBidRequestData`', function () { From 892f2bc4d9e7830783531ee9b45efa8d0b6a5017 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 11 Dec 2024 11:55:21 -0500 Subject: [PATCH 0750/1097] Equativ Bid Adapter: support native bid requests (#12566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * updates after pr review * SADR-6484: initial video setup for new PBJS adapter * SADR-6484: Adding logging requirement missed earlier * SADR-6484: handle ext.rewarded prop for video with new oRTBConverter * SADR-6484: test revision + not sending bid requests where video obj is empty * refactoring and u.t. * rename variable * Equativ: SADR-6615: adding unit tests for and additional logging to bid adapter to support native requests * revert changes rel. to test endpoint * revert changes rel. to test endpoint * split imp[0] into seperate requests and fix u.t. * Equativ bid adapter: adding support for native media type * Equativ bid adapter: update unit test for native-support work * Equativ bid adapter: removing console.log from unit test file * Equativ bid adapter: clarifying refinements regarding native-request processing * Equativ Bid Adapter: updating unit tests for native requests * PR feedback --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+krzysztofequativ@users.noreply.github.com> Co-authored-by: Krzysztof Sokół Co-authored-by: janzych-smart --- modules/equativBidAdapter.js | 33 ++-- test/spec/modules/equativBidAdapter_spec.js | 160 +++++++++++++++++++- 2 files changed, 180 insertions(+), 13 deletions(-) diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js index a53597a9074..646a54b6418 100644 --- a/modules/equativBidAdapter.js +++ b/modules/equativBidAdapter.js @@ -1,9 +1,10 @@ -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getBidFloor } from '../libraries/equativUtils/equativUtils.js' -import { getStorageManager } from '../src/storageManager.js'; + +import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; import { deepAccess, deepSetValue, logError, logWarn, mergeDeep } from '../src/utils.js'; /** @@ -18,14 +19,14 @@ const LOG_PREFIX = 'Equativ:'; const PID_COOKIE_NAME = 'eqt_pid'; /** - * Evaluates a bid request for validity. Returns false if the - * request contains a video media type with no properties, true - * otherwise. + * Evaluates impressions for validity. The entry evaluated is considered valid if NEITHER of these conditions are met: + * 1) it has a `video` property defined for `mediaTypes.video` which is an empty object + * 2) it has a `native` property defined for `mediaTypes.native` which is an empty object * @param {*} bidReq A bid request object to evaluate * @returns boolean */ function isValid(bidReq) { - return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}'); + return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}') && !(bidReq.mediaTypes.native && JSON.stringify(bidReq.mediaTypes.native) === '{}'); } export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); @@ -33,7 +34,7 @@ export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { code: BIDDER_CODE, gvlid: 45, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * @param bidRequests @@ -42,7 +43,7 @@ export const spec = { */ buildRequests: (bidRequests, bidderRequest) => { if (bidRequests.filter(isValid).length === 0) { - logError(`${LOG_PREFIX} No useful bid requests to process. No request will be sent.`, bidRequests); + logError(`${LOG_PREFIX} No useful bid requests to process. No requests will be sent.`, bidRequests); return undefined; } @@ -121,7 +122,6 @@ export const converter = ortbConverter({ imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest, config.getConfig('currency.adServerCurrency'), mediaType); imp.secure = 1; - imp.tagid = bidRequest.adUnitCode; if (!deepAccess(bidRequest, 'ortb2Imp.rwdd') && deepAccess(bidRequest, 'mediaTypes.video.ext.rewarded')) { @@ -151,6 +151,17 @@ export const converter = ortbConverter({ }); } + // "assets" is not included as a property to check here because the + // ortbConverter library checks for it already and will skip processing + // the request if it is missing + if (deepAccess(bid, 'mediaTypes.native')) { + ['privacy', 'plcmttype', 'eventtrackers'].forEach(prop => { + if (!bid.mediaTypes.native.ortb[prop]) { + logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request. Request will proceed, but the use of ${prop} for native requests is strongly encouraged.`, bid); + } + }); + } + const pid = storage.getCookie(PID_COOKIE_NAME); if (pid) { deepSetValue(req, 'user.buyeruid', pid); diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js index 9f767a3cd4e..c66e97e6dbc 100644 --- a/test/spec/modules/equativBidAdapter_spec.js +++ b/test/spec/modules/equativBidAdapter_spec.js @@ -1,6 +1,6 @@ -import { BANNER } from 'src/mediaTypes.js'; -import { getBidFloor } from 'libraries/equativUtils/equativUtils.js' +import { getBidFloor } from 'libraries/equativUtils/equativUtils.js'; import { converter, spec, storage } from 'modules/equativBidAdapter.js'; +import { BANNER } from 'src/mediaTypes.js'; import * as utils from '../../../src/utils.js'; describe('Equativ bid adapter tests', () => { @@ -78,6 +78,64 @@ describe('Equativ bid adapter tests', () => { } ]; + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }], + context: 1, + eventtrackers: [{ + event: 1, + methods: [1, 2] + }], + plcmttype: 1, + privacy: 1, + ver: '1.2', + }; + const DEFAULT_NATIVE_BID_REQUESTS = [ + { + adUnitCode: 'equativ_native_42', + bidId: 'equativ_native_bidid_42', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + }, + }, + nativeOrtbRequest, + bidder: 'equativ', + params: { + networkId: 777, + }, + requestId: 'equativ_native_reqid_42', + ortb2Imp: { + ext: { + tid: 'equativ_native_tid_42', + }, + }, + } + ]; + const DEFAULT_BANNER_BIDDER_REQUEST = { bidderCode: 'equativ', bids: DEFAULT_BANNER_BID_REQUESTS, @@ -88,6 +146,11 @@ describe('Equativ bid adapter tests', () => { bids: DEFAULT_VIDEO_BID_REQUESTS, }; + const DEFAULT_NATIVE_BIDDER_REQUEST = { + bidderCode: 'equativ', + bids: DEFAULT_NATIVE_BID_REQUESTS, + }; + const SAMPLE_RESPONSE = { body: { id: '12h712u7-k22g-8124-ab7a-h268s22dy271', @@ -495,6 +558,99 @@ describe('Equativ bid adapter tests', () => { expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request')); expect(request).to.be.undefined; }); + + it('should build a native request properly under normal circumstances', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const expectedResult = true; + + // ACT + const request = spec.buildRequests(DEFAULT_NATIVE_BID_REQUESTS, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('native'); + + const nativeObj = request.imp[0].native; + expect(nativeObj).to.have.property('ver').and.to.equal('1.2'); + expect(nativeObj).to.have.property('request').and.to.be.a('string'); + + const requestObj = JSON.parse(nativeObj.request); + expect(requestObj).to.have.property('assets').and.to.be.an('array'); + expect(requestObj).to.have.property('eventtrackers').and.to.be.an('array'); + expect(requestObj).to.have.property('plcmttype').and.to.equal(1); + expect(requestObj).to.have.property('privacy').and.to.equal(1); + expect(requestObj).to.have.property('ver').and.to.equal('1.2'); + } + }); + + it('should not send a native request when it has an empty body and no other impressions with any media types are defined', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const emptyNativeRequest = { + ...DEFAULT_NATIVE_BID_REQUESTS[0], + mediaTypes: { + native: {} + } + }; + const bidRequests = [ emptyNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + const request = spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request')); + expect(request).to.be.undefined; + } + }); + + it('should warn about missing "assets" property for native requests', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const missingRequiredNativeRequest = DEFAULT_NATIVE_BID_REQUESTS[0]; + + // removing just "assets" for this test + delete missingRequiredNativeRequest.nativeOrtbRequest.assets; + const bidRequests = [ missingRequiredNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // this value comes from native.js, part of the ortbConverter library + const warningMsgFromLibrary = 'mediaTypes.native is set, but no assets were specified. Native request skipped.' + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.callCount).to.equal(1) + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes(warningMsgFromLibrary)); + } + }); + + it('should warn about other missing required properties for native requests', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const missingRequiredNativeRequest = DEFAULT_NATIVE_BID_REQUESTS[0]; + + // ortbConverter library will warn about missing assets; we supply warnings for these properties here + delete missingRequiredNativeRequest.mediaTypes.native.ortb.eventtrackers; + delete missingRequiredNativeRequest.mediaTypes.native.ortb.plcmttype; + delete missingRequiredNativeRequest.mediaTypes.native.ortb.privacy; + + const bidRequests = [ missingRequiredNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.callCount).to.equal(4); // the first message, regarding missing assets, is supplied by the ortbConverter library + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('no assets were specified')); + expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"privacy" is missing')); + expect(utils.logWarn.getCall(2).args[0]).to.satisfy(arg => arg.includes('"plcmttype" is missing')); + expect(utils.logWarn.getCall(3).args[0]).to.satisfy(arg => arg.includes('"eventtrackers" is missing')); + } + }); }); describe('getBidFloor', () => { From 9ec218dc37b7d03b7de42bc828e49b1731758054 Mon Sep 17 00:00:00 2001 From: "pratik.ta" <143182729+Pratik3307@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:26:59 +0530 Subject: [PATCH 0751/1097] feat: auctionsCounter at adUnit level (#12557) --- modules/medianetBidAdapter.js | 2 +- src/adUnits.js | 18 ++++++ src/adapterManager.js | 8 +++ test/fixtures/fixtures.js | 62 ++++++++++++++++++++ test/spec/modules/medianetBidAdapter_spec.js | 36 ++++++------ test/spec/unit/adUnits_spec.js | 13 ++++ test/spec/unit/core/adapterManager_spec.js | 31 +++++++++- 7 files changed, 149 insertions(+), 21 deletions(-) diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 5adf3f743a7..bd6d684c9a0 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -210,7 +210,7 @@ function slotParams(bidRequest, bidderRequests) { transactionId: bidRequest.ortb2Imp?.ext?.tid, ext: { dfp_id: bidRequest.adUnitCode, - display_count: bidRequest.bidRequestsCount + display_count: bidRequest.auctionsCount }, all: bidRequest.params }; diff --git a/src/adUnits.js b/src/adUnits.js index 413fc6a7c28..5c07718bbea 100644 --- a/src/adUnits.js +++ b/src/adUnits.js @@ -46,6 +46,15 @@ export function incrementBidderWinsCounter(adunit, bidderCode) { return incrementAdUnitCount(adunit, 'winsCounter', bidderCode); } +/** + * Increments and returns current Adunit auctions counter + * @param {string} adunit id + * @returns {number} current adunit auctions count + */ +export function incrementAuctionsCounter(adunit) { + return incrementAdUnitCount(adunit, 'auctionsCounter'); +} + /** * Returns current Adunit counter * @param {string} adunit id @@ -74,3 +83,12 @@ export function getBidderRequestsCounter(adunit, bidder) { export function getBidderWinsCounter(adunit, bidder) { return adUnits?.[adunit]?.bidders?.[bidder]?.winsCounter || 0; } + +/** + * Returns current Adunit auctions counter + * @param {string} adunit id + * @returns {number} current adunit auctions count + */ +export function getAuctionsCounter(adunit) { + return adUnits?.[adunit]?.auctionsCounter || 0; +} diff --git a/src/adapterManager.js b/src/adapterManager.js index fb396d8d794..04bac6eb273 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -20,6 +20,7 @@ import { mergeDeep, shuffle, timestamp, + uniques, } from './utils.js'; import {decorateAdUnitsWithNativeParams, nativeAdapters} from './native.js'; import {newBidder} from './adapters/bidderFactory.js'; @@ -28,9 +29,11 @@ import {config, RANDOM} from './config.js'; import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import { + getAuctionsCounter, getBidderRequestsCounter, getBidderWinsCounter, getRequestsCounter, + incrementAuctionsCounter, incrementBidderRequestsCounter, incrementBidderWinsCounter, incrementRequestsCounter @@ -133,6 +136,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics} auctionId, src, metrics, + auctionsCount: getAuctionsCounter(adUnit.code), bidRequestsCount: getRequestsCounter(adUnit.code), bidderRequestsCount: getBidderRequestsCounter(adUnit.code, bid.bidder), bidderWinsCount: getBidderWinsCounter(adUnit.code, bid.bidder), @@ -260,6 +264,10 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a if (FEATURES.NATIVE) { decorateAdUnitsWithNativeParams(adUnits); } + adUnits + .map(adUnit => adUnit.code) + .filter(uniques) + .forEach(incrementAuctionsCounter); adUnits.forEach(au => { if (!isPlainObject(au.mediaTypes)) { diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 94513821ce1..b282d9006a6 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -822,6 +822,68 @@ export function getAdUnits() { ]; }; +export function getTwinAdUnits() { + return [ + { + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': 1234567, + 'adSlot': '1234567@728x90' + } + }, + { + 'bidder': 'medianet', + 'params': { + 'cid': '8CUWQS47C', + 'crid': '241882766' + }, + }, + ] + }, + { + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 970, + 90 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221' + } + }, + { + 'bidder': 'medianet', + 'params': { + 'cid': '8CUWQS47C', + 'crid': '241882764' + }, + }, + ] + } + ] +} + export function getBidResponsesFromAPI() { return { '/19968336/header-bid-tag-0': { diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 5f2efc7864f..009af5dbd66 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -30,7 +30,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1, }, { 'bidder': 'medianet', 'params': { @@ -57,7 +57,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_CRID = [{ @@ -87,7 +87,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -115,7 +115,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_ORTB2 = [{ 'bidder': 'medianet', @@ -144,7 +144,7 @@ let VALID_BID_REQUEST = [{ 'data': {'pbadslot': '/12345/my-gpt-tag-0'} } }, - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -173,7 +173,7 @@ let VALID_BID_REQUEST = [{ 'data': {'pbadslot': '/12345/my-gpt-tag-0'} } }, - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], // Protected Audience API Request VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP = [{ @@ -203,7 +203,7 @@ let VALID_BID_REQUEST = [{ 'ae': 1 } }, - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_USERID = [{ @@ -235,7 +235,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -263,7 +263,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_USERIDASEIDS = [{ 'bidder': 'medianet', @@ -301,7 +301,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -329,7 +329,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ @@ -359,7 +359,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -386,7 +386,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_NATIVE_BID_REQUEST = [{ 'bidder': 'medianet', @@ -414,7 +414,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1, + 'auctionsCount': 1, 'nativeParams': { 'image': { 'required': true, @@ -471,7 +471,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1, + 'auctionsCount': 1, 'nativeParams': { 'image': { 'required': true, @@ -1244,7 +1244,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_PAYLOAD_PAGE_META = (() => { @@ -1646,7 +1646,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -1673,7 +1673,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BIDDER_REQUEST_WITH_GDPR = { 'gdprConsent': { diff --git a/test/spec/unit/adUnits_spec.js b/test/spec/unit/adUnits_spec.js index 01c5bc49f3f..a1215ba3f52 100644 --- a/test/spec/unit/adUnits_spec.js +++ b/test/spec/unit/adUnits_spec.js @@ -61,4 +61,17 @@ describe('Adunit Counter', function () { adunitCounter.incrementBidderWinsCounter(adCode, BIDDER_ID_2); expect(adunitCounter.getBidderWinsCounter(adCode, BIDDER_ID_2)).to.be.equal(1); }); + it('increments and checks auctions counter of adunit 1', function () { + adunitCounter.incrementAuctionsCounter(ADUNIT_ID_1); + expect(adunitCounter.getAuctionsCounter(ADUNIT_ID_1)).to.be.equal(1); + }); + it('increments and checks auctions counter of adunit 2', function () { + adunitCounter.incrementAuctionsCounter(ADUNIT_ID_2); + expect(adunitCounter.getAuctionsCounter(ADUNIT_ID_2)).to.be.equal(1); + }); + it('increments and checks auctions counter if adUnitCode has a dots in it', function () { + const adUnitCode = 'adunit.1' + adunitCounter.incrementAuctionsCounter(adUnitCode); + expect(adunitCounter.getAuctionsCounter(adUnitCode)).to.be.equal(1); + }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 852c84263e9..1aacc518190 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -10,7 +10,8 @@ import { getAdUnits, getServerTestingConfig, getServerTestingsAds, - getBidRequests + getBidRequests, + getTwinAdUnits } from 'test/fixtures/fixtures.js'; import { EVENTS, S2S } from 'src/constants.js'; import * as utils from 'src/utils.js'; @@ -1739,13 +1740,14 @@ describe('adapterManager tests', function () { }); describe('makeBidRequests', function () { - let adUnits; + let adUnits, twinAdUnits; beforeEach(function () { resetAdUnitCounters(); adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); return adUnit; }) + twinAdUnits = getTwinAdUnits(); }); function makeBidRequests(au = adUnits) { @@ -1862,6 +1864,31 @@ describe('adapterManager tests', function () { }) }); + describe('adUnitAuctionsCounter', () => { + it('should set and increment auctionsCount at adUnitCode level', () => { + const [au1, au2] = adUnits; + makeBidRequests([au1]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(1); + }); + makeBidRequests([au1]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(2); + }); + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(bid.adUnitCode === au1.code ? 3 : 1); + }); + }); + + it('should increment the auctionsCount of each adUnitCode exactly once per auction for twin ad units', () => { + const [au1, au2] = twinAdUnits; + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(1); + }); + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(2); + }); + }); + }); + describe('and activity controls', () => { let redactOrtb2; let redactBidRequest; From f5ab0594e57107f38ad260e20ae20a79a53a7166 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 11 Dec 2024 17:13:51 -0800 Subject: [PATCH 0752/1097] Core: fix bug where FPD enrichments can modify bidder configuration (#12572) --- src/prebid.js | 2 +- test/spec/unit/pbjs_api_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index e745445295e..a536add9a96 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -561,7 +561,7 @@ pbjsInstance.requestBids = (function() { adUnitCodes = adUnitCodes.filter(uniques); const ortb2Fragments = { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), - bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) + bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, deepClone(cfg.ortb2)]).filter(([_, ortb2]) => ortb2 != null)) } return enrichFPD(GreedyPromise.resolve(ortb2Fragments.global)).then(global => { ortb2Fragments.global = global; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0a9de8e976c..c86742d25a4 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1839,6 +1839,29 @@ describe('Unit: Prebid Module', function () { })); }); + it('that cannot alter global config', () => { + configObj.setConfig({ortb2: {value: 'old'}}); + startAuctionStub.callsFake(({ortb2Fragments}) => { + ortb2Fragments.global.value = 'new' + }); + $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + expect(configObj.getAnyConfig('ortb2').value).to.eql('old'); + }); + + it('that cannot alter bidder config', () => { + configObj.setBidderConfig({ + bidders: ['mockBidder'], + config: { + ortb2: {value: 'old'} + } + }) + startAuctionStub.callsFake(({ortb2Fragments}) => { + ortb2Fragments.bidder.mockBidder.value = 'new'; + }) + $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + expect(configObj.getBidderConfig().mockBidder.ortb2.value).to.eql('old'); + }) + it('enriched through enrichFPD', () => { function enrich(next, fpd) { next.bail(fpd.then(ortb2 => { From 52de91dae22be8f15cc3da7805c6d8426d5279a2 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Thu, 12 Dec 2024 01:33:18 +0000 Subject: [PATCH 0753/1097] Rise Bid Adapter: Add ORTB2 device data to request payload (#12017) * Rise Bid Adapter: Add ORTB2 device data to request payload * Rise Bid Adapter: Move ORTB2 device under `params` key --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- libraries/riseUtils/index.js | 4 ++++ test/spec/modules/riseBidAdapter_spec.js | 26 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index 2bc337b9c55..60f31ef2603 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -290,6 +290,10 @@ export function generateGeneralParams(generalObject, bidderRequest, adapterVersi generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); } + if (ortb2Metadata.device) { + generalParams.device = ortb2Metadata.device; + } + if (syncEnabled) { const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); if (allowedSyncMethod) { diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index cb879231237..2d11ae16cb7 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -111,6 +111,7 @@ describe('riseAdapter', function () { const bidderRequest = { bidderCode: 'rise', + ortb2: {device: {}}, } const placementId = '12345678'; const api = [1, 2]; @@ -434,6 +435,31 @@ describe('riseAdapter', function () { expect(request.data.bids[0].sua).to.not.exist; }); + it('should send ORTB2 device data in bid request', function() { + const ortb2 = { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }, + }; + + const request = spec.buildRequests(bidRequests, { + ...bidderRequest, + ortb2, + }); + + expect(request.data.params.device).to.deep.equal(ortb2.device); + }); + describe('COPPA Param', function() { it('should set coppa equal 0 in bid request if coppa is set to false', function() { const request = spec.buildRequests(bidRequests, bidderRequest); From 0aa1fc0fe5a79b4cb2f6e94c0493422aa883c9c7 Mon Sep 17 00:00:00 2001 From: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:36:58 -0500 Subject: [PATCH 0754/1097] nextMillennium Bid Adapter : added gpid support (#12567) * added gpid and pbadslot * added test cases for gpid, and pbadslot * fixed lint error --------- Co-authored-by: Yakov Klein --- modules/nextMillenniumBidAdapter.js | 5 ++ .../modules/nextMillenniumBidAdapter_spec.js | 66 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 112f0d68504..a4169947e85 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -291,6 +291,11 @@ export function getImp(bid, id, mediaTypes) { }, }; + const gpid = bid?.ortb2Imp?.ext?.gpid; + const pbadslot = bid?.ortb2Imp?.ext?.data?.pbadslot; + if (gpid) imp.ext.gpid = gpid; + if (pbadslot) imp.ext.data = { pbadslot }; + getImpBanner(imp, banner); getImpVideo(imp, video); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index a5c5f5f714d..bfcf7e80420 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -100,6 +100,72 @@ describe('nextMillenniumBidAdapterTests', () => { video: {w: 640, h: 480, mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript']}, }, }, + + { + title: 'imp with gpid', + data: { + id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, + bid: { + mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, + adUnitCode: 'test-gpid-1', + bidId: 'e36ea395f67a', + ortb2Imp: {ext: {gpid: 'imp-gpid-123'}}, + }, + + mediaTypes: { + banner: { + data: {sizes: [[300, 250], [320, 250]]}, + }, + }, + }, + + expected: { + id: 'e36ea395f67a', + ext: { + prebid: {storedrequest: {id: '123'}}, + gpid: 'imp-gpid-123' + }, + banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + }, + }, + + { + title: 'imp with pbadslot', + data: { + id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, + bid: { + mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, + adUnitCode: 'test-gpid-1', + bidId: 'e36ea395f67a', + ortb2Imp: { + ext: { + data: { + pbadslot: 'slot-123' + } + } + }, + }, + + mediaTypes: { + banner: { + data: {sizes: [[300, 250], [320, 250]]}, + }, + }, + }, + + expected: { + id: 'e36ea395f67a', + ext: { + prebid: {storedrequest: {id: '123'}}, + data: { + pbadslot: 'slot-123' + } + }, + banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + }, + }, ]; for (let {title, data, expected} of dataTests) { From f0adee03d305c8cbe85974ed0f7a89c72e8495b5 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Fri, 13 Dec 2024 00:56:47 +1100 Subject: [PATCH 0755/1097] Reading advertiserDomains from adv response. (#12573) --- modules/adnuntiusBidAdapter.js | 10 ++++++---- test/spec/modules/adnuntiusBidAdapter_spec.js | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index cce1b5332ad..2747000c416 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -356,10 +356,12 @@ export const spec = { } function buildAdResponse(bidderCode, ad, adUnit, dealCount) { - const destinationUrls = ad.destinationUrls || {}; - const advertiserDomains = []; - for (const value of Object.values(destinationUrls)) { - advertiserDomains.push(value.split('/')[2]) + const advertiserDomains = ad.advertiserDomains || []; + if (advertiserDomains.length === 0) { + const destinationUrls = ad.destinationUrls || {}; + for (const value of Object.values(destinationUrls)) { + advertiserDomains.push(value.split('/')[2]) + } } const adResponse = { bidderCode: bidderCode, diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index a0846a829a8..7814c24f4ac 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -198,6 +198,7 @@ describe('adnuntiusBidAdapter', function () { 'urlsEsc': { 'destination': 'https%3A%2F%2Fads.adnuntius.delivery%2Fc%2FyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg%3Fct%3D673%26r%3Dhttp%253A%252F%252Fadnuntius.com' }, + 'advertiserDomains': ['fred.com', 'george.com'], 'destinationUrls': { 'destination': 'https://adnuntius.com' }, @@ -1255,8 +1256,9 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].currency).to.equal(deal.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); - expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(2); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('fred.com'); + expect(interpretedResponse[0].meta.advertiserDomains[1]).to.equal('george.com'); expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].deals[0].html); expect(interpretedResponse[0].ttl).to.equal(360); expect(interpretedResponse[0].dealId).to.equal('abc123xyz'); From 91ae09057b754e4b80b1189f877e96858ed4280d 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, 12 Dec 2024 17:15:22 +0100 Subject: [PATCH 0756/1097] Added page view ID to Kobler bid adapter. (#12556) --- modules/koblerBidAdapter.js | 6 +++++- test/spec/modules/koblerBidAdapter_spec.js | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 9cde2e96d4e..a5c14e7bce1 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,5 +1,6 @@ import { deepAccess, + generateUUID, getWindowSelf, isArray, isStr, @@ -14,6 +15,8 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j const additionalData = new WeakMap(); +export const pageViewId = generateUUID(); + export function setAdditionalData(obj, key, value) { const prevValue = additionalData.get(obj) || {}; additionalData.set(obj, { ...prevValue, [key]: value }); @@ -169,7 +172,8 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { ext: { kobler: { tcf_purpose_2_given: purpose2Given, - tcf_purpose_3_given: purpose3Given + tcf_purpose_3_given: purpose3Given, + page_view_id: pageViewId } } }; diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 47b89bb5956..8bee7c0e2cb 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec} from 'modules/koblerBidAdapter.js'; +import {pageViewId, spec} from 'modules/koblerBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -247,6 +247,21 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.site.page).to.be.equal(testUrl); }); + it('should reuse the same page view ID on subsequent calls', function () { + const testUrl = 'kobler.no'; + const auctionId1 = '8319af54-9795-4642-ba3a-6f57d6ff9100'; + const auctionId2 = 'e19f2d0c-602d-4969-96a1-69a22d483f47'; + const timeout = 5000; + const validBidRequests = [createValidBidRequest()]; + const bidderRequest1 = createBidderRequest(auctionId1, timeout, testUrl); + const bidderRequest2 = createBidderRequest(auctionId2, timeout, testUrl); + + const openRtbRequest1 = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest1).data); + expect(openRtbRequest1.ext.kobler.page_view_id).to.be.equal(pageViewId); + const openRtbRequest2 = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest2).data); + expect(openRtbRequest2.ext.kobler.page_view_id).to.be.equal(pageViewId); + }); + it('should read data from valid bid requests', function () { const firstSize = [400, 800]; const secondSize = [450, 950]; @@ -538,7 +553,8 @@ describe('KoblerAdapter', function () { ext: { kobler: { tcf_purpose_2_given: true, - tcf_purpose_3_given: false + tcf_purpose_3_given: false, + page_view_id: pageViewId } } }; From 3782de468144a6a02aa7a56fb71ba41308ab958d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 12 Dec 2024 10:18:41 -0800 Subject: [PATCH 0757/1097] PBS Adapter: fix inconsistency in how bidderconfig is merged, with a special case for EIDs (#12571) * PBS adapter: pre-merge arrays in bidder config * PBS Adapter: consolidate EIDs using eidpermissions * fix lint * optimizations --- .../prebidServerBidAdapter/bidderConfig.js | 158 ++++++ .../prebidServerBidAdapter/ortbConverter.js | 6 +- .../modules/prebidServerBidAdapter_spec.js | 457 +++++++++++++++++- 3 files changed, 596 insertions(+), 25 deletions(-) create mode 100644 modules/prebidServerBidAdapter/bidderConfig.js diff --git a/modules/prebidServerBidAdapter/bidderConfig.js b/modules/prebidServerBidAdapter/bidderConfig.js new file mode 100644 index 00000000000..44ab2f90d42 --- /dev/null +++ b/modules/prebidServerBidAdapter/bidderConfig.js @@ -0,0 +1,158 @@ +import {mergeDeep, deepEqual, deepAccess, deepSetValue, deepClone} from '../../src/utils.js'; +import {ORTB_EIDS_PATHS} from '../../src/activities/redactor.js'; + +/** + * Perform a partial pre-merge of bidder config for PBS. + * + * Prebid.js and Prebid Server use different strategies for merging global and bidder-specific config; JS attemps to + * merge arrays (concatenating them, with some deduping, cfr. mergeDeep), while PBS only merges objects - + * a bidder-specific array will replace a global array. + * + * This returns bidder config (from `bidder`) where arrays are replaced with what you get from merging them with `global`, + * so that the result of merging in PBS is the same as in JS. + */ +export function getPBSBidderConfig({global, bidder}) { + return Object.fromEntries( + Object.entries(bidder).map(([bidderCode, bidderConfig]) => { + return [bidderCode, replaceArrays(bidderConfig, mergeDeep({}, global, bidderConfig))] + }) + ) +} + +function replaceArrays(config, mergedConfig) { + return Object.fromEntries( + Object.entries(config).map(([key, value]) => { + const mergedValue = mergedConfig[key]; + if (Array.isArray(value)) { + if (!deepEqual(value, mergedValue) && Array.isArray(mergedValue)) { + value = mergedValue; + } + } else if (value != null && typeof value === 'object') { + value = replaceArrays(value, mergedValue); + } + return [key, value]; + }) + ) +} + +/** + * Extract all EIDs from FPD. + * + * Returns {eids, conflicts}, where: + * + * - `eids` contains an object of the form `{eid, bidders}` for each unique EID object found anywhere in FPD; + * `bidders` is a list of all the bidders that refer to that specific EID object, or false if that EID object is defined globally. + * - `conflicts` is a set containing all EID sources that appear in multiple, otherwise different, EID objects. + */ +export function extractEids({global, bidder}) { + const entries = []; + const bySource = {}; + const conflicts = new Set() + + function getEntry(eid) { + let entry = entries.find((candidate) => deepEqual(candidate.eid, eid)); + if (entry == null) { + entry = {eid, bidders: []} + entries.push(entry); + } + if (bySource[eid.source] == null) { + bySource[eid.source] = entry.eid; + } else if (entry.eid === eid) { + // if this is the first time we see this eid, but not the first time we see its source, we have a conflict + conflicts.add(eid.source); + } + return entry; + } + + ORTB_EIDS_PATHS.forEach(path => { + (deepAccess(global, path) || []).forEach(eid => { + getEntry(eid).bidders = false; + }); + }) + Object.entries(bidder).forEach(([bidderCode, bidderConfig]) => { + ORTB_EIDS_PATHS.forEach(path => { + (deepAccess(bidderConfig, path) || []).forEach(eid => { + const entry = getEntry(eid); + if (entry.bidders !== false) { + entry.bidders.push(bidderCode); + } + }) + }) + }) + return {eids: entries, conflicts}; +} + +/** + * Consolidate extracted EIDs to take advantage of PBS's eidpermissions feature: + * https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#eid-permissions + * + * If different bidders have different EID configurations, in most cases we can avoid repeating it in each bidder's + * specific config. As long as there are no conflicts (different EID objects that refer to the same source constitute a conflict), + * the EID can be set as global, and eidpermissions can restrict its access only to specific bidders. + * + * Returns {global, bidder, permissions}, where: + * - `global` is a list of global EID objects (some of which may be restricted through `permissions` + * - `bidder` is a map from bidder code to EID objects that are specific to that bidder, and cannot be restricted through `permissions` + * - `permissions` is a list of EID permissions as expected by PBS. + */ +export function consolidateEids({eids, conflicts = new Set()}) { + const globalEntries = []; + const bidderEntries = []; + const byBidder = {}; + eids.forEach(eid => { + (eid.bidders === false ? globalEntries : bidderEntries).push(eid); + }); + bidderEntries.forEach(({eid, bidders}) => { + if (!conflicts.has(eid.source)) { + globalEntries.push({eid, bidders}) + } else { + bidders.forEach(bidderCode => { + (byBidder[bidderCode] = byBidder[bidderCode] || []).push(eid) + }) + } + }); + return { + global: globalEntries.map(({eid}) => eid), + permissions: globalEntries.filter(({bidders}) => bidders !== false).map(({eid, bidders}) => ({ + source: eid.source, + bidders + })), + bidder: byBidder + } +} + +function replaceEids({global, bidder}) { + const consolidated = consolidateEids(extractEids({global, bidder})); + global = deepClone(global); + bidder = deepClone(bidder); + function removeEids(target) { + delete target?.user?.eids; + delete target?.user?.ext?.eids; + } + removeEids(global); + Object.values(bidder).forEach(removeEids); + if (consolidated.global.length) { + deepSetValue(global, 'user.ext.eids', consolidated.global); + } + if (consolidated.permissions.length) { + deepSetValue(global, 'ext.prebid.data.eidpermissions', consolidated.permissions); + } + Object.entries(consolidated.bidder).forEach(([bidderCode, bidderEids]) => { + if (bidderEids.length) { + deepSetValue(bidder[bidderCode], 'user.ext.eids', bidderEids); + } + }) + return {global, bidder} +} + +export function premergeFpd(ortb2Fragments) { + if (ortb2Fragments == null || Object.keys(ortb2Fragments.bidder || {}).length === 0) { + return ortb2Fragments; + } else { + ortb2Fragments = replaceEids(ortb2Fragments); + return { + ...ortb2Fragments, + bidder: getPBSBidderConfig(ortb2Fragments) + }; + } +} diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 9de7d3f05c9..2cc34586102 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -16,6 +16,7 @@ import {ACTIVITY_TRANSMIT_TID} from '../../src/activities/activities.js'; import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; import {minimum} from '../../src/utils/reducers.js'; import {s2sDefaultConfig} from './index.js'; +import {premergeFpd} from './bidderConfig.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; @@ -296,7 +297,10 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, ttl: s2sBidRequest.s2sConfig.defaultTtl || DEFAULT_S2S_TTL, requestTimestamp, - s2sBidRequest, + s2sBidRequest: { + ...s2sBidRequest, + ortb2Fragments: premergeFpd(s2sBidRequest.ortb2Fragments) + }, requestedBidders, actualBidderRequests: bidderRequests, nativeRequest: s2sBidRequest.s2sConfig.ortbNative, diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index dcd03a8882b..058175b878c 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -37,6 +37,11 @@ import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepSetValue} from '../../../src/utils.js'; import {ACTIVITY_TRANSMIT_UFPD} from '../../../src/activities/activities.js'; import {MODULE_TYPE_PREBID} from '../../../src/activities/modules.js'; +import { + consolidateEids, + extractEids, + getPBSBidderConfig +} from '../../../modules/prebidServerBidAdapter/bidderConfig.js'; let CONFIG = { accountId: '1', @@ -2065,37 +2070,95 @@ describe('S2S Adapter', function () { }); }); - it('should pass user.ext.eids from FPD', function () { - config.setConfig({s2sConfig: CONFIG}); - const req = { - ...REQUEST, - ortb2Fragments: { - global: { - user: { - ext: { - eids: [{id: 1}, {id: 2}] - } - } - }, - bidder: { - appnexus: { + describe('user.ext.eids', () => { + let req; + beforeEach(() => { + const s2sConfig = { + ...CONFIG, + bidders: ['appnexus', 'rubicon'] + } + config.setConfig({s2sConfig}); + req = { + ...REQUEST, + s2sConfig, + ortb2Fragments: { + global: { user: { ext: { - eids: [{id: 3}] + eids: [{source: 'idA', id: 1}, {source: 'idB', id: 2}] + } + } + }, + bidder: { + appnexus: { + user: { + ext: { + eids: [{source: 'idC', id: 3}] + } } } } } } - } - adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); - const payload = JSON.parse(server.requests[0].requestBody); - expect(payload.user.ext.eids).to.eql([{id: 1}, {id: 2}]); - expect(payload.ext.prebid.bidderconfig).to.eql([{ - bidders: ['appnexus'], - config: {ortb2: {user: {ext: {eids: [{id: 3}]}}}} - }]); - }); + }) + it('should get picked up from from FPD', function () { + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + expect(payload.user.ext.eids).to.eql([ + {source: 'idA', id: 1}, + {source: 'idB', id: 2}, + {source: 'idC', id: 3} + ]); + expect(payload.ext.prebid.data.eidpermissions).to.eql([{ + bidders: ['appnexus'], + source: 'idC' + }]); + }); + + it('should repeat global EIDs when bidder-specific EIDs conflict', () => { + BID_REQUESTS.push({ + ...BID_REQUESTS[0], + bidderCode: 'rubicon', + bids: [{ + bidder: 'rubicon', + params: {} + }] + }) + req.ortb2Fragments.bidder.rubicon = { + user: { + ext: { + eids: [{source: 'idC', id: 4}] + } + } + } + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + const globalEids = [ + {source: 'idA', id: 1}, + {source: 'idB', id: 2}, + ] + expect(payload.user.ext.eids).to.eql(globalEids); + expect(payload.ext.prebid?.data?.eidpermissions).to.not.exist; + expect(payload.ext.prebid.bidderconfig).to.have.deep.members([ + { + bidders: ['appnexus'], + config: { + ortb2: { + user: {ext: {eids: globalEids.concat([{source: 'idC', id: 3}])}} + } + } + }, + { + bidders: ['rubicon'], + config: { + ortb2: { + user: {ext: {eids: globalEids.concat([{source: 'idC', id: 4}])}} + } + } + } + ]) + }) + }) it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { config.setConfig({ @@ -4305,4 +4368,350 @@ describe('S2S Adapter', function () { expect(requestBid.ext.prebid.floors).to.deep.equal({ enabled: true, floorMin: 1, floorMinCur: 'CUR' }); }); }); + + describe('getPBSBidderConfig', () => { + [ + { + t: 'does not alter config when there are no conflicts', + global: { + k1: 'val' + }, + bidder: { + bidderA: { + k2: 'val' + } + }, + expected: { + bidderA: { + k2: 'val' + } + } + }, + { + t: 'uses bidder config on type mismatch (scalar/object)', + global: { + k1: 'val', + k2: 'val' + }, + bidder: { + bidderA: { + k1: {k3: 'val'} + } + }, + expected: { + bidderA: { + k1: {k3: 'val'} + } + } + }, + { + t: 'uses bidder config on type mismatch (array/object)', + global: { + k: [1, 2] + }, + bidder: { + bidderA: { + k: {inner: 'val'} + } + }, + expected: { + bidderA: { + k: {inner: 'val'} + } + } + }, + { + t: 'uses bidder config on type mismatch (object/array)', + global: { + k: {inner: 'val'} + }, + bidder: { + bidderA: { + k: [1, 2] + } + }, + expected: { + bidderA: { + k: [1, 2] + } + } + }, + { + t: 'uses bidder config on type mismatch (array/null)', + global: { + k: [1, 2] + }, + bidder: { + bidderA: { + k: null + } + }, + expected: { + bidderA: { + k: null + } + } + }, + { + t: 'uses bidder config on type mismatch (null/array)', + global: {}, + bidder: { + bidderA: { + k: [1, 2] + } + }, + expected: { + bidderA: { + k: [1, 2] + } + } + }, + { + t: 'concatenates arrays', + global: { + key: 'value', + array: [1] + }, + bidder: { + bidderA: { + array: [2] + } + }, + expected: { + bidderA: { + array: [1, 2] + } + } + }, + { + t: 'concatenates nested arrays', + global: { + nested: { + array: [1] + } + }, + bidder: { + bidderA: { + key: 'value', + nested: { + array: [2] + } + } + }, + expected: { + bidderA: { + key: 'value', + nested: { + array: [1, 2] + } + } + } + }, + { + t: 'does not repeat equal elements', + global: { + array: [{id: 1}] + }, + bidder: { + bidderA: { + array: [{id: 1}, {id: 2}] + } + }, + expected: { + bidderA: { + array: [{id: 1}, {id: 2}] + } + } + } + ].forEach(({t, global, bidder, expected}) => { + it(t, () => { + expect(getPBSBidderConfig({global, bidder})).to.eql(expected); + }) + }) + }); + describe('EID handling', () => { + function mkEid(source, value = source) { + return {source, value}; + } + + function eidEntry(source, value = source, bidders = false) { + return {eid: {source, value}, bidders}; + } + + describe('extractEids', () => { + [ + { + t: 'no bidder-specific eids', + global: { + user: { + ext: { + eids: [ + mkEid('idA', 'id1'), + mkEid('idA', 'id2') + ] + }, + eids: [mkEid('idB')] + } + }, + expected: { + eids: [ + eidEntry('idA', 'id1'), + eidEntry('idA', 'id2'), + eidEntry('idB') + ], + conflicts: ['idA'] + } + }, + { + t: 'bidder-specific eids', + global: { + user: { + eids: [ + mkEid('idA') + ] + }, + }, + bidder: { + bidderA: { + user: { + ext: { + eids: [ + mkEid('idB') + ] + } + } + } + }, + expected: { + eids: [ + eidEntry('idA'), + eidEntry('idB', 'idB', ['bidderA']) + ] + } + }, + { + t: 'conflicting bidder-specific eids', + global: { + user: { + eids: [mkEid('idA', 'idA1')] + }, + }, + bidder: { + bidderA: { + user: { + eids: [mkEid('idA', 'idA2'), mkEid('idB', 'idB1'), mkEid('idD')] + }, + }, + bidderB: { + user: { + ext: { + eids: [mkEid('idB', 'idB2'), mkEid('idC'), mkEid('idD')] + } + } + }, + }, + expected: { + eids: [ + eidEntry('idA', 'idA1'), + eidEntry('idA', 'idA2', ['bidderA']), + eidEntry('idB', 'idB1', ['bidderA']), + eidEntry('idB', 'idB2', ['bidderB']), + eidEntry('idC', 'idC', ['bidderB']), + eidEntry('idD', 'idD', ['bidderA', 'bidderB']) + ], + conflicts: ['idA', 'idB'] + } + } + ].forEach(({t, global = {}, bidder = {}, expected}) => { + it(t, () => { + const {eids, conflicts} = extractEids({global, bidder}); + expect(eids).to.have.deep.members(expected.eids); + expect(Array.from(conflicts)).to.have.members(expected.conflicts || []); + }) + }); + }); + describe('consolidateEids', () => { + it('returns global EIDs without permissions', () => { + expect(consolidateEids({ + eids: [eidEntry('idA'), eidEntry('idB')] + })).to.eql({ + global: [mkEid('idA'), mkEid('idB')], + permissions: [], + bidder: {} + }) + }); + + it('returns conflicting, but global EIDs', () => { + expect(consolidateEids({ + eids: [eidEntry('idA', 'idA1'), eidEntry('idA', 'idA2')], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('idA', 'idA1'), mkEid('idA', 'idA2')], + permissions: [], + bidder: {} + }) + }) + + it('sets permissions for bidder-speficic EIDS', () => { + expect(consolidateEids({ + eids: [ + eidEntry('idA'), + eidEntry('idB', 'idB', ['bidderB']) + ] + })).to.eql({ + global: [mkEid('idA'), mkEid('idB')], + permissions: [{source: 'idB', bidders: ['bidderB']}], + bidder: {} + }) + }) + + it('does not consolidate conflicting bidder-specific EIDs', () => { + expect(consolidateEids({ + eids: [ + eidEntry('global'), + eidEntry('idA', 'idA1', ['bidderA']), + eidEntry('idA', 'idA2', ['bidderB']) + ], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('global')], + permissions: [], + bidder: { + bidderA: [mkEid('idA', 'idA1')], + bidderB: [mkEid('idA', 'idA2')] + } + }) + }) + + it('does not set permissions for conflicting bidder-specific eids', () => { + expect(consolidateEids({ + eids: [eidEntry('idA', 'idA1'), eidEntry('idA', 'idA2', ['bidderA'])], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('idA', 'idA1')], + permissions: [], + bidder: { + bidderA: [mkEid('idA', 'idA2')] + } + }) + }); + + it('can do partial consolidation when only some IDs are conflicting', () => { + expect(consolidateEids({ + eids: [ + eidEntry('idA', 'idA1'), + eidEntry('idB', 'idB', ['bidderB']), + eidEntry('idA', 'idA2', ['bidderA']) + ], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('idA', 'idA1'), mkEid('idB')], + permissions: [{source: 'idB', bidders: ['bidderB']}], + bidder: { + bidderA: [mkEid('idA', 'idA2')] + } + }) + }) + }) + }); }); From 0310305b2888297c460e7647ba28b92a4111b6a5 Mon Sep 17 00:00:00 2001 From: Rich Rodriguez Date: Thu, 12 Dec 2024 14:06:27 -0500 Subject: [PATCH 0758/1097] Mobian RTD Module: Docs Update (#12576) * Updated overview section * Added detailed documentation for Mobian RTD module * Adding formatting for documentation * More formatting * More formatting * Updated values to be exact matches, clarified some descriptions --- modules/mobianRtdProvider.md | 125 +++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md index e974e56dcdd..100e529143f 100644 --- a/modules/mobianRtdProvider.md +++ b/modules/mobianRtdProvider.md @@ -3,16 +3,131 @@ ## Overview Module Name: Mobian Rtd Provider + Module Type: Rtd Provider + Maintainer: rich.rodriguez@themobian.com -## Description +The Mobian Real-Time Data (RTD) Module is a plug-and-play Prebid.js adapter that is designed to provide Mobian Contextual results on the publisher’s page. + +## Downloading and Configuring the Mobian RTD module + +Navigate to https://docs.prebid.org/download.html and check the box labeled Mobian Prebid Contextual Evaluation. If you have installed Prebid.js on your site previously, please be sure to select any other modules and adaptors to suit your needs. When clicking the "Get Prebid.js" button at the bottom of the page, the site will build a version of Prebid.js with all of your selections. + +Direct link to the Mobian module in the Prebid.js repository: https://github.com/prebid/Prebid.js/blob/a9de3c15ac9a108b43a1e2df04abd6dfb5297530/modules/mobianRtdProvider.js + +The client will need to provide Mobian with all the domains that would be using the prebid module so that Mobian can whitelist those domains. Failure to whitelist the domains will yield a 404 when making a request to the Mobian Contextual API at https://prebid.outcomes.net/. + +## Functionality + +At a high level, the Mobian RTD Module is designed to call the Mobian Contextal API on page load, requesting the Mobian classifications and results for the URL. The classifications and results are designed to be picked up by any SSP or DSP in the Prebid.js ecosystem. The module also supports placing the Mobian classifications on each ad slot on the page, thus allowing for targeting within GAM. + +## Available Classifications + +Risk: + +Key: mobianRisk + +Possible values: "none", "low", "medium" or "high" + +Description: Risk will contain Mobian’s brand safety assessment of the page. Brand Safety is determined via the Mobian AI models taking into account a semantic analysis of the content while understanding the context. A more detailed description of the reasoning for a given URL can be observed by going to mbs.themobian.com and entering the URL. + +------------------ + +Content Categories: + +Key: mobianContentCategories + +Possible values: "adult_content", "arms", "crime", "death_injury", "debated_issue", "hate_speech", "drugs_alcohol", "obscenity", "piracy", "spam", "terrorism" + +Description: Content Categories contain results based on the legacy GARM framework. GARM no longer is a standard and does not factor into our risk assessment but is included for posterity. + +------------------ + +Sentiment: + +Key: mobianSentiment + +Possible values: "negative", "neutral" or "positive" + +Description: Sentiment can only be one of the three values listed, and is determined via the Mobian AI analyzing the content and making one of these three determinations. + +------------------ + +Emotion: + +Key: mobianEmotions + +Possible values: "love", "joy", "surprise", "anger", "sadness", "fear" + +Description: The Mobian AI assesses the emotions exuded from the content, taking into account the context. A given piece of content can have multiple emotions. The current list of emotions is all possible emotions available but this will be updated to be more freeform and varied in a future release. + +------------------ + +Tone: + +Key: mobianTones + +Possible values: Various, but some examples include "comedic", "serious" or "emotional" + +Description: While the Mobian emotion classification looks at the emotions exuded from the content, tone examines the overall presentation of the content and determines the overall mood of the work. A given piece of content can have multiple tones. + +------------------ + +Theme: + +Key: mobianThemes + +Possible values: Various, but some examples include "skincare", "food" and "nightlife" + +Description: Themes are a wide classification of content categorization, taking into account the content and context to label the content with a theme. A given piece of content can have multiple themes. + +------------------ + +Genre: + +Key: mobianGenre + +Possible values: Various, but some examples include "journalism", "gaming" or "how-to" + +Description: Genres are a more narrow classification of content categorization, aiming to label the content towards its overall purpose and audience. A given piece of content can have multiple genres. + +------------------ + +AP Values + +Keys: ap_a0, ap_a1, ap_p0, ap_p1 + +Possible values: Various, numerically id-based and customizable based on Mobian Persona Settings. + +Description: Mobian AI Personas are custom created based on prompts to find a specific audience. Please contact your Mobian contact directly for more information on this tool. The difference between the keys is below: + +a0 = Advertisers (via Campaign IDs) in this list should NOT want to advertise on this page + +a1 = Advertisers (via Campaign IDs) should want to advertise on this page + +p0 = Advertisers (via Campaign IDs) should AVOID targeting these personas + +p1 = Advertisers (via Campaign IDs) should target these personas + +*AP Values is in the early stages of testing and is subject to change. + +## GAM Targeting: + +On each page load, the Mobian RTD module finds each ad slot on the page and performs the following function: + +```js +window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting(key, value); +``` + +"key" and "value" will be replaced with the various classifications as described in the previous section. Notably, this function runs before ad calls are made to GAM, which enables the keys and value to be used for targeting or blocking in GAM. + +For more details on how to set up key-value pairs in GAM, please see this documentation from Google: https://support.google.com/admanager/answer/9796369?sjid=12535178383871274096-NA -RTD provider for themobian Brand Safety determinations. Publishers -should use this to get Mobian's GARM Risk evaluations for -a URL. +For example, if you wanted to target articles where mobianRisk is "low", the key to set in GAM would be "mobianRisk" and the value would be "low". Once these keys and values are set within the Inventory section in GAM as listed by their documentation, you can then reference the key value pair in Custom Targeting for any line item you create. -## Configuration +## Configuration Highlight ```js pbjs.setConfig({ From c42cff1caffd05c907537392f90daabf01a67c65 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Dec 2024 19:36:10 +0000 Subject: [PATCH 0759/1097] Prebid 9.23.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3012d1d9a4..ad8653ca65f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.23.0-pre", + "version": "9.23.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.23.0-pre", + "version": "9.23.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 2b6d3cae7aa..becff88def6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.23.0-pre", + "version": "9.23.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From fb8fb3d0a798ee9316fbfd9d52f0efb0c644902b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Dec 2024 19:36:10 +0000 Subject: [PATCH 0760/1097] Increment version to 9.24.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ad8653ca65f..cf2b8a35c1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.23.0", + "version": "9.24.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.23.0", + "version": "9.24.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index becff88def6..fa9031dbf54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.23.0", + "version": "9.24.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 9fe52d9796c043e77a66a7d6c8cd6e1911a968c9 Mon Sep 17 00:00:00 2001 From: Victor <103455651+victorlassomarketing@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:04:49 -0800 Subject: [PATCH 0761/1097] Lasso Bid Adapter : add npi support (#12545) * New npi functionality for Lasso prebid adapter * New npi functionality for Lasso prebid adapter * Update unit tests for lasso prebid adapter --- modules/lassoBidAdapter.js | 8 ++- test/spec/modules/lassoBidAdapter_spec.js | 72 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index 6215af03a97..dc36cb4af23 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -44,6 +44,8 @@ export const spec = { sizes, aimXR, uid: '$UID', + npi: bidRequest.params.npi || '', + npi_hash: bidRequest.params.npiHash || '', params: JSON.stringify(bidRequest.params), crumbs: JSON.stringify(bidRequest.crumbs), prebidVersion: '$prebid.version$', @@ -128,10 +130,10 @@ function getBidRequestUrl(aimXR, params) { if (params && params.dtc) { path = '/dtc-request'; } - if (!aimXR) { - return GET_IUD_URL + ENDPOINT_URL + path; + if (aimXR || params.npi || params.npiHash) { + return ENDPOINT_URL + path; } - return ENDPOINT_URL + path; + return GET_IUD_URL + ENDPOINT_URL + path; } function getDeviceData() { diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index 15dee20e566..f33869e9366 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/lassoBidAdapter.js'; import { server } from '../../mocks/xhr'; const ENDPOINT_URL = 'https://trc.lhmos.com/prebid'; +const GET_IUD_URL = 'https://secure.adnxs.com/getuid?'; const bid = { bidder: 'lasso', @@ -78,7 +79,7 @@ describe('lassoBidAdapter', function () { }); }); - describe('buildRequests', function () { + describe('buildRequests with standard flow', function () { let validBidRequests, bidRequest; before(() => { validBidRequests = spec.buildRequests([bid], bidderRequest); @@ -97,6 +98,75 @@ describe('lassoBidAdapter', function () { expect(bidRequest.method).to.exist; expect(bidRequest.method).to.equal('GET'); }); + + it('should send request to get uid and trc via get request', () => { + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(GET_IUD_URL + ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with npi', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + npi: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with npi', () => { + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with npi hash', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + npiHash: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with npi', () => { + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); }); describe('interpretResponse', function () { From 0a0325f158efbba533c010c70a6fcee0532a3d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Primo=C5=BE?= Date: Mon, 16 Dec 2024 13:43:58 +0100 Subject: [PATCH 0762/1097] Initial commit for ResponsiveAds bid adapter (#12554) --- modules/responsiveAdsBidAdapter.js | 80 ++++++++++ modules/responsiveAdsBidAdapter.md | 38 +++++ .../modules/responsiveAdsBidAdapter_spec.js | 145 ++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 modules/responsiveAdsBidAdapter.js create mode 100644 modules/responsiveAdsBidAdapter.md create mode 100644 test/spec/modules/responsiveAdsBidAdapter_spec.js diff --git a/modules/responsiveAdsBidAdapter.js b/modules/responsiveAdsBidAdapter.js new file mode 100644 index 00000000000..a33a52f5644 --- /dev/null +++ b/modules/responsiveAdsBidAdapter.js @@ -0,0 +1,80 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { + logMessage, + isSafeFrameWindow, + mergeDeep, + canAccessWindowTop, +} from '../src/utils.js'; + +const BIDDER_VERSION = '1.0'; +const BIDDER_CODE = 'responsiveads'; +const ENDPOINT_URL = 'https://ve60c4xzl9.execute-api.us-east-1.amazonaws.com/prod/prebidjs'; +const DEFAULT_CURRENCY = 'USD'; +const GVLID = 1189; + +const converter = ortbConverter({ + context: { + mediaType: BANNER, + netRevenue: true, + ttl: 300, + currency: DEFAULT_CURRENCY, + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + // add additional information we might need on the backend + mergeDeep(req, { + ext: { + prebid: { + adapterVersion: `${BIDDER_VERSION}`, + }, + }, + }); + return req; + }, +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + isBidRequestValid: function(bid) { + // validate the bid request + return !!(bid.params); + }, + buildRequests: function(bidRequests, bidderRequest) { + // we only want to bid if we are not in a safeframe + if (isSafeFrameWindow()) { + return null; + } + + // if we can't access top we don't want to bid + if (!canAccessWindowTop()) { + return null; + } + const data = converter.toORTB({ bidRequests, bidderRequest }); + return { + method: 'POST', + url: ENDPOINT_URL, + data: data, + options: { + contentType: 'application/json', + withCredentials: false + }, + bidderRequest + }; + }, + interpretResponse: function(response, request) { + const res = converter.fromORTB({ response: response.body, request: request.data }); + const bids = res.bids; + return bids; + }, + + onBidWon: (bid) => { + logMessage('onBidWon', bid); + } + +}; + +registerBidder(spec); diff --git a/modules/responsiveAdsBidAdapter.md b/modules/responsiveAdsBidAdapter.md new file mode 100644 index 00000000000..af3b59e80e3 --- /dev/null +++ b/modules/responsiveAdsBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +```markdown +Module Name: responsiveAdsBidAdapter +Module Type: Bidder Adapter +Maintainer: support@responsiveads.com +``` + + +# Description +Module that connects to ResponsiveAds Programmatic Fluid demand. + + +## Running the code +To view an example of the on page setup required: + +```bash +gulp serve-and-test --file test/spec/modules/responsiveAdsBidAdapter_spec.js +``` + +# Test Parameters +``` +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'responsiveads', + params: {} + }] + +}]; +``` diff --git a/test/spec/modules/responsiveAdsBidAdapter_spec.js b/test/spec/modules/responsiveAdsBidAdapter_spec.js new file mode 100644 index 00000000000..83202ad0916 --- /dev/null +++ b/test/spec/modules/responsiveAdsBidAdapter_spec.js @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { spec } from 'modules/responsiveAdsBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('responsiveAdsBidAdapter', function() { + let bidRequests; + let bidderRequest; + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + bidRequests = [{ + bidder: 'responsiveads', + params: { + placementId: '1', + }, + adUnitCode: '/3434399/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidId: '123', + auctionId: '456', + bidderRequestId: '789', + transactionId: '123' + }]; + + bidderRequest = { + timeout: 3000, + } + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('Check if bid is valid', function() { + it('Should accept valid bid', function() { + const validBid = { + bidder: 'responsiveads', + params: {}, + }; + + const isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('Should not reject bid if missing placementId', function() { + const validBid = { + bidder: 'responsiveads', + params: {} + }; + + const isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + }); + + describe('Build requests', function () { + it('Should not bit on safeframe', function() { + utils.isSafeFrameWindow.restore(); + sandbox.stub(utils, 'isSafeFrameWindow').returns(true); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.be.null; + }); + + it('Should not bit if cant access window top', function () { + utils.canAccessWindowTop.restore(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.be.null; + }); + + it('Should use POST and have URL', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + expect(request.url).to.exist; + }); + + it('Should add adapter version', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.ext.prebid.adapterVersion).to.exist; + }); + }); + + describe('Handling responses', function() { + it('Should return complete bid response', function() { + const serverResponse = { + body: { + id: 'response-id', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '123', + impid: '123', + price: 0.5, + adm: ``, + nurl: 'https://example.com/win', + crid: '662d13e12e0c567af92d0918', + w: 300, + h: 250, + mediaType: 'banner', + adomain: ['responsiveads.com'], + attr: [1], + cat: ['IAB1'] + } + ] + } + ] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].requestId).to.equal('123'); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['responsiveads.com']); + }); + + it('should return empty bid response', function () { + const emptyServerResponse = { + body: [] + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(emptyServerResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + }); +}); From faace6f6a0ace950a94d2fcb7c6471e0718f5ef9 Mon Sep 17 00:00:00 2001 From: Sharon Green Date: Mon, 16 Dec 2024 15:29:55 +0200 Subject: [PATCH 0763/1097] Bridgeupp Bidder Adapter : initial release (#12549) * Bridgeupp Bidder Adapter: initial release * update referance to bidder from spec for helo world testing e2e * update server endpoint for performance --- modules/bridgeuppBidAdapter.js | 285 ++++ modules/bridgeuppBidAdapter.md | 39 + test/spec/modules/bridgeuppBidAdapter_spec.js | 1141 +++++++++++++++++ 3 files changed, 1465 insertions(+) create mode 100644 modules/bridgeuppBidAdapter.js create mode 100644 modules/bridgeuppBidAdapter.md create mode 100644 test/spec/modules/bridgeuppBidAdapter_spec.js diff --git a/modules/bridgeuppBidAdapter.js b/modules/bridgeuppBidAdapter.js new file mode 100644 index 00000000000..6adb2fea220 --- /dev/null +++ b/modules/bridgeuppBidAdapter.js @@ -0,0 +1,285 @@ +import {deepSetValue, isFn, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + +export const ADAPTER_VERSION = 1.0; +export const BIDDER_CODE = 'sonarads'; +export const GVLID = 1300; +export const DEFAULT_CUR = 'USD'; +export const SERVER_PATH_US1_BID = 'https://prebidjs-bids-us1.sonar-ads.com/analyze_request/bids'; +export const SERVER_PATH_US1_EVENTS = 'https://prebidjs-events-us1.sonar-ads.com/events'; +export const SERVER_PATH_US1_SYNC = 'https://prebidjs-sync-us1.sonar-ads.com/sync'; + +/** + * Bridgeupp : Report events for analytics and debuging. + */ +function reportEvents(eventType, eventData) { + if (!eventData || spec?.reportEventsEnabled !== true) { + return; + } + + const payload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: eventType, + eventPayload: eventData + }); + + fetch(`${SERVER_PATH_US1_EVENTS}`, { + body: payload, + keepalive: true, + credentials: 'include', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }).catch((_e) => { + // ignore errors for now + }); +} + +/** + * Bridgeupp : Defines the core oRTB converter inherited from converter library and all customization functions. + */ +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: DEFAULT_CUR + }, + imp, + request, + bidResponse, + response +}); + +/** + * Bridgeupp : Builds an impression object for oRTB requests based on the bid request. + * + * @param {function} buildImp - Function to build the imp object. + * @param {Object} bidRequest - The request containing bid details. + * @param {Object} context - Context for the impression. + * @returns {Object} The constructed impression object. + */ +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const params = bidRequest.params; + + imp.tagid = bidRequest.adUnitCode; + let floorInfo = {}; + + if (isFn(bidRequest.getFloor)) { + floorInfo = bidRequest.getFloor(); + } + + // if floor price module is not set reading from bidRequest.params or default + if (!imp.bidfloor) { + imp.bidfloor = bidRequest.params.bidfloor || 0.001; + imp.bidfloorcur = DEFAULT_CUR; + } + + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; + deepSetValue(imp, 'ext', { + ...imp.ext, + params: bidRequest.params, + bidder: { + siteId: params?.siteId, + }, + floorInfo: floorInfo + }); + + return imp; +} + +/** + * Bridgeupp: Constructs the request object. + * + * @param {function} buildRequest - Function to build the request. + * @param {Array} imps - Array of impression objects. + * @param {Object} bidderRequest - Object containing bidder request information. + * @param {Object} context - Additional context. + * @returns {Object} The complete oRTB request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + const siteId = context.bidRequests[0]?.params?.siteId; + + deepSetValue(request, 'ext.prebid.channel', { + name: 'pbjs_bridgeupp', + pbjsversion: '$prebid.version$', + adapterversion: ADAPTER_VERSION, + siteId: siteId + }); + + return request; +} + +function bidResponse(buildBidResponse, bid, context) { + return buildBidResponse(bid, context); +} + +/** + * Bridgeupp bid response + * + * @param {function} buildResponse - Function to build the response. + * @param {Array} bidResponses - List of bid responses. + * @param {Object} ortbResponse - Original oRTB response data. + * @param {Object} context - Additional context. + * @returns {Object} Prebid.js compatible bid response. + */ +function response(buildResponse, bidResponses, ortbResponse, context) { + return buildResponse(bidResponses, ortbResponse, context); +} + +export const spec = { + reportEventsEnabled: false, + code: BIDDER_CODE, + aliases: ['bridgeupp'], + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + /** + * Bridgeupp : Determines whether the given bid request is valid. + * + * @param {Object} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + if (!bid || bid.bidder !== BIDDER_CODE || !bid.params.siteId) { + logWarn('Bridgeupp - bid is not valid, reach out to support@bridgeupp.com'); + return false; + } + return true; + }, + + /** + * Bridgeupp: Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {Object} bidderRequest - Additional request details. + * @return {ServerRequest} Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + const data = CONVERTER.toORTB({ bidderRequest: bidderRequest, bidRequests: validBidRequests, }); + + if (data) { + return { + method: 'POST', + url: SERVER_PATH_US1_BID, + data: data, + options: { + contentType: 'application/json', + crossOrigin: true, + withCredentials: true + } + }; + } + }, + + /** + * Bridgeupp: Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {ServerRequest} bidRequest - Original bid request. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + if (typeof serverResponse?.body == 'undefined') { + return []; + } + + // reportEventsEnabled is returned from the server default false + spec.reportEventsEnabled = serverResponse.headers.get('reportEventsEnabled') > 0 + + const interpretedResponse = CONVERTER.fromORTB({ response: serverResponse.body, request: bidRequest.data }); + return interpretedResponse.bids || []; + }, + + /** + * Bridgeupp : User sync options based on consent, support only iframe for now. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + logWarn('Bridgeupp - Bidder ConnectAd: No User-Matching allowed'); + return []; + } + + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SERVER_PATH_US1_SYNC + '?'; + + syncUrl = gdprConsent ? tryAppendQueryString(syncUrl, 'gdpr', gdprConsent.gdprApplies ? 1 : 0) : syncUrl; + syncUrl = gdprConsent?.consentString ? tryAppendQueryString(syncUrl, 'gdpr_consent', gdprConsent.consentString) : syncUrl; + syncUrl = uspConsent ? tryAppendQueryString(syncUrl, 'us_privacy', uspConsent) : syncUrl; + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl = tryAppendQueryString(syncUrl, 'gpp', gppConsent.gppString); + syncUrl = tryAppendQueryString(syncUrl, 'gpp_sid', gppConsent.applicableSections.join(',')); + } + + if ((syncUrl.slice(-1) === '&') || (syncUrl.slice(-1) === '?')) { + syncUrl = syncUrl.slice(0, -1); + } + + return [{ + type: pixelType, + url: syncUrl + }]; + }, + + /** + * Bridgeupp: Callback to report timeout event. + * + * @param {TimedOutBid[]} timeoutData - Array of timeout details. + */ + onTimeout: (timeoutData) => { + reportEvents('onTimeout', timeoutData); + }, + + /** + * Bridgeupp: Callback to report targeting event. + * + * @param {Bid} bid - The bid object + */ + onSetTargeting: (bid) => { + reportEvents('onSetTargeting', bid); + }, + + /** + * Bridgeupp: Callback to report successful ad render event. + * + * @param {Bid} bid - The bid that successfully rendered. + */ + onAdRenderSucceeded: (bid) => { + reportEvents('onAdRenderSucceeded', bid); + }, + + /** + * Bridgeupp: Callback to report bidder error event. + * + * @param {Object} errorData - Details about the error. + */ + onBidderError: (errorData) => { + reportEvents('onBidderError', errorData); + }, + + /** + * Bridgeupp: Callback to report bid won event. + * + * @param {Bid} bid - The bid that won the auction. + */ + onBidWon: (bid) => { + reportEvents('onBidWon', bid); + } + +}; + +registerBidder(spec); diff --git a/modules/bridgeuppBidAdapter.md b/modules/bridgeuppBidAdapter.md new file mode 100644 index 00000000000..3e8fa62f1d8 --- /dev/null +++ b/modules/bridgeuppBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +Module Name: Bridgeupp Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@bridgeupp.com + +# Description + +Module that connects to Bridgeupp's demand sources. + +# Bid Params + +| Name | Scope | Description | Example | Type | +|---------------|----------|----------------------|----------|----------| +| `siteId` | required | Placement ID | `'1234'` | `string` | +| `bidfloor` | optional | Minimum price in USD | `'1.50'` | `float` | + +# Test Parameters + +## Banner +``` + var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 336]] + } + }, + + bids: [{ + bidder: 'sonarads', + params: { + siteId: 'site-id-example-132', // siteId provided by Bridgeupp + bidfloor: 0.01 + } + }] + + }]; +``` diff --git a/test/spec/modules/bridgeuppBidAdapter_spec.js b/test/spec/modules/bridgeuppBidAdapter_spec.js new file mode 100644 index 00000000000..0a786a7e9ec --- /dev/null +++ b/test/spec/modules/bridgeuppBidAdapter_spec.js @@ -0,0 +1,1141 @@ +import { expect } from 'chai'; +import { + spec, BIDDER_CODE, SERVER_PATH_US1_SYNC, SERVER_PATH_US1_EVENTS +} from '../../../modules/bridgeuppBidAdapter.js'; +import * as utils from 'src/utils.js'; +import * as ajax from 'src/ajax.js'; +import { hook } from '../../../src/hook'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/priceFloors.js'; +import sinon from 'sinon'; + +// constants +const SITE_DOMAIN_NAME = 'sonargames.com'; +const SITE_PAGE = 'https://sonargames.com'; +describe('bridgeuppBidAdapter_spec', function () { + let utilsMock, sandbox, ajaxStub, fetchStub; + + afterEach(function () { + sandbox.restore(); + utilsMock.restore(); + ajaxStub.restore(); + fetchStub.restore(); + }); + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + utilsMock = sinon.mock(utils); + ajaxStub = sandbox.stub(ajax, 'ajax'); + fetchStub = sinon.stub(global, 'fetch').resolves(new Response('OK')); + }); + + describe('isBidRequestValid', function () { + let bid; + beforeEach(function () { + bid = { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': 'site-id-12', + } + }; + }); + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when missing bidder', function () { + delete bid.bidder; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when bidder is not valid', function () { + bid.bidder = 'invalid-bidder'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing siteId', function () { + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + before(() => { + hook.ready(); + }) + afterEach(function () { + config.resetConfig(); + }); + const bidderRequest = { + refererInfo: { + page: 'https://sonarads.com/home', + ref: 'https://referrer' + }, + timeout: 1000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '1300': 1 + }, + }, + apiVersion: 1, + }, + }; + + it('request should build with correct siteId', function () { + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + 'siteId': 'site-id-12' + } + }, + ]; + + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].ext.bidder.siteId).to.deep.equal('site-id-12'); + }); + + it('request should build with correct imp', function () { + const expectedMetric = { + url: 'https://sonarads.com' + } + const bidRequests = [{ + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + ortb2Imp: { + instl: 1, + metric: expectedMetric, + ext: { + gpid: 'gpid_sonarads' + }, + rwdd: 1 + }, + params: { + siteId: 'site-id-12', + bidfloor: 2.12, + bidfloorcur: 'USD' + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].id).to.deep.equal('bidId'); + expect(ortbRequest.imp[0].tagid).to.deep.equal('impId'); + expect(ortbRequest.imp[0].instl).to.equal(1); + expect(ortbRequest.imp[0].bidfloor).to.equal(2.12); + expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); + expect(ortbRequest.imp[0].metric).to.deep.equal(expectedMetric); + expect(ortbRequest.imp[0].ext.gpid).to.equal('gpid_sonarads'); + expect(ortbRequest.imp[0].secure).to.equal(1); + expect(ortbRequest.imp[0].rwdd).to.equal(1); + }); + + it('request should build with proper site data', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortb2 = { + site: { + name: SITE_DOMAIN_NAME, + domain: SITE_DOMAIN_NAME, + keywords: 'keyword1, keyword2', + cat: ['IAB2'], + pagecat: ['IAB3'], + sectioncat: ['IAB4'], + page: SITE_PAGE, + ref: 'google.com', + privacypolicy: 1, + content: { + url: SITE_PAGE + '/games1' + } + } + }; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.site.domain).to.equal(SITE_DOMAIN_NAME); + expect(ortbRequest.site.publisher.domain).to.equal('sonarads.com'); + expect(ortbRequest.site.page).to.equal(SITE_PAGE); + expect(ortbRequest.site.name).to.equal(SITE_DOMAIN_NAME); + expect(ortbRequest.site.keywords).to.equal('keyword1, keyword2'); + expect(ortbRequest.site.cat).to.deep.equal(['IAB2']); + expect(ortbRequest.site.pagecat).to.deep.equal(['IAB3']); + expect(ortbRequest.site.sectioncat).to.deep.equal(['IAB4']); + expect(ortbRequest.site.ref).to.equal('google.com'); + expect(ortbRequest.site.privacypolicy).to.equal(1); + expect(ortbRequest.site.content.url).to.equal(SITE_PAGE + '/games1') + }); + + it('request should build with proper device data', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortb2 = { + device: { + dnt: 1, + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36', + ip: '203.0.113.42', + h: 800, + w: 1280, + language: 'fr', + lmt: 0, + js: 0, + connectiontype: 2, + hwv: 'iPad', + model: 'Pro', + mccmnc: '234-030', + geo: { + lat: 48.8566, + lon: 2.3522 + } + } + }; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.device.dnt).to.equal(1); + expect(ortbRequest.device.lmt).to.equal(0); + expect(ortbRequest.device.js).to.equal(0); + expect(ortbRequest.device.connectiontype).to.equal(2); + expect(ortbRequest.device.ua).to.equal('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'); + expect(ortbRequest.device.ip).to.equal('203.0.113.42'); + expect(ortbRequest.device.h).to.equal(800); + expect(ortbRequest.device.w).to.equal(1280); + expect(ortbRequest.device.language).to.deep.equal('fr'); + expect(ortbRequest.device.hwv).to.deep.equal('iPad'); + expect(ortbRequest.device.model).to.deep.equal('Pro'); + expect(ortbRequest.device.mccmnc).to.deep.equal('234-030'); + expect(ortbRequest.device.geo.lat).to.deep.equal(48.8566); + expect(ortbRequest.device.geo.lon).to.deep.equal(2.3522); + }); + + it('should properly build a request with source object', function () { + const expectedSchain = { id: 'prebid' }; + const ortb2 = { + source: { + pchain: 'sonarads', + schain: expectedSchain + } + }; + const bidRequests = [ + { + bidder: 'sonarads', + bidId: 'bidId', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.source.schain).to.deep.equal(expectedSchain); + expect(ortbRequest.source.pchain).to.equal('sonarads'); + }); + + it('should properly user object', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + } + }, + ]; + const br = { + ...bidderRequest, + ortb2: { + user: { + yob: 2012, + keyowrds: 'test test', + gender: 'M', + customdata: 'test no', + geo: { + lat: 48.8566, + lon: 2.3522 + }, + ext: { + eids: [ + { + source: 'sonarads.com', + uids: [{ + id: 'iid', + atype: 1 + }] + } + ] + } + } + } + } + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const ortbRequest = request.data; + expect(ortbRequest.user.yob).to.deep.equal(2012); + expect(ortbRequest.user.keyowrds).to.deep.equal('test test'); + expect(ortbRequest.user.gender).to.deep.equal('M'); + expect(ortbRequest.user.customdata).to.deep.equal('test no'); + expect(ortbRequest.user.geo.lat).to.deep.equal(48.8566); + expect(ortbRequest.user.geo.lon).to.deep.equal(2.3522); + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'sonarads.com', + uids: [{ + id: 'iid', + atype: 1 + }] + } + ]); + }); + + it('should properly build a request regs object', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortb2 = { + regs: { + coppa: 1, + gpp: 'consent_string', + gpp_sid: [0, 1, 2], + us_privacy: 'yes us privacy applied' + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.coppa).to.equal(1); + expect(ortbRequest.regs.gpp).to.equal('consent_string'); + expect(ortbRequest.regs.gpp_sid).to.deep.equal([0, 1, 2]); + expect(ortbRequest.regs.us_privacy).to.deep.equal('yes us privacy applied'); + }); + + it('gdpr test', function () { + // using privacy params from global bidder Request + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.ext.gdpr).to.deep.equal(1); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + }); + + it('should properly set tmax if available', function () { + // using tmax from global bidder Request + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'bid-1', + transactionId: 'trans-1', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); + }); + + it('should properly build a request with bcat field', function () { + const bcat = ['IAB1', 'IAB2']; + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const bidderRequest = { + ortb2: { + bcat + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bcat).to.deep.equal(bcat); + }); + + it('should properly build a request with badv field', function () { + const badv = ['nike.com']; + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const bidderRequest = { + ortb2: { + badv + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.badv).to.deep.equal(badv); + }); + + it('should properly build a request with bapp field', function () { + const bapp = ['nike.com']; + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const bidderRequest = { + ortb2: { + bapp + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bapp).to.deep.equal(bapp); + }); + + it('banner request test', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 250]], + pos: 1, + topframe: 0, + } + }, + params: { + siteId: 'site-id-12' + }, + ortb2Imp: { + banner: { + api: [1, 2, 3], + mimes: ['image/jpg', 'image/gif'] + } + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[0].banner.pos).to.equal(1); + expect(ortbRequest.imp[0].banner.topframe).to.equal(0); + expect(ortbRequest.imp[0].banner.api).to.deep.equal([1, 2, 3]); + expect(ortbRequest.imp[0].banner.mimes).to.deep.equal(['image/jpg', 'image/gif']); + }); + + it('banner request test with sizes > 1', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[336, 336], [720, 50]] + } + }, + params: { + siteId: 'site-id-12' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(336); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(336); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(720); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(50); + }); + + it('should properly build a request when coppa is true', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: true }); + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(1); + }); + + it('should properly build a request when coppa is false', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: false }); + let buildRequests = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = buildRequests.data; + expect(ortbRequest.regs.coppa).to.equal(0); + }); + + it('should properly build a request when coppa is not defined', function () { + const bidRequests = []; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs?.coppa).to.be.undefined; + }); + + it('build a banner request with bidFloor', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50], [720, 90]] + } + }, + params: { + siteId: 'site-id-12', + bidfloor: 1, + bidfloorcur: 'USD' + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1); + expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD'); + }); + + it('build a banner request with getFloor', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + getFloor: () => { + return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).equal(1.23); + expect(ortbRequest.imp[0].bidfloorcur).equal('USD'); + }); + }); + + describe('interpretResponse', function () { + const bidderRequest = { + refererInfo: { + page: 'https://sonarads.com/home', + ref: 'https://referrer' + }, + timeout: 1000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '1300': 1 + }, + }, + apiVersion: 1, + }, + } + + function mockResponse(bidId, mediaType) { + return { + id: 'sonarads-response-id-hash-123123', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'sonarads-seatbid-bid-id-hash-123qaasd34', + impid: bidId, + price: 1.12, + adomain: ['advertiserDomain.sandbox.sonarads.com'], + crid: 'd3868d0b2f43c11e4bd60333e5e5ee8be471c4061b3b0cd454539c7edb68a1ca', + w: 320, + h: 250, + adm: 'test-ad', + mtype: mediaType, + nurl: 'https://et-l.w.sonarads.com/c.asm/', + api: 3, + cat: [], + ext: { + prebid: { + meta: { + advertiserDomains: ['advertiserDomain.sandbox.sonarads.com'], + networkName: 'sonarads' + } + } + } + } + ] + } + ], + ext: { + prebidjs: { + urls: [ + { url: 'https://sync.sonarads.com/prebidjs' }, + { url: 'https://eus.rubiconproject.com/usync.html?p=test' } + ] + } + } + } + } + + it('should returns an empty array when bid response is empty', function () { + const bidRequests = []; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function () { + return undefined + } + }, + body: {} + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(interpretedBids).to.have.lengthOf(0); + expect(spec.reportEventsEnabled).to.equal(false); + }); + + it('should return an empty array when there is no bid response', function () { + const bidRequests = []; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function () { + return undefined + } + }, + body: { seatbid: [] } + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(interpretedBids).to.have.lengthOf(0); + expect(spec.reportEventsEnabled).to.equal(false); + }); + + it('return banner response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function () { + return undefined + } + }, + body: mockResponse('bidId', 1) + }; + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(spec.reportEventsEnabled).to.equal(false); + expect(interpretedBids).to.have.length(1); + expect(interpretedBids[0].currency).to.deep.equal('USD'); + expect(interpretedBids[0].mediaType).to.deep.equal('banner'); + expect(interpretedBids[0].requestId).to.deep.equal('bidId'); + expect(interpretedBids[0].seatBidId).to.deep.equal('sonarads-seatbid-bid-id-hash-123qaasd34'); + expect(interpretedBids[0].cpm).to.equal(1.12); + expect(interpretedBids[0].width).to.equal(320); + expect(interpretedBids[0].height).to.equal(250); + expect(interpretedBids[0].creativeId).to.deep.equal('d3868d0b2f43c11e4bd60333e5e5ee8be471c4061b3b0cd454539c7edb68a1ca'); + expect(interpretedBids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.sonarads.com'); + }); + + it('should set the reportEventsEnabled to true as part of the response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + siteId: 'site-id-12', + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function (header) { + if (header === 'reportEventsEnabled') { + return 1; + } + } + }, + body: mockResponse('bidId2', 1) + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(spec.reportEventsEnabled).to.equal(true); + expect(interpretedBids).to.have.length(1); + }); + + it('bid response when banner wins among two ad units', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + siteId: 'site-id-12', + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: { + siteId: 'site-id-12', + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function (header) { + if (header === 'reportEventsEnabled') { + return 1; + } + } + }, + body: mockResponse('bidId2', 1) + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(spec.reportEventsEnabled).to.equal(true); + expect(interpretedBids[0].currency).to.deep.equal('USD'); + expect(interpretedBids[0].mediaType).to.deep.equal('banner'); + expect(interpretedBids[0].requestId).to.deep.equal('bidId2'); + expect(interpretedBids[0].cpm).to.equal(1.12); + expect(interpretedBids[0].seatBidId).to.deep.equal('sonarads-seatbid-bid-id-hash-123qaasd34'); + expect(interpretedBids[0].creativeId).to.deep.equal('d3868d0b2f43c11e4bd60333e5e5ee8be471c4061b3b0cd454539c7edb68a1ca'); + expect(interpretedBids[0].width).to.equal(320); + expect(interpretedBids[0].height).to.equal(250); + expect(interpretedBids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.sonarads.com'); + }); + }); + + describe('getUserSyncs', function () { + it('should return empty if iframe disabled and pixelEnabled is false', function () { + const res = spec.getUserSyncs({}); + expect(res).to.deep.equal([]); + }); + + it('should return user sync if iframe enabled is true', function () { + const res = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr not present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr = 1 and gdpr consent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}?gdpr=1&gdpr_consent=GDPR_CONSENT` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr = 1 , gdpr consent present and usp_consent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }, 'USP_CONSENT'); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` }]); + }); + + it('should return user sync if iframe enabled is true usp_consent present and gppConsent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: undefined }, 'USP_CONSENT', { gppString: 'GPP_STRING', applicableSections: [32, 51] }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}?us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51` }]); + }); + }); + + describe('onTimeout', function () { + it('onTimeout function should be defined', function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + + it('should invoke onTimeout, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onTimeout(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onTimeout', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onTimeout should not be called if the bid data is null', function () { + // Call onTimeout with null data + spec.onTimeout(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onSetTargeting', function () { + it('onSetTargeting function should be defined', function () { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function'); + }); + + it('should invoke onSetTargeting, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onSetTargeting(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onSetTargeting', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onSetTargeting should not be called if the bid data is null', function () { + // Call onSetTargeting with null data + spec.onSetTargeting(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onAdRenderSucceeded', function () { + it('onAdRenderSucceeded function should be defined', function () { + expect(spec.onAdRenderSucceeded).to.exist.and.to.be.a('function'); + }); + + it('should invoke onAdRenderSucceeded, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + + spec.reportEventsEnabled = true; + spec.onAdRenderSucceeded(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onAdRenderSucceeded', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onAdRenderSucceeded should not be called if the bid data is null', function () { + // Call onAdRenderSucceeded with null data + spec.onAdRenderSucceeded(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onBidderError', function () { + it('onBidderError function should be defined', function () { + expect(spec.onBidderError).to.exist.and.to.be.a('function'); + }); + + it('should invoke onBidderError, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onBidderError(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onBidderError', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onBidderError should not be called if the bid data is null', function () { + // Call onBidderError with null data + spec.onBidderError(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onBidWon', function () { + it('onBidWon function should be defined', function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('should invoke onBidWon, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onBidWon(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onBidWon', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onBidWon should not be called if the bid data is null', function () { + // Call onBidWon with null data + spec.onBidWon(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); +}); From cb1dc740083a9cb61689d148e570ab468b03ee57 Mon Sep 17 00:00:00 2001 From: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:46:11 +0200 Subject: [PATCH 0764/1097] AcuityAds Bid Adapter: add gvlid (#12581) * add prebid.js adapter * changes * changes * changes * changes * fix downolad * add gpp * Merge remote-tracking branch 'prebid/master' * add gvlid --- modules/acuityadsBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index bd85d2b3cc0..3caaacb46a4 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -3,11 +3,13 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'acuityads'; +const GVLID = 231; const AD_URL = 'https://prebid.admanmedia.com/pbjs'; const SYNC_URL = 'https://cs.admanmedia.com'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(['placementId']), From 28b44c94ff009639d4c7022b9ea0ffba91141ac6 Mon Sep 17 00:00:00 2001 From: Oleksandr Solodovnikov Date: Mon, 16 Dec 2024 17:37:12 +0200 Subject: [PATCH 0765/1097] Use credentials in requests in Aniview Bid Adapter (#12579) --- modules/aniviewBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index e5baac1add0..82c77bfafb5 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -126,7 +126,7 @@ export const spec = { method: 'POST', url: endpoint, bids: [bidRequest], - options: { withCredentials: false }, + options: { withCredentials: true }, data: converter.toORTB({ bidderRequest, bidRequests: [bidRequest], From 2106a40c502ad760b14438cc1f2f5131ebbb8007 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Tue, 17 Dec 2024 05:10:14 +1100 Subject: [PATCH 0766/1097] Add screen and viewport to ad request (#12553) --- modules/adnuntiusBidAdapter.js | 9 ++++++++- test/spec/modules/adnuntiusBidAdapter_spec.js | 11 +++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 2747000c416..ffd02e43a99 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js'; +import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -244,6 +244,13 @@ export const spec = { queryParamsAndValues.push('consentString=' + consentString); queryParamsAndValues.push('gdpr=' + flag); } + const win = getWindowTop() || window; + if (win.screen && win.screen.availHeight) { + queryParamsAndValues.push('screen=' + win.screen.availWidth + 'x' + win.screen.availHeight); + } + if (win.innerWidth) { + queryParamsAndValues.push('viewport=' + win.innerWidth + 'x' + win.innerHeight); + } const searchParams = new URLSearchParams(window.location.search); if (searchParams.has('script-override')) { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 7814c24f4ac..c82671040d0 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -6,7 +6,7 @@ import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { getStorageManager } from 'src/storageManager.js'; import { getGlobal } from '../../../src/prebidGlobal'; -import {getUnixTimestampFromNow} from 'src/utils.js'; +import {getUnixTimestampFromNow, getWindowTop} from 'src/utils.js'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; @@ -48,11 +48,14 @@ describe('adnuntiusBidAdapter', function () { }); const tzo = new Date().getTimezoneOffset(); - const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`; + const win = getWindowTop() || window; + const screen = win.screen.availWidth + 'x' + win.screen.availHeight; + const viewport = win.innerWidth + 'x' + win.innerHeight; + const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid&screen=${screen}&viewport=${viewport}`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&screen=${screen}&viewport=${viewport}&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ @@ -670,7 +673,7 @@ describe('adnuntiusBidAdapter', function () { const bid = request[0].bid[0] expect(bid).to.have.property('bidId'); expect(request[0]).to.have.property('url'); - expect(request[0].url).to.equal(ENDPOINT_URL.replace('format=prebid', 'format=prebid&so=overridden-value')); + expect(request[0].url).to.equal(ENDPOINT_URL.replace('&userId', '&so=overridden-value&userId')); expect(request[0]).to.have.property('data'); expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"context":"https://canonical.com/something-else.html","canonical":"https://canonical.com/page.html"}'); }); From 9d8af2849fac244d6f22f377808761d7c26638d4 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Mon, 16 Dec 2024 21:13:06 +0200 Subject: [PATCH 0767/1097] Add first-party data handling to kueezRtbBidAdapter (#12503) --- modules/kueezRtbBidAdapter.js | 76 +++++++++++++------- test/spec/modules/kueezRtbBidAdapter_spec.js | 45 +++++++++++- 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index c0fb17672af..60eced6a490 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -5,7 +5,8 @@ import { createBuildRequestsFn, createInterpretResponseFn, createUserSyncGetter, - isBidRequestValid + isBidRequestValid, + tryParseJSON } from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1165; @@ -14,40 +15,63 @@ const BIDDER_CODE = 'kueezrtb'; const BIDDER_VERSION = '1.0.0'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests: createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false), + interpretResponse: createInterpretResponseFn(BIDDER_CODE, false), + getUserSyncs: createUserSyncGetter({ + iframeSyncUrl: 'https://sync.kueezrtb.com/api/sync/iframe', + imageSyncUrl: 'https://sync.kueezrtb.com/api/sync/image' + }), + createFirstPartyData, +}; + export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.kueezrtb.com`; } -function createUniqueRequestData(hashUrl, bid) { - const { - auctionId, - transactionId, - } = bid; +export function getAndSetFirstPartyData() { + if (!storage.hasLocalStorage()) { + return; + } + let fdata = tryParseJSON(storage.getDataFromLocalStorage('_iiq_fdata')); + if (!fdata) { + fdata = spec.createFirstPartyData(); + storage.setDataInLocalStorage('_iiq_fdata', JSON.stringify(fdata)); + } + return fdata; +} +export function createFirstPartyData() { return { - auctionId, - transactionId, + pcid: getFirstPartyUUID(), pcidDate: Date.now(), }; } -const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); - -const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); - -const getUserSyncs = createUserSyncGetter({ - iframeSyncUrl: 'https://sync.kueezrtb.com/api/sync/iframe', - imageSyncUrl: 'https://sync.kueezrtb.com/api/sync/image' -}); - -export const spec = { - code: BIDDER_CODE, - version: BIDDER_VERSION, - gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs +function getFirstPartyUUID() { + let d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); }; +function createUniqueRequestData(hashUrl, bid) { + const {auctionId, transactionId} = bid; + const fdata = getAndSetFirstPartyData(); + return { + auctionId, + transactionId, + ...(fdata && { + iiqpcid: fdata.pcid, + iiqpcidDate: fdata.pcidDate + }) + }; +} + registerBidder(spec); diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 2df5e299aeb..d3323a205b3 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -2,7 +2,9 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, - storage + storage, + getAndSetFirstPartyData, + createFirstPartyData, } from 'modules/kueezRtbBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; @@ -251,6 +253,7 @@ describe('KueezRtbBidAdapter', function () { describe('build requests', function () { let sandbox; + let createFirstPartyDataStub; before(function () { $$PREBID_GLOBAL$$.bidderSettings = { kueezrtb: { @@ -259,6 +262,10 @@ describe('KueezRtbBidAdapter', function () { }; sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1000); + createFirstPartyDataStub = sandbox.stub(adapter, 'createFirstPartyData').returns({ + pcid: 'pcid', + pcidDate: 1000 + }); }); it('should build video request', function () { @@ -295,6 +302,8 @@ describe('KueezRtbBidAdapter', function () { referrer: 'https://www.somereferrer.com', res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, + iiqpcid: 'pcid', + iiqpcidDate: 1000, sizes: ['545x307'], sua: { 'source': 2, @@ -363,6 +372,8 @@ describe('KueezRtbBidAdapter', function () { auctionId: 'auction_id', bidRequestsCount: 4, bidderRequestsCount: 3, + iiqpcid: 'pcid', + iiqpcidDate: 1000, bidderWinsCount: 1, bidderTimeout: 3000, bidderRequestId: '1fdb5ff1b6eaa7', @@ -668,4 +679,36 @@ describe('KueezRtbBidAdapter', function () { expect(parsed).to.be.equal(value); }); }); + + describe('First party data', () => { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + kueezrtb: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + storage.removeDataFromLocalStorage('_iiq_fdata'); + }) + + it('should create first party data', function () { + const data = createFirstPartyData(); + expect(data).to.have.property('pcid'); + expect(data).to.have.property('pcidDate'); + }); + + it('should get and set first party data', function () { + storage.removeDataFromLocalStorage('_iiq_fdata'); + + const data = getAndSetFirstPartyData(); + expect(data).to.have.property('pcid'); + expect(data).to.have.property('pcidDate'); + + const stored = storage.getDataFromLocalStorage('_iiq_fdata'); + const parsed = tryParseJSON(stored); + expect(parsed).to.deep.equal(data); + }); + }); }); From e2a4631ae256c8d99cead8da46f21156d56dae7e Mon Sep 17 00:00:00 2001 From: ss-toshihide-tajima <89903823+ss-toshihide-tajima@users.noreply.github.com> Date: Tue, 17 Dec 2024 05:57:37 +0900 Subject: [PATCH 0768/1097] AdGeneration Bid Adapter : change endpoint and add ortb converter (#12538) * AdGeneration adapter : use POST Method * AdGeneration adapter : use response results paramster * AdGeneration adapter : fix request parameters * AdGeneration adapter : fix response parameters * AdGeneration adapter : fix imark parameter * AdGeneration adapter : fix exclude invalid novatiq HyperID * AdGeneration adapter : fix creativeId * AdGeneration adapter : add sdktype parameter * AdGeneration adapter : fix deep copy additional parameters * AdGeneration adapter : fix test spec --- modules/adgenerationBidAdapter.js | 262 ++- .../modules/adgenerationBidAdapter_spec.js | 1622 ++++++++++------- 2 files changed, 1085 insertions(+), 799 deletions(-) diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 3ef9e495ea2..95eef114f32 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -3,8 +3,9 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { deepAccess, getBidIdParameter } from '../src/utils.js'; +import { getBidIdParameter, deepSetValue, prefixLog } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +const adgLogger = prefixLog('Adgeneration: '); /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -15,6 +16,31 @@ import { deepAccess, getBidIdParameter } from '../src/utils.js'; */ const ADG_BIDDER_CODE = 'adgeneration'; +const ADGENE_PREBID_VERSION = '1.6.4'; +const DEBUG_URL = 'https://api-test.scaleout.jp/adgen/prebid'; +const URL = 'https://d.socdm.com/adgen/prebid'; + +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 30// default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.params', bidRequest.params); + deepSetValue(imp, 'ext.mediaTypes', bidRequest.mediaTypes); + deepSetValue(imp, 'ext.novatiqSyncResponse', bidRequest?.userId?.novatiq?.snowflake?.syncResponse); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + return request; + }, + bidResponse(buildBidResponse, bid, context) { + return buildBidResponse(bid, context) + } +}); export const spec = { code: ADG_BIDDER_CODE, @@ -36,69 +62,61 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const ADGENE_PREBID_VERSION = '1.6.3'; - let serverRequests = []; - for (let i = 0, len = validBidRequests.length; i < len; i++) { - const validReq = validBidRequests[i]; - const DEBUG_URL = 'https://api-test.scaleout.jp/adsv/v1'; - const URL = 'https://d.socdm.com/adsv/v1'; - const url = validReq.params.debug ? DEBUG_URL : URL; - const criteoId = getCriteoId(validReq); - const id5id = getId5Id(validReq); - const id5LinkType = getId5LinkType(validReq); - const imuid = deepAccess(validReq, 'userId.imuid'); - const gpid = deepAccess(validReq, 'ortb2Imp.ext.gpid'); - const sua = deepAccess(validReq, 'ortb2.device.sua'); - const uid2 = deepAccess(validReq, 'userId.uid2.id'); - let data = ``; - data = tryAppendQueryString(data, 'posall', 'SSPLOC'); - const id = getBidIdParameter('id', validReq.params); - data = tryAppendQueryString(data, 'id', id); - data = tryAppendQueryString(data, 'sdktype', '0'); - data = tryAppendQueryString(data, 'hb', 'true'); - data = tryAppendQueryString(data, 't', 'json3'); - data = tryAppendQueryString(data, 'transactionid', validReq.ortb2Imp?.ext?.tid); - data = tryAppendQueryString(data, 'sizes', getSizes(validReq)); - data = tryAppendQueryString(data, 'currency', getCurrencyType(bidderRequest)); - data = tryAppendQueryString(data, 'pbver', '$prebid.version$'); - data = tryAppendQueryString(data, 'sdkname', 'prebidjs'); - data = tryAppendQueryString(data, 'adapterver', ADGENE_PREBID_VERSION); - data = tryAppendQueryString(data, 'adgext_criteo_id', criteoId); - data = tryAppendQueryString(data, 'adgext_id5_id', id5id); - data = tryAppendQueryString(data, 'adgext_id5_id_link_type', id5LinkType); - data = tryAppendQueryString(data, 'adgext_imuid', imuid); - data = tryAppendQueryString(data, 'adgext_uid2', uid2); - data = tryAppendQueryString(data, 'gpid', gpid); - data = tryAppendQueryString(data, 'uach', sua ? JSON.stringify(sua) : null); - data = tryAppendQueryString(data, 'schain', validReq.schain ? JSON.stringify(validReq.schain) : null); + const ortbObj = converter.toORTB({bidRequests: validBidRequests, bidderRequest}); + adgLogger.logInfo('ortbObj', ortbObj); + const {imp, ...rest} = ortbObj + const requests = imp.map((impObj) => { + const customParams = impObj?.ext?.params; + const id = getBidIdParameter('id', customParams); + const additionalParams = JSON.parse(JSON.stringify(rest)); - // native以外にvideo等の対応が入った場合は要修正 - if (!validReq.mediaTypes || !validReq.mediaTypes.native) { - data = tryAppendQueryString(data, 'imark', '1'); + // hyperIDが有効ではない場合、パラメータから削除する + if (!impObj?.ext?.novatiqSyncResponse || impObj?.ext?.novatiqSyncResponse !== 1) { + if (additionalParams?.user?.ext?.eids && Array.isArray(additionalParams?.user?.ext?.eids)) { + additionalParams.user.ext.eids = additionalParams?.user?.ext?.eids.filter((eid) => eid?.source !== 'novatiq.com'); + } } - data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.page); + let urlParams = ``; + urlParams = tryAppendQueryString(urlParams, 'id', id); + urlParams = tryAppendQueryString(urlParams, 'posall', 'SSPLOC');// not reaquired + urlParams = tryAppendQueryString(urlParams, 'sdktype', '0'); - const hyperId = getHyperId(validReq); - if (hyperId != null) { - data = tryAppendQueryString(data, 'hyper_id', hyperId); + // remove the trailing "&" + if (urlParams.lastIndexOf('&') === urlParams.length - 1) { + urlParams = urlParams.substring(0, urlParams.length - 1); } - // remove the trailing "&" - if (data.lastIndexOf('&') === data.length - 1) { - data = data.substring(0, data.length - 1); + const urlBase = customParams.debug ? (customParams.debug_url ? customParams.debug_url : DEBUG_URL) : URL + const url = `${urlBase}?${urlParams}`; + + let data = { + currency: getCurrencyType(bidderRequest), + pbver: '$prebid.version$', + sdkname: 'prebidjs', + adapterver: ADGENE_PREBID_VERSION, + ortb: { + imp: [impObj], + ...additionalParams + } + } + + // native以外にvideo等の対応が入った場合は要修正 + if (!impObj?.ext?.mediaTypes || !impObj?.ext?.mediaTypes.native) { + data.imark = 1; } - serverRequests.push({ - method: 'GET', + + return { + method: 'POST', url: url, - data: data, - bidRequest: validBidRequests[i], - bidderRequest - }); - } - return serverRequests; + data, + options: { + withCredentials: true, + crossOrigin: true + }, + } + }) + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -108,33 +126,42 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function (serverResponse, bidRequests) { + adgLogger.logInfo('serverResponse', JSON.parse(JSON.stringify(serverResponse))); const body = serverResponse.body; if (!body.results || body.results.length < 1) { return []; } - const bidRequest = bidRequests.bidRequest; + + if (!bidRequests?.data?.ortb?.imp || bidRequests?.data?.ortb?.imp.length < 1) { + return []; + } + + const adResult = body?.results[0]; + const targetImp = bidRequests?.data?.ortb?.imp[0]; + const requestId = targetImp?.id; + const bidResponse = { - requestId: bidRequest.bidId, - cpm: body.cpm || 0, - width: body.w ? body.w : 1, - height: body.h ? body.h : 1, - creativeId: body.creativeid || '', - dealId: body.dealid || '', - currency: getCurrencyFromBidderRequest(bidRequests.bidderRequest), + requestId: requestId, + cpm: adResult.cpm || 0, + width: adResult.w ? adResult.w : 1, + height: adResult.h ? adResult.h : 1, + creativeId: adResult.creativeid || '', + dealId: adResult.dealid || '', + currency: getCurrencyType(bidRequests.bidderRequest), netRevenue: true, - ttl: body.ttl || 10, + ttl: adResult.ttl || 10, }; - if (body.adomain && Array.isArray(body.adomain) && body.adomain.length) { + if (adResult.adomain && Array.isArray(adResult.adomain) && adResult.adomain.length) { bidResponse.meta = { - advertiserDomains: body.adomain + advertiserDomains: adResult.adomain } } - if (isNative(body)) { - bidResponse.native = createNativeAd(body); + if (isNative(adResult)) { + bidResponse.native = createNativeAd(adResult.native, adResult.beaconurl); bidResponse.mediaType = NATIVE; } else { // banner - bidResponse.ad = createAd(body, bidRequest); + bidResponse.ad = createAd(adResult, body?.location_params, targetImp.ext.params, requestId); } return [bidResponse]; }, @@ -152,37 +179,38 @@ export const spec = { } }; -function createAd(body, bidRequest) { - let ad = body.ad; - if (body.vastxml && body.vastxml.length > 0) { - if (isUpperBillboard(body)) { - const marginTop = bidRequest.params.marginTop ? bidRequest.params.marginTop : '0'; - ad = `${createADGBrowserMTag()}${insertVASTMethodForADGBrowserM(body.vastxml, marginTop)}`; +function createAd(adResult, locationPrams, bidParams, requestId) { + adgLogger.logInfo('params', bidParams); + let ad = adResult.ad; + if (adResult.vastxml && adResult.vastxml.length > 0) { + if (isUpperBillboard(locationPrams)) { + const marginTop = bidParams.marginTop ? bidParams.marginTop : '0'; + ad = `${createADGBrowserMTag()}${insertVASTMethodForADGBrowserM(adResult.vastxml, marginTop)}`; } else { - ad = `
${createAPVTag()}${insertVASTMethodForAPV(bidRequest.bidId, body.vastxml)}`; + ad = `
${createAPVTag()}${insertVASTMethodForAPV(requestId, adResult.vastxml)}`; } } - ad = appendChildToBody(ad, body.beacon); + ad = appendChildToBody(ad, adResult.beacon); if (removeWrapper(ad)) return removeWrapper(ad); return ad; } -function isUpperBillboard(body) { - if (body.location_params && body.location_params.option && body.location_params.option.ad_type) { - return body.location_params.option.ad_type === 'upper_billboard'; +function isUpperBillboard(locationParams) { + if (locationParams && locationParams.option && locationParams.option.ad_type) { + return locationParams.option.ad_type === 'upper_billboard'; } return false; } -function isNative(body) { - if (!body) return false; - return body.native_ad && body.native_ad.assets.length > 0; +function isNative(adResult) { + if (!adResult) return false; + return adResult.native && adResult.native.assets.length > 0; } -function createNativeAd(body) { +function createNativeAd(nativeAd, beaconUrl) { let native = {}; - if (body.native_ad && body.native_ad.assets.length > 0) { - const assets = body.native_ad.assets; + if (nativeAd && nativeAd.assets.length > 0) { + const assets = nativeAd.assets; for (let i = 0, len = assets.length; i < len; i++) { switch (assets[i].id) { case 1: @@ -216,11 +244,11 @@ function createNativeAd(body) { break; } } - native.clickUrl = body.native_ad.link.url; - native.clickTrackers = body.native_ad.link.clicktrackers || []; - native.impressionTrackers = body.native_ad.imptrackers || []; - if (body.beaconurl && body.beaconurl != '') { - native.impressionTrackers.push(body.beaconurl); + native.clickUrl = nativeAd.link.url; + native.clickTrackers = nativeAd.link.clicktrackers || []; + native.impressionTrackers = nativeAd.imptrackers || []; + if (beaconUrl && beaconUrl != '') { + native.impressionTrackers.push(beaconUrl); } } return native; @@ -282,26 +310,6 @@ function removeWrapper(ad) { return ad.substr(bodyIndex, lastBodyIndex).replace('', '').replace('', ''); } -/** - * request - * @param validReq request - * @returns {?string} 300x250,320x50... - */ -function getSizes(validReq) { - const sizes = validReq.sizes; - if (!sizes || sizes.length < 1) return null; - let sizesStr = ''; - for (const i in sizes) { - const size = sizes[i]; - if (size.length !== 2) return null; - sizesStr += `${size[0]}x${size[1]},`; - } - if (sizesStr || sizesStr.lastIndexOf(',') === sizesStr.length - 1) { - sizesStr = sizesStr.substring(0, sizesStr.length - 1); - } - return sizesStr; -} - /** * @return {?string} USD or JPY */ @@ -310,32 +318,4 @@ function getCurrencyType(bidderRequest) { return adServerCurrency.toUpperCase() === 'USD' ? 'USD' : 'JPY' } -/** - * - * @param validReq request - * @return {null|string} - */ -function getCriteoId(validReq) { - return (validReq.userId && validReq.userId.criteoId) ? validReq.userId.criteoId : null -} - -function getId5Id(validReq) { - return validId5(validReq) ? validReq.userId.id5id.uid : null -} - -function getId5LinkType(validReq) { - return validId5(validReq) ? validReq.userId.id5id.ext.linkType : null -} - -function validId5(validReq) { - return validReq.userId && validReq.userId.id5id && validReq.userId.id5id.uid && validReq.userId.id5id.ext.linkType -} - -function getHyperId(validReq) { - if (validReq.userId && validReq.userId.novatiq && validReq.userId.novatiq.snowflake.syncResponse === 1) { - return validReq.userId.novatiq.snowflake.id; - } - return null; -} - registerBidder(spec); diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index c0bb40a1bf2..f49b600c1bf 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -2,14 +2,15 @@ import {expect} from 'chai'; import {spec} from 'modules/adgenerationBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {NATIVE} from 'src/mediaTypes.js'; -import {config} from 'src/config.js'; import prebid from '../../../package.json'; import { setConfig as setCurrencyConfig } from '../../../modules/currency'; import { addFPDToBidderRequest } from '../../helpers/fpd'; describe('AdgenerationAdapter', function () { const adapter = newBidder(spec); - const ENDPOINT = ['https://api-test.scaleout.jp/adsv/v1', 'https://d.socdm.com/adsv/v1']; + const ADGENE_PREBID_VERSION = '1.6.4'; + const ENDPOINT_STG = 'https://api-test.scaleout.jp/adgen/prebid'; + const ENDPOINT_RELEASE = 'https://d.socdm.com/adgen/prebid'; describe('inherited functions', function () { it('exists and is a function', function () { @@ -37,15 +38,45 @@ describe('AdgenerationAdapter', function () { }); describe('buildRequests', function () { + const suaSample = { + source: 2, + platform: { + brand: 'macOS' + }, + browsers: [ + { + brand: 'Chromium', + version: ['112'] + }, + { + brand: 'Google Chrome', + version: ['112'] + }, + { + brand: 'Not:A-Brand', + version: ['99'] + } + ], + mobile: 0 + }; + const schainSmaple = {ver: '1.0', complete: 1, nodes: [{asi: 'indirectseller.com', sid: '00001', hp: 1}]}; const bidRequests = [ { // banner bidder: 'adg', params: { id: '58278', - currency: 'JPY', }, adUnitCode: 'adunit-code', - sizes: [[300, 250], [320, 100]], + mediaTypes: { + banner: { + sizes: [ + [ + 300, + 250 + ] + ] + } + }, bidId: '2f6ac468a9c15e', bidderRequestId: '14a9f773e30243', auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', @@ -55,7 +86,6 @@ describe('AdgenerationAdapter', function () { bidder: 'adg', params: { id: '58278', - currency: 'JPY', }, mediaTypes: { native: { @@ -154,30 +184,9 @@ describe('AdgenerationAdapter', function () { dnt: 0, ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,like Gecko)Chrome / 112.0.0.0Safari / 537.36', language: 'ja', - sua: { - source: 2, - platform: { - brand: 'macOS' - }, - browsers: [ - { - brand: 'Chromium', - version: ['112'] - }, - { - brand: 'Google Chrome', - version: ['112'] - }, - { - brand: 'Not:A-Brand', - version: ['99'] - } - ], - mobile: 0 - } + sua: suaSample } }, - schain: {ver: '1.0', complete: 1, nodes: [{asi: 'indirectseller.com', sid: '00001', hp: 1}]} } ]; const bidderRequest = { @@ -185,156 +194,786 @@ describe('AdgenerationAdapter', function () { page: 'https://example.com' } }; - const data = { - banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&imark=1&tp=https%3A%2F%2Fexample.com`, - bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&imark=1&tp=https%3A%2F%2Fexample.com`, - native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&tp=https%3A%2F%2Fexample.com`, - bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`, - bannerWithAdgextCriteoId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&adgext_criteo_id=criteo-id-test-1234567890&imark=1&tp=https%3A%2F%2Fexample.com`, - bannerWithAdgextIds: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&adgext_id5_id=id5-id-test-1234567890&adgext_id5_id_link_type=2&adgext_imuid=i.KrAH6ZAZTJOnH5S4N2sogA&adgext_uid2=AgAAAAVacu1uAxgAxH%2BHJ8%2BnWlS2H4uVqr6i%2BHBDCNREHD8WKsio%2Fx7D8xXFuq1cJycUU86yXfTH9Xe%2F4C8KkH%2B7UCiU7uQxhyD7Qxnv251pEs6K8oK%2BBPLYR%2B8BLY%2FsJKesa%2FkoKwx1FHgUzIBum582tSy2Oo%2B7C6wYUaaV4QcLr%2F4LPA%3D&gpid=%2F1111%2Fhomepage%23300x250&uach=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22macOS%22%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Not%3AA-Brand%22%2C%22version%22%3A%5B%2299%22%5D%7D%5D%2C%22mobile%22%3A0%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22indirectseller.com%22%2C%22sid%22%3A%2200001%22%2C%22hp%22%3A1%7D%5D%7D&imark=1&tp=https%3A%2F%2Fexample.com`, - }; - it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.url).to.equal(ENDPOINT[1]); - expect(request.method).to.equal('GET'); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + // check banner request + for (const req of request) { + const url = new URL(req.url); + expect(url.origin + url.pathname).to.equal(ENDPOINT_RELEASE); + } }); - it('sends bid request to debug ENDPOINT via GET', function () { - bidRequests[0].params.debug = true; - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.url).to.equal(ENDPOINT[0]); - expect(request.method).to.equal('GET'); + it('sends bid request to debug ENDPOINT via POST', function () { + // change the first bidRequest to debug mode + const copyBidRequests = JSON.parse(JSON.stringify(bidRequests)); + for (const copyBid of copyBidRequests) { + copyBid.params.debug = true + } + // check banner request + const request = spec.buildRequests(copyBidRequests, bidderRequest); + for (const req of request) { + const url = new URL(req.url); + expect(url.origin + url.pathname).to.equal(ENDPOINT_STG); + } }); it('should attache params to the banner request', function () { + const expectedMediaTypes = { + banner: { + sizes: [ + [ + 300, + 250 + ] + ] + } + } + const expectedBanner = { + topframe: 0, + format: [ + { + w: 300, + h: 250 + } + ] + } const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.data).to.equal(data.banner); + // check banner request + const url = new URL(request.url); + expect(url.searchParams.get('posall')).equal('SSPLOC'); + expect(url.searchParams.get('id')).equal('58278'); + expect(url.searchParams.get('sdktype')).equal('0'); + expect(request.method).to.equal('POST'); + + // check request data + expect(request.data.currency).to.equal('JPY'); + expect(request.data.pbver).to.equal(prebid.version); + expect(request.data.sdkname).to.equal('prebidjs'); + expect(request.data.adapterver).to.equal(ADGENE_PREBID_VERSION); + expect(request.data.imark).to.equal(1); + expect(request.data.ortb.imp[0].id).to.equal('2f6ac468a9c15e'); + expect(request.data.ortb.imp[0].ext.params.id).to.equal('58278'); + expect(request.data.ortb.imp[0].ext.mediaTypes).to.deep.equal(expectedMediaTypes); + expect(request.data.ortb.imp[0].banner).to.deep.equal(expectedBanner); }); it('should attache params to the native request', function () { + const expectedMediaTypes = { + native: { + image: { + required: true + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + }, + icon: { + required: true + } + } + } const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - expect(request.data).to.equal(data.native); + // check native request + const url = new URL(request.url); + expect(url.searchParams.get('posall')).equal('SSPLOC'); + expect(url.searchParams.get('id')).equal('58278'); + expect(url.searchParams.get('sdktype')).equal('0'); + expect(request.method).to.equal('POST'); + + // check request data + expect(request.data.currency).to.equal('JPY'); + expect(request.data.pbver).to.equal(prebid.version); + expect(request.data.sdkname).to.equal('prebidjs'); + expect(request.data.adapterver).to.equal(ADGENE_PREBID_VERSION); + expect(request.data.ortb.imp[0].id).to.equal('2f6ac468a9c15e'); + expect(request.data.ortb.imp[0].ext.novatiqSyncResponse).to.equal(undefined); + expect(request.data.ortb.imp[0].ext.params.id).to.equal('58278'); + expect(request.data.ortb.imp[0].ext.mediaTypes).to.deep.equal(expectedMediaTypes); }); it('should attache params to the bannerWithHyperId request', function () { - const defaultUA = window.navigator.userAgent; - window.navigator.__defineGetter__('userAgent', function () { - return 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'; - }); - const request = spec.buildRequests(bidRequests, bidderRequest)[2]; + const hyperIdParams = { + user: { + ext: { + eids: [ + { + source: 'novatiq.com', + uids: [ + { + 'id': 'xxxxxx' + } + ] + }, + ] + } + } + } + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: hyperIdParams})[2]; - window.navigator.__defineGetter__('userAgent', function () { - return defaultUA; - }); - expect(request.data).to.equal(data.bannerWithHyperId); + expect(request.data.ortb.imp[0].ext.novatiqSyncResponse).to.equal(1); + expect(request.data.ortb.user).to.deep.equal(hyperIdParams.user); }); it('should attache params to the bannerWithAdgextCriteoId request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[3]; - expect(request.data).to.equal(data.bannerWithAdgextCriteoId); + const criteoParams = { + user: { + ext: { + eids: [ + { + source: 'criteo.com', + uids: [ + { + id: 'xxxxxxx', + atype: 1 + } + ] + }, + ] + } + } + } + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: criteoParams})[0]; + expect(request.data.ortb.user).to.deep.equal(criteoParams.user); }); it('should attache params to the bannerWithAdgextIds request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[4]; - expect(request.data).to.equal(data.bannerWithAdgextIds); + const idparams = { + user: { + ext: { + eids: [ + { + source: 'id5-sync.com', + uids: [ + { + id: 'ID5*RCKp3flI7Jutz2TKfExBb6T2kY8KC6xJ5FAXIVuKo2_SDBBFN9x3KQf-FMHXA3Sv', + atype: 1, + ext: { + linkType: 1, + pba: 'L+L6bQ6WoA2INCSS31vtiawRuBYQQ5H6OioCAXUNkl8=', + abTestingControlGroup: false + } + } + ] + }, + { + source: 'intimatemerger.com', + uids: [ + { + id: 'h.c2bef39c502aef97', + atype: 1 + } + ] + }, + { + source: 'ppid.intimatemerger.com', + uids: [ + { + id: 'f11490d3c7903e7455ac4af887280a3f', + atype: 1 + } + ] + }, + ] + } + }, + device: { + sua: suaSample + }, + source: { + ext: { + schain: schainSmaple + } + }, + } + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: idparams})[4]; + expect(request.data.ortb.user).to.deep.equal(idparams.user); + + // gpid + expect(request.data.ortb.imp[0].ext.gpid).to.equal('/1111/homepage#300x250'); + // sua + expect(request.data.ortb.device.sua).to.deep.equal(suaSample); + // schain + expect(request.data.ortb.source.ext.schain).to.deep.equal(schainSmaple); }); it('allows setConfig to set bidder currency for JPY', function () { - config.setConfig({ - currency: { - adServerCurrency: 'JPY' - } + setCurrencyConfig({ adServerCurrency: 'JPY' }); + return addFPDToBidderRequest(bidderRequest).then(res => { + const bidRequest = spec.buildRequests(bidRequests, res)[0]; + expect(bidRequest.data.currency).to.equal('JPY'); + setCurrencyConfig({}); }); - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.data).to.equal(data.banner); - config.resetConfig(); }); + it('allows setConfig to set bidder currency for USD', function () { setCurrencyConfig({ adServerCurrency: 'USD' }); return addFPDToBidderRequest(bidderRequest).then(res => { const bidRequest = spec.buildRequests(bidRequests, res)[0]; - expect(bidRequest.data).to.equal(data.bannerUSD); + expect(bidRequest.data.currency).to.equal('USD'); setCurrencyConfig({}); }); }); }); + describe('interpretResponse', function () { const bidRequests = { banner: { bidderRequest: { ortb2: {ext: {prebid: {adServerCurrency: 'JPY'}}} }, - bidRequest: { - bidder: 'adg', - params: { - id: '58278', // banner + method: 'POST', + url: 'https://api-test.scaleout.jp/adgen/prebid?id=15415&posall=SSPLOC&sdktype=0', + data: { + currency: 'JPY', + pbver: prebid.version, + sdkname: 'prebidjs', + adapterver: ADGENE_PREBID_VERSION, + ortb: { + imp: [ + { + ext: { + gpid: '/1111/homepage-leftnav', + data: { + pbadslot: '/1111/homepage-leftnav' + }, + params: { + id: '15415', + debug: true + }, + mediaTypes: { + banner: { + sizes: [ + [ + 1, + 1 + ], + [ + 320, + 180 + ], + [ + 320, + 100 + ], + [ + 320, + 50 + ], + [ + 300, + 250 + ], + [ + 970, + 250 + ] + ] + } + } + }, + id: '2f6ac468a9c15e', + banner: { + topframe: 1, + format: [ + { + 'w': 1, + 'h': 1 + }, + { + 'w': 320, + 'h': 180 + }, + { + 'w': 320, + 'h': 100 + }, + { + 'w': 320, + 'h': 50 + }, + { + 'w': 300, + 'h': 250 + }, + { + 'w': 970, + 'h': 250 + } + ] + } + } + ], + source: {}, + '': { + ext: { + 'data': { + 'CxSegments': [ + 'xxxxxxx', + ] + }, + eids: [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'xxxxxxxx', + 'atype': 1 + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1, + 'ext': { + 'linkType': 1, + 'pba': 'xxxxxx', + 'abTestingControlGroup': false + } + } + ] + }, + { + 'source': 'intimatemerger.com', + 'uids': [ + { + 'id': 'xxxx', + 'atype': 1 + } + ] + }, + { + 'source': 'ppid.intimatemerger.com', + 'uids': [ + { + 'id': 'xxxxxxx', + 'atype': 1 + } + ] + } + ] + } + }, + 'site': { + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + }, + 'page': 'https://example.com/post/html/fi/test2.html?pbjs_debug=true' + }, + 'device': { + 'w': 1792, + 'h': 1120, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', + 'language': 'ja', + 'ext': { + 'vpw': 616, + 'vph': 974 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '129' + ] + }, + { + 'brand': 'Not=A?Brand', + 'version': [ + '8' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '129' + ] + } + ], + 'mobile': 0 + } + }, + 'id': 'f149e3b5-46af-414c-a93a-4bbca5503112', + 'test': 0, + 'tmax': 3000 }, - adUnitCode: 'adunit-code', - sizes: [[320, 100]], - bidId: '2f6ac468a9c15e', - bidderRequestId: '14a9f773e30243', - auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', - transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + 'imark': 1 }, + 'options': { + 'withCredentials': true, + 'crossOrigin': true + } }, native: { - bidderRequest: { - ortb2: {ext: {prebid: {adServerCurrency: 'JPY'}}} - }, - bidRequest: { - bidder: 'adg', - params: { - id: '58278', // banner - }, - mediaTypes: { - native: { - image: { - required: true - }, - title: { - required: true, - len: 80 - }, - sponsoredBy: { - required: true - }, - clickUrl: { - required: true + method: 'POST', + url: 'https://api-test.scaleout.jp/adgen/prebid?id=10697&posall=SSPLOC&sdktype=0', + data: { + 'currency': 'JPY', + 'pbver': prebid.version, + 'sdkname': 'prebidjs', + 'adapterver': ADGENE_PREBID_VERSION, + 'ortb': { + 'imp': [ + { + 'ext': { + 'gpid': '/1111/homepage-leftnav', + 'data': { + 'pbadslot': '/1111/homepage-leftnav' + }, + 'params': { + 'id': '10697', + 'debug': true + }, + 'mediaTypes': { + 'native': { + 'image': { + 'required': true + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true + }, + 'privacyLink': { + 'required': true, + 'sendId': false + } + } + }, + 'novatiqSyncResponse': 2 + }, + 'id': '2f6ac468a9c15e', + 'native': { + 'request': '{\'ver\':\'1.2\',\'assets\':[{\'id\':0,\'required\':1,\'img\':{\'type\':3}},{\'id\':1,\'required\':1,\'title\':{\'len\':80}},{\'id\':2,\'required\':1,\'data\':{\'type\':1}},{\'id\':3,\'required\':1,\'data\':{\'type\':2}},{\'id\':4,\'required\':1,\'img\':{\'type\':1}}],\'privacy\':1}', + 'ver': '1.2' + } + } + ], + 'source': {}, + 'user': { + 'ext': { + 'data': { + 'CxSegments': [ + 'xxxxxxxx', + 'xxxxxxxy', + ] + }, + 'eids': [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1 + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1, + 'ext': { + 'linkType': 1, + 'pba': 'xxxxxx', + 'abTestingControlGroup': false + } + } + ] + }, + { + 'source': 'intimatemerger.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1 + } + ] + }, + { + 'source': 'ppid.intimatemerger.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1 + } + ] + } + ] + } + }, + 'site': { + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' }, - body: { - required: true + 'page': 'https://example.com/post/html/test3.html?pbjs_debug=true' + }, + 'device': { + 'w': 1792, + 'h': 1120, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', + 'language': 'ja', + 'ext': { + 'vpw': 616, + 'vph': 974 }, - icon: { - required: true + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '129' + ] + }, + { + 'brand': 'Not=A?Brand', + 'version': [ + '8' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '129' + ] + } + ], + 'mobile': 0 } - } - }, - adUnitCode: 'adunit-code', - sizes: [[1, 1]], - bidId: '2f6ac468a9c15e', - bidderRequestId: '14a9f773e30243', - auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', - transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + }, + 'id': '8fa21cb7-d874-41e9-a735-9edf560b306c', + 'test': 0, + 'tmax': 20000 + } }, + options: { + withCredentials: true, + crossOrigin: true + } }, upperBillboard: { - bidderRequest: { - ortb2: {ext: {prebid: {adServerCurrency: 'JPY'}}} - }, - bidRequest: { - bidder: 'adg', - params: { - id: '143038', // banner - marginTop: '50', + method: 'POST', + url: 'https://api-test.scaleout.jp/adgen/prebid?id=15410&posall=SSPLOC&sdktype=0', + data: { + 'currency': 'JPY', + 'pbver': prebid.version, + 'sdkname': 'prebidjs', + 'adapterver': ADGENE_PREBID_VERSION, + 'ortb': { + 'imp': [ + { + 'ext': { + 'params': { + 'id': '15410', + 'debug': true, + 'marginTop': '50' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 1, + 1 + ], + [ + 320, + 180 + ], + [ + 320, + 100 + ], + [ + 320, + 50 + ], + [ + 300, + 250 + ], + [ + 970, + 250 + ] + ] + } + }, + 'novatiqSyncResponse': 2 + }, + 'id': '2f6ac468a9c15e', + 'banner': { + 'topframe': 1, + 'format': [ + { + 'w': 1, + 'h': 1 + }, + { + 'w': 320, + 'h': 180 + }, + { + 'w': 320, + 'h': 100 + }, + { + 'w': 320, + 'h': 50 + }, + { + 'w': 300, + 'h': 250 + }, + { + 'w': 970, + 'h': 250 + } + ] + } + } + ], + 'source': {}, + 'user': { + 'ext': { + 'data': { + 'CxSegments': [ + 'xxxxxxxx', + 'xxxxxxxy', + ] + }, + 'eids': [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1 + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1, + 'ext': { + 'linkType': 1, + 'pba': 'xxxxxx', + 'abTestingControlGroup': false + } + } + ] + }, + { + 'source': 'intimatemerger.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1 + } + ] + }, + { + 'source': 'ppid.intimatemerger.com', + 'uids': [ + { + 'id': 'xxxxxx', + 'atype': 1 + } + ] + } + ] + } + }, + 'site': { + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + }, + 'page': 'https://example.com/post/html/fi/test1.html?pbjs_debug=true' + }, + 'device': { + 'w': 1792, + 'h': 1120, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', + 'language': 'ja', + 'ext': { + 'vpw': 616, + 'vph': 974 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '129' + ] + }, + { + 'brand': 'Not=A?Brand', + 'version': [ + '8' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '129' + ] + } + ], + 'mobile': 0 + } + }, + 'id': '24cb6de9-68ee-49df-8a90-27259215e059', + 'test': 0, + 'tmax': 3000 }, - adUnitCode: 'adunit-code', - sizes: [[320, 180]], - bidId: '2f6ac468a9c15e', - bidderRequestId: '14a9f773e30243', - auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', - transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' + 'imark': 1 }, - }, + } }; const serverResponse = { @@ -343,133 +982,127 @@ describe('AdgenerationAdapter', function () { }, normal: { banner: { - ad: '
', - beacon: '', - cpm: 36.0008, displaytype: '1', - ids: {}, - w: 320, - h: 100, location_params: null, - locationid: '58279', - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000, results: [ - {ad: '<\!DOCTYPE html>
'}, + { + ad: '
', + beacon: '', + cpm: 36.0008, + ids: {}, + w: 320, + h: 100, + locationid: '58279', + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + adomain: ['advertiserdomain.com'] + }, ], - adomain: ['advertiserdomain.com'] }, native: { - ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, displaytype: '1', - ids: {}, location_params: null, locationid: '58279', - adomain: ['advertiserdomain.com'], - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' - }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' - }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + results: [ + { + ad: '
', + beacon: '', + cpm: 36.0008, + ids: {}, + adomain: ['advertiserdomain.com'], + scheduleid: '512603', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + native: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 - }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 + { + data: { + label: 'optout_url', + value: 'https://example.com/optout/#' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://example.com/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://example.com/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://example.com/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://example.com/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://example.com/1x1.gif'], + link: { + clicktrackers: [ + 'https://example.com/1x1_clicktracker_access.gif' + ], + url: 'https://example.com' }, - required: 1 }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 - }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } - ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' - ], - url: 'https://supership.jp' - }, - }, - results: [ - {ad: 'Creative<\/body>'} + } ], rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 }, upperBillboard: { - 'ad': '<\!DOCTYPE html>\n \n \n \n \n \n \n \n
\n \n
\n \n', - 'beacon': '', - 'beaconurl': 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif', - 'cpm': 80, - 'creative_params': {}, - 'creativeid': 'ScaleOut_2146187', - 'dealid': '2134-132864_newformat_test', 'displaytype': '1', 'h': 180, 'ids': { 'anid': '', 'diid': '', 'idfa': '', - 'soc': 'Xm8Q8cCo5r8AAHCCMg0AAAAA' + 'soc': 'yyyyyyyy' }, 'location_params': { 'option': { @@ -479,284 +1112,50 @@ describe('AdgenerationAdapter', function () { 'locationid': '143038', 'results': [ { - 'ad': '<\!DOCTYPE html>\n \n \n \n \n \n \n \n
\n \n
\n \n', - 'beacon': '', - 'beaconurl': 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif', + 'ad': '
', + 'beacon': '', + 'beaconurl': 'http://example.com', 'cpm': 80, 'creative_params': {}, 'creativeid': 'ScaleOut_2146187', 'dealid': '2134-132864_newformat_test', 'h': 180, - 'landing_url': 'https://supership.jp/', + 'landing_url': 'https://example.com/', 'rparams': {}, 'scheduleid': '1233323', 'trackers': { 'imp': [ - 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif' + 'https://example.com' ], 'viewable_imp': [ - 'https://tg.socdm.com/aux/inview?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + 'https://example.com' ], 'viewable_measured': [ - 'https://tg.socdm.com/aux/measured?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + 'https://example.com' ] }, 'ttl': 1000, - 'vastxml': '\n \n \n SOADS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n https://i.socdm.com/a/2/2095/2091787/20210810043037-de3e74aec30f36.mp4\n https://i.socdm.com/a/2/2095/2091788/20210810043037-6dd368dc91d507.mp4\n https://i.socdm.com/a/2/2095/2091789/20210810043037-c8eb814ddd85c4.webm\n https://i.socdm.com/a/2/2095/2091790/20210810043037-0a7f74c40268ab.webm\n \n \n \n \n \n \n', + 'vastxml': '', 'vcpm': 0, 'w': 320, 'weight': 1 } ], 'rotation': '0', - 'scheduleid': '1233323', - 'sdktype': '0', - 'trackers': { - 'imp': [ - 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif' - ], - 'viewable_imp': [ - 'https://tg.socdm.com/aux/inview?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' - ], - 'viewable_measured': [ - 'https://tg.socdm.com/aux/measured?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' - ] - }, - 'ttl': 1000, - 'vastxml': '\n \n \n SOADS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n https://i.socdm.com/a/2/2095/2091787/20210810043037-de3e74aec30f36.mp4\n https://i.socdm.com/a/2/2095/2091788/20210810043037-6dd368dc91d507.mp4\n https://i.socdm.com/a/2/2095/2091789/20210810043037-c8eb814ddd85c4.webm\n https://i.socdm.com/a/2/2095/2091790/20210810043037-0a7f74c40268ab.webm\n \n \n \n \n \n \n', - 'vcpm': 0, - 'w': 320, } }, - emptyAdomain: { - banner: { - ad: '
', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - w: 320, - h: 100, - location_params: null, - locationid: '58279', - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000, - results: [ - {ad: '<\!DOCTYPE html>
'}, - ], - adomain: [] - }, - native: { - ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - location_params: null, - locationid: '58279', - adomain: [], - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' - }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' - }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', - }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 - }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 - }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 - }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } - ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' - ], - url: 'https://supership.jp' - }, - }, - results: [ - {ad: 'Creative<\/body>'} - ], - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 - } - }, - noAdomain: { - banner: { - ad: '
', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - w: 320, - h: 100, - location_params: null, - locationid: '58279', - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000, - results: [ - {ad: '<\!DOCTYPE html>
'}, - ], - }, - native: { - ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - location_params: null, - locationid: '58279', - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' - }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' - }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', - }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 - }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 - }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 - }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } - ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' - ], - url: 'https://supership.jp' - }, - }, - results: [ - {ad: 'Creative<\/body>'} - ], - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 - } - } - }; + } + serverResponse.emptyAdomain = {}; + serverResponse.emptyAdomain.banner = JSON.parse(JSON.stringify(serverResponse.normal.banner)); + serverResponse.emptyAdomain.banner.results[0].adomain = []; + serverResponse.emptyAdomain.native = JSON.parse(JSON.stringify(serverResponse.normal.native)); + serverResponse.emptyAdomain.native.results[0].adomain = []; + + serverResponse.noAdomain = {}; + serverResponse.noAdomain.banner = JSON.parse(JSON.stringify(serverResponse.normal.banner)); + delete serverResponse.noAdomain.banner.results[0].adomain; + serverResponse.noAdomain.native = JSON.parse(JSON.stringify(serverResponse.normal.native)); + delete serverResponse.noAdomain.native.results[0].adomain; const bidResponses = { normal: { @@ -770,7 +1169,7 @@ describe('AdgenerationAdapter', function () { currency: 'JPY', netRevenue: true, ttl: 1000, - ad: '
', + ad: '
', adomain: ['advertiserdomain.com'] }, native: { @@ -784,26 +1183,26 @@ describe('AdgenerationAdapter', function () { netRevenue: true, ttl: 1000, adomain: ['advertiserdomain.com'], - ad: '↵
', + ad: '
', native: { title: 'Title', image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + url: 'https://example.com/adg-sample-ad/img/300x250.png', height: 250, width: 300 }, icon: { - url: 'https://placehold.jp/300x300.png', + url: 'https://example.com/300x300.png', height: 300, width: 300 }, sponsoredBy: 'Sponsored', body: 'Description', cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] + privacyLink: 'https://example.com/optout/#', + clickUrl: 'https://example.com', + clickTrackers: ['https://example.com/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://example.com/1x1.gif'] }, mediaType: NATIVE }, @@ -814,109 +1213,13 @@ describe('AdgenerationAdapter', function () { height: 180, creativeId: 'ScaleOut_2146187', dealId: '2134-132864_newformat_test', - currency: 'JPY', + currency: 'USD', netRevenue: true, ttl: 1000, - ad: ``, + ad: ``, adomain: ['advertiserdomain.com'] }, - }, - emptyAdomain: { - banner: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 320, - height: 100, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '
', - adomain: [] - }, - native: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 1, - height: 1, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - adomain: [], - ad: '↵
', - native: { - title: 'Title', - image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - height: 250, - width: 300 - }, - icon: { - url: 'https://placehold.jp/300x300.png', - height: 300, - width: 300 - }, - sponsoredBy: 'Sponsored', - body: 'Description', - cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] - }, - mediaType: NATIVE - }, - }, - noAdomain: { - banner: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 320, - height: 100, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '
', - }, - native: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 1, - height: 1, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '↵
', - native: { - title: 'Title', - image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - height: 250, - width: 300 - }, - icon: { - url: 'https://placehold.jp/300x300.png', - height: 300, - width: 300 - }, - sponsoredBy: 'Sponsored', - body: 'Description', - cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] - }, - mediaType: NATIVE - } - }, + } }; it('no bid responses', function () { @@ -925,14 +1228,13 @@ describe('AdgenerationAdapter', function () { }); it('handles ADGBrowserM responses', function () { - setCurrencyConfig({ adServerCurrency: 'JPY' }); + setCurrencyConfig({ adServerCurrency: 'USD' }); const bidderRequest = { refererInfo: { page: 'https://example.com' } }; return addFPDToBidderRequest(bidderRequest).then(res => { - spec.buildRequests(bidRequests, res)[0]; const result = spec.interpretResponse({body: serverResponse.normal.upperBillboard}, { ...bidRequests.upperBillboard, bidderRequest: res })[0]; expect(result.requestId).to.equal(bidResponses.normal.upperBillboard.requestId); expect(result.width).to.equal(bidResponses.normal.upperBillboard.width); @@ -949,88 +1251,92 @@ describe('AdgenerationAdapter', function () { it('handles banner responses for empty adomain', function () { const result = spec.interpretResponse({body: serverResponse.emptyAdomain.banner}, bidRequests.banner)[0]; - expect(result.requestId).to.equal(bidResponses.emptyAdomain.banner.requestId); - expect(result.width).to.equal(bidResponses.emptyAdomain.banner.width); - expect(result.height).to.equal(bidResponses.emptyAdomain.banner.height); - expect(result.creativeId).to.equal(bidResponses.emptyAdomain.banner.creativeId); - expect(result.dealId).to.equal(bidResponses.emptyAdomain.banner.dealId); - expect(result.currency).to.equal(bidResponses.emptyAdomain.banner.currency); - expect(result.netRevenue).to.equal(bidResponses.emptyAdomain.banner.netRevenue); - expect(result.ttl).to.equal(bidResponses.emptyAdomain.banner.ttl); - expect(result.ad).to.equal(bidResponses.emptyAdomain.banner.ad); + expect(result.requestId).to.equal(bidResponses.normal.banner.requestId); + expect(result.width).to.equal(bidResponses.normal.banner.width); + expect(result.height).to.equal(bidResponses.normal.banner.height); + expect(result.creativeId).to.equal(bidResponses.normal.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.banner.dealId); + expect(result.currency).to.equal(bidResponses.normal.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.banner.ttl); + expect(result.ad).to.equal(bidResponses.normal.banner.ad); + // no adomian expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); it('handles native responses for empty adomain', function () { const result = spec.interpretResponse({body: serverResponse.emptyAdomain.native}, bidRequests.native)[0]; - expect(result.requestId).to.equal(bidResponses.emptyAdomain.native.requestId); - expect(result.width).to.equal(bidResponses.emptyAdomain.native.width); - expect(result.height).to.equal(bidResponses.emptyAdomain.native.height); - expect(result.creativeId).to.equal(bidResponses.emptyAdomain.native.creativeId); - expect(result.dealId).to.equal(bidResponses.emptyAdomain.native.dealId); - expect(result.currency).to.equal(bidResponses.emptyAdomain.native.currency); - expect(result.netRevenue).to.equal(bidResponses.emptyAdomain.native.netRevenue); - expect(result.ttl).to.equal(bidResponses.emptyAdomain.native.ttl); - expect(result.native.title).to.equal(bidResponses.emptyAdomain.native.native.title); - expect(result.native.image.url).to.equal(bidResponses.emptyAdomain.native.native.image.url); - expect(result.native.image.height).to.equal(bidResponses.emptyAdomain.native.native.image.height); - expect(result.native.image.width).to.equal(bidResponses.emptyAdomain.native.native.image.width); - expect(result.native.icon.url).to.equal(bidResponses.emptyAdomain.native.native.icon.url); - expect(result.native.icon.width).to.equal(bidResponses.emptyAdomain.native.native.icon.width); - expect(result.native.icon.height).to.equal(bidResponses.emptyAdomain.native.native.icon.height); - expect(result.native.sponsoredBy).to.equal(bidResponses.emptyAdomain.native.native.sponsoredBy); - expect(result.native.body).to.equal(bidResponses.emptyAdomain.native.native.body); - expect(result.native.cta).to.equal(bidResponses.emptyAdomain.native.native.cta); - expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.emptyAdomain.native.native.privacyLink); - expect(result.native.clickUrl).to.equal(bidResponses.emptyAdomain.native.native.clickUrl); - expect(result.native.impressionTrackers[0]).to.equal(bidResponses.emptyAdomain.native.native.impressionTrackers[0]); - expect(result.native.clickTrackers[0]).to.equal(bidResponses.emptyAdomain.native.native.clickTrackers[0]); - expect(result.mediaType).to.equal(bidResponses.emptyAdomain.native.mediaType); + expect(result.requestId).to.equal(bidResponses.normal.native.requestId); + expect(result.width).to.equal(bidResponses.normal.native.width); + expect(result.height).to.equal(bidResponses.normal.native.height); + expect(result.creativeId).to.equal(bidResponses.normal.native.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.native.dealId); + expect(result.currency).to.equal(bidResponses.normal.native.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.native.ttl); + expect(result.native.title).to.equal(bidResponses.normal.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.normal.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.normal.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.normal.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.normal.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.normal.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.normal.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.normal.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.normal.native.native.body); + expect(result.native.cta).to.equal(bidResponses.normal.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.normal.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.normal.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.normal.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.normal.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.normal.native.mediaType); + // no adomain expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); it('handles banner responses for no adomain', function () { const result = spec.interpretResponse({body: serverResponse.noAdomain.banner}, bidRequests.banner)[0]; - expect(result.requestId).to.equal(bidResponses.noAdomain.banner.requestId); - expect(result.width).to.equal(bidResponses.noAdomain.banner.width); - expect(result.height).to.equal(bidResponses.noAdomain.banner.height); - expect(result.creativeId).to.equal(bidResponses.noAdomain.banner.creativeId); - expect(result.dealId).to.equal(bidResponses.noAdomain.banner.dealId); - expect(result.currency).to.equal(bidResponses.noAdomain.banner.currency); - expect(result.netRevenue).to.equal(bidResponses.noAdomain.banner.netRevenue); - expect(result.ttl).to.equal(bidResponses.noAdomain.banner.ttl); - expect(result.ad).to.equal(bidResponses.noAdomain.banner.ad); + expect(result.requestId).to.equal(bidResponses.normal.banner.requestId); + expect(result.width).to.equal(bidResponses.normal.banner.width); + expect(result.height).to.equal(bidResponses.normal.banner.height); + expect(result.creativeId).to.equal(bidResponses.normal.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.banner.dealId); + expect(result.currency).to.equal(bidResponses.normal.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.banner.ttl); + expect(result.ad).to.equal(bidResponses.normal.banner.ad); + // no adomain expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); it('handles native responses for no adomain', function () { const result = spec.interpretResponse({body: serverResponse.noAdomain.native}, bidRequests.native)[0]; - expect(result.requestId).to.equal(bidResponses.noAdomain.native.requestId); - expect(result.width).to.equal(bidResponses.noAdomain.native.width); - expect(result.height).to.equal(bidResponses.noAdomain.native.height); - expect(result.creativeId).to.equal(bidResponses.noAdomain.native.creativeId); - expect(result.dealId).to.equal(bidResponses.noAdomain.native.dealId); - expect(result.currency).to.equal(bidResponses.noAdomain.native.currency); - expect(result.netRevenue).to.equal(bidResponses.noAdomain.native.netRevenue); - expect(result.ttl).to.equal(bidResponses.noAdomain.native.ttl); - expect(result.native.title).to.equal(bidResponses.noAdomain.native.native.title); - expect(result.native.image.url).to.equal(bidResponses.noAdomain.native.native.image.url); - expect(result.native.image.height).to.equal(bidResponses.noAdomain.native.native.image.height); - expect(result.native.image.width).to.equal(bidResponses.noAdomain.native.native.image.width); - expect(result.native.icon.url).to.equal(bidResponses.noAdomain.native.native.icon.url); - expect(result.native.icon.width).to.equal(bidResponses.noAdomain.native.native.icon.width); - expect(result.native.icon.height).to.equal(bidResponses.noAdomain.native.native.icon.height); - expect(result.native.sponsoredBy).to.equal(bidResponses.noAdomain.native.native.sponsoredBy); - expect(result.native.body).to.equal(bidResponses.noAdomain.native.native.body); - expect(result.native.cta).to.equal(bidResponses.noAdomain.native.native.cta); - expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.noAdomain.native.native.privacyLink); - expect(result.native.clickUrl).to.equal(bidResponses.noAdomain.native.native.clickUrl); - expect(result.native.impressionTrackers[0]).to.equal(bidResponses.noAdomain.native.native.impressionTrackers[0]); - expect(result.native.clickTrackers[0]).to.equal(bidResponses.noAdomain.native.native.clickTrackers[0]); - expect(result.mediaType).to.equal(bidResponses.noAdomain.native.mediaType); + expect(result.requestId).to.equal(bidResponses.normal.native.requestId); + expect(result.width).to.equal(bidResponses.normal.native.width); + expect(result.height).to.equal(bidResponses.normal.native.height); + expect(result.creativeId).to.equal(bidResponses.normal.native.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.native.dealId); + expect(result.currency).to.equal(bidResponses.normal.native.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.native.ttl); + expect(result.native.title).to.equal(bidResponses.normal.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.normal.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.normal.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.normal.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.normal.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.normal.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.normal.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.normal.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.normal.native.native.body); + expect(result.native.cta).to.equal(bidResponses.normal.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.normal.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.normal.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.normal.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.normal.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.normal.native.mediaType); + // no adomain expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); From b39d070fa06126e99b09bc6c4d748746f5aa03c3 Mon Sep 17 00:00:00 2001 From: rediads <123890182+rediads@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:52:51 +0530 Subject: [PATCH 0769/1097] Rediads Bid Adapter : initial release (#12525) * feature: Rediads bid adapter * Test Cases updated | Bid Adapter improved * Bug fix * Readme updated * Test cases fixed * test cases updated * test cases reset hash fixed * Add semicolon * remove semicolon * Get site content from bidrequest instead of config --------- Co-authored-by: symplorpro Co-authored-by: Symplor Co-authored-by: symplorpro <161946060+symplorpro@users.noreply.github.com> --- modules/rediadsBidAdapter.js | 102 +++++++++++++++ modules/rediadsBidAdapter.md | 38 ++++++ test/spec/modules/rediadsBidAdapter_spec.js | 133 ++++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 modules/rediadsBidAdapter.js create mode 100644 modules/rediadsBidAdapter.md create mode 100644 test/spec/modules/rediadsBidAdapter_spec.js diff --git a/modules/rediadsBidAdapter.js b/modules/rediadsBidAdapter.js new file mode 100644 index 00000000000..ec4b12f6e39 --- /dev/null +++ b/modules/rediadsBidAdapter.js @@ -0,0 +1,102 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepSetValue, logWarn, logError } from '../src/utils.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'rediads'; +const ENDPOINT_URL = 'https://bidding.rediads.com/openrtb2/auction'; +const STAGING_ENDPOINT_URL = 'https://stagingbidding.rediads.com/openrtb2/auction'; +const DEFAULT_CURRENCY = 'USD'; +const LOG_PREFIX = 'Rediads: '; + +const MEDIA_TYPES = { + [BANNER]: 1, + [VIDEO]: 2, + [NATIVE]: 4, +}; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: DEFAULT_CURRENCY, + }, + bidResponse(buildBidResponse, bid, context) { + let mediaType = 'banner'; // Default media type + + if (bid.vastXml || bid.vastUrl || (bid.adm && bid.adm.startsWith(' { + // Reset the hash, ensuring no trailing # + if (originalHash) { + location.hash = originalHash; + } else { + history.replaceState(null, '', location.pathname + location.search); + } + } + + describe('isBidRequestValid', function () { + it('should return true for valid bid requests', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false if account_id is missing', function () { + const invalidBid = { ...bidRequest, params: {} }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should build a valid request with correct data', function () { + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests).to.be.an('array').that.is.not.empty; + + const request = requests[0]; + expect(request.method).to.equal('POST'); + expect(request.url).that.is.not.empty; + expect(request.data).to.have.property('ext'); + expect(request.data.ext.rediads.params).to.deep.equal(bidRequest.params); + }); + + it('should include test flag if testBidsRequested is true', function () { + const originalHash = location.hash; + location.hash = '#rediads-test-bids'; + + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests[0].data.test).to.equal(1); + + resetHash(originalHash); + }); + + it('should set staging environtment if stagingEnvRequested is true', function () { + const originalHash = location.hash; + location.hash = '#rediads-staging'; + + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests[0].url).to.equal(STAGING_ENDPOINT_URL); + + resetHash(originalHash); + }); + }); + + describe('interpretResponse', function () { + it('should interpret and return valid bid responses for banner bid', function () { + const serverResponse = { + body: { + seatbid: [ + { + bid: [ + { + price: 1.23, + impid: '2ab03f1234', + adm: '
Ad
', + crid: 'creative123', + w: 300, + h: 250, + }, + ], + }, + ], + }, + }; + const requestObj = spec.buildRequests([bidRequest], bidderRequest); + const bids = spec.interpretResponse(serverResponse, requestObj[0]); + expect(bids).to.be.an('array').that.is.not.empty; + + const bid = bids[0]; + expect(bid).to.include({ + requestId: '2ab03f1234', + cpm: 1.23, + creativeId: 'creative123', + width: 300, + height: 250, + ad: '
Ad
', + }); + expect(bid.mediaType).to.equal('banner'); + }); + + it('should return an empty array for invalid responses', function () { + const invalidResponse = { body: {} }; + const updatedBidRequest = {...bidRequest, params: undefined} + const requestObj = spec.buildRequests([updatedBidRequest], bidderRequest); + const bids = spec.interpretResponse(invalidResponse, requestObj[0]); + expect(bids).to.be.an('array').that.is.empty; + }); + }); + + describe('Miscellaneous', function () { + it('should support multiple media types', function () { + expect(spec.supportedMediaTypes).to.include.members(['banner', 'native', 'video']); + }); + }); +}); From e060b74a42d48b7e6a266e65e24c30573ce52bc0 Mon Sep 17 00:00:00 2001 From: jeremy-greenbids Date: Tue, 17 Dec 2024 21:19:58 +0100 Subject: [PATCH 0770/1097] Greenbids Bidder Adapter (#12510) * Greenbids Bidder adapter * refacto to make the code easier and clearer * refacto to make the code easier and clearer * Alexis' review * Alex's review part 2 * export more utils * add test on news utils * remove info that could lead to finger printing --- libraries/pageInfosUtils/pageInfosUtils.js | 65 + .../timeToFirstBytesUtils.js | 37 + modules/greenbidsBidAdapter.js | 238 ++++ modules/greenbidsBidAdapter.md | 32 + test/spec/libraries/pageInfosUtils.js | 102 ++ test/spec/libraries/timeToFirstBytesUtils.js | 54 + .../spec/modules/greenbidsBidAdapter_specs.js | 1051 +++++++++++++++++ 7 files changed, 1579 insertions(+) create mode 100644 libraries/pageInfosUtils/pageInfosUtils.js create mode 100644 libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js create mode 100644 modules/greenbidsBidAdapter.js create mode 100644 modules/greenbidsBidAdapter.md create mode 100644 test/spec/libraries/pageInfosUtils.js create mode 100644 test/spec/libraries/timeToFirstBytesUtils.js create mode 100644 test/spec/modules/greenbidsBidAdapter_specs.js diff --git a/libraries/pageInfosUtils/pageInfosUtils.js b/libraries/pageInfosUtils/pageInfosUtils.js new file mode 100644 index 00000000000..5e215ad3f3d --- /dev/null +++ b/libraries/pageInfosUtils/pageInfosUtils.js @@ -0,0 +1,65 @@ +/** + * Retrieves the referrer information from the bidder request. + * + * @param {Object} bidderRequest - The bidder request object. + * @param {Object} [bidderRequest.refererInfo] - The referer information object. + * @param {string} [bidderRequest.refererInfo.page] - The page URL of the referer. + * @returns {string} The referrer URL if available, otherwise an empty string. + */ +export function getReferrerInfo(bidderRequest) { + let ref = ''; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + ref = bidderRequest.refererInfo.page; + } + return ref; +} + +/** + * Retrieves the title of the current web page. + * + * This function attempts to get the title from the top-level window's document. + * If an error occurs (e.g., due to cross-origin restrictions), it falls back to the current document. + * It first tries to get the title from the `og:title` meta tag, and if that is not available, it uses the document's title. + * + * @returns {string} The title of the current web page, or an empty string if no title is found. + */ +export function getPageTitle() { + try { + const ogTitle = window.top.document.querySelector('meta[property="og:title"]'); + return window.top.document.title || (ogTitle && ogTitle.content) || ''; + } catch (e) { + const ogTitle = document.querySelector('meta[property="og:title"]'); + return document.title || (ogTitle && ogTitle.content) || ''; + } +} + +/** + * Retrieves the content of the page description meta tag. + * + * This function attempts to get the description from the top-level window's document. + * If it fails (e.g., due to cross-origin restrictions), it falls back to the current document. + * It looks for meta tags with either the name "description" or the property "og:description". + * + * @returns {string} The content of the description meta tag, or an empty string if not found. + */ +export function getPageDescription() { + try { + const element = window.top.document.querySelector('meta[name="description"]') || + window.top.document.querySelector('meta[property="og:description"]'); + return (element && element.content) || ''; + } catch (e) { + const element = document.querySelector('meta[name="description"]') || + document.querySelector('meta[property="og:description"]'); + return (element && element.content) || ''; + } +} + +/** + * Retrieves the downlink speed of the user's network connection. + * + * @param {object} nav - The navigator object, typically `window.navigator`. + * @returns {string} The downlink speed as a string if available, otherwise an empty string. + */ +export function getConnectionDownLink(nav) { + return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : ''; +} diff --git a/libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js b/libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js new file mode 100644 index 00000000000..5d5330d8127 --- /dev/null +++ b/libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js @@ -0,0 +1,37 @@ +/** + * Calculates the Time to First Byte (TTFB) for the given window object. + * + * This function attempts to use the Navigation Timing Level 2 API first, and falls back to + * the Navigation Timing Level 1 API if the former is not available. + * + * @param {Window} win - The window object from which to retrieve performance timing information. + * @returns {string} The TTFB in milliseconds as a string, or an empty string if the TTFB cannot be determined. + */ +export function getTimeToFirstByte(win) { + const performance = win.performance || win.webkitPerformance || win.msPerformance || win.mozPerformance; + + const ttfbWithTimingV2 = performance && + typeof performance.getEntriesByType === 'function' && + Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && + performance.getEntriesByType('navigation')[0] && + performance.getEntriesByType('navigation')[0].responseStart && + performance.getEntriesByType('navigation')[0].requestStart && + performance.getEntriesByType('navigation')[0].responseStart > 0 && + performance.getEntriesByType('navigation')[0].requestStart > 0 && + Math.round( + performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart + ); + + if (ttfbWithTimingV2) { + return ttfbWithTimingV2.toString(); + } + + const ttfbWithTimingV1 = performance && + performance.timing.responseStart && + performance.timing.requestStart && + performance.timing.responseStart > 0 && + performance.timing.requestStart > 0 && + performance.timing.responseStart - performance.timing.requestStart; + + return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; +} diff --git a/modules/greenbidsBidAdapter.js b/modules/greenbidsBidAdapter.js new file mode 100644 index 00000000000..418cb850527 --- /dev/null +++ b/modules/greenbidsBidAdapter.js @@ -0,0 +1,238 @@ +import { getValue, logError, deepAccess, parseSizesInput, getBidIdParameter, logInfo } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { getHLen } from '../libraries/navigatorData/navigatorData.js'; +import { getTimeToFirstByte } from '../libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js'; +import { getReferrerInfo, getPageTitle, getPageDescription, getConnectionDownLink } from '../libraries/pageInfosUtils/pageInfosUtils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + +const BIDDER_CODE = 'greenbids'; +const GVL_ID = 1232; +const ENDPOINT_URL = 'https://hb.greenbids.ai'; +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: ['banner', 'video'], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (typeof bid.params !== 'undefined' && parseInt(getValue(bid.params, 'placementId')) > 0) { + logInfo('Greenbids bidder adapter valid bid request'); + return true; + } else { + logError('Greenbids bidder adapter requires placementId to be defined and a positive number'); + return false; + } + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests array of bids + * @param bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const bids = validBidRequests.map(bids => { + const reqObj = {}; + let placementId = getValue(bids.params, 'placementId'); + const gpid = deepAccess(bids, 'ortb2Imp.ext.gpid'); + reqObj.sizes = getSizes(bids); + reqObj.bidId = getBidIdParameter('bidId', bids); + reqObj.bidderRequestId = getBidIdParameter('bidderRequestId', bids); + reqObj.placementId = parseInt(placementId, 10); + reqObj.adUnitCode = getBidIdParameter('adUnitCode', bids); + reqObj.transactionId = bids.ortb2Imp?.ext?.tid || ''; + if (gpid) { reqObj.gpid = gpid; } + }); + const topWindow = window.top; + + const payload = { + referrer: getReferrerInfo(bidderRequest), + pageReferrer: document.referrer, + pageTitle: getPageTitle().slice(0, 300), + pageDescription: getPageDescription().slice(0, 300), + networkBandwidth: getConnectionDownLink(window.navigator), + timeToFirstByte: getTimeToFirstByte(window), + data: bids, + device: bidderRequest?.ortb2?.device || {}, + deviceWidth: screen.width, + deviceHeight: screen.height, + devicePixelRatio: topWindow.devicePixelRatio, + screenOrientation: screen.orientation?.type, + historyLength: getHLen(), + viewportHeight: topWindow.visualViewport?.height, + viewportWidth: topWindow.visualViewport?.width, + prebid_version: '$prebid.version$', + }; + + const firstBidRequest = validBidRequests[0]; + + if (firstBidRequest.schain) { + payload.schain = firstBidRequest.schain; + } + + hydratePayloadWithGppConsentData(payload, bidderRequest.gppConsent); + hydratePayloadWithGdprConsentData(payload, bidderRequest.gdprConsent); + hydratePayloadWithUspConsentData(payload, bidderRequest.uspConsent); + + const userAgentClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + payload.userAgentClientHints = userAgentClientHints; + } + + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + payload.dsa = dsa; + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server response. + */ + interpretResponse: function (serverResponse) { + serverResponse = serverResponse.body; + if (!serverResponse.responses) { + return []; + } + return serverResponse.responses.map((bid) => { + const bidResponse = { + cpm: bid.cpm, + width: bid.width, + height: bid.height, + currency: bid.currency, + netRevenue: true, + size: bid.size, + ttl: bid.ttl, + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [], + }, + ad: bid.ad, + requestId: bid.bidId, + creativeId: bid.creativeId, + placementId: bid.placementId, + }; + if (bid.dealId) { + bidResponse.dealId = bid.dealId + } + if (bid?.ext?.dsa) { + bidResponse.meta.dsa = bid.ext.dsa; + } + return bidResponse; + }); + } +}; + +registerBidder(spec); + +/** + * Converts the sizes from the bid object to the required format. + * + * @param {Object} bid - The bid object containing size information. + * @param {Array} bid.sizes - The sizes array from the bid object. + * @returns {Array} - The parsed sizes in the required format. + */ +function getSizes(bid) { + return parseSizesInput(bid.sizes); +} + +// Privacy handling + +/** + * Hydrates the given payload with GPP consent data if available. + * + * @param {Object} payload - The payload object to be hydrated. + * @param {Object} gppData - The GPP consent data object. + * @param {string} gppData.gppString - The GPP consent string. + * @param {number[]} gppData.applicableSections - An array of applicable section IDs. + */ +function hydratePayloadWithGppConsentData(payload, gppData) { + if (!gppData) { return; } + let isValidConsentString = typeof gppData.gppString === 'string'; + let validateApplicableSections = + Array.isArray(gppData.applicableSections) && + gppData.applicableSections.every((section) => typeof (section) === 'number') + payload.gpp = { + consentString: isValidConsentString ? gppData.gppString : '', + applicableSectionIds: validateApplicableSections ? gppData.applicableSections : [], + }; +} + +/** + * Hydrates the given payload with GDPR consent data if available. + * + * @param {Object} payload - The payload object to be hydrated with GDPR consent data. + * @param {Object} gdprData - The GDPR data object containing consent information. + * @param {boolean} gdprData.gdprApplies - Indicates if GDPR applies. + * @param {string} gdprData.consentString - The GDPR consent string. + * @param {number} gdprData.apiVersion - The version of the GDPR API being used. + * @param {Object} gdprData.vendorData - Additional vendor data related to GDPR. + */ +function hydratePayloadWithGdprConsentData(payload, gdprData) { + if (!gdprData) { return; } + let isCmp = typeof gdprData.gdprApplies === 'boolean'; + let isConsentString = typeof gdprData.consentString === 'string'; + let status = isCmp + ? findGdprStatus(gdprData.gdprApplies, gdprData.vendorData) + : gdprStatus.CMP_NOT_FOUND_OR_ERROR; + payload.gdpr_iab = { + consent: isConsentString ? gdprData.consentString : '', + status: status, + apiVersion: gdprData.apiVersion + }; +} + +/** + * Adds USP (CCPA) consent data to the payload if available. + * + * @param {Object} payload - The payload object to be hydrated with USP consent data. + * @param {string} uspConsentData - The USP consent string to be added to the payload. + */ +function hydratePayloadWithUspConsentData(payload, uspConsentData) { + if (!uspConsentData) { return; } + payload.us_privacy = uspConsentData; +} + +const gdprStatus = { + GDPR_APPLIES_PUBLISHER: 12, + GDPR_APPLIES_GLOBAL: 11, + GDPR_DOESNT_APPLY: 0, + CMP_NOT_FOUND_OR_ERROR: 22 +}; + +/** + * Determines the GDPR status based on whether GDPR applies and the provided GDPR data. + * + * @param {boolean} gdprApplies - Indicates if GDPR applies. + * @param {Object} gdprData - The GDPR data object. + * @param {boolean} gdprData.isServiceSpecific - Indicates if the GDPR data is service-specific. + * @returns {string} The GDPR status. + */ +function findGdprStatus(gdprApplies, gdprData) { + let status = gdprStatus.GDPR_APPLIES_PUBLISHER; + if (gdprApplies) { + if (gdprData && !gdprData.isServiceSpecific) { + status = gdprStatus.GDPR_APPLIES_GLOBAL; + } + } else { + status = gdprStatus.GDPR_DOESNT_APPLY; + } + return status; +} diff --git a/modules/greenbidsBidAdapter.md b/modules/greenbidsBidAdapter.md new file mode 100644 index 00000000000..df536294f47 --- /dev/null +++ b/modules/greenbidsBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +**Module Name**: Greenbids Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: tech@greenbids.ai + +# Description + +Use `greenbids` as bidder. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'your-slot_1-div', //use exactly the same code as your slot div id. + sizes: [[300, 250]], + bids: [{ + bidder: 'greenbids', + params: { + placementId: 12345, + } + }] + },{ + code: 'your-slot_2-div', //use exactly the same code as your slot div id. + sizes: [[600, 800]], + bids: [{ + bidder: 'greenbids', + params: { + placementId: 12345, + } + }] + }]; +``` diff --git a/test/spec/libraries/pageInfosUtils.js b/test/spec/libraries/pageInfosUtils.js new file mode 100644 index 00000000000..59d62abc74c --- /dev/null +++ b/test/spec/libraries/pageInfosUtils.js @@ -0,0 +1,102 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { getReferrerInfo, getPageTitle, getPageDescription, getConnectionDownLink } from './pageInfosUtils'; + +describe('pageInfosUtils', () => { + describe('getReferrerInfo', () => { + it('should return the referrer URL if available', () => { + const bidderRequest = { + refererInfo: { + page: 'http://example.com' + } + }; + const result = getReferrerInfo(bidderRequest); + expect(result).to.equal('http://example.com'); + }); + + it('should return an empty string if referrer URL is not available', () => { + const bidderRequest = {}; + const result = getReferrerInfo(bidderRequest); + expect(result).to.equal(''); + }); + }); + + describe('getPageTitle', () => { + let topDocumentStub, documentStub; + + beforeEach(() => { + topDocumentStub = sinon.stub(window.top, 'document').value({ + title: 'Top Document Title', + querySelector: sinon.stub().returns(null) + }); + documentStub = sinon.stub(document, 'querySelector').returns(null); + }); + + afterEach(() => { + topDocumentStub.restore(); + documentStub.restore(); + }); + + it('should return the title from the top-level document', () => { + const result = getPageTitle(); + expect(result).to.equal('Top Document Title'); + }); + + it('should return the title from the current document if top-level document access fails', () => { + topDocumentStub.value({ + title: '', + querySelector: sinon.stub().throws(new Error('Cross-origin restriction')) + }); + documentStub.returns({ content: 'Current Document Title' }); + const result = getPageTitle(); + expect(result).to.equal('Current Document Title'); + }); + }); + + describe('getPageDescription', () => { + let topDocumentStub, documentStub; + + beforeEach(() => { + topDocumentStub = sinon.stub(window.top, 'document').value({ + querySelector: sinon.stub().returns(null) + }); + documentStub = sinon.stub(document, 'querySelector').returns(null); + }); + + afterEach(() => { + topDocumentStub.restore(); + documentStub.restore(); + }); + + it('should return the description from the top-level document', () => { + topDocumentStub.querySelector.withArgs('meta[name="description"]').returns({ content: 'Top Document Description' }); + const result = getPageDescription(); + expect(result).to.equal('Top Document Description'); + }); + + it('should return the description from the current document if top-level document access fails', () => { + topDocumentStub.querySelector.throws(new Error('Cross-origin restriction')); + documentStub.withArgs('meta[name="description"]').returns({ content: 'Current Document Description' }); + const result = getPageDescription(); + expect(result).to.equal('Current Document Description'); + }); + }); + + describe('getConnectionDownLink', () => { + it('should return the downlink speed if available', () => { + const nav = { + connection: { + downlink: 10 + } + }; + const result = getConnectionDownLink(nav); + expect(result).to.equal('10'); + }); + + it('should return an empty string if downlink speed is not available', () => { + const nav = {}; + const result = getConnectionDownLink(nav); + expect(result).to.equal(''); + }); + }); +}); diff --git a/test/spec/libraries/timeToFirstBytesUtils.js b/test/spec/libraries/timeToFirstBytesUtils.js new file mode 100644 index 00000000000..ea737e925c4 --- /dev/null +++ b/test/spec/libraries/timeToFirstBytesUtils.js @@ -0,0 +1,54 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { getTimeToFirstByte } from './timeToFirstBytesUtils'; + +describe('getTimeToFirstByte', () => { + let win; + + beforeEach(() => { + win = { + performance: { + getEntriesByType: sinon.stub(), + timing: { + responseStart: 0, + requestStart: 0 + } + } + }; + }); + + it('should return TTFB using Navigation Timing Level 2 API', () => { + win.performance.getEntriesByType.withArgs('navigation').returns([{ + responseStart: 100, + requestStart: 50 + }]); + + const ttfb = getTimeToFirstByte(win); + expect(ttfb).to.equal('50'); + }); + + it('should return TTFB using Navigation Timing Level 1 API', () => { + win.performance.getEntriesByType.returns([]); + win.performance.timing.responseStart = 100; + win.performance.timing.requestStart = 50; + + const ttfb = getTimeToFirstByte(win); + expect(ttfb).to.equal('50'); + }); + + it('should return an empty string if TTFB cannot be determined', () => { + win.performance.getEntriesByType.returns([]); + win.performance.timing.responseStart = 0; + win.performance.timing.requestStart = 0; + + const ttfb = getTimeToFirstByte(win); + expect(ttfb).to.equal(''); + }); + + it('should return an empty string if performance object is not available', () => { + win.performance = null; + + const ttfb = getTimeToFirstByte(win); + expect(ttfb).to.equal(''); + }); +}); diff --git a/test/spec/modules/greenbidsBidAdapter_specs.js b/test/spec/modules/greenbidsBidAdapter_specs.js new file mode 100644 index 00000000000..014c3545cad --- /dev/null +++ b/test/spec/modules/greenbidsBidAdapter_specs.js @@ -0,0 +1,1051 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec } from 'modules/greenbidsBidAdapter.js'; +const ENDPOINT = 'https://d.greenbids.ai/hb/bid-request'; +const AD_SCRIPT = '"'; + +describe('greenbidsBidAdapter', () => { + const adapter = newBidder(spec); + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'greenbids', + 'params': { + 'placementId': 4242 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + let bidNonGbCompatible = { + 'bidder': 'greenbids', + }; + expect(spec.isBidRequestValid(bidNonGbCompatible)).to.equal(false); + }); + + it('should return false when the placement is not a number', function () { + let bidNonGbCompatible = { + 'bidder': 'greenbids', + 'params': { + 'placementId': 'toto' + }, + }; + expect(spec.isBidRequestValid(bidNonGbCompatible)).to.equal(false); + }); + }) + describe('buildRequests', function () { + it('should send bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should not send auctionId in bid request ', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.data[0].auctionId).to.not.exist + }); + + it('should send US Privacy to endpoint', function () { + let usPrivacy = 'OHHHFCP1' + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': usPrivacy + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.equal(usPrivacy); + }); + + it('should send GPP values to endpoint when available and valid', function () { + let consentString = 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'; + let applicableSectionIds = [7, 8]; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': applicableSectionIds + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(consentString); + expect(payload.gpp.applicableSectionIds).to.have.members(applicableSectionIds); + }); + + it('should send default GPP values to endpoint when available but invalid', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': undefined, + 'applicableSections': ['a'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(''); + expect(payload.gpp.applicableSectionIds).to.have.members([]); + }); + + it('should not set the GPP object in the request sent to the endpoint when not present', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.not.exist; + }); + + it('should send GDPR to endpoint', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + page: 'https://example.com/page.html', + reachedTop: true, + numIframes: 2 + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer).to.exist; + expect(payload.referrer).to.deep.equal('https://example.com/page.html') + }); + + const originalConnection = window.navigator.connection; + const mockConnection = { downlink: 10 }; + + const setNavigatorConnection = (connection) => { + Object.defineProperty(window.navigator, 'connection', { + value: connection, + configurable: true, + }); + }; + + try { + setNavigatorConnection(mockConnection); + + const requestWithConnection = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithConnection = JSON.parse(requestWithConnection.data); + + expect(payloadWithConnection.networkBandwidth).to.exist; + expect(payloadWithConnection.networkBandwidth).to.deep.equal(mockConnection.downlink.toString()); + + setNavigatorConnection(undefined); + + const requestWithoutConnection = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutConnection = JSON.parse(requestWithoutConnection.data); + + expect(payloadWithoutConnection.networkBandwidth).to.exist; + expect(payloadWithoutConnection.networkBandwidth).to.deep.equal(''); + } finally { + setNavigatorConnection(originalConnection); + } + + it('should add pageReferrer info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageReferrer).to.exist; + expect(payload.pageReferrer).to.deep.equal(document.referrer); + }); + + it('should add width info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceWidth = screen.width + + expect(payload.deviceWidth).to.exist; + expect(payload.deviceWidth).to.deep.equal(deviceWidth); + }); + + it('should add height info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceHeight = screen.height + + expect(payload.deviceHeight).to.exist; + expect(payload.deviceHeight).to.deep.equal(deviceHeight); + }); + + it('should add pixelRatio info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const pixelRatio = window.top.devicePixelRatio + + expect(payload.devicePixelRatio).to.exist; + expect(payload.devicePixelRatio).to.deep.equal(pixelRatio); + }); + + it('should add screenOrientation info to payload', function () { + const originalScreenOrientation = window.top.screen.orientation; + + const mockScreenOrientation = (type) => { + Object.defineProperty(window.top.screen, 'orientation', { + value: { type }, + configurable: true, + }); + }; + + try { + const mockType = 'landscape-primary'; + mockScreenOrientation(mockType); + + const requestWithOrientation = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithOrientation = JSON.parse(requestWithOrientation.data); + + expect(payloadWithOrientation.screenOrientation).to.exist; + expect(payloadWithOrientation.screenOrientation).to.deep.equal(mockType); + + mockScreenOrientation(undefined); + + const requestWithoutOrientation = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutOrientation = JSON.parse(requestWithoutOrientation.data); + + expect(payloadWithoutOrientation.screenOrientation).to.not.exist; + } finally { + Object.defineProperty(window.top.screen, 'orientation', { + value: originalScreenOrientation, + configurable: true, + }); + } + }); + + it('should add historyLength info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.historyLength).to.exist; + expect(payload.historyLength).to.deep.equal(window.top.history.length); + }); + + it('should add viewportHeight info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportHeight).to.exist; + expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); + }); + + it('should add viewportWidth info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportWidth).to.exist; + expect(payload.viewportWidth).to.deep.equal(window.top.visualViewport.width); + }); + + it('should add viewportHeight info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportHeight).to.exist; + expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); + }); + + it('should add ortb2 device data to payload', function () { + const ortb2DeviceBidderRequest = { + ...bidderRequestDefault, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: { fiftyonedegrees_deviceId: '17595-133085-133468-18092' }, + }, + }, + }, + }; + const request = spec.buildRequests(bidRequests, ortb2DeviceBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); + }); + + it('should add hardwareConcurrency info to payload', function () { + const originalHardwareConcurrency = window.top.navigator.hardwareConcurrency; + + const mockHardwareConcurrency = (value) => { + Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { + value, + configurable: true, + }); + }; + + try { + const mockValue = 8; + mockHardwareConcurrency(mockValue); + + const requestWithHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithHardwareConcurrency = JSON.parse(requestWithHardwareConcurrency.data); + + expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.exist; + expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.deep.equal(mockValue); + + mockHardwareConcurrency(undefined); + + const requestWithoutHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutHardwareConcurrency = JSON.parse(requestWithoutHardwareConcurrency.data); + + expect(payloadWithoutHardwareConcurrency.hardwareConcurrency).to.not.exist; + } finally { + Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { + value: originalHardwareConcurrency, + configurable: true, + }); + } + }); + + it('should add deviceMemory info to payload', function () { + const originalDeviceMemory = window.top.navigator.deviceMemory; + + const mockDeviceMemory = (value) => { + Object.defineProperty(window.top.navigator, 'deviceMemory', { + value, + configurable: true, + }); + }; + + try { + const mockValue = 4; + mockDeviceMemory(mockValue); + + const requestWithDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithDeviceMemory = JSON.parse(requestWithDeviceMemory.data); + + expect(payloadWithDeviceMemory.deviceMemory).to.exist; + expect(payloadWithDeviceMemory.deviceMemory).to.deep.equal(mockValue); + + mockDeviceMemory(undefined); + + const requestWithoutDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutDeviceMemory = JSON.parse(requestWithoutDeviceMemory.data); + + expect(payloadWithoutDeviceMemory.deviceMemory).to.not.exist; + } finally { + Object.defineProperty(window.top.navigator, 'deviceMemory', { + value: originalDeviceMemory, + configurable: true, + }); + } + }); + }); + + describe('pageTitle', function () { + it('should add pageTitle info to payload based on document title', function () { + const testText = 'This is a title'; + sandbox.stub(window.top.document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + + it('should add pageTitle info to payload based on open-graph title', function () { + const testText = 'This is a title from open-graph'; + sandbox.stub(window.top.document, 'title').value(''); + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + + it('should add pageTitle info to payload sliced on 300 first characters', function () { + const testText = Array(500).join('a'); + sandbox.stub(window.top.document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.have.length(300); + }); + + it('should add pageTitle info to payload when fallbacking from window.top', function () { + const testText = 'This is a fallback title'; + sandbox.stub(window.top.document, 'querySelector').throws(); + sandbox.stub(document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + }); + + describe('pageDescription', function () { + it('should add pageDescription info to payload based on open-graph description', function () { + const testText = 'This is a description'; + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add pageDescription info to payload based on open-graph description', function () { + const testText = 'This is a description from open-graph'; + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[property="og:description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add pageDescription info to payload sliced on 300 first characters', function () { + const testText = Array(500).join('a'); + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.have.length(300); + }); + + it('should add pageDescription info to payload when fallbacking from window.top', function () { + const testText = 'This is a fallback description'; + sandbox.stub(window.top.document, 'querySelector').throws(); + sandbox.stub(document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add timeToFirstByte info to payload for Navigation Timing V2', function () { + // Mock `performance` object with Navigation Timing V2 data + const mockPerformance = { + getEntriesByType: () => [ + { requestStart: 100, responseStart: 150 }, + ], + }; + + // Override the global performance object + const originalPerformance = window.performance; + window.performance = mockPerformance; + + // Execute the code + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + // Calculate expected TTFB for V2 + const ttfbExpected = Math.round( + mockPerformance.getEntriesByType('navigation')[0].responseStart - + mockPerformance.getEntriesByType('navigation')[0].requestStart + ).toString(); + + // Assertions + expect(payload.timeToFirstByte).to.exist; + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpected); + + // Restore the original performance object + window.performance = originalPerformance; + }); + + it('should add timeToFirstByte info to payload for Navigation Timing V1', function () { + // Mock `performance` object with Navigation Timing V1 data + const mockPerformance = { + timing: { + requestStart: 100, + responseStart: 150, + }, + getEntriesByType: () => [], + }; + + // Override the global performance object + const originalPerformance = window.performance; + window.performance = mockPerformance; + + // Execute the code + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + // Calculate expected TTFB for V1 + const ttfbExpected = ( + mockPerformance.timing.responseStart - mockPerformance.timing.requestStart + ).toString(); + + // Assertions + expect(payload.timeToFirstByte).to.exist; + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpected); + + // Restore the original performance object + window.performance = originalPerformance; + }); + + it('should send GDPR to endpoint with 11 status', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': false, + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(11); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR TCF2 to endpoint with 12 status', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 22 status', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': undefined, + 'gdprApplies': undefined, + 'vendorData': undefined, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(''); + expect(payload.gdpr_iab.status).to.equal(22); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 0 status', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': false, + 'vendorData': { + 'hasGlobalScope': false + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(0); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 0 status when gdprApplies = false (vendorData = undefined)', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': undefined, + 'gdprApplies': false, + 'vendorData': undefined, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(''); + expect(payload.gdpr_iab.status).to.equal(0); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 12 status when apiVersion = 0', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 0 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(0); + }); + + it('should add schain info to payload if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + } + }); + + const request = spec.buildRequests([bidRequest], bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.schain).to.exist; + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + }); + }); + + it('should add userAgentClientHints info to payload if available', function () { + const sua = { + source: 2, + platform: { + brand: 'macOS', + version: ['12', '4', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + + const bidRequest = Object.assign({}, bidRequests[0], { + ortb2: { + device: { + sua: sua + } + } + }); + + const requestWithUserAgentClientHints = spec.buildRequests([bidRequest], bidderRequestDefault); + const payload = JSON.parse(requestWithUserAgentClientHints.data); + + expect(payload.userAgentClientHints).to.exist; + expect(payload.userAgentClientHints).to.deep.equal(sua); + + const defaultRequest = spec.buildRequests(bidRequests, bidderRequestDefault); + expect(JSON.parse(defaultRequest.data).userAgentClientHints).to.not.exist; + }); + + it('should use good mediaTypes banner sizes', function () { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + } + }; + checkMediaTypesSizes(mediaTypesBannerSize, '300x250'); + }); + }); + + describe('Global Placement Id', function () { + let bidRequests = [ + { + 'bidder': 'greenbids', + 'params': { + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + }, + { + 'bidder': 'greenbids', + 'params': { + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ef', + 'deviceWidth': 1680 + } + ]; + + it('should add gpid if ortb2Imp.ext.gpid is present and is non empty', function () { + const updatedBidRequests = bidRequests.map(function (bidRequest, index) { + return { + ...bidRequest, + ortb2Imp: { + ext: { + gpid: '1111/home-left-' + index + } + } + }; + } + ); + const request = spec.buildRequests(updatedBidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.data[0].gpid).to.equal('1111/home-left-0'); + expect(payload.data[1].gpid).to.equal('1111/home-left-1'); + }); + + it('should not add gpid if ortb2Imp.ext.gpid is present but empty', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + gpid: '' + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + payload.data.forEach(bid => { + expect(bid).not.to.have.property('gpid'); + }); + }); + + it('should not add gpid if ortb2Imp.ext.gpid is not present', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + payload.data.forEach(bid => { + expect(bid).not.to.have.property('gpid'); + }); + }); + + it('should add dsa info to payload if available', function () { + const bidRequestWithDsa = Object.assign({}, bidderRequestDefault, { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + } + } + } + }); + + const requestWithDsa = spec.buildRequests(bidRequests, bidRequestWithDsa); + const payload = JSON.parse(requestWithDsa.data); + + expect(payload.dsa).to.exist; + expect(payload.dsa).to.deep.equal( + { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + ); + + const defaultRequest = spec.buildRequests(bidRequests, bidderRequestDefault); + expect(JSON.parse(defaultRequest.data).dsa).to.not.exist; + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid responses', function () { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'bidId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'placementId': 4242 + }, { + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 200, + 'bidId': '4fef3b4gb1ec15', + 'ttl': 360, + 'width': 350, + 'creativeId': 'fs3ff', + 'placementId': 4242, + 'dealId': 'ABC_123', + 'ext': { + 'dsa': { + 'behalf': 'some-behalf', + 'paid': 'some-paid', + 'transparency': [{ + 'domain': 'test.com', + 'dsaparams': [1, 2, 3] + }], + 'adrender': 1 + } + } + }] + } + }; + let expectedResponse = [ + { + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'netRevenue': true, + 'meta': { + advertiserDomains: [] + }, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '3ede2a3fa0db94', + 'creativeId': 'er2ee', + 'placementId': 4242 + }, { + 'cpm': 0.5, + 'width': 350, + 'height': 200, + 'currency': 'USD', + 'netRevenue': true, + 'meta': { + advertiserDomains: [], + dsa: { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 + } + }, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '4fef3b4gb1ec15', + 'creativeId': 'fs3ff', + 'placementId': 4242, + 'dealId': 'ABC_123' + } + ] + ; + + let result = spec.interpretResponse(bids); + expect(result).to.eql(expectedResponse); + }); + + it('handles nobid responses', function () { + let bids = { + 'body': { + 'responses': [] + } + }; + + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); +}); + +let bidderRequestDefault = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 +}; + +let bidRequests = [ + { + 'bidder': 'greenbids', + 'params': { + 'placementId': 4242 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + } +]; + +function checkMediaTypesSizes(mediaTypes, expectedSizes) { + const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); + const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderRequestDefault); + const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); + + return payloadWithBannerSizes.data.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); +} From 8686d9eeaf029a4ccfb24602f9594eeefb065056 Mon Sep 17 00:00:00 2001 From: Hugh0222 Date: Wed, 18 Dec 2024 05:12:13 +0800 Subject: [PATCH 0771/1097] Brainx Bid Adapter : initial release (#12413) * add brainx adpater * fix adpater md * delete console * fix repeat * Modify fixed parameters * Modify endpoint to be optional * fix email * update endpoint url * remove hello_html & x-domain history changes * fix size * remove empty line * Update brainxBidAdapter.md --------- Co-authored-by: hugh.qu Co-authored-by: Chris Huie --- integrationExamples/gpt/hello_world.html | 2 +- modules/brainxBidAdapter.js | 117 ++++++++++++++++++ modules/brainxBidAdapter.md | 56 +++++++++ test/spec/modules/brainxBidAdapter_spec.js | 132 +++++++++++++++++++++ 4 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 modules/brainxBidAdapter.js create mode 100644 modules/brainxBidAdapter.md create mode 100644 test/spec/modules/brainxBidAdapter_spec.js diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 03a2356f0ef..bcf0c2fa1f2 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -92,4 +92,4 @@
Div-1
- \ No newline at end of file + diff --git a/modules/brainxBidAdapter.js b/modules/brainxBidAdapter.js new file mode 100644 index 00000000000..69832987fb7 --- /dev/null +++ b/modules/brainxBidAdapter.js @@ -0,0 +1,117 @@ +import { deepAccess, generateUUID, isArray, logWarn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +// import { config } from 'src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +// import { config } from '../src/config.js'; + +const BIDDER_CODE = 'brainx'; +const METHOD = 'POST'; +const TTL = 200; +const NET_REV = true; +let ENDPOINT = 'https://dsp.brainx.tech/bid' +// let ENDPOINT = 'http://adx-engine-gray.tec-do.cn/bid' + +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: NET_REV, // or false if your adapter should set bidResponse.netRevenue = false + ttl: TTL // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + } +}); + +export const spec = { + code: BIDDER_CODE, + // gvlid: IAB_GVL_ID, + // aliases: [ + // { code: "myalias", gvlid: IAB_GVL_ID_IF_DIFFERENT } + // ], + isBidRequestValid: function (bid) { + if (!(hasBanner(bid) || hasVideo(bid))) { + logWarn('Invalid bid request - missing required mediaTypes'); + return false; + } + if (!(bid && bid.params)) { + logWarn('Invalid bid request - missing required bid data'); + return false; + } + + if (!(bid.params.pubId)) { + logWarn('Invalid bid request - missing required field pubId'); + return false; + } + return true; + }, + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ bidRequests, bidderRequest }) + ENDPOINT = String(deepAccess(bidRequests[0], 'params.endpoint')) ? deepAccess(bidRequests[0], 'params.endpoint') : ENDPOINT + data.user = { + buyeruid: generateUUID() + } + return { + method: METHOD, + url: `${ENDPOINT}?token=${String(deepAccess(bidRequests[0], 'params.pubId'))}`, + data + } + }, + interpretResponse(response, request) { + let bids = []; + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + response.body.seatbid.forEach(function (bidder) { + if (isArray(bidder.bid)) { + bidder.bid.map((bid) => { + let serverBody = response.body; + // bidRequest = request.originalBidRequest, + let mediaType = BANNER; + let currency = serverBody.cur || 'USD' + + const cpm = (parseFloat(bid.price) || 0).toFixed(2); + const categories = deepAccess(bid, 'cat', []); + + const bidRes = { + ad: bid.adm, + width: bid.w, + height: bid.h, + requestId: bid.impid, + cpm: cpm, + currency: currency, + mediaType: mediaType, + ttl: TTL, + creativeId: bid.crid || bid.id, + netRevenue: NET_REV, + nurl: bid.nurl, + lurl: bid.lurl, + meta: { + mediaType: mediaType, + primaryCatId: categories[0], + secondaryCatIds: categories.slice(1), + } + }; + if (bid.adomain && isArray(bid.adomain) && bid.adomain.length > 0) { + bidRes.meta.advertiserDomains = bid.adomain; + bidRes.meta.clickUrl = bid.adomain[0]; + } + bids.push(bidRes); + }) + } + }); + } + + return bids; + }, + // getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { }, + // onTimeout: function (timeoutData) { }, + // onBidWon: function (bid) { }, + // onSetTargeting: function (bid) { }, + // onBidderError: function ({ error, bidderRequest }) { }, + // onAdRenderSucceeded: function (bid) { }, + supportedMediaTypes: [BANNER] +} +function hasBanner(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} +function hasVideo(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +registerBidder(spec); diff --git a/modules/brainxBidAdapter.md b/modules/brainxBidAdapter.md new file mode 100644 index 00000000000..a734147321b --- /dev/null +++ b/modules/brainxBidAdapter.md @@ -0,0 +1,56 @@ +# brianx Bidder Adapter + +## Overview + +``` +Module Name: brianx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: brainx.official@tec-do.com +``` + +## Description + +Module that connects to brianx's demand sources + +## Bid Parameters + +| Name | Scope | Type | Description | Example | +| ---------- | ------------ | ------ | ------------------------------------ | -------------------------------------- | +| `pubId` | required | String | The Pub Id provided by Brainx Ads. | `F7B53DBC-85C1-4685-9A06-9CF4B6261FA3` | +| `endpoint` | optional | String | The endpoint provided by Brainx Url. | `https://dsp.brainx.tech/bid` | + +## Example + +### Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [320, 480] + ] + } + }, + bids: [{ + bidder: 'brianx', + params: { + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'https://dsp.brainx.tech/bid' + } + }] +}]; +``` + +* For video ads, enable prebid cache. + +```javascript +pbjs.setConfig({ + ortb2: { + ortbVersion: '2.5' + }, + debug: false // or true +}); +``` diff --git a/test/spec/modules/brainxBidAdapter_spec.js b/test/spec/modules/brainxBidAdapter_spec.js new file mode 100644 index 00000000000..1d01e2cc642 --- /dev/null +++ b/test/spec/modules/brainxBidAdapter_spec.js @@ -0,0 +1,132 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from 'modules/brainxBidAdapter.js'; +import utils, { deepClone } from '../../../src/utils'; +// import adapter from 'src/adapters/'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; + +describe('Brain-X Aapater', function () { + describe('isBidRequestValid', function () { + it('undefined bid should return false', function () { + expect(spec.isBidRequestValid()).to.be.false; + }); + + it('null bid should return false', function () { + expect(spec.isBidRequestValid(null)).to.be.false; + }); + + it('bid.params should be set', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('bid.params.pubId should be set', function () { + expect(spec.isBidRequestValid({ + params: { pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', endpoint: 'http://adx-engine-gray.tec-do.cn/bid' } + })).to.be.false; + }); + }) + + // describe('isBidRequestValid', function () { + // it('Test the banner request processing function', function () { + // const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + // expect(request).to.not.be.empty; + // const payload = request.data; + // expect(payload).to.not.be.empty; + // }); + // it('Test the video request processing function', function () { + // const request = spec.buildRequests(videoRequest, videoRequest[0]); + // expect(request).to.not.be.empty; + // const payload = request.data; + // expect(payload).to.not.be.empty; + // }); + // it('Test the param', function () { + // const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + // const payload = JSON.parse(request.data); + // expect(payload.imp[0].tagid).to.eql(videoRequest[0].params.tagid); + // expect(payload.imp[0].bidfloor).to.eql(videoRequest[0].params.bidfloor); + // }); + // }) + + describe('interpretResponse', function () { + it('Test banner interpretResponse', function () { + const serverResponse = { + body: { + 'bidid': 'a82042c055b04e539ec6876112c10ced1729663902983', + 'cur': 'USD', + 'id': '28f8f1f525372a', + 'seatbid': [ + { + 'bid': [ + { + 'adid': '76797', + 'adm': "
\n \n
\n
\n \n \n
\n
\n\n", + 'adomain': [ + 'taobao.com' + ], + 'bundle': 'com.taobao', + 'burl': 'https://adx-event-server.bidtrail.top/billing?s=Eid0ZWMxNDk4NDQzODk3MjcwOTU1MzAwNzM3YjczMTQwMTA4ODk5ODQaDjI4ZjhmMWY1MjUzNzJhIKkCKgbmtYvor5U6DmxvY2FsaG9zdDo5OTk5Qgl1bmRlZmluZWRKA0hLR1ADYAFoAXADeAOAAQKKAQpjb20udGFvYmFvlQEX2U49nQEX2U49pQEX2U49ugEqCO4HEiUI7gcSCFRlY2RvRFNQGA01F9lOPTgBQBVKC1RlY2RvRFNQX1NHwAHuB8gBjcqCwKsy0AEC-gGbAmh0dHBzOi8vbm90aWNlLXNnLmJpZHRyYWlsLnRvcC93aW4_YmlkX2lkPWE4MjA0MmMwNTViMDRlNTM5ZWM2ODc2MTEyYzEwY2VkMTcyOTY2MzkwMjk4MyZjYW1wYWlnbl9pZD00MjgmYWRfZ3JvdXBfaWQ9OTc2MSZhZF9pZD03Njc5NyZjcmVhdGl2ZV9pZD02NTI2JmFmZmlsaWF0ZV9pZD0yOTcmYnVuZGxlPSZmaXJzdF9zc3A9YnJhaW54LnRlY2gmcHVibGlzaF9pZD0mb3M9QW5kcm9pZCZ0YWdfaWQ9dW5kZWZpbmVkJmJpZF9zdWNjZXNzX3ByaWNlPTAuMDUwNSZzaWduPWUzODQyNjhiYzAzYzhmYmOSAgp0YW9iYW8uY29togILYnJhaW54LnRlY2ioAgGyAgdhbmRyb2lkwAICygICc2fQAgHYAo3hlcWrMuACjeGVxasy&v=P-1b7JJWs-uUQ68A37V4xDLplU0&auction_price=${AUCTION_PRICE}', + 'cat': [ + 'IAB18-5' + ], + 'cid': '428', + 'crid': 'D06g1RVGMEnC+9Le4SZMJw==', + 'h': 480, + 'id': 'a82042c055b04e539ec6876112c10ced1729663902983', + 'impid': '3c1dd9e1700358', + 'iurl': 'https://creative.bidtrail.top/png/2/20f24c10f21e/091b422e3014033e57acffcf2a5c71dbb17383ec15ac9421', + 'lurl': 'https://notice-sg.bidtrail.top/loss?bid_id=a82042c055b04e539ec6876112c10ced1729663902983&sign=e384268bc03c8fbc&campaign_id=428&ad_group_id=9761&ad_id=76797&creative_id=6526&affiliate_id=297&loss_code=${AUCTION_LOSS}', + 'nurl': 'https://adx-event-server.bidtrail.top/winnotice?s=Eid0ZWMxNDk4NDQzODk3MjcwOTU1MzAwNzM3YjczMTQwMTA4ODk5ODQaDjI4ZjhmMWY1MjUzNzJhIKkCKgbmtYvor5U6DmxvY2FsaG9zdDo5OTk5Qgl1bmRlZmluZWRKA0hLR1ADYAFoAXADeAOAAQKKAQpjb20udGFvYmFvlQEX2U49nQEX2U49pQEX2U49ugEqCO4HEiUI7gcSCFRlY2RvRFNQGA01F9lOPTgBQBVKC1RlY2RvRFNQX1NHwAHuB8gBjcqCwKsy0AEB-gGbAmh0dHBzOi8vbm90aWNlLXNnLmJpZHRyYWlsLnRvcC93aW4_YmlkX2lkPWE4MjA0MmMwNTViMDRlNTM5ZWM2ODc2MTEyYzEwY2VkMTcyOTY2MzkwMjk4MyZjYW1wYWlnbl9pZD00MjgmYWRfZ3JvdXBfaWQ9OTc2MSZhZF9pZD03Njc5NyZjcmVhdGl2ZV9pZD02NTI2JmFmZmlsaWF0ZV9pZD0yOTcmYnVuZGxlPSZmaXJzdF9zc3A9YnJhaW54LnRlY2gmcHVibGlzaF9pZD0mb3M9QW5kcm9pZCZ0YWdfaWQ9dW5kZWZpbmVkJmJpZF9zdWNjZXNzX3ByaWNlPTAuMDUwNSZzaWduPWUzODQyNjhiYzAzYzhmYmOSAgp0YW9iYW8uY29togILYnJhaW54LnRlY2ioAgGyAgdhbmRyb2lkwAICygICc2fQAgHYAo3hlcWrMuACjeGVxasy&v=jFUg_b6F14d50JL-M6UE5Jc8VuA&auction_price=${AUCTION_PRICE}', + 'price': 0.0505, + 'w': 320 + } + ], + 'group': 0, + 'seat': 'agency' + } + ] + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + auctionId: '3eedbf83-7d1d-423c-be27-39e4af687040', + auctionStart: 1729663900819, + adUnitCode: 'dev-1', + bidId: '28f8f1f525372a', + bidder: 'brainx', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'http://adx-engine-gray.tec-do.cn/bid' + }, + src: 'client' + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(BANNER); + expect(bidResponse.requestId).to.equal(bid.impid); + expect(bidResponse.cpm).to.equal(parseFloat(bid.price).toFixed(2)) + expect(bidResponse.currency).to.equal(serverResponse.body.cur); + expect(bidResponse.creativeId).to.equal(bid.crid || bid.id); + expect(bidResponse.netRevenue).to.be.true; + expect(bidResponse.nurl).to.equal(bid.nurl); + expect(bidResponse.lurl).to.equal(bid.lurl); + + expect(bidResponse.meta).to.be.an('object'); + expect(bidResponse.meta.mediaType).to.equal(BANNER); + expect(bidResponse.meta.primaryCatId).to.equal('IAB18-5'); + // expect(bidResponse.meta.secondaryCatIds).to.deep.equal(['IAB8']); + expect(bidResponse.meta.advertiserDomains).to.deep.equal(bid.adomain); + expect(bidResponse.meta.clickUrl).to.equal(bid.adomain[0]); + + expect(bidResponse.ad).to.equal(bid.adm); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + }); + }); +}); From d3f3696d59df559d659f596214b65cac8da1cff9 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 18 Dec 2024 04:37:34 -0800 Subject: [PATCH 0772/1097] dfpAdServerVideo: set vconp (continuous playblack on) when playbackmethod contains 7 (#12590) --- modules/dfpAdServerVideo.js | 2 +- test/spec/modules/dfpAdServerVideo_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 87ead9fe980..773b3896270 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -133,7 +133,7 @@ export function buildDfpVideoUrl(options) { return 'preroll'; } }, - vconp: () => Array.isArray(video?.playbackmethod) && video.playbackmethod.every(m => m === 7) ? '2' : undefined, + vconp: () => Array.isArray(video?.playbackmethod) && video.playbackmethod.some(m => m === 7) ? '2' : undefined, vpa() { // playbackmethod = 3 is play on click; 1, 2, 4, 5, 6 are autoplay if (Array.isArray(video?.playbackmethod)) { diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 092cd1ff0f3..75765771d1a 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -271,7 +271,7 @@ describe('The DFP video support module', function () { video: { playbackmethod: [7, 1] }, - expected: undefined + expected: '2' } ], vpa: [ From cc2e8d20e9113082d72d9130d11d113913bfd67f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 18 Dec 2024 10:18:18 -0800 Subject: [PATCH 0773/1097] PBS Adapter: only include known bidders in eidpermissions (#12594) --- .../prebidServerBidAdapter/bidderConfig.js | 9 +++++--- .../prebidServerBidAdapter/ortbConverter.js | 2 +- .../modules/prebidServerBidAdapter_spec.js | 21 +++++++++++++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/modules/prebidServerBidAdapter/bidderConfig.js b/modules/prebidServerBidAdapter/bidderConfig.js index 44ab2f90d42..3b51810ff20 100644 --- a/modules/prebidServerBidAdapter/bidderConfig.js +++ b/modules/prebidServerBidAdapter/bidderConfig.js @@ -121,7 +121,7 @@ export function consolidateEids({eids, conflicts = new Set()}) { } } -function replaceEids({global, bidder}) { +function replaceEids({global, bidder}, requestedBidders) { const consolidated = consolidateEids(extractEids({global, bidder})); global = deepClone(global); bidder = deepClone(bidder); @@ -134,6 +134,9 @@ function replaceEids({global, bidder}) { if (consolidated.global.length) { deepSetValue(global, 'user.ext.eids', consolidated.global); } + if (requestedBidders?.length) { + consolidated.permissions.forEach((permission) => permission.bidders = permission.bidders.filter(bidder => requestedBidders.includes(bidder))); + } if (consolidated.permissions.length) { deepSetValue(global, 'ext.prebid.data.eidpermissions', consolidated.permissions); } @@ -145,11 +148,11 @@ function replaceEids({global, bidder}) { return {global, bidder} } -export function premergeFpd(ortb2Fragments) { +export function premergeFpd(ortb2Fragments, requestedBidders) { if (ortb2Fragments == null || Object.keys(ortb2Fragments.bidder || {}).length === 0) { return ortb2Fragments; } else { - ortb2Fragments = replaceEids(ortb2Fragments); + ortb2Fragments = replaceEids(ortb2Fragments, requestedBidders); return { ...ortb2Fragments, bidder: getPBSBidderConfig(ortb2Fragments) diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 2cc34586102..51a01e04fdd 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -299,7 +299,7 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste requestTimestamp, s2sBidRequest: { ...s2sBidRequest, - ortb2Fragments: premergeFpd(s2sBidRequest.ortb2Fragments) + ortb2Fragments: premergeFpd(s2sBidRequest.ortb2Fragments, requestedBidders) }, requestedBidders, actualBidderRequests: bidderRequests, diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 058175b878c..85ae7909f34 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2115,6 +2115,23 @@ describe('S2S Adapter', function () { }]); }); + it('should not set eidpermissions for unrequested bidders', () => { + req.ortb2Fragments.bidder.unknown = { + user: { + eids: [{source: 'idC', id: 3}, {source: 'idD', id: 4}] + } + } + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + expect(payload.ext.prebid.data.eidpermissions).to.eql([{ + bidders: ['appnexus'], + source: 'idC' + }, { + bidders: [], + source: 'idD' + }]); + }) + it('should repeat global EIDs when bidder-specific EIDs conflict', () => { BID_REQUESTS.push({ ...BID_REQUESTS[0], @@ -4710,8 +4727,8 @@ describe('S2S Adapter', function () { bidder: { bidderA: [mkEid('idA', 'idA2')] } - }) - }) + }); + }); }) }); }); From cc3e707267a80548166487d68d40179a15cbf03b Mon Sep 17 00:00:00 2001 From: vivekyadav15 Date: Thu, 19 Dec 2024 00:21:05 +0530 Subject: [PATCH 0774/1097] Bug fix: Export getProperties method (#12587) --- src/auction.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/auction.js b/src/auction.js index 3945e0bfe53..c7ccaa27b14 100644 --- a/src/auction.js +++ b/src/auction.js @@ -415,7 +415,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getFPD: () => ortb2Fragments, getMetrics: () => metrics, end: done.promise, - requestsDone: requestsDone.promise + requestsDone: requestsDone.promise, + getProperties }; } From 8162df6dff7fb038f38b4c3e4736f5042e71fd4a Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Wed, 18 Dec 2024 20:51:53 +0200 Subject: [PATCH 0775/1097] Adf Bid Adapter: use common ortb2 data (#12582) * use common ortb2 data from bidderRequest * set iab categories on bid response --- modules/adfBidAdapter.js | 56 +++++------- test/spec/modules/adfBidAdapter_spec.js | 117 +++++++++++------------- 2 files changed, 75 insertions(+), 98 deletions(-) diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index dc2cab498ea..70cd526065c 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -33,7 +33,7 @@ export const spec = { let app, site; const commonFpd = bidderRequest.ortb2 || {}; - let { user } = commonFpd; + let user = commonFpd.user || {}; if (typeof getConfig('app') === 'object') { app = getConfig('app') || {}; @@ -51,21 +51,35 @@ export const spec = { } } - const device = getConfig('device') || {}; + let device = getConfig('device') || {}; + if (commonFpd.device) { + mergeDeep(device, commonFpd.device); + } device.w = device.w || window.innerWidth; device.h = device.h || window.innerHeight; device.ua = device.ua || navigator.userAgent; + let source = commonFpd.source || {}; + source.fd = 1; + + let regs = commonFpd.regs || {}; + const adxDomain = setOnAny(validBidRequests, 'params.adxDomain') || 'adx.adform.net'; const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.ortb2?.source?.tid; const test = setOnAny(validBidRequests, 'params.test'); const currency = getCurrencyFromBidderRequest(bidderRequest); const cur = currency && [ currency ]; const eids = setOnAny(validBidRequests, 'userIdAsEids'); const schain = setOnAny(validBidRequests, 'schain'); - const dsa = commonFpd.regs?.ext?.dsa; + + if (eids) { + deepSetValue(user, 'ext.eids', eids); + } + + if (schain) { + deepSetValue(source, 'ext.schain', schain); + } const imp = validBidRequests.map((bid, id) => { bid.netRevenue = pt; @@ -151,10 +165,11 @@ export const spec = { app, user, device, - source: { tid, fd: 1 }, + source, ext: { pt }, cur, - imp + imp, + regs }; if (test) { @@ -162,31 +177,6 @@ export const spec = { request.test = 1; } - if (config.getConfig('coppa')) { - deepSetValue(request, 'regs.coppa', 1); - } - - if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); - } - - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (eids) { - deepSetValue(request, 'user.ext.eids', eids); - } - - if (schain) { - deepSetValue(request, 'source.ext.schain', schain); - } - - if (dsa) { - deepSetValue(request, 'regs.ext.dsa', dsa); - } - return { method: 'POST', url: 'https://' + adxDomain + '/adx/openrtb', @@ -224,7 +214,9 @@ export const spec = { meta: { mediaType, advertiserDomains: bidResponse.adomain, - dsa + dsa, + primaryCatId: bidResponse.cat?.[0], + secondaryCatIds: bidResponse.cat?.slice(1) } }; diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index 14abb33ba68..e5a2c9e76b5 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -82,33 +82,45 @@ describe('Adf adapter', function () { }); describe('user privacy', function () { - it('should send GDPR Consent data to adform if gdprApplies', function () { + it('should send GDPR Consent data to adform', function () { let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); - assert.equal(request.regs.ext.gdpr, bidderRequest.gdprConsent.gdprApplies); - assert.equal(typeof request.regs.ext.gdpr, 'number'); - }); - - it('should send gdpr as number', function () { - let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; + let ortb2 = { + regs: { + ext: { + gdpr: 1 + } + }, + user: { + ext: { + consent: 'consentDataString' + } + } + }; + let bidderRequest = { ortb2, refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - assert.equal(typeof request.regs.ext.gdpr, 'number'); + assert.equal(request.user.ext.consent, 'consentDataString'); assert.equal(request.regs.ext.gdpr, 1); }); it('should send CCPA Consent data to adform', function () { let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { uspConsent: '1YA-', refererInfo: { page: 'page' } }; + let ortb2 = { + regs: { + ext: { + us_privacy: '1YA-' + } + } + }; + let bidderRequest = { ortb2, refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.regs.ext.us_privacy, '1YA-'); - bidderRequest = { uspConsent: '1YA-', gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; + ortb2.regs.ext.gdpr = 1; + ortb2.user = { ext: { consent: 'consentDataString' } }; + + bidderRequest = { ortb2, refererInfo: { page: 'page' } }; request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.regs.ext.us_privacy, '1YA-'); @@ -116,34 +128,6 @@ describe('Adf adapter', function () { assert.equal(request.regs.ext.gdpr, 1); }); - it('should not send GDPR Consent data to adform if gdprApplies is undefined', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: { siteId: 'siteId' } - }]; - let bidderRequest = { gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, refererInfo: { page: 'page' } }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user.ext.consent, 'consentDataString'); - assert.equal(request.regs.ext.gdpr, 0); - - bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: { page: 'page' }}; - request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user, undefined); - assert.equal(request.regs, undefined); - }); - it('should send default GDPR Consent data to adform', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: { siteId: 'siteId' } - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); - - assert.equal(request.user, undefined); - assert.equal(request.regs, undefined); - }); - it('should transfer DSA info', function () { let validBidRequests = [ { bidId: 'bidId', params: { siteId: 'siteId' } } ]; @@ -200,7 +184,7 @@ describe('Adf adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,source,ext,imp'.split(','); + let keys = 'site,user,device,source,ext,imp,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId' } @@ -225,34 +209,30 @@ describe('Adf adapter', function () { assert.equal(request.source.fd, 1); }); - it('should not set coppa when coppa is not provided or is set to false', function () { - config.setConfig({ - }); + it('should send coppa flag', function () { + let ortb2 = { regs: { coppa: 1 } }; let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.regs.coppa, undefined); + let request = JSON.parse(spec.buildRequests(validBidRequests, { ortb2, refererInfo: { page: 'page' } }).data); - config.setConfig({ - coppa: false - }); - request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.regs.coppa, undefined); + assert.equal(request.regs.coppa, 1); }); - it('should set coppa to 1 when coppa is provided with value true', function () { + it('should send info about device', function () { config.setConfig({ - coppa: true + device: { w: 100, h: 100 } }); - let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: '1000' } + }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); - assert.equal(request.regs.coppa, 1); + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); }); - it('should send info about device', function () { + it('should merge ortb2.device info', function () { config.setConfig({ device: { w: 100, h: 100 } }); @@ -260,11 +240,13 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { mid: '1000' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); + let ortb2 = { device: { ua: 'customUA', w: 200, geo: { lat: 1, lon: 1 } } }; + let request = JSON.parse(spec.buildRequests(validBidRequests, { ortb2, refererInfo: { page: 'page' } }).data); - assert.equal(request.device.ua, navigator.userAgent); - assert.equal(request.device.w, 100); + assert.equal(request.device.ua, 'customUA'); + assert.equal(request.device.w, 200); assert.equal(request.device.h, 100); + assert.deepEqual(request.device.geo, { lat: 1, lon: 1 }); }); it('should send app info', function () { @@ -1075,7 +1057,8 @@ describe('Adf adapter', function () { }], adrender: 1 } - } + }, + cat: [ 'IAB1', 'IAB2' ] } ] }], @@ -1135,6 +1118,8 @@ describe('Adf adapter', function () { assert.deepEqual(bids[0].currency, serverResponse.body.cur); assert.deepEqual(bids[0].mediaType, 'native'); assert.deepEqual(bids[0].meta.mediaType, 'native'); + assert.deepEqual(bids[0].meta.primaryCatId, 'IAB1'); + assert.deepEqual(bids[0].meta.secondaryCatIds, [ 'IAB2' ]); assert.deepEqual(bids[0].meta.advertiserDomains, [ 'demo.com' ]); assert.deepEqual(bids[0].meta.dsa, { behalf: 'some-behalf', From c13b1b8b658b6ba334b4ebc9a24342d457585a60 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Wed, 18 Dec 2024 12:54:23 -0600 Subject: [PATCH 0776/1097] 33across ID System: Include hashed email from storage (#12529) * 33across ID System: Send hm param * fix typo thirthy to thirty * remove all the ID storage keys when response is successful but doesn't contain ID anymore. * Allow to send hashed email via config parameters --- modules/33acrossIdSystem.js | 24 ++- modules/33acrossIdSystem.md | 5 + test/spec/modules/33acrossIdSystem_spec.js | 202 +++++++++++++++------ 3 files changed, 173 insertions(+), 58 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 8f99846017a..fb5a7712f1f 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -27,6 +27,7 @@ const GVLID = 58; const STORAGE_FPID_KEY = '33acrossIdFp'; const STORAGE_TPID_KEY = '33acrossIdTp'; +const STORAGE_HEM_KEY = '33acrossIdHm' const DEFAULT_1PID_SUPPORT = true; const DEFAULT_TPID_SUPPORT = true; @@ -59,7 +60,7 @@ function calculateResponseObj(response) { }; } -function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) { +function calculateQueryStringParams({ pid, hem }, gdprConsentData, enabledStorageTypes) { const uspString = uspDataHandler.getConsentData(); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); @@ -97,6 +98,11 @@ function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) { params.tp = encodeURIComponent(tp); } + const hemParam = hem || getStoredValue(STORAGE_HEM_KEY, enabledStorageTypes); + if (hemParam) { + params.sha256 = encodeURIComponent(hemParam); + } + return params; } @@ -146,7 +152,7 @@ function handleSupplementalId(key, id, storageConfig) { } /** @type {Submodule} */ -export const thirthyThreeAcrossIdSubmodule = { +export const thirtyThreeAcrossIdSubmodule = { /** * used to link submodule with config * @type {string} @@ -188,7 +194,11 @@ export const thirthyThreeAcrossIdSubmodule = { return; } - const { pid, storeFpid = DEFAULT_1PID_SUPPORT, storeTpid = DEFAULT_TPID_SUPPORT, apiUrl = API_URL } = params; + const { + storeFpid = DEFAULT_1PID_SUPPORT, + storeTpid = DEFAULT_TPID_SUPPORT, apiUrl = API_URL, + ...options + } = params; return { callback(cb) { @@ -203,7 +213,9 @@ export const thirthyThreeAcrossIdSubmodule = { } if (!responseObj.envelope) { - deleteFromStorage(MODULE_NAME); + ['', '_last', '_exp', '_cst'].forEach(suffix => { + deleteFromStorage(`${MODULE_NAME}${suffix}`); + }); } if (storeFpid) { @@ -227,7 +239,7 @@ export const thirthyThreeAcrossIdSubmodule = { cb(); } - }, calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes), { + }, calculateQueryStringParams(options, gdprConsentData, enabledStorageTypes), { method: 'GET', withCredentials: true }); @@ -246,4 +258,4 @@ export const thirthyThreeAcrossIdSubmodule = { } }; -submodule('userId', thirthyThreeAcrossIdSubmodule); +submodule('userId', thirtyThreeAcrossIdSubmodule); diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index e983c8ab871..b6b68622344 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -51,5 +51,10 @@ The following settings are available in the `params` property in `userSync.userI | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` | +| hem | Optional | String | Hashed email address in sha256 format | `"ba4235544d6c91865fbf70fa1bdb70f2d375ded1b2b946b21c675dcbe9968cdc"` | | storeFpid | Optional | Boolean | Indicates whether a supplemental first-party ID may be stored to improve addressability, this feature is enabled by default | `true` (default) or `false` | | storeTpid | Optional | Boolean | Indicates whether a supplemental third-party ID may be stored to improve addressability, this feature is enabled by default | `true` (default) or `false` | + +### HEM Collection + +33Across ID System supports user's hashed email, if available in storage. diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 6ec3554d353..93e6a41d928 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -1,4 +1,4 @@ -import { thirthyThreeAcrossIdSubmodule, storage, domainUtils } from 'modules/33acrossIdSystem.js'; +import { thirtyThreeAcrossIdSubmodule, storage, domainUtils } from 'modules/33acrossIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; @@ -10,13 +10,13 @@ import {attachIdSystem} from '../../../modules/userId/index.js'; describe('33acrossIdSystem', () => { describe('name', () => { it('should expose the name of the submodule', () => { - expect(thirthyThreeAcrossIdSubmodule.name).to.equal('33acrossId'); + expect(thirtyThreeAcrossIdSubmodule.name).to.equal('33acrossId'); }); }); describe('gvlid', () => { it('should expose the vendor id', () => { - expect(thirthyThreeAcrossIdSubmodule.gvlid).to.equal(58); + expect(thirtyThreeAcrossIdSubmodule.gvlid).to.equal(58); }); }); @@ -24,7 +24,7 @@ describe('33acrossIdSystem', () => { it('should call endpoint and handle valid response', () => { const completeCallback = sinon.spy(); - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -65,7 +65,7 @@ describe('33acrossIdSystem', () => { it('should store the provided first-party ID in a cookie', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -103,7 +103,7 @@ describe('33acrossIdSystem', () => { it('should store the provided first-party ID in local storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -139,7 +139,7 @@ describe('33acrossIdSystem', () => { it('should store the provided first-party ID in each storage type', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -181,7 +181,7 @@ describe('33acrossIdSystem', () => { it('should wipe any existing first-party ID from storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -224,7 +224,7 @@ describe('33acrossIdSystem', () => { it('should store the provided third-party ID in a cookie', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -262,7 +262,7 @@ describe('33acrossIdSystem', () => { it('should store the provided third-party ID in local storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -298,7 +298,7 @@ describe('33acrossIdSystem', () => { it('should store the provided third-party ID in each storage type', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -340,7 +340,7 @@ describe('33acrossIdSystem', () => { it('should wipe any existing third-party ID from storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', ...opts @@ -383,7 +383,7 @@ describe('33acrossIdSystem', () => { it('should not store the provided first-party ID in a cookie', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', storeFpid: false @@ -421,7 +421,7 @@ describe('33acrossIdSystem', () => { it('should not store the provided first-party ID in local storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', storeFpid: false @@ -459,7 +459,7 @@ describe('33acrossIdSystem', () => { it('should not store the provided third-party ID in a cookie', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', storeTpid: false @@ -495,7 +495,7 @@ describe('33acrossIdSystem', () => { it('should not store the provided third-party ID in local storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', storeTpid: false @@ -532,7 +532,7 @@ describe('33acrossIdSystem', () => { it('should wipe any existing "envelope" ID from storage', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' }, @@ -558,8 +558,10 @@ describe('33acrossIdSystem', () => { expires: 1645667805067 })); - expect(removeDataFromLocalStorage.calledWith('33acrossId')).to.be.true; - expect(setCookie.calledWithExactly('33acrossId', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + ['', '_last', '_exp', '_cst'].forEach(suffix => { + expect(removeDataFromLocalStorage.calledWith(`33acrossId${suffix}`)).to.be.true; + expect(setCookie.calledWithExactly(`33acrossId${suffix}`, '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + }); removeDataFromLocalStorage.restore(); setCookie.restore(); @@ -571,7 +573,7 @@ describe('33acrossIdSystem', () => { it('should log a warning and don\'t expect a call to the endpoint', () => { const logWarnSpy = sinon.spy(utils, 'logWarn'); - const result = thirthyThreeAcrossIdSubmodule.getId({ + const result = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -589,7 +591,7 @@ describe('33acrossIdSystem', () => { context('when GDPR doesn\'t apply', () => { it('should call endpoint with \'gdpr=0\'', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -607,7 +609,7 @@ describe('33acrossIdSystem', () => { context('but the GDPR consent string is given', () => { it('should call endpoint with the GDPR consent string', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -628,7 +630,7 @@ describe('33acrossIdSystem', () => { context('when a valid US Privacy string is given', () => { it('should call endpoint with the US Privacy parameter', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -649,7 +651,7 @@ describe('33acrossIdSystem', () => { context('when an invalid US Privacy is given', () => { it('should call endpoint without the US Privacy parameter', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -671,7 +673,7 @@ describe('33acrossIdSystem', () => { context('when coppa is enabled', () => { it('should call endpoint with an enabled coppa signal', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -692,7 +694,7 @@ describe('33acrossIdSystem', () => { context('when coppa is not enabled', () => { it('should call endpoint with coppa signal not enabled', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -726,7 +728,7 @@ describe('33acrossIdSystem', () => { { gppString: 'foo', expected: 'foo' }, ].forEach(({ gppString, expected }, index) => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -752,7 +754,7 @@ describe('33acrossIdSystem', () => { { applicableSections: ['1', '2'], expected: '1%2C2' }, ].forEach(({ applicableSections, expected }, index) => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -773,7 +775,7 @@ describe('33acrossIdSystem', () => { context('when a first-party ID is present in local storage', () => { it('should call endpoint with the encoded first-party ID included', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' }, @@ -798,7 +800,7 @@ describe('33acrossIdSystem', () => { context('when a first-party ID is present in cookie storage', () => { it('should call endpoint with the first-party ID included', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' }, @@ -823,7 +825,7 @@ describe('33acrossIdSystem', () => { context('when a first-party ID is not present in storage', () => { it('should not call endpoint with the first-party ID included', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -840,7 +842,7 @@ describe('33acrossIdSystem', () => { context('when a third-party ID is present in local storage', () => { it('should call endpoint with the encoded third-party ID included', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' }, @@ -865,7 +867,7 @@ describe('33acrossIdSystem', () => { context('when a third-party ID is present in cookie storage', () => { it('should call endpoint with the third-party ID included', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' }, @@ -890,7 +892,7 @@ describe('33acrossIdSystem', () => { context('when a third-party ID is not present in storage', () => { it('should not call endpoint with the third-party ID included', () => { const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -904,11 +906,107 @@ describe('33acrossIdSystem', () => { }); }); + context('when a hashed email is provided via configuration options', () => { + it('should call endpoint with the hashed email included', () => { + const completeCallback = () => {}; + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + hem: '33acrossIdHmValue+' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + // Prioritizes the hem given via config options over the one stored in LS. + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdHm') + .returns('33acrossIdHmValueLS'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('sha256=33acrossIdHmValue%2B'); + + storage.getDataFromLocalStorage.restore(); + }); + }); + + context('when a hashed email is NOT provided via configuration options', () => { + context('but it\'s provided via local storage', () => { + it('should call endpoint with the hashed email included', () => { + const completeCallback = () => {}; + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdHm') + .returns('33acrossIdHmValue+'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('sha256=33acrossIdHmValue%2B'); + + storage.getDataFromLocalStorage.restore(); + }); + }); + + context('but it\'s provided via cookie storage', () => { + it('should call endpoint with the hashed email included', () => { + const completeCallback = () => {}; + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie' ], + storage: {} + }); + + sinon.stub(storage, 'getCookie') + .withArgs('33acrossIdHm') + .returns('33acrossIdHmValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('sha256=33acrossIdHmValue'); + + storage.getCookie.restore(); + }); + }); + + context('and hashed email is not present in storage', () => { + it('should not call endpoint with the hashed email included', () => { + const completeCallback = () => {}; + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).not.to.contain('sha256='); + }); + }); + }); + context('when the partner ID is not given', () => { it('should log an error', () => { const logErrorSpy = sinon.spy(utils, 'logError'); - thirthyThreeAcrossIdSubmodule.getId({ + thirtyThreeAcrossIdSubmodule.getId({ params: { /* No 'pid' param */ } }); @@ -922,7 +1020,7 @@ describe('33acrossIdSystem', () => { it('should log an error', () => { const logErrorSpy = sinon.spy(utils, 'logError'); - thirthyThreeAcrossIdSubmodule.getId({ + thirtyThreeAcrossIdSubmodule.getId({ params: { pid: 123456 // PID must be a string } @@ -939,7 +1037,7 @@ describe('33acrossIdSystem', () => { const logErrorSpy = sinon.spy(utils, 'logError'); const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -953,7 +1051,7 @@ describe('33acrossIdSystem', () => { 'Content-Type': 'application/json' }, 'invalid response'); - expect(logErrorSpy.lastCall.args[0]).to.eq(`${thirthyThreeAcrossIdSubmodule.name}: ID reading error:`); + expect(logErrorSpy.lastCall.args[0]).to.eq(`${thirtyThreeAcrossIdSubmodule.name}: ID reading error:`); logErrorSpy.restore(); }); @@ -961,7 +1059,7 @@ describe('33acrossIdSystem', () => { it('should execute complete callback with undefined value', () => { const completeCallback = sinon.spy(); - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -982,7 +1080,7 @@ describe('33acrossIdSystem', () => { context('when an endpoint override is given', () => { it('should call that endpoint', () => { const completeCallback = sinon.spy(); - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', apiUrl: 'https://staging-lexicon.33across.com/v1/envelope' @@ -1012,7 +1110,7 @@ describe('33acrossIdSystem', () => { const logErrorSpy = sinon.spy(utils, 'logError'); const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -1029,7 +1127,7 @@ describe('33acrossIdSystem', () => { error: 'foo' })); - expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: Unsuccessful response foo`)).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(`${thirtyThreeAcrossIdSubmodule.name}: Unsuccessful response foo`)).to.be.true; logErrorSpy.restore(); }); @@ -1037,7 +1135,7 @@ describe('33acrossIdSystem', () => { it('should execute complete callback with undefined value', () => { const completeCallback = sinon.spy(); - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -1063,7 +1161,7 @@ describe('33acrossIdSystem', () => { const logMessageSpy = sinon.spy(utils, 'logMessage'); const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -1080,7 +1178,7 @@ describe('33acrossIdSystem', () => { data: {} })); - expect(logMessageSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: No envelope was received`)).to.be.true; + expect(logMessageSpy.calledOnceWithExactly(`${thirtyThreeAcrossIdSubmodule.name}: No envelope was received`)).to.be.true; logMessageSpy.restore(); }); @@ -1088,7 +1186,7 @@ describe('33acrossIdSystem', () => { it('should execute complete callback with undefined value', () => { const completeCallback = sinon.spy(); - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -1114,7 +1212,7 @@ describe('33acrossIdSystem', () => { const logErrorSpy = sinon.spy(utils, 'logError'); const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -1126,7 +1224,7 @@ describe('33acrossIdSystem', () => { request.respond(404); - expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: ID error response`, 'Not Found')).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(`${thirtyThreeAcrossIdSubmodule.name}: ID error response`, 'Not Found')).to.be.true; logErrorSpy.restore(); }); @@ -1134,7 +1232,7 @@ describe('33acrossIdSystem', () => { it('should execute complete callback without any value', () => { const completeCallback = sinon.spy(); - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } @@ -1153,8 +1251,8 @@ describe('33acrossIdSystem', () => { describe('decode', () => { it('should wrap the given value inside an object literal', () => { - expect(thirthyThreeAcrossIdSubmodule.decode('foo')).to.deep.equal({ - [thirthyThreeAcrossIdSubmodule.name]: { + expect(thirtyThreeAcrossIdSubmodule.decode('foo')).to.deep.equal({ + [thirtyThreeAcrossIdSubmodule.name]: { envelope: 'foo' } }); @@ -1162,7 +1260,7 @@ describe('33acrossIdSystem', () => { }); describe('eid', () => { before(() => { - attachIdSystem(thirthyThreeAcrossIdSubmodule); + attachIdSystem(thirtyThreeAcrossIdSubmodule); }) it('33acrossId', function() { const userId = { From 6fe63f6ddfced91af3f7c9e473c509f517d6769e Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:01:58 +0200 Subject: [PATCH 0777/1097] Smarthub Bid Adapter : set skipPbsAliasing to false (#12601) * update adapter SmartHub: add aliases * smarthub set skipPbsAliasing to false --------- Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index c9cc737fac7..5f24f432a13 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -9,12 +9,12 @@ import { const BIDDER_CODE = 'smarthub'; const ALIASES = [ - {code: 'attekmi', skipPbsAliasing: true}, - {code: 'markapp', skipPbsAliasing: true}, - {code: 'jdpmedia', skipPbsAliasing: true}, - {code: 'tredio', skipPbsAliasing: true}, - {code: 'felixads', skipPbsAliasing: true}, - {code: 'vimayx', skipPbsAliasing: true}, + {code: 'attekmi'}, + {code: 'markapp'}, + {code: 'jdpmedia'}, + {code: 'tredio'}, + {code: 'felixads'}, + {code: 'vimayx'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', From 2baacb19a716a7cdeb609ac7b0e090e47a66197f Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Thu, 19 Dec 2024 16:42:42 +0300 Subject: [PATCH 0778/1097] AdMatic Bid Adapter : add yobee alias (#12588) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js --- modules/admaticBidAdapter.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 0287ff3de33..f90dd5a8762 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -24,7 +24,8 @@ export const spec = { {code: 'pixad', gvlid: 1281}, {code: 'monetixads', gvlid: 1281}, {code: 'netaddiction', gvlid: 1281}, - {code: 'adt', gvlid: 779} + {code: 'adt', gvlid: 779}, + {code: 'yobee', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -56,7 +57,7 @@ export const spec = { const ortb = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); let host = getValue(validBidRequests[0].params, 'host'); - const currency = getCurrencyFromBidderRequest(bidderRequest) || 'TRY'; + const currency = getCurrencyFromBidderRequest(bidderRequest) || null; const bidderName = validBidRequests[0].bidder; const payload = { @@ -86,6 +87,7 @@ export const spec = { }; payload.ext.cur = currency; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ''; @@ -139,6 +141,10 @@ export const spec = { case 'adt': SYNC_URL = 'https://static.cdn.adtarget.biz/adt/sync.html'; break; + case 'yobee': + SYNC_URL = 'https://static.cdn.yobee.it/yobee/sync.html'; + break; + case 'admatic': default: SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; break; From 5ddf3618d123b23b54b8dbd09d2e55c5fcb57e0f Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 19 Dec 2024 12:04:09 -0500 Subject: [PATCH 0779/1097] Integration example: Create id_lift_measurement.html (#12577) * Create id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html --- .../gpt/id_lift_measurement.html | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 integrationExamples/gpt/id_lift_measurement.html diff --git a/integrationExamples/gpt/id_lift_measurement.html b/integrationExamples/gpt/id_lift_measurement.html new file mode 100644 index 00000000000..fef5fd224ac --- /dev/null +++ b/integrationExamples/gpt/id_lift_measurement.html @@ -0,0 +1,173 @@ + + + + + Measure Lift of Multiple ID Modules + + + + + + + + + + +

Measure Lift of Multiple ID Modules

+ +

Generated IDs:

+

+
+    

Generated EIDs:

+

+
+    
+    

Instructions

+
    +
  1. Ensure that the `abg` key is definied in GAM targeting with all possible keys. Each value will be a combination of the following six possible key-value pairs: +
      +
    • id1:t0
    • +
    • id1:t1
    • +
    • id2:t0
    • +
    • id2:t1
    • +
    • id3:t0
    • +
    • id3:t1
    • +
    +
  2. +
  3. In Google Ad Manager (GAM), create a report with the following setup: +
      +
    • Dimensions: Ad Unit, Key-Value Targeting (`abg`).
    • +
    • Metrics: Impressions, Revenue.
    • +
    • Filters: Include the `abg` key in the report.
    • +
    +
  4. +
  5. Analyze the report for each ID module: +
      +
    • Filter by combinations of `t1` (treatment) and `t0` (control) for each ID module (e.g., `id1:t1`, `id1:t0`).
    • +
    • Compare performance metrics (eg Impressions, Revenue) for the `t1` vs. `t0` values.
    • +
    • Calculate lift for each module using the formula: +
      Lift (%) = ((Treatment Metric / Treatment Rate - Control Metric / Control Rate) / (Control Metric / Control Rate)) * 100
      + Replace "Metric" with the relevant performance metric. +
    • +
    +
  6. +
+ + + From 1bd2a8800b1fbb5428adb070eeb69e77f86eb1a8 Mon Sep 17 00:00:00 2001 From: artemAdp <133973660+artemAdp@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:36:03 +0200 Subject: [PATCH 0780/1097] AdPlayerPro Video Module : add PLCMT (#12593) * Add PLCMT to video module AdPlayer.Pro * fix tests --- .../adPlayerPro/bidRequestScheduling.html | 2 +- .../adPlayerPro/eventListeners.html | 2 +- modules/adplayerproVideoProvider.js | 12 +++++++++ .../adplayerproVideoProvider_spec.js | 25 +++++++++++++++++-- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html index 7c73312c5c3..9c799d25058 100644 --- a/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html +++ b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html @@ -13,7 +13,7 @@ var adUnits = [{ code: 'div-gpt-ad-51545-0', mediaTypes: { - video:{"context":"outstream"} + video: {context: 'outstream', playerSize: [640, 360]} }, video: { divId: 'player', // required to indicate which player is being used to render this ad unit. diff --git a/integrationExamples/videoModule/adPlayerPro/eventListeners.html b/integrationExamples/videoModule/adPlayerPro/eventListeners.html index 3c26ef42bee..8265d3c26a6 100644 --- a/integrationExamples/videoModule/adPlayerPro/eventListeners.html +++ b/integrationExamples/videoModule/adPlayerPro/eventListeners.html @@ -13,7 +13,7 @@ var adUnits = [{ code: 'div-gpt-ad-51545-0', mediaTypes: { - video:{"context":"outstream"} + video: {context: 'outstream', playerSize: [640, 360]} }, video: { divId: 'player', // required to indicate which player is being used to render this ad unit. diff --git a/modules/adplayerproVideoProvider.js b/modules/adplayerproVideoProvider.js index 826aee257ec..bd26be63b96 100644 --- a/modules/adplayerproVideoProvider.js +++ b/modules/adplayerproVideoProvider.js @@ -2,6 +2,7 @@ import { API_FRAMEWORKS, PLACEMENT, PLAYBACK_METHODS, + PLCMT, PROTOCOLS, VIDEO_MIME_TYPE, VPAID_MIME_TYPE @@ -109,6 +110,7 @@ export function AdPlayerProProvider(config, adPlayerPro_, callbackStorage_, util API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0 ], + plcmt: utils.getPlcmt(playerConfig) }; return video; @@ -347,6 +349,16 @@ export const utils = { return mute ? PLAYBACK_METHODS.AUTOPLAY_MUTED : PLAYBACK_METHODS.AUTOPLAY; } return PLAYBACK_METHODS.CLICK_TO_PLAY; + }, + + getPlcmt: function ({type, autoplay, muted, file}) { + type = type || 'inStream'; + if (!file) { + // INTERSTITIAL: primary focus of the page and take up the majority of the viewport and cannot be scrolled out of view. + return type === 'rewarded' || type === 'inView' ? PLCMT.INTERSTITIAL : PLCMT.OUTSTREAM; + } + // INSTREAM must be set to “sound on” by default at player start + return type === 'inStream' && (!muted || !autoplay) ? PLCMT.INSTREAM : PLCMT.ACCOMPANYING_CONTENT; } } diff --git a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js index 1a792411497..227e61494b6 100644 --- a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js @@ -23,7 +23,7 @@ import sinon from 'sinon'; const {AdPlayerProProvider, utils} = require('modules/adplayerproVideoProvider.js'); const { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE, PLCMT } = require('libraries/video/constants/ortb.js'); function getPlayerMock() { @@ -66,6 +66,8 @@ function getUtilsMock() { getPlacement: function () { }, getPlaybackMethod: function () { + }, + getPlcmt: function () { } }; } @@ -218,10 +220,12 @@ describe('AdPlayerProProvider', function () { const test_media_type = VIDEO_MIME_TYPE.MP4; const test_placement = PLACEMENT.ARTICLE; const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; + const test_plcmt = PLCMT.OUTSTREAM; utilsMock.getSupportedMediaTypes = () => [test_media_type]; utilsMock.getPlacement = () => test_placement; utilsMock.getPlaybackMethod = () => test_playback_method; + utilsMock.getPlcmt = () => test_plcmt; const provider = AdPlayerProProvider(config, null, null, utilsMock); provider.init(); @@ -242,7 +246,8 @@ describe('AdPlayerProProvider', function () { expect(video.playbackmethod).to.include(test_playback_method); expect(video.playbackend).to.equal(1); expect(video.api).to.have.length(2); - expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); // + expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); + expect(video.plcmt).to.equal(test_plcmt); }); }); @@ -409,6 +414,22 @@ describe('AdPlayerProProvider utils', function () { test(true, false, PLAYBACK_METHODS.AUTOPLAY); test(true, true, PLAYBACK_METHODS.AUTOPLAY_MUTED); }); + + it('getPlcmt', function () { + function test(type, autoplay, muted, file, expected) { + expect(utils.getPlcmt({type, autoplay, muted, file})).to.be.equal(expected); + } + + test('inStream', false, false, 'f', PLCMT.INSTREAM); + test(undefined, false, false, 'f', PLCMT.INSTREAM); + test('inStream', false, true, 'f', PLCMT.INSTREAM); + test('inStream', true, false, 'f', PLCMT.INSTREAM); + test('inStream', true, true, 'f', PLCMT.ACCOMPANYING_CONTENT); + + test('rewarded', true, false, undefined, PLCMT.INTERSTITIAL); + test('inView', true, false, undefined, PLCMT.INTERSTITIAL); + test('InPage', true, false, undefined, PLCMT.OUTSTREAM); + }); }); describe('AdPlayerProProvider callbackStorageFactory', function () { From 22ced7db40c67805e8f4404be7f2e2fc90f43207 Mon Sep 17 00:00:00 2001 From: Vadym Shatov <135347097+Gunnar97@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:37:55 +0200 Subject: [PATCH 0781/1097] adtelligent bid adapter: add new allias (#12559) * addStellormedia alias * add adtellintUtils * update adtelligentUtils * fix test --------- Co-authored-by: VadymShatov --- .../adtelligentUtils/adtelligentUtils.js | 79 +++++++++++++++++++ modules/adtargetBidAdapter.js | 72 +++-------------- modules/adtelligentBidAdapter.js | 78 +++--------------- modules/viewdeosDXBidAdapter.js | 53 +++---------- .../modules/adtelligentBidAdapter_spec.js | 1 + .../spec/modules/viewdeosDXBidAdapter_spec.js | 11 ++- 6 files changed, 119 insertions(+), 175 deletions(-) create mode 100644 libraries/adtelligentUtils/adtelligentUtils.js diff --git a/libraries/adtelligentUtils/adtelligentUtils.js b/libraries/adtelligentUtils/adtelligentUtils.js new file mode 100644 index 00000000000..c2543fa4cae --- /dev/null +++ b/libraries/adtelligentUtils/adtelligentUtils.js @@ -0,0 +1,79 @@ +import {deepAccess, isArray} from '../../src/utils.js'; +import { config } from '../../src/config.js'; +import {BANNER, VIDEO} from '../../src/mediaTypes.js'; + +export const supportedMediaTypes = [VIDEO, BANNER] + +export function isBidRequestValid (bid) { + return !!deepAccess(bid, 'params.aid'); +} + +export function getUserSyncsFn (syncOptions, serverResponses, syncsCache = {}) { + const syncs = []; + function addSyncs(bid) { + const uris = bid.cookieURLs; + const types = bid.cookieURLSTypes || []; + + if (Array.isArray(uris)) { + uris.forEach((uri, i) => { + const type = types[i] || 'image'; + + if ((!syncOptions.pixelEnabled && type === 'image') || + (!syncOptions.iframeEnabled && type === 'iframe') || + syncsCache[uri]) { + return; + } + + syncsCache[uri] = true; + syncs.push({ + type: type, + url: uri + }) + }) + } + } + + if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { + isArray(serverResponses) && serverResponses.forEach((response) => { + if (response.body) { + if (isArray(response.body)) { + response.body.forEach(b => { + addSyncs(b); + }) + } else { + addSyncs(response.body) + } + } + }) + } + return syncs; +} + +export function createTag(bidRequests, adapterRequest) { + const tag = { + // TODO: is 'page' the right value here? + Domain: deepAccess(adapterRequest, 'refererInfo.page'), + }; + + if (config.getConfig('coppa') === true) { + tag.Coppa = 1; + } + if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { + tag.GDPR = 1; + tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); + } + if (deepAccess(adapterRequest, 'uspConsent')) { + tag.USP = deepAccess(adapterRequest, 'uspConsent'); + } + if (deepAccess(bidRequests[0], 'schain')) { + tag.Schain = deepAccess(bidRequests[0], 'schain'); + } + if (deepAccess(bidRequests[0], 'userId')) { + tag.UserIds = deepAccess(bidRequests[0], 'userId'); + } + if (deepAccess(bidRequests[0], 'userIdAsEids')) { + tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); + } + + return tag; +} diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js index 0faf740ac54..4b20ff06dca 100644 --- a/modules/adtargetBidAdapter.js +++ b/modules/adtargetBidAdapter.js @@ -4,6 +4,11 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; import {chunk} from '../libraries/chunk/chunk.js'; +import { + createTag, getUserSyncsFn, + isBidRequestValid, + supportedMediaTypes +} from '../libraries/adtelligentUtils/adtelligentUtils.js'; const ENDPOINT = 'https://ghb.console.adtarget.com.tr/v2/auction/'; const BIDDER_CODE = 'adtarget'; @@ -13,50 +18,10 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 779, - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid: function (bid) { - return !!deepAccess(bid, 'params.aid'); - }, + supportedMediaTypes, + isBidRequestValid, getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - - function addSyncs(bid) { - const uris = bid.cookieURLs; - const types = bid.cookieURLSTypes || []; - - if (Array.isArray(uris)) { - uris.forEach((uri, i) => { - const type = types[i] || 'image'; - - if ((!syncOptions.pixelEnabled && type === 'image') || - (!syncOptions.iframeEnabled && type === 'iframe') || - syncsCache[uri]) { - return; - } - - syncsCache[uri] = true; - syncs.push({ - type: type, - url: uri - }) - }) - } - } - - if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - isArray(serverResponses) && serverResponses.forEach((response) => { - if (response.body) { - if (isArray(response.body)) { - response.body.forEach(b => { - addSyncs(b); - }) - } else { - addSyncs(response.body) - } - } - }) - } - return syncs; + return getUserSyncsFn(syncOptions, serverResponses, syncsCache) }, buildRequests: function (bidRequests, adapterRequest) { @@ -118,26 +83,7 @@ function parseResponse(serverResponse, adapterRequest) { } function bidToTag(bidRequests, adapterRequest) { - const tag = { - // TODO: is 'page' the right value here? - Domain: deepAccess(adapterRequest, 'refererInfo.page') - }; - if (config.getConfig('coppa') === true) { - tag.Coppa = 1; - } - if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { - tag.GDPR = 1; - tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); - } - if (deepAccess(adapterRequest, 'uspConsent')) { - tag.USP = deepAccess(adapterRequest, 'uspConsent'); - } - if (deepAccess(bidRequests[0], 'schain')) { - tag.Schain = deepAccess(bidRequests[0], 'schain'); - } - if (deepAccess(bidRequests[0], 'userId')) { - tag.UserIds = deepAccess(bidRequests[0], 'userId'); - } + const tag = createTag(bidRequests, adapterRequest); const bids = []; diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 6d4c0b6ed6a..f8ad874ab4d 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -5,6 +5,11 @@ import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; import {chunk} from '../libraries/chunk/chunk.js'; +import { + createTag, getUserSyncsFn, + isBidRequestValid, + supportedMediaTypes +} from '../libraries/adtelligentUtils/adtelligentUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -26,6 +31,7 @@ const HOST_GETTERS = { ocm: () => 'ghb.cenarius.orangeclickmedia.com', '9dotsmedia': () => 'ghb.platform.audiodots.com', indicue: () => 'ghb.console.indicue.com', + stellormedia: () => 'ghb.ads.stellormedia.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -48,51 +54,12 @@ export const spec = { { code: 'ocm', gvlid: 1148 }, '9dotsmedia', 'indicue', + 'stellormedia' ], - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid: function (bid) { - return !!deepAccess(bid, 'params.aid'); - }, + supportedMediaTypes, + isBidRequestValid, getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - - function addSyncs(bid) { - const uris = bid.cookieURLs; - const types = bid.cookieURLSTypes || []; - - if (Array.isArray(uris)) { - uris.forEach((uri, i) => { - const type = types[i] || 'image'; - - if ((!syncOptions.pixelEnabled && type === 'image') || - (!syncOptions.iframeEnabled && type === 'iframe') || - syncsCache[uri]) { - return; - } - - syncsCache[uri] = true; - syncs.push({ - type: type, - url: uri - }) - }) - } - } - - if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - isArray(serverResponses) && serverResponses.forEach((response) => { - if (response.body) { - if (isArray(response.body)) { - response.body.forEach(b => { - addSyncs(b); - }) - } else { - addSyncs(response.body) - } - } - }) - } - return syncs; + return getUserSyncsFn(syncOptions, serverResponses, syncsCache) }, /** * Make a server request from the list of BidRequests @@ -162,29 +129,7 @@ function parseRTBResponse(serverResponse, adapterRequest) { function bidToTag(bidRequests, adapterRequest) { // start publisher env - const tag = { - // TODO: is 'page' the right value here? - Domain: deepAccess(adapterRequest, 'refererInfo.page') - }; - if (config.getConfig('coppa') === true) { - tag.Coppa = 1; - } - if (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { - tag.GDPR = 1; - tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); - } - if (deepAccess(adapterRequest, 'uspConsent')) { - tag.USP = deepAccess(adapterRequest, 'uspConsent'); - } - if (deepAccess(bidRequests[0], 'schain')) { - tag.Schain = deepAccess(bidRequests[0], 'schain'); - } - if (deepAccess(bidRequests[0], 'userId')) { - tag.UserIds = deepAccess(bidRequests[0], 'userId'); - } - if (deepAccess(bidRequests[0], 'userIdAsEids')) { - tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); - } + const tag = createTag(bidRequests, adapterRequest); if (window.adtDmp && window.adtDmp.ready) { tag.DMPId = window.adtDmp.getUID(); } @@ -307,6 +252,7 @@ function createBid(bidResponse, bidRequest) { /** * Create Adtelligent renderer * @param requestId + * @param bidderParams * @returns {*} */ function newRenderer(requestId, bidderParams) { diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js index 7afd23cbde7..4f920fbd91f 100644 --- a/modules/viewdeosDXBidAdapter.js +++ b/modules/viewdeosDXBidAdapter.js @@ -1,61 +1,29 @@ import {deepAccess, flatten, isArray, logError, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {findIndex} from '../src/polyfill.js'; +import { + getUserSyncsFn, + isBidRequestValid, + supportedMediaTypes +} from '../libraries/adtelligentUtils/adtelligentUtils.js'; const URL = 'https://ghb.sync.viewdeos.com/auction/'; const OUTSTREAM_SRC = 'https://player.sync.viewdeos.com/outstream-unit/2.01/outstream.min.js'; const BIDDER_CODE = 'viewdeosDX'; const OUTSTREAM = 'outstream'; const DISPLAY = 'display'; +const syncsCache = {}; export const spec = { code: BIDDER_CODE, aliases: ['viewdeos'], gvlid: 924, - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid: function (bid) { - return !!deepAccess(bid, 'params.aid'); - }, + supportedMediaTypes, + isBidRequestValid, getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - - function addSyncs(bid) { - const uris = bid.cookieURLs; - const types = bid.cookieURLSTypes || []; - - if (Array.isArray(uris)) { - uris.forEach((uri, i) => { - const type = types[i] || 'image'; - - if ((!syncOptions.pixelEnabled && type === 'image') || - (!syncOptions.iframeEnabled && type === 'iframe')) { - return; - } - - syncs.push({ - type: type, - url: uri - }) - }) - } - } - - if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - isArray(serverResponses) && serverResponses.forEach((response) => { - if (response.body) { - if (isArray(response.body)) { - response.body.forEach(b => { - addSyncs(b); - }) - } else { - addSyncs(response.body) - } - } - }) - } - return syncs; + return getUserSyncsFn(syncOptions, serverResponses, syncsCache) }, /** * Make a server request from the list of BidRequests @@ -221,6 +189,7 @@ function createBid(bidResponse, mediaType, bidderParams) { /** * Create renderer * @param requestId + * @param bidderParams * @returns {*} */ function newRenderer(requestId, bidderParams) { diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index b03bba95357..b12744d3a28 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -17,6 +17,7 @@ const aliasEP = { 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', 'indicue': 'https://ghb.console.indicue.com/v2/auction/', + 'stellormedia': 'https://ghb.ads.stellormedia.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; diff --git a/test/spec/modules/viewdeosDXBidAdapter_spec.js b/test/spec/modules/viewdeosDXBidAdapter_spec.js index 31df9244ada..b60037aab4a 100644 --- a/test/spec/modules/viewdeosDXBidAdapter_spec.js +++ b/test/spec/modules/viewdeosDXBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/viewdeosDXBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {cloneDeep} from 'lodash'; const ENDPOINT = 'https://ghb.sync.viewdeos.com/auction/'; @@ -176,13 +177,15 @@ describe('viewdeosDXBidAdapter', function () { describe('user syncs with both types', function () { it('should be returned if pixel and iframe enabled', function () { + const mockedServerResponse = cloneDeep(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS); + mockedServerResponse.cookieURLs = ['link7', 'link8']; const syncs = spec.getUserSyncs({ iframeEnabled: true, - pixelEnabled: true - }, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + pixelEnabled: true, + }, [{body: mockedServerResponse}]); - expect(syncs.map(s => s.url)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs); - expect(syncs.map(s => s.type)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLSTypes); + expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); }) }) From eee512be13bdfdfc63c69afb885837524efc423c Mon Sep 17 00:00:00 2001 From: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:29:06 +0530 Subject: [PATCH 0782/1097] Doceree AdManager Bid Adapter : maintainence (#12578) * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Update docereeAdManagerBidAdapter.js * added test cases for payload formation in DocereeAdManager * Added support for publisherUrl * added some parameters * Added support for TCF 2.2 * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Written test cases for new method implemented. * indentation issues resolved * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Updated DocereeAdManager Bidder Adapter * Update docereeAdManagerBidAdapter.js --------- Co-authored-by: lokesh-doceree Co-authored-by: Patrick McCann --- modules/docereeAdManagerBidAdapter.js | 23 ++++++++++++++----- .../docereeAdManagerBidAdapter_spec.js | 11 ++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index e26045c8f1f..80f70d71a8b 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -79,6 +79,17 @@ export function getPageUrl() { return url; } +const handleConsent = (consentValue) => { + try { + if (consentValue === 0 || consentValue === '0') { + consentValue = '0'; + } + } catch (error) { + + } + return consentValue; +} + export function getPayload(bid, userData, bidderRequest) { if (!userData || !bid) { return false; @@ -98,14 +109,15 @@ export function getPayload(bid, userData, bidderRequest) { city, state, zipcode, - hashedNPI, hashedhcpid, hashedemail, hashedmobile, country, + hashedNPI, organization, platformUid, - mobile + mobile, + userconsent } = userData; const data = { @@ -119,19 +131,18 @@ export function getPayload(bid, userData, bidderRequest) { city: city || '', state: state || '', zipcode: zipcode || '', - hashedNPI: hashedNPI || '', pb: 1, adunit: placementId || '', requestId: bidId || '', - hashedhcpid: hashedhcpid || '', + hashedhcpid: hashedhcpid || hashedNPI || '', hashedemail: hashedemail || '', hashedmobile: hashedmobile || '', country: country || '', organization: organization || '', dob: dob || '', - userconsent: 1, + upref: handleConsent(userconsent) || '', mobile: mobile || '', - pageurl: publisherUrl || getPageUrl() || '' + pageurl: getPageUrl() || publisherUrl || '' }; try { diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js index 704b9c48d3a..6f7da056681 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -18,7 +18,6 @@ describe('docereeadmanager', function () { city: '', state: '', zipcode: '', - hashedNPI: '', hashedhcpid: '', hashedemail: '', hashedmobile: '', @@ -146,7 +145,6 @@ describe('docereeadmanager', function () { city: 'Xxxxx', state: 'Xxxxxx', zipcode: 'XXXXXX', - hashedNPI: 'xxxxxx', hashedhcpid: 'xxxxxxx', hashedemail: 'xxxxxxx', hashedmobile: 'xxxxxxx', @@ -155,6 +153,7 @@ describe('docereeadmanager', function () { dob: 'xx-xx-xxxx', platformUid: 'Xx.xxx.xxxxxx', mobile: 'XXXXXXXXXX', + userconsent: 1 } bid = { ...bid, params: { ...bid.params, placementId: 'DOC-19-1' } } const buildRequests = { @@ -177,7 +176,6 @@ describe('docereeadmanager', function () { 'city', 'state', 'zipcode', - 'hashedNPI', 'pb', 'adunit', 'requestId', @@ -187,7 +185,7 @@ describe('docereeadmanager', function () { 'country', 'organization', 'dob', - 'userconsent', + 'upref', 'mobile', 'pageurl', 'consent' @@ -202,9 +200,8 @@ describe('docereeadmanager', function () { expect(payloadData.city).to.equal('Xxxxx'); expect(payloadData.state).to.equal('Xxxxxx'); expect(payloadData.zipcode).to.equal('XXXXXX'); - expect(payloadData.hashedNPI).to.equal('xxxxxx'); expect(payloadData.pb).to.equal(1); - expect(payloadData.userconsent).to.equal(1); + expect(payloadData.upref).to.equal(1); expect(payloadData.dob).to.equal('xx-xx-xxxx'); expect(payloadData.organization).to.equal('Xxxxxx'); expect(payloadData.country).to.equal('Xxxxxx'); @@ -214,7 +211,7 @@ describe('docereeadmanager', function () { expect(payloadData.requestId).to.equal('testing'); expect(payloadData.mobile).to.equal('XXXXXXXXXX'); expect(payloadData.adunit).to.equal('DOC-19-1'); - expect(payloadData.pageurl).to.equal('xxxxxx.com/xxxx'); + expect(payloadData.pageurl).to.equal('http://localhost:9876/context.html'); expect(payloadData.consent.gdprstr).to.equal('COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw'); expect(payloadData.consent.gdpr).to.equal(0); }) From cd6997b09cfe1b34105f8f7c35a0686c4813bb58 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 19 Dec 2024 19:27:59 +0000 Subject: [PATCH 0783/1097] Prebid 9.24.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf2b8a35c1c..fb21f29e34f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.24.0-pre", + "version": "9.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.24.0-pre", + "version": "9.24.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index fa9031dbf54..22c6ec1d3d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.24.0-pre", + "version": "9.24.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From cb1812458b8e9a8f98e56680f79b626875a0f393 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 19 Dec 2024 19:28:00 +0000 Subject: [PATCH 0784/1097] Increment version to 9.25.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb21f29e34f..e0dd58cc9dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.24.0", + "version": "9.25.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.24.0", + "version": "9.25.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 22c6ec1d3d1..c45bdbe894d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.24.0", + "version": "9.25.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 2f713dd3028295eef3c2305df4a6e2c59ec9b63b Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 19 Dec 2024 12:06:52 -0800 Subject: [PATCH 0785/1097] PBS Adapter: handle edge case with duplicated EID permissions (#12595) * PBS Adapter: handle edge case with duplicated EID permissions * fix lint --- modules/prebidServerBidAdapter/bidderConfig.js | 6 +++--- .../spec/modules/prebidServerBidAdapter_spec.js | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/modules/prebidServerBidAdapter/bidderConfig.js b/modules/prebidServerBidAdapter/bidderConfig.js index 3b51810ff20..f6f4fb91389 100644 --- a/modules/prebidServerBidAdapter/bidderConfig.js +++ b/modules/prebidServerBidAdapter/bidderConfig.js @@ -52,7 +52,7 @@ export function extractEids({global, bidder}) { function getEntry(eid) { let entry = entries.find((candidate) => deepEqual(candidate.eid, eid)); if (entry == null) { - entry = {eid, bidders: []} + entry = {eid, bidders: new Set()} entries.push(entry); } if (bySource[eid.source] == null) { @@ -74,12 +74,12 @@ export function extractEids({global, bidder}) { (deepAccess(bidderConfig, path) || []).forEach(eid => { const entry = getEntry(eid); if (entry.bidders !== false) { - entry.bidders.push(bidderCode); + entry.bidders.add(bidderCode); } }) }) }) - return {eids: entries, conflicts}; + return {eids: entries.map(({eid, bidders}) => ({eid, bidders: bidders && Array.from(bidders)})), conflicts}; } /** diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 85ae7909f34..fc0f9ed7b68 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2130,7 +2130,7 @@ describe('S2S Adapter', function () { bidders: [], source: 'idD' }]); - }) + }); it('should repeat global EIDs when bidder-specific EIDs conflict', () => { BID_REQUESTS.push({ @@ -4638,6 +4638,21 @@ describe('S2S Adapter', function () { ], conflicts: ['idA', 'idB'] } + }, + { + t: 'duplicated bidder-specific eids', + bidder: { + bidderA: { + user: { + eids: [mkEid('id'), mkEid('id')] + } + } + }, + expected: { + eids: [ + eidEntry('id', 'id', ['bidderA']) + ] + } } ].forEach(({t, global = {}, bidder = {}, expected}) => { it(t, () => { From eb225a83cb079d9be359f8239732642425616206 Mon Sep 17 00:00:00 2001 From: Matthieu Wipliez <89922776+github-matthieu-wipliez@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:47:01 +0100 Subject: [PATCH 0786/1097] Autoplay detection update: ignore NotSupportedError exceptions (#12603) * Ignore NotSupportedError exceptions in autoplay detection This error is caused by a Content Security Policy that disables data: scheme for media URLs. Before this PR, this error would cause autoplay to be disabled; now, if this error is raised it has the same effect as disabling autoplay detection. * Remove warning that has been addressed by #11822 --- libraries/autoplayDetection/autoplay.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/autoplayDetection/autoplay.js b/libraries/autoplayDetection/autoplay.js index 3ca4c4a8d11..4b70145539a 100644 --- a/libraries/autoplayDetection/autoplay.js +++ b/libraries/autoplayDetection/autoplay.js @@ -1,7 +1,6 @@ let autoplayEnabled = null; /** - * DEVELOPER WARNING: IMPORTING THIS LIBRARY MAY MAKE YOUR ADAPTER NO LONGER COMPATIBLE WITH APP PUBLISHERS USING WKWEBVIEW * Note: this function returns true if detection is not done yet. This is by design: if autoplay is not allowed, * the call to video.play() will fail immediately, otherwise it may not terminate. * @returns true if autoplay is not forbidden @@ -41,8 +40,12 @@ function startDetection() { // if the video is played on a WebView with playsinline = false, this stops the video, to prevent it from being displayed fullscreen videoElement.src = ''; }) - .catch(() => { - autoplayEnabled = false; + .catch((error) => { + if (error instanceof DOMException && error.name === 'NotSupportedError') { + // ignore this error caused by a Content Security Policy that disables data: scheme for media URLs + } else { + autoplayEnabled = false; + } }); } From 059e77f488ab78b503813310e405403c75126d55 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Thu, 26 Dec 2024 16:25:11 +0000 Subject: [PATCH 0787/1097] Taboola bid adapter add ortb2 device (#11750) * Taboola Bid Adapter: Add full ORTB2 device data to request payload * Taboola Bid Adapter: Modify FPD test to verify presence of ORTB2 device data in request * Taboola Bid Adapter: Remove device data fallback and add ortb2 device data to `commonBidderRequest` (tests) as it is always present in real-world scenarios --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 22 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 7d1c58bcdcf..b4755678052 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -276,7 +276,7 @@ function getSiteProperties({publisherId}, refererInfo, ortb2) { function fillTaboolaReqData(bidderRequest, bidRequest, data) { const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); - deepSetValue(data, 'device.ua', navigator.userAgent); + deepSetValue(data, 'device', bidderRequest?.ortb2?.device); const extractedUserId = userData.getUserId(gdprConsent, uspConsent); if (data.user == undefined) { data.user = { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 5db0c8cf306..d71a701e93b 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -173,6 +173,11 @@ describe('Taboola Adapter', function () { page: 'https://example.com/ref', ref: 'https://ref', domain: 'example.com', + }, + ortb2: { + device: { + ua: navigator.userAgent, + }, } } @@ -198,9 +203,9 @@ describe('Taboola Adapter', function () { 'bidfloorcur': 'USD', 'ext': {} }], - id: 'mock-uuid', - 'test': 0, 'device': {'ua': navigator.userAgent}, + 'id': 'mock-uuid', + 'test': 0, 'user': { 'buyeruid': 0, 'ext': {}, @@ -366,6 +371,18 @@ describe('Taboola Adapter', function () { wlang: ['de'], user: { id: 'externalUserIdPassed' + }, + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4' } } } @@ -374,6 +391,7 @@ describe('Taboola Adapter', function () { expect(res.data.badv).to.deep.equal(bidderRequest.ortb2.badv) expect(res.data.wlang).to.deep.equal(bidderRequest.ortb2.wlang) expect(res.data.user.id).to.deep.equal(bidderRequest.ortb2.user.id) + expect(res.data.device).to.deep.equal(bidderRequest.ortb2.device); }); it('should pass user entities', function () { From 9ec45f6efa5df2bc33e2e3c20ec62884f4b4638c Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Thu, 26 Dec 2024 16:26:47 +0000 Subject: [PATCH 0788/1097] Adagio Bid Adapter: Add full ORTB2 device data to request payload (#12006) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/adagioBidAdapter.js | 34 +++++++++++++++------- test/spec/modules/adagioBidAdapter_spec.js | 34 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index d44c7e249d6..a837c54d1e1 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -5,7 +5,6 @@ import { deepAccess, deepClone, generateUUID, - getDNT, getWindowSelf, isArray, isFn, @@ -14,6 +13,7 @@ import { logError, logInfo, logWarn, + mergeDeep, } from '../src/utils.js'; import { getRefererInfo, parseDomain } from '../src/refererDetection.js'; import { OUTSTREAM, validateOrtbVideoFields } from '../src/video.js'; @@ -39,19 +39,33 @@ export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER. const CURRENCY = 'USD'; /** - * Returns the window.ADAGIO global object used to store Adagio data. - * This object is created in window.top if possible, otherwise in window.self. + * Get device data object, with some properties + * deviated from the OpenRTB spec. + * @param {Object} ortb2Data + * @returns {Object} Device data object */ -function getDevice() { +function getDevice(ortb2Data) { + const _device = {}; + + // Merge the device object from ORTB2 data. + if (ortb2Data?.device) { + mergeDeep(_device, ortb2Data.device); + } + + // If the geo object is not defined, create it. + if (!_device.geo) { + _device.geo = {}; + } + const language = navigator.language ? 'language' : 'userLanguage'; - return { + mergeDeep(_device, { userAgent: navigator.userAgent, language: navigator[language], - dnt: getDNT() ? 1 : 0, - geo: {}, js: 1 - }; -}; + }); + + return _device; +} function getSite(bidderRequest) { const { refererInfo } = bidderRequest; @@ -502,7 +516,7 @@ export const spec = { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const secure = (location.protocol === 'https:') ? 1 : 0; - const device = _internal.getDevice(); + const device = _internal.getDevice(bidderRequest?.ortb2); const site = _internal.getSite(bidderRequest); const pageviewId = _internal.getAdagioNs().pageviewId; const gdprConsent = _getGdprConsent(bidderRequest) || {}; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 75b0635bbef..e83437be397 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1042,6 +1042,40 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.regs.dsa).to.be.undefined; }); }) + + describe('with ORTB2', function() { + it('should add ortb2 device data to the request', function() { + const ortb2 = { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }, + }; + + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder({ortb2}).build(); + const requests = spec.buildRequests([bid01], bidderRequest); + + const expectedData = { + ...ortb2.device, + language: navigator[navigator.language ? 'language' : 'userLanguage'], + js: 1, + geo: {}, + userAgent: navigator.userAgent, + }; + + expect(requests[0].data.device).to.deep.equal(expectedData); + }); + }); }); describe('interpretResponse()', function() { From 6e42abee5b52ec65520fdf585a93420754f0807a Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Thu, 26 Dec 2024 16:28:06 +0000 Subject: [PATCH 0789/1097] Vidazoo Bid Adapter: Add ORTB2 device data to request payload (#12074) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- libraries/vidazooUtils/bidderUtils.js | 2 + test/spec/modules/illuminBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/kueezRtbBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/shinezRtbBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/tagorasBidAdapter_spec.js | 51 +++++++++++------- .../modules/twistDigitalBidAdapter_spec.js | 52 ++++++++++++------- test/spec/modules/vidazooBidAdapter_spec.js | 52 ++++++++++++------- 7 files changed, 202 insertions(+), 108 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index df947142a4c..9de95248c74 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -247,6 +247,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); const contentLang = deepAccess(bidderRequest, 'ortb2.site.content.language') || document.documentElement.lang; const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa', 0); + const device = deepAccess(bidderRequest, 'ortb2.device', {}); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ @@ -290,6 +291,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder bidderRequestsCount: bidderRequestsCount, bidderWinsCount: bidderWinsCount, bidderTimeout: bidderTimeout, + device, ...uniqueRequestData }; diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 3cd79c7468d..b4578a8d61f 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -315,6 +328,7 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -386,6 +400,7 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index d3323a205b3..0bb65d0aef0 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -93,6 +93,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -116,24 +146,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -321,6 +334,7 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -394,6 +408,7 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index ebd2e987491..892d88b3b7b 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -119,24 +149,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -317,6 +330,7 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -388,6 +402,7 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 6ebe2896ffc..8d8c8cb62b7 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -314,6 +327,7 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -384,6 +398,7 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index c15a6d1d909..4ddac3261f1 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -129,24 +159,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - }, + device: ORTB2_DEVICE, user: { data: [ { @@ -341,6 +354,7 @@ describe('TwistDigitalBidAdapter', function () { }, contentLang: 'en', coppa: 0, + device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -422,6 +436,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -510,6 +525,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 14a49b21cdc..01d7aa20e53 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -98,6 +98,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -133,24 +163,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - }, + device: ORTB2_DEVICE, user: { data: [ { @@ -346,6 +359,7 @@ describe('VidazooBidAdapter', function () { }, contentLang: 'en', coppa: 0, + device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -430,6 +444,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -523,6 +538,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, From 9b15b22087cc17dc87d1c6c49b15752873d181c7 Mon Sep 17 00:00:00 2001 From: eknis Date: Fri, 27 Dec 2024 01:46:55 +0900 Subject: [PATCH 0790/1097] ImRtdProvider: add imuid param (#12475) * ImRtdProvider: add bidder function for craft * ImRtdProvider: remove bidder function for craft * ImRtdProvider: add imuid param --- modules/imRtdProvider.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index 78681c2beda..46573a81c15 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -109,6 +109,7 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { const segments = getSegments(data.im_segments, moduleConfig); const ortb2 = bidConfig.ortb2Fragments?.global || {}; deepSetValue(ortb2, 'user.ext.data.im_segments', segments); + deepSetValue(ortb2, 'user.ext.data.im_uid', data.im_uid); if (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues')) { window.googletag = window.googletag || {cmd: []}; @@ -145,6 +146,7 @@ export function getRealTimeData(reqBidsConfigObj, onDone, moduleConfig) { onDone(); return; } + const uid = storage.getDataFromLocalStorage(imUidLocalName); const sids = storage.getDataFromLocalStorage(imRtdLocalName); const parsedSids = sids ? sids.split(',') : []; const mt = storage.getDataFromLocalStorage(`${imRtdLocalName}_mt`); @@ -163,7 +165,7 @@ export function getRealTimeData(reqBidsConfigObj, onDone, moduleConfig) { } if (sids !== null) { - setRealTimeData(reqBidsConfigObj, moduleConfig, {im_segments: parsedSids}); + setRealTimeData(reqBidsConfigObj, moduleConfig, {im_uid: uid, im_segments: parsedSids}); onDone(); alreadyDone = true; } @@ -210,7 +212,7 @@ export function getApiCallback(reqBidsConfigObj, onDone, moduleConfig) { } if (parsedResponse.segments) { - setRealTimeData(reqBidsConfigObj, moduleConfig, {im_segments: parsedResponse.segments}); + setRealTimeData(reqBidsConfigObj, moduleConfig, {im_uid: parsedResponse.uid, im_segments: parsedResponse.segments}); storage.setDataInLocalStorage(imRtdLocalName, parsedResponse.segments); storage.setDataInLocalStorage(`${imRtdLocalName}_mt`, new Date(timestamp()).toUTCString()); } From 24e17806cab5915973cddbe9114719e1297db575 Mon Sep 17 00:00:00 2001 From: annavane <101708287+annavane@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:48:14 +0100 Subject: [PATCH 0791/1097] TNC Id Module : user input url validation and optimizations (#12527) * Bug Fixes: modules/tncIdSystem.js - Optimized User ID Recovery: Replaced the existing user ID recovery function with a faster and more efficient method, improving performance. modules/userId/userId.md - Documentation Correction: Resolved inconsistencies in the documentation, ensuring accurate information for module configuration and usage. * - Tests fixed * - TNCID module fix: "getTNCID is not a function" error * modules/tncIdSystem.js - user input URL validations added - TNCID recovered from cookie storage if available - code optimizations for faster TNCID load ________________________________________ test/spec/modules/tncIdSystem_spec.js - added tests for new functions ________________________________________ modules/tncIdSystem.md - updated documentation * - fixed lint errors * - Sales description removed * Looking forward for code approval. --- modules/tncIdSystem.js | 130 +++++++++++++++++++++----- modules/tncIdSystem.md | 32 +++++-- test/spec/modules/tncIdSystem_spec.js | 70 +++++++------- 3 files changed, 167 insertions(+), 65 deletions(-) diff --git a/modules/tncIdSystem.js b/modules/tncIdSystem.js index 9d3187462be..3e1d1e9b926 100644 --- a/modules/tncIdSystem.js +++ b/modules/tncIdSystem.js @@ -1,37 +1,114 @@ +/** + * This module adds TncId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/tncIdSystem + * @requires module:modules/userId + */ + import { submodule } from '../src/hook.js'; -import { logInfo } from '../src/utils.js'; +import { parseUrl, buildUrl, logInfo, logMessage, logError } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; import { loadExternalScript } from '../src/adloader.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'tncId'; -let url = null; +const TNC_API_URL = 'https://js.tncid.app/remote.js'; +const TNC_DEFAULT_NS = '__tnc'; +const TNC_PREBID_NS = '__tncPbjs'; +const TNC_PREBIDJS_PROVIDER_ID = 'c8549079-f149-4529-a34b-3fa91ef257d1'; +const TNC_LOCAL_VALUE_KEY = 'tncid'; +let moduleConfig = null; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +function fixURL(config, ns) { + config.params = (config && config.params) ? config.params : {}; + config.params.url = config.params.url || TNC_API_URL; + let url = parseUrl(config.params.url); + url.search = url.search || {}; + let providerId = config.params.publisherId || config.params.providerId || url.search.publisherId || url.search.providerId || TNC_PREBIDJS_PROVIDER_ID; + delete url.search.publisherId; + url.search.providerId = providerId; + url.search.ns = ns; + return url; +} -const waitTNCScript = (tncNS) => { +const loadRemoteScript = function(url) { return new Promise((resolve, reject) => { - var tnc = window[tncNS]; - if (!tnc) reject(new Error('No TNC Object')); - if (tnc.tncid) resolve(tnc.tncid); - tnc.ready(async () => { - let tncid = await tnc.getTNCID('prebid'); - resolve(tncid); - }); + let endpoint = buildUrl(url); + logMessage('TNC Endpoint', endpoint); + loadExternalScript(endpoint, MODULE_TYPE_UID, MODULE_NAME, resolve); }); } -const loadRemoteScript = () => { - return new Promise((resolve) => { - loadExternalScript(url, MODULE_TYPE_UID, MODULE_NAME, resolve); - }) +function TNCObject(ns) { + let tnc = window[ns]; + tnc = typeof tnc !== 'undefined' && tnc !== null && typeof tnc.ready == 'function' ? tnc : { + ready: function(f) { this.ready.q = this.ready.q || []; return typeof f == 'function' ? (this.ready.q.push(f), this) : new Promise(resolve => this.ready.q.push(resolve)); }, + }; + window[ns] = tnc; + return tnc; } -const tncCallback = function (cb) { - let tncNS = '__tnc'; - let promiseArray = []; - if (!window[tncNS]) { - tncNS = '__tncPbjs'; - promiseArray.push(loadRemoteScript()); +function getlocalValue(key) { + let value; + if (storage.hasLocalStorage()) { + value = storage.getDataFromLocalStorage(key); + } + if (!value) { + value = storage.getCookie(key); + } + + if (typeof value === 'string') { + // if it's a json object parse it and return the tncid value, otherwise assume the value is the id + if (value.charAt(0) === '{') { + try { + const obj = JSON.parse(value); + if (obj) { + return obj.tncid; + } + } catch (e) { + logError(e); + } + } else { + return value; + } + } + return null; +} + +const tncCallback = async function(cb) { + try { + let tncNS = TNC_DEFAULT_NS; + let tncid = getlocalValue(TNC_LOCAL_VALUE_KEY); + + if (!window[tncNS] || typeof window[tncNS].ready !== 'function') { + tncNS = TNC_PREBID_NS; // Register a new namespace for TNC global object + let url = fixURL(moduleConfig, tncNS); + if (!url) return cb(); + TNCObject(tncNS); // create minimal TNC object + await loadRemoteScript(url); // load remote script + } + if (!tncid) { + await new Promise(resolve => window[tncNS].ready(resolve)); + tncid = await window[tncNS].getTNCID('prebid'); // working directly with (possibly) overridden TNC Object + logMessage('tncId Module - tncid retrieved from remote script', tncid); + } else { + logMessage('tncId Module - tncid already exists', tncid); + window[tncNS].ready(() => window[tncNS].getTNCID('prebid')); + } + return cb(tncid); + } catch (err) { + logMessage('tncId Module', err); + return cb(); } - return Promise.all(promiseArray).then(() => waitTNCScript(tncNS)).then(cb).catch(() => cb()); } export const tncidSubModule = { @@ -42,6 +119,14 @@ export const tncidSubModule = { }; }, gvlid: 750, + /** + * performs action to obtain id + * Use a tncid cookie first if it is present, otherwise callout to get a new id + * @function + * @param {SubmoduleConfig} [config] Config object with params and storage properties + * @param {ConsentData} [consentData] GDPR consent + * @returns {IdResponse} + */ getId(config, consentData) { const gdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const consentString = gdpr ? consentData.consentString : ''; @@ -51,10 +136,11 @@ export const tncidSubModule = { return; } - if (config.params && config.params.url) { url = config.params.url; } + moduleConfig = config; return { callback: function (cb) { return tncCallback(cb); } + // callback: tncCallback } }, eids: { diff --git a/modules/tncIdSystem.md b/modules/tncIdSystem.md index b94d03c8e85..b806d545cc4 100644 --- a/modules/tncIdSystem.md +++ b/modules/tncIdSystem.md @@ -1,14 +1,16 @@ -# TNCID UserID Module +# Overview -### Prebid Configuration +Module Name: tncIdSystem + +## Prebid Configuration First, make sure to add the TNCID submodule to your Prebid.js package with: -``` +```bash gulp build --modules=tncIdSystem,userId ``` -### TNCIDIdSystem module Configuration +## TNCIdSystem module Configuration Disclosure: This module loads external script unreviewed by the prebid.js community @@ -20,16 +22,26 @@ pbjs.setConfig({ userIds: [{ name: 'tncId', params: { - url: 'https://js.tncid.app/remote.min.js' //Optional + url: 'TNC-fallback-script-url' // Fallback url, not required if onpage tag is present (ask TNC for it) + }, + storage: { + type: "cookie", + name: "tncid", + expires: 365 // in days } }], syncDelay: 5000 } }); ``` -#### Configuration Params -| Param Name | Required | Type | Description | -| --- | --- | --- | --- | -| name | Required | String | ID value for the TNCID module: `"tncId"` | -| params.url | Optional | String | Provide TNC fallback script URL, this script is loaded if there is no TNC script on page | +## Configuration Params + +The following configuration parameters are available: + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this sub-module | `"tncId"` | +| params ||| Details for the sub-module initialization || +| params.url | Optional | String | TNC script fallback URL - This script is loaded if there is no TNC script on page | `"https://js.tncid.app/remote.min.js"` | +| params.publisherId | Optional | String | Publisher ID used in TNC fallback script - As default Prebid specific Publisher ID is used | `"c8549079-f149-4529-a34b-3fa91ef257d1"` | diff --git a/test/spec/modules/tncIdSystem_spec.js b/test/spec/modules/tncIdSystem_spec.js index 4626c940a59..9fbffdddd68 100644 --- a/test/spec/modules/tncIdSystem_spec.js +++ b/test/spec/modules/tncIdSystem_spec.js @@ -1,7 +1,6 @@ import { tncidSubModule } from 'modules/tncIdSystem'; -import {attachIdSystem} from '../../../modules/userId/index.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; -import {expect} from 'chai/index.mjs'; +import { attachIdSystem } from '../../../modules/userId/index.js'; +import { createEidsArray } from '../../../modules/userId/eids.js'; const consentData = { gdprApplies: true, @@ -40,39 +39,38 @@ describe('TNCID tests', function () { expect(res).to.be.undefined; }); - it('GDPR is OK and page has no TNC script on page, script goes in error, no TNCID is returned', function () { + it('Should NOT give TNCID if there is no TNC script on page and no fallback url in configuration', async function () { const completeCallback = sinon.spy(); const {callback} = tncidSubModule.getId({}, consentData); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnce).to.be.true; - }) + await callback(completeCallback); + expect(callback).to.be.an('function'); + expect(completeCallback.calledOnceWithExactly()).to.be.true; }); - it('GDPR is OK and page has TNC script with ns: __tnc, present TNCID is returned', function () { - Object.defineProperty(window, '__tnc', { - value: { - ready: (readyFunc) => { readyFunc() }, - tncid: 'TNCID_TEST_ID_1', - providerId: 'TEST_PROVIDER_ID_1', - }, - configurable: true - }); + it('Should NOT give TNCID if fallback script is not loaded correctly', async function () { + const completeCallback = sinon.spy(); + const {callback} = tncidSubModule.getId({ + params: { url: 'www.thenewco.tech' } + }, consentData); + await callback(completeCallback); + expect(completeCallback.calledOnceWithExactly()).to.be.true; + }); + + it(`Should call external script if TNC is not loaded on page`, async function() { const completeCallback = sinon.spy(); - const {callback} = tncidSubModule.getId({}, { gdprApplies: false }); + const {callback} = tncidSubModule.getId({params: {url: 'https://www.thenewco.tech?providerId=test'}}, { gdprApplies: false }); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; - }) + await callback(completeCallback); + expect(window).to.contain.property('__tncPbjs'); }); - it('GDPR is OK and page has TNC script with ns: __tnc but not loaded, TNCID is assigned and returned', function () { + it('TNCID is returned if page has TNC script with ns: __tnc', async function () { Object.defineProperty(window, '__tnc', { value: { - ready: async (readyFunc) => { await readyFunc() }, + ready: (readyFunc) => { readyFunc() }, getTNCID: async (name) => { return 'TNCID_TEST_ID_1' }, - providerId: 'TEST_PROVIDER_ID_1', }, configurable: true }); @@ -80,17 +78,23 @@ describe('TNCID tests', function () { const completeCallback = sinon.spy(); const {callback} = tncidSubModule.getId({}, { gdprApplies: false }); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; - }) + await callback(completeCallback); + expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; }); - it('GDPR is OK and page has TNC script with ns: __tncPbjs, TNCID is returned', function () { + it('TNC script with ns __tncPbjs is created', async function () { + const completeCallback = sinon.spy(); + const {callback} = tncidSubModule.getId({params: {url: 'TEST_URL'}}, consentData); + + await callback(completeCallback); + expect(window).to.contain.property('__tncPbjs'); + }); + + it('TNCID is returned if page has TNC script with ns: __tncPbjs', async function () { Object.defineProperty(window, '__tncPbjs', { value: { - ready: async (readyFunc) => { await readyFunc() }, + ready: (readyFunc) => { readyFunc() }, getTNCID: async (name) => { return 'TNCID_TEST_ID_2' }, - providerId: 'TEST_PROVIDER_ID_1', options: {}, }, configurable: true, @@ -98,13 +102,13 @@ describe('TNCID tests', function () { }); const completeCallback = sinon.spy(); - const {callback} = tncidSubModule.getId({params: {url: 'TEST_URL'}}, consentData); + const {callback} = tncidSubModule.getId({params: {url: 'www.thenewco.tech'}}, consentData); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_2')).to.be.true; - }) + await callback(completeCallback); + expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_2')).to.be.true; }); }); + describe('eid', () => { before(() => { attachIdSystem(tncidSubModule); From 0a1401644fdcb5b756a169f8fa1d7bcb8eed3efd Mon Sep 17 00:00:00 2001 From: Ben Brachmann <49547103+bevenio@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:49:50 +0100 Subject: [PATCH 0792/1097] Goldbach Bid Adapter: connecting to new backend, reduced bid parameters (#12540) * Squashed commit of the following: Updated Goldbach Bidder Adapter - connecting to goldlayer-api - using custom outstream video player * added slotId to bid params * changed native assets order --- modules/goldbachBidAdapter.js | 1401 ++++--------- modules/goldbachBidAdapter.md | 224 +-- test/spec/modules/goldbachBidAdapter_spec.js | 1897 +++++++----------- 3 files changed, 1125 insertions(+), 2397 deletions(-) diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 07ad6b2fa97..e9f9604e594 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -1,1107 +1,422 @@ -import {Renderer} from '../src/Renderer.js'; -import { - createTrackPixelHtml, - deepAccess, - deepClone, - getBidRequest, - getParameterByName, - isArray, - isArrayOfNums, - isEmpty, - isFn, - isNumber, - isPlainObject, - isStr, - logError, - logInfo, - logMessage -} from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; -import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {hasPurpose1Consent} from '../src/utils/gdpr.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; -import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; -import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {chunk} from '../libraries/chunk/chunk.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - */ +import { ajax } from '../src/ajax.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +/* General config */ +const IS_LOCAL_MODE = false; const BIDDER_CODE = 'goldbach'; -const URL = 'https://ib.adnxs.com/ut/v3/prebid'; -const PRICING_URL = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; -const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; -const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', - 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; -const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api']; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately -const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; -const DEFAULT_PRICE_MAPPING = { - '0x0': 2.5, - '300x600': 5, - '800x250': 6, - '350x600': 6 -}; -let PRICE_MAPPING; -const VIDEO_MAPPING = { - playback_method: { - 'unknown': 0, - 'auto_play_sound_on': 1, - 'auto_play_sound_off': 2, - 'click_to_play': 3, - 'mouse_over': 4, - 'auto_play_sound_unknown': 5 - }, - context: { - 'unknown': 0, - 'pre_roll': 1, - 'mid_roll': 2, - 'post_roll': 3, - 'outstream': 4, - 'in-banner': 5 - } -}; -const NATIVE_MAPPING = { - body: 'description', - body2: 'desc2', - cta: 'ctatext', - image: { - serverName: 'main_image', - requiredParams: { required: true } - }, - icon: { - serverName: 'icon', - requiredParams: { required: true } - }, - sponsoredBy: 'sponsored_by', - privacyLink: 'privacy_link', - salePrice: 'saleprice', - displayUrl: 'displayurl' -}; -const SOURCE = 'pbjs'; -const MAX_IMPS_PER_REQUEST = 15; -const SCRIPT_TAG_START = ' { - if (Array.isArray(bid.params.placementId)) { - const ids = bid.params.placementId; - for (let i = 0; i < ids.length; i++) { - const newBid = Object.assign({}, bid, {params: {placementId: ids[i]}}); - localBidRequests.push(newBid) - } - } else { - localBidRequests.push(bid); - } - }); - const tags = localBidRequests.map(bidToTag); - const userObjBid = find(bidRequests, hasUserInfo); - let userObj = {}; - if (config.getConfig('coppa') === true) { - userObj = { 'coppa': true }; - } - if (userObjBid) { - Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) - .forEach((param) => { - let uparam = convertCamelToUnderscore(param); - if (param === 'segments' && isArray(userObjBid.params.user[param])) { - let segs = []; - userObjBid.params.user[param].forEach(val => { - if (isNumber(val)) { - segs.push({'id': val}); - } else if (isPlainObject(val)) { - segs.push(val); - } - }); - userObj[uparam] = segs; - } else if (param !== 'segments') { - userObj[uparam] = userObjBid.params.user[param]; - } - }); - } - - const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); - let appDeviceObj; - if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { - appDeviceObj = {}; - Object.keys(appDeviceObjBid.params.app) - .filter(param => includes(APP_DEVICE_PARAMS, param)) - .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); - } - - const appIdObjBid = find(bidRequests, hasAppId); - let appIdObj; - if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { - appIdObj = { - appid: appIdObjBid.params.app.id - }; - } - - let debugObj = {}; - let debugObjParams = {}; - const debugBidRequest = find(bidRequests, hasDebug); - if (debugBidRequest && debugBidRequest.debug) { - debugObj = debugBidRequest.debug; - } - - if (debugObj && debugObj.enabled) { - Object.keys(debugObj) - .filter(param => includes(DEBUG_PARAMS, param)) - .forEach(param => { - debugObjParams[param] = debugObj[param]; - }); - } - - const memberIdBid = find(bidRequests, hasMemberId); - const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; - const schain = bidRequests[0].schain; - const omidSupport = find(bidRequests, hasOmidSupport); - - const payload = { - tags: [...tags], - user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - }, - schain: schain - }; - - if (omidSupport) { - payload['iab_support'] = { - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }; - } - - if (member > 0) { - payload.member_id = member; - } - - if (appDeviceObjBid) { - payload.device = appDeviceObj; - } - if (appIdObjBid) { - payload.app = appIdObj; - } - - if (config.getConfig('adpod.brandCategoryExclusion')) { - payload.brand_category_uniqueness = true; - } - - if (debugObjParams.enabled) { - payload.debug = debugObjParams; - logInfo('Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); - } - - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - - if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { - let ac = bidderRequest.gdprConsent.addtlConsent; - // pull only the ids from the string (after the ~) and convert them to an array of ints - let acStr = ac.substring(ac.indexOf('~') + 1); - payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.refererInfo) { - let refererinfo = { - // TODO: this collects everything it finds, except for topmostLocation - rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), - rd_top: bidderRequest.refererInfo.reachedTop, - rd_ifs: bidderRequest.refererInfo.numIframes, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }; - payload.referrer_detection = refererinfo; - } - - const hasAdPodBid = find(bidRequests, hasAdPod); - if (hasAdPodBid) { - bidRequests.filter(hasAdPod).forEach(adPodBid => { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); - } - - if (bidRequests[0].userId) { - let eids = []; - - addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); - addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); - - if (eids.length) { - payload.eids = eids; - } - } - - if (tags[0].publisher_id) { - payload.publisher_id = tags[0].publisher_id; - } - - const request = formatRequest(payload, bidderRequest); - // add pricing endpoint - return [{method: 'GET', url: PRICING_URL, options: {withCredentials: false}}, request]; - }, - - parseAndMapCpm: function(serverResponse) { - const responseBody = serverResponse.body; - if (Array.isArray(responseBody) && responseBody.length) { - let localData = {}; - responseBody.forEach(cpmPerSize => { - Object.keys(cpmPerSize).forEach(size => { - let obj = {}; - obj[size] = cpmPerSize[size]; - localData = Object.assign({}, localData, obj) - }) - }) - PRICE_MAPPING = localData; - return null; - } - - if (responseBody.version) { - const localPriceMapping = PRICE_MAPPING || DEFAULT_PRICE_MAPPING; - if (responseBody.tags && Array.isArray(responseBody.tags) && responseBody.tags.length) { - responseBody.tags.forEach((tag) => { - if (tag.ads && Array.isArray(tag.ads) && tag.ads.length) { - tag.ads.forEach(ad => { - if (ad.ad_type === 'banner') { - const size = `${ad.rtb.banner.width}x${ad.rtb.banner.height}`; - if (localPriceMapping[size]) { - ad.cpm = localPriceMapping[size]; - } else { - ad.cpm = localPriceMapping['0x0']; - } - } - }) - } - }); - } - } - return responseBody; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, { bidderRequest }) { - serverResponse = this.parseAndMapCpm(serverResponse); - if (!serverResponse) return []; - const bids = []; - if (serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter : ${serverResponse.error}`; - logError(errorMessage); - return bids; - } - - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid, bidderRequest); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); - } - } - }); - } - - if (serverResponse.debug && serverResponse.debug.debug_info) { - let debugHeader = 'AppNexus Debug Auction for Prebid\n\n' - let debugText = debugHeader + serverResponse.debug.debug_info - debugText = debugText - .replace(/(|)/gm, '\t') // Tables - .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables - .replace(/^
/gm, '') // Remove leading
- .replace(/(
\n|
)/gm, '\n') //
- .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 - .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers - .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags - logMessage(debugText); - } - - return bids; - }, - - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { - return [{ - type: 'iframe', - url: 'https://acdn.adnxs.com/dmp/async_usersync.html' - }]; - } - }, - - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function (bid) { - if (bid.native) { - reloadViewabilityScriptWithCorrectParameters(bid); - } +const URL = 'https://goldlayer-api.prod.gbads.net/bid/pbjs'; +const URL_LOCAL = 'http://localhost:3000/bid/pbjs'; +const LOGGING_PERCENTAGE_REGULAR = 0.0001; +const LOGGING_PERCENTAGE_ERROR = 0.001; +const LOGGING_URL = 'https://l.da-services.ch/pb'; + +/* Renderer settings */ +const RENDERER_OPTIONS = { + OUTSTREAM_GP: { + MIN_HEIGHT: 300, + MIN_WIDTH: 300, + URL: 'https://goldplayer.prod.gbads.net/scripts/goldplayer.js' } }; -function reloadViewabilityScriptWithCorrectParameters(bid) { - let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); - - if (viewJsPayload) { - let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; - - let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); - - let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); - - // find iframe containing script tag - let frameArray = document.getElementsByTagName('iframe'); - - // boolean var to modify only one script. That way if there are muliple scripts, - // they won't all point to the same creative. - let modifiedAScript = false; - - // first, loop on all ifames - for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { - let currentFrame = frameArray[i]; - try { - // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 - let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; - - if (nestedDoc) { - // if the doc is present, we look for our jstracker - let scriptArray = nestedDoc.getElementsByTagName('script'); - for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { - let currentScript = scriptArray[j]; - if (currentScript.getAttribute('data-src') == jsTrackerSrc) { - currentScript.setAttribute('src', newJsTrackerSrc); - currentScript.setAttribute('data-src', ''); - if (currentScript.removeAttribute) { - currentScript.removeAttribute('data-src'); - } - modifiedAScript = true; - } - } - } - } catch (exception) { - // trying to access a cross-domain iframe raises a SecurityError - // this is expected and ignored - if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { - // all other cases are raised again to be treated by the calling function - throw exception; - } - } - } - } -} - -function strIsAppnexusViewabilityScript(str) { - let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); - let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; - - let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); - let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; +/* Event types */ +const EVENTS = { + BID_WON: 'bid_won', + TARGETING: 'targeting_set', + RENDER: 'creative_render', + TIMEOUT: 'timeout', + ERROR: 'error' +}; - return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; -} +/* Targeting mapping */ +const TARGETING_KEYS = { + // request level + GEO_LAT: 'lat', + GEO_LON: 'long', + GEO_ZIP: 'zip', + CONNECTION_TYPE: 'connection', + // slot level + VIDEO_DURATION: 'duration', +}; -function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { - let viewJsPayload; - if (isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { - viewJsPayload = jsTrackerArray; - } else if (isArray(jsTrackerArray)) { - for (let i = 0; i < jsTrackerArray.length; i++) { - let currentJsTracker = jsTrackerArray[i]; - if (strIsAppnexusViewabilityScript(currentJsTracker)) { - viewJsPayload = currentJsTracker; - } +/* Native mapping */ +export const OPENRTB = { + NATIVE: { + IMAGE_TYPE: { + ICON: 1, + MAIN: 3, + }, + ASSET_ID: { + TITLE: 1, + IMAGE: 2, + ICON: 3, + BODY: 4, + CTA: 5, + SPONSORED: 6, } } - return viewJsPayload; -} - -function getViewabilityScriptUrlFromPayload(viewJsPayload) { - // extracting the content of the src attribute - // -> substring between src=" and " - let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 - let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); - let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); - return jsTrackerSrc; -} - -function formatRequest(payload, bidderRequest) { - let request = []; - let options = { - withCredentials: true - }; - - let endpointUrl = URL; - - if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { - endpointUrl = URL_SIMPLE; - } - - if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { - options.customHeaders = { - 'X-Is-Test': 1 - }; - } - - if (payload.tags.length > MAX_IMPS_PER_REQUEST) { - const clonedPayload = deepClone(payload); - - chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { - clonedPayload.tags = tags; - const payloadString = JSON.stringify(clonedPayload); - request.push({ - method: 'POST', - url: endpointUrl, - data: payloadString, - bidderRequest, - options - }); - }); - } else { - const payloadString = JSON.stringify(payload); - request = { - method: 'POST', - url: endpointUrl, - data: payloadString, - bidderRequest, - options - }; - } +}; - return request; -} +/* Mapping */ +const convertToCustomTargeting = (bidderRequest) => { + const customTargeting = {}; -function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rtbBid.renderer_id, - url: rtbBid.renderer_url, - config: rendererOptions, - loaded: false, - adUnitCode - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logError('Prebid Error calling setRender on renderer', err); - } - - renderer.setEventHandlers({ - impression: () => logMessage('Outstream video impression event'), - loaded: () => logMessage('Outstream video loaded event'), - ended: () => { - logMessage('Outstream renderer video event'); - document.querySelector(`#${adUnitCode}`).style.display = 'none'; + // geo - lat/long + if (bidderRequest?.ortb2?.device?.geo) { + if (bidderRequest?.ortb2?.device?.geo?.lon) { + customTargeting[TARGETING_KEYS.GEO_LON] = bidderRequest.ortb2.device.geo.lon; } - }); - return renderer; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); - const bid = { - requestId: serverBid.uuid, - cpm: rtbBid.cpm, - creativeId: rtbBid.creative_id, - dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - ttl: 300, - adUnitCode: bidRequest.adUnitCode, - appnexus: { - buyerMemberId: rtbBid.buyer_member_id, - dealPriority: rtbBid.deal_priority, - dealCode: rtbBid.deal_code + if (bidderRequest?.ortb2?.device?.geo?.lat) { + customTargeting[TARGETING_KEYS.GEO_LAT] = bidderRequest.ortb2.device.geo.lat; } - }; - - // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance - if (rtbBid.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); } - if (rtbBid.advertiser_id) { - bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); - } - - if (rtbBid.rtb.video) { - // shared video properties used for all 3 contexts - Object.assign(bid, { - width: rtbBid.rtb.video.player_width, - height: rtbBid.rtb.video.player_height, - vastImpUrl: rtbBid.notify_url, - ttl: 3600 - }); - - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - switch (videoContext) { - case ADPOD: - const primaryCatId = (APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id]) ? APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id] : null; - bid.meta = Object.assign({}, bid.meta, { primaryCatId }); - const dealTier = rtbBid.deal_priority; - bid.video = { - context: ADPOD, - durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), - dealTier - }; - bid.vastUrl = rtbBid.rtb.video.asset_url; + // connection + if (bidderRequest?.ortb2?.device?.connectiontype) { + switch (bidderRequest.ortb2.device.connectiontype) { + case 1: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = 'ethernet'; break; - case OUTSTREAM: - bid.adResponse = serverBid; - bid.adResponse.ad = bid.adResponse.ads[0]; - bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; - bid.vastXml = rtbBid.rtb.video.content; - - if (rtbBid.renderer_url) { - const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); - const rendererOptions = deepAccess(videoBid, 'renderer.options'); - bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); - } + case 2: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = 'wifi'; break; - case INSTREAM: - bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); + case 4: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = '2G'; + break; + case 5: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = '3G'; + break; + case 6: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = '4G'; + break; + case 0: + case 3: + default: break; } - } else if (rtbBid.rtb[NATIVE]) { - const nativeAd = rtbBid.rtb[NATIVE]; - - // setting up the jsTracker: - // we put it as a data-src attribute so that the tracker isn't called - // until we have the adId (see onBidWon) - let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); - - let jsTrackers = nativeAd.javascript_trackers; - - if (jsTrackers == undefined) { - jsTrackers = jsTrackerDisarmed; - } else if (isStr(jsTrackers)) { - jsTrackers = [jsTrackers, jsTrackerDisarmed]; - } else { - jsTrackers.push(jsTrackerDisarmed); - } - - bid[NATIVE] = { - title: nativeAd.title, - body: nativeAd.desc, - body2: nativeAd.desc2, - cta: nativeAd.ctatext, - rating: nativeAd.rating, - sponsoredBy: nativeAd.sponsored, - privacyLink: nativeAd.privacy_link, - address: nativeAd.address, - downloads: nativeAd.downloads, - likes: nativeAd.likes, - phone: nativeAd.phone, - price: nativeAd.price, - salePrice: nativeAd.saleprice, - clickUrl: nativeAd.link.url, - displayUrl: nativeAd.displayurl, - clickTrackers: nativeAd.link.click_trackers, - impressionTrackers: nativeAd.impression_trackers, - javascriptTrackers: jsTrackers - }; - if (nativeAd.main_img) { - bid['native'].image = { - url: nativeAd.main_img.url, - height: nativeAd.main_img.height, - width: nativeAd.main_img.width, - }; - } - if (nativeAd.icon) { - bid['native'].icon = { - url: nativeAd.icon.url, - height: nativeAd.icon.height, - width: nativeAd.icon.width, - }; - } - } else { - Object.assign(bid, { - width: rtbBid.rtb.banner.width, - height: rtbBid.rtb.banner.height, - ad: rtbBid.rtb.banner.content - }); - try { - if (rtbBid.rtb.trackers) { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = createTrackPixelHtml(url); - bid.ad += tracker; - } - } catch (error) { - logError('Error appending tracking pixel', error); - } - } - - return bid; -} - -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); - } else { - tag.code = bid.params.invCode; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; - tag.prebid = true; - tag.disable_psa = true; - let bidFloor = getBidFloor(bid); - if (bidFloor) { - tag.reserve = bidFloor; - } - if (bid.params.position) { - tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.publisherId) { - tag.publisher_id = parseInt(bid.params.publisherId, 10); - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords); - - let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - if (gpid) { - tag.gpid = gpid; - } - - if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - if (tag.sizes.length === 0) { - tag.sizes = transformSizes([1, 1]); - } - - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = { layouts: [nativeRequest] }; - } - } - - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = deepAccess(bid, 'mediaTypes.video.context'); - - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } - - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'context': - case 'playback_method': - let type = bid.params.video[param]; - type = (isArray(type)) ? type[0] : type; - tag.video[param] = VIDEO_MAPPING[param][type]; - break; - // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks - case 'frameworks': - break; - default: - tag.video[param] = bid.params.video[param]; - } - }); - - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; - } - } - - // use IAB ORTB values if the corresponding values weren't already set by bid.params.video - if (videoMediaType) { - tag.video = tag.video || {}; - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_RTB_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'minduration': - case 'maxduration': - if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; - break; - case 'skip': - if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); - break; - case 'skipafter': - if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; - break; - case 'playbackmethod': - if (typeof tag.video['playback_method'] !== 'number') { - let type = videoMediaType[param]; - type = (isArray(type)) ? type[0] : type; - - // we only support iab's options 1-4 at this time. - if (type >= 1 && type <= 4) { - tag.video['playback_method'] = type; - } - } - break; - case 'api': - if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { - // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) - let apiTmp = videoMediaType[param].map(val => { - let v = (val === 4) ? 5 : (val === 5) ? 4 : val; - - if (v >= 1 && v <= 5) { - return v; - } - }).filter(v => v); - tag['video_frameworks'] = apiTmp; - } - break; - } - }); } - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + // zip + if (bidderRequest?.ortb2?.device?.geo?.zip) { + customTargeting[TARGETING_KEYS.GEO_ZIP] = bidderRequest.ortb2.device.geo.zip; } - if (bid.params.frameworks && isArray(bid.params.frameworks)) { - tag['banner_frameworks'] = bid.params.frameworks; - } - - if (bid.mediaTypes?.banner) { - tag.ad_types.push(BANNER); - } - - if (tag.ad_types.length === 0) { - delete tag.ad_types; - } - - return tag; + return customTargeting; } -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; +const convertToCustomSlotTargeting = (validBidRequest) => { + const customTargeting = {}; - if (isArray(requestSizes) && requestSizes.length === 2 && - !isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); + // Video duration + if (validBidRequest.mediaTypes?.[VIDEO]) { + if (validBidRequest.params?.video?.maxduration) { + const duration = validBidRequest.params?.video?.maxduration; + if (duration <= 15) customTargeting[TARGETING_KEYS.VIDEO_DURATION] = 'M'; + if (duration > 15 && duration <= 30) customTargeting[TARGETING_KEYS.VIDEO_DURATION] = 'XL'; + if (duration > 30) customTargeting[TARGETING_KEYS.VIDEO_DURATION] = 'XXL'; } } - return sizes; + return customTargeting } -function hasUserInfo(bid) { - return !!bid.params.user; -} +const convertToProprietaryData = (validBidRequests, bidderRequest) => { + const requestData = { + mock: false, + debug: false, + timestampStart: undefined, + timestampEnd: undefined, + config: { + publisher: { + id: undefined, + } + }, + gdpr: { + consent: undefined, + consentString: undefined, + }, + contextInfo: { + contentUrl: undefined, + bidderResources: undefined, + }, + appInfo: { + id: undefined, + }, + userInfo: { + ip: undefined, + ua: undefined, + ifa: undefined, + ppid: [], + }, + slots: [], + targetings: {}, + }; -function hasMemberId(bid) { - return !!parseInt(bid.params.member, 10); -} + // Set timestamps + requestData.timestampStart = Date.now(); + requestData.timestampEnd = Date.now() + (!isNaN(bidderRequest.timeout) ? Number(bidderRequest.timeout) : 0); -function hasAppDeviceInfo(bid) { - if (bid.params) { - return !!bid.params.app + // Set config + if (validBidRequests[0]?.params?.publisherId) { + requestData.config.publisher.id = validBidRequests[0].params.publisherId; } -} -function hasAppId(bid) { - if (bid.params && bid.params.app) { - return !!bid.params.app.id + // Set GDPR + if (bidderRequest?.gdprConsent) { + requestData.gdpr.consent = bidderRequest.gdprConsent.gdprApplies; + requestData.gdpr.consentString = bidderRequest.gdprConsent.consentString; } - return !!bid.params.app -} -function hasDebug(bid) { - return !!bid.debug -} + // Set contextInfo + requestData.contextInfo.contentUrl = bidderRequest.refererInfo?.canonicalUrl || bidderRequest.refererInfo?.topmostLocation || bidderRequest?.ortb2?.site?.page; -function hasAdPod(bid) { - return ( - bid.mediaTypes && - bid.mediaTypes.video && - bid.mediaTypes.video.context === ADPOD - ); -} - -function hasOmidSupport(bid) { - let hasOmid = false; - const bidderParams = bid.params; - const videoParams = bid.params.video; - if (bidderParams.frameworks && isArray(bidderParams.frameworks)) { - hasOmid = includes(bid.params.frameworks, 6); - } - if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) { - hasOmid = includes(bid.params.video.frameworks, 6); - } - return hasOmid; -} + // Set appInfo + requestData.appInfo.id = bidderRequest?.ortb2?.site?.domain || bidderRequest.refererInfo?.page; -/** - * Expand an adpod placement into a set of request objects according to the - * total adpod duration and the range of duration seconds. Sets minduration/ - * maxduration video property according to requireExactDuration configuration - */ -function createAdPodRequest(tags, adPodBid) { - const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; + // Set userInfo + requestData.userInfo.ip = bidderRequest?.ortb2?.device?.ip || navigator.ip; + requestData.userInfo.ua = bidderRequest?.ortb2?.device?.ua || navigator.userAgent; - const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = Math.max(...durationRangeSec); - - const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); - let request = fill(...tagToDuplicate, numberOfPlacements); - - if (requireExactDuration) { - const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); - const chunked = chunk(request, divider); - - // each configured duration is set as min/maxduration for a subset of requests - durationRangeSec.forEach((duration, index) => { - chunked[index].map(tag => { - setVideoProperty(tag, 'minduration', duration); - setVideoProperty(tag, 'maxduration', duration); + // Set userInfo.ppid + requestData.userInfo.ppid = (validBidRequests || []).reduce((ppids, validBidRequest) => { + const extractedPpids = []; + (validBidRequest.userIdAsEids || []).forEach((eid) => { + (eid?.uids || []).forEach(uid => { + if (uid?.ext?.stype === 'ppuid') { + const isExistingInExtracted = !!extractedPpids.find(id => id.source === eid.source); + const isExistingInPpids = !!ppids.find(id => id.source === eid.source); + if (!isExistingInExtracted && !isExistingInPpids) extractedPpids.push({source: eid.source, id: uid.id}); + } }); - }); + }) + return [...ppids, ...extractedPpids]; + }, []); + + // Set userInfo.ifa + if (bidderRequest.ortb2?.device?.ifa) { + requestData.userInfo.ifa = bidderRequest.ortb2.device.ifa; } else { - // all maxdurations should be the same - request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + requestData.userInfo.ifa = validBidRequests.find(validBidRequest => { + return !!validBidRequest.ortb2?.device?.ifa; + }); } - return request; -} - -function getAdPodPlacementNumber(videoParams) { - const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = Math.min(...durationRangeSec); - const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); - - return requireExactDuration - ? Math.max(numberOfPlacements, durationRangeSec.length) - : numberOfPlacements; -} - -function setVideoProperty(tag, key, value) { - if (isEmpty(tag.video)) { tag.video = {}; } - tag.video[key] = value; -} - -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} - -function buildNativeRequest(params) { - const request = {}; - - // map standard prebid native asset identifier to /ut parameters - // e.g., tag specifies `body` but /ut only knows `description`. - // mapping may be in form {tag: ''} or - // {tag: {serverName: '', requiredParams: {...}}} - Object.keys(params).forEach(key => { - // check if one of the forms is used, otherwise - // a mapping wasn't specified so pass the key straight through - const requestKey = - (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || - NATIVE_MAPPING[key] || - key; - - // required params are always passed on request - const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; - request[requestKey] = Object.assign({}, requiredParams, params[key]); - - // convert the sizes of image/icon assets to proper format (if needed) - const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); - if (isImageAsset && request[requestKey].sizes) { - let sizes = request[requestKey].sizes; - if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { - request[requestKey].sizes = transformSizes(request[requestKey].sizes); + // Set slots + requestData.slots = validBidRequests.map((validBidRequest) => { + const slot = { + id: validBidRequest.params?.slotId, + sizes: [ + ...(validBidRequest.sizes || []), + ...(validBidRequest.mediaTypes?.[VIDEO]?.sizes ? validBidRequest.mediaTypes[VIDEO].sizes : []) + ], + targetings: { + ...validBidRequest?.params?.customTargeting, + ...convertToCustomSlotTargeting(validBidRequest) } - } - - if (requestKey === NATIVE_MAPPING.privacyLink) { - request.privacy_supported = true; - } + }; + return slot; }); - return request; -} + // Set targetings + requestData.targetings = convertToCustomTargeting(bidderRequest); -/** - * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. - * @param {string} elementId element id - */ -function hidedfpContainer(elementId) { - var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); - if (el[0]) { - el[0].style.setProperty('display', 'none'); - } + return requestData; } -function hideSASIframe(elementId) { - try { - // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. - const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); - if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { - el[0].nextSibling.style.setProperty('display', 'none'); - } - } catch (e) { - // element not found! +const getRendererForBid = (bidRequest, creative) => { + if (!bidRequest.renderer && creative.contextType === 'video_outstream') { + if (!creative.vastUrl && !creative.vastXml) return undefined; + + const config = { documentResolver: (_, sourceDocument, renderDocument) => renderDocument ?? sourceDocument }; + const renderer = Renderer.install({id: bidRequest.bidId, url: RENDERER_OPTIONS.OUTSTREAM_GP.URL, adUnitCode: bidRequest.adUnitCode, config}); + + renderer.setRender((bid, doc) => { + bid.renderer.push(() => { + if (doc.defaultView?.GoldPlayer) { + const options = { + vastUrl: creative.vastUrl, + vastXML: creative.vastXml, + autoplay: false, + muted: false, + controls: true, + styling: { progressbarColor: '#000' }, + videoHeight: Math.min(doc.defaultView?.innerWidth / 16 * 9, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_HEIGHT), + videoVerticalHeight: Math.min(doc.defaultView?.innerWidth / 9 * 16, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_WIDTH), + }; + const GP = doc.defaultView.GoldPlayer; + const player = new GP(options); + player.play(); + } + }); + }); + + return renderer; } + return undefined; } -function outstreamRender(bid) { - hidedfpContainer(bid.adUnitCode); - hideSASIframe(bid.adUnitCode); - // push to render queue because ANOutstreamVideo may not be loaded yet - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - tagId: bid.adResponse.tag_id, - sizes: [bid.getSize().split('x')], - targetId: bid.adUnitCode, // target div id to render video - uuid: bid.adResponse.uuid, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); +const getNativeAssetsForBid = (bidRequest, creative) => { + if (creative.contextType === 'native' && creative.ad) { + const nativeAssets = JSON.parse(creative.ad); + const result = { + clickUrl: encodeURI(nativeAssets?.link?.url), + impressionTrackers: nativeAssets?.imptrackers, + clickTrackers: nativeAssets?.clicktrackers, + javascriptTrackers: nativeAssets?.jstracker && [nativeAssets.jstracker], + }; + (nativeAssets?.assets || []).forEach(asset => { + switch (asset.id) { + case OPENRTB.NATIVE.ASSET_ID.TITLE: + result.title = asset.title?.text; + break; + case OPENRTB.NATIVE.ASSET_ID.IMAGE: + result.image = { + url: encodeURI(asset.img?.url), + width: asset.img?.w, + height: asset.img?.h + }; + break; + case OPENRTB.NATIVE.ASSET_ID.ICON: + result.icon = { + url: encodeURI(asset.img.url), + width: asset.img?.w, + height: asset.img?.h + }; + break; + case OPENRTB.NATIVE.ASSET_ID.BODY: + result.body = asset.data?.value; + break; + case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + result.sponsoredBy = asset.data?.value; + break; + case OPENRTB.NATIVE.ASSET_ID.CTA: + result.cta = asset.data?.value; + break; + } + }); + return result; + } + return undefined; } -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} +const convertProprietaryResponseToBidResponses = (serverResponse, bidRequest) => { + const bidRequests = bidRequest?.bidderRequest?.bids || []; + const creativeGroups = serverResponse?.body?.creatives || {}; -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { - return VIDEO; - } else if (adType === NATIVE) { - return NATIVE; - } else { - return BANNER; - } + return bidRequests.reduce((bidResponses, bidRequest) => { + const matchingCreativeGroup = (creativeGroups[bidRequest.params?.slotId] || []).filter((creative) => { + if (bidRequest.mediaTypes?.[BANNER] && creative.mediaType === BANNER) return true; + if (bidRequest.mediaTypes?.[VIDEO] && creative.mediaType === VIDEO) return true; + if (bidRequest.mediaTypes?.[NATIVE] && creative.mediaType === NATIVE) return true; + return false; + }); + const matchingBidResponses = matchingCreativeGroup.map((creative) => { + return { + requestId: bidRequest.bidId, + cpm: creative.cpm, + currency: creative.currency, + width: creative.width, + height: creative.height, + creativeId: creative.creativeId, + dealId: creative.dealId, + netRevenue: creative.netRevenue, + ttl: creative.ttl, + ad: creative.ad, + vastUrl: creative.vastUrl, + vastXml: creative.vastXml, + mediaType: creative.mediaType, + meta: creative.meta, + native: getNativeAssetsForBid(bidRequest, creative), + renderer: getRendererForBid(bidRequest, creative), + }; + }); + return [...bidResponses, ...matchingBidResponses]; + }, []); } -function addUserId(eids, id, source, rti) { - if (id) { - if (rti) { - eids.push({ source, id, rti_partner: rti }); - } else { - eids.push({ source, id }); - } - } - return eids; +/* Logging */ +const sendLog = (data, percentage = 0.0001) => { + if (Math.random() > percentage) return; + const encodedData = `data=${window.btoa(JSON.stringify({...data, source: 'goldbach_pbjs', projectedAmount: (1 / percentage)}))}`; + ajax(LOGGING_URL, null, encodedData, { + withCredentials: false, + method: 'POST', + crossOrigin: true, + contentType: 'application/x-www-form-urlencoded', + }); } -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return (bid.params.reserve) ? bid.params.reserve : null; - } - - let floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid: function (bid) { + return typeof bid.params.publisherId === 'string' && Array.isArray(bid.sizes); + }, + buildRequests: function (validBidRequests, bidderRequest) { + const url = IS_LOCAL_MODE ? URL_LOCAL : URL; + const data = convertToProprietaryData(validBidRequests, bidderRequest); + return [{ + method: 'POST', + url: url, + data: data, + bidderRequest: bidderRequest, + options: { + withCredentials: false, + contentType: 'application/json', + } + }]; + }, + interpretResponse: function (serverResponse, request) { + return convertProprietaryResponseToBidResponses(serverResponse, request); + }, + onTimeout: function(timeoutData) { + const payload = { + event: EVENTS.TIMEOUT, + error: timeoutData, + }; + sendLog(payload, LOGGING_PERCENTAGE_ERROR); + }, + onBidWon: function(bid) { + const payload = { + event: EVENTS.BID_WON, + adUnitCode: bid.adUnitCode, + adId: bid.adId, + mediaType: bid.mediaType, + size: bid.size, + }; + sendLog(payload, LOGGING_PERCENTAGE_REGULAR); + }, + onSetTargeting: function(bid) { + const payload = { + event: EVENTS.TARGETING, + adUnitCode: bid.adUnitCode, + adId: bid.adId, + mediaType: bid.mediaType, + size: bid.size, + }; + sendLog(payload, LOGGING_PERCENTAGE_REGULAR); + }, + onBidderError: function({ error }) { + const payload = { + event: EVENTS.ERROR, + error: error, + }; + sendLog(payload, LOGGING_PERCENTAGE_ERROR); + }, + onAdRenderSucceeded: function(bid) { + const payload = { + event: EVENTS.RENDER, + adUnitCode: bid.adUnitCode, + adId: bid.adId, + mediaType: bid.mediaType, + size: bid.size, + }; + sendLog(payload, LOGGING_PERCENTAGE_REGULAR); + }, } registerBidder(spec); diff --git a/modules/goldbachBidAdapter.md b/modules/goldbachBidAdapter.md index f7c9479439b..7335c95f77f 100644 --- a/modules/goldbachBidAdapter.md +++ b/modules/goldbachBidAdapter.md @@ -1,151 +1,89 @@ -#Overview +# Goldbach Bidder Adapter -``` -Module Name: Goldbach Bid Adapter -Module Type: Bidder Adapter -Maintainer: dusan.veljovic@goldbach.com -``` +## Overview -# Description +```text +Module Name: Goldbach Bidder Adapter +Module Type: Bidder Adapter +Maintainer: benjamin.brachmann@goldbach.com +``` -Connects to Xandr exchange for bids. +## Description -Goldbach bid adapter supports Banner, Video (instream and outstream) and Native. +Module that connects to Goldbach SSP demand sources. -# Test Parameters +```shell +gulp build --modules=goldbachBidAdapter,userId,pubProvidedIdSystem ``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'goldbach', - params: { - placementId: 13144370 - } - }] - }, - // Native adUnit - { - code: 'native-div', - sizes: [[1, 1]], - mediaTypes: { - native: { - title: { - required: true - }, - body: { - required: true - }, - image: { - required: true - }, - sponsoredBy: { - required: true - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'goldbach', - params: { - placementId: 13232354, - allowSmallerSizes: true - } - }] - }, - // Video instream adUnit - { - code: 'video-instream', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' + +## Test Parameters + +```javascript + var adUnits = [ + { + code: 'au-1', + mediaTypes: { + video: { + sizes: [[640, 480]], + maxduration: 30, + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + publisherId: 'goldbach_debug', + slotId: '/1235/example.com/video/video/example' + } + } + ] }, - }, - bids: [{ - goldbach: 'goldbach', - params: { - placementId: 13232361, - video: { - skippable: true, - playback_methods: ['auto_play_sound_off'] - } - } - }] - }, - // Video outstream adUnit - { - code: 'video-outstream', - sizes: [[300, 250]], - mediaTypes: { - video: { - playerSize: [[300, 250]], - context: 'outstream', - // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. - // To note - goldbach supports additional values for our system that are not part of the ORTB spec. If you want - // to use these values, they will have to be declared in the bids[].params.video object instead using the goldbach syntax. - // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will - // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. - minduration: 1, - maxduration: 60, - skip: 0, // 1 - true, 0 - false - skipafter: 5, - playbackmethod: [2], // note - we only support options 1-4 at this time - api: [1,2,3] // note - option 6 is not supported at this time - } - }, - bids: [ - { - bidder: 'goldbach', - params: { - placementId: 13232385, - video: { - skippable: true, - playback_method: 'auto_play_sound_off' - } - } - } - ] - }, - // Banner adUnit in a App Webview - // Only use this for situations where prebid.js is in a webview of an App - // See Prebid Mobile for displaying ads via an SDK - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - } - bids: [{ - bidder: 'goldbach', - params: { - placementId: 13144370, - app: { - id: "B1O2W3M4AN.com.prebid.webview", - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier - aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier - md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID - sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID - windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier - } - } - } - }] - } -]; + { + code: 'au-2', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + image: { + required: true, + sizes: [300, 157] + }, + icon: { + required: true, + sizes: [30, 30] + } + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + publisherId: 'goldbach_debug', + slotId: '/1235/example.com/inside-full-test-native/example' + } + } + ] + }, + { + code: 'au-3', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + publisherId: 'goldbach_debug', + slotId: '/1235/example.com/inside-full-test-banner/example' + } + } + ] + }, + ]; ``` diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js index 6ea84ed6931..744379efd19 100644 --- a/test/spec/modules/goldbachBidAdapter_spec.js +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -1,16 +1,424 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { spec } from 'modules/goldbachBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; import { auctionManager } from 'src/auctionManager.js'; import { deepClone } from 'src/utils.js'; -import { config } from 'src/config.js'; +import { VIDEO } from 'src/mediaTypes.js'; +import * as ajaxLib from 'src/ajax.js'; -const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; -const PRICING_ENDPOINT = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; +const BIDDER_NAME = 'goldbach' +const ENDPOINT = 'https://goldlayer-api.prod.gbads.net/bid/pbjs'; -describe('GoldbachXandrAdapter', function () { +/* Eids */ +let eids = [ + { + source: 'goldbach.com', + uids: [ + { + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1b', + atype: 1, + ext: { stype: 'ppuid' } + } + ] + }, + { + source: 'niceid.live', + uids: [ + { + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1a', + atype: 1, + ext: { stype: 'ppuid' } + } + ] + }, + { + source: 'otherid.live', + uids: [ + { + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1a', + atype: 1, + ext: { stype: 'other-id' } + } + ] + } +]; + +const validNativeAd = { + link: { + url: 'https://example.com/cta', + }, + imptrackers: [ + 'https://example.com/impression1', + 'https://example.com/impression2', + ], + assets: [ + { + id: 1, + title: { + text: 'Amazing Product - Don’t Miss Out!', + }, + }, + { + id: 2, + img: { + url: 'https://example.com/main-image.jpg', + w: 300, + h: 250, + }, + }, + { + id: 3, + img: { + url: 'https://example.com/icon-image.jpg', + w: 50, + h: 50, + }, + }, + { + id: 4, + data: { + value: 'This is the description of the product. Its so good youll love it!', + }, + }, + { + id: 5, + data: { + value: 'Sponsored by ExampleBrand', + }, + }, + { + id: 6, + data: { + value: 'Shop Now', + }, + }, + ], +}; + +/* Ortb2 bid information */ +let ortb2 = { + device: { + ip: '133.713.371.337', + connectiontype: 6, + w: 1512, + h: 982, + ifa: '23575619-ef35-4908-b468-ffc4000cdf07', + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + geo: {lat: 47.318054, lon: 8.582883, zip: '8700'} + }, + site: { + domain: 'publisher-page.ch', + page: 'https://publisher-page.ch/home', + publisher: { domain: 'publisher-page.ch' }, + ref: 'https://publisher-page.ch/home' + }, + user: { + ext: { + eids: eids + } + } +}; + +/* Minimal bidderRequest */ +let validBidderRequest = { + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + start: 1731680672810, + auctionStart: 1731680672808, + ortb2: ortb2, + bidderCode: BIDDER_NAME, + gdprConsent: { + gdprApplies: true, + consentString: 'trust-me-i-consent' + }, + timeout: 300 +}; + +/* Minimal validBidRequests */ +let validBidRequests = [ + { + bidder: BIDDER_NAME, + adUnitCode: 'au-1', + adUnitId: 'c3400db6-c4c5-465e-bf67-1545751944b7', + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + bidId: '3d52a1909b972a', + bidderRequestId: '2b63a1826ab946', + userIdAsEids: eids, + ortb2: ortb2, + mediaTypes: { + banner: { + sizes: [[300, 50], [300, 250], [300, 600], [320, 50], [320, 480], [320, 64], [320, 160], [320, 416], [336, 280]] + } + }, + sizes: [[300, 50], [300, 250], [300, 600], [320, 50], [320, 480], [320, 64], [320, 160], [320, 416], [336, 280]], + params: { + publisherId: 'de-publisher.ch-ios', + slotId: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test', + customTargeting: { + language: 'de' + } + } + }, + { + bidder: BIDDER_NAME, + adUnitCode: 'au-2', + adUnitId: 'c3400db6-c4c5-465e-bf67-1545751944b8', + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + bidId: '3d52a1909b972b', + bidderRequestId: '2b63a1826ab946', + userIdAsEids: eids, + ortb2: ortb2, + mediaTypes: { + video: { + sizes: [[640, 480]] + } + }, + sizes: [[640, 480]], + params: { + publisherId: 'de-publisher.ch-ios', + slotId: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/video', + video: { + maxduration: 30, + }, + customTargeting: { + language: 'de' + } + } + }, + { + bidder: BIDDER_NAME, + adUnitCode: 'au-3', + adUnitId: 'c3400db6-c4c5-465e-bf67-1545751944b9', + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + bidId: '3d52a1909b972c', + bidderRequestId: '2b63a1826ab946', + userIdAsEids: eids, + ortb2: ortb2, + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + image: { + required: true, + sizes: [300, 157] + }, + icon: { + required: true, + sizes: [30, 30] + }, + body: { + required: true, + len: 150 + }, + cta: { + required: true, + len: 15 + }, + sponsoredBy: { + required: true, + len: 25 + }, + } + }, + params: { + publisherId: 'de-publisher.ch-ios', + slotId: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/native', + customTargeting: { + language: 'de' + } + } + } +]; + +/* Creative request send to server */ +let validCreativeRequest = { + mock: false, + debug: false, + timestampStart: 1731680672811, + timestampEnd: 1731680675811, + config: { + publisher: { + id: 'de-20minuten.ch', + }, + }, + gdpr: {}, + contextInfo: { + contentUrl: 'http://127.0.0.1:5500/sample-request.html', + }, + appInfo: { + id: '127.0.0.1:5500', + }, + userInfo: { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + ifa: '23575619-ef35-4908-b468-ffc4000cdf07', + ppid: [ + { + source: 'oneid.live', + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1b', + }, + { + source: 'goldbach.com', + id: 'aa07ead5044f47bb28894ffa0346ed2c', + }, + ], + }, + slots: [ + { + id: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test', + sizes: [ + [300, 50], + [300, 250], + [300, 600], + [320, 50], + [320, 480], + [320, 64], + [320, 160], + [320, 416], + [336, 280], + ], + targetings: { + gpsenabled: 'false', + fr: 'false', + pagetype: 'story', + darkmode: 'false', + userloggedin: 'false', + iosbuild: '24110', + language: 'de', + storyId: '103211763', + connection: 'wifi', + }, + }, + { + id: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/video', + sizes: [[640, 480]], + targetings: { + gpsenabled: 'false', + fr: 'false', + pagetype: 'story', + darkmode: 'false', + userloggedin: 'false', + iosbuild: '24110', + language: 'de', + storyId: '103211763', + connection: 'wifi', + duration: 'XL', + }, + }, + ], + targetings: { + long: 8.582883, + lat: 47.318054, + connection: '4G', + zip: '8700', + }, +}; + +/* Creative response received from server */ +let validCreativeResponse = { + creatives: { + '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test': [ + { + cpm: 32.2, + currency: 'USD', + width: 1, + height: 1, + creativeId: '1', + ttl: 3600, + mediaType: 'native', + netRevenue: true, + contextType: 'native', + ad: JSON.stringify(validNativeAd), + meta: { + advertiserDomains: ['example.com'], + mediaType: 'native' + } + }, + { + cpm: 21.9, + currency: 'USD', + width: 300, + height: 50, + creativeId: '2', + ttl: 3600, + mediaType: 'banner', + netRevenue: true, + contextType: 'banner', + ad: 'banner-ad', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'banner' + } + } + ], + '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/video': [ + { + cpm: 44.2, + currency: 'USD', + width: 1, + height: 1, + creativeId: '3', + ttl: 3600, + mediaType: 'video', + netRevenue: true, + contextType: 'video_preroll', + ad: 'video-ad', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'video' + } + } + ], + '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/native': [ + { + cpm: 10.2, + currency: 'USD', + width: 1, + height: 1, + creativeId: '4', + ttl: 3600, + mediaType: 'native', + netRevenue: true, + contextType: 'native', + ad: JSON.stringify(validNativeAd), + meta: { + advertiserDomains: ['example.com'], + mediaType: 'native' + } + } + ], + } +}; + +/* composed request */ +let validRequest = { + url: ENDPOINT, + method: 'POST', + data: validCreativeRequest, + options: { + contentType: 'application/json', + withCredentials: false + }, + bidderRequest: { + ...validBidderRequest, + bids: validBidRequests + } +} + +describe('GoldbachBidAdapter', function () { const adapter = newBidder(spec); + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(ajaxLib, 'ajax'); + sinon.stub(Math, 'random').returns(0); + }); + + afterEach(() => { + ajaxStub.restore(); + Math.random.restore(); + }); describe('inherited functions', function () { it('exists and is a function', function () { @@ -20,37 +428,23 @@ describe('GoldbachXandrAdapter', function () { describe('isBidRequestValid', function () { let bid = { - 'bidder': 'goldbach', - 'params': { - 'placementId': '10433394' + bidder: BIDDER_NAME, + params: { + publisherId: 'de-publisher.ch-ios', }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test', + sizes: [[300, 250], [300, 600]] }; it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when required params found', function () { - let invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = { - 'member': '1234', - 'invCode': 'ABCD' - }; - - expect(spec.isBidRequestValid(invalidBid)).to.equal(true); - }); - it('should return false when required params are not passed', function () { let invalidBid = Object.assign({}, bid); delete invalidBid.params; invalidBid.params = { - 'placementId': 0 + publisherId: undefined }; expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); @@ -58,20 +452,6 @@ describe('GoldbachXandrAdapter', function () { describe('buildRequests', function () { let getAdUnitsStub; - let bidRequests = [ - { - 'bidder': 'goldbach', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; beforeEach(function() { getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { @@ -83,1258 +463,353 @@ describe('GoldbachXandrAdapter', function () { getAdUnitsStub.restore(); }); - it('should parse out private sizes', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - privateSizes: [300, 250] - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); + it('should use defined endpoint', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - expect(payload.tags[0].private_sizes).to.exist; - expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); - }); - - it('should add publisher_id in request', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - publisherId: '1231234' - } - }); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].publisher_id).to.exist; - expect(payload.tags[0].publisher_id).to.deep.equal(1231234); - expect(payload.publisher_id).to.exist; - expect(payload.publisher_id).to.deep.equal(1231234); + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(ENDPOINT); }) - it('should add source and verison to the tag', function () { - const request = spec.buildRequests(bidRequests)[1]; - const payload = JSON.parse(request.data); - expect(payload.sdk).to.exist; - expect(payload.sdk).to.deep.equal({ - source: 'pbjs', - version: '$prebid.version$' - }); - }); + it('should parse all bids to valid slots', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - it('should populate the ad_types array on all requests', function () { - let adUnits = [{ - code: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bids: [{ - bidder: 'goldbach', - params: { - placementId: '10433394' - } - }], - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' - }]; - - ['banner', 'video', 'native'].forEach(type => { - getAdUnitsStub.callsFake(function(...args) { - return adUnits; - }); - - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes[type] = {}; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.deep.equal([type]); - - if (type === 'banner') { - delete adUnits[0].mediaTypes; - } - }); - }); - - it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.not.exist; - }); - - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests)[1]; - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests)[0]; - expect(request.url).to.equal(PRICING_ENDPOINT); - expect(request.method).to.equal('GET'); + expect(payload.slots).to.exist; + expect(Array.isArray(payload.slots)).to.be.true; + expect(payload.slots.length).to.equal(3); + expect(payload.slots[0].id).to.equal(bidRequests[0].params.slotId); + expect(Array.isArray(payload.slots[0].sizes)).to.be.true; + expect(payload.slots[0].sizes.length).to.equal(bidRequests[0].sizes.length); + expect(payload.slots[1].id).to.equal(bidRequests[1].params.slotId); + expect(Array.isArray(payload.slots[1].sizes)).to.be.true; }); - it('should attach valid video params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - video: { - id: 123, - minduration: 100, - foobar: 'invalid' - } - } - } - ); + it('should parse all video bids to valid video slots (use video sizes)', function () { + let bidRequests = validBidRequests.map(request => Object.assign({}, [])); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('should include ORTB video values when video params were not set', function() { - let bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { - skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 - }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); - - it('should add video property when adUnit includes a renderer', function () { - const videoData = { + const requests = spec.buildRequests([{ + ...bidRequests[1], + sizes: [], mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] - } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] + [VIDEO]: { + sizes: [[640, 480]] } } - }; + }], bidderRequest); + const payload = requests[0].data; - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () {} - } - }); - - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); - - const request = spec.buildRequests([bidRequest1, bidRequest2])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: 2, - custom_renderer_present: true - }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 - }); + expect(payload.slots.length).to.equal(1); + expect(payload.slots[0].sizes.length).to.equal(1); + expect(payload.slots[0].sizes[0][0]).to.equal(640); + expect(payload.slots[0].sizes[0][1]).to.equal(480); }); - it('should attach valid user params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - user: { - externalUid: '123', - segments: [123, { id: 987, value: 876 }], - foobar: 'invalid' - } - } - } - ); + it('should set timestamps on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - expect(payload.user).to.exist; - expect(payload.user).to.deep.equal({ - external_uid: '123', - segments: [{id: 123}, {id: 987, value: 876}] - }); + expect(payload.timestampStart).to.exist; + expect(payload.timestampStart).to.be.greaterThan(1) + expect(payload.timestampEnd).to.exist; + expect(payload.timestampEnd).to.be.greaterThan(1) }); - it('should attach reserve param when either bid param or getFloor function exists', function () { - let getFloorResponse = { currency: 'USD', floor: 3 }; - let request, payload = null; - let bidRequest = deepClone(bidRequests[0]); - - // 1 -> reserve not defined, getFloor not defined > empty - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); - - expect(payload.tags[0].reserve).to.not.exist; - - // 2 -> reserve is defined, getFloor not defined > reserve is used - bidRequest.params = { - 'placementId': '10433394', - 'reserve': 0.5 - }; - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); - - expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); - - // 3 -> reserve is defined, getFloor is defined > getFloor is used - bidRequest.getFloor = () => getFloorResponse; + it('should set config on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - expect(payload.tags[0].reserve).to.exist.and.to.equal(3); + expect(payload.config.publisher.id).to.equal(bidRequests[0].params.publisherId); }); - it('should duplicate adpod placements into batches and set correct maxduration', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); + it('should set config on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); + expect(payload.config.publisher.id).to.equal(bidRequests[0].params.publisherId); }); - it('should round down adpod placements when numbers are uneven', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); + it('should set gdpr on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - it('should duplicate adpod placements when requireExactDuration is set', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest])[1]; - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.equal(bidderRequest.gdprConsent.gdprApplies); + expect(payload.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); }); - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - // it('should contain hb_source value for adpod', function() { - // let bidRequest = Object.assign({}, - // bidRequests[0], - // { - // params: { placementId: '14542875' } - // }, - // { - // mediaTypes: { - // video: { - // context: 'adpod', - // playerSize: [640, 480], - // adPodDurationSec: 300, - // durationRangeSec: [15, 30], - // } - // } - // } - // ); - // const request = spec.buildRequests([bidRequest])[1]; - // const payload = JSON.parse(request.data); - // expect(payload.tags[0].hb_source).to.deep.equal(7); - // }); - - it('should contain hb_source value for other media', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'banner', - params: { - sizes: [[300, 250], [300, 600]], - placementId: 13144370 - } - } - ); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('adds brand_category_exclusion to request when set', function() { - let bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('adpod.brandCategoryExclusion') - .returns(true); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.brand_category_uniqueness).to.equal(true); - - config.getConfig.restore(); - }); - - it('should attach native params to the request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - title: {required: true}, - body: {required: true}, - body2: {required: true}, - image: {required: true, sizes: [100, 100]}, - icon: {required: true}, - cta: {required: false}, - rating: {required: true}, - sponsoredBy: {required: true}, - privacyLink: {required: true}, - displayUrl: {required: true}, - address: {required: true}, - downloads: {required: true}, - likes: {required: true}, - phone: {required: true}, - price: {required: true}, - salePrice: {required: true} - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - title: {required: true}, - description: {required: true}, - desc2: {required: true}, - main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, - icon: {required: true}, - ctatext: {required: false}, - rating: {required: true}, - sponsored_by: {required: true}, - privacy_link: {required: true}, - displayurl: {required: true}, - address: {required: true}, - downloads: {required: true}, - likes: {required: true}, - phone: {required: true}, - price: {required: true}, - saleprice: {required: true}, - privacy_supported: true - }); - expect(payload.tags[0].hb_source).to.equal(1); - }); - - it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - image: { required: true } - } - } - ); - bidRequest.sizes = [[150, 100], [300, 250]]; - - let request = spec.buildRequests([bidRequest])[1]; - let payload = JSON.parse(request.data); - expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); - - delete bidRequest.sizes; - - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); - - expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); - }); - - it('should convert keyword params to proper form and attaches to request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); + it('should set contextInfo on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - it('should add payment rules to the request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - usePaymentRule: true - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].use_pmt_rule).to.equal(true); - }); - - it('should add gpid to the request', function () { - let testGpid = '/12345/my-gpt-tag-0'; - let bidRequest = deepClone(bidRequests[0]); - bidRequest.ortb2Imp = { ext: { data: { pbadslot: testGpid } } }; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) - }); - - it('should add gdpr consent information to the request', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let bidderRequest = { - 'bidderCode': 'goldbach', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - addtlConsent: '1~7.12.35.62.66.70.89.93.108' - } - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - expect(request.options).to.deep.equal({withCredentials: true}); - const payload = JSON.parse(request.data); - - expect(payload.gdpr_consent).to.exist; - expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); - expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; - expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); - }); - - it('should add us privacy string to payload', function() { - let consentString = '1YA-'; - let bidderRequest = { - 'bidderCode': 'goldbach', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'uspConsent': consentString - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - const payload = JSON.parse(request.data); - - expect(payload.us_privacy).to.exist; - expect(payload.us_privacy).to.exist.and.to.equal(consentString); - }); - - it('supports sending hybrid mobile app parameters', function () { - let appRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - app: { - id: 'B1O2W3M4AN.com.prebid.webview', - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier - md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier - } - } - } - } - ); - const request = spec.buildRequests([appRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.app).to.exist; - expect(payload.app).to.deep.equal({ - appid: 'B1O2W3M4AN.com.prebid.webview' - }); - expect(payload.device.device_id).to.exist; - expect(payload.device.device_id).to.deep.equal({ - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', - md5udid: '5756ae9022b2ea1e47d84fead75220c8', - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' - }); - expect(payload.device.geo).to.exist; - expect(payload.device.geo).to.deep.equal({ - lat: 40.0964439, - lng: -75.3009142 - }); - }); - - it('should add referer info to payload', function () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequest = { - refererInfo: { - topmostLocation: 'https://example.com/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'https://example.com/page.html', - 'https://example.com/iframe1.html', - 'https://example.com/iframe2.html' - ] - } - } - const request = spec.buildRequests([bidRequest], bidderRequest)[1]; - const payload = JSON.parse(request.data); - - expect(payload.referrer_detection).to.exist; - expect(payload.referrer_detection).to.deep.equal({ - rd_ref: 'https%3A%2F%2Fexample.com%2Fpage.html', - rd_top: true, - rd_ifs: 2, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }); - }); - - it('should populate schain if available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - 'asi': 'blob.com', - 'sid': '001', - 'hp': 1 - } - ] - } - }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.schain).to.deep.equal({ - ver: '1.0', - complete: 1, - nodes: [ - { - 'asi': 'blob.com', - 'sid': '001', - 'hp': 1 - } - ] - }); + expect(payload.contextInfo.contentUrl).to.exist; + expect(payload.contextInfo.contentUrl).to.equal(bidderRequest.ortb2.site.page); }); - it('should populate coppa if set in config', function () { - let bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + it('should set appInfo on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - expect(payload.user.coppa).to.equal(true); - - config.getConfig.restore(); + expect(payload.appInfo.id).to.exist; + expect(payload.appInfo.id).to.equal(bidderRequest.ortb2.site.domain); }); - it('should set the X-Is-Test customHeader if test flag is enabled', function () { - let bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('apn_test') - .returns(true); + it('should set userInfo on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - config.getConfig.restore(); + expect(payload.userInfo).to.exist; + expect(payload.userInfo.ua).to.equal(bidderRequest.ortb2.device.ua); + expect(payload.userInfo.ip).to.equal(bidderRequest.ortb2.device.ip); + expect(payload.userInfo.ifa).to.equal(bidderRequest.ortb2.device.ifa); + expect(Array.isArray(payload.userInfo.ppid)).to.be.true; + expect(payload.userInfo.ppid.length).to.equal(2); }); - it('should always set withCredentials: true on the request.options', function () { - let bidRequest = Object.assign({}, bidRequests[0]); - const request = spec.buildRequests([bidRequest])[1]; - expect(request.options.withCredentials).to.equal(true); - }); + it('should set mapped general targetings on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - it('should set simple domain variant if purpose 1 consent is not given', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let bidderRequest = { - 'bidderCode': 'goldbach', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - apiVersion: 2, - vendorData: { - purpose: { - consents: { - 1: false - } - } - } - } - }; - bidderRequest.bids = bidRequests; + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); + expect(payload.slots[0].targetings['duration']).to.not.exist; + expect(payload.slots[1].targetings['duration']).to.exist; + expect(payload.targetings['duration']).to.not.exist; + expect(payload.targetings['lat']).to.exist; + expect(payload.targetings['long']).to.exist; + expect(payload.targetings['zip']).to.exist; + expect(payload.targetings['connection']).to.exist; }); - it('should populate eids when supported userIds are available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - userId: { - tdid: 'sample-userid', - uid2: { id: 'sample-uid2-value' }, - criteoId: 'sample-criteo-userid', - netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid' - } - }); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.eids).to.deep.include({ - source: 'adserver.org', - id: 'sample-userid', - rti_partner: 'TDID' - }); - - expect(payload.eids).to.deep.include({ - source: 'criteo.com', - id: 'sample-criteo-userid', - }); - - expect(payload.eids).to.deep.include({ - source: 'netid.de', - id: 'sample-netId-userid', - }); - - expect(payload.eids).to.deep.include({ - source: 'liveramp.com', - id: 'sample-idl-userid' - }); + it('should set mapped video duration targetings on request', function () { + let bidRequests = deepClone(validBidRequests); + let videoRequest = deepClone(validBidRequests[1]); + let bidderRequest = deepClone(validBidderRequest); - expect(payload.eids).to.deep.include({ - source: 'uidapi.com', - id: 'sample-uid2-value', - rti_partner: 'UID2' - }); - }); - - it('should populate iab_support object at the root level if omid support is detected', function () { - // with bid.params.frameworks - let bidRequest_A = Object.assign({}, bidRequests[0], { + bidRequests.push({ + ...videoRequest, params: { - frameworks: [1, 2, 5, 6], + ...videoRequest.params, video: { - frameworks: [1, 2, 5, 6] + maxduration: 10 } } - }); - let request = spec.buildRequests([bidRequest_A])[1]; - let payload = JSON.parse(request.data); - expect(payload.iab_support).to.be.an('object'); - expect(payload.iab_support).to.deep.equal({ - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }); - expect(payload.tags[0].banner_frameworks).to.be.an('array'); - expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video_frameworks).to.be.an('array'); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video.frameworks).to.not.exist; - - // without bid.params.frameworks - const bidRequest_B = Object.assign({}, bidRequests[0]); - request = spec.buildRequests([bidRequest_B])[1]; - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; - - // with video.frameworks but it is not an array - const bidRequest_C = Object.assign({}, bidRequests[0], { + }) + + bidRequests.push({ + ...videoRequest, params: { + ...videoRequest.params, video: { - frameworks: "'1', '2', '3', '6'" + maxduration: 35 } } + }) + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; + + expect(payload.slots[0].targetings['duration']).to.not.exist; + expect(payload.slots[1].targetings['duration']).to.exist; + expect(payload.slots[1].targetings['duration']).to.equal('XL'); + expect(payload.slots[3].targetings['duration']).to.equal('M'); + expect(payload.slots[4].targetings['duration']).to.equal('XXL'); + }); + + it('should set mapped connection targetings on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); + + const bidderRequestEthernet = deepClone(bidderRequest); + bidderRequestEthernet.ortb2.device.connectiontype = 1; + const payloadEthernet = spec.buildRequests(bidRequests, bidderRequestEthernet)[0].data; + + const bidderRequestWifi = deepClone(bidderRequest); + bidderRequestWifi.ortb2.device.connectiontype = 2; + const payloadWifi = spec.buildRequests(bidRequests, bidderRequestWifi)[0].data; + + const bidderRequest2G = deepClone(bidderRequest); + bidderRequest2G.ortb2.device.connectiontype = 4; + const payload2G = spec.buildRequests(bidRequests, bidderRequest2G)[0].data; + + const bidderRequest3G = deepClone(bidderRequest); + bidderRequest3G.ortb2.device.connectiontype = 5; + const payload3G = spec.buildRequests(bidRequests, bidderRequest3G)[0].data; + + const bidderRequest4G = deepClone(bidderRequest); + bidderRequest4G.ortb2.device.connectiontype = 6; + const payload4G = spec.buildRequests(bidRequests, bidderRequest4G)[0].data; + + const bidderRequestNoConnection = deepClone(bidderRequest); + bidderRequestNoConnection.ortb2.device.connectiontype = undefined; + const payloadNoConnection = spec.buildRequests(bidRequests, bidderRequestNoConnection)[0].data; + + expect(payloadEthernet.targetings['connection']).to.equal('ethernet'); + expect(payloadWifi.targetings['connection']).to.equal('wifi'); + expect(payload2G.targetings['connection']).to.equal('2G'); + expect(payload3G.targetings['connection']).to.equal('3G'); + expect(payload4G.targetings['connection']).to.equal('4G'); + expect(payloadNoConnection.targetings['connection']).to.equal(undefined); + }); + + it('should create a request with minimal information', function () { + let bidderRequest = Object.assign({}, validBidderRequest); + let bidRequests = validBidRequests.map(request => Object.assign({}, request)); + + // Removing usable bidderRequest values + bidderRequest.gdprConsent = undefined; + bidderRequest.ortb2.device.connectiontype = undefined; + bidderRequest.ortb2.device.geo = undefined; + bidderRequest.ortb2.device.ip = undefined; + bidderRequest.ortb2.device.ifa = undefined; + bidderRequest.ortb2.device.ua = undefined; + + // Removing usable bidRequests values + bidRequests = bidRequests.map(request => { + request.ortb2.device.connectiontype = undefined; + request.ortb2.device.geo = undefined; + request.ortb2.device.ip = undefined; + request.ortb2.device.ifa = undefined; + request.ortb2.device.ua = undefined; + request.userIdAsEids = undefined; + request.params = { + publisherId: 'de-publisher.ch-ios' + }; + return request; }); - request = spec.buildRequests([bidRequest_C])[1]; - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; + + // bidderRequest mappings + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.not.exist; + expect(payload.gdpr.consentString).to.not.exist; + expect(payload.userInfo).to.exist; + expect(payload.userInfo.ua).to.exist; + expect(payload.userInfo.ip).to.not.exist; + expect(payload.userInfo.ifa).to.not.exist; + expect(payload.userInfo.ppid.length).to.equal(0); + expect(payload.targetings).to.exist; + expect(payload.targetings['connection']).to.not.exist; + expect(payload.targetings['lat']).to.not.exist; + expect(payload.targetings['long']).to.not.exist; + expect(payload.targetings['zip']).to.not.exist; + + // bidRequests mapping + expect(payload.slots).to.exist; + expect(payload.slots.length).to.equal(3); + expect(payload.slots[0].targetings).to.exist + expect(payload.slots[1].targetings).to.exist }); - }) + }); describe('interpretResponse', function () { - let response = { - 'version': '3.0.0', - 'tags': [ - { - 'uuid': '3db3773286ee59', - 'tag_id': 10433394, - 'auction_id': '4534722592064951574', - 'nobid': false, - 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 10000, - 'ad_profile_id': 27079, - 'ads': [ - { - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 958, - 'creative_id': 29681110, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 0.5, - 'cpm_publisher_currency': 0.5, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'viewability': { - 'config': '' - }, - 'rtb': { - 'banner': { - 'content': '', - 'width': 300, - 'height': 250 - }, - 'trackers': [ - { - 'impression_urls': [ - 'https://lax1-ib.adnxs.com/impression' - ], - 'video_events': {} - } - ] - } - } - ] - } - ] - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - 'requestId': '3db3773286ee59', - 'cpm': 0.5, - 'creativeId': 29681110, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '', - 'mediaType': 'banner', - 'currency': 'USD', - 'ttl': 300, - 'netRevenue': true, - 'adUnitCode': 'code', - 'appnexus': { - 'buyerMemberId': 958 - } - } - ]; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); + it('should map response to valid bids (amount)', function () { + let request = deepClone(validRequest); + let bidResponse = deepClone({body: validCreativeResponse}); - it('handles nobid responses', function () { - let response = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '84ab500420319d', - 'tag_id': 5976557, - 'auction_id': '297492697822162468', - 'nobid': true - }] - }; - let bidderRequest; + const response = spec.interpretResponse(bidResponse, request); - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result.length).to.equal(0); + expect(response).to.exist; + expect(response.length).to.equal(3); + expect(response.filter(bid => bid.requestId === validBidRequests[0].bidId).length).to.equal(1) + expect(response.filter(bid => bid.requestId === validBidRequests[1].bidId).length).to.equal(1) }); - it('handles outstream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' - }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - } + it('should attach a custom video renderer ', function () { + let request = deepClone(validRequest); + let bidResponse = deepClone({body: validCreativeResponse}); + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].mediaType = 'video'; + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].vastXml = ''; + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].contextType = 'video_outstream'; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + const response = spec.interpretResponse(bidResponse, request); - it('handles instream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/vid' - } - }, - 'javascriptTrackers': '' - }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'instream' - } - } - }] - } - - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); + expect(response).to.exist; + expect(response.filter(bid => !!bid.renderer).length).to.equal(1); }); - it('handles adpod responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, - } - }, - 'viewability': { - 'config': '' - } - }] - }] - }; + it('should not attach a custom video renderer when VAST url/xml is missing', function () { + let request = deepClone(validRequest); + let bidResponse = deepClone({body: validCreativeResponse}); + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].mediaType = 'video'; + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].contextType = 'video_outstream'; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - }; + const response = spec.interpretResponse(bidResponse, request); - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); + expect(response).to.exist; + expect(response.filter(bid => !!bid.renderer).length).to.equal(0); }); + }); - it('handles native responses', function () { - let response1 = deepClone(response); - response1.tags[0].ads[0].ad_type = 'native'; - response1.tags[0].ads[0].rtb.native = { - 'title': 'Native Creative', - 'desc': 'Cool description great stuff', - 'desc2': 'Additional body text', - 'ctatext': 'Do it', - 'sponsored': 'AppNexus', - 'icon': { - 'width': 0, - 'height': 0, - 'url': 'https://cdn.adnxs.com/icon.png' - }, - 'main_img': { - 'width': 2352, - 'height': 1516, - 'url': 'https://cdn.adnxs.com/img.png' - }, - 'link': { - 'url': 'https://www.appnexus.com', - 'fallback_url': '', - 'click_trackers': ['https://nym1-ib.adnxs.com/click'] - }, - 'impression_trackers': ['https://example.com'], - 'rating': '5', - 'displayurl': 'https://AppNexus.com/?url=display_url', - 'likes': '38908320', - 'downloads': '874983', - 'price': '9.99', - 'saleprice': 'FREE', - 'phone': '1234567890', - 'address': '28 W 23rd St, New York, NY 10010', - 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '' - }; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - - let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); - expect(result[0].native.title).to.equal('Native Creative'); - expect(result[0].native.body).to.equal('Cool description great stuff'); - expect(result[0].native.cta).to.equal('Do it'); - expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + describe('sendLogs', function () { + it('should not send logs when percentage is not met', function () { + Math.random.returns(1); + spec.onTimeout([]); + expect(ajaxStub.calledOnce).to.be.false; }); + }); - it('supports configuring outstream renderers', function () { - const outstreamResponse = deepClone(response); - outstreamResponse.tags[0].ads[0].rtb.video = {}; - outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; - - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - renderer: { - options: { - adText: 'configured' - } - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - }; - - const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); - expect(result[0].renderer.config).to.deep.equal( - bidderRequest.bids[0].renderer.options - ); + describe('onTimeout', function () { + it('should send logs on timeout', function () { + spec.onTimeout([]); + expect(ajaxStub.calledOnce).to.be.true; }); + }); - it('should add deal_priority and deal_code', function() { - let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, - }; - - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - } - let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); - expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); + describe('onBidWon', function () { + it('should send logs on won', function () { + spec.onBidWon([]); + expect(ajaxStub.calledOnce).to.be.true; }); + }); - it('should add advertiser id', function() { - let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; - - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + describe('onSetTargeting', function () { + it('should send logs on targeting', function () { + spec.onSetTargeting([]); + expect(ajaxStub.calledOnce).to.be.true; }); + }); - it('should add advertiserDomains', function() { - let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + describe('onBidderError', function () { + it('should send logs on bidder error', function () { + spec.onBidderError([]); + expect(ajaxStub.calledOnce).to.be.true; + }); + }); - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); - expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + describe('onAdRenderSucceeded', function () { + it('should send logs on render succeeded', function () { + spec.onAdRenderSucceeded([]); + expect(ajaxStub.calledOnce).to.be.true; }); }); }); From 9d733b5c6c27e330052064063a5514238499dcb4 Mon Sep 17 00:00:00 2001 From: alukonin1 <152840501+alukonin1@users.noreply.github.com> Date: Thu, 26 Dec 2024 18:51:03 +0200 Subject: [PATCH 0793/1097] Yield one bid adapter: Conditionally stop sending push_sync requests (#12591) * stop sending push_sync requests in YieldOne adapter in case of Safari browser OR iOS device * stop sending push_sync requests in YieldOne adapter in case of GDPR applies * fix linter * adjust unit tests for Safari and iOS cases * adjust unit tests for GDPR applies cases --------- Co-authored-by: alukonin --- modules/yieldoneBidAdapter.js | 24 +++++++++++++++-- test/spec/modules/yieldoneBidAdapter_spec.js | 27 +++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 8ad7e69aa6e..5852663dc99 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -2,6 +2,8 @@ import {deepAccess, isEmpty, isStr, logWarn, parseSizesInput} from '../src/utils import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getBrowser, getOS} from '../libraries/userAgentUtils/index.js'; +import {browserTypes, osTypes} from '../libraries/userAgentUtils/userAgentTypes.enums.js'; /** * @typedef {import('../src/adapters/bidderFactory').Bid} Bid @@ -219,10 +221,12 @@ export const spec = { /** * Register the user sync pixels which should be dropped after the auction. * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @param {Object} gdprConsent Is the GDPR Consent object wrapping gdprApplies {boolean} and consentString {string} attributes. * @returns {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + if (syncOptions.iframeEnabled && !skipSync(gdprConsent)) { return [{ type: 'iframe', url: USER_SYNC_URL @@ -393,4 +397,20 @@ function cmerRender(bid) { }); } +/** + * Stop sending push_sync requests in case it's either Safari browser OR iOS device OR GDPR applies. + * Data extracted from navigator's userAgent + * @param {Object} gdprConsent Is the GDPR Consent object wrapping gdprApplies {boolean} and consentString {string} attributes. + */ +function skipSync(gdprConsent) { + return (getBrowser() === browserTypes.SAFARI || getOS() === osTypes.IOS) || gdprApplies(gdprConsent); +} + +/** + * Check if GDPR applies. + */ +function gdprApplies(gdprConsent) { + return gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies; +} + registerBidder(spec); diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index 4664f77216d..983f67bcdd6 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -1,6 +1,8 @@ import { expect } from 'chai'; import { spec } from 'modules/yieldoneBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { getBrowser, getOS } from '../../../libraries/userAgentUtils/index.js'; +import { browserTypes, osTypes } from '../../../libraries/userAgentUtils/userAgentTypes.enums.js'; const ENDPOINT = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; @@ -8,7 +10,7 @@ const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/d const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; -describe('yieldoneBidAdapter', function() { +describe('yieldoneBidAdapter', function () { const adapter = newBidder(spec); describe('isBidRequestValid', function () { @@ -638,12 +640,25 @@ describe('yieldoneBidAdapter', function() { expect(spec.getUserSyncs({})).to.be.undefined; }); - it('should return a sync url if iframe syncs are enabled', function () { - expect(spec.getUserSyncs({ + it('should return a sync url if iframe syncs are enabled and UserAgent is not Safari or iOS', function () { + const result = spec.getUserSyncs({ 'iframeEnabled': true - })).to.deep.equal([{ - type: 'iframe', url: USER_SYNC_URL - }]); + }); + + if (getBrowser() === browserTypes.SAFARI || getOS() === osTypes.IOS) { + expect(result).to.be.undefined; + } else { + expect(result).to.deep.equal([{ + type: 'iframe', url: USER_SYNC_URL + }]); + } + }); + + it('should skip sync request in case GDPR applies', function () { + expect(spec.getUserSyncs({'iframeEnabled': true}, [], { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + })).to.be.undefined; }); }); }); From 7651cb378a5f066c568117992c6f0d2ff47b90a0 Mon Sep 17 00:00:00 2001 From: Oleksandr Solodovnikov Date: Thu, 26 Dec 2024 18:52:19 +0200 Subject: [PATCH 0794/1097] Aniview: send `format` and `w`/`h` with banner request + refactoring and small fixes (#12592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use credentials in requests in Aniview Bid Adapter * - isBannerType / isVideoType as functions; - Now for the banner request, we’re sending format and w / h as a fallback; - Getting w, h, crid, adid, adomain from the serving (with fallbacks); - Changed the bidResponse name to prebidBid; - Updated getSize function; - Removed unnecessary conditions; - Flat map for seatbids array; - Using the proper bid in interpretResponse. --- modules/aniviewBidAdapter.js | 116 ++++++++++---------- test/spec/modules/aniviewBidAdapter_spec.js | 5 +- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 82c77bfafb5..70550e2daf9 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -4,11 +4,9 @@ import { Renderer } from '../src/Renderer.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { deepAccess, - deepSetValue, mergeDeep, isFn, isStr, - isEmptyStr, isPlainObject, getUniqueIdentifierStr } from '../src/utils.js'; @@ -31,8 +29,7 @@ const converter = ortbConverter({ imp(buildImp, bidRequest, context) { const { mediaType } = context; const imp = buildImp(bidRequest, context); - const isBanner = mediaType === BANNER; - const { width, height } = getSize(context, bidRequest); + const { width, height } = getSize(mediaType, bidRequest); const floor = getFloor(bidRequest, { width, height }, mediaType); imp.tagid = deepAccess(bidRequest, 'params.AV_CHANNELID'); @@ -42,9 +39,8 @@ const converter = ortbConverter({ imp.bidfloorcur = DEFAULT_CURRENCY; } - if (isBanner) { - // TODO: remove once serving will be fixed - deepSetValue(imp, 'banner', { w: width, h: height }); + if (isBannerType(mediaType)) { + mergeDeep(imp.banner, { w: width, h: height }); } return imp; @@ -67,41 +63,38 @@ const converter = ortbConverter({ bidResponse(buildBidResponse, bid, context) { const { bidRequest, mediaType } = context; - const { width, height } = getSize(context, bidRequest); - const isVideoBid = mediaType === VIDEO; - const isBannerBid = mediaType === BANNER; + const { width, height } = getSize(mediaType, bidRequest); - if (isVideoBid) { - context.vastXml = bid.adm; + if (!bid.w || !bid.h) { + bid.w = width; + bid.h = height; } - const bidResponse = buildBidResponse(bid, context); + bid.crid ??= getUniqueIdentifierStr(); + bid.adid ??= getUniqueIdentifierStr(); + bid.bidid ??= getUniqueIdentifierStr(); - if (isEmptyStr(bidRequest?.bidId) || !bid.adm || bidResponse.cpm <= 0) { - return bidResponse; + const prebidBid = buildBidResponse(bid, context); + + if (!bid.adm || prebidBid.cpm <= 0) { + return prebidBid; } - mergeDeep(bidResponse, { - width, - height, - creativeId: bid.crid || 'creativeId', - meta: { advertiserDomains: [] }, - adId: getUniqueIdentifierStr(), - }); + mergeDeep(prebidBid, { meta: { advertiserDomains: bid.adomain || [] } }); - if (isVideoBid) { + if (isVideoType(mediaType)) { if (bidRequest.mediaTypes.video.context === 'outstream') { - bidResponse.renderer = createRenderer(bidRequest); + prebidBid.renderer = createRenderer(bidRequest); } - } else if (isBannerBid) { + } else if (isBannerType(mediaType)) { if (bid.adm?.trim().startsWith(' seatbid?.bid || []) || []; - if (!bids || !data || !bidResponse) { + if (!bidderRequest.data || bids.length <= 0) { return []; } - const response = converter.fromORTB({ response: body, request: data }); - - return response.bids.map(bid => { + return converter.fromORTB({ response: body, request: bidderRequest.data }).bids.map((prebidBid, index) => { + const bid = bids[index]; const replacements = { - auctionPrice: bid.cpm, - auctionId: bid.requestId, - auctionBidId: bidResponse.impid, - auctionImpId: bidResponse.impid, - auctionSeatId: bid.seatBidId, - auctionAdId: bid.adId, + auctionPrice: prebidBid.cpm, + auctionId: prebidBid.requestId, + auctionBidId: bid.bidid, + auctionImpId: bid.impid, + auctionSeatId: prebidBid.seatBidId, + auctionAdId: bid.adid, }; - const bidAdmWithReplacedMacros = replaceMacros(bidResponse.adm, replacements); + const bidAdmWithReplacedMacros = replaceMacros(bid.adm, replacements); - if (bid.mediaType === VIDEO) { - bid.vastXml = bidAdmWithReplacedMacros; + if (isVideoType(prebidBid.mediaType)) { + prebidBid.vastXml = bidAdmWithReplacedMacros; - if (bidResponse?.nurl) { - bid.vastUrl = replaceMacros(bidResponse.nurl, replacements); + if (bid?.nurl) { + prebidBid.vastUrl = replaceMacros(bid.nurl, replacements); } } else { - bid.ad = bidAdmWithReplacedMacros; + prebidBid.ad = bidAdmWithReplacedMacros; } - return bid; + return prebidBid; }); }, @@ -193,6 +184,14 @@ export const spec = { }, }; +function isVideoType(mediaType) { + return mediaType === VIDEO; +} + +function isBannerType(mediaType) { + return mediaType === BANNER; +} + function getValidSyncs(syncs, options) { return syncs .filter(sync => isSyncValid(sync, options)) @@ -210,14 +209,19 @@ function processSync(sync) { return { url: sync.url, type: sync.t === 1 ? 'image' : 'iframe' }; } -function getSize(context, bid) { - const isVideoBid = context.mediaType === VIDEO; +function getSize(mediaType, bidRequest) { + const { mediaTypes, sizes } = bidRequest; + const videoSizes = mediaTypes?.video?.playerSize; + const bannerSizes = mediaTypes?.banner?.sizes; + let size = [640, 480]; - if (isVideoBid && bid.mediaTypes?.video?.playerSize?.length) { - size = bid.mediaTypes.video.playerSize[0]; - } else if (bid.sizes?.length) { - size = bid.sizes[0]; + if (isVideoType(mediaType) && videoSizes?.length > 0) { + size = videoSizes[0]; + } else if (isBannerType(mediaType) && bannerSizes?.length > 0) { + size = bannerSizes[0]; + } else if (sizes?.length > 0) { + size = sizes[0]; } return { @@ -227,13 +231,13 @@ function getSize(context, bid) { } // https://docs.prebid.org/dev-docs/modules/floors.html#example-getfloor-scenarios -function getFloor(bid, size, mediaType) { - if (!isFn(bid?.getFloor)) { +function getFloor(bidRequest, size, mediaType) { + if (!isFn(bidRequest?.getFloor)) { return null; } try { - const bidFloor = bid.getFloor({ + const bidFloor = bidRequest.getFloor({ currency: DEFAULT_CURRENCY, mediaType, // or '*' for all media types size: [size.width, size.height], // or '*' for all sizes diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index 726bccaa027..a4ccdce1117 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -265,9 +265,8 @@ describe('Aniview Bid Adapter', function () { const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); const bid = bids[0]; - expect(bid.width).to.not.exist; - expect(bid.height).to.not.exist; - expect(bid.creativeId).to.not.exist; + expect(bid.renderer).to.not.exist; + expect(bid.ad).to.not.exist; }); it('should return empty bids array if no bids in response', function () { From 1ae68140825ed623660a0732dab93e9d0ee0c551 Mon Sep 17 00:00:00 2001 From: Victor <103455651+victorlassomarketing@users.noreply.github.com> Date: Thu, 26 Dec 2024 08:53:58 -0800 Subject: [PATCH 0795/1097] Update with testing params (#12600) --- modules/lassoBidAdapter.js | 26 +++++- test/spec/modules/lassoBidAdapter_spec.js | 102 ++++++++++++++++++++++ 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index dc36cb4af23..ccee9616044 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -32,6 +32,22 @@ export const spec = { sizes = bidRequest.mediaTypes[BANNER].sizes; } + const { params } = bidRequest; + + let npi = params.npi || ''; + let dgid = params.dgid || ''; + let test = false; + + if (params.testNPI) { + npi = params.testNPI; + test = true; + } + + if (params.testDGID) { + dgid = params.testDGID; + test = true; + } + const payload = { auctionStart: bidderRequest.auctionStart, url: encodeURIComponent(window.location.href), @@ -44,14 +60,16 @@ export const spec = { sizes, aimXR, uid: '$UID', - npi: bidRequest.params.npi || '', - npi_hash: bidRequest.params.npiHash || '', + npi, + dgid, + npi_hash: params.npiHash || '', params: JSON.stringify(bidRequest.params), crumbs: JSON.stringify(bidRequest.crumbs), prebidVersion: '$prebid.version$', version: 4, coppa: config.getConfig('coppa') == true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined + ccpa: bidderRequest.uspConsent || undefined, + test } if ( @@ -130,7 +148,7 @@ function getBidRequestUrl(aimXR, params) { if (params && params.dtc) { path = '/dtc-request'; } - if (aimXR || params.npi || params.npiHash) { + if (aimXR || params.npi || params.dgid || params.npiHash || params.testNPI || params.testDGID) { return ENDPOINT_URL + path; } return GET_IUD_URL + ENDPOINT_URL + path; diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index f33869e9366..94ec86aba69 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -100,11 +100,45 @@ describe('lassoBidAdapter', function () { }); it('should send request to get uid and trc via get request', () => { + expect(bidRequest.data.test).to.equal(false) expect(bidRequest.method).to.equal('GET'); expect(bidRequest.url).to.equal(GET_IUD_URL + ENDPOINT_URL + '/request'); }); }); + describe('buildRequests with dgid', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + dgid: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with dgid', () => { + expect(bidRequest.data.test).to.equal(false) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + describe('buildRequests with npi', function () { let validBidRequests, bidRequest; before(() => { @@ -132,6 +166,73 @@ describe('lassoBidAdapter', function () { }); it('should send request to trc via get request with npi', () => { + expect(bidRequest.data.test).to.equal(false) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with test npi', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + testNPI: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with npi and test param', () => { + expect(bidRequest.data.test).to.equal(true) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with test dgid', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + testDGID: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with dgid and test param', () => { + expect(bidRequest.data.test).to.equal(true) expect(bidRequest.method).to.equal('GET'); expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); }); @@ -164,6 +265,7 @@ describe('lassoBidAdapter', function () { }); it('should send request to trc via get request with npi', () => { + expect(bidRequest.data.test).to.equal(false) expect(bidRequest.method).to.equal('GET'); expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); }); From 12e70fd8ad989d60a99aa517272bea2b0d043466 Mon Sep 17 00:00:00 2001 From: Mikhail Malkov Date: Thu, 26 Dec 2024 19:55:36 +0300 Subject: [PATCH 0796/1097] nextMillenniumBidAdapter: Added support `imp.video.pos` and `imp.banner.pos` (#12606) * PB-2866 - added position to imp * PB-2866 - added position to imp * PB-2866 - added position to imp - 2 --- modules/nextMillenniumBidAdapter.js | 12 +++++- .../modules/nextMillenniumBidAdapter_spec.js | 40 ++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index a4169947e85..6e1443b85e5 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -21,7 +21,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '4.2.1'; +const NM_VERSION = '4.3.0'; const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; @@ -308,13 +308,15 @@ export function getImpBanner(imp, banner) { if (banner.bidfloorcur) imp.bidfloorcur = banner.bidfloorcur; if (banner.bidfloor) imp.bidfloor = banner.bidfloor; - const format = (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }) + const format = (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }); const {w, h} = (format[0] || {}) imp.banner = { w, h, format, }; + + setImpPos(imp.banner, banner?.pos); }; export function getImpVideo(imp, video) { @@ -336,6 +338,12 @@ export function getImpVideo(imp, video) { imp.video.w = video.data.w; imp.video.h = video.data.h; }; + + setImpPos(imp.video, video?.pos); +}; + +export function setImpPos(obj, pos) { + if (typeof pos === 'number' && pos >= 0 && pos <= 7) obj.pos = pos; }; export function setConsentStrings(postBody = {}, bidderRequest) { diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index bfcf7e80420..c7fabd562c7 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { getImp, + setImpPos, getSourceObj, replaceUsersyncMacros, setConsentStrings, @@ -28,6 +29,7 @@ describe('nextMillenniumBidAdapterTests', () => { data: {sizes: [[300, 250], [320, 250]]}, bidfloorcur: 'EUR', bidfloor: 1.11, + pos: 3, }, }, }, @@ -37,7 +39,12 @@ describe('nextMillenniumBidAdapterTests', () => { bidfloorcur: 'EUR', bidfloor: 1.11, ext: {prebid: {storedrequest: {id: '123'}}}, - banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + banner: { + pos: 3, + w: 300, + h: 250, + format: [{w: 300, h: 250}, {w: 320, h: 250}], + }, }, }, @@ -56,6 +63,7 @@ describe('nextMillenniumBidAdapterTests', () => { video: { data: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}, bidfloorcur: 'USD', + pos: 0, }, }, }, @@ -71,6 +79,7 @@ describe('nextMillenniumBidAdapterTests', () => { plcmt: 1, w: 400, h: 300, + pos: 0, }, }, }, @@ -177,6 +186,35 @@ describe('nextMillenniumBidAdapterTests', () => { } }); + describe('function setImpPos', () => { + const tests = [ + { + title: 'position is - 1', + pos: 0, + expected: {pos: 0}, + }, + + { + title: 'position is - 2', + pos: 7, + expected: {pos: 7}, + }, + + { + title: 'position is empty', + expected: {}, + }, + ]; + + for (const {title, pos, expected} of tests) { + it(title, () => { + const obj = {}; + setImpPos(obj, pos); + expect(obj).to.deep.equal(expected); + }); + }; + }); + describe('function getSourceObj', () => { const dataTests = [ { From 0419f1a7305e90a7c0eb5c771d223fbf212a17ae Mon Sep 17 00:00:00 2001 From: rs-guian <119166974+rs-guian@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:59:30 +0100 Subject: [PATCH 0797/1097] Retailspot bidAdapter : Endpoint update (#12602) * add retailspot GVL_ID, update retailspotads domain name * update port for local testing * update unit tests * update test parameters with new placement id * simplify getSize workflow * fix lint error on type definition --------- Co-authored-by: Guillaume Andouard --- modules/retailspotBidAdapter.js | 42 ++++++++++--------- ...BidAdapter .md => retailspotBidAdapter.md} | 2 +- .../spec/modules/retailspotBidAdapter_spec.js | 8 ++-- 3 files changed, 28 insertions(+), 24 deletions(-) rename modules/{retailspotBidAdapter .md => retailspotBidAdapter.md} (92%) diff --git a/modules/retailspotBidAdapter.js b/modules/retailspotBidAdapter.js index 557dd617274..da8e46bec81 100644 --- a/modules/retailspotBidAdapter.js +++ b/modules/retailspotBidAdapter.js @@ -5,17 +5,21 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ const BIDDER_CODE = 'retailspot'; -const DEFAULT_SUBDOMAIN = 'ssp'; -const PREPROD_SUBDOMAIN = 'ssp-preprod'; -const HOST = 'retail-spot.io'; -const ENDPOINT = '/prebid'; -const DEV_URL = 'http://localhost:8090/prebid'; +const GVL_ID = 1319; + +const DEFAULT_SUBDOMAIN = 'hbapi'; +const PREPROD_SUBDOMAIN = 'hbapi-preprod'; +const HOST = 'retailspotads.com'; +const ENDPOINT = '/'; +const DEV_URL = 'http://localhost:3030/'; export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [BANNER, VIDEO], aliases: ['rs'], // short code /** @@ -25,7 +29,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - const sizes = getSize(getSizeArray(bid)); + const sizes = getSize(bid); const sizeValid = sizes.width > 0 && sizes.height > 0; return deepAccess(bid, 'params.placement') && sizeValid; @@ -33,7 +37,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {BidRequests} - bidRequests.bids[] is an array of AdUnits and bids + * @param {BidRequest} bidRequests is an array of AdUnits and bids + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { @@ -96,40 +101,39 @@ export const spec = { } } -function getSizeArray(bid) { +/* Get parsed size from request size */ +function getSize(bid) { let inputSize = bid.sizes || []; - if (bid.mediaTypes && bid.mediaTypes.banner) { + if (bid.mediaTypes?.banner) { inputSize = bid.mediaTypes.banner.sizes || []; } - // handle size in bid.params in formats: [w, h] and [[w,h]]. - if (bid.params && Array.isArray(bid.params.size)) { + // Size can be [w, h] or array of sizes : [[w,h]]. + if (Array.isArray(bid.params?.size)) { inputSize = bid.params.size; if (!Array.isArray(inputSize[0])) { inputSize = [inputSize] } } - return parseSizesInput(inputSize); -} - -/* Get parsed size from request size */ -function getSize(sizesArray) { + const sizesArray = parseSizesInput(inputSize); const parsed = {}; - // the main requested size is the first one + + // Use the first size as the main requested one const size = sizesArray[0]; + // size is ready if (typeof size !== 'string') { return parsed; } - const parsedSize = size.toUpperCase().split('X'); + // size is given as string "wwwxhhh" or "www*hhh" + const parsedSize = size.includes('*') ? size.split('*') : size.toUpperCase().split('X'); const width = parseInt(parsedSize[0], 10); if (width) { parsed.width = width; } - const height = parseInt(parsedSize[1], 10); if (height) { parsed.height = height; diff --git a/modules/retailspotBidAdapter .md b/modules/retailspotBidAdapter.md similarity index 92% rename from modules/retailspotBidAdapter .md rename to modules/retailspotBidAdapter.md index a9b4cb4bec3..1f56f66fbc8 100644 --- a/modules/retailspotBidAdapter .md +++ b/modules/retailspotBidAdapter.md @@ -25,7 +25,7 @@ Banner and Video ad formats are supported. bids: [{ bidder: "retailspot", params: { - placement: "test-12345" + placement: "eq-609785-1856964-125234" } }] }; diff --git a/test/spec/modules/retailspotBidAdapter_spec.js b/test/spec/modules/retailspotBidAdapter_spec.js index f1fb5ae3fd3..c5cb001c1ba 100644 --- a/test/spec/modules/retailspotBidAdapter_spec.js +++ b/test/spec/modules/retailspotBidAdapter_spec.js @@ -246,7 +246,7 @@ describe('RetailSpot Adapter', function () { ]; const adapter = newBidder(spec); - const DEV_URL = 'http://localhost:8090/'; + const DEV_URL = 'http://localhost:3030/'; describe('inherited functions', function () { it('exists and is a function', function () { @@ -333,7 +333,7 @@ describe('RetailSpot Adapter', function () { const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); - expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.url).to.contain('https://hbapi.retailspotads.com/'); expect(request.method).to.equal('POST'); expect(payload).to.deep.equal(bidderRequest); @@ -344,7 +344,7 @@ describe('RetailSpot Adapter', function () { const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); - expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.url).to.contain('https://hbapi.retailspotads.com/'); expect(request.method).to.equal('POST'); expect(payload).to.deep.equal(bidderRequest); @@ -355,7 +355,7 @@ describe('RetailSpot Adapter', function () { const request = spec.buildRequests(bidRequestMultiPlacements, bidderRequest); const payload = JSON.parse(request.data); - expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.url).to.contain('https://hbapi.retailspotads.com/'); expect(request.method).to.equal('POST'); expect(payload).to.deep.equal(bidderRequest); From 3bfc45232f7cb6da215fccab3a53ec3c0ef3cc82 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 27 Dec 2024 13:47:46 -0500 Subject: [PATCH 0798/1097] Revert "Vidazoo Bid Adapter: Add ORTB2 device data to request payload (#12074)" (#12607) This reverts commit 6e42abee5b52ec65520fdf585a93420754f0807a. --- libraries/vidazooUtils/bidderUtils.js | 2 - test/spec/modules/illuminBidAdapter_spec.js | 51 +++++++----------- test/spec/modules/kueezRtbBidAdapter_spec.js | 51 +++++++----------- test/spec/modules/shinezRtbBidAdapter_spec.js | 51 +++++++----------- test/spec/modules/tagorasBidAdapter_spec.js | 51 +++++++----------- .../modules/twistDigitalBidAdapter_spec.js | 52 +++++++------------ test/spec/modules/vidazooBidAdapter_spec.js | 52 +++++++------------ 7 files changed, 108 insertions(+), 202 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 9de95248c74..df947142a4c 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -247,7 +247,6 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); const contentLang = deepAccess(bidderRequest, 'ortb2.site.content.language') || document.documentElement.lang; const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa', 0); - const device = deepAccess(bidderRequest, 'ortb2.device', {}); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ @@ -291,7 +290,6 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder bidderRequestsCount: bidderRequestsCount, bidderWinsCount: bidderWinsCount, bidderTimeout: bidderTimeout, - device, ...uniqueRequestData }; diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index b4578a8d61f..3cd79c7468d 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -94,36 +94,6 @@ const VIDEO_BID = { } } -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -147,7 +117,24 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': ORTB2_DEVICE, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } } }; @@ -328,7 +315,6 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -400,7 +386,6 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 0bb65d0aef0..d3323a205b3 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -93,36 +93,6 @@ const VIDEO_BID = { } } -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -146,7 +116,24 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': ORTB2_DEVICE, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } } }; @@ -334,7 +321,6 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -408,7 +394,6 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 892d88b3b7b..ebd2e987491 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -94,36 +94,6 @@ const VIDEO_BID = { } } -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -149,7 +119,24 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': ORTB2_DEVICE, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } } }; @@ -330,7 +317,6 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -402,7 +388,6 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 8d8c8cb62b7..6ebe2896ffc 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -94,36 +94,6 @@ const VIDEO_BID = { } } -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -147,7 +117,24 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': ORTB2_DEVICE, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } } }; @@ -327,7 +314,6 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -398,7 +384,6 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 4ddac3261f1..c15a6d1d909 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -94,36 +94,6 @@ const VIDEO_BID = { } } -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -159,7 +129,24 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - device: ORTB2_DEVICE, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + }, user: { data: [ { @@ -354,7 +341,6 @@ describe('TwistDigitalBidAdapter', function () { }, contentLang: 'en', coppa: 0, - device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -436,7 +422,6 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -525,7 +510,6 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 01d7aa20e53..14a49b21cdc 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -98,36 +98,6 @@ const VIDEO_BID = { } } -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -163,7 +133,24 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - device: ORTB2_DEVICE, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + }, user: { data: [ { @@ -359,7 +346,6 @@ describe('VidazooBidAdapter', function () { }, contentLang: 'en', coppa: 0, - device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -444,7 +430,6 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -538,7 +523,6 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, - device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, From 08a8481a63f24fbfcabfb8d590c81036b18804c2 Mon Sep 17 00:00:00 2001 From: adxpremium <55161519+adxpremium@users.noreply.github.com> Date: Fri, 27 Dec 2024 19:49:47 +0100 Subject: [PATCH 0799/1097] LuponMedia BidAdapter: Add adomain to bidResponse (#12604) * Fix (9.0 fixes): Remove discontinued fpd.context, remove unused onBidWon event * Update luponmediaBidAdapter.js | adomain Add adomain to bidResponse * Update luponmediaBidAdapter.js Add meta field * Update luponmediaBidAdapter.js Fix eslint trailing spaces * Update luponmediaBidAdapter_spec.js add new fields to the test spec --------- Co-authored-by: Lupon Media --- modules/luponmediaBidAdapter.js | 6 +++++- test/spec/modules/luponmediaBidAdapter_spec.js | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 63435437967..3dab4524db1 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -172,7 +172,11 @@ export const spec = { netRevenue: false, ttl: 300, referrer: parsedReferrer, - ad: bid.adm + ad: bid.adm, + adomain: bid.adomain || [], + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + } }; bidResponses.push(newBid); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 664c888b45e..3d9be5a40bf 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -188,6 +188,9 @@ describe('luponmediaBidAdapter', function () { 'price': 0.43, 'adm': ' ', 'adid': '56380110', + 'adomain': [ + 'mi.betrivers.com' + ], 'cid': '44724710', 'crid': '443801010', 'w': 300, @@ -232,7 +235,15 @@ describe('luponmediaBidAdapter', function () { 'netRevenue': false, 'ttl': 300, 'referrer': '', - 'ad': ' ' + 'ad': ' ', + 'adomain': [ + 'mi.betrivers.com' + ], + 'meta': { + 'advertiserDomains': [ + 'mi.betrivers.com' + ] + } } ]; From db7bb1177285430e43e6f8835433639771738b2e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 27 Dec 2024 20:17:54 +0000 Subject: [PATCH 0800/1097] Prebid 9.25.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0dd58cc9dd..c0db5948c34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.25.0-pre", + "version": "9.25.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.25.0-pre", + "version": "9.25.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index c45bdbe894d..fc6d6ce3469 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.25.0-pre", + "version": "9.25.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From b6cd2e653860dbf0882e73491ff8696c1e206dc1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 27 Dec 2024 20:17:55 +0000 Subject: [PATCH 0801/1097] Increment version to 9.26.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0db5948c34..78aff02eb1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.25.0", + "version": "9.26.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.25.0", + "version": "9.26.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index fc6d6ce3469..617a82c8e6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.25.0", + "version": "9.26.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From a88e8010773a4d8614a6828472dfbab074860336 Mon Sep 17 00:00:00 2001 From: Roger <104763658+rogerDyl@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:49:06 +0100 Subject: [PATCH 0802/1097] Akcelo bid adapter : initial release (#12583) * Add Akcelo bidder * Use real identifiers in bid example --- modules/akceloBidAdapter.js | 148 ++++++++++++ modules/akceloBidAdapter.md | 57 +++++ test/spec/modules/akceloBidAdapter_spec.js | 263 +++++++++++++++++++++ 3 files changed, 468 insertions(+) create mode 100644 modules/akceloBidAdapter.js create mode 100644 modules/akceloBidAdapter.md create mode 100644 test/spec/modules/akceloBidAdapter_spec.js diff --git a/modules/akceloBidAdapter.js b/modules/akceloBidAdapter.js new file mode 100644 index 00000000000..bfada1cc2eb --- /dev/null +++ b/modules/akceloBidAdapter.js @@ -0,0 +1,148 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { deepSetValue, getParameterByName, logError } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { ORTB_MTYPES } from '../libraries/ortbConverter/processors/mediaType.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + +const BIDDER_CODE = 'akcelo'; +const COOKIE_SYNC_ENDPOINT = 'akcelo'; + +const AUCTION_URL = 'https://s2s.sportslocalmedia.com/openrtb2/auction'; +const IFRAME_SYNC_URL = 'https://ads.sportslocalmedia.com/load-cookie.html'; + +const DEFAULT_TTL = 300; + +const akceloDemoIsOn = () => getParameterByName('akcelo_demo') === 'true'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_TTL, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + + if (bidRequest.params.siteId) { + deepSetValue(imp, 'ext.akcelo.siteId', bidRequest.params.siteId); + } else { + logError('Missing parameter : siteId'); + } + + if (bidRequest.params.adUnitId) { + deepSetValue(imp, 'ext.akcelo.adUnitId', bidRequest.params.adUnitId); + } else { + logError('Missing parameter : adUnitId'); + } + + if (akceloDemoIsOn()) { + deepSetValue(imp, 'ext.akcelo.test', 1); + } + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + deepSetValue(request, 'test', akceloDemoIsOn() ? 1 : 0); + + const siteId = bidderRequest.bids.map((bid) => bid.params.siteId).find(Boolean); + deepSetValue(request, 'site.publisher.ext.prebid.parentAccount', siteId); + + return request; + }, + bidResponse(buildBidResponse, bid, context) { + // In ORTB 2.5, bid responses do not specify their mediatype, which is something Prebid.js requires + context.mediaType = bid.mtype && ORTB_MTYPES[bid.mtype] + ? ORTB_MTYPES[bid.mtype] + : bid.ext?.prebid?.type; + + return buildBidResponse(bid, context); + }, +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + /** + * Determines whether the given bid request is valid. + * + * @param {Bid} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid(bid) { + if (!bid?.params?.adUnitId) { + logError("Missing required parameter 'adUnitId'"); + return false; + } + if (!bid?.params?.siteId) { + logError("Missing required parameter 'siteId'"); + return false; + } + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @param {BidderRequest} bidderRequest bidder request object. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ bidRequests, bidderRequest }); + + return [{ method: 'POST', url: AUCTION_URL, data }]; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest The bid request sent to the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse(serverResponse, bidRequest) { + const { bids } = converter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }); + + return bids; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @param {*} gdprConsent + * @param {*} uspConsent + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled) { + let syncParams = `?endpoint=${COOKIE_SYNC_ENDPOINT}`; + if (gdprConsent) { + syncParams += `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + syncParams += `&gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`; + } + if (uspConsent) { + syncParams += `&us_privacy=${encodeURIComponent(uspConsent)}`; + } + + return [{ type: 'iframe', url: IFRAME_SYNC_URL + syncParams }]; + } + return []; + }, +}; + +registerBidder(spec); diff --git a/modules/akceloBidAdapter.md b/modules/akceloBidAdapter.md new file mode 100644 index 00000000000..02881ede2e1 --- /dev/null +++ b/modules/akceloBidAdapter.md @@ -0,0 +1,57 @@ +# Overview + +**Module Name**: Akcelo Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: tech@akcelo.io + +# Description + +A module that connects to the Akcelo network for bids + +## AdUnits configuration example + +```javascript +const adUnits = [ + { + code: 'div-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + video: { + context: "outstream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + }, + }, + bids: [ + { + bidder: 'akcelo', + params: { + siteId: 763, // required + adUnitId: 7965, // required + test: 1, // optional, use 0 to disable test creatives + }, + }, + ], + }, +]; + +pbjs.que.push(function () { + pbjs.addAdUnits(adUnits); +}); +``` diff --git a/test/spec/modules/akceloBidAdapter_spec.js b/test/spec/modules/akceloBidAdapter_spec.js new file mode 100644 index 00000000000..5c519ea9834 --- /dev/null +++ b/test/spec/modules/akceloBidAdapter_spec.js @@ -0,0 +1,263 @@ +import { converter, spec } from 'modules/akceloBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import { deepClone } from '../../../src/utils.js'; +import sinon from 'sinon'; + +describe('Akcelo bid adapter tests', () => { + let sandBox; + + beforeEach(() => { + sandBox = sinon.createSandbox(); + sandBox.stub(utils, 'logError'); + }); + + afterEach(() => sandBox.restore()); + + const DEFAULT_BANNER_BID_REQUESTS = [ + { + adUnitCode: 'div-banner-id', + bidId: 'bid-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bidder: 'akcelo', + params: { + siteId: 123, + adUnitId: 456, + }, + requestId: 'request-123', + } + ]; + + const DEFAULT_BANNER_BIDDER_REQUEST = { + bidderCode: 'akcelo', + bids: DEFAULT_BANNER_BID_REQUESTS, + }; + + const SAMPLE_RESPONSE = { + body: { + id: '12h712u7-k22g-8124-ab7a-h268s22dy271', + seatbid: [ + { + bid: [ + { + id: '1bh7jku7-ko2g-8654-ab72-h268abcde271', + impid: 'bid-123', + price: 0.6565, + adm: '

AD

', + adomain: ['abc.com'], + cid: '1242512', + crid: '535231', + w: 300, + h: 600, + mtype: 1, + ext: { + prebid: { + type: 'banner', + } + } + }, + ], + seat: '4212', + }, + ], + cur: 'EUR', + }, + }; + + describe('isBidRequestValid', () => { + it('should return true if params.siteId and params.adUnitId are set', () => { + const bidRequest = { + params: { + siteId: 123, + adUnitId: 456, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false if params.siteId is missing', () => { + const bidRequest = { + params: { + adUnitId: 456, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false if params.adUnitId is missing', () => { + const bidRequest = { + params: { + siteId: 123, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('should build correct requests using ORTB converter', () => { + const request = spec.buildRequests( + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + ); + const dataFromConverter = converter.toORTB({ + bidderRequest: DEFAULT_BANNER_BIDDER_REQUEST, + bidRequests: DEFAULT_BANNER_BID_REQUESTS, + }); + expect(request[0]).to.deep.equal({ + data: { ...dataFromConverter, id: request[0].data.id }, + method: 'POST', + url: 'https://s2s.sportslocalmedia.com/openrtb2/auction', + }); + }); + + it('should add site.publisher.ext.prebid.parentAccount to request object when siteId is defined', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.site.publisher.ext.prebid.parentAccount).to.equal(123); + }); + + it('should not add site.publisher.ext.prebid.parentAccount to request object when siteId is not defined', () => { + const bidRequests = [ + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { adUnitId: 456 } }, + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.site.publisher.ext.prebid.parentAccount).to.be.undefined; + }); + + it('should add ext.akcelo to imp object when siteId and adUnitId are defined', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo).to.deep.equal({ + siteId: 123, + adUnitId: 456, + }); + }); + + it('should not add ext.akcelo.siteId to imp object when siteId is not defined', () => { + const bidRequests = [ + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { adUnitId: 456 } }, + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.imp[0].ext.akcelo.siteId).to.be.undefined; + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.equal('Missing parameter : siteId') + }); + + it('should not add ext.akcelo.adUnitId to imp object when adUnitId is not defined', () => { + const bidRequests = [ + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { siteId: 123 } }, + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.imp[0].ext.akcelo.adUnitId).to.be.undefined; + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.equal('Missing parameter : adUnitId') + }); + + it('should add ext.akcelo.test=1 to imp object when param akcelo_demo is true', () => { + sandBox.stub(utils, 'getParameterByName').callsFake(() => 'true'); + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo.test).to.equal(1); + }); + + it('should not add ext.akcelo.test to imp object when param akcelo_demo is not true', () => { + sandBox.stub(utils, 'getParameterByName').callsFake(() => 'something_else'); + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo.test).to.be.undefined; + }); + + it('should not add ext.akcelo.test to imp object when param akcelo_demo is not defined', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo.test).to.be.undefined; + }); + }); + + describe('interpretResponse', () => { + it('should return data returned by ORTB converter', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(SAMPLE_RESPONSE, request); + const responseFromConverter = converter.fromORTB({ + request: request.data, + response: SAMPLE_RESPONSE.body, + }); + expect(bids).to.deep.equal(responseFromConverter.bids); + }); + + it('should find the media type from bid.mtype if possible', () => { + const serverResponse = deepClone(SAMPLE_RESPONSE); + serverResponse.body.seatbid[0].bid[0].mtype = 2; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids[0].mediaType).to.equal('video'); + }); + + it('should find the media type from bid.ext.prebid.type if mtype is not defined', () => { + const serverResponse = deepClone(SAMPLE_RESPONSE); + delete serverResponse.body.seatbid[0].bid[0].mtype; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids[0].mediaType).to.equal('banner'); + }); + + it('should skip the bid if bid.mtype and bid.ext.prebid.type are not defined', () => { + const serverResponse = deepClone(SAMPLE_RESPONSE); + delete serverResponse.body.seatbid[0].bid[0].mtype; + delete serverResponse.body.seatbid[0].bid[0].ext.prebid.type; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).to.be.empty; + }); + }); + + describe('getUserSyncs', () => { + it('should return an empty array if iframe sync is not enabled', () => { + const syncs = spec.getUserSyncs({}, [SAMPLE_RESPONSE], null, null); + expect(syncs).to.deep.equal([]); + }); + + it('should return an array with iframe url', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], null, null); + expect(syncs).to.deep.equal([{ + type: 'iframe', + url: 'https://ads.sportslocalmedia.com/load-cookie.html?endpoint=akcelo' + }]); + }); + + it('should return an array with iframe URL and GDPR parameters', () => { + const gdprConsent = { gdprApplies: true, consentString: 'the_gdpr_consent' }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], gdprConsent, null); + expect(syncs[0].url).to.contain('?endpoint=akcelo&gdpr=1&gdpr_consent=the_gdpr_consent'); + }); + + it('should return an array with iframe URL containing empty GDPR parameters when GDPR does not apply', () => { + const gdprConsent = { gdprApplies: false }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], gdprConsent, null); + expect(syncs[0].url).to.contain('?endpoint=akcelo&gdpr=0&gdpr_consent='); + }); + + it('should URI encode the GDPR consent string', () => { + const gdprConsent = { gdprApplies: true, consentString: 'the_gdpr_consent==' }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], gdprConsent, null); + expect(syncs[0].url).to.contain('?endpoint=akcelo&gdpr=1&gdpr_consent=the_gdpr_consent%3D%3D'); + }); + + it('should return an array with iframe URL containing USP parameters when USP is defined', () => { + const uspConsent = 'the_usp_consent'; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], null, uspConsent); + expect(syncs[0].url).to.contain('?endpoint=akcelo&us_privacy=the_usp_consent'); + }); + + it('should URI encode the USP consent string', () => { + const uspConsent = 'the_usp_consent=='; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], null, uspConsent); + expect(syncs[0].url).to.contain('?endpoint=akcelo&us_privacy=the_usp_consent%3D%3D'); + }); + }); +}); From 376a491ae73dc3ed7aed339a774bfc3ecdf78944 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 27 Dec 2024 13:44:59 -0800 Subject: [PATCH 0803/1097] Analytics adapters: attach arbitrary labels to analytics events (#12597) --- .../analyticsAdapter/AnalyticsAdapter.js | 18 +++- test/spec/AnalyticsAdapter_spec.js | 89 +++++++++++++++++-- .../modules/genericAnalyticsAdapter_spec.js | 2 +- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/libraries/analyticsAdapter/AnalyticsAdapter.js b/libraries/analyticsAdapter/AnalyticsAdapter.js index 395a21e5571..d6455750ea3 100644 --- a/libraries/analyticsAdapter/AnalyticsAdapter.js +++ b/libraries/analyticsAdapter/AnalyticsAdapter.js @@ -2,12 +2,20 @@ import { EVENTS } from '../../src/constants.js'; import {ajax} from '../../src/ajax.js'; import {logError, logMessage} from '../../src/utils.js'; import * as events from '../../src/events.js'; +import {config} from '../../src/config.js'; export const _internal = { ajax }; const ENDPOINT = 'endpoint'; const BUNDLE = 'bundle'; +const LABELS_KEY = 'analyticsLabels'; + +let labels = {}; + +config.getConfig(LABELS_KEY, (cfg) => { + labels = cfg[LABELS_KEY] +}); export const DEFAULT_INCLUDE_EVENTS = Object.values(EVENTS) .filter(ev => ev !== EVENTS.AUCTION_DEBUG); @@ -90,12 +98,18 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } function _callEndpoint({ eventType, args, callback }) { - _internal.ajax(url, callback, JSON.stringify({ eventType, args })); + _internal.ajax(url, callback, JSON.stringify({ eventType, args, labels })); } function _enqueue({eventType, args}) { queue.push(() => { - this.track({eventType, args}); + if (Object.keys(labels || []).length > 0) { + args = { + [LABELS_KEY]: labels, + ...args, + } + } + this.track({eventType, labels, args}); }); emptyQueue(); } diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index e853fb72fa8..274f7668fd4 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -9,12 +9,13 @@ import { DEFAULT_INCLUDE_EVENTS, setDebounceDelay } from '../../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {config} from 'src/config.js'; const BID_WON = EVENTS.BID_WON; const NO_BID = EVENTS.NO_BID; const AnalyticsAdapter = require('libraries/analyticsAdapter/AnalyticsAdapter.js').default; -const config = { +const adapterConfig = { url: 'https://localhost:9999/endpoint', analyticsType: 'endpoint' }; @@ -29,7 +30,7 @@ FEATURE: Analytics Adapters API after(disableAjaxForAnalytics); beforeEach(function () { - adapter = new AnalyticsAdapter(config); + adapter = new AnalyticsAdapter(adapterConfig); }); afterEach(function () { @@ -52,7 +53,7 @@ FEATURE: Analytics Adapters API adapter.track({eventType, args}); let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {some: 'data'}, eventType}); + sinon.assert.match(result, {args: {some: 'data'}, eventType}) }); it(`SHOULD queue the event first and then track it WHEN an event occurs before tracking library is available`, function () { @@ -65,9 +66,79 @@ FEATURE: Analytics Adapters API // As now AUCTION_DEBUG is triggered for WARNINGS too, the BID_RESPONSE goes last in the array const index = server.requests.length - 1; let result = JSON.parse(server.requests[index].requestBody); - expect(result).to.deep.equal({eventType, args: {wat: 'wot'}}); + sinon.assert.match(result, {eventType, args: {wat: 'wot'}}) }); + describe('analyticsLabels', () => { + let analyticsLabels; + beforeEach(() => { + analyticsLabels = { + experiment_1: 'group_a', + experiment_2: 'group_b' + } + config.setConfig({ + analyticsLabels + }) + }) + + it('should be attached to payloads (type: endpoint)', () => { + events.emit(BID_WON, {foo: 'bar'}); + adapter.enableAnalytics(); + server.requests + .map(req => JSON.parse(req.requestBody)) + .forEach(payload => sinon.assert.match(payload, {labels: analyticsLabels, args: sinon.match({analyticsLabels})})) + }); + + it('should be attached payloads (type: bundle)', () => { + adapter = new AnalyticsAdapter({ + analyticsType: 'bundle', + global: 'analytics' + }) + window.analytics = sinon.stub(); + try { + events.emit(BID_WON, {foo: 'bar'}) + adapter.enableAnalytics(); + sinon.assert.calledWith(window.analytics, sinon.match.any, BID_WON, sinon.match({analyticsLabels})) + } finally { + delete window.analytics; + } + }); + + it('should be passed to custom track', () => { + Object.assign(adapter, { + track: sinon.stub() + }); + events.emit(BID_WON, {foo: 'bar'}); + adapter.enableAnalytics(); + sinon.assert.calledWith(adapter.track, sinon.match({ + eventType: BID_WON, + args: sinon.match({analyticsLabels}), + labels: analyticsLabels + })) + }) + + it('should not override the "analyticsLabels" property an event payload may have', () => { + adapter.track = sinon.stub(); + events.emit(BID_WON, {analyticsLabels: 'not these ones'}); + adapter.enableAnalytics(); + sinon.assert.calledWith(adapter.track, sinon.match({ + args: {analyticsLabels: 'not these ones'} + })); + }); + + it('should not modify event payloads when there are no labels', () => { + config.resetConfig(); + adapter.track = sinon.stub(); + events.emit(BID_WON, {'foo': 'bar'}); + adapter.enableAnalytics(); + sinon.assert.calledWith(adapter.track, { + labels: {}, + args: {foo: 'bar'}, + eventType: BID_WON + }) + }) + }) + describe('event filters', () => { function fireEvents() { events.emit(BID_WON, {}); @@ -112,7 +183,7 @@ FEATURE: Analytics Adapters API events.emit(BID_WON, {}) } })(adapter.track); - adapter.enableAnalytics(config); + adapter.enableAnalytics(adapterConfig); events.emit(BID_WON, {}); expect(i >= 100).to.eql(false); }) @@ -176,7 +247,7 @@ FEATURE: Analytics Adapters API expect(server.requests.length).to.equal(1); let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {more: 'info'}, eventType: 'bidWon'}); + sinon.assert.match(result, {args: {more: 'info'}, eventType: 'bidWon'}) }); it(`THEN should disable analytics when random number is outside sample range`, function () { @@ -205,7 +276,7 @@ describe('Analytics asynchronous event tracking', () => { beforeEach(() => { clock = sinon.useFakeTimers(); - adapter = new AnalyticsAdapter(config); + adapter = new AnalyticsAdapter(adapterConfig); adapter.track = sinon.stub(); adapter.enableAnalytics({}); }); @@ -224,7 +295,7 @@ describe('Analytics asynchronous event tracking', () => { sinon.assert.notCalled(adapter.track); clock.tick(100); sinon.assert.calledTwice(adapter.track); - sinon.assert.calledWith(adapter.track.firstCall, {eventType: BID_WON, args: {i: 0}}); - sinon.assert.calledWith(adapter.track.secondCall, {eventType: BID_WON, args: {i: 1}}); + sinon.assert.calledWith(adapter.track.firstCall, sinon.match({eventType: BID_WON, args: {i: 0}})); + sinon.assert.calledWith(adapter.track.secondCall, sinon.match({eventType: BID_WON, args: {i: 1}})); }); }) diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index f574a33bf86..8ec61b70810 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -120,7 +120,7 @@ describe('Generic analytics', () => { recv = arg; }); events.emit(BID_RESPONSE, {i: 1}); - expect(recv).to.eql([{eventType: BID_RESPONSE, args: {i: 1}}]); + sinon.assert.match(recv, [sinon.match({eventType: BID_RESPONSE, args: {i: 1}})]) }); it('should not cause infinite recursion, if handler triggers more events', () => { From 97594d954341ba033ec696db721231d55e6ce0e7 Mon Sep 17 00:00:00 2001 From: Ariel <155993606+arielmtk@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:28:12 -0300 Subject: [PATCH 0804/1097] Mobian RTD Provider: Adds prefix to ortb data as per config (#12596) * Adds prefix to ortb data * Updates * Fixes tests * Updates * Fixes AP values not setting in GAM * Fixes tests * Updates types --- modules/mobianRtdProvider.js | 125 ++++++++++++-------- test/spec/modules/mobianRtdProvider_spec.js | 96 ++++++++++----- 2 files changed, 140 insertions(+), 81 deletions(-) diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 02f2d1b83cf..01a0a5d93d1 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -37,15 +37,24 @@ import { setKeyValue } from '../libraries/gptUtils/gptUtils.js'; export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async'; +export const AP_VALUES = 'apValues'; +export const CATEGORIES = 'categories'; +export const EMOTIONS = 'emotions'; +export const GENRES = 'genres'; +export const RISK = 'risk'; +export const SENTIMENT = 'sentiment'; +export const THEMES = 'themes'; +export const TONES = 'tones'; + export const CONTEXT_KEYS = [ - 'apValues', - 'categories', - 'emotions', - 'genres', - 'risk', - 'sentiment', - 'themes', - 'tones' + AP_VALUES, + CATEGORIES, + EMOTIONS, + GENRES, + RISK, + SENTIMENT, + THEMES, + TONES ]; const AP_KEYS = ['a0', 'a1', 'p0', 'p1']; @@ -73,6 +82,24 @@ function makeMemoizedFetch() { export const getContextData = makeMemoizedFetch(); +const entriesToObjectReducer = (acc, [key, value]) => ({ ...acc, [key]: value }); + +export function makeContextDataToKeyValuesReducer(config) { + const { prefix } = config; + return function contextDataToKeyValuesReducer(keyValues, [key, value]) { + if (key === AP_VALUES) { + AP_KEYS.forEach((apKey) => { + if (!value?.[apKey]?.length) return; + keyValues.push([`${prefix}_ap_${apKey}`, value[apKey].map((v) => String(v))]); + }); + } + if (value?.length) { + keyValues.push([`${prefix}_${key}`, value]); + } + return keyValues; + } +} + export async function fetchContextData() { const pageUrl = encodeURIComponent(window.location.href); const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; @@ -101,33 +128,22 @@ export function getConfig(config) { } /** - * @param {MobianConfigParams} parsedConfig + * @param {MobianConfig} config * @param {MobianContextData} contextData - * @returns {function} */ -export function setTargeting(parsedConfig, contextData) { - const { publisherTargeting, prefix } = parsedConfig; +export function setTargeting(config, contextData) { logMessage('context', contextData); + const keyValues = Object.entries(contextData) + .filter(([key]) => config.publisherTargeting.includes(key)) + .reduce(makeContextDataToKeyValuesReducer(config), []) - CONTEXT_KEYS.forEach((key) => { - if (!publisherTargeting.includes(key)) return; - - if (key === 'apValues') { - AP_KEYS.forEach((apKey) => { - if (!contextData[key]?.[apKey]?.length) return; - logMessage(`${prefix}_ap_${apKey}`, contextData[key][apKey]); - setKeyValue(`${prefix}_ap_${apKey}`, contextData[key][apKey]); - }); - return; - } - - if (contextData[key]?.length) { - logMessage(`${prefix}_${key}`, contextData[key]); - setKeyValue(`${prefix}_${key}`, contextData[key]); - } - }); + keyValues.forEach(([key, value]) => setKeyValue(key, value)); } +/** + * @param {Object|string} contextData + * @returns {MobianContextData} + */ export function makeDataFromResponse(contextData) { const data = typeof contextData === 'string' ? safeJSONParse(contextData) : contextData; const results = data.results; @@ -135,50 +151,57 @@ export function makeDataFromResponse(contextData) { return {}; } return { - apValues: results.ap || {}, - categories: results.mobianContentCategories, - emotions: results.mobianEmotions, - genres: results.mobianGenres, - risk: results.mobianRisk || 'unknown', - sentiment: results.mobianSentiment || 'unknown', - themes: results.mobianThemes, - tones: results.mobianTones, + [AP_VALUES]: results.ap || {}, + [CATEGORIES]: results.mobianContentCategories, + [EMOTIONS]: results.mobianEmotions, + [GENRES]: results.mobianGenres, + [RISK]: results.mobianRisk || 'unknown', + [SENTIMENT]: results.mobianSentiment || 'unknown', + [THEMES]: results.mobianThemes, + [TONES]: results.mobianTones, }; } -export function extendBidRequestConfig(bidReqConfig, contextData) { +/** + * @param {Object} bidReqConfig + * @param {MobianContextData} contextData + * @param {MobianConfig} config + */ +export function extendBidRequestConfig(bidReqConfig, contextData, config) { logMessage('extendBidRequestConfig', bidReqConfig, contextData); const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + const keyValues = Object.entries(contextData) + .filter(([key]) => config.advertiserTargeting.includes(key)) + .reduce(makeContextDataToKeyValuesReducer(config), []) + .reduce(entriesToObjectReducer, {}); ortb2Site.ext = ortb2Site.ext || {}; ortb2Site.ext.data = { ...(ortb2Site.ext.data || {}), - ...contextData + ...keyValues }; return bidReqConfig; } /** - * @param {MobianConfig} config + * @param {MobianConfig} rawConfig * @returns {boolean} */ -function init(config) { - logMessage('init', config); - - const parsedConfig = getConfig(config); - - if (parsedConfig.publisherTargeting.length) { - getContextData().then((contextData) => setTargeting(parsedConfig, contextData)); +function init(rawConfig) { + logMessage('init', rawConfig); + const config = getConfig(rawConfig); + if (config.publisherTargeting.length) { + getContextData().then((contextData) => setTargeting(config, contextData)); } - return true; } -function getBidRequestData(bidReqConfig, callback, config) { +function getBidRequestData(bidReqConfig, callback, rawConfig) { logMessage('getBidRequestData', bidReqConfig); - const { advertiserTargeting } = getConfig(config); + const config = getConfig(rawConfig); + const { advertiserTargeting } = config; if (!advertiserTargeting.length) { callback(); @@ -187,7 +210,7 @@ function getBidRequestData(bidReqConfig, callback, config) { getContextData() .then((contextData) => { - extendBidRequestConfig(bidReqConfig, contextData); + extendBidRequestConfig(bidReqConfig, contextData, config); }) .catch(() => {}) .finally(() => callback()); diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index 796a79e4e1c..0794e99151d 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -4,10 +4,19 @@ import * as ajax from 'src/ajax.js'; import * as gptUtils from 'libraries/gptUtils/gptUtils.js'; import { CONTEXT_KEYS, + AP_VALUES, + CATEGORIES, + EMOTIONS, + GENRES, + RISK, + SENTIMENT, + THEMES, + TONES, extendBidRequestConfig, fetchContextData, getConfig, getContextData, + makeContextDataToKeyValuesReducer, makeDataFromResponse, setTargeting, } from 'modules/mobianRtdProvider.js'; @@ -35,14 +44,29 @@ describe('Mobian RTD Submodule', function () { }); const mockContextData = { - apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }, - categories: [], - emotions: ['affection'], - genres: [], - risk: 'low', - sentiment: 'positive', - themes: [], - tones: [], + [AP_VALUES]: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }, + [CATEGORIES]: [], + [EMOTIONS]: ['affection'], + [GENRES]: [], + [RISK]: 'low', + [SENTIMENT]: 'positive', + [THEMES]: [], + [TONES]: [], + } + + const mockKeyValues = { + 'mobian_ap_a1': ['2313', '12'], + 'mobian_ap_p0': ['1231231', '212'], + 'mobian_ap_p1': ['231', '419'], + 'mobian_emotions': ['affection'], + 'mobian_risk': 'low', + 'mobian_sentiment': 'positive', + } + + const mockConfig = { + prefix: 'mobian', + publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], + advertiserTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], } beforeEach(function () { @@ -79,10 +103,6 @@ describe('Mobian RTD Submodule', function () { describe('makeDataFromResponse', function () { it('should format context data response', async function () { - ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success(mockResponse); - }); - const data = makeDataFromResponse(mockResponse); expect(data).to.deep.equal(mockContextData); }); @@ -103,14 +123,14 @@ describe('Mobian RTD Submodule', function () { it('should set targeting key-value pairs as per config', function () { const parsedConfig = { prefix: 'mobian', - publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'], + publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], }; setTargeting(parsedConfig, mockContextData); expect(setKeyValueSpy.callCount).to.equal(6); - expect(setKeyValueSpy.calledWith('mobian_ap_a1', [2313, 12])).to.equal(true); - expect(setKeyValueSpy.calledWith('mobian_ap_p0', [1231231, 212])).to.equal(true); - expect(setKeyValueSpy.calledWith('mobian_ap_p1', [231, 419])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_a1', ['2313', '12'])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_p0', ['1231231', '212'])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_p1', ['231', '419'])).to.equal(true); expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true); expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true); expect(setKeyValueSpy.calledWith('mobian_sentiment', 'positive')).to.equal(true); @@ -124,7 +144,7 @@ describe('Mobian RTD Submodule', function () { it('should not set key-value pairs if context data is empty', function () { const parsedConfig = { prefix: 'mobian', - publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'], + publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], }; setTargeting(parsedConfig, {}); @@ -134,7 +154,7 @@ describe('Mobian RTD Submodule', function () { it('should only set key-value pairs for the keys specified in config', function () { const parsedConfig = { prefix: 'mobian', - publisherTargeting: ['emotions', 'risk'], + publisherTargeting: [EMOTIONS, RISK], }; setTargeting(parsedConfig, mockContextData); @@ -155,8 +175,8 @@ describe('Mobian RTD Submodule', function () { describe('extendBidRequestConfig', function () { it('should extend bid request config with context data', function () { - const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); - expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData); + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockKeyValues); }); it('should not override existing data', function () { @@ -164,17 +184,17 @@ describe('Mobian RTD Submodule', function () { existing: 'data' }; - const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig); expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal({ existing: 'data', - ...mockContextData + ...mockKeyValues }); }); it('should create data object if missing', function () { delete bidReqConfig.ortb2Fragments.global.site.ext.data; - const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); - expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData); + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockKeyValues); }); }); @@ -184,14 +204,14 @@ describe('Mobian RTD Submodule', function () { name: 'mobianBrandSafety', params: { prefix: 'mobiantest', - publisherTargeting: ['apValues'], - advertiserTargeting: ['emotions'], + publisherTargeting: [AP_VALUES], + advertiserTargeting: [EMOTIONS], } }); expect(config).to.deep.equal({ prefix: 'mobiantest', - publisherTargeting: ['apValues'], - advertiserTargeting: ['emotions'], + publisherTargeting: [AP_VALUES], + advertiserTargeting: [EMOTIONS], }); }); @@ -199,12 +219,12 @@ describe('Mobian RTD Submodule', function () { const config = getConfig({ name: 'mobianBrandSafety', params: { - publisherTargeting: ['apValues'], + publisherTargeting: [AP_VALUES], } }); expect(config).to.deep.equal({ prefix: 'mobian', - publisherTargeting: ['apValues'], + publisherTargeting: [AP_VALUES], advertiserTargeting: [], }); }); @@ -242,4 +262,20 @@ describe('Mobian RTD Submodule', function () { }); }); }); + + describe('makeContextDataToKeyValuesReducer', function () { + it('should format context data to key-value pairs', function () { + const config = getConfig({ + name: 'mobianBrandSafety', + params: { + prefix: 'mobian', + publisherTargeting: true, + advertiserTargeting: true, + } + }); + const keyValues = Object.entries(mockContextData).reduce(makeContextDataToKeyValuesReducer(config), []); + const keyValuesObject = Object.fromEntries(keyValues); + expect(keyValuesObject).to.deep.equal(mockKeyValues); + }); + }); }); From a363b69c989753fd4cb5b6a0ca8dfc3208453d23 Mon Sep 17 00:00:00 2001 From: Rich Rodriguez Date: Mon, 30 Dec 2024 22:46:51 -0500 Subject: [PATCH 0805/1097] Mobian RTD Module: Documentation update (#12608) * Updated overview section * Added detailed documentation for Mobian RTD module * Adding formatting for documentation * More formatting * More formatting * Updated values to be exact matches, clarified some descriptions * Updated Mobian RTD Module documentation with more detail --- modules/mobianRtdProvider.md | 116 ++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 48 deletions(-) diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md index 100e529143f..48e7a1c6cec 100644 --- a/modules/mobianRtdProvider.md +++ b/modules/mobianRtdProvider.md @@ -14,91 +14,139 @@ The Mobian Real-Time Data (RTD) Module is a plug-and-play Prebid.js adapter that Navigate to https://docs.prebid.org/download.html and check the box labeled Mobian Prebid Contextual Evaluation. If you have installed Prebid.js on your site previously, please be sure to select any other modules and adaptors to suit your needs. When clicking the "Get Prebid.js" button at the bottom of the page, the site will build a version of Prebid.js with all of your selections. -Direct link to the Mobian module in the Prebid.js repository: https://github.com/prebid/Prebid.js/blob/a9de3c15ac9a108b43a1e2df04abd6dfb5297530/modules/mobianRtdProvider.js +Direct link to the Mobian module in the Prebid.js repository: https://github.com/prebid/Prebid.js/blob/master/modules/mobianRtdProvider.js The client will need to provide Mobian with all the domains that would be using the prebid module so that Mobian can whitelist those domains. Failure to whitelist the domains will yield a 404 when making a request to the Mobian Contextual API at https://prebid.outcomes.net/. +## Configuration Highlight + +Below is Mobian's suggested default for configuration: + +```js +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'mobianBrandSafety', + params: { + // Prefix for the targeting keys (default: 'mobian') + prefix: 'mobian', + + // Enable targeting keys for advertiser data + advertiserTargeting: true, + // Or set it as an array to pick specific targeting keys: + // advertiserTargeting: ['genres', 'emotions', 'themes'], + // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones' + + // Enable targeting keys for publisher data + publisherTargeting: true, + // Or set it as an array to pick specific targeting keys: + // publisherTargeting: ['tones', 'risk'], + // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones' + } + }] + } +}); +``` + ## Functionality At a high level, the Mobian RTD Module is designed to call the Mobian Contextal API on page load, requesting the Mobian classifications and results for the URL. The classifications and results are designed to be picked up by any SSP or DSP in the Prebid.js ecosystem. The module also supports placing the Mobian classifications on each ad slot on the page, thus allowing for targeting within GAM. ## Available Classifications +NOTE: The examples below for targetable keys for GAM or otherwise in the ortb2 object assume that your prefix is the default of "mobian". The prefix in the targetable key will change based on your settings. + Risk: -Key: mobianRisk +Prebid.outcomes.net endpoint key: mobianRisk + +Targetable Key: mobian_risk Possible values: "none", "low", "medium" or "high" -Description: Risk will contain Mobian’s brand safety assessment of the page. Brand Safety is determined via the Mobian AI models taking into account a semantic analysis of the content while understanding the context. A more detailed description of the reasoning for a given URL can be observed by going to mbs.themobian.com and entering the URL. +Description: This category assesses whether content contains any potential risks or concerns to advertisers and returns a determination of Low Risk, Medium Risk, or High Risk based on the inclusion of sensitive or high-risk topics. Content that might be categorized as unsafe may include violence, hate speech, misinformation, or sensitive topics that most advertisers would like to avoid. Content that is explicit or overly graphic in nature will be more likely to fall into the High Risk tier compared to content that describes similar subjects in a more informative or educational manner. ------------------ Content Categories: -Key: mobianContentCategories +Prebid.outcomes.net endpoint key: mobianContentCategories + +Targetable Key: mobian_categories Possible values: "adult_content", "arms", "crime", "death_injury", "debated_issue", "hate_speech", "drugs_alcohol", "obscenity", "piracy", "spam", "terrorism" -Description: Content Categories contain results based on the legacy GARM framework. GARM no longer is a standard and does not factor into our risk assessment but is included for posterity. +Description: Brand Safety Categories contain categorical results for brand safety when relevant (e.g. Low Risk Adult Content). Note there can be Medium and High Risk content that is not associated to a specific brand safety category. ------------------ Sentiment: -Key: mobianSentiment +Prebid.outcomes.net endpoint key: mobianSentiment + +Targetable Key: mobian_sentiment Possible values: "negative", "neutral" or "positive" -Description: Sentiment can only be one of the three values listed, and is determined via the Mobian AI analyzing the content and making one of these three determinations. +Description: This category analyzes the overall positivity, negativity, or neutrality of a piece of content. This is a broad categorization of the content’s tone; every piece of content receives one of three possible sentiment ratings: Positive, Negative, or Neutral. ------------------ Emotion: -Key: mobianEmotions +Prebid.outcomes.net endpoint key: mobianEmotions -Possible values: "love", "joy", "surprise", "anger", "sadness", "fear" +Targetable Key: mobian_emotions -Description: The Mobian AI assesses the emotions exuded from the content, taking into account the context. A given piece of content can have multiple emotions. The current list of emotions is all possible emotions available but this will be updated to be more freeform and varied in a future release. +Possible values: Various but some examples include "love", "joy", "surprise", "anger", "sadness", "fear" + +Description: This category represents the specific feelings expressed or evoked through the content. Emotions are the reactions tied to the content’s presentation. Multiple emotions may be evoked by a single piece of content as this category reflects the way humans engage with the content. ------------------ Tone: -Key: mobianTones +Prebid.outcomes.net endpoint key: mobianTones + +Targetable Key: mobian_tones Possible values: Various, but some examples include "comedic", "serious" or "emotional" -Description: While the Mobian emotion classification looks at the emotions exuded from the content, tone examines the overall presentation of the content and determines the overall mood of the work. A given piece of content can have multiple tones. +Description: This category represents the content’s stylistic attitude or perspective that is being conveyed. If the Genre classification above represents the more objective structure, the Tone classification represents the subjective form. This categorization influences the way audiences may receive the piece of content and how they could be impacted by it. ------------------ Theme: -Key: mobianThemes +Prebid.outcomes.net endpoint key: mobianThemes + +Targetable Key: mobian_themes Possible values: Various, but some examples include "skincare", "food" and "nightlife" -Description: Themes are a wide classification of content categorization, taking into account the content and context to label the content with a theme. A given piece of content can have multiple themes. +Description: This category includes broad conceptual ideas or underlying topics that form the foundation of the content. Themes represent the central message or idea conveyed throughout, rather than the specific details of the subject. Themes are intended to be broad and high-level, describing the overall purpose and intent of the content, and can connect multiple pieces of content, even if they are not from the same property. ------------------ Genre: -Key: mobianGenre +Prebid.outcomes.net endpoint key: mobianGenres + +Targetable Key: mobian_genres Possible values: Various, but some examples include "journalism", "gaming" or "how-to" -Description: Genres are a more narrow classification of content categorization, aiming to label the content towards its overall purpose and audience. A given piece of content can have multiple genres. +Description: This category represents the type or style of the content, focusing on the purpose, format, or presentation of the content. Genres group pieces of content into recognizable categories based on style and provide a framework for understanding the structure of the content. ------------------ AP Values -Keys: ap_a0, ap_a1, ap_p0, ap_p1 +Prebid.outcomes.net endpoint key: ap (an array, containing values of a0, a1, p0, p1) -Possible values: Various, numerically id-based and customizable based on Mobian Persona Settings. +Targetable Keys: mobian_ap_a0, mobian_ap_a1, mobian_ap_p0, mobian_ap_p1 + +Possible values: Various, numerically id-based and customizable based on Mobian Context Settings. Description: Mobian AI Personas are custom created based on prompts to find a specific audience. Please contact your Mobian contact directly for more information on this tool. The difference between the keys is below: @@ -123,34 +171,6 @@ window.googletag.cmd.push(() => { "key" and "value" will be replaced with the various classifications as described in the previous section. Notably, this function runs before ad calls are made to GAM, which enables the keys and value to be used for targeting or blocking in GAM. -For more details on how to set up key-value pairs in GAM, please see this documentation from Google: https://support.google.com/admanager/answer/9796369?sjid=12535178383871274096-NA - -For example, if you wanted to target articles where mobianRisk is "low", the key to set in GAM would be "mobianRisk" and the value would be "low". Once these keys and values are set within the Inventory section in GAM as listed by their documentation, you can then reference the key value pair in Custom Targeting for any line item you create. - -## Configuration Highlight - -```js -pbjs.setConfig({ - realTimeData: { - dataProviders: [{ - name: 'mobianBrandSafety', - params: { - // Prefix for the targeting keys (default: 'mobian') - prefix: 'mobian', - - // Enable targeting keys for advertiser data - advertiserTargeting: true, - // Or set it as an array to pick specific targeting keys: - // advertiserTargeting: ['genres', 'emotions', 'themes'], - // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones' +For more details on how to set up key-value pairs in GAM, please see this documentation from Google: https://support.google.com/admanager/answer/9796369 - // Enable targeting keys for publisher data - publisherTargeting: true, - // Or set it as an array to pick specific targeting keys: - // publisherTargeting: ['tones', 'risk'], - // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones' - } - }] - } -}); -``` +For example, if you wanted to target articles where mobianRisk is "low", the key to set in GAM would be "mobian_risk" and the value would be "low". Once these keys and values are set within the Inventory section in GAM as listed by their documentation, you can then reference the key value pair in Custom Targeting for any line item you create. From f0301548afc04e0cb272d988e695234bf8c5ea22 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:53:40 +0200 Subject: [PATCH 0806/1097] Attekmi add new alias artechnology (#12609) * update adapter SmartHub: add aliases * Attekmi: add new alias Artechnology --------- Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 5f24f432a13..d6600b4afd5 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -15,6 +15,7 @@ const ALIASES = [ {code: 'tredio'}, {code: 'felixads'}, {code: 'vimayx'}, + {code: 'artechnology'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', @@ -24,6 +25,7 @@ const BASE_URLS = { tredio: 'https://tredio-prebid.attekmi.com/pbjs', felixads: 'https://felixads-prebid.attekmi.com/pbjs', vimayx: 'https://vimayx-prebid.attekmi.com/pbjs', + artechnology: 'https://artechnology-prebid.attekmi.com/pbjs', }; const _getUrl = (partnerName) => { From 7e0cda11ceffd0c78fbf8829ef1f184a9d05a92b Mon Sep 17 00:00:00 2001 From: escalax Date: Thu, 2 Jan 2025 23:55:17 +0200 Subject: [PATCH 0807/1097] Escalax Bid Adapter: initial release (#12483) * init escalax adapter * region substitution based on time zone --- modules/escalaxBidAdapter.js | 106 +++++++ modules/escalaxBidAdapter.md | 80 ++++++ test/spec/modules/escalaxBidAdapter_spec.js | 303 ++++++++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 modules/escalaxBidAdapter.js create mode 100644 modules/escalaxBidAdapter.md create mode 100644 test/spec/modules/escalaxBidAdapter_spec.js diff --git a/modules/escalaxBidAdapter.js b/modules/escalaxBidAdapter.js new file mode 100644 index 00000000000..70a10d748bc --- /dev/null +++ b/modules/escalaxBidAdapter.js @@ -0,0 +1,106 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'escalax'; +const ESCALAX_SOURCE_ID_MACRO = '[sourceId]'; +const ESCALAX_ACCOUNT_ID_MACRO = '[accountId]'; +const ESCALAX_SUBDOMAIN_MACRO = '[subdomain]'; +const ESCALAX_URL = `https://${ESCALAX_SUBDOMAIN_MACRO}.escalax.io/bid?type=pjs&partner=${ESCALAX_SOURCE_ID_MACRO}&token=${ESCALAX_ACCOUNT_ID_MACRO}`; +const ESCALAX_DEFAULT_CURRENCY = 'USD'; +const ESCALAX_DEFAULT_SUBDOMAIN = 'bidder_us'; + +function createImp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + imp.ext = { + [BIDDER_CODE]: { + sourceId: bidRequest.params.sourceId, + accountId: bidRequest.params.accountId, + } + }; + if (!imp.bidfloor) imp.bidfloor = bidRequest.params.bidfloor; + return imp; +} + +function createRequest(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + request.test = config.getConfig('debug') ? 1 : 0; + if (!request.cur) request.cur = [bid.params.currency || ESCALAX_DEFAULT_CURRENCY]; + return request; +} + +function createBidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.cur = 'USD'; + return bidResponse; +} + +function getSubdomain() { + const regionMap = { + 'Europe': 'bidder_eu', + 'Africa': 'bidder_eu', + 'Atlantic': 'bidder_eu', + 'Arctic': 'bidder_eu', + 'Asia': 'bidder_apac', + 'Australia': 'bidder_apac', + 'Antarctica': 'bidder_apac', + 'Pacific': 'bidder_apac', + 'Indian': 'bidder_apac', + 'America': 'bidder_us' + }; + + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + return regionMap[region] || 'bidder_us'; + } catch (err) { + return 'bidder_us'; + } +} + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 20, + }, + imp: createImp, + request: createRequest, + bidResponse: createBidResponse +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.params.sourceId) && Boolean(bid.params.accountId); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return []; + const { sourceId, accountId } = validBidRequests[0].params; + const subdomain = getSubdomain(); + const endpointURL = ESCALAX_URL + .replace(ESCALAX_SUBDOMAIN_MACRO, subdomain || ESCALAX_DEFAULT_SUBDOMAIN) + .replace(ESCALAX_ACCOUNT_ID_MACRO, sourceId) + .replace(ESCALAX_SOURCE_ID_MACRO, accountId); + const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); + return { + method: 'POST', + url: endpointURL, + data: request + }; + }, + + interpretResponse: (response, request) => { + if (response?.body) { + const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + return bids; + } + return []; + }, +}; + +registerBidder(spec); diff --git a/modules/escalaxBidAdapter.md b/modules/escalaxBidAdapter.md new file mode 100644 index 00000000000..7cd45cabdc6 --- /dev/null +++ b/modules/escalaxBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: Escalax SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: connect@escalax.io +``` + +# Description + +Escalax Bidding adapter requires setup before beginning. Please contact us at + +# Test Parameters + +```js +const adUnits = [ + { + code: "banner1", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bids: [ + { + bidder: "escalax", + params: { + accountId: "hash", + sourceId: "sourceId", + }, + }, + ], + }, + { + code: "native_example", + mediaTypes: { + native: {}, + }, + bids: [ + { + bidder: "escalax", + params: { + accountId: "hash", + sourceId: "sourceId", + }, + }, + ], + }, + { + code: "video1", + sizes: [640, 480], + mediaTypes: { + video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: ["application/javascript", "video/mp4"], + w: 1920, + h: 1080, + protocols: [2], + linearity: 1, + api: [1, 2], + }, + }, + bids: [ + { + bidder: "escalax", + params: { + accountId: "hash", + sourceId: "sourceId", + }, + }, + ], + }, +]; +``` diff --git a/test/spec/modules/escalaxBidAdapter_spec.js b/test/spec/modules/escalaxBidAdapter_spec.js new file mode 100644 index 00000000000..bc375ff3dae --- /dev/null +++ b/test/spec/modules/escalaxBidAdapter_spec.js @@ -0,0 +1,303 @@ +import { expect } from 'chai'; +import { spec } from 'modules/escalaxBidAdapter'; +import 'modules/priceFloors.js'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; + +import 'src/prebid.js'; +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; + +const SIMPLE_BID_REQUEST = { + bidder: 'escalax', + params: { + sourceId: 'sourceId', + accountId: 'accountId', + }, + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-1234567890123-0', + transactionId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + bidId: 'abcdef1234567890', + bidderRequestId: '1234567890abcdef', + auctionId: 'abcdef1234567890', + sizes: [[300, 250], [160, 600]], + gdprConsent: { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', + }, +} + +const BANNER_BID_REQUEST = { + bidder: 'escalax', + params: { + sourceId: 'sourceId', + accountId: 'accountId', + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + code: 'banner_example', + timeout: 1000, +} + +const VIDEO_BID_REQUEST = { + placementCode: '/DfpAccount1/slotVideo', + bidId: 'test-bid-id-2', + mediaTypes: { + video: { + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + bidder: 'escalax', + params: { + sourceId: '123', + accountId: '123', + }, + adUnitCode: '/adunit-code/test-path', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, +} + +const NATIVE_BID_REQUEST = { + code: 'native_example', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'escalax', + params: { + sourceId: 'sourceId', + accountId: 'accountId', + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, + uspConsent: 'uspConsent' +}; + +const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + } +}; + +const gdprConsent = { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', +} + +describe('escalaxAdapter', function () { + const adapter = newBidder(spec); + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('with user privacy regulations', function () { + it('should send the Coppa "required" flag set to "1" in the request', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(serverRequest.data.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should send the GDPR Consent data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); + }); + + it('should send the CCPA data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(true); + }); + + it('should return false when sourceId/accountId is missing', function () { + let localbid = Object.assign({}, BANNER_BID_REQUEST); + delete localbid.params.sourceId; + delete localbid.params.accountId; + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(false); + }); + }); + + describe('build request', function () { + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + expect(bidRequest).to.be.an('array'); + expect(bidRequest.length).to.equal(0); + }); + + it('should return a valid bid request object', function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request).to.not.equal('array'); + expect(request.data).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.not.equal(''); + expect(request.url).to.not.equal(undefined); + expect(request.url).to.not.equal(null); + + expect(request.data.site).to.have.property('page'); + expect(request.data.site).to.have.property('domain'); + expect(request.data).to.have.property('id'); + expect(request.data).to.have.property('imp'); + expect(request.data).to.have.property('device'); + }); + + it('should return a valid bid BANNER request object', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].banner).to.exist; + expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); + expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); + }); + + if (FEATURES.VIDEO) { + it('should return a valid bid VIDEO request object', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].video).to.exist; + expect(request.data.imp[0].video.w).to.be.an('number'); + expect(request.data.imp[0].video.h).to.be.an('number'); + }); + } + + it('should return a valid bid NATIVE request object', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0]).to.be.an('object'); + }); + }) + + describe('interpretResponse', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [{ + 'bidId': '28ffdk2B952532', + 'bidder': 'escalax', + 'userId': { + 'freepassId': { + 'userIp': '172.21.0.1', + 'userId': '123', + 'commonId': 'commonIdValue' + } + }, + 'adUnitCode': 'adunit-code', + 'params': { + 'publisherId': 'publisherIdValue' + } + }]; + bidderRequest = {}; + }); + + it('Empty response must return empty array', function () { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse, BANNER_BID_REQUEST); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const serverResponse = { + body: { + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'impid': '28ffdk2B952532', + 'price': 97, + 'adm': '', + 'w': 300, + 'h': 250, + 'crid': 'creative0' + }] + }] + } + }; + it('should interpret server response', function () { + const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse(serverResponse, bidRequest); + expect(bids).to.be.an('array'); + const bid = bids[0]; + expect(bid).to.be.an('object'); + expect(bid.currency).to.equal('USD'); + expect(bid.cpm).to.equal(97); + expect(bid.ad).to.equal(ad) + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('creative0'); + }); + }) + }); +}); From 6ba8de60fc9fd5413a5fcb988a05200ea6143ffc Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Sat, 4 Jan 2025 11:24:17 +0200 Subject: [PATCH 0808/1097] Adkernel: add UrekaMedia alias (#12614) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 414d1ca28e9..2120a73dabd 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -102,7 +102,8 @@ export const spec = { {code: 'revbid'}, {code: 'spinx', gvlid: 1308}, {code: 'oppamedia'}, - {code: 'pixelpluses', gvlid: 1209} + {code: 'pixelpluses', gvlid: 1209}, + {code: 'urekamedia'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From 03a2ea56ac5eb8744947a7ce7e9bdeb227a1f567 Mon Sep 17 00:00:00 2001 From: tarasmatokhniuk Date: Sat, 4 Jan 2025 10:26:23 +0100 Subject: [PATCH 0809/1097] Adtrgtme bid adapter changes (#12580) * adtrgtme bid adapter Bugfix, add params.zid as placement ident * #12580 add fixes add ortb2.device.ip remove deepAccess refactor site object refactor user sync * Fix linter --- modules/adtrgtmeBidAdapter.js | 494 ++++++------- modules/adtrgtmeBidAdapter.md | 19 +- test/spec/modules/adtrgtmeBidAdapter_spec.js | 726 +++++++++---------- 3 files changed, 574 insertions(+), 665 deletions(-) diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js index 18cbd1e6ae7..ba30b17e3d1 100644 --- a/modules/adtrgtmeBidAdapter.js +++ b/modules/adtrgtmeBidAdapter.js @@ -1,333 +1,295 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { deepAccess, isFn, isStr, isNumber, isArray, isEmpty, isPlainObject, generateUUID, logWarn } from '../src/utils.js'; +import { + isFn, + isStr, + isNumber, + isEmpty, + isPlainObject, + generateUUID, + logWarn, +} from '../src/utils.js'; import { config } from '../src/config.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; -const INTEGRATION_METHOD = 'prebid.js'; const BIDDER_CODE = 'adtrgtme'; -const ENDPOINT = 'https://z.cdn.adtarget.market/ssp?prebid&s='; -const ADAPTER_VERSION = '1.0.0'; -const PREBID_VERSION = '$prebid.version$'; -const DEFAULT_BID_TTL = 300; -const DEFAULT_CURRENCY = 'USD'; - -function transformSizes(sizes) { - const getSize = (size) => { - return { - w: parseInt(size[0]), - h: parseInt(size[1]) - } - } - if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { - return [ getSize(sizes) ]; - } - return sizes.map(getSize); +const BIDDER_VERSION = '1.0.5'; +const BIDDER_URL = 'https://z.cdn.adtarget.market/ssp?prebid&s='; +const PREBIDJS_VERSION = '$prebid.version$'; +const DEFAULT_TTL = 300; +const DEFAULT_CUR = 'USD'; + +function getFormat(s) { + const parseSize = ([w, h]) => ({ w: parseInt(w, 10), h: parseInt(h, 10) }); + return Array.isArray(s) && s.length === 2 && !Array.isArray(s[0]) + ? [parseSize(s)] + : s.map(parseSize); } -function extractUserSyncUrls(syncOptions, pixels) { - let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi; - let tagNameRegExp = /\w*(?=\s)/; - let srcRegExp = /src=("|')(.*?)\1/; - let userSyncObjects = []; - - if (pixels) { - let matchedItems = pixels.match(itemsRegExp); - if (matchedItems) { - matchedItems.forEach(item => { - let tagName = item.match(tagNameRegExp)[0]; - let url = item.match(srcRegExp)[2]; - if (tagName && url) { - let tagType = tagName.toLowerCase() === 'img' ? 'image' : 'iframe'; - if ((!syncOptions.iframeEnabled && tagType === 'iframe') || - (!syncOptions.pixelEnabled && tagType === 'image')) { - return; - } - userSyncObjects.push({ - type: tagType, - url: url - }); - } - }); - } - } - return userSyncObjects; +function getType(b) { + return b?.mediaTypes?.banner ? BANNER : false; } -function isSecure(bid) { - return deepAccess(bid, 'params.bidOverride.imp.secure') ?? deepAccess(bid, 'ortb2Imp.secure') ?? 1; -}; - -function getMediaType(bid) { - return deepAccess(bid, 'mediaTypes.banner') ? BANNER : false; +function getBidfloor(b) { + return isFn(b.getFloor) + ? b.getFloor({ + size: '*', + currency: b?.params?.bidOverride?.cur ?? DEFAULT_CUR, + mediaType: BANNER, + }) + : false; } -function validateAppendObject(validationFunction, allowedKeys, inputObject, appendToObject) { - const outputObject = { - ...appendToObject - }; - if (allowedKeys.length > 0 && typeof validationFunction === 'function') { - for (const objectKey in inputObject) { - if (allowedKeys.indexOf(objectKey) !== -1 && validationFunction(inputObject[objectKey])) { - outputObject[objectKey] = inputObject[objectKey] - } - } - } - return outputObject; -}; - -function getTtl(bidderRequest) { - const ttl = config.getConfig('adtrgtme.ttl'); - const validateTTL = (ttl) => { - return (isNumber(ttl) && ttl > 0 && ttl < 3600) ? ttl : DEFAULT_BID_TTL - }; - return ttl ? validateTTL(ttl) : validateTTL(deepAccess(bidderRequest, 'params.ttl')); -}; - -function getFloorModuleData(bid) { - const getFloorRequestObject = { - currency: deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY, - mediaType: BANNER, - size: '*' - }; - return (isFn(bid.getFloor)) ? (bid.getFloor(getFloorRequestObject) || {}) : false; -}; +function getTtl(b) { + const t = config.getConfig('adtrgtme.ttl'); + const validate = (t) => (isNumber(t) && t > 0 && t < 3000 ? t : DEFAULT_TTL); + return t ? validate(t) : validate(b?.params?.ttl); +} -function generateOpenRtbObject(bidderRequest, bid) { - if (bidderRequest) { - let outBoundBidRequest = { - id: generateUUID(), - cur: [getFloorModuleData(bidderRequest).currency || deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY], - imp: [], - site: { - page: deepAccess(bidderRequest, 'refererInfo.page') - }, - device: { - dnt: 0, - ua: navigator.userAgent, - ip: deepAccess(bid, 'params.bidOverride.device.ip') || deepAccess(bid, 'params.ext.ip') || undefined +function createORTB(bR, bid) { + if (!bR || !bid) return; + + const { currency = bid.params?.bidOverride?.cur || DEFAULT_CUR } = + getBidfloor(bR); + const ip = + bid.params?.bidOverride?.device?.ip || + bid.ortb2?.device?.ip || + bid.params?.ext?.ip; + const site = bid.ortb2?.site || undefined; + const user = bid.ortb2?.user || undefined; + const gdpr = bR.gdprConsent?.gdprApplies ? 1 : 0; + const consentString = gdpr ? bR.gdprConsent?.consentString : ''; + const usPrivacy = bR.uspConsent || ''; + + let oR = { + id: generateUUID(), + cur: [currency], + imp: [], + site: { + id: String(bid.params?.sid), + page: bR.refererInfo?.page || '', + ...site, + }, + device: { + dnt: bid?.params?.dnt ? 1 : 0, + ua: bid?.params?.ua || navigator.userAgent, + ip, + }, + regs: { + ext: { + us_privacy: usPrivacy, + gdpr, }, - regs: { - ext: { - 'us_privacy': bidderRequest.uspConsent ? bidderRequest.uspConsent : '', - gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - } + }, + source: { + ext: { + hb: 1, + bidderver: BIDDER_VERSION, + prebidjsver: PREBIDJS_VERSION, + ...(bid?.schain && { schain: bid.schain }), }, - source: { - ext: { - hb: 1, - adapterver: ADAPTER_VERSION, - prebidver: PREBID_VERSION, - integration: { - name: INTEGRATION_METHOD, - ver: PREBID_VERSION - } - }, - fd: 1 + fd: 1, + }, + user: { + ...user, + ext: { + consent: consentString, + ...(user?.ext || {}), }, - user: { - ext: { - consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies - ? bidderRequest.gdprConsent.consentString : '' - } - } - }; - - outBoundBidRequest.site.id = bid.params.sid; - - if (bidderRequest.ortb2) { - outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); - }; - - if (deepAccess(bid, 'schain')) { - outBoundBidRequest.source.ext.schain = bid.schain; - outBoundBidRequest.source.ext.schain.nodes[0].rid = outBoundBidRequest.id; - }; - - return outBoundBidRequest; + }, }; -}; - -function appendImpObject(bid, openRtbObject) { - const mediaTypeMode = getMediaType(bid); - - if (openRtbObject && bid) { - const impObject = { - id: bid.bidId, - secure: isSecure(bid), - bidfloor: getFloorModuleData(bid).floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor') || 0.000001 - }; - - if (mediaTypeMode === BANNER) { - impObject.banner = { - mimes: bid.mediaTypes.banner.mimes || ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], - format: transformSizes(bid.sizes) - }; - if (bid.mediaTypes.banner.pos) { - impObject.banner.pos = bid.mediaTypes.banner.pos; - }; - }; - - impObject.ext = { - dfp_ad_unit_code: bid.adUnitCode - }; - if (deepAccess(bid, 'params.zid')) { - impObject.tagid = bid.params.zid; - } - - if (deepAccess(bid, 'ortb2Imp.ext.data') && isPlainObject(bid.ortb2Imp.ext.data)) { - impObject.ext.data = bid.ortb2Imp.ext.data; - }; - - if (deepAccess(bid, 'ortb2Imp.instl') && isNumber(bid.ortb2Imp.instl) && (bid.ortb2Imp.instl === 1)) { - impObject.instl = bid.ortb2Imp.instl; - }; - - openRtbObject.imp.push(impObject); - }; -}; - -function appendFirstPartyData(outBoundBidRequest, bid) { - const ortb2Object = bid.ortb2; - const siteObject = deepAccess(ortb2Object, 'site') || undefined; - const siteContentObject = deepAccess(siteObject, 'content') || undefined; - const userObject = deepAccess(ortb2Object, 'user') || undefined; + if (bid?.schain) { + oR.source.ext.schain.nodes[0].rid = oR.id; + } - if (siteObject && isPlainObject(siteObject)) { - const allowedSiteStringKeys = ['name', 'domain', 'page', 'ref', 'keywords']; - const allowedSiteArrayKeys = ['cat', 'sectioncat', 'pagecat']; - const allowedSiteObjectKeys = ['ext']; - outBoundBidRequest.site = validateAppendObject(isStr, allowedSiteStringKeys, siteObject, outBoundBidRequest.site); - outBoundBidRequest.site = validateAppendObject(isArray, allowedSiteArrayKeys, siteObject, outBoundBidRequest.site); - outBoundBidRequest.site = validateAppendObject(isPlainObject, allowedSiteObjectKeys, siteObject, outBoundBidRequest.site); - }; + return oR; +} - if (siteContentObject && isPlainObject(siteContentObject)) { - const allowedContentStringKeys = ['id', 'title', 'language']; - const allowedContentArrayKeys = ['cat']; - outBoundBidRequest.site.content = validateAppendObject(isStr, allowedContentStringKeys, siteContentObject, outBoundBidRequest.site.content); - outBoundBidRequest.site.content = validateAppendObject(isArray, allowedContentArrayKeys, siteContentObject, outBoundBidRequest.site.content); +function appendImp(bid, oRtb) { + if (!oRtb || !bid) return; + + const type = getType(bid); + const { floor: bidfloor = 0, currency: bidfloorcur = '' } = getBidfloor(bid); + + const impObject = { + id: bid.bidId, + secure: 1, + bidfloor: bid?.params?.bidOverride?.imp?.bidfloor || bidfloor, + bidfloorcur: bid?.params?.bidOverride?.imp?.bidfloorcur || bidfloorcur, + ext: { + dfp_ad_unit_code: bid.adUnitCode, + ...(bid?.ortb2Imp?.ext?.data && + isPlainObject(bid.ortb2Imp.ext.data) && { + data: bid.ortb2Imp.ext.data, + }), + }, + ...(bid?.params?.zid && { tagid: String(bid.params.zid) }), + ...(bid?.ortb2Imp?.instl === 1 && { instl: 1 }), }; - if (userObject && isPlainObject(userObject)) { - const allowedUserStrings = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; - const allowedUserObjects = ['ext']; - outBoundBidRequest.user = validateAppendObject(isStr, allowedUserStrings, userObject, outBoundBidRequest.user); - outBoundBidRequest.user.ext = validateAppendObject(isPlainObject, allowedUserObjects, userObject, outBoundBidRequest.user.ext); - }; + if (type === BANNER) { + impObject.banner = { + mimes: bid.mediaTypes.banner.mimes || [ + 'text/html', + 'text/javascript', + 'application/javascript', + 'image/jpg', + ], + format: getFormat(bid.sizes), + ...(bid.mediaTypes.banner.pos && { pos: bid.mediaTypes.banner.pos }), + }; + } - return outBoundBidRequest; -}; + oRtb.imp.push(impObject); +} -function generateServerRequest({payload, requestOptions, bidderRequest}) { +function createRequest({ data, options, bidderRequest }) { return { - url: (config.getConfig('adtrgtme.endpoint') || ENDPOINT) + (payload.site.id || ''), + url: `${config.getConfig('adtrgtme.endpoint') || BIDDER_URL}${ + data.site?.id || '' + }`, method: 'POST', - data: payload, - options: requestOptions, - bidderRequest: bidderRequest + data, + options, + bidderRequest, }; -}; +} export const spec = { code: BIDDER_CODE, aliases: [], supportedMediaTypes: [BANNER], - isBidRequestValid: function(bid) { + isOK: function (bid) { const params = bid.params; - if (isPlainObject(params) && isNumber(params.sid)) { + if ( + isPlainObject(params) && + isStr(params.sid) && + !isEmpty(params.sid) && + params.sid.length > 0 && + (isEmpty(params.zid) || + isNumber(params.zid) || + (isStr(params.zid) && !isNaN(parseInt(params.zid)))) + ) { return true; } else { - logWarn('Adtrgtme bidder params missing or incorrect'); + logWarn('Adtrgtme request invalid'); return false; } }, - buildRequests: function(validBidRequests, bidderRequest) { - if (isEmpty(validBidRequests) || isEmpty(bidderRequest)) { + buildRequests: function (bR, aR) { + if (isEmpty(bR) || isEmpty(aR)) { logWarn('Adtrgtme Adapter: buildRequests called with empty request'); return undefined; - }; + } - const requestOptions = { + const options = { contentType: 'application/json', - customHeaders: { - 'x-openrtb-version': '2.5' - } + withCredentials: hasPurpose1Consent(aR.gdprConsent), }; - requestOptions.withCredentials = hasPurpose1Consent(bidderRequest.gdprConsent); - if (config.getConfig('adtrgtme.singleRequestMode') === true) { - const payload = generateOpenRtbObject(bidderRequest, validBidRequests[0]); - validBidRequests.forEach(bid => { - appendImpObject(bid, payload); + const data = createORTB(aR, bR[0]); + bR.forEach((bid) => { + appendImp(bid, data); }); - return generateServerRequest({payload, requestOptions, bidderRequest}); + return createRequest({ data, options, bidderRequest: aR }); } - return validBidRequests.map(bid => { - const payloadClone = generateOpenRtbObject(bidderRequest, bid); - appendImpObject(bid, payloadClone); + return bR.map((b) => { + const data = createORTB(aR, b); + appendImp(b, data); - return generateServerRequest({payload: payloadClone, requestOptions, bidderRequest: bid}); + return createRequest({ + data, + options, + bidderRequest: b, + }); }); }, - interpretResponse: function(serverResponse, { data, bidderRequest }) { - const response = []; - if (!serverResponse.body || !Array.isArray(serverResponse.body.seatbid)) { - return response; + interpretResponse: function (sR, { data, bidderRequest }) { + const res = []; + if (!sR.body || !Array.isArray(sR.body.seatbid)) { + return res; } - let seatbids = serverResponse.body.seatbid; - seatbids.forEach(seatbid => { - let bid; - + sR.body.seatbid.forEach((sb) => { try { - bid = seatbid.bid[0]; + let b = sb.bid[0]; + + res.push({ + adId: b?.adId ? b.adId : b.impid || b.crid, + ad: b.adm, + adUnitCode: bidderRequest.adUnitCode, + requestId: b.impid, + cpm: b.price, + width: b.w, + height: b.h, + mediaType: BANNER, + creativeId: b.crid || 0, + currency: b.cur || DEFAULT_CUR, + dealId: b.dealid ? b.dealid : null, + netRevenue: true, + ttl: getTtl(bidderRequest), + meta: { + advertiserDomains: b.adomain || [], + mediaType: BANNER, + }, + }); } catch (e) { - return response; + return res; } - - let cpm = bid.price; - - let bidResponse = { - adId: deepAccess(bid, 'adId') ? bid.adId : bid.impid || bid.crid, - ad: bid.adm, - adUnitCode: bidderRequest.adUnitCode, - requestId: bid.impid, - cpm: cpm, - width: bid.w, - height: bid.h, - creativeId: bid.crid || 0, - currency: bid.cur || DEFAULT_CURRENCY, - dealId: bid.dealid ? bid.dealid : null, - netRevenue: true, - ttl: getTtl(bidderRequest), - mediaType: BANNER, - meta: { - advertiserDomains: bid.adomain, - mediaType: BANNER, - } - }; - - response.push(bidResponse); }); - return response; + return res; }, - - getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const bidResponse = !isEmpty(serverResponses) && serverResponses[0].body; - if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) { - return extractUserSyncUrls(syncOptions, bidResponse.ext.pixels); + getUserSyncs: function (options, res, gdprConsent, uspConsent, gppConsent) { + const s = []; + if (!options.pixelEnabled && !options.iframeEnabled) { + return s; } - return []; - } + if (Array.isArray(res)) { + res.forEach((response) => { + const p = response.body?.ext?.pixels; + if (Array.isArray(p)) { + p.forEach(([stype, url]) => { + const type = stype.toLowerCase(); + if ( + typeof url === 'string' && + url.startsWith('http') && + (((type === 'image' || type === 'img') && options.pixelEnabled) || + (type === 'iframe' && options.iframeEnabled)) + ) { + s.push({ type, url: addConsentParams(url) }); + } + }); + } + }); + } + function addConsentParams(url) { + if (gdprConsent) { + url += `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}&gdpr_consent=${ + encodeURIComponent(gdprConsent.consentString) || '' + }`; + } + if (uspConsent) { + url += `&us_privacy=${encodeURIComponent(uspConsent)}`; + } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + url += `&gpp=${encodeURIComponent( + gppConsent.gppString + )}&gpp_sid=${encodeURIComponent( + gppConsent.applicableSections?.join(',') + )}`; + } + return url; + } + return s; + }, }; registerBidder(spec); diff --git a/modules/adtrgtmeBidAdapter.md b/modules/adtrgtmeBidAdapter.md index d136b17067d..b1a01e2e7b7 100644 --- a/modules/adtrgtmeBidAdapter.md +++ b/modules/adtrgtmeBidAdapter.md @@ -31,18 +31,23 @@ const adUnits = [{ { bidder: 'adtrgtme', params: { - sid: 1220291391, // Site/App ID provided from SSP + sid: '1220291391', // Site/App ID provided from SSP } } ] }]; ``` -# Optional: Price floors module & bidfloor -The adtargerme adapter supports the Prebid.org Price Floors module and will use it to define the outbound bidfloor and currency. +# Optional +## Price floors module & bidfloor +The adapter supports the Prebid.org Price Floors module and will use it to define the outbound bidfloor and currency. By default the adapter will always check the existance of Module price floor. -If a module price floor does not exist you can set a custom bid floor for your impression using "params.bidOverride.imp.bidfloor". +If a module price floor does not exist you can set a custom bid floor for your impression using "params.bidOverride.imp.bidfloor" and "params.bidOverride.imp.bidfloorcur". +## Strict placement identification +It's possible to use params.zid for strict identification for placement id provided from SSP like tagid. + +## Example: ```javascript const adUnits = [{ code: 'your-placement', @@ -56,10 +61,12 @@ const adUnits = [{ bids: [{ bidder: 'adtrgtme', params: { - sid: 1220291391, + sid: '1220291391', + zid: '1836455615', bidOverride :{ imp: { - bidfloor: 5.00 // bidOverride bidfloor + bidfloor: 5.00, // bidOverride bidfloor + bidfloorcur: 'USD' // bidOverride currency } } } diff --git a/test/spec/modules/adtrgtmeBidAdapter_spec.js b/test/spec/modules/adtrgtmeBidAdapter_spec.js index fce270b4ea7..a74844857ce 100644 --- a/test/spec/modules/adtrgtmeBidAdapter_spec.js +++ b/test/spec/modules/adtrgtmeBidAdapter_spec.js @@ -1,249 +1,238 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; -import { BANNER } from 'src/mediaTypes.js'; import { spec } from 'modules/adtrgtmeBidAdapter.js'; -const DEFAULT_SID = 1220291391; -const DEFAULT_ZID = 1836455615; -const DEFAULT_BID_ID = '84ab500420319d'; +const DEFAULT_SID = '1220291391'; +const DEFAULT_ZID = '1836455615'; +const DEFAULT_PIXEL_URL = 'https://cdn.adtarget.me/libs/1x1.gif'; +const DEFAULT_BANNER_URL = 'https://cdn.adtarget.me/libs/banner/300x250.jpg'; +const BIDDER_VERSION = '1.0.5'; +const PREBIDJS_VERSION = '$prebid.version$'; -const DEFAULT_AD_UNIT_CODE = '/1220291391/header-banner'; -const DEFAULT_AD_UNIT_TYPE = BANNER; -const DEFAULT_PARAMS_BID_OVERRIDE = {}; - -const ADAPTER_VERSION = '1.0.0'; -const PREBID_VERSION = '$prebid.version$'; -const INTEGRATION_METHOD = 'prebid.js'; - -// Utility functions -const generateBidRequest = ({bidId, adUnitCode, bidOverrideObject, zid, ortb2}) => { - const bidRequest = { +const createBidRequest = ({bidId, adUnitCode, bidOverride, zid, ortb2}) => { + const bR = { + auctionId: 'f3c594t-3o0ch1b0rm-ayn93c3o0ch1b0rm', adUnitCode, - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId, - bidderRequestsCount: 1, bidder: 'adtrgtme', - bidderRequestId: '7101db09af0db2', - bidderWinsCount: 0, mediaTypes: {}, params: { - bidOverride: bidOverrideObject + sid: DEFAULT_SID, + bidOverride }, - src: 'client', transactionId: '5b17b67d-7704-4732-8cc9-5b1723e9bcf9', ortb2 }; - const bannerObj = { + bR.mediaTypes.banner = { sizes: [[300, 250]] }; + bR.sizes = [[300, 250]]; - bidRequest.mediaTypes.banner = bannerObj; - bidRequest.sizes = [[300, 250]]; - - bidRequest.params.sid = DEFAULT_SID; - if (typeof zid == 'number') { - bidRequest.params.zid = zid; + if (typeof zid == 'string') { + bR.params.zid = zid; } - - return bidRequest; + return bR; } -let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { - const bidderRequest = { - adUnitCode: adUnitCode || 'default-adUnitCode', +let createBidderRequest = (arr, code = 'default-code', ortb2 = {}) => { + return { + adUnitCode: code, auctionId: 'd4c83a3b-18e4-4208-b98b-63848449c7aa', - auctionStart: new Date().getTime(), bidderCode: 'adtrgtme', - bidderRequestId: '112f1c7c5d399a', - bids: bidRequestArray, + bids: arr, refererInfo: { - page: 'https://publisher-test.com', - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: ['https://publisher-test.com'], + page: 'https://partner-site.com', + stack: ['https://partner-site.com'], }, gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', vendorData: {}, gdprApplies: true }, - start: new Date().getTime(), timeout: 1000, ortb2 }; - - return bidderRequest; }; -const generateBuildRequestMock = ({bidId, adUnitCode, adUnitType, zid, bidOverrideObject, pubIdMode, ortb2}) => { - const bidRequestConfig = { - bidId: bidId || DEFAULT_BID_ID, - adUnitCode: adUnitCode || DEFAULT_AD_UNIT_CODE, - adUnitType: adUnitType || DEFAULT_AD_UNIT_TYPE, +const createRequestMock = ({bidId, adUnitCode, type, zid, bidOverride, pubIdMode, ortb2}) => { + const bR = createBidRequest({ + bidId: bidId || '84ab500420319d', + adUnitCode: adUnitCode || '/1220291391/banner', + type: type || 'banner', zid: zid || DEFAULT_ZID, - bidOverrideObject: bidOverrideObject || DEFAULT_PARAMS_BID_OVERRIDE, - + bidOverride: bidOverride || {}, pubIdMode: pubIdMode || false, ortb2: ortb2 || {} - }; - const bidRequest = generateBidRequest(bidRequestConfig); - const validBidRequests = [bidRequest]; - const bidderRequest = generateBidderRequest(validBidRequests, adUnitCode, ortb2); - - return { bidRequest, validBidRequests, bidderRequest } + }); + return { bidRequest: bR, validBR: [bR], bidderRequest: createBidderRequest([bR], adUnitCode, ortb2) } }; -const generateAdmPayload = (admPayloadType) => { - let ADM_PAYLOAD; - switch (admPayloadType) { +const createAdm = (type) => { + let ADM; + switch (type) { case 'banner': - ADM_PAYLOAD = ''; // banner + ADM = ` + `; break; - default: ''; break; + default: 'Ad is here'; break; }; - - return ADM_PAYLOAD; + return ADM; }; -const generateResponseMock = (admPayloadType) => { - const bidResponse = { - id: 'fc0c35df-21fb-4f93-9ebd-88759dbe31f9', - impid: '274395c06a24e5', - adm: generateAdmPayload(admPayloadType), - price: 1, - w: 300, - h: 250, - crid: 'ssp-placement-name', - adomain: ['advertiser-domain.com'] - }; - - const serverResponse = { +const createResponseMock = (type) => { + const sR = { body: { - id: 'fc0c35df-21fb-4f93-9ebd-88759dbe31f9', - seatbid: [{ bid: [ bidResponse ], seat: 13107 }] + id: '5qtvluj7bk6jhzmqwu4zzulv', + seatbid: [{ + bid: [{ + id: '5qtvluj7bk6jhzmqwu4zzulv', + impid: 'y7v7iu0uljj94rbjcw9', + adm: createAdm(type), + price: 1, + w: 300, + h: 250, + crid: 'creativeid', + adomain: ['some-advertiser-domain.com'] + }], + seat: 12345 + }] } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: admPayloadType}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const { validBR, bidderRequest } = createRequestMock({type}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; - return {serverResponse, data, bidderRequest}; + return {sR, data, bidderRequest}; } -// Unit tests -describe('adtrgtme Bid Adapter:', () => { +describe('Adtrgtme Bid Adapter:', () => { it('PLACEHOLDER TO PASS GULP', () => { - const obj = {}; - expect(obj).to.be.an('object'); + expect({}).to.be.an('object'); }); - describe('Validate basic properties', () => { - it('should define the correct bidder code', () => { + describe('check basic properties', () => { + it('should define bidder code', () => { expect(spec.code).to.equal('adtrgtme') }); }); - describe('getUserSyncs()', () => { - const IMAGE_PIXEL_URL = 'http://image-pixel.com/foo/bar?1234&baz=true'; - const IFRAME_ONE_URL = 'http://image-iframe.com/foo/bar?1234&baz=true'; - const IFRAME_TWO_URL = 'http://image-iframe-two.com/foo/bar?1234&baz=true'; + describe('getUserSyncs', () => { + const BAD_SYNC_URL = 'cdn.adtarget.me/libs/1x1.gif?image&rnd=5fr55r'; + const IMAGE_SYNC_URL = `${DEFAULT_PIXEL_URL}?image&rnd=5fr55r`; + const IFRAME_SYNC_ONE_URL = `${DEFAULT_PIXEL_URL}?iframe1&rnd=5fr55r`; + const IFRAME_SYNC_TWO_URL = `${DEFAULT_PIXEL_URL}?iframe2&rnd=5fr55r`; - let serverResponses = []; + let sRs = []; beforeEach(() => { - serverResponses[0] = { + sRs[0] = { body: { ext: { - pixels: `` + pixels: [ + ['image', BAD_SYNC_URL], + ['invalid', IMAGE_SYNC_URL], + ['image', IMAGE_SYNC_URL], + ['iframe', IFRAME_SYNC_ONE_URL], + ['iframe', IFRAME_SYNC_TWO_URL] + ] } } } }); after(() => { - serverResponses = undefined; + sRs = undefined; }); - it('for only iframe enabled syncs', () => { - let syncOptions = { + it('sync check bad url and type in pixels', () => { + let opt = { + iframeEnabled: true, + pixelEnabled: true + }; + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(3); + }); + + it('sync check for iframe only', () => { + let opt = { iframeEnabled: true, pixelEnabled: false }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(2); - expect(pixelsObjects).to.deep.equal( + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(2); + expect(pixels).to.deep.equal( [ - {type: 'iframe', 'url': IFRAME_ONE_URL}, - {type: 'iframe', 'url': IFRAME_TWO_URL} + {type: 'iframe', 'url': IFRAME_SYNC_ONE_URL}, + {type: 'iframe', 'url': IFRAME_SYNC_TWO_URL} ] ) }); - it('for only pixel enabled syncs', () => { - let syncOptions = { + it('sync check for image only', () => { + let opt = { iframeEnabled: false, pixelEnabled: true }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(1); - expect(pixelsObjects).to.deep.equal( + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(1); + expect(pixels).to.deep.equal( [ - {type: 'image', 'url': IMAGE_PIXEL_URL} + {type: 'image', 'url': IMAGE_SYNC_URL} ] ) }); - it('for both pixel and iframe enabled syncs', () => { - let syncOptions = { + it('Sync for iframe and image', () => { + let opt = { iframeEnabled: true, pixelEnabled: true }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(3); - expect(pixelsObjects).to.deep.equal( + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(3); + expect(pixels).to.deep.equal( [ - {type: 'iframe', 'url': IFRAME_ONE_URL}, - {type: 'image', 'url': IMAGE_PIXEL_URL}, - {type: 'iframe', 'url': IFRAME_TWO_URL} + {type: 'image', 'url': IMAGE_SYNC_URL}, + {type: 'iframe', 'url': IFRAME_SYNC_ONE_URL}, + {type: 'iframe', 'url': IFRAME_SYNC_TWO_URL} ] ) }); }); - // Validate Bid Requests - describe('isBidRequestValid()', () => { - const INVALID_INPUT = [ + describe('Check if bid request is OK', () => { + const BAD_VALUE = [ {}, {params: {}}, - {params: {sid: '1234', zid: '4321'}}, - {params: {sid: '1220291391', zid: 4321}}, + {params: {sid: 1220291391, zid: '1836455615'}}, + {params: {sid: '1220291391', zid: 'A'}}, + {params: {sid: '', zid: '1836455615'}}, + {params: {sid: '', zid: 'A'}}, {params: {zid: ''}}, - {params: {sid: '', zid: ''}}, ]; - INVALID_INPUT.forEach(input => { - it(`should determine that the bid is INVALID for the input ${JSON.stringify(input)}`, () => { - expect(spec.isBidRequestValid(input)).to.be.false; + BAD_VALUE.forEach(value => { + it(`should determine bad bid for ${JSON.stringify(value)}`, () => { + expect(spec.isOK(value)).to.be.false; }); }); - it('should determine that the bid is VALID if sid and zid are present on the params object', () => { - const validBid = { - params: { - sid: 1220291391, - zid: 1836455615 - } - }; - expect(spec.isBidRequestValid(validBid)).to.be.true; + const OK_VALUE = [ + {params: {sid: '1220291391'}}, + {params: {sid: '1220291391', zid: 1836455615}}, + {params: {sid: '1220291391', zid: '1836455615'}}, + {params: {sid: '1220291391', zid: '1836455615A'}}, + ]; + + OK_VALUE.forEach(value => { + it(`should determine OK bid for ${JSON.stringify(value)}`, () => { + expect(spec.isOK(value)).to.be.true; + }); }); }); - describe('Price Floor module support:', () => { + describe('Bidfloor support:', () => { it('should get bidfloor from getFloor method', () => { - const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); - bidRequest.params.bidOverride = {cur: 'EUR'}; + const { bidRequest, validBR, bidderRequest } = createRequestMock({}); + bidRequest.params.bidOverride = {cur: 'AUD'}; bidRequest.getFloor = (floorObj) => { return { floor: bidRequest.floors.values[floorObj.mediaType + '|300x250'], @@ -252,203 +241,167 @@ describe('adtrgtme Bid Adapter:', () => { } }; bidRequest.floors = { - currency: 'EUR', + currency: 'AUD', values: { - 'banner|300x250': 5.55 + 'banner|300x250': 1.111 } }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.cur).to.deep.equal(['EUR']); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.cur).to.deep.equal(['AUD']); expect(data.imp[0].bidfloor).is.a('number'); - expect(data.imp[0].bidfloor).to.equal(5.55); + expect(data.imp[0].bidfloor).to.equal(1.111); }); }); - describe('Schain module support:', () => { - it('should send Global or Bidder specific schain', function () { - const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + describe('Schain support:', () => { + it('should send schains', function () { + const { bidRequest, validBR, bidderRequest } = createRequestMock({}); const globalSchain = { ver: '1.0', complete: 1, nodes: [{ - asi: 'some-platform.com', - sid: '111111', + asi: 'adtarget-partner.com', + sid: '1234567890', rid: bidRequest.bidId, hp: 1 }] }; bidRequest.schain = globalSchain; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const data = spec.buildRequests(validBR, bidderRequest)[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); expect(schain).to.equal(globalSchain); }); }); - describe('First party data module - "Site" support (ortb2):', () => { - // Should not allow invalid "site" data types - const INVALID_ORTB2_TYPES = [ null, [], 123, 'unsupportedKeyName', true, false, undefined ]; - INVALID_ORTB2_TYPES.forEach(param => { - it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { - const ortb2 = { site: param } - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site[param]).to.be.undefined; + describe('Check Site obj support (ortb2):', () => { + const BAD_ORTB2_TYPES = [ null, [], 123, 'invalidID', true, false, undefined ]; + BAD_ORTB2_TYPES.forEach(key => { + it(`should remove bad site data: ${JSON.stringify(key)}`, () => { + const ortb2 = { site: key } + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site[key]).to.be.undefined; }); }); - // Should add valid "site" params - const VALID_SITE_STRINGS = ['name', 'domain', 'page', 'ref', 'keywords']; - const VALID_SITE_ARRAYS = ['cat', 'sectioncat', 'pagecat']; + const OK_SITE_STR = ['id', 'name', 'domain', 'page', 'ref', 'keywords']; + const OK_SITE_ARR = ['cat', 'sectioncat', 'pagecat']; - VALID_SITE_STRINGS.forEach(param => { - it(`should allow supported site keys to be added bid-request: ${JSON.stringify(param)}`, () => { + OK_SITE_STR.forEach(key => { + it(`should allow supported site keys to be added bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { - [param]: 'something' + [key]: 'some value here' } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site[param]).to.exist; - expect(data.site[param]).to.be.a('string'); - expect(data.site[param]).to.be.equal(ortb2.site[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site[key]).to.exist; + expect(data.site[key]).to.be.a('string'); + expect(data.site[key]).to.be.equal(ortb2.site[key]); }); }); - VALID_SITE_ARRAYS.forEach(param => { - it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + OK_SITE_ARR.forEach(key => { + it(`should determine valid keys of the ortb2 site and append to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { - [param]: ['something'] + [key]: ['some value here'] } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site[param]).to.exist; - expect(data.site[param]).to.be.a('array'); - expect(data.site[param]).to.be.equal(ortb2.site[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site[key]).to.exist; + expect(data.site[key]).to.be.a('array'); + expect(data.site[key]).to.be.equal(ortb2.site[key]); }); }); - // Should not allow invalid "site.content" data types - INVALID_ORTB2_TYPES.forEach(param => { - it(`should determine that the ortb2.site.content key is invalid and should not be added to bid-request: ${JSON.stringify(param)}`, () => { - const ortb2 = { - site: { - content: param - } - }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content).to.be.undefined; - }); - }); - - // Should not allow invalid "site.content" keys - it(`should not allow invalid ortb2.site.content object keys to be added to bid-request: {custom object}`, () => { - const ortb2 = { - site: { - content: { - fake: 'news', - unreal: 'param', - counterfit: 'data' - } - } - }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content).to.be.a('object'); - }); - - // Should append valid "site.content" keys - const VALID_CONTENT_STRINGS = ['id', 'title', 'language']; - VALID_CONTENT_STRINGS.forEach(param => { - it(`should determine that the ortb2.site String key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const OK_CONTENT_STR = ['id', 'title', 'language']; + OK_CONTENT_STR.forEach(key => { + it(`should determine that the ortb2.site String key is ok and append to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { content: { - [param]: 'something' + [key]: 'some value here' } } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content[param]).to.exist; - expect(data.site.content[param]).to.be.a('string'); - expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site.content[key]).to.exist; + expect(data.site.content[key]).to.be.a('string'); + expect(data.site.content[key]).to.be.equal(ortb2.site.content[key]); }); }); - const VALID_CONTENT_ARRAYS = ['cat']; - VALID_CONTENT_ARRAYS.forEach(param => { - it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const OK_CONTENT_ARR = ['cat']; + OK_CONTENT_ARR.forEach(key => { + it(`should determine that the ortb2.site key is ok and append to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { content: { - [param]: ['something', 'something-else'] + [key]: ['some value here', 'something-else'] } } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content[param]).to.be.a('array'); - expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site.content[key]).to.be.a('array'); + expect(data.site.content[key]).to.be.equal(ortb2.site.content[key]); }); }); }); - describe('First party data module - "User" support (ortb2):', () => { - // Global ortb2.user validations - // Should not allow invalid "user" data types - const INVALID_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; - INVALID_ORTB2_TYPES.forEach(param => { - it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { - const ortb2 = { user: param } - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.be.undefined; + describe('Check ortb2 user support:', () => { + const BAD_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; + BAD_ORTB2_TYPES.forEach(key => { + it(`should not allow bad site types to be added to bid request: ${JSON.stringify(key)}`, () => { + const ortb2 = { user: key } + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.user[key]).to.be.undefined; }); }); - // Should add valid "user" params - const VALID_USER_STRINGS = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; - VALID_USER_STRINGS.forEach(param => { - it(`should allow supported user string keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const OK_USER_STR = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; + OK_USER_STR.forEach(key => { + it(`should allow valid keys of the user to be added to bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { user: { - [param]: 'something' + [key]: 'some value here' } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.exist; - expect(data.user[param]).to.be.a('string'); - expect(data.user[param]).to.be.equal(ortb2.user[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.user[key]).to.exist; + expect(data.user[key]).to.be.a('string'); + expect(data.user[key]).to.be.equal(ortb2.user[key]); }); }); - const VALID_USER_OBJECTS = ['ext']; - VALID_USER_OBJECTS.forEach(param => { - it(`should allow supported user extObject keys to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const OK_USER_OBJECTS = ['ext']; + OK_USER_OBJECTS.forEach(key => { + it(`should allow user ext to be added to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { user: { - [param]: {a: '123', b: '456'} + [key]: {a: '123', b: '456'} } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.exist; - expect(data.user[param]).to.be.a('object'); - expect(data.user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.user[key]).to.exist; + expect(data.user[key]).to.be.a('object'); + expect(data.user[key].a).to.be.equal('123'); + expect(data.user[key].b).to.be.equal('456'); config.setConfig({ortb2: {}}); }); }); - // adUnit.ortb2Imp.ext.data - it(`should allow adUnit.ortb2Imp.ext.data object to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should allow adUnit.ortb2Imp.ext.data object to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { ext: { data: { pbadslot: 'homepage-top-rect', @@ -456,43 +409,42 @@ describe('adtrgtme Bid Adapter:', () => { } } }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].ext.data).to.deep.equal(validBidRequests[0].ortb2Imp.ext.data); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.imp[0].ext.data).to.deep.equal(validBR[0].ortb2Imp.ext.data); }); - // adUnit.ortb2Imp.instl - it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { instl: 1 }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].instl).to.deep.equal(validBidRequests[0].ortb2Imp.instl); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.imp[0].instl).to.deep.equal(validBR[0].ortb2Imp.instl); }); - it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { instl: true }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].instl).to.not.exist; }); - it(`should prevent adUnit.ortb2Imp.instl boolean "false" to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should prevent adUnit.ortb2Imp.instl boolean false to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { instl: false }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].instl).to.not.exist; }); }); - describe('GDPR & Consent:', () => { + describe('GDPR:', () => { it('should return request objects that do not send cookies if purpose 1 consent is not provided', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBR, bidderRequest } = createRequestMock({}); bidderRequest.gdprConsent = { - consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + consentString: 'BOtmiBKO234234tmiBKABABAEN234AFAAAAACeAAA', apiVersion: 2, vendorData: { purpose: { @@ -503,22 +455,22 @@ describe('adtrgtme Bid Adapter:', () => { }, gdprApplies: true }; - const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; - expect(options.withCredentials).to.be.false; + const opt = spec.buildRequests(validBR, bidderRequest)[0].options; + expect(opt.withCredentials).to.be.false; }); }); - describe('Endpoint & Impression Request Mode:', () => { + describe('Endpoint & Impression request mode:', () => { it('should route request to config override endpoint', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const sid = validBidRequests[0].params.sid; - const testOverrideEndpoint = 'http://new_bidder_host.com/ssp?s='; + const { validBR, bidderRequest } = createRequestMock({}); + const sid = validBR[0].params.sid; + const testOverrideEndpoint = 'http://partner-adserv-domain.com/ssp?s='; config.setConfig({ adtrgtme: { endpoint: testOverrideEndpoint } }); - const response = spec.buildRequests(validBidRequests, bidderRequest)[0]; + const response = spec.buildRequests(validBR, bidderRequest)[0]; expect(response).to.deep.include( { method: 'POST', @@ -530,9 +482,9 @@ describe('adtrgtme Bid Adapter:', () => { config.setConfig({ adtrgtme: {} }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const sid = validBidRequests[0].params.sid; - const response = spec.buildRequests(validBidRequests, bidderRequest); + const { validBR, bidderRequest } = createRequestMock({}); + const sid = validBR[0].params.sid; + const response = spec.buildRequests(validBR, bidderRequest); expect(response[0]).to.deep.include({ method: 'POST', url: 'https://z.cdn.adtarget.market/ssp?prebid&s=' + sid @@ -540,13 +492,10 @@ describe('adtrgtme Bid Adapter:', () => { }); it('should return a single request object for single request mode', () => { - let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const BID_ID_2 = '84ab50xxxxx'; - const BID_ZID_2 = 98876543210; - const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; - const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, zid: BID_ZID_2, adUnitCode: AD_UNIT_CODE_2}); - validBidRequests = [bidRequest, bidRequest2]; - bidderRequest.bids = validBidRequests; + let { bidRequest, validBR, bidderRequest } = createRequestMock({}); + const { bidRequest: mock } = createRequestMock({bidId: '6heos7ks8z0j', zid: '98876543210', adUnitCode: 'bidder-code'}); + validBR = [bidRequest, mock]; + bidderRequest.bids = validBR; config.setConfig({ adtrgtme: { @@ -554,60 +503,57 @@ describe('adtrgtme Bid Adapter:', () => { } }); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.imp).to.be.an('array').with.lengthOf(2); expect(data.imp[0]).to.deep.include({ - id: DEFAULT_BID_ID, + id: '84ab500420319d', ext: { - dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + dfp_ad_unit_code: '/1220291391/banner' } }); expect(data.imp[1]).to.deep.include({ - id: BID_ID_2, - tagid: BID_ZID_2, + id: '6heos7ks8z0j', + tagid: '98876543210', ext: { - dfp_ad_unit_code: AD_UNIT_CODE_2 + dfp_ad_unit_code: 'bidder-code' } }); }); }); - describe('Validate request filtering:', () => { - it('should not return request when no bids are present', function () { + describe('validate request filtering:', () => { + it('should return undefined when no bids', function () { let request = spec.buildRequests([]); expect(request).to.be.undefined; }); - it('buildRequests(): should return an array with the correct amount of request objects', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const response = spec.buildRequests(validBidRequests, bidderRequest).bidderRequest; + it('buildRequests should return correct amount of objects', () => { + const { validBR, bidderRequest } = createRequestMock({}); + const response = spec.buildRequests(validBR, bidderRequest).bidderRequest; expect(response.bids).to.be.an('array').to.have.lengthOf(1); }); }); - describe('Request Headers validation:', () => { - it('should return request objects with the relevant custom headers and content type declaration', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + describe('Validate request headers:', () => { + it('should return request objects with the custom headers and content type', () => { + const { validBR, bidderRequest } = createRequestMock({}); bidderRequest.gdprConsent.gdprApplies = false; - const options = spec.buildRequests(validBidRequests, bidderRequest).options; - expect(options).to.deep.equal( + const opt = spec.buildRequests(validBR, bidderRequest).options; + expect(opt).to.deep.equal( { contentType: 'application/json', - customHeaders: { - 'x-openrtb-version': '2.5' - }, withCredentials: true }); }); }); - describe('Request Payload oRTB bid validation:', () => { - it('should generate a valid openRTB bid-request object in the data field', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + describe('Request data oRTB bid validation:', () => { + it('should create valid oRTB bid request object in the data field', () => { + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.site).to.deep.include({ id: bidderRequest.bids[0].params.sid, page: bidderRequest.refererInfo.page @@ -629,12 +575,8 @@ describe('adtrgtme Bid Adapter:', () => { expect(data.source).to.deep.equal({ ext: { hb: 1, - adapterver: ADAPTER_VERSION, - prebidver: PREBID_VERSION, - integration: { - name: INTEGRATION_METHOD, - ver: PREBID_VERSION - } + bidderver: BIDDER_VERSION, + prebidjsver: PREBIDJS_VERSION }, fd: 1 }); @@ -642,52 +584,49 @@ describe('adtrgtme Bid Adapter:', () => { expect(data.cur).to.deep.equal(['USD']); }); - it('should generate a valid openRTB imp.ext object in the bid-request', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const bid = validBidRequests[0]; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + it('should create valid oRTB imp.ext in the bid request', () => { + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.imp[0].ext).to.deep.equal({ - dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + dfp_ad_unit_code: '/1220291391/banner' }); }); - it('should use siteId value as site.id in the outbound bid-request when using "pubId" integration mode', () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); - validBidRequests[0].params.sid = 9876543210; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; - expect(data.site.id).to.equal(9876543210); + it('should use siteId value as site.id', () => { + let { validBR, bidderRequest } = createRequestMock({pubIdMode: true}); + validBR[0].params.sid = '9876543210'; + const data = spec.buildRequests(validBR, bidderRequest).data; + expect(data.site.id).to.equal('9876543210'); }); - it('should use placementId value as imp.tagid in the outbound bid-request when using "zid"', () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}), - TEST_ZID = 54321; - validBidRequests[0].params.zid = TEST_ZID; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + it('should use placementId value as imp.tagid when using "zid"', () => { + let { validBR, bidderRequest } = createRequestMock({}), + TEST_ZID = '54321'; + validBR[0].params.zid = TEST_ZID; + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.imp[0].tagid).to.deep.equal(TEST_ZID); }); }); - describe('Request Payload oRTB bid.imp validation:', () => { - // Validate Banner imp imp when adtrgtme.mode=undefined - it('should generate a valid "Banner" imp object', () => { + describe('Request oRTB bid.imp validation:', () => { + it('should create valid default Banner imp', () => { config.setConfig({ adtrgtme: {} }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].banner).to.deep.equal({ mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], format: [{w: 300, h: 250}] }); }); - // Validate Banner imp - it('should generate a valid "Banner" imp object', () => { + it('should create valid Banner imp', () => { config.setConfig({ adtrgtme: { mode: 'banner' } }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].banner).to.deep.equal({ mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], format: [{w: 300, h: 250}] @@ -697,104 +636,105 @@ describe('adtrgtme Bid Adapter:', () => { describe('interpretResponse()', () => { describe('for mediaTypes: "banner"', () => { - it('should insert banner payload into response[0].ad', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ad).to.equal(''); + it('should insert banner object into response[0].ad', () => { + const { sR, bidderRequest } = createResponseMock('banner'); + const response = spec.interpretResponse(sR, {bidderRequest}); + expect(response[0].ad).to.equal(` + `); expect(response[0].mediaType).to.equal('banner'); }) }); - describe('Support Advertiser domains', () => { + describe('Support adomains', () => { it('should append bid-response adomain to meta.advertiserDomains', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const { sR, bidderRequest } = createResponseMock('banner'); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].meta.advertiserDomains).to.be.a('array'); - expect(response[0].meta.advertiserDomains[0]).to.equal('advertiser-domain.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('some-advertiser-domain.com'); }) }); - describe('bid response Ad ID / Creative ID', () => { + describe('Check response Ad ID / Creative ID', () => { it('should use adId if it exists in the bid-response', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const { sR, bidderRequest } = createResponseMock('banner'); const adId = 'bid-response-adId'; - serverResponse.body.seatbid[0].bid[0].adId = adId; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + sR.body.seatbid[0].bid[0].adId = adId; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].adId).to.equal(adId); }); it('should use impid if adId does not exist in the bid-response', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const impid = '25b6c429c1f52f'; - serverResponse.body.seatbid[0].bid[0].impid = impid; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const { sR, bidderRequest } = createResponseMock('banner'); + const impid = 'y7v7iu0uljj94rbjcw9'; + sR.body.seatbid[0].bid[0].impid = impid; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].adId).to.equal(impid); }); it('should use crid if adId & impid do not exist in the bid-response', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const { sR, bidderRequest } = createResponseMock('banner'); const crid = 'passback-12579'; - serverResponse.body.seatbid[0].bid[0].impid = undefined; - serverResponse.body.seatbid[0].bid[0].crid = crid; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + sR.body.seatbid[0].bid[0].impid = undefined; + sR.body.seatbid[0].bid[0].crid = crid; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].adId).to.equal(crid); }); }); describe('Time To Live (ttl)', () => { - const UNSUPPORTED_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; - UNSUPPORTED_TTL_FORMATS.forEach(param => { + const BAD_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; + BAD_TTL_FORMATS.forEach(key => { it('should not allow unsupported global adtrgtme.ttl formats and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const { sR, bidderRequest } = createResponseMock('banner'); config.setConfig({ - adtrgtme: { ttl: param } + adtrgtme: { ttl: key } }); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); - it('should not allow unsupported params.ttl formats and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - bidderRequest.bids[0].params.ttl = param; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + it('should not set unsupported ttl formats and check default to 300', () => { + const { sR, bidderRequest } = createResponseMock('banner'); + bidderRequest.bids[0].params.ttl = key; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); }); - const UNSUPPORTED_TTL_VALUES = [-1, 3601]; - UNSUPPORTED_TTL_VALUES.forEach(param => { - it('should not allow invalid global adtrgtme.ttl values 3600 < ttl < 0 and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const BAD_TTL_VALUES = [-1, 12345]; + BAD_TTL_VALUES.forEach(key => { + it('should not set bad global adtrgtme.ttl and check default to 300', () => { + const { sR, bidderRequest } = createResponseMock('banner'); config.setConfig({ - adtrgtme: { ttl: param } + adtrgtme: { ttl: key } }); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); - it('should not allow invalid params.ttl values 3600 < ttl < 0 and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - bidderRequest.bids[0].params.ttl = param; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + it('should not set bad keys.ttl values', () => { + const { sR, bidderRequest } = createResponseMock('banner'); + bidderRequest.bids[0].params.ttl = key; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); }); - it('should give presedence to Gloabl ttl over params.ttl ', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + it('should set gloabl ttl over params.ttl if it presents', () => { + const { sR, bidderRequest } = createResponseMock('banner'); config.setConfig({ adtrgtme: { ttl: 500 } }); bidderRequest.bids[0].params.ttl = 400; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(500); }); }); - describe('Aliasing support', () => { + describe('Alias support', () => { it('should return undefined as the bidder code value', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const { sR, bidderRequest } = createResponseMock('banner'); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].bidderCode).to.be.undefined; }); }); From e16153eed8e480890149c54853fbff7881ad83ff Mon Sep 17 00:00:00 2001 From: MadSense Ops Date: Sat, 4 Jan 2025 10:33:51 +0100 Subject: [PATCH 0810/1097] MadSense Bid Adapter : initial release (#12546) * Submit madSense bidder adapter * madSense Bid Adapter: Prefer banner, use optional chaining - Preferred banner over instream for non-instream contexts. - Replaced all instances of deepAccess with optional chaining. * Use mtype exclusively for media type determination - Simplified determineMediaType function to rely solely on `mtype` provided by the exchange. - Removed redundant checks for hasVideoMediaType and hasBannerMediaType to streamline logic. --- modules/madsenseBidAdapter.js | 264 +++++++++++++++++++ modules/madsenseBidAdapter.md | 124 +++++++++ test/spec/modules/madsenseBidAdapter_spec.js | 188 +++++++++++++ 3 files changed, 576 insertions(+) create mode 100644 modules/madsenseBidAdapter.js create mode 100644 modules/madsenseBidAdapter.md create mode 100644 test/spec/modules/madsenseBidAdapter_spec.js diff --git a/modules/madsenseBidAdapter.js b/modules/madsenseBidAdapter.js new file mode 100644 index 00000000000..ba866a9314e --- /dev/null +++ b/modules/madsenseBidAdapter.js @@ -0,0 +1,264 @@ +import { + logError, + logWarn, + logMessage, + deepSetValue, + mergeDeep, +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'madsense'; +const DEFAULT_BID_TTL = 55; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_CURRENCY = 'USD'; +const MS_EXCHANGE_BASE_URL = 'https://ads.madsense.io/pbjs'; + +const buildImpWithDefaults = (buildImp, bidRequest, context) => { + const imp = buildImp(bidRequest, context); + + imp.bidfloor = imp.bidfloor || bidRequest.params.bidfloor || 0; + imp.bidfloorcur = imp.bidfloorcur || bidRequest.params.currency || DEFAULT_CURRENCY; + + return imp; +}; + +const enrichRequestWithConsent = (req, bidderRequest) => { + if (bidderRequest.gdprConsent) { + deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + + if (bidderRequest.uspConsent) { + deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } +}; + +const determineMediaType = (bid) => { + if (bid.mtype === 2) { + return VIDEO; + } + + if (bid.mtype === 1) { + return BANNER; + } + + logWarn('Unrecognized media type, defaulting to BANNER (madSense)'); + return BANNER; +}; + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + }, + imp: (buildImp, bidRequest, context) => buildImpWithDefaults(buildImp, bidRequest, context), + request: (buildRequest, imps, bidderRequest, context) => { + const req = buildRequest(imps, bidderRequest, context); + + mergeDeep(req, { + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: '1.0.0', + }, + }); + + enrichRequestWithConsent(req, bidderRequest); + return req; + }, + bidResponse: (buildBidResponse, bid, context) => { + const resMediaType = determineMediaType(bid); + + Object.assign(context, { + mediaType: resMediaType, + currency: DEFAULT_CURRENCY, + ...(resMediaType === VIDEO && { vastXml: bid.adm }), + }); + + return buildBidResponse(bid, context); + }, +}); + +export const spec = { + code: BIDDER_CODE, + VERSION: '1.0.0', + supportedMediaTypes: [BANNER, VIDEO], + ENDPOINT: MS_EXCHANGE_BASE_URL, + + isBidRequestValid: function (bid) { + return validateBidRequest(bid); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const contextMediaType = determineMediaType(validBidRequests[0]); + const data = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context: { contextMediaType }, + }); + + const companyId = getCompanyId(validBidRequests); + const madsenseExchangeEndpointUrl = buildEndpointUrl(companyId); + + return { + method: 'POST', + url: madsenseExchangeEndpointUrl, + data: data, + }; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bids = parseServerResponse(serverResponse, bidRequest); + return filterValidBids(bids); + }, +}; + +function validateBidRequest(bid) { + return ( + _validateParams(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); +} + +function getCompanyId(validBidRequests) { + let companyId = validBidRequests[0].params.company_id; + + if (validBidRequests[0].params.test) { + logMessage('madsense: test mode'); + companyId = 'test'; + } + + return companyId; +} + +function buildEndpointUrl(companyId) { + return `${spec.ENDPOINT}?company_id=${companyId}`; +} + +function parseServerResponse(serverResponse, bidRequest) { + return converter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; +} + +function filterValidBids(bids) { + return bids + .map((bid) => { + if (bid.mtype === 2 && bid.adm) { + if (!config.getConfig('cache.url')) { + bid.vastXml = bid.adm; + delete bid.adm; + } else { + logError('Prebid Cache is not configured (madSense)'); + return null; + } + } + return bid; + }) + .filter((bid) => bid !== null); +} + +function hasBannerMediaType(bidRequest) { + return !!bidRequest.mediaTypes?.banner; +} + +function hasVideoMediaType(bidRequest) { + return !!bidRequest.mediaTypes?.video; +} + +function _validateParams(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (bidRequest.params.test) { + return true; + } + + if (!bidRequest.params.company_id) { + logError('company_id not declared (madSense)'); + return false; + } + + const mediaTypesExists = hasVideoMediaType(bidRequest) || hasBannerMediaType(bidRequest); + if (!mediaTypesExists) { + return false; + } + + return true; +} + +function _validateBanner(bidRequest) { + if (!hasBannerMediaType(bidRequest)) { + return true; + } + + const bannerSizes = bidRequest.mediaTypes?.banner?.sizes; + + if (!Array.isArray(bannerSizes)) { + return false; + } + + return true; +} + +function _validateVideo(bidRequest) { + if (!hasVideoMediaType(bidRequest)) { + return true; + } + + const videoPlacement = bidRequest.mediaTypes?.video || {}; + const videoBidderParams = bidRequest.params?.video || {}; + const params = bidRequest.params || {}; + + if (params && params.test) { + return true; + } + + const videoParams = { + ...videoPlacement, + ...videoBidderParams, + }; + + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + logWarn('Invalid MIME types (madSense)'); + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + logWarn('Invalid protocols (madSense)'); + return false; + } + + if (!videoParams.context) { + logWarn('Context not declared (madSense)'); + return false; + } + + if (videoParams.context !== 'instream') { + if (hasBannerMediaType(bidRequest)) { + logWarn('Context is not instream, preferring banner (madSense)'); + return true; + } else { + logWarn('Only instream context is supported (madSense)'); + } + } + + if ( + typeof videoParams.playerSize === 'undefined' || + !Array.isArray(videoParams.playerSize) || + !Array.isArray(videoParams.playerSize[0]) + ) { + logWarn('Player size not declared or not in [[w,h]] format'); + return false; + } + + return true; +} + +registerBidder(spec); diff --git a/modules/madsenseBidAdapter.md b/modules/madsenseBidAdapter.md new file mode 100644 index 00000000000..aacfbbafacb --- /dev/null +++ b/modules/madsenseBidAdapter.md @@ -0,0 +1,124 @@ +# Overview +``` +Module Name: madSense Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@madsense.io +``` + +# Description + +- A module that integrates with madSense's demand sources. +- The madSense bid adapter supports both Banner and Video formats. + + +### Test Parameters + +#### Banner + +``` +var adUnits = [ + { + code: 'adUnitBanner_div_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'madsense', + params: { + company_id: '1234567', + bidfloor: 2.7, + } + }] + } +]; +``` + +#### Video + +We support the following OpenRTB parameters, which can be defined in `mediaTypes.video` or `bids[].params.video`. +- `mimes`, `minduration`, `maxduration`, `plcmt`, `protocols`, `startdelay`, `skip`, `skipafter`, `minbitrate`, `maxbitrate`, `delivery`, `playbackmethod`, `api`, `linearity` + + +##### Instream Video Ad Unit with mediaTypes.video +- Note: The adapter, by default, will retrieve the required parameters from mediaTypes.video. +- Note: The Video SSP ad server will return a VAST XML, which can be loaded into your specified player. +``` + var adUnits = [ + { + code: 'adUnitVideo_1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + plcmt: 1, + playbackmethod: [1,5], + } + }, + bids: [ + { + bidder: 'madsense', + params: { + company_id: '1234567' + } + } + ] + } + ] +``` + +## End To End Testing Mode +By setting `bid.params.test = true`, you can receive a test creative. + +#### Banner +``` +var adUnits = [ + { + code: 'adUnitBanner_div_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'madsense', + params: { + test: true + } + }] + } +]; +``` + +#### Video +``` +var adUnits = [ + { + code: 'adUnitVideo_1', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2,5], + } + }, + bids: [ + { + bidder: 'madsense', + params: { + test: true + } + } + ] + } +] +``` diff --git a/test/spec/modules/madsenseBidAdapter_spec.js b/test/spec/modules/madsenseBidAdapter_spec.js new file mode 100644 index 00000000000..eeb8c2d6f25 --- /dev/null +++ b/test/spec/modules/madsenseBidAdapter_spec.js @@ -0,0 +1,188 @@ +import { expect } from 'chai'; +import { spec } from 'modules/madsenseBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { generateUUID } from '../../../src/utils.js'; + +const getCommonParams = () => ({ + company_id: '1234567' +}); + +const getVideoParams = () => ({ + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 123, + }, + site: { + id: 1, + page: 'https://test.io', + referrer: 'http://test.io', + }, +}); + +const getBannerRequest = () => ({ + bidderCode: 'madsense', + auctionId: generateUUID(), + bidderRequestId: 'bidderRequestId', + bids: [ + { + bidder: 'madsense', + params: getCommonParams(), + auctionId: generateUUID(), + placementCode: 'dummy-placement-code', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bidId: 'a1b2c3d4', + bidderRequestId: 'bidderRequestId', + }, + ], + start: Date.now(), + auctionStart: Date.now() - 1, + timeout: 3000, +}); + +const getVideoBid = (bidId) => ({ + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + }, + }, + bidder: 'madsense', + sizes: [640, 480], + bidId, + adUnitCode: 'video1', + params: { + ...getVideoParams(), + ...getCommonParams(), + }, +}); + +const getVideoRequest = () => ({ + bidderCode: 'madsense', + auctionId: generateUUID(), + bidderRequestId: 'q1w2e3r4', + bids: [getVideoBid('i8u7y6t5'), getVideoBid('i8u7y6t5')], + auctionStart: Date.now(), + timeout: 5000, + start: Date.now() + 4, + refererInfo: { + numIframes: 1, + reachedTop: true, + referer: 'test.io', + }, +}); + +const getBidderResponse = () => ({ + headers: null, + body: { + id: 'bid-response', + seatbid: [ + { + bid: [ + { + id: 'a1b2c3d4', + impid: 'a1b2c3d4', + price: 0.18, + adm: '', + adid: '144762342', + adomain: ['https://test.io'], + iurl: 'iurl', + cid: '109', + crid: 'creativeId', + w: 300, + h: 250, + ext: { + prebid: { type: 'banner' }, + bidder: { + appnexus: { + brand_id: 321654987, + auction_id: 321654987000000, + bidder_id: 2, + bid_ad_type: 0, + }, + }, + }, + }, + ], + seat: 'madsense', + }, + ] + }, +}); + +describe('madsenseBidAdapter', function () { + describe('interpretResponse', function () { + context('when mediaType is banner', function () { + it('handles empty response', function () { + const bidderRequest = getBannerRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + const EMPTY_RESP = { ...getBidderResponse(), body: {} }; + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; + }); + + it('validates banner bid', function () { + const bidderRequest = getBannerRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + const bidderResponse = getBidderResponse(); + const bids = spec.interpretResponse(bidderResponse, bidRequest); + + expect(bids).to.be.an('array').that.is.not.empty; + validateBid(bids[0], bidderResponse.body.seatbid[0].bid[0]); + }); + }); + + context('when mediaType is video', function () { + it('handles empty response', function () { + const bidderRequest = getVideoRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + const EMPTY_RESP = { ...getBidderResponse(), body: {} }; + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; + }); + + it('returns no bids if required fields are missing', function () { + const bidderRequest = getVideoRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + + const MISSING_FIELDS_RESP = { + ...getBidderResponse(), + body: { seatbid: [{ bid: [{ price: 6.01 }] }] }, + }; + const bids = spec.interpretResponse(MISSING_FIELDS_RESP, bidRequest); + expect(bids.length).to.equal(0); + }); + }); + }); +}); + +function validateBid(actualBid, expectedBid) { + expect(actualBid).to.include({ + currency: 'USD', + requestId: expectedBid.impid, + cpm: expectedBid.price, + width: expectedBid.w, + height: expectedBid.h, + ad: expectedBid.adm, + creativeId: expectedBid.crid, + ttl: 55, + netRevenue: true, + }); + expect(actualBid.meta).to.have.property('advertiserDomains'); +} From 5e57caa8ceb422955725976740d79a24ca6a7770 Mon Sep 17 00:00:00 2001 From: BitmediaDevTeam Date: Sat, 4 Jan 2025 16:33:23 +0200 Subject: [PATCH 0811/1097] Bitmedia Bidder Adapter : initial release (#12610) * init bitmedia adapter * add newline at end of file * change default CDN URL * remove deepAccess, config, rewrite OTRB request --- modules/bitmediaBidAdapter.js | 265 ++++++++++ modules/bitmediaBidAdapter.md | 184 +++++++ test/spec/modules/bitmediaBidAdapter_spec.js | 499 +++++++++++++++++++ 3 files changed, 948 insertions(+) create mode 100644 modules/bitmediaBidAdapter.js create mode 100644 modules/bitmediaBidAdapter.md create mode 100644 test/spec/modules/bitmediaBidAdapter_spec.js diff --git a/modules/bitmediaBidAdapter.js b/modules/bitmediaBidAdapter.js new file mode 100644 index 00000000000..c07c3b4b228 --- /dev/null +++ b/modules/bitmediaBidAdapter.js @@ -0,0 +1,265 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import { + generateUUID, + isEmpty, + isFn, + isPlainObject, + logError, + logInfo, + triggerPixel +} from '../src/utils.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; + +const BIDDER_CODE = 'bitmedia'; +export const ENDPOINT_URL = 'https://cdn.bmcdn7.com/prebid/'; +const AVAILABLE_SIZES = [ + [320, 100], [125, 125], [250, 250], [728, 90], [468, 60], + [300, 100], [300, 250], [120, 240], [320, 1200], [200, 200], + [160, 600], [336, 280], [120, 600], [300, 600], [180, 150], + [320, 50], [468, 90], [970, 90], [250, 100], +]; + +const SIZE_SET = new Set(AVAILABLE_SIZES.map(([w, h]) => `${w}x${h}`)); +const DEFAULT_TTL = 30; // seconds +const DEFAULT_CURRENCY = 'USD'; +const ALLOWED_CURRENCIES = [ + 'USD' +]; +const DEFAULT_NET_REVENUE = true; +const PREBID_VERSION = '$prebid.version$'; +const ADAPTER_VERSION = '1.0'; +export const STORAGE = getStorageManager({bidderCode: BIDDER_CODE}); +const USER_FINGERPRINT_KEY = 'bitmedia_fid'; + +const _handleOnBidWon = (endpoint) => { + logInfo(BIDDER_CODE, `____handle bid won____`, endpoint); + triggerPixel(endpoint); +} + +const _getFidFromBitmediaFid = (bitmediaFid) => { + try { + const decoded = atob(bitmediaFid); + const parsed = JSON.parse(decoded); + logInfo(BIDDER_CODE, 'Parsed bitmedia_fid', parsed); + return parsed.fid || null; + } catch (e) { + logError(BIDDER_CODE, 'Failed to parse bitmedia_fid', e); + return null; + } +} + +const _getBidFloor = (bid, size) => { + logInfo(BIDDER_CODE, '[Bid Floor] Retrieving bid floor for bid:', bid, size); + if (isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: BANNER, + size: size || '*' + }); + + logInfo(BIDDER_CODE, '[Bid Floor] Floor data received:', floor); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) { + logInfo(BIDDER_CODE, '[Bid Floor] Valid floor found:', floor); + return floor.floor; + } + } + logInfo(BIDDER_CODE, '[Bid Floor] Returning null for bid floor.'); + return null; +} + +const CONVERTER = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL, + currency: DEFAULT_CURRENCY, // Only one currency available + mediaType: BANNER, + }, + + imp(buildImp, bidRequest) { + logInfo(BIDDER_CODE, 'Building imp object for bidRequest', bidRequest); + const sizes = bidRequest.sizes; + + const validSizes = sizes.filter(([w, h]) => SIZE_SET.has(`${w}x${h}`)); + + const imps = validSizes.map(size => { + const imp = { + id: bidRequest.bidId, + tagid: bidRequest.params.adUnitID, + banner: { + w: size[0], + h: size[1], + } + }; + + const floor = _getBidFloor(bidRequest, size); + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } + + return imp; + }); + logInfo(BIDDER_CODE, 'Result imp objects for bidRequest', imps); + // Should hasOwnProperty id. + return {id: bidRequest.bidId, imps}; + }, + + request(buildRequest, imps, bidderRequest, context) { + logInfo(BIDDER_CODE, 'Building request with imps and bidderRequest', imps, bidderRequest); + + const requestImps = imps[0].imps;// Unpacking: each imp has the same id, but different banner size + + const reqData = { + ...bidderRequest.ortb2, + id: generateUUID(), + imp: requestImps, + cur: [context.currency], + tmax: bidderRequest.timeout, + ext: { + ...bidderRequest.ortb2.ext, + adapter_version: ADAPTER_VERSION, + prebid_version: PREBID_VERSION + } + }; + + let userId = null; + let bitmediaFid = null; + if (STORAGE.hasLocalStorage()) { + bitmediaFid = STORAGE.getDataFromLocalStorage(USER_FINGERPRINT_KEY); + logInfo(BIDDER_CODE, 'Fingerprint in localstorage', bitmediaFid); + } + + if (!bitmediaFid && STORAGE.cookiesAreEnabled()) { + bitmediaFid = STORAGE.getCookie(USER_FINGERPRINT_KEY); + logInfo(BIDDER_CODE, 'Fingerprint in cookies', bitmediaFid); + } + + if (bitmediaFid) { + userId = _getFidFromBitmediaFid(bitmediaFid); + } + + if (userId) { + reqData.user = reqData.user || {}; + reqData.user.id = userId; + } + + return reqData; + }, + + bidResponse(buildBidResponse, bid, context) { + logInfo(BIDDER_CODE, 'Processing bid response in converter', bid); + const bidResponse = { + requestId: bid.impid, + cpm: bid.price, + currency: bid.cur || context.currency, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: bid.exp || context.ttl, + creativeId: bid.crid, + netRevenue: context.netRevenue, + meta: { + advertiserDomains: bid.adomain || [], + }, + nurl: bid.nurl || bid.burl, + mediaType: context.mediaType, + }; + + return bidResponse; + }, +}); + +const isBidRequestValid = (bid) => { + logInfo(BIDDER_CODE, 'Validating bid request', bid); + const {banner} = bid.mediaTypes || {}; + const {adUnitID, currency} = bid.params || {}; + + if (!banner || !Array.isArray(banner.sizes)) { + logError(BIDDER_CODE, 'Invalid bid: missing or malformed banner sizes', banner); + return false; + } + + const hasValidSize = banner.sizes.some(([w, h]) => SIZE_SET.has(`${w}x${h}`)); + if (!hasValidSize) { + logError(BIDDER_CODE, 'Invalid bid: no valid sizes found', banner.sizes); + return false; + } + + if (typeof adUnitID !== 'string') { + logError(BIDDER_CODE, 'Invalid bid: malformed adUnitId', adUnitID); + return false; + } + + const isCurrencyValid = ALLOWED_CURRENCIES.includes(currency); + if (!isCurrencyValid) { + logError(BIDDER_CODE, `Invalid currency: ${currency}. Allowed currencies are ${ALLOWED_CURRENCIES.join(', ')}`); + return false; + } + + logInfo(BIDDER_CODE, 'Bid request is valid', bid); + return true; +}; + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + logInfo(BIDDER_CODE, 'Building OpenRTB request', {validBidRequests, bidderRequest}); + const requests = validBidRequests.map(bidRequest => { + const data = CONVERTER.toORTB({ + bidRequests: [bidRequest], + bidderRequest, + }); + + logInfo(BIDDER_CODE, 'Result OpenRTB request data for bidRequest', data); + + const adUnitId = bidRequest.params.adUnitID; + + return { + method: 'POST', + url: `${ENDPOINT_URL}${adUnitId}`, + data: data, + bids: [bidRequest], + options: { + withCredentials: false, + crossOrigin: true + }, + }; + }); + return requests; +}; + +const interpretResponse = (serverResponse, bidRequest) => { + logInfo(BIDDER_CODE, 'Interpreting server response', {serverResponse, bidRequest}); + + if (isEmpty(serverResponse.body)) { + logInfo(BIDDER_CODE, 'Empty response'); + return []; + } + + const bids = CONVERTER.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + logInfo(BIDDER_CODE, `${bids.length} bids successfully interpreted`, bids); + + return bids; +}; + +const onBidWon = (bid) => { + const cpm = bid.adserverTargeting?.hb_pb || ''; + logInfo(BIDDER_CODE, `-----Bid won-----`, {bid, cpm: cpm}); + _handleOnBidWon(bid.nurl); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, +}; + +registerBidder(spec); diff --git a/modules/bitmediaBidAdapter.md b/modules/bitmediaBidAdapter.md new file mode 100644 index 00000000000..dd02b4785c0 --- /dev/null +++ b/modules/bitmediaBidAdapter.md @@ -0,0 +1,184 @@ +# Bitmedia Bid Adapter + +## Overview + +``` +Module Name: Bitmedia Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@bitmedia.io +``` + +## Description + +The Bitmedia Bid Adapter allows you to integrate BitmediaIO for banner advertising. + +### Key Points: +- Supported Media Type: **Banner** +- Bids are only provided in **USD**. +- Access to **local storage** is optional. + +Before using this adapter, simply [create a publisher account](https://bitmedia.io/become-a-publisher) on our platform to obtain your `adUnitID`. + +More about us: [https://bitmedia.io](https://bitmedia.io) + +--- + +## Test Parameters + +### Example +```javascript +var adUnits = [ + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bids: [{ + bidder: 'bitmedia', + params: { + adUnitID: 'exampleAdUnitID', + currency: 'USD', + }, + }], + }, +]; +``` + +--- + +## Testing Instructions + +The HTML file below can be used to test the integration of the Bitmedia Bid Adapter. + +### Simple Test HTML + +```html + + + + + + + + + + + + + + + + + + +

Ad Serverless Test Page

+ +
+
+
+ + +``` \ No newline at end of file diff --git a/test/spec/modules/bitmediaBidAdapter_spec.js b/test/spec/modules/bitmediaBidAdapter_spec.js new file mode 100644 index 00000000000..be454690235 --- /dev/null +++ b/test/spec/modules/bitmediaBidAdapter_spec.js @@ -0,0 +1,499 @@ +import {expect} from 'chai'; +import {spec, STORAGE, ENDPOINT_URL} from 'modules/bitmediaBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; +import {BANNER} from '../../../src/mediaTypes'; + +describe('Bitmedia Bid Adapter', function () { + const createBidRequest = (sandbox, overrides = {}) => { + return Object.assign({ + bidder: 'bitmedia', + params: { + adUnitID: 'test-ad-unit-id', + currency: 'USD' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid123', + auctionId: 'auction123', + transactionId: 'transaction123', + adUnitCode: 'adunit123', + sizes: [[300, 250], [300, 600]], + getFloor: sandbox.stub().returns({ + currency: 'USD', + floor: 0.4 + }) + }, overrides); + } + + const createBidderRequest = (overrides = {}) => { + return Object.assign({ + refererInfo: { + page: 'https://example.com/page.html', + domain: 'example.com', + referer: 'https://google.com' + }, + timeout: 2000, + bidderCode: 'bitmedia', + auctionId: 'auction123', + ortb2: { + site: { + domain: 'publisher.com', + page: 'https://publisher.com/homepage.html', + publisher: { + domain: 'publisher.com' + } + }, + device: { + ua: 'custom-user-agent', + language: 'fr' + } + } + }, overrides); + } + // Helper function to stub storage for user ID + const stubStorage = (sandbox, userIdInLocalStorage = null, userIdInCookies = null) => { + sandbox.stub(STORAGE, 'hasLocalStorage').returns(true); + sandbox.stub(STORAGE, 'getDataFromLocalStorage') + .withArgs('bitmedia_fid') + .returns(userIdInLocalStorage); + + sandbox.stub(STORAGE, 'cookiesAreEnabled').returns(true); + sandbox.stub(STORAGE, 'getCookie') + .withArgs('bitmedia_fid') + .returns(userIdInCookies); + + if (userIdInLocalStorage || userIdInCookies) { + const encodedFid = userIdInLocalStorage || userIdInCookies; + sandbox.stub(window, 'atob') + .withArgs(encodedFid) + .returns(`{"fid":"user123"}`); + } + } + + describe('isBidRequestValid', function () { + let bid; + beforeEach(function () { + bid = { + bidder: 'bitmedia', + params: { + adUnitID: 'test-ad-unit-id', + currency: 'USD' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }; + }); + + it('should return true when required params found and sizes are valid', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if adUnitID is missing', function () { + delete bid.params.adUnitID; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if currency is invalid', function () { + bid.params.currency = 'EUR'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if no valid sizes provided', function () { + bid.mediaTypes.banner.sizes = [[123, 456], [789, 101]]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if mediaTypes.banner is missing', function () { + delete bid.mediaTypes.banner; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if banner sizes are not an array', function () { + bid.mediaTypes.banner.sizes = '300x250'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if bid params are missing', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + + // Stub generateUUID to return a fixed value for testing + sandbox.stub(utils, 'generateUUID').returns('test-generated-uuid'); + + // Stub config.getConfig for bidderTimeout + sandbox.stub(config, 'getConfig').withArgs('bidderTimeout').returns(30); + }); + + afterEach(function () { + sandbox.restore(); + }); + describe('when building the request', function () { + let bidRequests; + let bidderRequest; + let requests; + let request; + let data; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox,)]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, 'encodedFidString'); // User ID in cookies + + requests = spec.buildRequests(bidRequests, bidderRequest); + request = requests[0]; + data = request.data; + }); + + it('should return an array with one request', function () { + expect(requests).to.be.an('array').with.lengthOf(1); + }); + + it('should have method POST and the correct URL', function () { + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(`${ENDPOINT_URL}${bidRequests[0].params.adUnitID}`); + }); + + it('should have the correct request options', function () { + expect(request.options).to.deep.equal({ + withCredentials: false, + crossOrigin: true + }); + }); + + it('should have the correct request data structure', function () { + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('id', 'imp', 'site', 'device', 'cur', 'tmax', 'ext', 'user'); + }); + + it('should include the generated UUID in the request data', function () { + expect(data.id).to.equal('test-generated-uuid'); + }); + + it('should include the correct impressions in the request data', function () { + expect(data.imp).to.be.an('array').with.lengthOf(2); + + const expectedImp1 = { + id: 'bid123', + tagid: 'test-ad-unit-id', + banner: { + w: 300, + h: 250 + }, + bidfloor: 0.4, + bidfloorcur: 'USD' + }; + + const expectedImp2 = { + id: 'bid123', + tagid: 'test-ad-unit-id', + banner: { + w: 300, + h: 600 + }, + bidfloor: 0.4, + bidfloorcur: 'USD' + }; + + expect(data.imp[0]).to.deep.equal(expectedImp1); + expect(data.imp[1]).to.deep.equal(expectedImp2); + }); + + it('should include the correct site information', function () { + expect(data.site).to.deep.equal({ + domain: 'publisher.com', + page: 'https://publisher.com/homepage.html', + publisher: { + domain: 'publisher.com' + } + }); + }); + + it('should include the correct device information', function () { + expect(data.device).to.deep.equal({ + ua: 'custom-user-agent', + language: 'fr' + }); + }); + + it('should include the default currency', function () { + expect(data.cur).to.deep.equal(['USD']); + }); + + it('should include the correct timeout (tmax)', function () { + expect(data.tmax).to.equal(2000); + }); + + it('should include the ext field with adapter_version and prebid_version as strings', function () { + expect(data.ext.adapter_version).to.be.a('string'); + expect(data.ext.prebid_version).to.be.a('string'); + }); + + it('should include the user ID when present in cookies', function () { + expect(data.user).to.deep.equal({ + id: 'user123' + }); + }); + }); + + describe('when some invalid sizes are provided', function () { + let bidRequests; + let bidderRequest; + let requests; + let request; + let data; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox, { + mediaTypes: { + banner: { + sizes: [[300, 600], [888, 888]], + }, + }, + sizes: [[300, 600], [888, 888]], + })]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, null); + + requests = spec.buildRequests(bidRequests, bidderRequest); + request = requests[0]; + data = request.data; + }); + + it('should not build imp with invalid size', function () { + expect(data.imp).to.be.an('array').with.lengthOf(1); + }); + }); + + describe('when user ID is absent', function () { + let bidRequests; + let bidderRequest; + let requests; + let data; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox,)]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, null); + + requests = spec.buildRequests(bidRequests, bidderRequest); + data = requests[0].data; + }); + + it('should not include user ID in the request data', function () { + expect(data.user).to.be.undefined; + }); + }); + + describe('when getFloor is not available', function () { + let bidRequests; + let bidderRequest; + let requests; + let imp; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox, { + getFloor: undefined + })]; + bidderRequest = createBidderRequest(); + + requests = spec.buildRequests(bidRequests, bidderRequest); + imp = requests[0].data.imp; + }); + + it('should not include bidfloor in imp objects', function () { + imp.forEach(impression => { + expect(impression).to.not.have.property('bidfloor'); + expect(impression).to.not.have.property('bidfloorcur'); + }); + }); + }); + + describe('when different bid floors are provided per size', function () { + let bidRequests; + let bidderRequest; + let requests; + let imp; + + beforeEach(function () { + const getFloorStub = sinon.stub(); + getFloorStub.withArgs({currency: 'USD', mediaType: 'banner', size: [300, 250]}).returns({ + currency: 'USD', + floor: 0.5 + }); + getFloorStub.withArgs({currency: 'USD', mediaType: 'banner', size: [300, 600]}).returns({ + currency: 'USD', + floor: 0.7 + }); + + bidRequests = [createBidRequest(sandbox, { + getFloor: getFloorStub + })]; + bidderRequest = createBidderRequest(); + + requests = spec.buildRequests(bidRequests, bidderRequest); + imp = requests[0].data.imp; + }); + + it('should include the correct bidfloor per impression', function () { + expect(imp[0].bidfloor).to.equal(0.5); + expect(imp[0].banner).to.deep.equal({w: 300, h: 250}); + expect(imp[1].bidfloor).to.equal(0.7); + expect(imp[1].banner).to.deep.equal({w: 300, h: 600}); + }); + }); + + describe('when bid floor data is invalid', function () { + let bidRequests; + let bidderRequest; + let requests; + let imp; + + beforeEach(function () { + const invalidGetFloor = sinon.stub().returns({ + currency: 'USD', + floor: 'invalid' + }); + bidRequests = [createBidRequest(sandbox, { + getFloor: invalidGetFloor + })]; + bidderRequest = createBidderRequest(); + + requests = spec.buildRequests(bidRequests, bidderRequest); + imp = requests[0].data.imp; + }); + + it('should not include bidfloor when floor value is invalid', function () { + imp.forEach(impression => { + expect(impression).to.not.have.property('bidfloor'); + expect(impression).to.not.have.property('bidfloorcur'); + }); + }); + }); + }); + + describe('interpretResponse', function () { + let sandbox; + let bidRequests; + let bidderRequest; + let requests; + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'generateUUID').returns('test-generated-uuid'); + bidRequests = [createBidRequest(sandbox,)]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, null); // No user ID for simplicity + + requests = spec.buildRequests(bidRequests, bidderRequest); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should return bids with all required keys when server response has valid bids', function () { + const request = requests[0]; + + const serverResponse = { + body: { + id: request.data.id, + seatbid: [ + { + bid: [ + { + impid: request.data.imp[0].id, + price: 1.5, + w: request.data.imp[0].banner.w, + h: request.data.imp[0].banner.h, + adm: '
Ad Content
', + crid: 'creative123', + adomain: ['example.com'], + nurl: 'https://example.com/win', + exp: 360, + }, + ], + }, + ], + cur: 'USD', + }, + }; + + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.an('array').with.lengthOf(1); + const bid = bids[0]; + expect(bid).to.include.all.keys( + 'requestId', + 'cpm', + 'currency', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'meta', + 'nurl', + 'mediaType' + ); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.ttl).to.equal(360); + expect(bid.nurl).to.equal('https://example.com/win'); + }); + + it('should return an empty array when server response is empty', function () { + const serverResponse = {body: {}}; + const bidRequest = {}; + + const bids = spec.interpretResponse(serverResponse, bidRequest); + + expect(bids).to.be.an('array').that.is.empty; + }); + }); + + describe('onBidWon', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('should return nothing and trigger a pixel with nurl', function () { + const bid = { + nurl: 'https://example.com/win', + }; + + const triggerPixelSpy = sandbox.spy(utils, 'triggerPixel'); + + const response = spec.onBidWon(bid); + + expect(response).to.be.undefined; + + expect(triggerPixelSpy.calledOnce).to.equal(true); + + expect(triggerPixelSpy.calledWith(bid.nurl)).to.equal(true); + }); + }); +}); From a8f4d5b5a2480a5e8bc819820ccf36605ee1ad25 Mon Sep 17 00:00:00 2001 From: yogeshverse Date: Mon, 6 Jan 2025 23:53:16 +0530 Subject: [PATCH 0812/1097] Nexverse Bid Adapter : remove slash from the endpoint (#12617) * Add nexverseBidAdapter implementation and tests * Updated Nexverse adapter to support OpenRTB 2.5, handle 204 responses, and added device and connection type detection * Resolved conflicts and merged master into nexverse-bid-adapter * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * * Removed bidfloor from Adunit * Fixed issues of 1.Category 2.Domain 3.Attributes * Added Nexverse Demo html for testing with sample bid * Handled Height and width issue * * Added test cases for bid param checks * fixed bidFloor issue * * Added testcases for utils functions ' * * removed duplicate function and used from available library * * removed unwated logging * * added logger in try catch * fixed linter error * * fixed linter issue * fixed the getOsVersion missing function issue * fixed the getOsVersion missing function issue * removed hardcoded cpm value * Added comment for is debug param * removed extra slash from URL --------- Co-authored-by: Anand Kumar --- modules/nexverseBidAdapter.js | 2 +- test/spec/modules/nexverseBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/nexverseBidAdapter.js b/modules/nexverseBidAdapter.js index 5ff6aa10bc5..6243ef575ed 100644 --- a/modules/nexverseBidAdapter.js +++ b/modules/nexverseBidAdapter.js @@ -12,7 +12,7 @@ import { getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; import { getOsVersion } from '../libraries/advangUtils/index.js'; const BIDDER_CODE = 'nexverse'; -const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai/'; +const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; const DEFAULT_CURRENCY = 'USD'; const BID_TTL = 300; diff --git a/test/spec/modules/nexverseBidAdapter_spec.js b/test/spec/modules/nexverseBidAdapter_spec.js index fb648a154f7..fd20a37cc0d 100644 --- a/test/spec/modules/nexverseBidAdapter_spec.js +++ b/test/spec/modules/nexverseBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from 'modules/nexverseBidAdapter.js'; import { getDeviceModel, buildEndpointUrl, parseNativeResponse } from '../../../libraries/nexverseUtils/index.js'; import { getOsVersion } from '../../../libraries/advangUtils/index.js'; -const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai/'; +const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai'; describe('nexverseBidAdapterTests', () => { describe('isBidRequestValid', function () { From 83ba93c2a3013e21f909cf79de7b114810a8601c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 6 Jan 2025 19:04:45 +0000 Subject: [PATCH 0813/1097] Prebid 9.26.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78aff02eb1c..c4222194ba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.26.0-pre", + "version": "9.26.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.26.0-pre", + "version": "9.26.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 617a82c8e6f..350f221b31a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.26.0-pre", + "version": "9.26.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 64df88d27465540e4f34237aaa0e3dab55716282 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 6 Jan 2025 19:04:45 +0000 Subject: [PATCH 0814/1097] Increment version to 9.27.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4222194ba7..2001909266e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.26.0", + "version": "9.27.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.26.0", + "version": "9.27.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 350f221b31a..648cf2e0736 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.26.0", + "version": "9.27.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 445ca1478a2ea7a3072018fd8b4f6eca5634782f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20Chr=C3=A9tien?= Date: Mon, 6 Jan 2025 20:19:29 +0100 Subject: [PATCH 0815/1097] AdagioRtdProvider: always set session.expiry (#12611) --- modules/adagioRtdProvider.js | 24 ++-- test/spec/modules/adagioRtdProvider_spec.js | 122 +++++++++++++++++++- 2 files changed, 134 insertions(+), 12 deletions(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index ee81e1337dd..a5642fdca02 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -76,15 +76,17 @@ const _SESSION = (function() { const isNewSess = isNewSession(expiry); - // if lastActivityTime is defined it means that the website is using the original version of the snippet - const v = !lastActivityTime ? LATEST_ABTEST_VERSION : undefined; + const abTest = _internal.getAbTestFromLocalStorage(storageValue); + + // if abTest is defined it means that the website is using the new version of the snippet + const v = abTest ? LATEST_ABTEST_VERSION : undefined; data.session = { - v, rnd, pages: pages || 1, new: isNewSess, // legacy: `new` was used but the choosen name is not good. // Don't use values if they are not defined. + ...(v !== undefined && { v }), ...(vwSmplg !== undefined && { vwSmplg }), ...(vwSmplgNxt !== undefined && { vwSmplgNxt }), ...(expiry !== undefined && { expiry }), @@ -98,15 +100,19 @@ const _SESSION = (function() { data.session.rnd = Math.random(); } - const { testName, testVersion, expiry: abTestExpiry, sessionId } = _internal.getAbTestFromLocalStorage(storageValue); if (v === LATEST_ABTEST_VERSION) { + const { testName, testVersion, expiry: abTestExpiry, sessionId } = abTest; if (abTestExpiry && abTestExpiry > Date.now() && (!sessionId || sessionId === data.session.id)) { // if AbTest didn't set a session id, it's probably because it's a new one and it didn't retrieve it yet, assume it's okay to get test Name and Version. - data.session.testName = testName; - data.session.testVersion = testVersion; + if (testName && testVersion) { + data.session.testName = testName; + data.session.testVersion = testVersion; + } } } else { - data.session.testName = legacyTestName; - data.session.testVersion = legacyTestVersion; + if (legacyTestName && legacyTestVersion) { + data.session.testName = legacyTestName; + data.session.testVersion = legacyTestVersion; + } } _internal.getAdagioNs().queue.push({ @@ -215,7 +221,7 @@ export const _internal = { getAbTestFromLocalStorage: function(storageValue) { const obj = this.getObjFromStorageValue(storageValue); - return (!obj || !obj.abTest) ? {} : obj.abTest; + return (!obj || !obj.abTest) ? null : obj.abTest; }, /** diff --git a/test/spec/modules/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js index 7b9fec595b1..aea76ef4c3d 100644 --- a/test/spec/modules/adagioRtdProvider_spec.js +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -127,7 +127,7 @@ describe('Adagio Rtd Provider', function () { }; it('store new session data for further usage', function () { - const storageValue = null; + const storageValue = JSON.stringify({abTest: {}}); sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); sandbox.stub(Date, 'now').returns(1714116520710); sandbox.stub(Math, 'random').returns(0.8); @@ -155,7 +155,7 @@ describe('Adagio Rtd Provider', function () { }); it('store existing session data for further usage', function () { - const storageValue = JSON.stringify({session: session}); + const storageValue = JSON.stringify({session: session, abTest: {}}); sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); sandbox.stub(Date, 'now').returns(1714116520710); sandbox.stub(Math, 'random').returns(0.8); @@ -179,7 +179,7 @@ describe('Adagio Rtd Provider', function () { }); it('store new session if old session has expired data for further usage', function () { - const storageValue = JSON.stringify({session: session}); + const storageValue = JSON.stringify({session: session, abTest: {}}); sandbox.stub(Date, 'now').returns(1715679344351); sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); sandbox.stub(Math, 'random').returns(0.8); @@ -197,6 +197,122 @@ describe('Adagio Rtd Provider', function () { rnd: Math.random(), } } + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); + + describe('store session data in localStorage for old snippet', function () { + it('store new session data for further usage', function () { + const storageValue = null; + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + sandbox.stub(utils, 'generateUUID').returns('uid-1234'); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + new: true, + id: utils.generateUUID(), + rnd: Math.random(), + pages: 1 + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + + it('update session data for further usage', function () { + const storageValue = JSON.stringify({ + session: { + new: true, + id: 'uid-1234', + rnd: 0.8, + pages: 1, + expiry: 1714116520710, + testName: 't', + testVersion: 'clt' + } + }); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + sandbox.stub(utils, 'generateUUID').returns('uid-1234'); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + new: false, + expiry: 1714116520710, + id: utils.generateUUID(), + rnd: Math.random(), + pages: 1, + testName: 't', + testVersion: 'clt' + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); + + describe('update session data in localStorage from old snippet to new version', function () { + it('update session data for new snippet', function () { + const storageValue = JSON.stringify({ + session: { + new: false, + id: 'uid-1234', + rnd: 0.8, + pages: 1, + expiry: 1714116520710, + testName: 't', + testVersion: 'clt' + }, + abTest: { + expiry: 1714116520810, + testName: 't', + testVersion: 'srv' + } + }); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + sandbox.stub(utils, 'generateUUID').returns('uid-1234'); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + new: false, + expiry: 1714116520710, + id: utils.generateUUID(), + rnd: Math.random(), + pages: 1, + testName: 't', + testVersion: 'srv', + v: 2 + } + } expect(spy.withArgs({ action: 'session', From 3d91b5c45744dbe369a1a10bea88b08279abb59d Mon Sep 17 00:00:00 2001 From: "yuu.t" Date: Tue, 7 Jan 2025 17:37:54 +0100 Subject: [PATCH 0816/1097] YieldlabBidAdapter forward consent under param gdpr_consent (#12623) --- modules/yieldlabBidAdapter.js | 4 ++-- test/spec/modules/yieldlabBidAdapter_spec.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9be01096084..fda54328b85 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -100,7 +100,7 @@ export const spec = { if (bidderRequest.gdprConsent) { query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true; if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString; + query.gdpr_consent = bidderRequest.gdprConsent.consentString; } } @@ -184,7 +184,7 @@ export const spec = { const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : ''; const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : ''; const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : ''; - const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : ''; + const gdprConsent = reqParams.gdpr_consent ? '&gdpr_consent=' + reqParams.gdpr_consent : ''; const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : ''; const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : ''; diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 25d8f2baec0..92fd20fb37d 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -263,7 +263,7 @@ const REQPARAMS = { const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { gdpr: true, - consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', + gdpr_consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', }); const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { @@ -457,8 +457,8 @@ describe('yieldlabBidAdapter', () => { }, }); - expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); - expect(gdprRequest.url).to.include('gdpr=true'); + expect(gdprRequest.url).to.include('&gdpr_consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(gdprRequest.url).to.include('&gdpr=true'); }); describe('sizes handling', () => { @@ -691,7 +691,7 @@ describe('yieldlabBidAdapter', () => { const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS_GDPR}); expect(result[0].ad).to.include('&gdpr=true'); - expect(result[0].ad).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(result[0].ad).to.include('&gdpr_consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); }); it('should append iab_content to adtag', () => { @@ -814,7 +814,7 @@ describe('yieldlabBidAdapter', () => { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS_GDPR}); expect(result[0].vastUrl).to.include('&gdpr=true'); - expect(result[0].vastUrl).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(result[0].vastUrl).to.include('&gdpr_consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); }); it('should add renderer if outstream context', () => { From 1f838f8728725d9ca7fa3eea98fc36f100bdedff Mon Sep 17 00:00:00 2001 From: VitalyOrbit Date: Wed, 8 Jan 2025 16:06:30 +0300 Subject: [PATCH 0817/1097] Orbit soft bid Module: Change aliases (#12628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding Orbitsoft module * Adding Orbitsoft module (corrected) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to new constructor) * Adding Orbitsoft module (delete unnecessary aliases) * Adding Orbitsoft module (delete unnecessary aliases) * fixed orbitsoftAdapter * fixed orbitsoftAdapter processing undefined request referrer * fixed orbitsoftAdapter processing undefined request referrer * fix-orbitsoftAdaper: codereview fixes * added changes for new spec * added changes for new spec * added changes for new spec * added "custom" currency * fix: currency key replaced into ortb2 * rollback a change * rollback a change * added a new alias --------- Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий Co-authored-by: Хатламаджиян Виталий --- modules/orbitsoftBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index f55c7ff9917..a22afd16a49 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -25,7 +25,7 @@ let styleParamsMap = { }; export const spec = { code: BIDDER_CODE, - aliases: ['oas', '152media'], // short code and customer aliases + aliases: ['oas', '152media', 'paradocs'], // short code and customer aliases isBidRequestValid: function (bid) { switch (true) { case !('params' in bid): From b2d161990b88d1fc8cc5a47d849845f5c948cfaf Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 8 Jan 2025 08:07:29 -0500 Subject: [PATCH 0818/1097] Equativ Bid Adapter: add warning messages for audio-related properties in bid requests (#12616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * updates after pr review * SADR-6484: initial video setup for new PBJS adapter * SADR-6484: Adding logging requirement missed earlier * SADR-6484: handle ext.rewarded prop for video with new oRTBConverter * SADR-6484: test revision + not sending bid requests where video obj is empty * refactoring and u.t. * rename variable * Equativ: SADR-6615: adding unit tests for and additional logging to bid adapter to support native requests * revert changes rel. to test endpoint * revert changes rel. to test endpoint * split imp[0] into seperate requests and fix u.t. * Equativ bid adapter: adding support for native media type * Equativ bid adapter: update unit test for native-support work * Equativ bid adapter: removing console.log from unit test file * Equativ bid adapter: clarifying refinements regarding native-request processing * Equativ Bid Adapter: updating unit tests for native requests * PR feedback * Equativ Bid Adapter: add audio-specific warnings for missing fields in bid requests --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+krzysztofequativ@users.noreply.github.com> Co-authored-by: Krzysztof Sokół Co-authored-by: janzych-smart --- modules/equativBidAdapter.js | 31 +++++++++------------ test/spec/modules/equativBidAdapter_spec.js | 11 ++++---- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js index 646a54b6418..39c7ee94586 100644 --- a/modules/equativBidAdapter.js +++ b/modules/equativBidAdapter.js @@ -143,24 +143,19 @@ export const converter = ortbConverter({ let env = ['ortb2.site.publisher', 'ortb2.app.publisher', 'ortb2.dooh.publisher'].find(propPath => deepAccess(bid, propPath)) || 'ortb2.site.publisher'; deepSetValue(req, env.replace('ortb2.', '') + '.id', deepAccess(bid, env + '.id') || bid.params.networkId); - if (deepAccess(bid, 'mediaTypes.video')) { - ['mimes', 'placement'].forEach(prop => { - if (!bid.mediaTypes.video[prop]) { - logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request`, bid); - } - }); - } - - // "assets" is not included as a property to check here because the - // ortbConverter library checks for it already and will skip processing - // the request if it is missing - if (deepAccess(bid, 'mediaTypes.native')) { - ['privacy', 'plcmttype', 'eventtrackers'].forEach(prop => { - if (!bid.mediaTypes.native.ortb[prop]) { - logWarn(`${LOG_PREFIX} Property "${prop}" is missing from request. Request will proceed, but the use of ${prop} for native requests is strongly encouraged.`, bid); - } - }); - } + [ + { path: 'mediaTypes.video', props: ['mimes', 'placement'] }, + { path: 'ortb2Imp.audio', props: ['mimes'] }, + { path: 'mediaTypes.native.ortb', props: ['privacy', 'plcmttype', 'eventtrackers'] }, + ].forEach(({ path, props }) => { + if (deepAccess(bid, path)) { + props.forEach(prop => { + if (!deepAccess(bid, `${path}.${prop}`)) { + logWarn(`${LOG_PREFIX} Property "${path}.${prop}" is missing from request. Request will proceed, but the use of "${prop}" is strongly encouraged.`, bid); + } + }); + } + }); const pid = storage.getCookie(PID_COOKIE_NAME); if (pid) { diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js index c66e97e6dbc..2784b5c4946 100644 --- a/test/spec/modules/equativBidAdapter_spec.js +++ b/test/spec/modules/equativBidAdapter_spec.js @@ -535,8 +535,8 @@ describe('Equativ bid adapter tests', () => { // ASSERT expect(utils.logWarn.callCount).to.equal(2); - expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('"mimes" is missing')); - expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"placement" is missing')); + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.video.mimes" is missing')); + expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.video.placement" is missing')); }); it('should not send a video request when it has an empty body and no other impressions with any media types are defined', () => { @@ -622,7 +622,6 @@ describe('Equativ bid adapter tests', () => { spec.buildRequests(bidRequests, bidderRequest); // ASSERT - expect(utils.logWarn.callCount).to.equal(1) expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes(warningMsgFromLibrary)); } }); @@ -646,9 +645,9 @@ describe('Equativ bid adapter tests', () => { // ASSERT expect(utils.logWarn.callCount).to.equal(4); // the first message, regarding missing assets, is supplied by the ortbConverter library expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('no assets were specified')); - expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"privacy" is missing')); - expect(utils.logWarn.getCall(2).args[0]).to.satisfy(arg => arg.includes('"plcmttype" is missing')); - expect(utils.logWarn.getCall(3).args[0]).to.satisfy(arg => arg.includes('"eventtrackers" is missing')); + expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.native.ortb.privacy" is missing')); + expect(utils.logWarn.getCall(2).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.native.ortb.plcmttype" is missing')); + expect(utils.logWarn.getCall(3).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.native.ortb.eventtrackers" is missing')); } }); }); From a63a49ccca1f6f9a49f5347255dd5aac00926818 Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Wed, 8 Jan 2025 08:20:17 -0500 Subject: [PATCH 0819/1097] Contxtful Bid Adapter: Updates the default sampling rate (#12622) * fix: default sampling rate * doc: update * fix: non inclusive comparison * doc: update * doc: ci trigger --------- Co-authored-by: rufiange --- modules/contxtfulBidAdapter.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js index acd9811b871..4905c499998 100644 --- a/modules/contxtfulBidAdapter.js +++ b/modules/contxtfulBidAdapter.js @@ -16,6 +16,7 @@ const BIDDER_ENDPOINT = 'prebid.receptivity.io'; const MONITORING_ENDPOINT = 'monitoring.receptivity.io'; const DEFAULT_NET_REVENUE = true; const DEFAULT_TTL = 300; +const DEFAULT_SAMPLING_RATE = 1.0; const PREBID_VERSION = '$prebid.version$'; // ORTB conversion @@ -94,7 +95,7 @@ const buildRequests = (validBidRequests = [], bidderRequest = {}) => { pathname: `/${version}/prebid/${customer}/bid`, }); - // https://docs.prebid.org/dev-docs/bidder-adaptor.html + // See https://docs.prebid.org/dev-docs/bidder-adaptor.html let req = { url: adapterUrl, method: 'POST', @@ -147,7 +148,7 @@ const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gpp // Retrieve the sampling rate for events const getSamplingRate = (bidderConfig, eventType) => { const entry = Object.entries(bidderConfig?.contxtful?.sampling ?? {}).find(([key, value]) => key.toLowerCase() === eventType.toLowerCase()); - return entry ? entry[1] : 0.001; + return entry ? entry[1] : DEFAULT_SAMPLING_RATE; }; // Handles the logging of events @@ -164,7 +165,7 @@ const logEvent = (eventType, data) => { if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) { const randomNumber = Math.random(); const samplingRate = getSamplingRate(bidderConfig, eventType); - if (randomNumber >= samplingRate) { + if (!(randomNumber <= samplingRate)) { return; // Don't sample } } else if (!['onTimeout', 'onBidderError', 'onBidWon'].includes(eventType)) { From 6790c789103763616eefb433bd20987ce7e232ef Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:27:10 +0100 Subject: [PATCH 0820/1097] PBjs Core : fix creation of per-bidder syncOptions (#12615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * sspbc adapter -> update to v6 * bugfix proposal - using isActivityAllowed to build per-bidder syncOptions * Fix preexisting tests * Use canBidderRegisterSync --------- Co-authored-by: Wojciech Biały Co-authored-by: decemberWP <155962474+decemberWP@users.noreply.github.com> Co-authored-by: Demetrio Girardi --- src/adapters/bidderFactory.js | 13 +- test/spec/unit/core/bidderFactory_spec.js | 163 ++++++++++++++++------ 2 files changed, 127 insertions(+), 49 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index e4829d76a1d..2e3f2f3ec8f 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -5,7 +5,7 @@ import {createBid} from '../bidfactory.js'; import {userSync} from '../userSync.js'; import {nativeBidIsValid} from '../native.js'; import {isValidVideoBid} from '../video.js'; -import { EVENTS, STATUS, REJECTION_REASON } from '../constants.js'; +import {EVENTS, REJECTION_REASON, STATUS} from '../constants.js'; import * as events from '../events.js'; import {includes} from '../polyfill.js'; import { @@ -13,9 +13,11 @@ import { isArray, isPlainObject, logError, - logWarn, memoize, + logWarn, + memoize, parseQueryStringParameters, - parseSizesInput, pick, + parseSizesInput, + pick, uniques } from '../utils.js'; import {hook} from '../hook.js'; @@ -523,10 +525,9 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent, gppConsent) { const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { - let filterConfig = config.getConfig('userSync.filterSettings'); let syncs = spec.getUserSyncs({ - iframeEnabled: !!(filterConfig && (filterConfig.iframe || filterConfig.all)), - pixelEnabled: !!(filterConfig && (filterConfig.image || filterConfig.all)), + iframeEnabled: userSync.canBidderRegisterSync('iframe', spec.code), + pixelEnabled: userSync.canBidderRegisterSync('image', spec.code), }, responses, gdprConsent, uspConsent, gppConsent); if (syncs) { if (!Array.isArray(syncs)) { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 1aab16a5e46..036e45d9b86 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -91,53 +91,130 @@ describe('bidderFactory', () => { sandbox.restore(); }); - it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: true - } - }); - spec.code = 'fakeBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - - it('should let registerSyncs run with valid alias and aliasSync enabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: true + describe('user syncs', () => { + [ + { + t: 'invalid alias, aliasSync enabled', + alias: false, + aliasSyncEnabled: true, + shouldRegister: true + }, + { + t: 'valid alias, aliasSync enabled', + alias: true, + aliasSyncEnabled: true, + shouldRegister: true + }, + { + t: 'invalid alias, aliasSync disabled', + alias: false, + aliasSyncEnabled: false, + shouldRegister: true, + }, + { + t: 'valid alias, aliasSync disabled', + alias: true, + aliasSyncEnabled: false, + shouldRegister: false } + ].forEach(({t, alias, aliasSyncEnabled, shouldRegister}) => { + describe(t, () => { + it(shouldRegister ? 'should register sync' : 'should NOT register sync', () => { + config.setConfig({ + userSync: { + aliasSyncEnabled + } + }); + spec.code = 'someBidder'; + if (alias) { + aliasRegistry[spec.code] = 'original'; + } + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + if (shouldRegister) { + sinon.assert.called(spec.getUserSyncs); + } else { + sinon.assert.notCalled(spec.getUserSyncs); + } + }); + }); }); - spec.code = 'aliasBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - it('should let registerSyncs run with invalid alias and aliasSync disabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: false - } - }); - spec.code = 'fakeBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); + describe('getUserSyncs syncOptions', () => { + [ + { + t: 'all image allowed, specific bidder denied iframe', + userSync: { + syncEnabled: true, + pixelEnabled: true, + iframeEnabled: true, + filterSettings: { + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: ['bidderB'], + filter: 'include' + } + } + }, + expected: { + bidderA: { + iframeEnabled: false, + pixelEnabled: true + }, + bidderB: { + iframeEnabled: true, + pixelEnabled: true, + } + } + }, + { + t: 'specific bidders allowed specific methods', + userSync: { + syncEnabled: true, + pixelEnabled: true, + iframeEnabled: true, + filterSettings: { + image: { + bidders: ['bidderA'], + filter: 'include' + }, + iframe: { + bidders: ['bidderB'], + filter: 'include' + } + }, + }, + expected: { + bidderA: { + iframeEnabled: false, + pixelEnabled: true + }, + bidderB: { + iframeEnabled: true, + pixelEnabled: false, + } + } + } + ].forEach(({t, userSync, expected}) => { + describe(`when ${t}`, () => { + beforeEach(() => { + config.setConfig({userSync}); + }); - it('should not let registerSyncs run with valid alias and aliasSync disabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: false - } - }); - spec.code = 'aliasBidder'; - const bidder = newBidder(spec); - aliasRegistry = {[spec.code]: CODE}; - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); + Object.entries(expected).forEach(([bidderCode, syncOptions]) => { + it(`should pass ${JSON.stringify(syncOptions)} to ${bidderCode}`, () => { + spec.code = bidderCode; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(spec.getUserSyncs, syncOptions); + }) + }) + }) + }) + }) }); describe('transaction IDs', () => { From 9cad047dcf29bf97cb1e200153151e0f0a40aaf5 Mon Sep 17 00:00:00 2001 From: Andrius Versockas Date: Wed, 8 Jan 2025 20:34:38 +0200 Subject: [PATCH 0821/1097] Eskimi Bid Adapter: use credentials in requests (#12629) Co-authored-by: Andrius Versockas --- modules/eskimiBidAdapter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index 8b1beba09c3..5516656f467 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -232,7 +232,10 @@ function createRequest(bidRequests, bidderRequest, mediaType) { method: 'POST', url: getBidRequestUrlByRegion(), data: data, - options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} + options: { + withCredentials: true, + contentType: 'application/json;charset=UTF-8', + } } } From 24c4a07bd204378dbbcfe70240cda7b0108bbcfa Mon Sep 17 00:00:00 2001 From: cpcpn-emil <115714010+cpcpn-emil@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:57:17 +0100 Subject: [PATCH 0822/1097] ConceptX Bid Adapter: add gvlid (#12632) * New adapter: concepx * Syntax change * Revert syntax change * Defensive check for response from bidder server * Add better validation for the request * Merge branch 'master' of https://github.com/prebid/Prebid.js * Don't append url on every buildrequest * Add gvlId to conceptX --- modules/conceptxBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 87ac96f2131..47c50a4c0ad 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -5,10 +5,12 @@ import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'conceptx'; const ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; // const LOG_PREFIX = 'ConceptX: '; +const GVLID = 1340; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], + gvlid: GVLID, isBidRequestValid: function (bid) { return !!(bid.bidId && bid.params.site && bid.params.adunit); }, From 44ce97ed8c72ef0d7f65e3b20cd87fdb204410fd Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 9 Jan 2025 17:46:25 -0300 Subject: [PATCH 0823/1097] JW Player Video Adapter: Determine Size before player is rendered (#12624) * interpets size before render * checks for getContainer * updates test * tests size calculation * checks for NaN * uses proper vars --- .../videoModule/jwplayer/bidMarkedAsUsed.html | 4 +- .../jwplayer/bidRequestScheduling.html | 2 + .../jwplayer/bidsBackHandlerOverride.html | 2 + .../videoModule/jwplayer/eventListeners.html | 2 + .../videoModule/jwplayer/eventsUI.html | 2 + .../jwplayer/gamAdServerMediation.html | 2 + .../videoModule/jwplayer/mediaMetadata.html | 4 +- .../videoModule/jwplayer/playlist.html | 4 +- modules/jwplayerVideoProvider.js | 105 +++++++++++++++++- .../submodules/jwplayerVideoProvider_spec.js | 81 ++++++++++++++ 10 files changed, 199 insertions(+), 9 deletions(-) diff --git a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html index d0b261043e4..2246f4e76e3 100644 --- a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html +++ b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html @@ -48,7 +48,9 @@ params: { vendorConfig: { file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', - advertising: { client: 'vast' } + advertising: { client: 'vast' }, + width: 640, + height: 480 } } }, diff --git a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html index c40af32cac2..fac6b51b44e 100644 --- a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html +++ b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html @@ -24,6 +24,8 @@ params: { vendorConfig: { file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', + width: 640, + height: 480, advertising: { client: "googima", schedule: { diff --git a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html index 75a72ba3501..308809caa87 100644 --- a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html +++ b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html @@ -47,6 +47,8 @@ params: { vendorConfig: { file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', + width: 640, + height: 480, advertising: { client: 'vast' } } } diff --git a/integrationExamples/videoModule/jwplayer/eventListeners.html b/integrationExamples/videoModule/jwplayer/eventListeners.html index 6f04f37264b..e6f49af3e24 100644 --- a/integrationExamples/videoModule/jwplayer/eventListeners.html +++ b/integrationExamples/videoModule/jwplayer/eventListeners.html @@ -49,6 +49,8 @@ vendorConfig: { file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', mediaid: 'd9J2zcaA', + width: 640, + height: 480, advertising: { client: 'vast' } } } diff --git a/integrationExamples/videoModule/jwplayer/eventsUI.html b/integrationExamples/videoModule/jwplayer/eventsUI.html index cfd1efe7624..f86721a8b7e 100644 --- a/integrationExamples/videoModule/jwplayer/eventsUI.html +++ b/integrationExamples/videoModule/jwplayer/eventsUI.html @@ -53,6 +53,8 @@ file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', title: "Subaru Outback on Street and Dirt", }], + width: 640, + height: 480, advertising: { client: 'vast' } } } diff --git a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html index 1f4331785ea..b8228615fae 100644 --- a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html +++ b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html @@ -47,6 +47,8 @@ params: { vendorConfig: { file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', + width: 640, + height: 480, advertising: { client: 'googima' } } } diff --git a/integrationExamples/videoModule/jwplayer/mediaMetadata.html b/integrationExamples/videoModule/jwplayer/mediaMetadata.html index 63e62aa4b82..11762733f18 100644 --- a/integrationExamples/videoModule/jwplayer/mediaMetadata.html +++ b/integrationExamples/videoModule/jwplayer/mediaMetadata.html @@ -51,7 +51,9 @@ file: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4', title: 'Subaru Outback On Street And Dirt', description: 'Smoking Tire takes the all-new Subaru Outback to the highest point we can find in hopes our customer-appreciation Balloon Launch will get some free T-shirts into the hands of our viewers.', - advertising: { client: 'googima' } + advertising: { client: 'googima' }, + width: 640, + height: 480 } } } diff --git a/integrationExamples/videoModule/jwplayer/playlist.html b/integrationExamples/videoModule/jwplayer/playlist.html index 9e89f606f23..447817ffd80 100644 --- a/integrationExamples/videoModule/jwplayer/playlist.html +++ b/integrationExamples/videoModule/jwplayer/playlist.html @@ -63,7 +63,9 @@ title : "Sintel", description: "Sintel is an independently produced short film, initiated by the Blender Foundation as a means to further improve and validate the free/open source 3D creation suite Blender. With initial funding provided by 1000s of donations via the internet community, it has again proven to be a viable development model for both open 3D technology as for independent animation film.\nThis 15 minute film has been realized in the studio of the Amsterdam Blender Institute, by an international team of artists and developers. In addition to that, several crucial technical and creative targets have been realized online, by developers and artists and teams all over the world.\nwww.sintel.org", }], - advertising: { client: 'vast' } + advertising: { client: 'vast' }, + width: 640, + height: 480 } } } diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 54de1949e6f..e2b194057fd 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -50,6 +50,8 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba VIDEO_MIME_TYPE.AAC, VIDEO_MIME_TYPE.HLS ]; + let height = null; + let width = null; function init() { if (!jwplayer) { @@ -92,6 +94,20 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba const adConfig = config.advertising || {}; supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES); + if (height === null) { + height = utils.getPlayerHeight(player, config); + } + + if (width === null) { + width = utils.getPlayerWidth(player, config); + } + + if (config.aspectratio && !height && !width) { + const size = utils.getPlayerSizeFromAspectRatio(player, config); + height = size.height; + width = size.width; + } + const video = { mimes: supportedMediaTypes, protocols: [ @@ -102,8 +118,8 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba PROTOCOLS.VAST_3_0_WRAPPER, PROTOCOLS.VAST_4_0_WRAPPER ], - h: player.getHeight(), // TODO does player call need optimization ? - w: player.getWidth(), // TODO does player call need optimization ? + h: height, + w: width, startdelay: utils.getStartDelay(), placement: utils.getPlacement(adConfig, player), // linearity is omitted because both forms are supported. @@ -414,10 +430,14 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba break; case PLAYER_RESIZE: - getEventPayload = e => ({ - height: e.height, - width: e.width, - }); + getEventPayload = e => { + height = e.height; + width = e.width; + return { + height, + width + }; + }; break; case VIEWABLE: @@ -585,6 +605,79 @@ export const utils = { return jwConfig; }, + getPlayerHeight: function(player, config) { + let height; + + if (player.getHeight) { + height = player.getHeight(); + } + + // Height is undefined when player has not yet rendered + if (height !== undefined) { + return height; + } + + return config.height; + }, + + getPlayerWidth: function(player, config) { + let width; + + if (player.getWidth) { + width = player.getWidth(); + } + + // Width is undefined when player has not yet rendered + if (width !== undefined) { + return width; + } + + // Width can be a string when aspectratio is set + if (typeof config.width === 'number') { + return config.width; + } + }, + + getPlayerSizeFromAspectRatio: function(player, config) { + const aspectRatio = config.aspectratio; + let percentageWidth = config.width; + + if (typeof aspectRatio !== 'string' || typeof percentageWidth !== 'string') { + return {}; + } + + const ratios = aspectRatio.split(':'); + + if (ratios.length !== 2) { + return {}; + } + + const containerElement = player.getContainer && player.getContainer(); + if (!containerElement) { + return {}; + } + + const containerWidth = containerElement.clientWidth; + const containerHeight = containerElement.clientHeight; + + const xRatio = parseInt(ratios[0], 10); + const yRatio = parseInt(ratios[1], 10); + + if (isNaN(xRatio) || isNaN(yRatio)) { + return {}; + } + + const numericWidthPercentage = parseInt(percentageWidth, 10); + + const desiredWidth = containerWidth * numericWidthPercentage / 100; + const desiredHeight = Math.min(desiredWidth * yRatio / xRatio, containerHeight); + + return { + height: desiredHeight, + width: desiredWidth + }; + }, + getJwEvent: function(eventName) { switch (eventName) { case SETUP_COMPLETE: diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js index 3cede6c8eda..1a9643c7d3d 100644 --- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -26,6 +26,7 @@ function getPlayerMock() { getVolume: function () {}, getConfig: function () {}, getHeight: function () {}, + getContainer: function () {}, getWidth: function () {}, getFullscreen: function () {}, getPlaylistItem: function () {}, @@ -51,6 +52,9 @@ function makePlayerFactoryMock(playerMock_) { function getUtilsMock() { return { getJwConfig: function () {}, + getPlayerHeight: function () {}, + getPlayerWidth: function () {}, + getPlayerSizeFromAspectRatio: function () {}, getSupportedMediaTypes: function () {}, getStartDelay: function () {}, getPlacement: function () {}, @@ -212,6 +216,8 @@ describe('JWPlayerProvider', function () { player.getWidth = () => test_width; player.getFullscreen = () => true; // + utils.getPlayerHeight = () => 100; + utils.getPlayerWidth = () => 200; utils.getSupportedMediaTypes = () => [test_media_type]; utils.getStartDelay = () => test_start_delay; utils.getPlacement = () => test_placement; @@ -690,6 +696,81 @@ describe('utils', function () { expect(jwConfig.advertising).to.have.property('client', 'vast'); }); }); + + describe('getPlayerHeight', function () { + const getPlayerHeight = utils.getPlayerHeight; + + it('should return height from API when defined', function () { + const expectedHeight = 500; + const playerMock = { getHeight: () => expectedHeight }; + expect(getPlayerHeight(playerMock, {})).to.equal(expectedHeight); + }); + + it('should return height from config when API returns undefined', function () { + const expectedHeight = 500; + const playerMock = { getHeight: () => undefined }; + expect(getPlayerHeight(playerMock, { height: 500 })).to.equal(expectedHeight); + }); + }); + + describe('getPlayerWidth', function () { + const getPlayerWidth = utils.getPlayerWidth; + + it('should return width from API when defined', function () { + const expectedWidth = 1000; + const playerMock = { getWidth: () => expectedWidth }; + expect(getPlayerWidth(playerMock, {})).to.equal(expectedWidth); + }); + + it('should return width from config when API returns undefined', function () { + const expectedWidth = 1000; + const playerMock = { getWidth: () => undefined }; + expect(getPlayerWidth(playerMock, { width: expectedWidth })).to.equal(expectedWidth); + }); + + it('should return undefined when width is string', function () { + const playerMock = { getWidth: () => undefined }; + expect(getPlayerWidth(playerMock, { width: '50%' })).to.be.undefined; + }); + }); + + describe('getPlayerSizeFromAspectRatio', function () { + const getPlayerSizeFromAspectRatio = utils.getPlayerSizeFromAspectRatio; + const testContainer = { + clientWidth: 640, + clientHeight: 480 + }; + + it('should return an empty object when width and aspectratio are not strings', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {width: 100})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:2', width: 100})).to.deep.equal({}); + }); + + it('should return an empty object when aspectratio is malformed', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '0.5', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1-2', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: ':2', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: ':', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:2:3', width: '100%'})).to.deep.equal({}); + }); + + it('should return an empty object when player container cannot be obtained', function () { + expect(getPlayerSizeFromAspectRatio({}, {aspectratio: '1:2', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => undefined }, {aspectratio: '1:2', width: '100%'})).to.deep.equal({}); + }); + + it('should calculate the size given the width percentage and aspect ratio', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '2:1', width: '100%'})).to.deep.equal({ height: 320, width: 640 }); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '4:1', width: '70%'})).to.deep.equal({ height: 112, width: 448 }); + }); + + it('should return the container height when smaller than the calculated height', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:1', width: '100%'})).to.deep.equal({ height: 480, width: 640 }); + }); + }); + describe('getSkipParams', function () { const getSkipParams = utils.getSkipParams; From fab7207fad66ddb1e2a11b2194f63343f38579fc Mon Sep 17 00:00:00 2001 From: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:03:20 -0500 Subject: [PATCH 0824/1097] IX Bid Adapter: fix request options field [PB-3461] (#12637) * fix: fix requests options field [PB-3461] * feat: set credentials options field explicitly [PB-3461] * fix: fix lint issue [PB-3461] * chore: add fetch request header test [PB-3461] * chore: add fetch request header test [PB-3461] * chore: add fetch request header test [PB-3461] * chore: update fetch test [PB-3461] * chore: update fetch test [PB-3461] * chore: change fetch stub approach [PB-3461] --------- Co-authored-by: shahin.rahbariasl --- modules/ixBidAdapter.js | 3 +- test/spec/modules/ixBidAdapter_spec.js | 58 +++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 0ec60b51ca2..e2a712e0afc 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -751,8 +751,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { method: 'POST', url: exchangeUrl, data: deepClone(r), - option: { + options: { contentType: 'text/plain', + withCredentials: true }, validBidRequests }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index e0162617be3..5f75e224b50 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec, storage, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo } from '../../../modules/ixBidAdapter.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; +import * as ajaxLib from 'src/ajax.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/openrtb/pbjs'; @@ -2088,7 +2089,11 @@ describe('IndexexchangeAdapter', function () { it('request should be made to IX endpoint with POST method and siteId in query param', function () { expect(requestMethod).to.equal('POST'); expect(requestUrl).to.equal(IX_SECURE_ENDPOINT + '?s=' + DEFAULT_BANNER_VALID_BID[0].params.siteId); - expect(request.option.contentType).to.equal('text/plain') + }); + + it('request made to IX endpoint with POST method should have correct options fields set', function () { + expect(request.options.contentType).to.equal('text/plain') + expect(request.options.withCredentials).to.equal(true) }); it('auction type should be set correctly', function () { @@ -5003,6 +5008,7 @@ describe('IndexexchangeAdapter', function () { expect(result['b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7'].ixImps.length).to.equal(1); }); }); + describe('apply floors test', function () { it('video test', function() { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); @@ -5367,6 +5373,7 @@ describe('IndexexchangeAdapter', function () { expect(removeSiteIDs(request)).to.deep.equal(expected); }); }); + describe('addDeviceInfo', () => { it('should add device to request when device already exists', () => { let r = { @@ -5385,4 +5392,53 @@ describe('IndexexchangeAdapter', function () { expect(r.device.h).to.exist; }); }); + + describe('fetch requests', function () { + let ajaxStub; + + beforeEach(function () { + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return sinon.spy(function (url, callback, data, options) { + callback.success('OK'); + }); + }); + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should send the correct headers in the actual fetch call', function (done) { + const requests = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const request = requests[0]; + const ajax = ajaxLib.ajaxBuilder(); + + ajax( + request.url, + { + success: () => { + try { + sinon.assert.calledOnce(ajaxStub); + const ajaxCall = ajaxStub.returnValues[0]; + sinon.assert.calledOnce(ajaxCall); + const [calledUrl, callback, calledData, calledOptions] = ajaxCall.getCall(0).args; + + expect(calledUrl).to.equal(request.url); + expect(calledData).to.equal(request.data); + + expect(calledOptions.contentType).to.equal('text/plain'); + expect(calledOptions.withCredentials).to.be.true; + + done(); + } catch (err) { + done(err); + } + }, + error: (err) => done(err || new Error('Ajax request failed')), + }, + request.data, + request.options + ); + }); + }); }); From 7a210da2865dcd97bfb5812f0f94bb18d78a57a4 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Fri, 10 Jan 2025 20:18:19 +0000 Subject: [PATCH 0825/1097] Vidazoo bid adapter add ortb2 device (#12640) * Vidazoo Bid Adapter: Add ORTB2 device data to request payload * Vidazoo Bid Adapter: Update exco adapter tests to include ORTB2 device --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- libraries/vidazooUtils/bidderUtils.js | 2 + test/spec/modules/excoBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/illuminBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/kueezRtbBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/shinezRtbBidAdapter_spec.js | 51 +++++++++++------- test/spec/modules/tagorasBidAdapter_spec.js | 51 +++++++++++------- .../modules/twistDigitalBidAdapter_spec.js | 52 ++++++++++++------- test/spec/modules/vidazooBidAdapter_spec.js | 52 ++++++++++++------- 8 files changed, 235 insertions(+), 126 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index df947142a4c..9de95248c74 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -247,6 +247,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); const contentLang = deepAccess(bidderRequest, 'ortb2.site.content.language') || document.documentElement.lang; const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa', 0); + const device = deepAccess(bidderRequest, 'ortb2.device', {}); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ @@ -290,6 +291,7 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder bidderRequestsCount: bidderRequestsCount, bidderWinsCount: bidderWinsCount, bidderTimeout: bidderTimeout, + device, ...uniqueRequestData }; diff --git a/test/spec/modules/excoBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js index 39844f0bc6a..be85d7735d6 100644 --- a/test/spec/modules/excoBidAdapter_spec.js +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -315,6 +328,7 @@ describe('ExcoBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -386,6 +400,7 @@ describe('ExcoBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 3cd79c7468d..b4578a8d61f 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -315,6 +328,7 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -386,6 +400,7 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index d3323a205b3..0bb65d0aef0 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -93,6 +93,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -116,24 +146,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -321,6 +334,7 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -394,6 +408,7 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index ebd2e987491..892d88b3b7b 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -119,24 +149,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -317,6 +330,7 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -388,6 +402,7 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 6ebe2896ffc..8d8c8cb62b7 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -314,6 +327,7 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -384,6 +398,7 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index c15a6d1d909..4ddac3261f1 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -129,24 +159,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - }, + device: ORTB2_DEVICE, user: { data: [ { @@ -341,6 +354,7 @@ describe('TwistDigitalBidAdapter', function () { }, contentLang: 'en', coppa: 0, + device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -422,6 +436,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -510,6 +525,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 14a49b21cdc..01d7aa20e53 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -98,6 +98,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -133,24 +163,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - }, + device: ORTB2_DEVICE, user: { data: [ { @@ -346,6 +359,7 @@ describe('VidazooBidAdapter', function () { }, contentLang: 'en', coppa: 0, + device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -430,6 +444,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -523,6 +538,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, From 16d892e21d00c122b97e94d9c65f902c4b4af2fc Mon Sep 17 00:00:00 2001 From: "Tulio S. Duarte" Date: Mon, 13 Jan 2025 11:14:27 -0300 Subject: [PATCH 0826/1097] Blue Bid Adapter : initial release (#12513) * add blue * uncommited stuff * uncommited stuff * uncommited stuff * less duplicated code * less duplicated code * less duplicated code * less duplicated code * less duplicated code * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * add floor module support * add floor module support * add floor module support * add floor module support * add floor module support * only import what we need * Prebib --------- Co-authored-by: Tulio Duarte --- modules/blueBidAdapter.js | 122 +++++++++++++++++++++++ modules/blueBidAdapter.md | 28 ++++++ test/spec/modules/blueBidAdapter_spec.js | 91 +++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 modules/blueBidAdapter.js create mode 100644 modules/blueBidAdapter.md create mode 100755 test/spec/modules/blueBidAdapter_spec.js diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js new file mode 100644 index 00000000000..ba8d0a9e8fe --- /dev/null +++ b/modules/blueBidAdapter.js @@ -0,0 +1,122 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { deepSetValue, isFn, isPlainObject } from '../src/utils.js'; + +const BIDDER_CODE = 'blue'; +const ENDPOINT_URL = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const GVLID = 620; // GVLID for your bidder +const COOKIE_NAME = 'ckid'; // Cookie name for identifying users +const CURRENCY = 'USD'; // Currency used in bid floors + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +const converter = ortbConverter({ + context: { + netRevenue: true, // Default netRevenue setting + ttl: 100, // Default time-to-live for bid responses + }, + imp, + request, +}); + +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'site.publisher.id', context.publisherId); + return request; +} + +function imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floor = getBidFloor(bidRequest); + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = CURRENCY; + } + return imp; +} + +function getBidFloor(bid) { + if (isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: CURRENCY, + mediaType: BANNER, + size: '*', + }); + if ( + isPlainObject(floor) && + !isNaN(floor.floor) && + floor.currency === CURRENCY + ) { + return floor.floor; + } + } + return null; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], // Supported ad types + + // Validate bid request + isBidRequestValid: function (bid) { + return !!bid.params.placementId && !!bid.params.publisherId; + }, + + // Build OpenRTB requests using `ortbConverter` + buildRequests: function (validBidRequests, bidderRequest) { + const context = { + publisherId: validBidRequests.find( + (bidRequest) => bidRequest.params?.publisherId + )?.params.publisherId, + }; + + const ortbRequest = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context, + }); + + // Add GVLID and cookie ID to the request + ortbRequest.ext = ortbRequest.ext || {}; + deepSetValue(ortbRequest, 'ext.gvlid', GVLID); + + // Include user cookie if available + const ckid = storage.getDataFromLocalStorage('blueID') || storage.getCookie(COOKIE_NAME) || null; + if (ckid) { + deepSetValue(ortbRequest, 'user.ext.buyerid', ckid); + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'text/plain', + }, + }; + }, + + // Interpret OpenRTB responses using `ortbConverter` + interpretResponse: function (serverResponse, request) { + const ortbResponse = serverResponse.body; + + // Parse the OpenRTB response into Prebid bid responses + const prebidResponses = converter.fromORTB({ + response: ortbResponse, + request: request.data, + }).bids; + + // Example: Modify bid responses if needed + prebidResponses.forEach((bid) => { + bid.meta = bid.meta || {}; + bid.meta.adapterVersion = '1.0.0'; + }); + + return prebidResponses; + }, +}; + +registerBidder(spec); diff --git a/modules/blueBidAdapter.md b/modules/blueBidAdapter.md new file mode 100644 index 00000000000..4f446b1ff4b --- /dev/null +++ b/modules/blueBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +Module Name: Blue Bidder Adapter +Module Type: Bidder Adapter +Maintainer: celsooliveira@getblue.io + +# Description + +Module that connects to Blue's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[300, 250], [728, 90]], + bids: [ + { + bidder: 'blue', + params: { + publisherId: "xpto", + placementId: "xpto", + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/blueBidAdapter_spec.js b/test/spec/modules/blueBidAdapter_spec.js new file mode 100755 index 00000000000..f3f6f435f20 --- /dev/null +++ b/test/spec/modules/blueBidAdapter_spec.js @@ -0,0 +1,91 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec, storage } from 'modules/blueBidAdapter.js'; + +const BIDDER_CODE = 'blue'; +const ENDPOINT_URL = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const GVLID = 620; +const COOKIE_NAME = 'ckid'; +const CURRENCY = 'USD'; + +describe('blueBidAdapter:', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid:', function () { + it('should return true for valid bid requests', function () { + const validBid = { + params: { + placementId: '12345', + publisherId: '67890', + }, + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false for invalid bid requests', function () { + const invalidBid = { + params: { + placementId: '12345', + }, + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests:', function () { + let validBidRequests; + let bidderRequest; + + beforeEach(function () { + validBidRequests = [ + { + bidId: 'bid1', + params: { + placementId: '12345', + publisherId: '67890', + }, + getFloor: () => ({ currency: CURRENCY, floor: 1.5 }), + }, + ]; + + bidderRequest = { + refererInfo: { + page: 'https://example.com', + }, + }; + + sandbox.stub(storage, 'getDataFromLocalStorage').returns('testBuyerId'); + }); + + it('should build a valid OpenRTB request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.options.contentType).to.equal('text/plain'); + + const ortbRequest = JSON.parse(request.data); + expect(ortbRequest.ext.gvlid).to.equal(GVLID); + expect(ortbRequest.user.ext.buyerid).to.equal('testBuyerId'); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.5); + expect(ortbRequest.imp[0].bidfloorcur).to.equal(CURRENCY); + }); + + it('should omit bidfloor if getFloor is not implemented', function () { + validBidRequests[0].getFloor = undefined; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const ortbRequest = JSON.parse(request.data); + + expect(ortbRequest.imp[0].bidfloor).to.be.undefined; + }); + }); +}); From 743f100fcfc2551b79cc1e677170a4e1796ebd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriela=20Mi=C4=99dlar?= <155444733+gmiedlar-ox@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:16:30 +0100 Subject: [PATCH 0827/1097] OpenX Bid Adapter : support native (#12625) * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * Revert "Merge remote-tracking branch 'origin/EXCH-10877-add-support-for-native-imps' into EXCH-10877-add-support-for-native-imps" This reverts commit c88138628dda3ac9409c1846d2baaf022521c4d6, reversing changes made to 55a23a5f526e2a4d2cebb4fe4bea4369dde22859. * [EXCH-10877] Add info about native support to the docs * [EXCH-10877] create const, use null instead of undefined * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * Revert "Merge remote-tracking branch 'origin/EXCH-10877-add-support-for-native-imps' into EXCH-10877-add-support-for-native-imps" This reverts commit c88138628dda3ac9409c1846d2baaf022521c4d6, reversing changes made to 55a23a5f526e2a4d2cebb4fe4bea4369dde22859. * [EXCH-10877] Add info about native support to the docs * [EXCH-10877] create const, use null instead of undefined * [EXCH-10877] Fix merge issues * [EXCH-10877] Use various params.platform in test --- modules/openxBidAdapter.js | 25 ++- modules/openxBidAdapter.md | 47 ++++- test/spec/modules/openxBidAdapter_spec.js | 244 +++++++++++++++++++++- 3 files changed, 301 insertions(+), 15 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 19da19e661f..cf8a54242de 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -2,7 +2,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import {mergeDeep} from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const bidderConfig = 'hb_pb_ortb'; @@ -13,7 +13,7 @@ export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; export const spec = { code: 'openx', gvlid: 69, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid, buildRequests, interpretResponse, @@ -25,7 +25,12 @@ registerBidder(spec); const converter = ortbConverter({ context: { netRevenue: true, - ttl: 300 + ttl: 300, + nativeRequest: { + eventtrackers: [ + {event: 1, methods: [1, 2]}, + ] + } }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); @@ -158,8 +163,11 @@ function isBidRequestValid(bidRequest) { function buildRequests(bids, bidderRequest) { let videoBids = bids.filter(bid => isVideoBid(bid)); - let bannerBids = bids.filter(bid => isBannerBid(bid)); - let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; + let bannerAndNativeBids = bids.filter(bid => isBannerBid(bid) || isNativeBid(bid)) + // In case of multi-format bids remove `video` from mediaTypes as for video a separate bid request is built + .map(bid => ({...bid, mediaTypes: {...bid.mediaTypes, video: undefined}})); + + let requests = bannerAndNativeBids.length ? [createRequest(bannerAndNativeBids, bidderRequest, null)] : []; videoBids.forEach(bid => { requests.push(createRequest([bid], bidderRequest, VIDEO)); }); @@ -178,8 +186,13 @@ function isVideoBid(bid) { return utils.deepAccess(bid, 'mediaTypes.video'); } +function isNativeBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.native'); +} + function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); + const isNotVideoOrNativeBid = !isVideoBid(bid) && !isNativeBid(bid) + return utils.deepAccess(bid, 'mediaTypes.banner') || isNotVideoOrNativeBid; } function interpretResponse(resp, req) { diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index a39aa1580cd..61b6426d113 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -34,6 +34,16 @@ Please note you should only include either openxBidAdapter or openxOrtbBidAdapte | `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` +## Native + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true # Example ```javascript @@ -84,7 +94,42 @@ var adUnits = [ mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }]p + }] + }, + { + code: 'native1', + mediaTypes: { + native: { + ortb: { + ver: '1.2', + assets: [ + { + required: 1, + img: { + type: 1, + hmin: 50 + }, + }, { + required: 1, + title: { + len: 80 + } + } + ] + } + } + }, + bids: [{ + bidder: 'openx', + params: { + unit: '1611023124', + delDomain: 'PUBLISHER-d.openx.net', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + } + } + }] } ]; ``` diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 5dc60b25ab0..dde9e2bd7c8 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -249,12 +249,119 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('when request is for a native ad', function () { + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + } + describe('and request config uses mediaTypes', () => { + const nativeBidWithMediaTypes = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(nativeBidWithMediaTypes)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidNativeBidWithMediaTypes = Object.assign({}, nativeBidWithMediaTypes); + invalidNativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidNativeBidWithMediaTypes)).to.equal(false); + }); + }); + + describe('and request config uses both delDomain and platform', () => { + const nativeBidWithDelDomainAndPlatform = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(nativeBidWithDelDomainAndPlatform)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidNativeBidWithMediaTypes = Object.assign({}, nativeBidWithDelDomainAndPlatform); + invalidNativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidNativeBidWithMediaTypes)).to.equal(false); + }); + }); + }); }); describe('buildRequests()', function () { let bidRequestsWithMediaTypes; - let bidRequestsWithPlatform; let mockBidderRequest; + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + const nativeBidRequest = { + bidder: 'openx', + params: { + unit: '33', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: 'test-bid-id-3', + bidderRequestId: 'test-bid-request-3', + auctionId: 'test-auction-3', + transactionId: 'test-transactionId-3' + }; beforeEach(function () { mockBidderRequest = {refererInfo: {}}; @@ -302,16 +409,64 @@ describe('OpenxRtbAdapter', function () { }); context('common requests checks', function() { - it('should be able to handle multiformat requests', () => { + it('should be able to handle multiformat request - banner + video', () => { const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); multiformat.mediaTypes.video = { context: 'outstream', playerSize: [640, 480] + }; + const requests = spec.buildRequests([multiformat], mockBidderRequest); + expect(requests).to.have.length(2); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + expect(requests[0].data.imp[0].native).to.not.exist; + expect(requests[1].data.imp).to.have.length(1); + expect(requests[1].data.imp[0].banner).to.not.exist; + expect(requests[1].data.imp[0].native).to.not.exist; + if (FEATURES.VIDEO) { + expect(requests[1].data.imp[0].video).to.exist; + } + }) + + it('should be able to handle multiformat request - banner + native', () => { + const multiformat = utils.deepClone(nativeBidRequest); + multiformat.mediaTypes.banner = { + sizes: [[300, 250], [300, 600]] + } + const requests = spec.buildRequests([multiformat], mockBidderRequest); + expect(requests).to.have.length(1); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + if (FEATURES.NATIVE) { + expect(requests[0].data.imp[0].native).to.exist; + } + }) + + it('should be able to handle multiformat request - banner + video + native', () => { + const multiformat = utils.deepClone(nativeBidRequest); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] + }; + multiformat.mediaTypes.banner = { + sizes: [[300, 250]] } const requests = spec.buildRequests([multiformat], mockBidderRequest); - const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); - const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] - expect(outgoingFormats).to.have.members(expected); + expect(requests).to.have.length(2); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + if (FEATURES.NATIVE) { + expect(requests[0].data.imp[0].native).to.exist; + } + expect(requests[1].data.imp).to.have.length(1); + expect(requests[1].data.imp[0].banner).to.not.exist; + expect(requests[1].data.imp[0].native).to.not.exist; + if (FEATURES.VIDEO) { + expect(requests[1].data.imp[0].video).to.exist; + } }) it('should send bid request to openx url via POST', function () { @@ -329,11 +484,11 @@ describe('OpenxRtbAdapter', function () { it('should send platform id, if available', function () { bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '51ca3159-abc2-4035-8e00-fe26eaa09397'; const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); - expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[1].params.platform); }); it('should send openx adunit codes', function () { @@ -1226,6 +1381,7 @@ describe('OpenxRtbAdapter', function () { crid: 'test-creative-id', dealid: 'test-deal-id', adm: 'test-ad-markup', + mtype: 1, adomain: ['brand.com'], ext: { dsp_id: '123', @@ -1389,7 +1545,7 @@ describe('OpenxRtbAdapter', function () { expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); - it('should return the proper mediaType', function () { + it('should return the proper vastUrl', function () { const winUrl = 'https//my.win.url'; bidResponse.seatbid[0].bid[0].nurl = winUrl bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; @@ -1399,6 +1555,78 @@ describe('OpenxRtbAdapter', function () { }); } + if (FEATURES.NATIVE) { + context('when the response is a native', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + mtype: 4, + adm: '{"ver": "1.2", "assets": [{"id": 1, "required": 1,"title": {"text": "OpenX (Title)"}}], "link": {"url": "https://www.openx.com/"}, "eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + }] + }], + cur: 'AUS' + }; + }); + + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + + it('should return parsed adm JSON in native.ortb response field', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + + expect(bid.native.ortb).to.deep.equal({ + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: {text: 'OpenX (Title)'} + }], + link: {url: 'https://www.openx.com/'}, + eventtrackers: [{ + event: 1, + method: 1, + url: 'http://example.com/impression' + }] + }); + }); + }); + } + context('when the response contains FLEDGE interest groups config', function() { let response; From ea87c0e78eedb6d87f0dd50d9688e9b22add0118 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Mon, 13 Jan 2025 14:47:54 -0600 Subject: [PATCH 0828/1097] 33across ID System: Store hashed email when feature is enabled via config (#12630) * Allow to read hashed email from 33across global * refactoring of supplemental IDs * store hashed email as another FP supplemental ID. * split existing unit test about successful 33across ID system response. * should clear hashed email if 33x response doesn't contain ID * rename some of the internal 33x ID system variables * code review feedback. HEM sources order & var rename --- modules/33acrossIdSystem.js | 87 +++++-- modules/33acrossIdSystem.md | 3 +- test/spec/modules/33acrossIdSystem_spec.js | 290 ++++++++++++++++++++- 3 files changed, 354 insertions(+), 26 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index fb5a7712f1f..277cb8b2f6d 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -60,7 +60,7 @@ function calculateResponseObj(response) { }; } -function calculateQueryStringParams({ pid, hem }, gdprConsentData, enabledStorageTypes) { +function calculateQueryStringParams({ pid, pubProvidedHem }, gdprConsentData, enabledStorageTypes) { const uspString = uspDataHandler.getConsentData(); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); @@ -98,9 +98,9 @@ function calculateQueryStringParams({ pid, hem }, gdprConsentData, enabledStorag params.tp = encodeURIComponent(tp); } - const hemParam = hem || getStoredValue(STORAGE_HEM_KEY, enabledStorageTypes); - if (hemParam) { - params.sha256 = encodeURIComponent(hemParam); + const hem = pubProvidedHem || getStoredValue(STORAGE_HEM_KEY, enabledStorageTypes); + if (hem) { + params.sha256 = encodeURIComponent(hem); } return params; @@ -145,10 +145,51 @@ function getStoredValue(key, enabledStorageTypes) { return storedValue; } -function handleSupplementalId(key, id, storageConfig) { - id - ? storeValue(key, id, storageConfig) - : deleteFromStorage(key); +function filterEnabledSupplementalIds({ tp, fp, hem }, { storeFpid, storeTpid, envelopeAvailable }) { + const ids = []; + + if (storeFpid) { + ids.push( + /** + * [ + * , + * < ID value to store or remove >, + * < clear flag: indicates if existing storage item should be removed or not based on certain condition> + * ] + */ + [STORAGE_FPID_KEY, fp, !fp], + [STORAGE_HEM_KEY, hem, !envelopeAvailable] // Clear hashed email if envelope is not available + ); + } + + if (storeTpid) { + ids.push([STORAGE_TPID_KEY, tp, !tp]); + } + + return ids; +} + +function updateSupplementalIdStorage(supplementalId, storageConfig) { + const [ key, id, clear ] = supplementalId; + + if (clear) { + deleteFromStorage(key); + + return; + } + + if (id) { + storeValue(key, id, storageConfig); + } +} + +function handleSupplementalIds(ids, { enabledStorageTypes, expires, ...options }) { + filterEnabledSupplementalIds(ids, options).forEach((supplementalId) => { + updateSupplementalIdStorage(supplementalId, { + enabledStorageTypes, + expires + }) + }); } /** @type {Submodule} */ @@ -197,8 +238,10 @@ export const thirtyThreeAcrossIdSubmodule = { const { storeFpid = DEFAULT_1PID_SUPPORT, storeTpid = DEFAULT_TPID_SUPPORT, apiUrl = API_URL, - ...options + pid, + hem } = params; + const pubProvidedHem = hem || window._33across?.hem?.sha256; return { callback(cb) { @@ -218,19 +261,17 @@ export const thirtyThreeAcrossIdSubmodule = { }); } - if (storeFpid) { - handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, { - enabledStorageTypes, - expires: storageConfig.expires - }); - } - - if (storeTpid) { - handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, { - enabledStorageTypes, - expires: storageConfig.expires - }); - } + handleSupplementalIds({ + fp: responseObj.fp, + tp: responseObj.tp, + hem: pubProvidedHem + }, { + storeFpid, + storeTpid, + envelopeAvailable: !!responseObj.envelope, + enabledStorageTypes, + expires: storageConfig.expires + }); cb(responseObj.envelope); }, @@ -239,7 +280,7 @@ export const thirtyThreeAcrossIdSubmodule = { cb(); } - }, calculateQueryStringParams(options, gdprConsentData, enabledStorageTypes), { + }, calculateQueryStringParams({ pid, pubProvidedHem }, gdprConsentData, enabledStorageTypes), { method: 'GET', withCredentials: true }); diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index b6b68622344..7dabb08eebd 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -57,4 +57,5 @@ The following settings are available in the `params` property in `userSync.userI ### HEM Collection -33Across ID System supports user's hashed email, if available in storage. +33Across ID System supports user's hashed emails (HEMs). HEMs could be collected from 3 different sources in following +priority order: `hem` configuration parameter, global `_33across.hem.sha256` field or from storage (cookie or local storage). diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 93e6a41d928..99fe2d4921d 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -21,7 +21,7 @@ describe('33acrossIdSystem', () => { }); describe('getId', () => { - it('should call endpoint and handle valid response', () => { + it('should call endpoint', () => { const completeCallback = sinon.spy(); const { callback } = thirtyThreeAcrossIdSubmodule.getId({ @@ -50,7 +50,71 @@ describe('33acrossIdSystem', () => { const regExp = new RegExp('https://lexicon.33across.com/v1/envelope\\?pid=12345&gdpr=\\d&src=pbjs&ver=$prebid.version$'); expect(request.url).to.match(regExp); - expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; + }); + + context('when there\'s a successful response containing an ID', () => { + it('should execute the callback and pass the ID', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' + }, + expires: 1645667805067 + })); + + expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; + }); + + it('should NOT wipe any stored hashed email', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdHm')).to.be.false; + expect(setCookie.calledWithExactly('33acrossIdHm', '', sinon.match.string, 'Lax', 'foo.com')).to.be.false; + + removeDataFromLocalStorage.restore(); + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); }); const additionalOptions = { @@ -931,9 +995,194 @@ describe('33acrossIdSystem', () => { storage.getDataFromLocalStorage.restore(); }); + + context('and the enabled storage types include "html5"', () => { + it('should store the hashed email in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + hem: '33acrossIdHmValue+' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdHm', '33acrossIdHmValue+')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types include "cookie"', () => { + it('should store the hashed email in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + hem: '33acrossIdHmValue+' + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdHm', '33acrossIdHmValue+', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); }); context('when a hashed email is NOT provided via configuration options', () => { + context('but it\'s provided via global 33across object', () => { + beforeEach(() => { + window._33across = { + hem: { + sha256: 'fake-sha256-hashed-email+' + } + } + }); + + afterEach(() => { + delete window._33across; + }); + + it('should call endpoint with the hashed email included', () => { + const completeCallback = () => {}; + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + // No hashed email via config option. + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + // Prioritizes the hashed email given via global object over the one stored in LS. + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdHm') + .returns('33acrossIdHmValueLS'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('sha256=fake-sha256-hashed-email%2B'); + + storage.getDataFromLocalStorage.restore(); + }); + + context('and the enabled storage types include "html5"', () => { + it('should store the hashed email in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdHm', 'fake-sha256-hashed-email+')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types include "cookie"', () => { + it('should store the hashed email in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdHm', 'fake-sha256-hashed-email+', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); + }); + context('but it\'s provided via local storage', () => { it('should call endpoint with the hashed email included', () => { const completeCallback = () => {}; @@ -1205,6 +1454,43 @@ describe('33acrossIdSystem', () => { expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; }); + + it('should wipe any stored hashed email', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + // no envelope field + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdHm')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdHm', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + removeDataFromLocalStorage.restore(); + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); }); context('when the server returns an error status code', () => { From 09fa2008438bdb40c2a0bc1727ded74604fcc32b Mon Sep 17 00:00:00 2001 From: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:02:14 +0200 Subject: [PATCH 0829/1097] encode hints in report (#12652) --- libraries/intentIqConstants/intentIqConstants.js | 2 +- modules/intentIqAnalyticsAdapter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index ed9856fc213..2cc9acc1844 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -7,4 +7,4 @@ export const OPT_OUT = 'O'; export const BLACK_LIST = 'L'; export const CLIENT_HINTS_KEY = '_iiq_ch'; export const EMPTY = 'EMPTY' -export const VERSION = 0.24 +export const VERSION = 0.25 diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 1cf270117b7..e3b5625dc7b 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -304,7 +304,7 @@ function constructFullUrl(data) { '&jsver=' + VERSION + '&source=pbjs' + '&payload=' + JSON.stringify(report) + - '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints + + '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : ''); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); From f499398bfb335de4f603c66c8f4d038df393faf4 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 14 Jan 2025 09:53:33 -0800 Subject: [PATCH 0830/1097] UserID: allow any contents in EIDs (#12651) --- modules/userId/eids.js | 35 +++++++++++-- test/spec/modules/userId_spec.js | 86 +++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 7 deletions(-) diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 57341773184..ee194bdc71c 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,4 +1,4 @@ -import {deepClone, isFn, isStr} from '../../src/utils.js'; +import {logError, deepClone, isFn, isStr} from '../../src/utils.js'; /** * @typedef {import('./index.js').SubmodulePriorityMap} SubmodulePriorityMap @@ -38,7 +38,10 @@ function createEidObject(userIdData, subModuleKey, eidConf) { export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { const allEids = {}; function collect(eid) { - const key = JSON.stringify([eid.source?.toLowerCase(), eid.ext]); + const key = JSON.stringify([ + eid.source?.toLowerCase(), + ...Object.keys(eid).filter(k => !['uids', 'source'].includes(k)).sort().map(k => eid[k]) + ]); if (allEids.hasOwnProperty(key)) { allEids[key].uids.push(...eid.uids); } else { @@ -48,8 +51,25 @@ export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { Object.entries(bidRequestUserId).forEach(([name, values]) => { values = Array.isArray(values) ? values : [values]; - const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name, eidConfigs.get(name))); - eids.filter(eid => eid != null).forEach(collect); + const eidConf = eidConfigs.get(name); + let eids; + if (name === 'pubProvidedId') { + eids = deepClone(values); + } else if (typeof eidConf === 'function') { + try { + eids = eidConf(values); + if (!Array.isArray(eids)) { + eids = [eids]; + } + } catch (e) { + logError(`Could not generate EID for "${name}"`, e); + } + } else { + eids = values.map(value => createEidObject(value, name, eidConf)); + } + if (Array.isArray(eids)) { + eids.filter(eid => eid != null).forEach(collect); + } }) return Object.values(allEids); } @@ -64,7 +84,12 @@ export function getEids(priorityMap) { const submodule = submodules.find(mod => mod.idObj?.[key] != null); if (submodule) { idValues[key] = submodule.idObj[key]; - eidConfigs.set(key, submodule.submodule.eids?.[key]) + let eidConf = submodule.submodule.eids?.[key]; + if (typeof eidConf === 'function') { + // if eid config is given as a function, append the active module configuration to its args + eidConf = ((orig) => (...args) => orig(...args, submodule.config))(eidConf); + } + eidConfigs.set(key, eidConf); } }) return createEidsArray(idValues, eidConfigs); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index c4f333e56ac..7a81c8b5f6d 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -456,14 +456,35 @@ describe('User ID', function () { {'mockId2v1': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 1})}}), createMockIdSubmodule('mockId2v2', null, null, {'mockId2v2': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 2})}}), + createMockIdSubmodule('mockId2v3', null, null, { + 'mockId2v3'(ids) { + return { + source: 'mock2source', + inserter: 'ins', + ext: {v: 2}, + uids: ids.map(id => ({id, atype: 2})) + } + } + }), + createMockIdSubmodule('mockId2v4', null, null, { + 'mockId2v4'(ids) { + return ids.map(id => ({ + uids: [{id, atype: 0}], + source: 'mock2source', + inserter: 'ins', + ext: {v: 2} + })) + } + }) ]); }); - it('should group UIDs by source and ext', () => { + it('should group UIDs by everything except uid', () => { const eids = createEidsArray({ mockId1: ['mock-1-1', 'mock-1-2'], mockId2v1: ['mock-2-1', 'mock-2-2'], - mockId2v2: ['mock-2-1', 'mock-2-2'] + mockId2v2: ['mock-2-1', 'mock-2-2'], + mockId2v3: ['mock-2-1', 'mock-2-2'] }); expect(eids).to.eql([ { @@ -510,10 +531,50 @@ describe('User ID', function () { atype: 2, } ] + }, + { + source: 'mock2source', + inserter: 'ins', + ext: {v: 2}, + uids: [ + { + id: 'mock-2-1', + atype: 2, + }, + { + id: 'mock-2-2', + atype: 2, + } + ] } ]) }); + it('should group matching EIDs regardless of entry order', () => { + const eids = createEidsArray({ + mockId2v3: ['id1', 'id2'], + mockId2v4: ['id3'] + }); + expect(eids).to.eql([{ + source: 'mock2source', + inserter: 'ins', + uids: [ + { + id: 'id1', + atype: 2, + }, + { + id: 'id2', + atype: 2 + }, + { + id: 'id3', + atype: 0 + } + ], + ext: {v: 2} + }]) + }) it('when merging with pubCommonId, should not alter its eids', () => { const uid = { pubProvidedId: [ @@ -705,6 +766,27 @@ describe('User ID', function () { }); }); + it('pbjs.getUserIdsAsEids should pass config to eid function', async function () { + const eidFn = sinon.stub(); + init(config); + setSubmoduleRegistry([createMockIdSubmodule('mockId', null, null, { + mockId: eidFn + })]); + const moduleConfig = { + name: 'mockId', + value: {mockId: 'mockIdValue'}, + some: 'config' + }; + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [moduleConfig] + } + }); + await getGlobal().getUserIdsAsync(); + sinon.assert.calledWith(eidFn, ['mockIdValue'], moduleConfig); + }) + it('pbjs.getUserIdsAsEids should prioritize user ids according to config available to core', () => { init(config); setSubmoduleRegistry([ From f1019c1e9f499762d922509399b85e9bbfb2676f Mon Sep 17 00:00:00 2001 From: Jeff Palladino <1226357+jpalladino84@users.noreply.github.com> Date: Wed, 15 Jan 2025 06:12:32 -0700 Subject: [PATCH 0831/1097] symitriDapRtdProvider - Enable X2 Tokenize endpoint (#12636) * symitriDapRtdProvider - Enable X2 Tokenize endpoint * Adding test case for X2 Tokenize --------- Co-authored-by: Jeff Palladino --- modules/symitriDapRtdProvider.js | 8 ++--- modules/symitriDapRtdProvider.md | 4 +-- .../modules/symitriDapRtdProvider_spec.js | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 7bf523170fe..7befe826382 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -663,6 +663,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { switch (config.api_version) { case 'x1': case 'x1-dev': + case 'x2': method = 'POST'; path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize'; body = JSON.stringify(apiParams); @@ -685,6 +686,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { switch (config.api_version) { case 'x1': case 'x1-dev': + case 'x2': token = request.getResponseHeader(headerPrefix + '-DAP-Token'); break; } @@ -744,8 +746,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return; } - let path = '/data-activation/' + - config.api_version + + let path = '/data-activation/x1' + '/token/' + token + '/membership'; @@ -812,8 +813,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { error: (error, request) => { onError(request, request.status, error, onDone); } }; - let path = '/data-activation/' + - config.api_version + + let path = '/data-activation/x1' + '/token/' + token + '/membership/encrypt'; diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index e3429e24144..31dd506b791 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -42,7 +42,7 @@ pbjs.setConfig({ waitForIt: true, params: { apiHostname: '', - apiVersion: "x1", + apiVersion: 'x1'|'x2', apiAuthToken: '', domain: 'your-domain.com', identityType: 'simpleid'|'compositeid'|'hashedid'|'dap-signature:1.0.0', @@ -68,7 +68,7 @@ Please reach out to your Symitri account representative() to | name | String | Symitri Dap Rtd module name | 'symitriDap' always| | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | apiHostname | String | Hostname provided by Symitri | Please reach out to your Symitri account representative() for this value| -| apiVersion | String | This holds the API version | It should be "x1" always | +| apiVersion | String | This holds the API version | Please reach out to your Symitri account representative() for this value | | apiAuthToken | String | Symitri API AuthToken | Please reach out to your Symitri account representative() for this value | | domain | String | The domain name of your webpage | | | identityType | String | 'simpleid' or 'compositeid' or 'hashedid' or 'dap-signature:1.0.0' | Use 'simpleid' to pass email or other plain text ids and SymitriRTD Module will hash it. diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index ec3ba4fdbed..440949aeb52 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -74,6 +74,14 @@ describe('symitriDapRtdProvider', function() { 'identity': sampleIdentity } + const sampleX2Config = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x2', + 'domain': 'prebid.org', + 'segtax': 708, + 'identity': sampleIdentity + } + const esampleConfig = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', @@ -260,6 +268,34 @@ describe('symitriDapRtdProvider', function() { }); }); + describe('dapX2Tokenize', function () { + it('dapX2Tokenize error callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleX2Config)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, 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); + }); + + it('dapX2Tokenize success callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleX2Config)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, 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); + }); + }); + 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); From 0954a82f891c36efefabdfc29423b3c50cbbdd5f Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Wed, 15 Jan 2025 15:13:44 +0200 Subject: [PATCH 0832/1097] Missena Bid Adapter : send bid sizes (#12560) * Missena Bid Adapter : send bid sizes * Move function to utils * Move the function to the proper place --- libraries/sizeUtils/sizeUtils.js | 25 +++++++++++++++++++++ modules/missenaBidAdapter.js | 2 ++ test/spec/modules/missenaBidAdapter_spec.js | 15 ++++++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/libraries/sizeUtils/sizeUtils.js b/libraries/sizeUtils/sizeUtils.js index 41cdd71df89..c0fe8510d7e 100644 --- a/libraries/sizeUtils/sizeUtils.js +++ b/libraries/sizeUtils/sizeUtils.js @@ -27,3 +27,28 @@ export function getAdUnitSizes(adUnit) { } return sizes; } + +/** + * Normalize adUnit.mediaTypes.banner.sizes to Array.> + * + * @param {Array. | Array.>} bidSizes - value of adUnit.mediaTypes.banner.sizes. + * @returns {Array.>} - Normalized value. + */ + +export function normalizeBannerSizes(bidSizes) { + let sizes = []; + if (Array.isArray(bidSizes) && bidSizes.length === 2 && !Array.isArray(bidSizes[0])) { + sizes.push({ + width: parseInt(bidSizes[0], 10), + height: parseInt(bidSizes[1], 10), + }); + } else if (Array.isArray(bidSizes) && Array.isArray(bidSizes[0])) { + bidSizes.forEach((size) => { + sizes.push({ + width: parseInt(size[0], 10), + height: parseInt(size[1], 10), + }); + }); + } + return sizes; +} diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index cd5650e73c2..c5c8678e0b1 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -12,6 +12,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js'; +import { normalizeBannerSizes } from '../libraries/sizeUtils/sizeUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -87,6 +88,7 @@ function toPayload(bidRequest, bidderRequest) { payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0; payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; payload.screen = { height: screen.height, width: screen.width }; + payload.sizes = normalizeBannerSizes(bidRequest.mediaTypes.banner.sizes); return { method: 'POST', diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index afd96091d84..2ca262dcf7f 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -23,7 +23,6 @@ describe('Missena Adapter', function () { const bid = { bidder: 'missena', bidId: bidId, - sizes: [[1, 1]], mediaTypes: { banner: { sizes: [[1, 1]] } }, ortb2: { device: { @@ -55,14 +54,14 @@ describe('Missena Adapter', function () { const bidWithoutFloor = { bidder: 'missena', bidId: bidId, - sizes: [[1, 1]], - mediaTypes: { banner: { sizes: [[1, 1]] } }, + mediaTypes: { banner: { sizes: [1, 1] } }, params: { apiKey: API_KEY, placement: 'sticky', formats: ['sticky-banner'], }, }; + const consentString = 'AAAAAAAAA=='; const bidderRequest = { @@ -178,6 +177,16 @@ describe('Missena Adapter', function () { expect(payload.screen.height).to.equal(screen.height); }); + it('should send size', function () { + expect(payload.sizes[0].width).to.equal(1); + expect(payload.sizes[0].height).to.equal(1); + }); + + it('should send single size', function () { + expect(payloadNoFloor.sizes[0].width).to.equal(1); + expect(payloadNoFloor.sizes[0].height).to.equal(1); + }); + getDataFromLocalStorageStub.restore(); getDataFromLocalStorageStub = sinon.stub( storage, From ea2aaa908c9b0bc70d4da77c30cac81ec83f48df Mon Sep 17 00:00:00 2001 From: Alexander Clouter Date: Wed, 15 Jan 2025 13:14:47 +0000 Subject: [PATCH 0833/1097] Adloox Analytics Module: apply 'js' param constraint (#12618) * Adloox Analytics: enforce only adlooxtracking.com as a subdomain may be used * Adloox Ad Server Video: remove un-necessary default parameter from test * Adloox Analytics: fix test Stop being clever for my own good with the NOOP function blatting, it is a non-idempotent operation --- modules/adlooxAnalyticsAdapter.js | 18 +++++++---- test/spec/modules/adlooxAdServerVideo_spec.js | 1 - .../modules/adlooxAnalyticsAdapter_spec.js | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 0a953584e26..c7321799f3c 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -81,8 +81,6 @@ const PARAMS_DEFAULT = { 'id11': '$ADLOOX_WEBSITE' }; -const NOOP = function() {}; - let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { track({ eventType, args }) { if (!analyticsAdapter[`handle_${eventType}`]) return; @@ -109,6 +107,10 @@ analyticsAdapter.enableAnalytics = function(config) { logError(MODULE, 'invalid js options value'); return; } + if (isStr(config.options.js) && !/\.adlooxtracking\.(com|ru)$/.test(parseUrl(config.options.js, { 'noDecodeWholeURL': true }).host)) { + logError(MODULE, "invalid js options value, must be a sub-domain of 'adlooxtracking.com'"); + return; + } if (!(config.options.toselector === undefined || isFn(config.options.toselector))) { logError(MODULE, 'invalid toselector options value'); return; @@ -221,20 +223,24 @@ analyticsAdapter.url = function(url, args, bid) { return url + a2qs(args); } +const preloaded = {}; analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; - analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; - - logMessage(MODULE, 'preloading verification JS'); const uri = parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); + const href = `${uri.protocol}://${uri.host}${uri.pathname}`; + if (preloaded[href]) return; + + logMessage(MODULE, 'preloading verification JS'); const link = document.createElement('link'); - link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); + link.setAttribute('href', href); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); // TODO fix rules violation insertElement(link); + + preloaded[href] = true; } analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index 58277bc830d..c941b9dc710 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -34,7 +34,6 @@ describe('Adloox Ad Server Video', function () { }; const analyticsOptions = { - js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', client: 'adlooxtest', clientid: 127, platformid: 0, diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index fa8204a9dc5..964eb6650e9 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -45,6 +45,11 @@ describe('Adloox Analytics Adapter', function () { adapter: analyticsAdapter }); describe('enableAnalytics', function () { + afterEach(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + }); + describe('invalid options', function () { it('should require options', function (done) { adapterManager.enableAnalytics({ @@ -68,6 +73,32 @@ describe('Adloox Analytics Adapter', function () { done(); }); + it('should accept subdomains of adlooxtracking.com for options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = 'https://test.adlooxtracking.com/test.js'; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.not.null; + + done(); + }); + + it('should reject non-subdomains of adlooxtracking.com for options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = 'https://example.com/test.js'; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + it('should reject non-function options.toselector', function (done) { const analyticsOptionsLocal = utils.deepClone(analyticsOptions); analyticsOptionsLocal.toselector = esplode; From ac8927271e1d4db7cded91c00d0d05a5b92233c4 Mon Sep 17 00:00:00 2001 From: Wiem Zine Elabidine Date: Wed, 15 Jan 2025 14:15:57 +0100 Subject: [PATCH 0834/1097] LiveIntent Rtd Provider: initial release (#12631) * implement liveIntentRtdProvider * fix test * trigger circleci * add typedef --- .../gpt/liveIntentRtdProviderExample.html | 164 ++++++++++++++++++ libraries/liveIntentId/shared.js | 7 +- modules/liveIntentRtdProvider.js | 52 ++++++ modules/liveIntentRtdProvider.md | 45 +++++ .../liveIntentExternalIdSystem_spec.js | 17 +- .../modules/liveIntentIdMinimalSystem_spec.js | 17 +- test/spec/modules/liveIntentIdSystem_spec.js | 17 +- .../modules/liveIntentRtdProvider_spec.js | 116 +++++++++++++ 8 files changed, 412 insertions(+), 23 deletions(-) create mode 100644 integrationExamples/gpt/liveIntentRtdProviderExample.html create mode 100644 modules/liveIntentRtdProvider.js create mode 100644 modules/liveIntentRtdProvider.md create mode 100644 test/spec/modules/liveIntentRtdProvider_spec.js diff --git a/integrationExamples/gpt/liveIntentRtdProviderExample.html b/integrationExamples/gpt/liveIntentRtdProviderExample.html new file mode 100644 index 00000000000..489df86f7a7 --- /dev/null +++ b/integrationExamples/gpt/liveIntentRtdProviderExample.html @@ -0,0 +1,164 @@ + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+
+
Div-2
+
+ +
+ + diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js index 509f91e44d9..f6a5d29c0a7 100644 --- a/libraries/liveIntentId/shared.js +++ b/libraries/liveIntentId/shared.js @@ -59,11 +59,8 @@ export function composeIdObject(value) { // old versions stored lipbid in unifiedId. Ensure that we can still read the data. const lipbid = value.nonId || value.unifiedId - if (lipbid) { - const lipb = { ...value, lipbid }; - delete lipb.unifiedId; - result.lipb = lipb; - } + result.lipb = lipbid ? { ...value, lipbid } : value + delete result.lipb?.unifiedId // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. // As adapters are applied in lexicographical order, we will always diff --git a/modules/liveIntentRtdProvider.js b/modules/liveIntentRtdProvider.js new file mode 100644 index 00000000000..92cd09ae346 --- /dev/null +++ b/modules/liveIntentRtdProvider.js @@ -0,0 +1,52 @@ +/** + * This module adds the LiveIntent provider to the Real Time Data module (rtdModule). + */ +import { submodule } from '../src/hook.js'; +import {deepAccess, deepSetValue} from '../src/utils.js' + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + +const SUBMODULE_NAME = 'liveintent'; +const GVLID = 148; + +/** + * Init + * @param {Object} config Module configuration + * @param {UserConsentData} userConsent User consent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +/** + * onBidRequest is called for each bidder during an auction and contains the bids for that bidder. + * + * @param {Object} bidRequest + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +function onBidRequest(bidRequest, config, userConsent) { + bidRequest.bids.forEach(bid => { + const providedSegmentsFromUserId = deepAccess(bid, 'userId.lipb.segments', []) + if (providedSegmentsFromUserId.length > 0) { + const providedSegments = { name: 'liveintent.com', segment: providedSegmentsFromUserId.map(id => ({ id })) } + const existingData = deepAccess(bid, 'ortb2.user.data', []) + deepSetValue(bid, 'ortb2.user.data', existingData.concat(providedSegments)) + } + }) +} + +export const liveIntentRtdSubmodule = { + name: SUBMODULE_NAME, + gvlid: GVLID, + init: init, + onBidRequestEvent: onBidRequest +}; + +submodule('realTimeData', liveIntentRtdSubmodule); diff --git a/modules/liveIntentRtdProvider.md b/modules/liveIntentRtdProvider.md new file mode 100644 index 00000000000..e742fb74bee --- /dev/null +++ b/modules/liveIntentRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +Module Name: LiveIntent Provider +Module Type: Rtd Provider +Maintainer: product@liveIntent.com + +# Description + +This module extracts segments from `bidRequest.userId.lipb.segments` enriched by the userID module and +injects them in `ortb2.user.data` array entry. + +Please visit [LiveIntent](https://www.liveIntent.com/) for more information. + +# Testing + +To run the example and test the Rtd provider: + +```sh +gulp serve --modules=appnexusBidAdapter,rtdModule,liveIntentRtdProvider,userId,liveIntentIdSystem +``` + +Open chrome with this URL: +`http://localhost:9999/integrationExamples/gpt/liveIntentRtdProviderExample.html` + +To run the unit test: +```sh +gulp test --file "test/spec/modules/liveIntentRtdProvider_spec.js" +``` + +# Integration + +```bash +gulp build --modules=userId,liveIntentIdSystem,rtdModule,liveIntentRtdProvider +``` + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders:[{ + name: 'liveintent', + waitForIt: true + }] + } +}); +``` diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js index 3e49eb5fc4b..0ee827a97b5 100644 --- a/test/spec/modules/liveIntentExternalIdSystem_spec.js +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -269,11 +269,6 @@ describe('LiveIntentExternalId', function() { expect(window.liQHub).to.have.length(1) // instead of 2 }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentExternalIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); - expect(result).to.be.eql({}); - }); - it('should decode a unifiedId to lipbId and remove it', function() { const result = liveIntentExternalIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); @@ -316,6 +311,11 @@ describe('LiveIntentExternalId', function() { }) }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentExternalIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -323,7 +323,7 @@ describe('LiveIntentExternalId', function() { it('should decode values with uid2 but no nonId', function() { const result = liveIntentExternalIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -460,4 +460,9 @@ describe('LiveIntentExternalId', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + + it('should decode the segments as part of lipb', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); }); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index a859b3e7995..7142b2cb2c1 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -49,11 +49,6 @@ describe('LiveIntentMinimalId', function() { expect(server.requests[0]).to.eql(undefined) }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ additionalData: 'data' }); - expect(result).to.be.eql({}); - }); - it('should initialize LiveConnect and send no data', function() { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); @@ -245,6 +240,11 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -252,7 +252,7 @@ describe('LiveIntentMinimalId', function() { it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -328,4 +328,9 @@ describe('LiveIntentMinimalId', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + + it('should decode the segments as part of lipb', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 50f51bd3dc8..8bdd9041f60 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -188,11 +188,6 @@ describe('LiveIntentId', function() { }, 300); }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ params: { fireEventDelay: 1, additionalData: 'data' } }); - expect(result).to.be.eql({}); - }); - it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { @@ -422,9 +417,14 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, { params: defaultConfigParams }); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -469,6 +469,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); + it('should decode the segments as part of lipb', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { diff --git a/test/spec/modules/liveIntentRtdProvider_spec.js b/test/spec/modules/liveIntentRtdProvider_spec.js new file mode 100644 index 00000000000..d3c34830dd0 --- /dev/null +++ b/test/spec/modules/liveIntentRtdProvider_spec.js @@ -0,0 +1,116 @@ +import {liveIntentRtdSubmodule} from 'modules/liveIntentRtdProvider.js'; +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; + +describe('LiveIntent Rtd Provider', function () { + const SUBMODULE_NAME = 'liveintent'; + + describe('submodule `init`', function () { + const config = { + name: SUBMODULE_NAME, + }; + it('init returns true when the subModuleName is defined', function () { + const value = liveIntentRtdSubmodule.init(config); + expect(value).to.equal(true); + }); + }) + + describe('submodule `onBidRequestEvent`', function () { + const bidRequestExample = { + bidderCode: 'appnexus', + auctionId: '8dbd7cb1-7f6d-4f84-946c-d0df4837234a', + bidderRequestId: '2a038c6820142b', + bids: [ + { + bidder: 'appnexus', + userId: { + lipb: { + segments: [ + 'asa_1231', + 'lalo_4311', + 'liurl_99123' + ] + } + } + } + ] + } + + it('exists', function () { + expect(liveIntentRtdSubmodule.onBidRequestEvent).to.be.a('function'); + }); + + it('undefined segments field does not change the ortb2', function() { + const bidRequest = { + bidderCode: 'appnexus', + auctionId: '8dbd7cb1-7f6d-4f84-946c-d0df4837234a', + bidderRequestId: '2a038c6820142b', + bids: [ + { + bidder: 'appnexus', + ortb2: {} + } + ] + } + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + expect(ortb2).to.deep.equal({}); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when the ortb2 is undefined', function() { + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when user is undefined', function() { + bidRequestExample.bids[0].ortb2 = { source: {} } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when data is undefined', function() { + bidRequestExample.bids[0].ortb2 = { + source: {}, + user: {} + } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data with the existing data', function() { + bidRequestExample.bids[0].ortb2 = { + source: {}, + user: { + data: [ + { + name: 'example.com', + segment: [ + { id: 'a_1231' }, + { id: 'b_4311' } + ] + } + ] + } + } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'example.com', segment: [{id: 'a_1231'}, {id: 'b_4311'}]}, {name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + }); +}); From 8ed696d48d2c5bf7ce344b98ed734e0a647e622a Mon Sep 17 00:00:00 2001 From: escalax Date: Wed, 15 Jan 2025 16:14:19 +0200 Subject: [PATCH 0835/1097] fix: source and account params (#12657) --- modules/escalaxBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/escalaxBidAdapter.js b/modules/escalaxBidAdapter.js index 70a10d748bc..027e41d7c56 100644 --- a/modules/escalaxBidAdapter.js +++ b/modules/escalaxBidAdapter.js @@ -84,8 +84,8 @@ export const spec = { const subdomain = getSubdomain(); const endpointURL = ESCALAX_URL .replace(ESCALAX_SUBDOMAIN_MACRO, subdomain || ESCALAX_DEFAULT_SUBDOMAIN) - .replace(ESCALAX_ACCOUNT_ID_MACRO, sourceId) - .replace(ESCALAX_SOURCE_ID_MACRO, accountId); + .replace(ESCALAX_SOURCE_ID_MACRO, sourceId) + .replace(ESCALAX_ACCOUNT_ID_MACRO, accountId); const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); return { method: 'POST', From c52ee5d92ba192a79c536fb2df44f2d7a0bd09ce Mon Sep 17 00:00:00 2001 From: Sean McGroarty Date: Wed, 15 Jan 2025 16:52:54 +0100 Subject: [PATCH 0836/1097] feat(docs): updates permutive GDPR docs (#12660) Ensures docs are up to date & have clear instructions --- modules/permutiveRtdProvider.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 9399dffab93..3cf3ed2b367 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -50,14 +50,15 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd #### Context -Permutive is not listed as a TCF vendor as all data collection is on behalf of the publisher and based on consent the publisher has received from the user. -Rather than through the TCF framework, this consent is provided to Permutive when the user gives the relevant permissions on the publisher website which allow the Permutive SDK to run. -This means that if GDPR enforcement is configured _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. -As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Permutive needs to be labeled within the Vendor Exceptions +While Permutive is listed as a TCF vendor (ID: 361), Permutive does not obtain consent directly from the TCF. As we act as a processor on behalf of our publishers consent is given to the Permutive SDK by the publisher, not by the [GDPR Consent Management Module](https://prebid-docs.atre.net/dev-docs/modules/consentManagement.html). + +This means that if GDPR enforcement is configured within the Permutive SDK _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. + +If you are also using the [TCF Control Module](https://docs.prebid.org/dev-docs/modules/tcfControl.html), in order to prevent Permutive from being blocked, it needs to be labeled within the Vendor Exceptions. #### Instructions -1. Publisher enables rules within Prebid GDPR module +1. Publisher enables rules within Prebid.js configuration. 2. Label Permutive as an exception, as shown below. ```javascript [ From b5f9de7eb17dc7de04d07220e55fd041554cb84c Mon Sep 17 00:00:00 2001 From: michachen Date: Wed, 15 Jan 2025 18:41:56 +0200 Subject: [PATCH 0837/1097] Rise Bid Adapters: native and multiformat support (#12653) * RPRD-1638: Add support for Native media type and multi-format bid requests in `index.js`, Populate the changes on `rise/minutemedia/openweb/shinez/stn/BidAdapter.js`, Update all relevant `***BidAdapter_spec.js`, Update all relevant `***BidAdapter.md`, Keep backwards compatibility, Move `mimes` and `api` determination to VIDEO media type as its only relevant to video. * RPRD-1638: Move all `spec` code duplication to `index.js` and populate across all maintained adapters via `makeBaseSpec` factory function, Move all rise related constants to `constants.js`. * RPRD-1638: Align with seller response. * RPRD-1638: fix cr comments * RPRD-1638: Align tests with native response * circle ci test --- libraries/riseUtils/constants.js | 20 ++ libraries/riseUtils/index.js | 181 ++++++++++++++---- modules/minutemediaBidAdapter.js | 86 +-------- modules/minutemediaBidAdapter.md | 2 +- modules/openwebBidAdapter.js | 87 +-------- modules/openwebBidAdapter.md | 2 +- modules/riseBidAdapter.js | 103 ++-------- modules/riseBidAdapter.md | 2 +- modules/shinezBidAdapter.js | 84 +------- modules/shinezBidAdapter.md | 4 +- modules/stnBidAdapter.js | 93 +-------- modules/stnBidAdapter.md | 2 +- .../modules/minutemediaBidAdapter_spec.js | 132 ++++++++++++- test/spec/modules/openwebBidAdapter_spec.js | 132 ++++++++++++- test/spec/modules/riseBidAdapter_spec.js | 132 ++++++++++++- test/spec/modules/shinezBidAdapter_spec.js | 142 +++++++++++++- test/spec/modules/stnBidAdapter_spec.js | 131 ++++++++++++- 17 files changed, 840 insertions(+), 495 deletions(-) create mode 100644 libraries/riseUtils/constants.js diff --git a/libraries/riseUtils/constants.js b/libraries/riseUtils/constants.js new file mode 100644 index 00000000000..4acb9920291 --- /dev/null +++ b/libraries/riseUtils/constants.js @@ -0,0 +1,20 @@ +import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js'; + +const OW_GVLID = 280 +export const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; +export const ADAPTER_VERSION = '7.0.0'; +export const DEFAULT_TTL = 360; +export const DEFAULT_CURRENCY = 'USD'; +export const BASE_URL = 'https://hb.yellowblue.io/'; +export const BIDDER_CODE = 'rise'; +export const DEFAULT_GVLID = 1043; + +export const ALIASES = [ + { code: 'risexchange', gvlid: DEFAULT_GVLID }, + { code: 'openwebxchange', gvlid: OW_GVLID } +] + +export const MODES = { + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' +}; diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index 60f31ef2603..3046e6dcf4a 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -1,34 +1,135 @@ import { - isArray, - isFn, + contains, deepAccess, + getBidIdParameter, + isArray, isEmpty, - contains, + isFn, isInteger, - getBidIdParameter, - isPlainObject + isPlainObject, + logInfo, + triggerPixel } from '../../src/utils.js'; -import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js'; import {config} from '../../src/config.js'; +import {ADAPTER_VERSION, DEFAULT_CURRENCY, DEFAULT_TTL, SUPPORTED_AD_TYPES} from './constants.js'; + +export const makeBaseSpec = (baseUrl, modes) => { + return { + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; + + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; + const rtbDomain = generalObject.params.rtbDomain || baseUrl; + + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode, rtbDomain, modes), + data: combinedRequestsObject + } + }, + interpretResponse: function ({ body }) { + const bidResponses = []; + + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = buildBidResponse(adUnit); + bidResponses.push(bidResponse); + }); + } + + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { + syncs.push({ + type: 'iframe', + url: deepAccess(response, 'body.params.userSyncURL') + }); + } + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }); + syncs.push(...pixels); + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + } + } +} + +export function getBidRequestMediaTypes(bidRequest) { + const mediaTypes = deepAccess(bidRequest, 'mediaTypes'); + if (isPlainObject(mediaTypes)) { + return Object.keys(mediaTypes); + } + return []; +} + +export function getPos(bidRequest) { + const mediaTypes = getBidRequestMediaTypes(bidRequest); + const firstMediaType = mediaTypes[0]; + if (mediaTypes.length === 1) { + return deepAccess(bidRequest, `mediaTypes.${firstMediaType}.pos`); + } +} + +export function getName(bidRequest) { + const mediaTypes = getBidRequestMediaTypes(bidRequest); + const firstMediaType = mediaTypes[0]; + if (mediaTypes.length === 1) { + return deepAccess(bidRequest, `mediaTypes.${firstMediaType}.name`); + } +} -export function getFloor(bid, mediaType) { +export function getFloor(bid) { if (!isFn(bid.getFloor)) { return 0; } + + const mediaTypes = getBidRequestMediaTypes(bid) + const firstMediaType = mediaTypes[0]; + let floorResult = bid.getFloor({ currency: 'USD', - mediaType: mediaType, + mediaType: mediaTypes.length === 1 ? firstMediaType : '*', size: '*' }); return isPlainObject(floorResult) && floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0; } -export function getSizesArray(bid, mediaType) { +export function getSizesArray(bid) { let sizesArray = []; - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + const mediaTypes = getBidRequestMediaTypes(bid); + const firstMediaType = mediaTypes[0]; + + if (mediaTypes.length === 1 && deepAccess(bid, `mediaTypes.${firstMediaType}.sizes`)) { + sizesArray = bid.mediaTypes[firstMediaType].sizes; + } else if (isArray(bid.sizes) && bid.sizes.length > 0) { sizesArray = bid.sizes; } @@ -111,18 +212,17 @@ export function generateBidsParams(validBidRequests, bidderRequest) { export function generateBidParameters(bid, bidderRequest) { const { params } = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); + const mediaTypes = getBidRequestMediaTypes(bid); if (isNaN(params.floorPrice)) { params.floorPrice = 0; } const bidObject = { - mediaType, + mediaType: mediaTypes.join(','), adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + sizes: getSizesArray(bid), + floorPrice: Math.max(getFloor(bid), params.floorPrice), bidId: getBidIdParameter('bidId', bid), loop: bid.bidderRequestsCount || 0, bidderRequestId: getBidIdParameter('bidderRequestId', bid), @@ -130,8 +230,8 @@ export function generateBidParameters(bid, bidderRequest) { coppa: 0, }; - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { + const pos = getPos(bid); + if (isInteger(pos)) { bidObject.pos = pos; } @@ -140,21 +240,11 @@ export function generateBidParameters(bid, bidderRequest) { bidObject.gpid = gpid; } - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + const placementId = params.placementId || getName(bid); if (placementId) { bidObject.placementId = placementId; } - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - const sua = deepAccess(bid, `ortb2.device.sua`); if (sua) { bidObject.sua = sua; @@ -165,11 +255,11 @@ export function generateBidParameters(bid, bidderRequest) { bidObject.coppa = 1; } - if (mediaType === VIDEO) { + if (mediaTypes.includes(VIDEO)) { const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); let playbackMethodValue; - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + if (isArray(playbackMethod) && isInteger(playbackMethod[0])) { playbackMethodValue = playbackMethod[0]; } else if (isInteger(playbackMethod)) { playbackMethodValue = playbackMethod; @@ -213,19 +303,36 @@ export function generateBidParameters(bid, bidderRequest) { if (plcmt) { bidObject.plcmt = plcmt; } + + const mimes = deepAccess(bid, `mediaTypes.video.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + + const api = deepAccess(bid, `mediaTypes.video.api`); + if (api) { + bidObject.api = api; + } + } + + if (mediaTypes.includes(NATIVE)) { + const nativeOrtbRequest = deepAccess(bid, `nativeOrtbRequest`); + if (nativeOrtbRequest) { + bidObject.nativeOrtbRequest = nativeOrtbRequest; + } } return bidObject; } -export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { +export function buildBidResponse(adUnit) { const bidResponse = { requestId: adUnit.requestId, cpm: adUnit.cpm, currency: adUnit.currency || DEFAULT_CURRENCY, width: adUnit.width, height: adUnit.height, - ttl: adUnit.ttl || TTL, + ttl: adUnit.ttl || DEFAULT_TTL, creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, @@ -239,6 +346,8 @@ export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { bidResponse.vastXml = adUnit.vastXml; } else if (adUnit.mediaType === BANNER) { bidResponse.ad = adUnit.ad; + } else if (adUnit.mediaType === NATIVE) { + bidResponse.native = {ortb: adUnit.native}; } if (adUnit.adomain && adUnit.adomain.length) { @@ -248,10 +357,6 @@ export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { return bidResponse; } -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - export function generateGeneralParams(generalObject, bidderRequest, adapterVersion) { const domain = window.location.hostname; const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 4e83c5c6db4..d5aae8a84d0 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -1,35 +1,19 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; const BASE_URL = 'https://hb.minutemedia-prebid.com/'; +const GVLID = 918; const MODES = { PRODUCTION: 'hb-mm-multi', TEST: 'hb-multi-mm-test' }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - gvlid: 918, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, + gvlid: GVLID, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to MinuteMedia adapter'); @@ -42,64 +26,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - }; - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - }; - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 66b54adaf0e..b22cf856364 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to MinuteMedia's demand sources. The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 60364f41d3c..0dff314ce17 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,35 +1,19 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; const BASE_URL = 'https://hb.openwebmp.com/'; +const GVLID = 280; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - gvlid: 280, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, + gvlid: GVLID, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to OpenWeb adapter'); @@ -47,65 +31,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({body}) { - const bidResponses = []; - - if (body && body.bids && body.bids.length) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md index 5450182265c..c5bc10c3c12 100644 --- a/modules/openwebBidAdapter.md +++ b/modules/openwebBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to OpenWeb's demand sources. The OpenWeb adapter requires setup and approval from OpenWeb. Please reach out to monetization@openweb.com to create an OpenWeb account. -The adapter supports Video and Display demand. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 236c048982a..a6970e959ce 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,40 +1,19 @@ +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; - -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const BIDDER_CODE = 'rise'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_GVLID = 1043; -const BASE_URL = 'https://hb.yellowblue.io/'; -const MODES = { - PRODUCTION: 'hb-multi', - TEST: 'hb-multi-test' -}; + ALIASES, + BASE_URL, + BIDDER_CODE, + DEFAULT_GVLID, + MODES, +} from '../libraries/riseUtils/constants.js'; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - aliases: [ - { code: 'risexchange', gvlid: DEFAULT_GVLID }, - { code: 'openwebxchange', gvlid: 280 } - ], + aliases: ALIASES, gvlid: DEFAULT_GVLID, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Rise adapter'); @@ -47,66 +26,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - const rtbDomain = generalObject.params.rtbDomain || BASE_URL; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, rtbDomain, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 94d36a08510..2d355cd0cb6 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Rise's demand sources. The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-rise-engage@risecodes.com to create an Rise account. -The adapter supports Video(instream). +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 89f39284bed..f88360c4c38 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -1,24 +1,8 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'shinez'; -const ADAPTER_VERSION = '1.0.0'; -const TTL = 360; -const CURRENCY = 'USD'; const BASE_URL = 'https://hb.sweetgum.io/'; const MODES = { PRODUCTION: 'hb-sz-multi', @@ -26,9 +10,8 @@ const MODES = { }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Shinez adapter'); @@ -41,65 +24,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({body}) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/shinezBidAdapter.md b/modules/shinezBidAdapter.md index f0ef7a6c218..203b6ece4ff 100644 --- a/modules/shinezBidAdapter.md +++ b/modules/shinezBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Shinez's demand sources. The Shinez adapter requires setup and approval from the Shinez. Please reach out to tech-team@shinez.io to create an Shinez account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video @@ -73,4 +73,4 @@ var adUnits = [{ }] } ]; -``` \ No newline at end of file +``` diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index ba922c0fd57..62d82b8d4b2 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -1,38 +1,17 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -export const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -export const BIDDER_CODE = 'stn'; -export const ADAPTER_VERSION = '6.1.0'; -export const TTL = 360; -export const DEFAULT_CURRENCY = 'USD'; -export const SELLER_ENDPOINT = 'https://hb.stngo.com/'; -export const MODES = { +const BIDDER_CODE = 'stn'; +const BASE_URL = 'https://hb.stngo.com/'; +const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' }; -export const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -}; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to STN adapter'); @@ -45,64 +24,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, SELLER_ENDPOINT, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }) - syncs.push(...pixels) - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/stnBidAdapter.md b/modules/stnBidAdapter.md index 90b0b58e34b..d5f5f4f3e1d 100644 --- a/modules/stnBidAdapter.md +++ b/modules/stnBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to STN's demand sources. The STN adapter requires setup and approval from the STN. Please reach out to hb@stnvideo.com to create an STN account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index f2bdd3b6c9d..4e5cd4883d3 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/minutemediaBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-multi'; const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-multi-mm-test'; @@ -63,7 +64,6 @@ describe('minutemediaAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -80,7 +80,59 @@ describe('minutemediaAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -151,10 +203,10 @@ describe('minutemediaAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -170,12 +222,21 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -455,6 +516,28 @@ describe('minutemediaAdapter', function () { nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -494,10 +577,42 @@ describe('minutemediaAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -509,6 +624,11 @@ describe('minutemediaAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index f6f6ad22476..9ab8f608598 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/openwebBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.openwebmp.com/hb-multi'; const TEST_ENDPOINT = 'https://hb.openwebmp.com/hb-multi-test'; @@ -74,7 +75,6 @@ describe('openwebAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -91,7 +91,59 @@ describe('openwebAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -160,10 +212,10 @@ describe('openwebAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -179,12 +231,21 @@ describe('openwebAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -464,6 +525,28 @@ describe('openwebAdapter', function () { nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id-3', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -503,10 +586,42 @@ describe('openwebAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id-3', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -518,6 +633,11 @@ describe('openwebAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 2d11ae16cb7..a3fef50f825 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.yellowblue.io/hb-multi'; const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-multi-test'; @@ -72,7 +73,6 @@ describe('riseAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -89,7 +89,59 @@ describe('riseAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -175,10 +227,10 @@ describe('riseAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -194,12 +246,21 @@ describe('riseAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -506,6 +567,28 @@ describe('riseAdapter', function () { creativeId: 'creative-id', nurl: 'http://example.com/win/1234', mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -545,10 +628,42 @@ describe('riseAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -560,6 +675,11 @@ describe('riseAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/shinezBidAdapter_spec.js b/test/spec/modules/shinezBidAdapter_spec.js index 4e6c2d3420e..d4ad99359bb 100644 --- a/test/spec/modules/shinezBidAdapter_spec.js +++ b/test/spec/modules/shinezBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/shinezBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.sweetgum.io/hb-sz-multi'; const TEST_ENDPOINT = 'https://hb.sweetgum.io/hb-multi-sz-test'; @@ -61,7 +62,6 @@ describe('shinezAdapter', function () { 'context': 'instream' } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -77,7 +77,59 @@ describe('shinezAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -130,12 +182,21 @@ describe('shinezAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -305,6 +366,8 @@ describe('shinezAdapter', function () { height: 480, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: VIDEO }, { @@ -314,7 +377,31 @@ describe('shinezAdapter', function () { height: 250, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -325,7 +412,7 @@ describe('shinezAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -340,10 +427,10 @@ describe('shinezAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -354,10 +441,42 @@ describe('shinezAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -369,6 +488,11 @@ describe('shinezAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js index de851158ed0..98859385828 100644 --- a/test/spec/modules/stnBidAdapter_spec.js +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/stnBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.stngo.com/hb-multi'; const TEST_ENDPOINT = 'https://hb.stngo.com/hb-multi-test'; @@ -63,7 +64,6 @@ describe('stnAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -80,6 +80,59 @@ describe('stnAdapter', function () { 'banner': { } }, + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, 'ad': '""' } ]; @@ -151,10 +204,10 @@ describe('stnAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -170,12 +223,21 @@ describe('stnAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -455,6 +517,28 @@ describe('stnAdapter', function () { adomain: ['abc.com'], mediaType: BANNER, nurl: 'http://example.com/win/1234', + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id-3', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -494,10 +578,42 @@ describe('stnAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id-3', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -509,6 +625,11 @@ describe('stnAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { From 8ed14d899c8a501151b29701ee9eab87b14e7d44 Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:22:22 -0500 Subject: [PATCH 0838/1097] fix: error logging (#12656) Co-authored-by: rufiange --- modules/contxtfulBidAdapter.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js index 4905c499998..f7d263ae74f 100644 --- a/modules/contxtfulBidAdapter.js +++ b/modules/contxtfulBidAdapter.js @@ -8,7 +8,7 @@ import { interpretResponse, getUserSyncs as getUserSyncsLib, } from '../libraries/teqblazeUtils/bidderUtils.js'; -import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; // Constants const BIDDER_CODE = 'contxtful'; @@ -75,7 +75,7 @@ const extractParameters = (config) => { // Construct the Payload towards the Bidding endpoint const buildRequests = (validBidRequests = [], bidderRequest = {}) => { - const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); + const ortb2 = converter.toORTB({ bidderRequest: bidderRequest, bidRequests: validBidRequests }); const bidRequests = []; _each(validBidRequests, bidRequest => { @@ -88,7 +88,7 @@ const buildRequests = (validBidRequests = [], bidderRequest = {}) => { }); const config = pbjsConfig.getConfig(); config.pbjsVersion = PREBID_VERSION; - const {version, customer} = extractParameters(config) + const { version, customer } = extractParameters(config) const adapterUrl = buildUrl({ protocol: 'https', host: BIDDER_ENDPOINT, @@ -151,6 +151,17 @@ const getSamplingRate = (bidderConfig, eventType) => { return entry ? entry[1] : DEFAULT_SAMPLING_RATE; }; +const logBidderError = ({ error, bidderRequest }) => { + if (error) { + let jsonReason = { + message: error.reason?.message, + stack: error.reason?.stack, + }; + error.reason = jsonReason; + } + logEvent('onBidderError', { error, bidderRequest }); +}; + // Handles the logging of events const logEvent = (eventType, data) => { try { @@ -159,7 +170,7 @@ const logEvent = (eventType, data) => { // Get Config const bidderConfig = pbjsConfig.getConfig(); - const {version, customer} = extractParameters(bidderConfig); + const { version, customer } = extractParameters(bidderConfig); // Sampled monitoring if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) { @@ -206,12 +217,12 @@ export const spec = { buildRequests, interpretResponse, getUserSyncs, - onBidWon: function(bid) { logEvent('onBidWon', bid); }, - onBidBillable: function(bid) { logEvent('onBidBillable', bid); }, - onAdRenderSucceeded: function(bid) { logEvent('onAdRenderSucceeded', bid); }, - onSetTargeting: function(bid) { }, - onTimeout: function(timeoutData) { logEvent('onTimeout', timeoutData); }, - onBidderError: function({ error, bidderRequest }) { logEvent('onBidderError', { error, bidderRequest }); }, + onBidWon: function (bid) { logEvent('onBidWon', bid); }, + onBidBillable: function (bid) { logEvent('onBidBillable', bid); }, + onAdRenderSucceeded: function (bid) { logEvent('onAdRenderSucceeded', bid); }, + onSetTargeting: function (bid) { }, + onTimeout: function (timeoutData) { logEvent('onTimeout', timeoutData); }, + onBidderError: logBidderError, }; registerBidder(spec); From 3fd206662560544cd3ff2d4d04184ba31cf3989a Mon Sep 17 00:00:00 2001 From: Maximiliano Zurita <76264143+maximilianozurita@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:40:22 -0300 Subject: [PATCH 0839/1097] EPlanning Bid Adapter : adding support for schain (#12635) * Add schain support to eplanning bid adapter * Se modifica bid adapter para tomar Nodes de schain en lugar de un valor random --------- Co-authored-by: Maxi --- modules/eplanningBidAdapter.js | 6 +- test/spec/modules/eplanningBidAdapter_spec.js | 75 ++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 1fe5cb09c10..c163a69b502 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js'; const BIDDER_CODE = 'eplanning'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -39,6 +40,7 @@ export const spec = { const method = 'GET'; const dfpClientId = '1'; const sec = 'ROS'; + const schain = bidRequests[0].schain; let url; let params; const urlConfig = getUrlConfig(bidRequests); @@ -70,7 +72,9 @@ export const spec = { if (pcrs) { params.crs = pcrs; } - + if (schain && schain.nodes.length > 2) { + params.sch = serializeSupplyChain(schain, ['asi', 'sid', 'hp', 'rid', 'name', 'domain']); + } if (referrerUrl) { params.fr = cutUrl(referrerUrl); } diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index a381d7644a1..60845c7354f 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -53,6 +53,68 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE2, 'sizes': [[300, 250], [300, 600]], }; + const validBidWithSchain = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + name: 'publisher', + domain: 'publisher.com' + }, + { + asi: 'reseller.com', + sid: 'aaaaa', + rid: 'BidRequest2', + hp: 1, + name: 'publisher2', + domain: 'publisher2.com' + }, + { + asi: 'reseller3.com', + sid: 'aaaaab', + rid: 'BidRequest3', + hp: 1, + name: 'publisher3', + domain: 'publisher3.com' + } + ] + } + }; + const validBidWithSchainNodes = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + name: 'publisher', + domain: 'publisher.com' + } + ] + } + }; const ML = '1'; const validBidMappingLinear = { 'bidder': 'eplanning', @@ -727,7 +789,18 @@ describe('E-Planning Adapter', function () { expect(data.vctx).to.equal(2); expect(data.vv).to.equal(3); }); - + it('should return sch parameter', function () { + let bidRequests = [validBidWithSchain], schainExpected, schain; + schain = validBidWithSchain.schain; + schainExpected = schain.ver + ',' + schain.complete + '!' + schain.nodes.map(node => node.asi + ',' + node.sid + ',' + node.hp + ',' + node.rid + ',' + node.name + ',' + node.domain).join('!'); + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.sch).to.deep.equal(schainExpected); + }); + it('should not return sch parameter', function () { + let bidRequests = [validBidWithSchainNodes]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.sch).to.equal(undefined); + }); it('should return correct e parameter with linear mapping attribute with more than one adunit', function () { let bidRequestsML = [validBidMappingLinear]; const NEW_CODE = ADUNIT_CODE + '2'; From 634ff5de85b0dfaa0e4293d1033dc0b219770360 Mon Sep 17 00:00:00 2001 From: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:50:43 +0200 Subject: [PATCH 0840/1097] AcuityAds Bid Adapter: add endpointId param (#12644) * add prebid.js adapter * changes * changes * changes * changes * fix downolad * add gpp * Merge remote-tracking branch 'prebid/master' * add gvlid * add endpointId param --- modules/acuityadsBidAdapter.js | 2 +- test/spec/modules/acuityadsBidAdapter_spec.js | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index 3caaacb46a4..b94234c2c26 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -12,7 +12,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: isBidRequestValid(['placementId']), + isBidRequestValid: isBidRequestValid(), buildRequests: buildRequests(AD_URL), interpretResponse, getUserSyncs: getUserSyncs(SYNC_URL) diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index ecc40025c95..3f2705a26c3 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -191,6 +191,55 @@ describe('AcuityAdsBidAdapter', function () { } }); + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + it('Returns data with gdprConsent and without uspConsent', function () { delete bidderRequest.uspConsent; serverRequest = spec.buildRequests(bids, bidderRequest); From f14229a62e5f97b9e3bb6d98bb268adaad78a66e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 16 Jan 2025 08:18:28 -0800 Subject: [PATCH 0841/1097] SharedIdSystem: add configurable inserter (#12664) --- modules/sharedIdSystem.js | 12 ++++++++--- modules/userId/eids.js | 2 ++ test/spec/modules/sharedIdSystem_spec.js | 26 +++++++++++++++++++++++- test/spec/modules/userId_spec.js | 19 +++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index fa8b5e3bfdb..a96681f749c 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -183,9 +183,15 @@ export const sharedIdSystemSubmodule = { domainOverride: domainOverrideToRootDomain(storage, 'sharedId'), eids: { - 'pubcid': { - source: 'pubcid.org', - atype: 1 + 'pubcid'(values, config) { + const eid = { + source: 'pubcid.org', + uids: values.map(id => ({id, atype: 1})) + } + if (config?.params?.inserter != null) { + eid.inserter = config.params.inserter; + } + return eid; }, } }; diff --git a/modules/userId/eids.js b/modules/userId/eids.js index ee194bdc71c..8289e43c963 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -61,6 +61,8 @@ export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { if (!Array.isArray(eids)) { eids = [eids]; } + eids.forEach(eid => eid.uids = eid.uids.filter(({id}) => isStr(id))) + eids = eids.filter(({uids}) => uids?.length > 0); } catch (e) { logError(`Could not generate EID for "${name}"`, e); } diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 359cbeb4651..71a531aa6c5 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -1,10 +1,12 @@ import {sharedIdSystemSubmodule, storage} from 'modules/sharedIdSystem.js'; import {coppaDataHandler} from 'src/adapterManager'; +import {config} from 'src/config.js'; import sinon from 'sinon'; import * as utils from 'src/utils.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; -import {attachIdSystem} from '../../../modules/userId/index.js'; +import {attachIdSystem, init} from '../../../modules/userId/index.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; let expect = require('chai').expect; @@ -97,6 +99,9 @@ describe('SharedId System', function () { before(() => { attachIdSystem(sharedIdSystemSubmodule); }); + afterEach(() => { + config.resetConfig(); + }); it('pubCommonId', function() { const userId = { pubcid: 'some-random-id-value' @@ -108,5 +113,24 @@ describe('SharedId System', function () { uids: [{id: 'some-random-id-value', atype: 1}] }); }); + + it('should set inserter, if provided in config', async () => { + config.setConfig({ + userSync: { + userIds: [{ + name: 'sharedId', + params: { + inserter: 'mock-inserter' + }, + value: {pubcid: 'mock-id'} + }] + } + }); + const eids = getGlobal().getUserIdsAsEids(); + sinon.assert.match(eids[0], { + source: 'pubcid.org', + inserter: 'mock-inserter' + }) + }) }) }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 7a81c8b5f6d..066f00feb8f 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -479,6 +479,25 @@ describe('User ID', function () { ]); }); + it('should filter out non-string uid returned by generator functions', () => { + const eids = createEidsArray({ + mockId2v3: [null, 'id1', 123], + }); + expect(eids[0].uids).to.eql([ + { + atype: 2, + id: 'id1' + } + ]); + }); + + it('should filter out entire EID if none of the uids are strings', () => { + const eids = createEidsArray({ + mockId2v3: [null], + }); + expect(eids).to.eql([]); + }) + it('should group UIDs by everything except uid', () => { const eids = createEidsArray({ mockId1: ['mock-1-1', 'mock-1-2'], From e0fe3ac3701460263fa0cfe0567bb752c420702d Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:35:17 -0500 Subject: [PATCH 0842/1097] feat: session marker (#12634) Co-authored-by: rufiange --- modules/contxtfulRtdProvider.js | 21 +++++----- .../spec/modules/contxtfulRtdProvider_spec.js | 39 ++++++++++++++++++- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 55623c00591..1bd977afed1 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -15,6 +15,7 @@ import { isEmpty, buildUrl, isArray, + generateUUID, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -26,13 +27,17 @@ const MODULE = `${MODULE_NAME}RtdProvider`; const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io'; const CONTXTFUL_DEFER_DEFAULT = 0; +let _sm; +function sm() { + return _sm ??= generateUUID(); +} + const storageManager = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME, }); let rxApi = null; -let isFirstBidRequestCall = true; /** * Return current receptivity value for the requester. @@ -150,7 +155,7 @@ function initCustomer(config) { addConnectorEventListener(customer, config); - const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME); + const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME, undefined, undefined, { 'data-sm': sm() }); // Optionally defer the loading of the script if (Number.isFinite(defer) && defer > 0) { setTimeout(loadScript, defer); @@ -228,9 +233,6 @@ function getTargetingData(adUnits, config, _userConsent) { */ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { function onReturn() { - if (isFirstBidRequestCall) { - isFirstBidRequestCall = false; - } onDone(); } @@ -245,16 +247,10 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`)); let sources = [fromStorage, fromApi]; - if (isFirstBidRequestCall) { - sources.reverse(); - } let rxBatch = Object.assign(...sources); - let singlePointEvents; - if (isEmpty(rxBatch)) { - singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); - } + let singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); bidders .forEach(bidderCode => { @@ -266,6 +262,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { ext: { rx: rxBatch[bidderCode], events: singlePointEvents, + sm: sm(), params: { ev: config.params?.version, ci: config.params?.customer, diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index e31ef554da0..68e38d63364 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -4,12 +4,15 @@ import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; import { getStorageManager } from '../../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../../src/activities/modules.js'; import * as events from '../../../src/events'; +import * as utils from 'src/utils.js'; import Sinon from 'sinon'; +import { deepClone } from '../../../src/utils.js'; const MODULE_NAME = 'contxtful'; const VERSION = 'v1'; const CUSTOMER = 'CUSTOMER'; +const SM = 'SM'; const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/rxConnector.js`; const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_from_session_storage' }; @@ -62,6 +65,8 @@ describe('contxtfulRtdProvider', function () { eventsEmitSpy = sandbox.spy(events, ['emit']); + sandbox.stub(utils, 'generateUUID').returns(SM); + let tagId = CUSTOMER; sessionStorage.clear(); }); @@ -534,6 +539,7 @@ describe('contxtfulRtdProvider', function () { name: 'contxtful', ext: { rx: RX_FROM_API, + sm: SM, params: { ev: config.params?.version, ci: config.params?.customer, @@ -549,11 +555,40 @@ describe('contxtfulRtdProvider', function () { expect(data.name).to.deep.equal(expectedData.name); expect(data.ext.rx).to.deep.equal(expectedData.ext.rx); + expect(data.ext.sm).to.deep.equal(expectedData.ext.sm); expect(data.ext.params).to.deep.equal(expectedData.ext.params); done(); }, TIMEOUT); }); + it('does not change the sm', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let firstReqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let secondReqBidsConfigObj = deepClone(firstReqBidsConfigObj); + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(firstReqBidsConfigObj, onDoneSpy, config); + contxtfulSubmodule.getBidRequestData(secondReqBidsConfigObj, onDoneSpy, config); + + let firstData = firstReqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + let secondData = secondReqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + + expect(firstData.ext.sm).to.equal(secondData.ext.sm); + + done(); + }, TIMEOUT); + }); + describe('before rxApi is loaded', function () { const moveEventTheories = [ [ @@ -628,7 +663,7 @@ describe('contxtfulRtdProvider', function () { }); describe('after rxApi is loaded', function () { - it('does not add event', function (done) { + it('should add event', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); @@ -648,7 +683,7 @@ describe('contxtfulRtdProvider', function () { let events = ext.events; - expect(events).to.be.undefined; + expect(events).to.be.not.undefined; done(); }, TIMEOUT); }); From 582e649ea1c618f78065c354b0e8b9bc92d7ac55 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 16 Jan 2025 09:29:12 -0800 Subject: [PATCH 0843/1097] native Rendering : fix bug where click trackers are not fired (#12655) * nativeRendering: fix bug where click trackers are not fired * Cleanup --- creative/renderers/native/renderer.js | 37 ++++++++++--- .../creative-renderer-native/renderer.js | 2 +- modules/nativeRendering.js | 3 +- src/native.js | 9 ++- test/spec/creative/nativeRenderer_spec.js | 55 +++++++++++++++---- test/spec/native_spec.js | 6 +- 6 files changed, 87 insertions(+), 25 deletions(-) diff --git a/creative/renderers/native/renderer.js b/creative/renderers/native/renderer.js index 5cc8f100108..f7c124b41eb 100644 --- a/creative/renderers/native/renderer.js +++ b/creative/renderers/native/renderer.js @@ -45,6 +45,16 @@ function loadScript(url, doc) { }); } +function getRenderFrames(node) { + return Array.from(node.querySelectorAll('iframe[srcdoc*="render"]')) +} + +function getInnerHTML(node) { + const clone = node.cloneNode(true); + getRenderFrames(clone).forEach(node => node.parentNode.removeChild(node)); + return clone.innerHTML; +} + export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) { const {rendererUrl, assets, ortb, adTemplate} = nativeData; const doc = win.document; @@ -58,21 +68,32 @@ export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) return win.renderAd(payload); }); } else { - return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML)); + return Promise.resolve(replacer(adTemplate ?? getInnerHTML(doc.body))); } } export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) { const {head, body} = win.document; - const resize = () => sendMessage(MESSAGE_NATIVE, { - action: ACTION_RESIZE, - height: body.offsetHeight, - width: body.offsetWidth - }); + const resize = () => { + // force redraw - for some reason this is needed to get the right dimensions + body.style.display = 'none'; + body.style.display = 'block'; + sendMessage(MESSAGE_NATIVE, { + action: ACTION_RESIZE, + height: body.offsetHeight, + width: body.offsetWidth + }); + } + function replaceMarkup(target, markup) { + // do not remove the rendering logic if it's embedded in this window; things will break otherwise + const renderFrames = getRenderFrames(target); + Array.from(target.childNodes).filter(node => !renderFrames.includes(node)).forEach(node => target.removeChild(node)); + target.insertAdjacentHTML('afterbegin', markup); + } const replacer = getReplacer(adId, native); - head && (head.innerHTML = replacer(head.innerHTML)); + replaceMarkup(head, replacer(getInnerHTML(head))); return getMarkup(adId, native, replacer, win).then(markup => { - body.innerHTML = markup; + replaceMarkup(body, markup); if (typeof win.postRenderAd === 'function') { win.postRenderAd({adId, ...native}); } diff --git a/libraries/creative-renderer-native/renderer.js b/libraries/creative-renderer-native/renderer.js index d7d85cdd7ba..5651cc3f0ca 100644 --- a/libraries/creative-renderer-native/renderer.js +++ b/libraries/creative-renderer-native/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "(()=>{\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}})();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e){return Array.from(e.querySelectorAll('iframe[srcdoc*=\"render\"]'))}function i(e){const t=e.cloneNode(!0);return r(t).forEach((e=>e.parentNode.removeChild(e))),t.innerHTML}function o(e,t,r,o,s=n){const{rendererUrl:c,assets:d,ortb:a,adTemplate:l}=t,u=o.document;return c?s(c,u).then((()=>{if(\"function\"!=typeof o.renderAd)throw new Error(`Renderer from '${c}' does not define renderAd()`);const e=d||[];return e.ortb=a,o.renderAd(e)})):Promise.resolve(r(l??i(u.body)))}window.render=function({adId:n,native:s},{sendMessage:c},d,a=o){const{head:l,body:u}=d.document,f=()=>{u.style.display=\"none\",u.style.display=\"block\",c(e,{action:\"resizeNativeHeight\",height:u.offsetHeight,width:u.offsetWidth})};function b(e,t){const n=r(e);Array.from(e.childNodes).filter((e=>!n.includes(e))).forEach((t=>e.removeChild(t))),e.insertAdjacentHTML(\"afterbegin\",t)}const h=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,s);return b(l,h(i(l))),a(n,s,h,d).then((t=>{b(u,t),\"function\"==typeof d.postRenderAd&&d.postRenderAd({adId:n,...s}),d.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>c(e,{action:\"click\",assetId:n})))})),c(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===d.document.readyState?f():d.onload=f}))}})();" \ No newline at end of file diff --git a/modules/nativeRendering.js b/modules/nativeRendering.js index 8e6b6baab55..a6a404a0253 100644 --- a/modules/nativeRendering.js +++ b/modules/nativeRendering.js @@ -7,7 +7,8 @@ import {getCreativeRendererSource} from '../src/creativeRenderers.js'; function getRenderingDataHook(next, bidResponse, options) { if (isNativeResponse(bidResponse)) { next.bail({ - native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)) + native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)), + rendererVersion: 2 // 9.28 fixed a rendering bug; this signals to PUC that the native renderer is safe to use }) } else { next(bidResponse, options) diff --git a/src/native.js b/src/native.js index 19833406451..d1ac4ea17d7 100644 --- a/src/native.js +++ b/src/native.js @@ -436,13 +436,16 @@ function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {} message: 'assetResponse', adId: data.adId, }; - let renderData = getRenderingData(adObject).native; + let {native: renderData, rendererVersion} = getRenderingData(adObject); if (renderData) { // if we have native rendering data (set up by the nativeRendering module) // include it in full ("all assets") together with the renderer. // this is to allow PUC to use dynamic renderers without requiring changes in creative setup - msg.native = Object.assign({}, renderData); - msg.renderer = getCreativeRendererSource(adObject); + Object.assign(msg, { + native: Object.assign({}, renderData), + renderer: getCreativeRendererSource(adObject), + rendererVersion, + }) if (keys != null) { renderData.assets = renderData.assets.filter(({key}) => keys.includes(key)) } diff --git a/test/spec/creative/nativeRenderer_spec.js b/test/spec/creative/nativeRenderer_spec.js index 66e81a517c7..935f3db1ad3 100644 --- a/test/spec/creative/nativeRenderer_spec.js +++ b/test/spec/creative/nativeRenderer_spec.js @@ -34,10 +34,16 @@ describe('Native creative renderer', () => { }); }); describe('otherwise, calls replacer', () => { - let replacer; + let replacer, frame; beforeEach(() => { replacer = sinon.stub().returns('markup'); + frame = document.createElement('iframe'); + document.body.appendChild(frame); + win.document = frame.contentDocument; }); + afterEach(() => { + document.body.removeChild(frame); + }) it('with adTemplate, if present', () => { return getAdMarkup('123', {adTemplate: 'tpl'}, replacer, win).then((result) => { expect(result).to.eql('markup'); @@ -45,7 +51,7 @@ describe('Native creative renderer', () => { }); }); it('with document body otherwise', () => { - win.document = {body: {innerHTML: 'body'}}; + win.document.body.innerHTML = 'body' return getAdMarkup('123', {}, replacer, win).then((result) => { expect(result).to.eql('markup'); sinon.assert.calledWith(replacer, 'body'); @@ -186,26 +192,29 @@ describe('Native creative renderer', () => { }); describe('render', () => { - let getMarkup, sendMessage, adId, nativeData, exc; + let getMarkup, sendMessage, adId, nativeData, exc, frame; beforeEach(() => { adId = '123'; nativeData = {} getMarkup = sinon.stub(); sendMessage = sinon.stub() exc = sinon.stub(); - win.document = { - querySelectorAll() { return [] }, - body: {} - } + frame = document.createElement('iframe'); + document.body.appendChild(frame); + win.document = frame.contentDocument; }); + afterEach(() => { + document.body.removeChild(frame); + }) + function runRender() { return render({adId, native: nativeData}, {sendMessage, exc}, win, getMarkup) } it('replaces placeholders in head, if present', () => { getMarkup.returns(Promise.resolve('')) - win.document.head = {innerHTML: '##hb_native_asset_id_1##'}; + win.document.head.innerHTML = '##hb_native_asset_id_1##'; nativeData.ortb = { assets: [ {id: 1, data: {value: 'repl'}} @@ -216,6 +225,14 @@ describe('Native creative renderer', () => { }) }); + it('does not replace iframes with srcdoc that contain "renderer"', () => { + win.document.head.innerHTML = win.document.body.innerHTML = ''; + getMarkup.returns(Promise.resolve('')) + return runRender().then(() => { + expect(Array.from(win.document.querySelectorAll('iframe[srcdoc="renderer"]')).length).to.eql(2); + }) + }) + it('drops markup on body, and fires imp trackers', () => { getMarkup.returns(Promise.resolve('markup')); return runRender().then(() => { @@ -246,9 +263,27 @@ describe('Native creative renderer', () => { describe('requests resize', () => { beforeEach(() => { + const mkNode = () => { + const node = { + innerHTML: '', + childNodes: [], + insertAdjacentHTML: () => {}, + style: {}, + querySelectorAll: () => [], + cloneNode: () => node + }; + return node; + } + win.document = { + head: mkNode(), + body: Object.assign(mkNode(), { + offsetHeight: 123, + offsetWidth: 321 + }), + querySelectorAll: () => [], + style: {} + }; getMarkup.returns(Promise.resolve('markup')); - win.document.body.offsetHeight = 123; - win.document.body.offsetWidth = 321; }); it('immediately, if document is loaded', () => { diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 01214cdb3ae..7ea9b5949fe 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -402,7 +402,8 @@ describe('native.js', function () { 'returns native data': { renderDataHook(next, bidResponse) { next.bail({ - native: getNativeRenderingData(bidResponse, adUnit) + native: getNativeRenderingData(bidResponse, adUnit), + rendererVersion: 'native-render-version' }); }, renderSourceHook(next) { @@ -433,8 +434,9 @@ describe('native.js', function () { function checkRenderer(message) { if (withRenderer) { expect(message.renderer).to.eql('mock-native-renderer') + expect(message.rendererVersion).to.eql('native-render-version'); Object.entries(message).forEach(([key, val]) => { - if (!['native', 'adId', 'message', 'assets', 'renderer'].includes(key)) { + if (!['native', 'adId', 'message', 'assets', 'renderer', 'rendererVersion'].includes(key)) { expect(message.native[key]).to.eql(val); } }) From 6b37ddc80cada6aad1244f25f8a9833d4e550629 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 16 Jan 2025 18:10:46 +0000 Subject: [PATCH 0844/1097] Prebid 9.27.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2001909266e..d41b5a5c3a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.27.0-pre", + "version": "9.27.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.27.0-pre", + "version": "9.27.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 648cf2e0736..35a4ed1cdb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.27.0-pre", + "version": "9.27.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 57e17676e2cd59efbe56dd9e41b9c2516d21ebba Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 16 Jan 2025 18:10:46 +0000 Subject: [PATCH 0845/1097] Increment version to 9.28.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d41b5a5c3a5..e87d90c2d87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.27.0", + "version": "9.28.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.27.0", + "version": "9.28.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 35a4ed1cdb5..4e9b53a24de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.27.0", + "version": "9.28.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 32689b42cabb3b19eba9f0c36f93d1ac1229328f Mon Sep 17 00:00:00 2001 From: Maximiliano Zurita <76264143+maximilianozurita@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:51:00 -0300 Subject: [PATCH 0846/1097] Change expected nodes greater than 2 to less than or equal to 2 (#12670) --- modules/eplanningBidAdapter.js | 2 +- test/spec/modules/eplanningBidAdapter_spec.js | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index c163a69b502..5bd1eab8863 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -72,7 +72,7 @@ export const spec = { if (pcrs) { params.crs = pcrs; } - if (schain && schain.nodes.length > 2) { + if (schain && schain.nodes.length <= 2) { params.sch = serializeSupplyChain(schain, ['asi', 'sid', 'hp', 'rid', 'name', 'domain']); } if (referrerUrl) { diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index 60845c7354f..9f46c57e422 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -72,22 +72,6 @@ describe('E-Planning Adapter', function () { hp: 1, name: 'publisher', domain: 'publisher.com' - }, - { - asi: 'reseller.com', - sid: 'aaaaa', - rid: 'BidRequest2', - hp: 1, - name: 'publisher2', - domain: 'publisher2.com' - }, - { - asi: 'reseller3.com', - sid: 'aaaaab', - rid: 'BidRequest3', - hp: 1, - name: 'publisher3', - domain: 'publisher3.com' } ] } @@ -111,6 +95,22 @@ describe('E-Planning Adapter', function () { hp: 1, name: 'publisher', domain: 'publisher.com' + }, + { + asi: 'reseller.com', + sid: 'aaaaa', + rid: 'BidRequest2', + hp: 1, + name: 'publisher2', + domain: 'publisher2.com' + }, + { + asi: 'reseller3.com', + sid: 'aaaaab', + rid: 'BidRequest3', + hp: 1, + name: 'publisher3', + domain: 'publisher3.com' } ] } From b6ce350a2483d70d5d5cf482db696a2a8f2469eb Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:00:38 +0100 Subject: [PATCH 0847/1097] Bump live-connect dependency (#12677) --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e87d90c2d87..6f821471e62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^7.1.0" + "live-connect-js": "^7.2.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -18209,9 +18209,10 @@ } }, "node_modules/live-connect-js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz", - "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.2.0.tgz", + "integrity": "sha512-oZY4KqwrG1C+CDKApcsdDdMG4j2d44lhmvbNy4ZE6sPFr+W8R3m0+V+JxXB8p6tgSePJ8X/uhzAGos0lDg/MAg==", + "license": "Apache-2.0", "dependencies": { "live-connect-common": "^v4.1.0", "tiny-hashes": "1.0.1" @@ -41730,9 +41731,9 @@ "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw==" }, "live-connect-js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz", - "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.2.0.tgz", + "integrity": "sha512-oZY4KqwrG1C+CDKApcsdDdMG4j2d44lhmvbNy4ZE6sPFr+W8R3m0+V+JxXB8p6tgSePJ8X/uhzAGos0lDg/MAg==", "requires": { "live-connect-common": "^v4.1.0", "tiny-hashes": "1.0.1" diff --git a/package.json b/package.json index 4e9b53a24de..6cdf75abeb1 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^7.1.0" + "live-connect-js": "^7.2.0" }, "optionalDependencies": { "fsevents": "^2.3.2" From cfe1e79335d091b1a4d6ee51e5f1ee5f69776768 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 21 Jan 2025 11:05:43 -0500 Subject: [PATCH 0848/1097] Liveintent Id Module: support for additional eid (#12659) * Update shared.js * Update liveIntentExternalIdSystem_spec.js * Update liveIntentIdMinimalSystem_spec.js * Update liveIntentIdSystem_spec.js * Update liveIntentIdMinimalSystem_spec.js * Update test/spec/modules/liveIntentIdSystem_spec.js Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> * Update liveIntentExternalIdSystem_spec.js * Update liveIntentIdSystem_spec.js --------- Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> --- libraries/liveIntentId/shared.js | 16 ++++++++ .../liveIntentExternalIdSystem_spec.js | 5 +++ .../modules/liveIntentIdMinimalSystem_spec.js | 5 +++ test/spec/modules/liveIntentIdSystem_spec.js | 38 +++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js index f6a5d29c0a7..ab00417ccc3 100644 --- a/libraries/liveIntentId/shared.js +++ b/libraries/liveIntentId/shared.js @@ -73,6 +73,10 @@ export function composeIdObject(value) { result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } } + if (value.triplelift) { + result.triplelift = { 'id': value.triplelift, ext: { provider: LI_PROVIDER_DOMAIN } } + } + if (value.medianet) { result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } } @@ -258,6 +262,18 @@ export const eids = { } } }, + 'triplelift': { + source: 'liveintent.triplelift.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, 'vidazoo': { source: 'liveintent.vidazoo.com', atype: 3, diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js index 0ee827a97b5..b751f288b33 100644 --- a/test/spec/modules/liveIntentExternalIdSystem_spec.js +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -456,6 +456,11 @@ describe('LiveIntentExternalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 7142b2cb2c1..810b6a23a20 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -324,6 +324,11 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 8bdd9041f60..8f7a3465d88 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -541,6 +541,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -887,6 +892,39 @@ describe('LiveIntentId', function() { }); }); + it('triplelift', function () { + const userId = { + triplelift: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.triplelift.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('triplelift with ext', function () { + const userId = { + triplelift: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.triplelift.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('vidazoo', function () { const userId = { vidazoo: { 'id': 'sample_id' } From 5b0024e59a23f19bbcc40028fd4f30908dd805ff Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:09:22 +0100 Subject: [PATCH 0849/1097] ZetaGlobalSspAnalytics Adapter: domain and page (#12674) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 7 +++++-- test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 805e2c51c81..03e3f8f556e 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -4,6 +4,8 @@ import adapterManager from '../src/adapterManager.js'; import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {config} from '../src/config.js'; +import {parseDomain} from '../src/refererDetection.js'; const ZETA_GVL_ID = 833; const ADAPTER_CODE = 'zeta_global_ssp'; @@ -27,10 +29,11 @@ function sendEvent(eventType, event) { /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// function adRenderSucceededHandler(args) { + const page = config.getConfig('pageUrl') || args.doc?.location?.host + args.doc?.location?.pathname; const event = { zetaParams: zetaParams, - domain: args.doc?.location?.host, - page: args.doc?.location?.host + args.doc?.location?.pathname, + domain: parseDomain(page, {noLeadingWww: true}), + page: page, bid: { adId: args.bid?.adId, requestId: args.bid?.requestId, diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index a308eb44987..04554df560e 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -529,6 +529,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { sandbox = sinon.sandbox.create(); requests = server.requests; sandbox.stub(events, 'getEvents').returns([]); + config.setConfig({ pageUrl: 'https://www.config.domain.com/index.html' }) }); afterEach(function () { @@ -624,8 +625,8 @@ describe('Zeta Global SSP Analytics Adapter', function () { shortname: 'name' } }); - expect(auctionSucceeded.domain).to.eql('test-zeta-ssp.net'); - expect(auctionSucceeded.page).to.eql('test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html'); + expect(auctionSucceeded.domain).to.eql('config.domain.com'); + expect(auctionSucceeded.page).to.eql('https://www.config.domain.com/index.html'); expect(auctionSucceeded.bid).to.be.deep.equal({ adUnitCode: '/19968336/header-bid-tag-0', adId: '5759bb3ef7be1e8', From 7eb00b5608a2ddc3d2e7ecd6bd8f5d870bbee686 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 21 Jan 2025 08:10:46 -0800 Subject: [PATCH 0850/1097] GPP MSPA Control Module: add support for usnat version 2 (#12667) * MSPA: add support for usnat version 2 * Use 15 (array bound) for testing --- libraries/mspa/activityControls.js | 33 +++++++-- .../libraries/mspa/activityControls_spec.js | 72 ++++++++++++++++--- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js index eb68259d585..c93748f73c7 100644 --- a/libraries/mspa/activityControls.js +++ b/libraries/mspa/activityControls.js @@ -24,6 +24,8 @@ export function isBasicConsentDenied(cd) { cd.PersonalDataConsents === 2 || // minors 13+ who have not given consent cd.KnownChildSensitiveDataConsents[0] === 1 || + // minors 16+ who have not given consent (added in usnat version 2) + cd.KnownChildSensitiveDataConsents[2] === 1 || // minors under 13 cannot consent isApplicable(cd.KnownChildSensitiveDataConsents[1]) || // covered cannot be zero @@ -53,14 +55,31 @@ export function isConsentDenied(cd) { } export const isTransmitUfpdConsentDenied = (() => { - // deny anything that smells like: genetic, biometric, state/national ID, financial, union membership, - // or personal communication data - const cannotBeInScope = [6, 7, 9, 10, 12].map(el => --el); - // require consent for everything else (except geo, which is treated separately) - const allExceptGeo = Array.from(Array(12).keys()).filter((el) => el !== SENSITIVE_DATA_GEO) - const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el)); + const sensitiveFlags = (() => { + // deny anything that smells like: genetic, biometric, state/national ID, financial, union membership, + // personal communication data, status as victim of crime (version 2), status as transgender/nonbinary (version 2) + const cannotBeInScope = [6, 7, 9, 10, 12, 14, 16].map(el => --el); + // require consent for everything else (except geo, which is treated separately) + const allExceptGeo = Array.from(Array(16).keys()).filter((el) => el !== SENSITIVE_DATA_GEO) + const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el)); + + return Object.fromEntries( + Object.entries({ + 1: 12, + 2: 16 + }).map(([version, cardinality]) => { + const isInVersion = (el) => el < cardinality + return [version, { + cannotBeInScope: cannotBeInScope.filter(isInVersion), + allExceptGeo: allExceptGeo.filter(isInVersion), + mustHaveConsent: mustHaveConsent.filter(isInVersion) + }] + }) + ) + })() return function (cd) { + const {cannotBeInScope, mustHaveConsent, allExceptGeo} = sensitiveFlags[cd.Version]; return isConsentDenied(cd) || // no notice about sensitive data was given sensitiveNoticeIs(cd, 2) || @@ -97,7 +116,7 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat if (consent == null) { return {allow: false, reason: 'consent data not available'}; } - if (consent.Version !== 1) { + if (![1, 2].includes(consent.Version)) { return {allow: false, reason: `unsupported consent specification version "${consent.Version}"`} } if (denies(consent)) { diff --git a/test/spec/libraries/mspa/activityControls_spec.js b/test/spec/libraries/mspa/activityControls_spec.js index 80d9fc500b1..dcbebf9974c 100644 --- a/test/spec/libraries/mspa/activityControls_spec.js +++ b/test/spec/libraries/mspa/activityControls_spec.js @@ -45,6 +45,13 @@ describe('Consent interpretation', () => { expect(isBasicConsentDenied(mkConsent({ KnownChildSensitiveDataConsents: [0, null] }))).to.be.false; + }); + + it('should deny when Version = 2 & childconsent[3] is 1', () => { + expect(isBasicConsentDenied(mkConsent({ + Version: 2, + KnownChildSensitiveDataConsents: [null, null, 1] + }))).to.be.true; }) }); @@ -84,13 +91,60 @@ describe('Consent interpretation', () => { expect(result).to.equal(false); }); Object.entries({ - 'health information': 2, - 'biometric data': 6, - }).forEach(([t, flagNo]) => { - it(`'should be true (consent denied to add ufpd) if no consent to process ${t}'`, () => { - const consent = mkConsent(); - consent.SensitiveDataProcessing[flagNo] = 1; - expect(isTransmitUfpdConsentDenied(consent)).to.be.true; + 'health information': { + flagNo: 2, + consents: { + 1: true, + 2: false + }, + versions: [1, 2] + }, + 'biometric data': { + flagNo: 6, + consents: { + 1: true, + 2: true + }, + versions: [1, 2] + }, + 'consumer health data': { + flagNo: 12, + consents: { + 1: true, + 2: false + }, + versions: [2] + }, + 'status as transgender': { + flagNo: 15, + consents: { + 1: true, + 2: true, + }, + versions: [2] + } + + }).forEach(([t, {flagNo, consents, versions}]) => { + describe(t, () => { + Object.entries(consents).forEach(([flagValue, shouldBeDenied]) => { + const flagDescription = ({ + 1: 'denied', + 2: 'given' + })[flagValue] + describe(`consent is ${flagValue} (${flagDescription})`, () => { + let consent; + beforeEach(() => { + consent = mkConsent(); + consent.SensitiveDataProcessing[flagNo] = parseInt(flagValue, 10); + }); + versions.forEach(version => { + it(`should ${shouldBeDenied ? 'deny' : 'allow'} (version ${version})`, () => { + consent.Version = version; + expect(isTransmitUfpdConsentDenied(consent)).to.eql(shouldBeDenied); + }) + }) + }) + }) }) }); @@ -181,8 +235,8 @@ describe('mspaRule', () => { expect(mkRule()().allow).to.equal(false); }); - it('should deny when consent is using version != 1', () => { - consent = {Version: 2}; + it('should deny when consent is using version other than 1/2', () => { + consent = {Version: 3}; expect(mkRule()().allow).to.equal(false); }) From 9355e47b590a0a8180bacebc85b26691608eda15 Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:22:42 +0530 Subject: [PATCH 0851/1097] PubMatic Analytics Adapter: Moving slot level parameters to root level and adding few parameters to log in tracker (#12665) * Moved floors fields at root level from slot level * Added fv value in logger and tracker * Added safecheck for null object * Reading frv,fv value from bidResponse floorData instead of auctionCache * targeting keys issue when sendAllBids is true * Removed unused function --- modules/pubmaticAnalyticsAdapter.js | 80 ++++++++++++++----- .../modules/pubmaticAnalyticsAdapter_spec.js | 65 +++++++-------- 2 files changed, 90 insertions(+), 55 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 2fec213a612..95890bb2546 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -281,6 +281,31 @@ function isOWPubmaticBid(adapterName) { }) } +function getFloorsCommonField (floorData) { + if (!floorData) return; + const { location, fetchStatus, floorProvider, modelVersion } = floorData; + return { + ffs: { + [FLOOR_VALUES.SUCCESS]: 1, + [FLOOR_VALUES.ERROR]: 2, + [FLOOR_VALUES.TIMEOUT]: 4, + undefined: 0 + }[fetchStatus], + fsrc: { + [FLOOR_VALUES.FETCH]: 2, + [FLOOR_VALUES.NO_DATA]: 0, + [FLOOR_VALUES.AD_UNIT]: 1, + [FLOOR_VALUES.SET_CONFIG]: 1 + }[location], + fp: floorProvider, + mv: modelVersion + } +} + +function getFloorType(floorResponseData) { + return floorResponseData ? (floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -325,6 +350,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, 'frv': bid.bidResponse ? bid.bidResponse.floorData?.floorRuleValue : undefined, + 'fv': bid.bidResponse ? bid.bidResponse.floorData?.floorValue : undefined, 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined, 'pb': pg || undefined }); @@ -403,9 +429,22 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tgid'] = getTgId(); outputObj['pbv'] = '$prebid.version$' || '-1'; - if (floorData && floorFetchStatus) { - outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; - outputObj['ft'] = floorData.floorResponseData ? (floorData.floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; + if (floorData) { + const floorRootValues = getFloorsCommonField(floorData?.floorRequestData); + if (floorRootValues) { + const { ffs, fsrc, fp, mv } = floorRootValues; + if (floorData?.floorRequestData) { + outputObj['ffs'] = ffs; + outputObj['fsrc'] = fsrc; + outputObj['fp'] = fp; + } + if (floorFetchStatus) { + outputObj['fmv'] = mv || undefined; + } + } + if (floorFetchStatus) { + outputObj['ft'] = getFloorType(floorData?.floorResponseData); + } } outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { @@ -421,22 +460,6 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, 'sid': generateUUID() }; - if (floorData?.floorRequestData) { - const { location, fetchStatus, floorProvider } = floorData?.floorRequestData; - slotObject.ffs = { - [FLOOR_VALUES.SUCCESS]: 1, - [FLOOR_VALUES.ERROR]: 2, - [FLOOR_VALUES.TIMEOUT]: 4, - undefined: 0 - }[fetchStatus]; - slotObject.fsrc = { - [FLOOR_VALUES.FETCH]: 2, - [FLOOR_VALUES.NO_DATA]: 2, - [FLOOR_VALUES.AD_UNIT]: 1, - [FLOOR_VALUES.SET_CONFIG]: 1 - }[location]; - slotObject.fp = floorProvider; - } slotsArray.push(slotObject); return slotsArray; }, []); @@ -509,6 +532,25 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&orig=' + enc(getDomainFromUrl(referrer)); pixelURL += '&ss=' + enc(isS2SBidder(winningBid.bidder)); (fskp != undefined) && (pixelURL += '&fskp=' + enc(fskp)); + if (floorData) { + const floorRootValues = getFloorsCommonField(floorData.floorRequestData); + const { fsrc, fp, mv } = floorRootValues || {}; + const params = { fsrc, fp, fmv: mv }; + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + pixelURL += `&${key}=${enc(value)}`; + } + }); + const floorType = getFloorType(floorData.floorResponseData); + if (floorType !== undefined) { + pixelURL += '&ft=' + enc(floorType); + } + const floorRuleValue = winningBid?.bidResponse?.floorData?.floorRuleValue; + (floorRuleValue !== undefined) && (pixelURL += '&frv=' + enc(floorRuleValue)); + + const floorValue = winningBid?.bidResponse?.floorData?.floorValue; + (floorValue !== undefined) && (pixelURL += '&fv=' + enc(floorValue)); + } pixelURL += '&af=' + enc(winningBid.bidResponse ? (winningBid.bidResponse.mediaType || undefined) : undefined); ajax( diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index f77b167a3e9..263d0416897 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -571,13 +571,13 @@ describe('pubmatic analytics adapter', function () { expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -609,9 +609,6 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -789,14 +786,14 @@ describe('pubmatic analytics adapter', function () { expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.tgid).to.equal(0); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -915,15 +912,13 @@ describe('pubmatic analytics adapter', function () { let request = requests[1]; // logger is executed late, trackers execute first let data = getLoggerJsonFromRequest(request.requestBody); expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); - expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1004,12 +999,13 @@ describe('pubmatic analytics adapter', function () { expect(requests.length).to.equal(1); // 1 logger and 0 win-tracker let request = requests[0]; let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1119,12 +1115,12 @@ describe('pubmatic analytics adapter', function () { let request = requests[2]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1239,12 +1235,12 @@ describe('pubmatic analytics adapter', function () { let request = requests[2]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1357,14 +1353,14 @@ describe('pubmatic analytics adapter', function () { let request = requests[1]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 2 // Testing only for rejected bid as other scenarios will be covered under other TCs expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); @@ -1438,13 +1434,13 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); @@ -1477,9 +1473,6 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); @@ -1570,13 +1563,13 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); From 57936b22f13fcdf48f101fe23e306f55b1d05c4f Mon Sep 17 00:00:00 2001 From: Luca Corbo Date: Wed, 22 Jan 2025 16:07:31 +0100 Subject: [PATCH 0852/1097] WURFL Rtd Provide: add wurfl_id to device.ext (#12675) --- modules/wurflRtdProvider.js | 8 +++++++- modules/wurflRtdProvider.md | 2 +- test/spec/modules/wurflRtdProvider_spec.js | 11 ++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js index dbf50744d25..7bb785366bc 100644 --- a/modules/wurflRtdProvider.js +++ b/modules/wurflRtdProvider.js @@ -65,7 +65,7 @@ const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => { } url.searchParams.set('mode', 'prebid') - logger.logMessage('url', url.toString()); + url.searchParams.set('wurfl_id', 'true') try { loadExternalScript(url.toString(), MODULE_TYPE_RTD, MODULE_NAME, () => { @@ -120,6 +120,9 @@ function enrichBidderRequests(reqBidsConfigObj, bidders, wjsResponse) { */ export const bidderData = (wurflData, caps, filter) => { const data = {}; + if ('wurfl_id' in wurflData) { + data['wurfl_id'] = wurflData.wurfl_id; + } caps.forEach((cap, index) => { if (!filter.includes(index)) { return; @@ -152,6 +155,9 @@ export const lowEntropyData = (wurflData, lowEntropyCaps) => { if ('brand_name' in wurflData) { data['brand_name'] = wurflData.brand_name; } + if ('wurfl_id' in wurflData) { + data['wurfl_id'] = wurflData.wurfl_id; + } return data; } /** diff --git a/modules/wurflRtdProvider.md b/modules/wurflRtdProvider.md index d656add3543..c7993a67364 100644 --- a/modules/wurflRtdProvider.md +++ b/modules/wurflRtdProvider.md @@ -9,7 +9,7 @@ ## Description The WURFL RTD module enriches the OpenRTB 2.0 device data with [WURFL data](https://www.scientiamobile.com/wurfl-js-business-edition-at-the-intersection-of-javascript-and-enterprise/). -The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilites like `is_mobile`, `complete_device_name` and `form_factor`. +The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilities like `is_mobile`, `complete_device_name` and `form_factor`, and the `wurfl_id`. For a more detailed analysis bidders can subscribe to detect iPhone and iPad models and receive additional [WURFL device capabilities](https://www.scientiamobile.com/capabilities/?products%5B%5D=wurfl-js). diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js index 0ac324ef8b2..a683be5304c 100644 --- a/test/spec/modules/wurflRtdProvider_spec.js +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -52,7 +52,8 @@ describe('wurflRtdProvider', function () { pixel_density: 443, pointing_method: 'touchscreen', resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }; // expected analytics values @@ -100,6 +101,7 @@ describe('wurflRtdProvider', function () { const expectedURL = new URL(altHost); expectedURL.searchParams.set('debug', true); expectedURL.searchParams.set('mode', 'prebid'); + expectedURL.searchParams.set('wurfl_id', true); const callback = () => { const v = { @@ -147,7 +149,8 @@ describe('wurflRtdProvider', function () { pixel_density: 443, pointing_method: 'touchscreen', resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }, }, }, @@ -187,7 +190,8 @@ describe('wurflRtdProvider', function () { model_name: 'Nexus 5', pixel_density: 443, resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }, }, }, @@ -203,6 +207,7 @@ describe('wurflRtdProvider', function () { is_mobile: !0, model_name: 'Nexus 5', brand_name: 'Google', + wurfl_id: 'lg_nexus5_ver1', }, }, }, From 65aade6daadd15085f2046604f462e1783893b64 Mon Sep 17 00:00:00 2001 From: Siminko Vlad <85431371+siminkovladyslav@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:17:08 +0100 Subject: [PATCH 0853/1097] OMS Adapter: add video support, test coverage and update documentation (#12671) --- modules/omsBidAdapter.js | 36 ++++++++---- modules/omsBidAdapter.md | 15 +++++ test/spec/modules/omsBidAdapter_spec.js | 78 +++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 11 deletions(-) diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 901b9a138d5..de03f65781e 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -13,7 +13,7 @@ import { formatQS, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {percentInView} from '../libraries/percentInView/percentInView.js'; import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; @@ -27,7 +27,7 @@ export const spec = { code: BIDDER_CODE, aliases: ['brightcom', 'bcmssp'], gvlid: 883, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, @@ -39,7 +39,7 @@ export const spec = { function buildRequests(bidReqs, bidderRequest) { try { const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid?.mediaTypes?.video?.playerSize || bid.sizes; bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); bidSizes = bidSizes.filter(size => isArray(size)); const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); @@ -52,18 +52,25 @@ function buildRequests(bidReqs, bidderRequest) { const imp = { id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded, - } - }, ext: { ...gpidData }, tagid: String(bid.adUnitCode) }; + if (bid?.mediaTypes?.video) { + imp.video = { + ...bid.mediaTypes.video, + } + } else { + imp.banner = { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded, + } + } + } + const bidFloor = _getBidFloor(bid); if (bidFloor) { @@ -158,7 +165,7 @@ function interpretResponse(serverResponse) { try { if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { response = seatbid[0].bid.map(bid => { - return { + const bidResponse = { requestId: bid.impid, cpm: parseFloat(bid.price), width: parseInt(bid.w), @@ -166,13 +173,20 @@ function interpretResponse(serverResponse) { creativeId: bid.crid || bid.id, currency: 'USD', netRevenue: true, - mediaType: BANNER, ad: _getAdMarkup(bid), ttl: 300, meta: { advertiserDomains: bid?.adomain || [] } }; + + if (bid.mtype === 2) { + bidResponse.mediaType = VIDEO; + } else { + bidResponse.mediaType = BANNER; + } + + return bidResponse; }); } } catch (e) { diff --git a/modules/omsBidAdapter.md b/modules/omsBidAdapter.md index 0a6b9cac82c..506ba5fdbd5 100644 --- a/modules/omsBidAdapter.md +++ b/modules/omsBidAdapter.md @@ -41,6 +41,21 @@ var adUnits = [ publisherId: 2141020 } }] + }, + { + code: 'video-instream', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'oms', + params: { + publisherId: 2141020 + } + }] } ] ``` diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index dc2d8ffebb6..54d744131cd 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -130,6 +130,45 @@ describe('omsBidAdapter', function () { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); + it('sets the proper video object when ad unit media type is video', function () { + const bidRequests = [ + { + 'bidder': 'oms', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + } + ] + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.context).to.equal('instream'); + expect(payload.imp[0].video.playerSize).to.deep.equal([640, 480]); + }); + it('accepts a single array as a size', function () { bidRequests[0].mediaTypes.banner.sizes = [300, 250]; const request = spec.buildRequests(bidRequests); @@ -379,6 +418,45 @@ describe('omsBidAdapter', function () { expect(result[0]).to.deep.equal(expectedResponse[0]); }); + it('should get the correct bid response for video bids', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'video', + 'ad': `
`, + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + const response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'], + 'mtype': 2 + }] + }] + } + }; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + it('crid should default to the bid id if not on the response', function () { let expectedResponse = [{ 'requestId': '283a9f4cd2415d', From da5039e4ff585c191da9a9ff52b28272ae04c4a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 08:26:52 -0700 Subject: [PATCH 0854/1097] Bump undici from 6.19.8 to 6.21.1 (#12679) Bumps [undici](https://github.com/nodejs/undici) from 6.19.8 to 6.21.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.19.8...v6.21.1) --- updated-dependencies: - dependency-name: undici dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f821471e62..086c2813ab9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25555,10 +25555,11 @@ "dev": true }, "node_modules/undici": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", - "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.17" } @@ -47359,9 +47360,9 @@ "dev": true }, "undici": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", - "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", "dev": true }, "undici-types": { From b19ae7c1f34dfabe37cdf94f13ff7f9f38250472 Mon Sep 17 00:00:00 2001 From: Chris Corbo Date: Wed, 22 Jan 2025 11:11:18 -0500 Subject: [PATCH 0855/1097] feat: signal ext.ibv and pass it through [PB-3505] (#12666) Co-authored-by: Chris Corbo --- modules/ixBidAdapter.js | 5 +++++ test/spec/modules/ixBidAdapter_spec.js | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index e2a712e0afc..a96b0da132b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -496,6 +496,11 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.ext?.dsa) { bid.meta.dsa = rawBid.ext.dsa } + + if (rawBid.ext?.ibv) { + bid.ext = bid.ext || {} + bid.ext.ibv = rawBid.ext.ibv + } return bid; } diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 5f75e224b50..e0fc7d5affd 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -616,7 +616,8 @@ describe('IndexexchangeAdapter', function () { dspid: 50, pricelevel: '_100', advbrandid: 303325, - advbrand: 'OECTA' + advbrand: 'OECTA', + ibv: true }, adm: '' } @@ -3608,6 +3609,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -3719,6 +3723,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -3775,6 +3782,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; From 2e9b00a1247c42e15bffc38d197e3c55cc4b5b94 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 22 Jan 2025 10:25:03 -0800 Subject: [PATCH 0856/1097] Pubmatic analytics: fix whitespace (#12684) --- modules/pubmaticAnalyticsAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 95890bb2546..8f9dead2cba 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -285,7 +285,7 @@ function getFloorsCommonField (floorData) { if (!floorData) return; const { location, fetchStatus, floorProvider, modelVersion } = floorData; return { - ffs: { + ffs: { [FLOOR_VALUES.SUCCESS]: 1, [FLOOR_VALUES.ERROR]: 2, [FLOOR_VALUES.TIMEOUT]: 4, @@ -535,7 +535,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { if (floorData) { const floorRootValues = getFloorsCommonField(floorData.floorRequestData); const { fsrc, fp, mv } = floorRootValues || {}; - const params = { fsrc, fp, fmv: mv }; + const params = { fsrc, fp, fmv: mv }; Object.entries(params).forEach(([key, value]) => { if (value !== undefined) { pixelURL += `&${key}=${enc(value)}`; From e5bf92176c405adb60bacc4e44f3283da22b6e30 Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Thu, 23 Jan 2025 00:14:57 +0200 Subject: [PATCH 0857/1097] Improve Digital Bid Adapter : remove razr creative logic (#12678) * 12238 - Azerion / Improve: does not properly support currency module * **Type:** Fix * **Scope:** improvedigitalBidAdapter * **Subject:** Bid floors are always converted to USD. * **Details:** * Adds `DEFAULT_CURRENCY` variable which is set to USD * Adds `convertBidFloorCurrency` function which in used to convert the bid floor when both `imp.bidfloor` and `imp.bidfloorcur` are present, and `imp.bidfloorcur` is not equal to the adapter's `DEFAULT_CURRENCY`; * **Breaks:** N/A * restored accidentally discarded change from unit test expect * * Modifies behavior to pass bid floor as is when it cannot be converted to USD; * Removes rounding of bid floor when converting its currency to USD; * remove unnecessary uses of `toUpperCase()` * * fix `convertCurrency` mock * remove redundant checks for type and NaN from `convertBidFloorCurrency` function * ImproveDigital Bid Adapter: remove RAZR specific code * Remove ImproveDigital from loadExternal whitelist --------- Co-authored-by: Lyubomir Shishkov Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> --- modules/improvedigitalBidAdapter.js | 67 ------------------- src/adloader.js | 1 - .../modules/improvedigitalBidAdapter_spec.js | 6 +- 3 files changed, 3 insertions(+), 71 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 33ba08e8fd2..a0347382dd1 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -6,14 +6,6 @@ import {Renderer} from '../src/Renderer.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; -/** - * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception - * ImproveDigital only imports after winning a bid and only if the creative cannot reach top - * Also see https://github.com/prebid/Prebid.js/issues/11656 - */ -// eslint-disable-next-line no-restricted-imports -import {loadExternalScript} from '../src/adloader.js'; -import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -225,7 +217,6 @@ export const CONVERTER = ortbConverter({ renderer: ID_OUTSTREAM.createRenderer(bidRequest) }) } - ID_RAZR.forwardBid({bidRequest, bid: bidResponse}); return bidResponse; }, overrides: { @@ -367,61 +358,3 @@ const ID_OUTSTREAM = { bid.renderer.handleVideoEvent({ id, eventName }); }, }; - -const ID_RAZR = { - RENDERER_URL: 'https://cdn.360yield.com/razr/tag.js', - - forwardBid({bidRequest, bid}) { - if (bid.mediaType !== BANNER) { - return; - } - - const cfg = { - prebid: { - bidRequest, - bid - } - }; - - const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>'); - const s = ``; - // prepend RAZR config to ad markup: - bid.ad = s + bid.ad; - - this.installListener(); - }, - - installListener() { - if (this._listenerInstalled) { - return; - } - - window.addEventListener('message', function(e) { - const data = e.data?.razr?.load; - if (!data) { - return; - } - - if (e.source) { - data.source = e.source; - if (data.id) { - e.source.postMessage({ - razr: { - id: data.id - } - }, '*'); - } - } - - const ns = window.razr = window.razr || {}; - ns.q = ns.q || []; - ns.q.push(data); - - if (!ns.loaded) { - loadExternalScript(ID_RAZR.RENDERER_URL, MODULE_TYPE_BIDDER, BIDDER_CODE); - } - }); - - this._listenerInstalled = true; - } -}; diff --git a/src/adloader.js b/src/adloader.js index bf695dd627b..8fad053f7f5 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -11,7 +11,6 @@ const _approvedLoadExternalJSList = [ 'debugging', 'outstream', // Bid Modules - only exception is on rendering edge cases, to clean up in Prebid 10: - 'improvedigital', 'showheroes-bs', // RTD modules: 'aaxBlockmeter', diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 46e07bacbe4..f6266503297 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1006,7 +1006,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1016,7 +1016,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', }) ]; @@ -1029,7 +1029,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', creativeId: '479163', dealId: 320896, netRevenue: false, From e5da6c8c75571c255a43233e33503f337ad358ff Mon Sep 17 00:00:00 2001 From: Jason Piros Date: Wed, 22 Jan 2025 15:16:50 -0700 Subject: [PATCH 0858/1097] Consumable Bid Adapter: remove EID non-objects (#12646) * consumableBidAdapter: remove eid non-objects * fix lint errors --- modules/consumableBidAdapter.js | 4 +- .../spec/modules/consumableBidAdapter_spec.js | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index cb802508de9..e01078890f9 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -33,7 +33,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests[]} validBidRequests An array of bids + * @param {Object} bidderRequest The bidder's request info. * @return ServerRequest Info describing the request to the server. */ @@ -300,6 +301,7 @@ function retrieveAd(decision, unitId, unitName) { function handleEids(data, validBidRequests) { let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + bidUserIdAsEids = bidUserIdAsEids.filter(e => typeof e === 'object'); deepSetValue(data, 'user.eids', bidUserIdAsEids); } else { deepSetValue(data, 'user.eids', undefined); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 073e889d172..4b0eefd7e8d 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -755,6 +755,48 @@ describe('Consumable BidAdapter', function () { expect(data.user.eids).to.deep.equal(bidderRequest.bidRequest[0].userIdAsEids); }); + it('Request should remove non-objects for userIdAsEids', function () { + bidderRequest.bidRequest[0].userId = {}; + bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; + bidderRequest.bidRequest[0].userIdAsEids = [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + }, + ], + }, + 'RANDOM_IDENTIFIER_STRING' + ]; + let scrubbedEids = [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + }, + ], + }, + ]; + let request = spec.buildRequests( + bidderRequest.bidRequest, + BIDDER_REQUEST_1 + ); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal( + scrubbedEids + ); + }); + it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); let data = JSON.parse(request.data); From 75cb76fd6f5720cfa47767ed46dfa96e0650c9d5 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 Jan 2025 15:18:41 +0000 Subject: [PATCH 0859/1097] Prebid 9.28.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 086c2813ab9..bf174009b54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.28.0-pre", + "version": "9.28.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.28.0-pre", + "version": "9.28.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 6cdf75abeb1..6c64af18cdc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.28.0-pre", + "version": "9.28.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From d782fc53815f036bfd6801e63ba460d176962e67 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 23 Jan 2025 15:18:41 +0000 Subject: [PATCH 0860/1097] Increment version to 9.29.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf174009b54..71717e5bd94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.28.0", + "version": "9.29.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.28.0", + "version": "9.29.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 6c64af18cdc..73cb168609d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.28.0", + "version": "9.29.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From c867a9b6159ffa0e8371e2522a547a1f8050e200 Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:58:32 +0200 Subject: [PATCH 0861/1097] Rubicon bid adapter: Support rtipartner and rtiPartner (#12688) --- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 33 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 411e194b1ee..037e19c5d51 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -566,7 +566,7 @@ export const spec = { inserter || '', matcher || '', mm || '', - uidData?.ext?.rtipartner || '' + uidData?.ext?.rtiPartner || uidData?.ext?.rtipartner || '' ].join('^'); // Return a single string formatted with '^' delimiter const eidValue = buildEidValue(uidData); // Build the EID value string diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index aff6c6df171..9821fc45126 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1562,6 +1562,39 @@ describe('the rubicon adapter', function () { // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123'; + // Check if the generated EID value matches the expected format + expect(data.get('eid_example.com')).to.equal(expectedEidValue); + }); + it('should generate eidValue with all attributes including rtiPartner', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + // Simulating a full EID object with multiple fields + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + source: 'example.com', + uids: [{ + id: '11111', // UID + atype: 2, // atype + ext: { + rtiPartner: 'rtipartner123', // rtiPartner (note the different capitalization) + stype: 'ppuid' // stype + } + }], + inserter: 'inserter123', // inserter + matcher: 'matcher123', // matcher + mm: 'mm123' // mm + }] + } + } + }; + + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = new URLSearchParams(request.data); + + // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner + const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123'; + // Check if the generated EID value matches the expected format expect(data.get('eid_example.com')).to.equal(expectedEidValue); }); From 1e1381aa7c150991078034a9497327fe558baa0d Mon Sep 17 00:00:00 2001 From: Antoine Zazzera Date: Fri, 24 Jan 2025 16:26:09 +0100 Subject: [PATCH 0862/1097] Ogury Bid Adapter : integrate ORTB Converter Library (#12661) * use ortb converter lib on Ogury adapter * override site.page with current location when publisher overrides ortb2 object with wrong values --- modules/oguryBidAdapter.js | 205 ++---- test/spec/modules/oguryBidAdapter_spec.js | 768 ++++++---------------- 2 files changed, 276 insertions(+), 697 deletions(-) diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index ecf512f11d3..d4e5fea1cf0 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -1,10 +1,11 @@ 'use strict'; -import {BANNER} from '../src/mediaTypes.js'; -import {getWindowSelf, getWindowTop, isFn, logWarn, deepAccess, isPlainObject} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ajax} from '../src/ajax.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getWindowSelf, getWindowTop, isFn, deepAccess, isPlainObject, deepSetValue, mergeDeep } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ajax } from '../src/ajax.js'; +import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'ogury'; const GVLID = 31; @@ -12,38 +13,68 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.7.0'; +const ADAPTER_VERSION = '2.0.0'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 60, + mediaType: 'banner' + }, + + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + req.tmax = DEFAULT_TIMEOUT; + deepSetValue(req, 'device.pxratio', window.devicePixelRatio); + deepSetValue(req, 'site.page', getWindowContext().location.href); + + req.ext = mergeDeep({}, req.ext, { + adapterversion: ADAPTER_VERSION, + prebidversion: '$prebid.version$' + }); -function getClientWidth() { - const documentElementClientWidth = window.top.document.documentElement.clientWidth - ? window.top.document.documentElement.clientWidth - : 0 - const innerWidth = window.top.innerWidth ? window.top.innerWidth : 0 - const outerWidth = window.top.outerWidth ? window.top.outerWidth : 0 - const screenWidth = window.top.screen.width ? window.top.screen.width : 0 + const bidWithAssetKey = bidderRequest.bids.find(bid => Boolean(deepAccess(bid, 'params.assetKey', false))); + if (bidWithAssetKey) deepSetValue(req, 'site.id', bidWithAssetKey.params.assetKey); - return documentElementClientWidth || innerWidth || outerWidth || screenWidth -} + const bidWithUserIds = bidderRequest.bids.find(bid => Boolean(bid.userId)); + if (bidWithUserIds) deepSetValue(req, 'user.ext.uids', bidWithUserIds.userId); -function getClientHeight() { - const documentElementClientHeight = window.top.document.documentElement.clientHeight - ? window.top.document.documentElement.clientHeight - : 0 - const innerHeight = window.top.innerHeight ? window.top.innerHeight : 0 - const outerHeight = window.top.outerHeight ? window.top.outerHeight : 0 - const screenHeight = window.top.screen.height ? window.top.screen.height : 0 + return req; + }, - return documentElementClientHeight || innerHeight || outerHeight || screenHeight -} + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const timeSpentOnPage = document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 + const gpid = bidRequest.adUnitCode; + imp.tagid = bidRequest.adUnitCode; + imp.ext = mergeDeep({}, bidRequest.params, { timeSpentOnPage, gpid }, imp.ext); + + const bidfloor = getFloor(bidRequest); + + if (!bidfloor) { + delete imp.bidfloor; + } else { + imp.bidfloor = bidfloor; + } + + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.currency = 'USD'; + return bidResponse; + } +}); function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); - const isValidSizes = Boolean(adUnitSizes) && adUnitSizes.length > 0; - const isValidAdUnitId = !!bid.params.adUnitId; - const isValidAssetKey = !!bid.params.assetKey; + const isValidSize = (Boolean(adUnitSizes) && adUnitSizes.length > 0); + const hasAssetKeyAndAdUnitId = !!deepAccess(bid, 'params.adUnitId') && !!deepAccess(bid, 'params.assetKey'); + const hasPublisherIdAndAdUnitCode = !!deepAccess(bid, 'ortb2.site.publisher.id') && !!bid.adUnitCode; - return (isValidSizes && isValidAdUnitId && isValidAssetKey); + return isValidSize && (hasAssetKeyAndAdUnitId || hasPublisherIdAndAdUnitCode); } function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { @@ -80,126 +111,19 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gpp return []; } -function buildRequests(validBidRequests, bidderRequest) { - const openRtbBidRequestBanner = { - id: bidderRequest.bidderRequestId, - tmax: Math.min(DEFAULT_TIMEOUT, bidderRequest.timeout), - at: 1, - regs: { - ext: { - gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0, - }, - }, - site: { - domain: location.hostname, - page: location.href - }, - user: { - ext: { - consent: '' - } - }, - imp: [], - ext: { - adapterversion: ADAPTER_VERSION, - prebidversion: '$prebid.version$' - }, - device: { - w: getClientWidth(), - h: getClientHeight(), - pxratio: window.devicePixelRatio - } - }; - - if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { - openRtbBidRequestBanner.user.ext.consent = bidderRequest.gdprConsent.consentString - } - if (bidderRequest.gppConsent && bidderRequest.gppConsent.gppString) { - openRtbBidRequestBanner.regs.ext.gpp = bidderRequest.gppConsent.gppString - } - if (bidderRequest.gppConsent && bidderRequest.gppConsent.applicableSections) { - openRtbBidRequestBanner.regs.ext.gpp_sid = bidderRequest.gppConsent.applicableSections - } - - validBidRequests.forEach((bidRequest) => { - const sizes = getAdUnitSizes(bidRequest) - .map(size => ({ w: size[0], h: size[1] })); - - if (bidRequest.mediaTypes && - bidRequest.mediaTypes.hasOwnProperty('banner')) { - openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; - const floor = getFloor(bidRequest); - - if (bidRequest.userId) { - openRtbBidRequestBanner.user.ext.uids = bidRequest.userId - } - if (bidRequest.userIdAsEids) { - openRtbBidRequestBanner.user.ext.eids = bidRequest.userIdAsEids - } - - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); - - openRtbBidRequestBanner.imp.push({ - id: bidRequest.bidId, - tagid: bidRequest.params.adUnitId, - ...(floor && {bidfloor: floor}), - banner: { - format: sizes - }, - ext: { - ...bidRequest.params, - ...(gpid && {gpid}), - timeSpentOnPage: document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 - } - }); - } - }); +function buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}); return { method: 'POST', url: BID_HOST, - data: openRtbBidRequestBanner, + data, options: {contentType: 'application/json'}, }; } -function interpretResponse(openRtbBidResponse) { - if (!openRtbBidResponse || - !openRtbBidResponse.body || - typeof openRtbBidResponse.body != 'object' || - Object.keys(openRtbBidResponse.body).length === 0) { - logWarn('no response or body is malformed'); - return []; - } - - const bidResponses = []; - - openRtbBidResponse.body.seatbid.forEach((seatbid) => { - seatbid.bid.forEach((bid) => { - let bidResponse = { - requestId: bid.impid, - cpm: bid.price, - currency: 'USD', - width: bid.w, - height: bid.h, - creativeId: bid.id, - netRevenue: true, - ttl: 60, - ext: bid.ext, - meta: { - advertiserDomains: bid.adomain - }, - nurl: bid.nurl, - adapterVersion: ADAPTER_VERSION, - prebidVersion: '$prebid.version$' - }; - - bidResponse.ad = bid.adm; - - bidResponses.push(bidResponse); - }); - }); - return bidResponses; +function interpretResponse(response, request) { + return converter.fromORTB({response: response.body, request: request.data}).bids; } function getFloor(bid) { @@ -211,6 +135,7 @@ function getFloor(bid) { mediaType: 'banner', size: '*' }); + return (isPlainObject(floorResult) && floorResult.currency === 'USD') ? floorResult.floor : 0; } diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index 99c16d0b1af..369540de668 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -1,14 +1,16 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { spec } from 'modules/oguryBidAdapter'; import * as utils from 'src/utils.js'; -import {server} from '../../mocks/xhr.js'; +import { server } from '../../mocks/xhr.js'; const BID_URL = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_URL = 'https://ms-ads-monitoring-events.presage.io/bid_timeout' -describe('OguryBidAdapter', function () { - let bidRequests; - let bidderRequest; +describe('OguryBidAdapter', () => { + let bidRequests, bidderRequestBase, ortb2; + + const currentLocation = 'https://mwtt.ogury.tech/advanced'; bidRequests = [ { @@ -48,20 +50,7 @@ describe('OguryBidAdapter', function () { return floorResult; }, transactionId: 'transactionId', - userId: { - pubcid: '2abb10e5-c4f6-4f70-9f45-2200e4487714' - }, - userIdAsEids: [ - { - source: 'pubcid.org', - uids: [ - { - id: '2abb10e5-c4f6-4f70-9f45-2200e4487714', - atype: 1 - } - ] - } - ] + userId: { pubcid: 'f5debac9-9a8e-4c08-9820-51e96b69f858' } }, { adUnitCode: 'adUnitCode2', @@ -81,57 +70,119 @@ describe('OguryBidAdapter', function () { }, ]; - bidderRequest = { + ortb2 = { + regs: { + gpp_sid: [7], + gpp: 'DBABLA~BAAAAAAAAQA.QA', + ext: { gdpr: 1 } + }, + site: { + domain: 'mwtt.ogury.tech', + publisher: { domain: 'ogury.tech', id: 'ca06d4199b92bf6808e5ce15b28c6d30' }, + page: currentLocation, + ref: 'https://google.com' + }, + user: { + ext: { + consent: 'CQJI3tqQJI3tqFzABBENBJFsAP_gAEPgAAqIg1NX_H__bW9r8Xr3aft0eY1P99j77sQxBhfJE-4FyLvW_JwXx2EwNA26tqIKmRIEu3ZBIQFlHJHURVigaogVryHsYkGcgTNKJ6BkgFMRI2dYCF5vmYtj-QKY5_p_d3fx2D-t_dv83dzzz8VHn3e5fmckcKCdQ58tDfn9bRKb-5IO9-78v4v09l_rk2_eTVn_pcvr7B-uft87_XU-9_fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQagCzDQqIA-yJCQi0DCKBACIKwgIoEAAAAJA0QEAJAwKdgYBLrCRACBFAAMEAIAAUZAAgAAEgAQiACQAoEAAEAgEAAAAAAgEADAwADgAtBAIAAQHQMUwoAFAsIEiMiIUwIQoEggJbKBBICgQVwgCLDAigERMFAAgCQAVgAAAsVgMASAlYkECWUG0AABAAgFFKFQik6MAQwJmy1U4om0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAA.YAAAAAAAAAAA', + eids: [ + { + source: 'pubcid.org', + uids: [{ 'id': 'f5debac9-9a8e-4c08-9820-51e96b69f858', 'atype': 1 }] + } + ] + } + }, + device: { + w: 412, + h: 915, + dnt: 0, + ua: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36', + language: 'en', + ext: { vpw: 412, vph: 915 }, + sua: { + source: 1, + platform: { brand: 'Android' }, + browsers: [{ brand: 'Google Chrome', version: ['131'] }], + mobile: 1 + } + } + }; + + bidderRequestBase = { + bids: bidRequests, bidderRequestId: 'mock-uuid', auctionId: bidRequests[0].auctionId, gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, gppConsent: {gppString: 'myGppString', gppData: {}, applicableSections: [7], parsedSections: {}}, - timeout: 1000 + timeout: 1000, + ortb2 }; - describe('isBidRequestValid', function () { + describe('isBidRequestValid', () => { it('should validate correct bid', () => { let validBid = utils.deepClone(bidRequests[0]); let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); + expect(isValid).to.true; }); - it('should not validate incorrect bid', () => { + it('should not validate when sizes is not defined', () => { let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.sizes; delete invalidBid.mediaTypes; let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }); - it('should not validate bid if adunit is not present', () => { + it('should not validate bid when adunit is not defined', () => { let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.params.adUnitId; let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.to.be.false; }); - it('should not validate bid if assetKet is not present', () => { + it('should not validate bid when assetKey is not defined', () => { let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.params.assetKey; let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }); - it('should validate bid if getFloor is not present', () => { - let invalidBid = utils.deepClone(bidRequests[1]); - delete invalidBid.getFloor; + it('should validate the request when only publisherId and adUnitCode is defined', () => { + const validBid = utils.deepClone(bidRequests[0]) + delete validBid.params.adUnitId + delete validBid.params.assetKey - let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(true); + validBid.ortb2 = { site: { publisher: { id: 'publisherId' } } } + + expect(spec.isBidRequestValid(validBid)).to.be.true + }); + + it('should not validate the request when only publisherId is defined', () => { + const invalidBid = utils.deepClone(bidRequests[0]) + delete invalidBid.params.adUnitId + delete invalidBid.params.assetKey + delete invalidBid.adUnitCode + + invalidBid.ortb2 = { site: { publisher: { id: 'publisherId' } } } + + expect(spec.isBidRequestValid(invalidBid)).to.be.false + }); + + it('should not validate the request when only adUnitCode is defined', () => { + const invalidBid = utils.deepClone(bidRequests[0]) + delete invalidBid.params.adUnitId + delete invalidBid.params.assetKey + + expect(spec.isBidRequestValid(invalidBid)).to.be.false }); }); - describe('getUserSyncs', function() { + describe('getUserSyncs', () => { let syncOptions, gdprConsent, gppConsent; beforeEach(() => { @@ -612,17 +663,10 @@ describe('OguryBidAdapter', function () { }); }); - describe('buildRequests', function () { - const stubbedWidth = 200 - const stubbedHeight = 600 + describe('buildRequests', () => { + let windowTopStub; const stubbedCurrentTime = 1234567890 const stubbedDevicePixelRatio = 1 - const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { - return stubbedWidth; - }); - const stubbedHeightMethod = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { - return stubbedHeight; - }); const stubbedCurrentTimeMethod = sinon.stub(document.timeline, 'currentTime').get(function() { return stubbedCurrentTime; }); @@ -632,496 +676,144 @@ describe('OguryBidAdapter', function () { }); const defaultTimeout = 1000; - const expectedRequestObject = { - id: 'mock-uuid', - at: 1, - tmax: defaultTimeout, - imp: [{ - id: bidRequests[0].bidId, - tagid: bidRequests[0].params.adUnitId, - bidfloor: 4, - banner: { - format: [{ - w: 300, - h: 250 - }] - }, - ext: { - ...bidRequests[0].params, - gpid: bidRequests[0].ortb2Imp.ext.gpid, - timeSpentOnPage: stubbedCurrentTime - } - }, { - id: bidRequests[1].bidId, - tagid: bidRequests[1].params.adUnitId, - banner: { - format: [{ - w: 600, - h: 500 - }] - }, - ext: { - ...bidRequests[1].params, - timeSpentOnPage: stubbedCurrentTime - } - }], - regs: { - ext: { - gdpr: 1, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - site: { - id: bidRequests[0].params.assetKey, - domain: window.location.hostname, - page: window.location.href - }, - user: { - ext: { - consent: bidderRequest.gdprConsent.consentString, - uids: { - pubcid: '2abb10e5-c4f6-4f70-9f45-2200e4487714' - }, - eids: [ - { - source: 'pubcid.org', - uids: [ - { - id: '2abb10e5-c4f6-4f70-9f45-2200e4487714', - atype: 1 - } - ] - } - ], - }, - }, - ext: { - prebidversion: '$prebid.version$', - adapterversion: '1.7.0' - }, - device: { - w: stubbedWidth, - h: stubbedHeight, - pxratio: stubbedDevicePixelRatio, - } - }; - - after(function() { - stubbedWidthMethod.restore(); - stubbedHeightMethod.restore(); - stubbedCurrentTimeMethod.restore(); - stubbedDevicePixelMethod.restore(); - }); - - it('sends bid request to ENDPOINT via POST', function () { - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.url).to.equal(BID_URL); - expect(request.method).to.equal('POST'); - }); - - it('timeSpentOnpage should be 0 if timeline is undefined', function () { - const stubbedTimelineMethod = sinon.stub(document, 'timeline').get(function() { - return undefined; - }); - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data.imp[0].ext.timeSpentOnPage).to.equal(0); - stubbedTimelineMethod.restore(); - }); - - it('send device pixel ratio in bid request', function() { - const validBidRequests = utils.deepClone(bidRequests) - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestObject); - expect(request.data.device.pxratio).to.be.a('number'); - }) - - it('bid request object should be conform', function () { - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestObject); - expect(request.data.regs.ext.gdpr).to.be.a('number'); - }); - - describe('getClientWidth', () => { - function testGetClientWidth(testGetClientSizeParams) { - const stubbedClientWidth = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { - return testGetClientSizeParams.docClientSize - }) - - const stubbedInnerWidth = sinon.stub(window.top, 'innerWidth').get(function() { - return testGetClientSizeParams.innerSize - }) - - const stubbedOuterWidth = sinon.stub(window.top, 'outerWidth').get(function() { - return testGetClientSizeParams.outerSize - }) - - const stubbedWidth = sinon.stub(window.top.screen, 'width').get(function() { - return testGetClientSizeParams.screenSize - }) - - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data.device.w).to.equal(testGetClientSizeParams.expectedSize); - - stubbedClientWidth.restore(); - stubbedInnerWidth.restore(); - stubbedOuterWidth.restore(); - stubbedWidth.restore(); - } - - it('should get documentElementClientWidth by default', () => { - testGetClientWidth({ - docClientSize: 22, - innerSize: 50, - outerSize: 45, - screenSize: 10, - expectedSize: 22, - }) - }) - - it('should get innerWidth as first fallback', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: 700, - outerSize: 650, - screenSize: 10, - expectedSize: 700, - }) - }) - - it('should get outerWidth as second fallback', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: undefined, - outerSize: 650, - screenSize: 10, - expectedSize: 650, - }) - }) - - it('should get screenWidth as last fallback', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: 10, - expectedSize: 10, - }); + function assertImpObject(ortbBidRequest, bidRequest) { + expect(ortbBidRequest.secure).to.equal(1); + expect(ortbBidRequest.id).to.equal(bidRequest.bidId); + expect(ortbBidRequest.tagid).to.equal(bidRequest.adUnitCode); + expect(ortbBidRequest.banner).to.deep.equal({ + topframe: 0, + format: [{ + w: bidRequest.mediaTypes.banner.sizes[0][0], + h: bidRequest.mediaTypes.banner.sizes[0][1], + }] }); - it('should return 0 if all window width values are undefined', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: undefined, - expectedSize: 0, - }); + expect(ortbBidRequest.ext).to.deep.equal({ + ...bidRequest.params, + gpid: bidRequest.ortb2Imp?.ext.gpid || bidRequest.adUnitCode, + timeSpentOnPage: stubbedCurrentTime }); - }); - - describe('getClientHeight', () => { - function testGetClientHeight(testGetClientSizeParams) { - const stubbedClientHeight = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { - return testGetClientSizeParams.docClientSize - }) - - const stubbedInnerHeight = sinon.stub(window.top, 'innerHeight').get(function() { - return testGetClientSizeParams.innerSize - }) - - const stubbedOuterHeight = sinon.stub(window.top, 'outerHeight').get(function() { - return testGetClientSizeParams.outerSize - }) - - const stubbedHeight = sinon.stub(window.top.screen, 'height').get(function() { - return testGetClientSizeParams.screenSize - }) + } - const validBidRequests = utils.deepClone(bidRequests) + function assertRequestObject(dataRequest) { + expect(dataRequest.id).to.be.a('string'); + expect(dataRequest.tmax).to.equal(defaultTimeout); - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data.device.h).to.equal(testGetClientSizeParams.expectedSize); + assertImpObject(dataRequest.imp[0], bidRequests[0]); + assertImpObject(dataRequest.imp[1], bidRequests[1]); - stubbedClientHeight.restore(); - stubbedInnerHeight.restore(); - stubbedOuterHeight.restore(); - stubbedHeight.restore(); - } - - it('should get documentElementClientHeight by default', () => { - testGetClientHeight({ - docClientSize: 420, - innerSize: 500, - outerSize: 480, - screenSize: 230, - expectedSize: 420, - }); + expect(dataRequest.imp[0].bidfloor).to.equal(4); + expect(dataRequest.regs).to.deep.equal(ortb2.regs); + expect(dataRequest.site).to.deep.equal({ + ...ortb2.site, + page: currentLocation, + id: bidRequests[0].params.assetKey }); - it('should get innerHeight as first fallback', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: 500, - outerSize: 480, - screenSize: 230, - expectedSize: 500, - }); + expect(dataRequest.user).to.deep.equal({ + ext: { + ...ortb2.user.ext, + uids: bidRequests[0].userId + } }); - it('should get outerHeight as second fallback', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: undefined, - outerSize: 480, - screenSize: 230, - expectedSize: 480, - }); + expect(dataRequest.ext).to.deep.equal({ + prebidversion: '$prebid.version$', + adapterversion: '2.0.0' }); - it('should get screenHeight as last fallback', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: 230, - expectedSize: 230, - }); + expect(dataRequest.device).to.deep.equal({ + ...ortb2.device, + pxratio: stubbedDevicePixelRatio, }); - it('should return 0 if all window height values are undefined', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: undefined, - expectedSize: 0, - }); - }); - }); + expect(dataRequest.regs.ext.gdpr).to.be.a('number'); + expect(dataRequest.device.pxratio).to.be.a('number'); + } - it('should not add gdpr infos if not present', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gdprConsent: {}, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 0, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - user: { - ext: { - consent: '', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); - expect(request.data.regs.ext.gdpr).to.be.a('number'); + beforeEach(() => { + windowTopStub = sinon.stub(utils, 'getWindowTop'); + windowTopStub.returns({ location: { href: currentLocation } }); }); - it('should not add gpp infos if not present', () => { - const bidderRequestWithoutGpp = { - ...bidderRequest, - gppConsent: {}, - } - const expectedRequestObjectWithoutGpp = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 1 - }, - }, - user: { - ext: { - consent: 'myConsentString', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGpp); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGpp); + afterEach(() => { + windowTopStub.restore(); }); - it('should not add gdpr infos if gdprConsent is undefined', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gdprConsent: undefined, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 0, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - user: { - ext: { - consent: '', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); - expect(request.data.regs.ext.gdpr).to.be.a('number'); + after(() => { + stubbedCurrentTimeMethod.restore(); + stubbedDevicePixelMethod.restore(); }); - it('should not add gpp infos if gppConsent is undefined', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gppConsent: undefined, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 1, - }, - }, - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequestBase); + expect(request.url).to.equal(BID_URL); + expect(request.method).to.equal('POST'); }); - it('should not add tcString and turn off gdpr-applies if consentString and gdprApplies are undefined', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gdprConsent: { consentString: undefined, gdprApplies: undefined }, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 0, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - user: { - ext: { - consent: '', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests + it('timeSpentOnpage should be 0 if timeline is undefined', function () { + const stubbedTimelineMethod = sinon.stub(document, 'timeline').get(function() { + return undefined; + }); - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); - expect(request.data.regs.ext.gdpr).to.be.a('number'); + const request = spec.buildRequests(bidRequests, bidderRequestBase); + expect(request.data.imp[0].ext.timeSpentOnPage).to.equal(0); + stubbedTimelineMethod.restore(); }); - it('should should not add uids infos if userId is undefined', () => { - const expectedRequestWithUndefinedUserId = { - ...expectedRequestObject, - user: { - ext: { - consent: expectedRequestObject.user.ext.consent, - eids: expectedRequestObject.user.ext.eids - } - } - }; + it('bid request object should be conform', function () { + const request = spec.buildRequests(bidRequests, bidderRequestBase); + assertRequestObject(request.data); + }); - const validBidRequests = utils.deepClone(bidRequests); - validBidRequests[0] = { - ...validBidRequests[0], - userId: undefined - }; + it('should not set site.id when assetKey is not present', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + delete validBidRequests[0].params.assetKey; + delete validBidRequests[1].params.assetKey; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedUserId); + expect(request.data.site.id).to.be.an('undefined'); }); - it('should should not add uids infos if userIdAsEids is undefined', () => { - const expectedRequestWithUndefinedUserIdAsEids = { - ...expectedRequestObject, - user: { - ext: { - consent: expectedRequestObject.user.ext.consent, - uids: expectedRequestObject.user.ext.uids - } - } - }; - - const validBidRequests = utils.deepClone(bidRequests); - validBidRequests[0] = { - ...validBidRequests[0], - userIdAsEids: undefined - }; + it('should not set user.ext.uids when userId is not present', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + delete validBidRequests[0].userId; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedUserIdAsEids); + expect(request.data.user.ext.uids).to.be.an('undefined'); }); it('should handle bidFloor undefined', () => { - const expectedRequestWithUndefinedFloor = { - ...expectedRequestObject - }; - - const validBidRequests = utils.deepClone(bidRequests); - validBidRequests[1] = { - ...validBidRequests[1], + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + validBidRequests[0] = { + ...validBidRequests[0], getFloor: undefined }; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedFloor); + expect(request.data.imp[0].bidfloor).to.be.an('undefined'); }); it('should handle bidFloor when is not function', () => { - const expectedRequestWithNotAFunctionFloor = { - ...expectedRequestObject - }; - - let validBidRequests = utils.deepClone(bidRequests); - validBidRequests[1] = { - ...validBidRequests[1], + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + validBidRequests[0] = { + ...validBidRequests[0], getFloor: 'getFloor' }; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithNotAFunctionFloor); + expect(request.data.imp[0].bidfloor).to.be.an('undefined'); }); it('should handle bidFloor when currency is not USD', () => { - const expectedRequestWithUnsupportedFloorCurrency = utils.deepClone(expectedRequestObject) - delete expectedRequestWithUnsupportedFloorCurrency.imp[0].bidfloor; - let validBidRequests = utils.deepClone(bidRequests); + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + validBidRequests[0] = { ...validBidRequests[0], getFloor: ({ size, currency, mediaType }) => { @@ -1131,30 +823,23 @@ describe('OguryBidAdapter', function () { } } }; + const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUnsupportedFloorCurrency); + expect(request.data.imp[0].bidfloor).to.be.an('undefined'); }); - it('should not add gpid if ortb2 undefined', () => { - const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) - - delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; - delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; - - const validBidRequests = utils.deepClone(bidRequests); + it('should use adUnitCode when gpid from ortb2 is undefined', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; delete validBidRequests[0].ortb2Imp.ext.gpid; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + expect(request.data.imp[0].ext.gpid).to.equal(bidRequests[0].adUnitCode); }); - it('should not add gpid if gpid undefined', () => { - const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) - - delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; - delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; - - const validBidRequests = utils.deepClone(bidRequests); + it('should use adUnitCode when gpid is not present in ortb2Imp object', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; validBidRequests[0] = { ...validBidRequests[0], ortb2Imp: { @@ -1163,18 +848,17 @@ describe('OguryBidAdapter', function () { }; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + expect(request.data.imp[0].ext.gpid).to.equal(bidRequests[0].adUnitCode); }); - it('should send gpid in bid request', function() { - const validBidRequests = utils.deepClone(bidRequests) + it('should set the actual site location in site.page when the ORTB object contains the referrer instead of the current location', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + bidderRequest.ortb2.site.page = 'https://google.com'; - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestObject); - expect(request.data.imp[0].ext.gpid).to.be.a('string'); - expect(request.data.imp[1].ext.gpid).to.be.undefined - }) - }) + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.site.page).to.equal(currentLocation); + }); + }); describe('interpretResponse', function () { let openRtbBidResponse = { @@ -1186,7 +870,7 @@ describe('OguryBidAdapter', function () { impid: 'bidId', price: 100, nurl: 'url', - adm: `test creative
cookies
`, + adm: `
cookies
`, adomain: ['renault.fr'], ext: { adcontent: 'sample_creative', @@ -1205,7 +889,7 @@ describe('OguryBidAdapter', function () { impid: 'bidId2', price: 150, nurl: 'url2', - adm: `test creative
cookies
`, + adm: `
cookies
`, adomain: ['peugeot.fr'], ext: { adcontent: 'sample_creative', @@ -1225,57 +909,27 @@ describe('OguryBidAdapter', function () { } }; - it('should correctly interpret bidResponse', () => { - let expectedInterpretedBidResponse = [{ - requestId: openRtbBidResponse.body.seatbid[0].bid[0].impid, - cpm: openRtbBidResponse.body.seatbid[0].bid[0].price, - currency: 'USD', - width: openRtbBidResponse.body.seatbid[0].bid[0].w, - height: openRtbBidResponse.body.seatbid[0].bid[0].h, - ad: openRtbBidResponse.body.seatbid[0].bid[0].adm, - ttl: 60, - ext: openRtbBidResponse.body.seatbid[0].bid[0].ext, - creativeId: openRtbBidResponse.body.seatbid[0].bid[0].id, - netRevenue: true, - meta: { - advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain - }, - nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.7.0', - prebidVersion: '$prebid.version$' - }, { - requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, - cpm: openRtbBidResponse.body.seatbid[0].bid[1].price, - currency: 'USD', - width: openRtbBidResponse.body.seatbid[0].bid[1].w, - height: openRtbBidResponse.body.seatbid[0].bid[1].h, - ad: openRtbBidResponse.body.seatbid[0].bid[1].adm, - ttl: 60, - ext: openRtbBidResponse.body.seatbid[0].bid[1].ext, - creativeId: openRtbBidResponse.body.seatbid[0].bid[1].id, - netRevenue: true, - meta: { - advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain - }, - nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.7.0', - prebidVersion: '$prebid.version$' - }] + function assertPrebidBidResponse(prebidBidResponse, ortbResponse) { + expect(prebidBidResponse.ttl).to.equal(60); + expect(prebidBidResponse.currency).to.equal('USD'); + expect(prebidBidResponse.netRevenue).to.be.true; + expect(prebidBidResponse.mediaType).to.equal('banner'); + expect(prebidBidResponse.requestId).to.equal(ortbResponse.impid); + expect(prebidBidResponse.cpm).to.equal(ortbResponse.price); + expect(prebidBidResponse.width).to.equal(ortbResponse.w); + expect(prebidBidResponse.height).to.equal(ortbResponse.h); + expect(prebidBidResponse.ad).to.contain(ortbResponse.adm); + expect(prebidBidResponse.meta.advertiserDomains).to.equal(ortbResponse.adomain); + expect(prebidBidResponse.seatBidId).to.equal(ortbResponse.id); + } - let request = spec.buildRequests(bidRequests, bidderRequest); - let result = spec.interpretResponse(openRtbBidResponse, request); + it('should correctly interpret bidResponse', () => { + const request = spec.buildRequests(bidRequests, bidderRequestBase); + const result = spec.interpretResponse(openRtbBidResponse, request); - expect(result).to.deep.equal(expectedInterpretedBidResponse) + assertPrebidBidResponse(result[0], openRtbBidResponse.body.seatbid[0].bid[0]) + assertPrebidBidResponse(result[1], openRtbBidResponse.body.seatbid[0].bid[1]) }); - - it('should return empty array if error during parsing', () => { - const wrongOpenRtbBidReponse = 'wrong data' - let request = spec.buildRequests(bidRequests, bidderRequest); - let result = spec.interpretResponse(wrongOpenRtbBidReponse, request); - - expect(result).to.be.instanceof(Array); - expect(result.length).to.equal(0) - }) }); describe('onBidWon', function() { From 3ee33a26712b4d7a1acdfcbc4b70f29bbc7cb148 Mon Sep 17 00:00:00 2001 From: MaksymTeqBlaze Date: Fri, 24 Jan 2025 17:36:25 +0200 Subject: [PATCH 0863/1097] TeqBlaze Utils : added support of bcat, badv, bapp, battr (#12685) * Added library * updated bidderUtils and used it in some adapters * added library functions to several new adapters * added library functions to several new adapters * added library functions to several new adapters * added support of bcat, bapp, badv, battr * fix playdigo unit tests --- libraries/teqblazeUtils/bidderUtils.js | 6 +++++- test/spec/libraries/teqblazeUtils/bidderUtils_spec.js | 6 +++++- test/spec/modules/acuityadsBidAdapter_spec.js | 6 +++++- test/spec/modules/admanBidAdapter_spec.js | 6 +++++- test/spec/modules/adprimeBidAdapter_spec.js | 6 +++++- test/spec/modules/ads_interactiveBidAdapter_spec.js | 6 +++++- test/spec/modules/axisBidAdapter_spec.js | 6 +++++- test/spec/modules/beyondmediaBidAdapter_spec.js | 6 +++++- test/spec/modules/boldwinBidAdapter_spec.js | 6 +++++- test/spec/modules/compassBidAdapter_spec.js | 6 +++++- test/spec/modules/contentexchangeBidAdapter_spec.js | 6 +++++- test/spec/modules/copper6sspBidAdapter_spec.js | 6 +++++- test/spec/modules/e_volutionBidAdapter_spec.js | 6 +++++- test/spec/modules/edge226BidAdapter_spec.js | 6 +++++- test/spec/modules/emtvBidAdapter_spec.js | 6 +++++- test/spec/modules/globalsunBidAdapter_spec.js | 6 +++++- test/spec/modules/iqzoneBidAdapter_spec.js | 6 +++++- test/spec/modules/kiviadsBidAdapter_spec.js | 6 +++++- test/spec/modules/krushmediaBidAdapter_spec.js | 6 +++++- test/spec/modules/loyalBidAdapter_spec.js | 6 +++++- test/spec/modules/lunamediahbBidAdapter_spec.js | 6 +++++- test/spec/modules/mathildeadsBidAdapter_spec.js | 6 +++++- test/spec/modules/mgidXBidAdapter_spec.js | 6 +++++- test/spec/modules/mobfoxpbBidAdapter_spec.js | 6 +++++- test/spec/modules/orakiBidAdapter_spec.js | 6 +++++- test/spec/modules/pgamsspBidAdapter_spec.js | 6 +++++- test/spec/modules/playdigoBidAdapter_spec.js | 6 +++++- test/spec/modules/pubCircleBidAdapter_spec.js | 6 +++++- test/spec/modules/pubriseBidAdapter_spec.js | 6 +++++- test/spec/modules/qtBidAdapter_spec.js | 6 +++++- test/spec/modules/smarthubBidAdapter_spec.js | 6 +++++- test/spec/modules/smootBidAdapter_spec.js | 6 +++++- test/spec/modules/visiblemeasuresBidAdapter_spec.js | 6 +++++- 33 files changed, 165 insertions(+), 33 deletions(-) diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index f9484ebe5d1..84010adbbd6 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -148,7 +148,11 @@ export const buildRequestsBase = (config) => { page, placements, coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - tmax: bidderRequest.timeout + tmax: bidderRequest.timeout, + bcat: deepAccess(bidderRequest, 'ortb2.bcat'), + badv: deepAccess(bidderRequest, 'ortb2.badv'), + bapp: deepAccess(bidderRequest, 'ortb2.bapp'), + battr: deepAccess(bidderRequest, 'ortb2.battr') }; if (bidderRequest.uspConsent) { diff --git a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js index ba8b986163b..4bc74f50de5 100644 --- a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -151,7 +151,11 @@ describe('TeqBlazeBidderUtils', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index 3f2705a26c3..40de7db69c3 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AcuityAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index ae3b935619e..abae74b9749 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AdmanBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 4199145e80a..c185ef3c1ad 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AdprimeBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js index 273df230287..a0b8c67af93 100644 --- a/test/spec/modules/ads_interactiveBidAdapter_spec.js +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -146,7 +146,11 @@ describe('AdsInteractiveBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/axisBidAdapter_spec.js b/test/spec/modules/axisBidAdapter_spec.js index 2db6c907851..65f1b9cbd94 100644 --- a/test/spec/modules/axisBidAdapter_spec.js +++ b/test/spec/modules/axisBidAdapter_spec.js @@ -154,7 +154,11 @@ describe('AxisBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 9f31294dc54..b117b2c4972 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AndBeyondMediaBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 820938dcec2..1435ba2c28b 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('BoldwinBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index d0cecc2272f..48be231d7d4 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('CompassBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 913c9072dd5..c006aa520e1 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('ContentexchangeBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js index a97ef3ecbe5..4e62a416fb8 100644 --- a/test/spec/modules/copper6sspBidAdapter_spec.js +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -146,7 +146,11 @@ describe('Copper6SSPBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index 4777b73aa3c..4a97988b128 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('EvolutionTechBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/edge226BidAdapter_spec.js b/test/spec/modules/edge226BidAdapter_spec.js index e9e1c34b9cd..b1cf07d5bed 100644 --- a/test/spec/modules/edge226BidAdapter_spec.js +++ b/test/spec/modules/edge226BidAdapter_spec.js @@ -146,7 +146,11 @@ describe('Edge226BidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js index ce81dc15ad4..e68a65a04d6 100644 --- a/test/spec/modules/emtvBidAdapter_spec.js +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -147,7 +147,11 @@ describe('EMTVBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 0651b05894f..f8d6e2b710d 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('GlobalsunBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index f642a935f69..210d3a2d60b 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('IQZoneBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index e2d23d06822..bd59a50e3ae 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -147,7 +147,11 @@ describe('KiviAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 516b587edff..f6fe1b5661b 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('KrushmediabBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/loyalBidAdapter_spec.js b/test/spec/modules/loyalBidAdapter_spec.js index fbfcdcd0742..1c9106e3be8 100644 --- a/test/spec/modules/loyalBidAdapter_spec.js +++ b/test/spec/modules/loyalBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('LoyalBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index 8ae3220504d..b715fb0d0c3 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('LunamediaHBBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index cdd1ffbc4bd..eb4318199af 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('MathildeAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 9b39b307dc4..f933a61ee55 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -157,7 +157,11 @@ describe('MGIDXBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index 1bf1ec12bc4..c926c2c9bfc 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('MobfoxHBBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js index 9a7c777212b..1a00100cf61 100644 --- a/test/spec/modules/orakiBidAdapter_spec.js +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -142,7 +142,11 @@ describe('OrakiBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index bdb837b70a4..ace20539459 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('PGAMBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js index 591892beb8c..107e0ebc7aa 100644 --- a/test/spec/modules/playdigoBidAdapter_spec.js +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('PlaydigoBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index 083031101f1..f02aab9d4d6 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('PubCircleBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js index e6dc710382c..37f1c742c65 100644 --- a/test/spec/modules/pubriseBidAdapter_spec.js +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -146,7 +146,11 @@ describe('PubriseBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js index f1c1ca61664..9319df0f660 100644 --- a/test/spec/modules/qtBidAdapter_spec.js +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('QTBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index 302195ea944..e810acf5271 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -159,7 +159,11 @@ describe('SmartHubBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/smootBidAdapter_spec.js b/test/spec/modules/smootBidAdapter_spec.js index cf72b41b348..f51c054f883 100644 --- a/test/spec/modules/smootBidAdapter_spec.js +++ b/test/spec/modules/smootBidAdapter_spec.js @@ -136,7 +136,11 @@ describe('SmootBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index 55f2a74ce77..a6efeca73e2 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -147,7 +147,11 @@ describe('VisibleMeasuresBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); From 0786b8589a2f0e7b0d225d368c068e48940459a8 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:45:08 +0200 Subject: [PATCH 0864/1097] Smarthub Bid Adapter : add Adinify alias (#12672) * update adapter SmartHub: add aliases * Attekmi: add new adapter Adinify --------- Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index d6600b4afd5..e5aa50bcad6 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -16,6 +16,7 @@ const ALIASES = [ {code: 'felixads'}, {code: 'vimayx'}, {code: 'artechnology'}, + {code: 'adinify'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', @@ -26,6 +27,7 @@ const BASE_URLS = { felixads: 'https://felixads-prebid.attekmi.com/pbjs', vimayx: 'https://vimayx-prebid.attekmi.com/pbjs', artechnology: 'https://artechnology-prebid.attekmi.com/pbjs', + adinify: 'https://adinify-prebid.attekmi.com/pbjs', }; const _getUrl = (partnerName) => { From a68abf33c697cff6f5ded5bc86e5b7f53c7e9a98 Mon Sep 17 00:00:00 2001 From: Sir-Will Date: Fri, 24 Jan 2025 16:59:10 +0100 Subject: [PATCH 0865/1097] Update x-domain creative example, to use double quotes (#12682) Using single quotes leads to issues with `%%PATTERN:url%%`, as `encodeURIComponent(`'___"`)` does not encode single quotes and therefore results in syntax errors when a URL contains a single quote. --- integrationExamples/gpt/x-domain/creative.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 63842b00882..f523c87b326 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -6,8 +6,8 @@ From 3ad2f3973a6f798ffd92844f414100783d95aca6 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 27 Jan 2025 09:12:18 -0800 Subject: [PATCH 0866/1097] priceFloors & PBS adapter: support mediaType and size specific floors (#12690) * priceFloors: pass floor request parameters to inverse adjustment function * priceFloors: add support for per-mediatype/format floors in ortbConverter * PBS adapter: support mediaType/format level floors * do not import ortbConverter from priceFloors; cleanup tests --- .../prebidServerBidAdapter/ortbConverter.js | 56 +++- modules/priceFloors.js | 80 ++++- src/mediaTypes.js | 2 + .../modules/prebidServerBidAdapter_spec.js | 124 +++++--- test/spec/modules/priceFloors_spec.js | 297 ++++++++++-------- test/spec/ortbConverter/priceFloors_spec.js | 84 ++++- 6 files changed, 434 insertions(+), 209 deletions(-) diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 51a01e04fdd..99fbcf3d2bb 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -1,5 +1,5 @@ import {ortbConverter} from '../../libraries/ortbConverter/converter.js'; -import {deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; +import {deepClone, deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; import {config} from '../../src/config.js'; import {S2S, STATUS} from '../../src/constants.js'; import {createBid} from '../../src/bidfactory.js'; @@ -17,12 +17,25 @@ import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; import {minimum} from '../../src/utils/reducers.js'; import {s2sDefaultConfig} from './index.js'; import {premergeFpd} from './bidderConfig.js'; +import {ALL_MEDIATYPES, BANNER} from '../../src/mediaTypes.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; const DEFAULT_S2S_NETREVENUE = true; const BIDDER_SPECIFIC_REQUEST_PROPS = new Set(['bidderCode', 'bidderRequestId', 'uniquePbsTid', 'bids', 'timeout']); +const getMinimumFloor = (() => { + const getMin = minimum(currencyCompare(floor => [floor.bidfloor, floor.bidfloorcur])); + return function(candidates) { + let min; + for (const candidate of candidates) { + if (candidate?.bidfloorcur == null || candidate?.bidfloor == null) return null; + min = min == null ? candidate : getMin(min, candidate); + } + return min; + } +})(); + const PBS_CONVERTER = ortbConverter({ processors: pbsExtensions, context: { @@ -126,24 +139,39 @@ const PBS_CONVERTER = ortbConverter({ } } }, + // for bid floors, we pass each bidRequest associated with this imp through normal bidfloor/extBidfloor processing, + // and aggregate all of them into a single, minimum floor to put in the request bidfloor(orig, imp, proxyBidRequest, context) { - // for bid floors, we pass each bidRequest associated with this imp through normal bidfloor processing, - // and aggregate all of them into a single, minimum floor to put in the request - const getMin = minimum(currencyCompare(floor => [floor.bidfloor, floor.bidfloorcur])); - let min; - for (const req of context.actualBidRequests.values()) { - const floor = {}; - orig(floor, req, context); - // if any bid does not have a valid floor, do not attempt to send any to PBS - if (floor.bidfloorcur == null || floor.bidfloor == null) { - min = null; - break; + const min = getMinimumFloor((function * () { + for (const req of context.actualBidRequests.values()) { + const floor = {}; + orig(floor, req, context); + yield floor; } - min = min == null ? floor : getMin(min, floor); - } + })()) if (min != null) { Object.assign(imp, min); } + }, + extBidfloor(orig, imp, proxyBidRequest, context) { + function setExtFloor(target, minFloor) { + if (minFloor != null) { + deepSetValue(target, 'ext.bidfloor', minFloor.bidfloor); + deepSetValue(target, 'ext.bidfloorcur', minFloor.bidfloorcur); + } + } + const imps = Array.from(context.actualBidRequests.values()) + .map(request => { + const requestImp = deepClone(imp); + orig(requestImp, request, context); + return requestImp; + }); + Object.values(ALL_MEDIATYPES).forEach(mediaType => { + setExtFloor(imp[mediaType], getMinimumFloor(imps.map(imp => imp[mediaType]?.ext))) + }); + (imp[BANNER]?.format || []).forEach((format, i) => { + setExtFloor(format, getMinimumFloor(imps.map(imp => imp[BANNER].format[i]?.ext))) + }) } }, [REQUEST]: { diff --git a/modules/priceFloors.js b/modules/priceFloors.js index d14a82af360..ab499ce7698 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -31,6 +31,7 @@ import {adjustCpm} from '../src/utils/cpm.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js'; +import {ALL_MEDIATYPES, BANNER} from '../src/mediaTypes.js'; export const FLOOR_SKIPPED_REASON = { NOT_FOUND: 'not_found', @@ -264,7 +265,10 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // pub provided inverse function takes precedence, otherwise do old adjustment stuff const inverseFunction = bidderSettings.get(bidRequest.bidder, 'inverseBidAdjustment'); if (inverseFunction) { - floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); + const definedParams = Object.fromEntries( + Object.entries(requestParams).filter(([key, val]) => val !== '*' && ['mediaType', 'size'].includes(key)) + ); + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest, definedParams); } else { let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; @@ -801,30 +805,70 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a config.getConfig('floors', config => handleSetFloorsConfig(config.floors)); -/** - * Sets bidfloor and bidfloorcur for ORTB imp objects - */ -export function setOrtbImpBidFloor(imp, bidRequest, context) { +function tryGetFloor(bidRequest, {currency = config.getConfig('currency.adServerCurrency') || 'USD', mediaType = '*', size = '*'}, fn) { if (typeof bidRequest.getFloor === 'function') { - let currency, floor; + let floor; try { - ({currency, floor} = bidRequest.getFloor({ - currency: context.currency || config.getConfig('currency.adServerCurrency') || 'USD', - mediaType: context.mediaType || '*', - size: '*' - }) || {}); + floor = bidRequest.getFloor({ + currency, + mediaType, + size + }) || {}; } catch (e) { logWarn('Cannot compute floor for bid', bidRequest); return; } - floor = parseFloat(floor); - if (currency != null && floor != null && !isNaN(floor)) { - Object.assign(imp, { - bidfloor: floor, - bidfloorcur: currency - }); + floor.floor = parseFloat(floor.floor); + if (floor.currency != null && floor.floor && !isNaN(floor.floor)) { + fn(floor.floor, floor.currency); + } + } +} + +/** + * Sets bidfloor and bidfloorcur for ORTB imp objects + */ +export function setOrtbImpBidFloor(imp, bidRequest, context) { + tryGetFloor(bidRequest, { + currency: context.currency, + mediaType: context.mediaType || '*', + size: '*' + }, (bidfloor, bidfloorcur) => { + Object.assign(imp, { + bidfloor, + bidfloorcur + }); + }) +} + +/** + * Set per-mediatype and per-format bidfloor + */ +export function setGranularBidfloors(imp, bidRequest, context) { + function setIfDifferent(bidfloor, bidfloorcur) { + if (bidfloor !== imp.bidfloor || bidfloorcur !== imp.bidfloorcur) { + deepSetValue(this, 'ext.bidfloor', bidfloor); + deepSetValue(this, 'ext.bidfloorcur', bidfloorcur); } } + + Object.values(ALL_MEDIATYPES) + .filter(mediaType => imp[mediaType] != null) + .forEach(mediaType => { + tryGetFloor(bidRequest, { + currency: imp.bidfloorcur || context?.currency, + mediaType + }, setIfDifferent.bind(imp[mediaType])) + }); + (imp[BANNER]?.format || []) + .filter(({w, h}) => w != null && h != null) + .forEach(format => { + tryGetFloor(bidRequest, { + currency: imp.bidfloorcur || context?.currency, + mediaType: BANNER, + size: [format.w, format.h] + }, setIfDifferent.bind(format)) + }) } export function setImpExtPrebidFloors(imp, bidRequest, context) { @@ -867,5 +911,7 @@ export function setOrtbExtPrebidFloors(ortbRequest, bidderRequest, context) { } registerOrtbProcessor({type: IMP, name: 'bidfloor', fn: setOrtbImpBidFloor}); +// granular floors should be set after both "normal" bidfloors and mediaypes +registerOrtbProcessor({type: IMP, name: 'extBidfloor', fn: setGranularBidfloors, priority: -10}) registerOrtbProcessor({type: IMP, name: 'extPrebidFloors', fn: setImpExtPrebidFloors, dialects: [PBS], priority: -1}); registerOrtbProcessor({type: REQUEST, name: 'extPrebidFloors', fn: setOrtbExtPrebidFloors, dialects: [PBS]}); diff --git a/src/mediaTypes.js b/src/mediaTypes.js index 2afa2aefaf9..9b5318e0657 100644 --- a/src/mediaTypes.js +++ b/src/mediaTypes.js @@ -18,3 +18,5 @@ export const VIDEO = 'video'; export const BANNER = 'banner'; /** @type {VideoContext} */ export const ADPOD = 'adpod'; + +export const ALL_MEDIATYPES = [NATIVE, VIDEO, BANNER]; diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index fc0f9ed7b68..fb1b62b9dc6 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1372,65 +1372,97 @@ describe('S2S Adapter', function () { updateBid(BID_REQUESTS[1].bids[0]); adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const pbsReq = JSON.parse(server.requests[server.requests.length - 1].requestBody); - expect(pbsReq.imp[0].bidfloor).to.be.undefined; - expect(pbsReq.imp[0].bidfloorcur).to.be.undefined; + [pbsReq.imp[0], pbsReq.imp[0].banner, pbsReq.imp[0].banner.format[0]].forEach(obj => { + expect(obj.bidfloor).to.be.undefined; + expect(obj.bidfloorcur).to.be.undefined; + }) }); }) Object.entries({ - 'is available': { - expectDesc: 'minimum after conversion', - expectedFloor: 10, - expectedCur: '0.1', - conversionFn: (amount, from, to) => { - from = parseFloat(from); - to = parseFloat(to); - return amount * from / to; - }, + 'imp level floors': { + target: 'imp.0' }, - 'is not available': { - expectDesc: 'absolute minimum', - expectedFloor: 1, - expectedCur: '10', - conversionFn: null + 'mediaType level floors': { + target: 'imp.0.banner.ext', + floorFilter: ({mediaType, size}) => size === '*' && mediaType !== '*' }, - 'is not working': { - expectDesc: 'absolute minimum', - expectedFloor: 1, - expectedCur: '10', - conversionFn: () => { - throw new Error(); - } + 'format level floors': { + target: 'imp.0.banner.format.0.ext', + floorFilter: ({size}) => size !== '*' } - }).forEach(([t, {expectDesc, expectedFloor, expectedCur, conversionFn}]) => { - describe(`and currency conversion ${t}`, () => { - let mockConvertCurrency; - const origConvertCurrency = getGlobal().convertCurrency; + }).forEach(([t, {target, floorFilter}]) => { + describe(t, () => { beforeEach(() => { - if (conversionFn) { - getGlobal().convertCurrency = mockConvertCurrency = sinon.stub().callsFake(conversionFn) - } else { - mockConvertCurrency = null; - delete getGlobal().convertCurrency; + if (floorFilter != null) { + BID_REQUESTS + .flatMap(req => req.bids) + .forEach(req => { + req.getFloor = ((orig) => (params) => { + if (floorFilter(params)) { + return orig(params); + } + })(req.getFloor); + }) } - }); + }) - afterEach(() => { - if (origConvertCurrency != null) { - getGlobal().convertCurrency = origConvertCurrency; - } else { - delete getGlobal().convertCurrency; + Object.entries({ + 'is available': { + expectDesc: 'minimum after conversion', + expectedFloor: 10, + expectedCur: '0.1', + conversionFn: (amount, from, to) => { + from = parseFloat(from); + to = parseFloat(to); + return amount * from / to; + }, + }, + 'is not available': { + expectDesc: 'absolute minimum', + expectedFloor: 1, + expectedCur: '10', + conversionFn: null + }, + 'is not working': { + expectDesc: 'absolute minimum', + expectedFloor: 1, + expectedCur: '10', + conversionFn: () => { + throw new Error(); + } } - }); + }).forEach(([t, {expectDesc, expectedFloor, expectedCur, conversionFn}]) => { + describe(`and currency conversion ${t}`, () => { + let mockConvertCurrency; + const origConvertCurrency = getGlobal().convertCurrency; + beforeEach(() => { + if (conversionFn) { + getGlobal().convertCurrency = mockConvertCurrency = sinon.stub().callsFake(conversionFn) + } else { + mockConvertCurrency = null; + delete getGlobal().convertCurrency; + } + }); - it(`should pick the ${expectDesc}`, () => { - adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); - const pbsReq = JSON.parse(server.requests[server.requests.length - 1].requestBody); - expect(pbsReq.imp[0].bidfloor).to.eql(expectedFloor); - expect(pbsReq.imp[0].bidfloorcur).to.eql(expectedCur); + afterEach(() => { + if (origConvertCurrency != null) { + getGlobal().convertCurrency = origConvertCurrency; + } else { + delete getGlobal().convertCurrency; + } + }); + + it(`should pick the ${expectDesc}`, () => { + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); + const pbsReq = JSON.parse(server.requests[server.requests.length - 1].requestBody); + expect(deepAccess(pbsReq, `${target}.bidfloor`)).to.eql(expectedFloor); + expect(deepAccess(pbsReq, `${target}.bidfloorcur`)).to.eql(expectedCur) + }); + }); }); - }); - }); + }) + }) }); }); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 41f1609f74f..8cfa6aabb12 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1865,159 +1865,194 @@ describe('the price floors module', function () { }); }); - it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { - let functionUsed; - getGlobal().bidderSettings = { - rubicon: { - bidCpmAdjustment: function (bidCpm, bidResponse) { - functionUsed = 'Rubicon Adjustment'; - bidCpm *= 0.5; - if (bidResponse.mediaType === 'video') bidCpm -= 0.18; - return bidCpm; - }, - inverseBidAdjustment: function (bidCpm, bidRequest) { - functionUsed = 'Rubicon Inverse'; - // if video is the only mediaType on Bid Request => add 0.18 - if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; - return bidCpm / 0.5; - }, - }, - appnexus: { - bidCpmAdjustment: function (bidCpm, bidResponse) { - functionUsed = 'Appnexus Adjustment'; - bidCpm *= 0.75; - if (bidResponse.mediaType === 'video') bidCpm -= 0.18; - return bidCpm; - }, - inverseBidAdjustment: function (bidCpm, bidRequest) { - functionUsed = 'Appnexus Inverse'; - // if video is the only mediaType on Bid Request => add 0.18 - if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; - return bidCpm / 0.75; - }, - } - }; + describe('inverse adjustment', () => { + beforeEach(() => { + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + }); - _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { + let functionUsed; + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Rubicon Adjustment'; + bidCpm *= 0.5; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Rubicon Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Appnexus Adjustment'; + bidCpm *= 0.75; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Appnexus Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.75; + }, + } + }; - _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; - // start with banner as only mediaType - bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; - let appnexusBid = { - ...bidRequest, - bidder: 'appnexus', - }; + // should be same as the adjusted calculated inverses above test (banner) + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); - // should be same as the adjusted calculated inverses above test (banner) - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 2.0 - }); + // should use rubicon inverse + expect(functionUsed).to.equal('Rubicon Inverse'); - // should use rubicon inverse - expect(functionUsed).to.equal('Rubicon Inverse'); + // appnexus just using banner should be same + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 + }); - // appnexus just using banner should be same - expect(appnexusBid.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.3334 - }); + expect(functionUsed).to.equal('Appnexus Inverse'); - expect(functionUsed).to.equal('Appnexus Inverse'); + // now since asking for 'video' only mediaType inverse function should include the .18 + bidRequest.mediaTypes = { video: { context: 'instream' } }; + expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 2.36 + }); - // now since asking for 'video' only mediaType inverse function should include the .18 - bidRequest.mediaTypes = { video: { context: 'instream' } }; - expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ - currency: 'USD', - floor: 2.36 - }); + expect(functionUsed).to.equal('Rubicon Inverse'); - expect(functionUsed).to.equal('Rubicon Inverse'); + // now since asking for 'video' inverse function should include the .18 + appnexusBid.mediaTypes = { video: { context: 'instream' } }; + expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 1.5734 + }); - // now since asking for 'video' inverse function should include the .18 - appnexusBid.mediaTypes = { video: { context: 'instream' } }; - expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ - currency: 'USD', - floor: 1.5734 + expect(functionUsed).to.equal('Appnexus Inverse'); }); - expect(functionUsed).to.equal('Appnexus Inverse'); - }); - - it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { - // Adjustment factors based on Bid Media Type - const mediaTypeFactors = { - banner: 0.5, - native: 0.7, - video: 0.9 - } - getGlobal().bidderSettings = { - rubicon: { - bidCpmAdjustment: function (bidCpm, bidResponse) { - return bidCpm * mediaTypeFactors[bidResponse.mediaType]; - }, - inverseBidAdjustment: function (bidCpm, bidRequest) { - // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number - let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); - factor = factor / Object.keys(bidRequest.mediaTypes).length; - return bidCpm / factor; - }, + it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { + // Adjustment factors based on Bid Media Type + const mediaTypeFactors = { + banner: 0.5, + native: 0.7, + video: 0.9 } - }; - - _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidCpm * mediaTypeFactors[bidResponse.mediaType]; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number + let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); + factor = factor / Object.keys(bidRequest.mediaTypes).length; + return bidCpm / factor; + }, + } + }; - _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); - // banner only should be 2 - bidRequest.mediaTypes = { banner: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 2.0 - }); + // native only should be 1.4286 + bidRequest.mediaTypes = { native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); - // native only should be 1.4286 - bidRequest.mediaTypes = { native: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.4286 - }); + // video only should be 1.1112 + bidRequest.mediaTypes = { video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); - // video only should be 1.1112 - bidRequest.mediaTypes = { video: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.1112 - }); + // video and banner should even out to 0.7 factor so 1.4286 + bidRequest.mediaTypes = { video: {}, banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); - // video and banner should even out to 0.7 factor so 1.4286 - bidRequest.mediaTypes = { video: {}, banner: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.4286 - }); + // video and native should even out to 0.8 factor so -- 1.25 + bidRequest.mediaTypes = { video: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.25 + }); - // video and native should even out to 0.8 factor so -- 1.25 - bidRequest.mediaTypes = { video: {}, native: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.25 - }); + // banner and native should even out to 0.6 factor so -- 1.6667 + bidRequest.mediaTypes = { banner: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.6667 + }); - // banner and native should even out to 0.6 factor so -- 1.6667 - bidRequest.mediaTypes = { banner: {}, native: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.6667 + // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 + bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); }); - // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 - bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; - expect(bidRequest.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.4286 - }); + Object.entries({ + 'both unspecified': { + getFloorParams: undefined, + inverseParams: {} + }, + 'only mediaType': { + getFloorParams: {mediaType: 'video'}, + inverseParams: {mediaType: 'video'} + }, + 'only size': { + getFloorParams: {mediaType: '*', size: [1, 2]}, + inverseParams: {size: [1, 2]} + }, + 'both': { + getFloorParams: {mediaType: 'banner', size: [1, 2]}, + inverseParams: {mediaType: 'banner', size: [1, 2]} + } + }).forEach(([t, {getFloorParams, inverseParams}]) => { + it(`should pass inverseFloorAdjustment mediatype and size (${t})`, () => { + getGlobal().bidderSettings = { + standard: { + inverseBidAdjustment: sinon.stub() + } + } + bidRequest.mediaTypes = { + video: {}, + native: {}, + banner: { + sizes: [[100, 200], [200, 300]] + } + } + bidRequest.getFloor(getFloorParams); + sinon.assert.calledWith(getGlobal().bidderSettings.standard.inverseBidAdjustment, 1, bidRequest, inverseParams); + }); + }) }); it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { diff --git a/test/spec/ortbConverter/priceFloors_spec.js b/test/spec/ortbConverter/priceFloors_spec.js index f6d37992711..73e94b6eb65 100644 --- a/test/spec/ortbConverter/priceFloors_spec.js +++ b/test/spec/ortbConverter/priceFloors_spec.js @@ -1,6 +1,7 @@ import {config} from 'src/config.js'; -import {setOrtbExtPrebidFloors, setOrtbImpBidFloor} from '../../../modules/priceFloors.js'; +import {setOrtbExtPrebidFloors, setOrtbImpBidFloor, setGranularBidfloors} from '../../../modules/priceFloors.js'; import 'src/prebid.js'; +import {ORTB_MTYPES} from '../../../libraries/ortbConverter/processors/mediaType.js'; describe('pbjs - ortb imp floor params', () => { before(() => { @@ -113,6 +114,87 @@ describe('pbjs - ortb imp floor params', () => { }) }); +describe('setOrtbImpExtBidFloor', () => { + let bidRequest, floor, currency, imp; + beforeEach(() => { + bidRequest = { + getFloor: sinon.stub().callsFake(() => ({floor, currency})) + } + imp = { + bidfloor: 1.23, + bidfloorcur: 'EUR' + }; + }); + + Object.values(ORTB_MTYPES).forEach(mediaType => { + describe(`${mediaType}.ext.bidfloor`, () => { + it(`should NOT be set if imp has no ${mediaType}`, () => { + setGranularBidfloors(imp, bidRequest); + expect(imp[mediaType]).to.not.exist; + }); + it('should NOT be set if floor is same as imp.bidfloor', () => { + floor = 1.23 + currency = 'EUR' + imp[mediaType] = {}; + setGranularBidfloors(imp, bidRequest); + expect(imp[mediaType].ext).to.not.exist; + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({mediaType})) + }); + it('should be set otherwise', () => { + floor = 3.21; + currency = 'JPY'; + imp[mediaType] = {}; + setGranularBidfloors(imp, bidRequest); + expect(imp[mediaType].ext).to.eql({ + bidfloor: 3.21, + bidfloorcur: 'JPY' + }); + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({mediaType})) + }); + }); + }); + describe('per-format floors', () => { + beforeEach(() => { + imp = { + banner: { + format: [ + {w: 1, h: 2}, + {w: 3, h: 4} + ] + } + } + }); + + it('should NOT be set if same as imp.bidfloor', () => { + floor = 1.23 + currency = 'EUR'; + setGranularBidfloors(imp, bidRequest); + expect(imp.banner.format.filter(fmt => fmt.bidfloor)).to.eql([]); + [[1, 2], [3, 4]].forEach(size => { + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({size})); + }); + }); + + it('should be set otherwise', () => { + floor = 3.21; + currency = 'JPY'; + setGranularBidfloors(imp, bidRequest); + imp.banner.format.forEach((fmt) => { + expect(fmt.ext).to.eql({bidfloor: 3.21, bidfloorcur: 'JPY'}); + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({mediaType: 'banner', size: [fmt.w, fmt.h]})); + }) + }); + + it('should not be set if format is missing w/h', () => { + floor = 3.21; + currency = 'JPY'; + delete imp.banner.format[0].w; + setGranularBidfloors(imp, bidRequest); + expect(imp.banner.format[0].ext).to.not.exist; + }) + }) +}) + describe('setOrtbExtPrebidFloors', () => { before(() => { config.setConfig({floors: {}}); From 0c60b11c0fafd35bf0a3724cfc0e5306205de3cd Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 27 Jan 2025 13:21:09 -0800 Subject: [PATCH 0867/1097] Update ortbConverter README with mention of ext.bidfloor (#12693) --- libraries/ortbConverter/README.md | 112 +++++++++++++++++------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/libraries/ortbConverter/README.md b/libraries/ortbConverter/README.md index 751971eebdc..92843c0241e 100644 --- a/libraries/ortbConverter/README.md +++ b/libraries/ortbConverter/README.md @@ -1,7 +1,7 @@ # Prebid.js - ORTB conversion library -This library provides methods to convert Prebid.js bid request objects to ORTB requests, -and ORTB responses to Prebid.js bid response objects. +This library provides methods to convert Prebid.js bid request objects to ORTB requests, +and ORTB responses to Prebid.js bid response objects. ## Usage @@ -37,13 +37,25 @@ registerBidder({ }) ``` -Without any customization, the library will generate complete ORTB requests, but ignores your [bid params](#params). +Without any customization, the library will generate complete ORTB requests, but ignores your [bid params](#params). If your endpoint sets `response.seatbid[].bid[].mtype` (part of the ORTB 2.6 spec), it will also parse the response into complete bidResponse objects. See [setting response mediaTypes](#response-mediaTypes) if that is not the case. ### Module-specific conversions Prebid.js features that require a module also require it for their corresponding ORTB conversion logic. For example, `imp.bidfloor` is only populated if the `priceFloors` module is active; `request.cur` needs the `currency` module, and so on. Notably, this means that to get those fields populated from your unit tests, you must import those modules first; see [this suite](https://github.com/prebid/Prebid.js/blob/master/test/spec/modules/openxOrtbBidAdapter_spec.js) for an example. +#### priceFloors extensions + +In addition to `imp.bidfloor` and `imp.bidfloorcur`, the `priceFloors` module also populates media type and `format` objects, if their floors differ: + +| Path | `getFloor` invocation | +|-----------------------------------------------------|----------------------------------------------------------------| +| `imp.bidfloor` & `.bidfloorcur` | `.getFloor()` | +| `imp.banner.ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'banner', size: '*'})` | +| `imp.banner.format[].ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'banner', size: [format.w, format.h]})` | +| `imp.native.ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'native', size: '*'})` | +| `imp.video.ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'video', size: '*'})` | + ## Customization ### Modifying return values directly @@ -57,35 +69,35 @@ deepSetValue(data.imp[0], 'ext.myCustomParam', bidRequests[0].params.myCustomPar However, there are two restrictions (to avoid them, use the [other customization options](#fine-customization)): - - you may not change the `imp[].id` returned by `toORTB`; they ared used internally to match responses to their requests. - ```javascript - const data = converter.toORTB({bidRequests, bidderRequest}); - data.imp[0].id = 'custom-imp-id' // do not do this - it will cause an error later in `fromORTB` - ``` - See also [overriding `imp.id`](#imp-id). - - the `request` argument passed to `fromORTB` must be the same object returned by `toORTB`. +- you may not change the `imp[].id` returned by `toORTB`; they ared used internally to match responses to their requests. ```javascript - let data = converter.toORTB({bidRequests, bidderRequest}); - - data = mergeDeep( // the original object is lost - {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, // `fromORTB` will later throw an error - data - ); - - // do this instead: - mergeDeep( - data, - {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, - data - ) + const data = converter.toORTB({bidRequests, bidderRequest}); + data.imp[0].id = 'custom-imp-id' // do not do this - it will cause an error later in `fromORTB` ``` + See also [overriding `imp.id`](#imp-id). +- the `request` argument passed to `fromORTB` must be the same object returned by `toORTB`. + ```javascript + let data = converter.toORTB({bidRequests, bidderRequest}); + + data = mergeDeep( // the original object is lost + {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, // `fromORTB` will later throw an error + data + ); + + // do this instead: + mergeDeep( + data, + {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, + data + ) + ``` ### Fine grained customization - imp, request, bidResponse, response When invoked, `toORTB({bidRequests, bidderRequest})` first loops through each request in `bidRequests`, converting them into ORTB `imp` objects. It then packages them into a single ORTB request, adding other parameters that are not imp-specific (such as for example `request.tmax`). -Likewise, `fromORTB({request, response})` first loops through each `response.seatbid[].bid[]`, converting them into Prebid bidResponses; it then packages them into +Likewise, `fromORTB({request, response})` first loops through each `response.seatbid[].bid[]`, converting them into Prebid bidResponses; it then packages them into a single return value. You can customize each of these steps using the `ortbConverter` arguments `imp`, `request`, `bidResponse` and `response`: @@ -98,8 +110,8 @@ The arguments are: - `buildImp`: a function taking `(bidRequest, context)` and returning an ORTB `imp` object; - `bidRequest`: the bid request object to convert; - `context`: a [context object](#context) that contains at least: - - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`. - + - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`. + #### Example: attaching custom bid params ```javascript @@ -194,7 +206,7 @@ const converter = ortbConverter({ }) ``` -If you know that a particular ORTB request/response pair deals with exclusively one mediaType, you may also pass it directly in the [context parameter](#context). +If you know that a particular ORTB request/response pair deals with exclusively one mediaType, you may also pass it directly in the [context parameter](#context). Note that - compared to the above - this has additional effects, because `context.mediaType` is also considered during `imp` generation - see [special context properties](#special-context). ```javascript @@ -223,7 +235,7 @@ const converter = ortbConverter({ ### Customizing the response: `response(buildResponse, bidResponses, ortbResponse, context)` -Invoked once, after all `seatbid[].bid[]` objects have been converted to corresponding bid responses. The value returned +Invoked once, after all `seatbid[].bid[]` objects have been converted to corresponding bid responses. The value returned by this function is also the value returned by `fromORTB`. The arguments are: @@ -249,7 +261,7 @@ const converter = ortbConverter({ ### Even finer grained customization - processor overrides Each of the four conversion steps described above - imp, request, bidResponse and response - is further broken down into -smaller units of work (called _processors_). For example, when the currency module is included, it adds a _request processor_ +smaller units of work (called _processors_). For example, when the currency module is included, it adds a _request processor_ that sets `request.cur`; the priceFloors module adds an _imp processor_ that sets `imp.bidfloor` and `imp.bidfloorcur`, and so on. Each processor can be overridden or disabled through the `overrides` argument: @@ -310,21 +322,21 @@ const converter = ortbConverter({ Processor overrides are similar to the override options described above, except that they take the object to process as argument: - `imp` processor overrides take `(orig, imp, bidRequest, context)`, where: - - `orig` is the processor function being overridden, which itself takes `(imp, bidRequest, context)`; - - `imp` is the (partial) imp object to modify; - - `bidRequest` and `context` are the same arguments passed to [imp](#imp). + - `orig` is the processor function being overridden, which itself takes `(imp, bidRequest, context)`; + - `imp` is the (partial) imp object to modify; + - `bidRequest` and `context` are the same arguments passed to [imp](#imp). - `request` processor overrides take `(orig, ortbRequest, bidderRequest, context)`, where: - - `orig` is the processor function being overridden, and takes `(ortbRequest, bidderRequest, context)`; - - `ortbRequest` is the partial request to modify; - - `bidderRequest` and `context` are the same arguments passed to [request](#reuqest). -- `bidResponse` processor overrides take `(orig, bidResponse, bid, context)`, where: - - `orig` is the processor function being overridden, and takes `(bidResponse, bid, context)`; - - `bidResponse` is the partial bid response to modify; - - `bid` and `context` are the same arguments passed to [bidResponse](#bidResponse) + - `orig` is the processor function being overridden, and takes `(ortbRequest, bidderRequest, context)`; + - `ortbRequest` is the partial request to modify; + - `bidderRequest` and `context` are the same arguments passed to [request](#reuqest). +- `bidResponse` processor overrides take `(orig, bidResponse, bid, context)`, where: + - `orig` is the processor function being overridden, and takes `(bidResponse, bid, context)`; + - `bidResponse` is the partial bid response to modify; + - `bid` and `context` are the same arguments passed to [bidResponse](#bidResponse) - `response` processor overrides take `(orig, response, ortbResponse, context)`, where: - - `orig` is the processor function being overriden, and takes `(response, ortbResponse, context)`; - - `response` is the partial response to modify; - - `ortbRespones` and `context` are the same arguments passed to [response](#response). + - `orig` is the processor function being overriden, and takes `(response, ortbResponse, context)`; + - `response` is the partial response to modify; + - `ortbRespones` and `context` are the same arguments passed to [response](#response). ### The `context` argument @@ -354,19 +366,19 @@ const converter = ortbConverter({ For ease of use, the conversion logic gives special meaning to some context properties: - - `currency`: a currency string (e.g. `'EUR'`). If specified, overrides the currency to use for computing price floors and `request.cur`. If omitted, both default to `getConfig('currency.adServerCurrency')`. - - `mediaType`: a bid mediaType (`'banner'`, `'video'`, or `'native'`). If specified: +- `currency`: a currency string (e.g. `'EUR'`). If specified, overrides the currency to use for computing price floors and `request.cur`. If omitted, both default to `getConfig('currency.adServerCurrency')`. +- `mediaType`: a bid mediaType (`'banner'`, `'video'`, or `'native'`). If specified: - disables `imp` generation for other media types (i.e., if `context.mediaType === 'banner'`, only `imp.banner` will be populated; `imp.video` and `imp.native` will not, even if the bid request specifies them); - is passed as the `mediaType` option to `bidRequest.getFloor` when computing price floors; - sets `bidResponse.mediaType`. - - `nativeRequest`: a plain object that serves as the base value for `imp.native.request` (and is relevant only for native bid requests). - If not specified, the only property that is guaranteed to be populated is `assets`, since Prebid does not require anything else to define a native adUnit. You can use `context.nativeRequest` to provide other properties; for example, you may want to signal support for native impression trackers by setting it to `{eventtrackers: [{event: 1, methods: [1, 2]}]}` (see also the [ORTB Native spec](https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf)). - - `netRevenue`: the value to set as `bidResponse.netRevenue`. This is a required property of bid responses that does not have a clear ORTB counterpart. - - `ttl`: the default value to use for `bidResponse.ttl` (if the ORTB response does not provide one in `seatbid[].bid[].exp`). - +- `nativeRequest`: a plain object that serves as the base value for `imp.native.request` (and is relevant only for native bid requests). + If not specified, the only property that is guaranteed to be populated is `assets`, since Prebid does not require anything else to define a native adUnit. You can use `context.nativeRequest` to provide other properties; for example, you may want to signal support for native impression trackers by setting it to `{eventtrackers: [{event: 1, methods: [1, 2]}]}` (see also the [ORTB Native spec](https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf)). +- `netRevenue`: the value to set as `bidResponse.netRevenue`. This is a required property of bid responses that does not have a clear ORTB counterpart. +- `ttl`: the default value to use for `bidResponse.ttl` (if the ORTB response does not provide one in `seatbid[].bid[].exp`). + ## Prebid Server extensions -If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. +If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. ```javascript import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js' From 698ede8a1cca503d978095439d77df78931e24e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriela=20Mi=C4=99dlar?= <155444733+gmiedlar-ox@users.noreply.github.com> Date: Tue, 28 Jan 2025 23:52:56 +0100 Subject: [PATCH 0868/1097] OpenX Bid Adapter : fix to determine bid mediaType based on ad markup (#12695) * hotfix: Determine bid mtype based on ad markup * hotfix: Determine mediaType only when no context.mediaType * hotfix: Determine mediaType only when no context.mediaType and no bid.mtype * hotfix: Fix unit test for mediaType * hotfix: Check if adm contains any of vast keywords * hotfix: Add unit test * hotfix: Add unit test * hotfix: Remove dur from vast keywords --- modules/openxBidAdapter.js | 13 ++ test/spec/modules/openxBidAdapter_spec.js | 232 +++++++++++++++++++++- 2 files changed, 235 insertions(+), 10 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index cf8a54242de..292df1d4152 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -4,6 +4,7 @@ import * as utils from '../src/utils.js'; import {mergeDeep} from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '2.0'; @@ -79,6 +80,18 @@ const converter = ortbConverter({ return req; }, bidResponse(buildBidResponse, bid, context) { + if (!context.mediaType && !bid.mtype) { + let mediaType = BANNER; // default media type + const vastKeywords = ['VAST ', 'vast ', 'videoad', 'VAST_VERSION', 'dc_vast', 'video ']; + if (bid.adm && bid.adm.startsWith('{') && bid.adm.includes('"assets"')) { + mediaType = NATIVE; + } else if (bid.vastXml || bid.vastUrl || (bid.adm && vastKeywords.some(v => bid.adm.includes(v)))) { + mediaType = VIDEO; + } + bid.mediaType = mediaType; + bid.mtype = Object.keys(ORTB_MTYPES).find(key => ORTB_MTYPES[key] === mediaType); + } + const bidResponse = buildBidResponse(bid, context); if (bid.ext) { bidResponse.meta.networkId = bid.ext.dsp_id; diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index dde9e2bd7c8..4f172fb1195 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; // load modules that register ORTB processors @@ -1381,7 +1381,6 @@ describe('OpenxRtbAdapter', function () { crid: 'test-creative-id', dealid: 'test-deal-id', adm: 'test-ad-markup', - mtype: 1, adomain: ['brand.com'], ext: { dsp_id: '123', @@ -1456,7 +1455,7 @@ describe('OpenxRtbAdapter', function () { }); }); - context('when the response is a banner', function() { + context('when banner request and the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1496,14 +1495,12 @@ describe('OpenxRtbAdapter', function () { }); it('should return the proper mediaType', function () { - it('should return a creativeId', function () { - expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); - }); + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); }); if (FEATURES.VIDEO) { - context('when the response is a video', function() { + context('when video request and the response is a video', function() { beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1533,7 +1530,7 @@ describe('OpenxRtbAdapter', function () { h: 480, crid: 'test-creative-id', dealid: 'test-deal-id', - adm: 'test-ad-markup', + adm: '', }] }], cur: 'AUS' @@ -1553,10 +1550,107 @@ describe('OpenxRtbAdapter', function () { expect(bid.vastUrl).to.equal(winUrl); }); }); + + context('when multi-format request (banner + video) and the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + size: [[300, 600]] + }, + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 5, + adm: '', + }] + }], + cur: 'USD' + }; + }); + + it('should return video mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(VIDEO); + }); + }); + + context('when multiple bid requests (banner + video) and the response is a banner', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bidder-request-id-1', + auctionId: 'test-auction-id-1' + }, + { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bidder-request-id-2', + auctionId: 'test-auction-id-2' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id-2', + price: 2, + adm: '', + }] + }], + cur: 'USD' + }; + }); + + it('should return banner mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(BANNER); + }); + }); } if (FEATURES.NATIVE) { - context('when the response is a native', function() { + context('when native request and the response is a native', function() { beforeEach(function () { const nativeOrtbRequest = { ver: '1.2', @@ -1593,7 +1687,6 @@ describe('OpenxRtbAdapter', function () { bid: [{ impid: 'test-bid-id', price: 2, - mtype: 4, adm: '{"ver": "1.2", "assets": [{"id": 1, "required": 1,"title": {"text": "OpenX (Title)"}}], "link": {"url": "https://www.openx.com/"}, "eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', }] }], @@ -1625,6 +1718,125 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + context('when multi-format request (banner + native) and the response is a banner', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + adm: '', + }] + }], + cur: 'AUS' + }; + }); + + it('should return banner mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(BANNER); + }); + }); + + context('when multiple bid requests (banner + native) and the response is a native', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bidder-request-id-1', + auctionId: 'test-auction-id-1' + }, + { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bidder-request-id-2', + auctionId: 'test-auction-id-2' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id-1', + price: 2, + adm: '{"ver": "1.2", "assets": [{"id": 1, "required": 1,"title": {"text": "OpenX (Title)"}}], "link": {"url": "https://www.openx.com/"}, "eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + }] + }], + cur: 'USD' + }; + }); + + it('should return native mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(NATIVE); + }); + }); } context('when the response contains FLEDGE interest groups config', function() { From 6d9e06836e2ac6d13144286b3c7dd0971c5a740d Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Thu, 30 Jan 2025 13:33:06 +0100 Subject: [PATCH 0869/1097] RichAudience Bid Adapter : change user eids (#12703) * Richaudience Bid Adapter: test/spec change in user eids * Richaudience Bid Adapter: change in user eids --- modules/richaudienceBidAdapter.js | 28 +- .../modules/richaudienceBidAdapter_spec.js | 370 +++++++----------- 2 files changed, 154 insertions(+), 244 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 135d2e94b65..5156fb052ef 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, isStr, triggerPixel} from '../src/utils.js'; +import {deepAccess, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -48,7 +48,7 @@ export const spec = { numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.ortb2Imp?.ext?.tid, timeout: bidderRequest.timeout || 600, - user: raiSetEids(bid), + eids: deepAccess(bid, 'userIdAsEids') ? bid.userIdAsEids : [], demand: raiGetDemandType(bid), videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), @@ -266,30 +266,6 @@ function raiGetVideoInfo(bid) { return videoData; } -function raiSetEids(bid) { - let eids = []; - - if (bid && bid.userId) { - raiSetUserId(bid, eids, 'id5-sync.com', deepAccess(bid, `userId.id5id.uid`)); - raiSetUserId(bid, eids, 'pubcommon', deepAccess(bid, `userId.pubcid`)); - raiSetUserId(bid, eids, 'criteo.com', deepAccess(bid, `userId.criteoId`)); - raiSetUserId(bid, eids, 'liveramp.com', deepAccess(bid, `userId.idl_env`)); - raiSetUserId(bid, eids, 'liveintent.com', deepAccess(bid, `userId.lipb.lipbid`)); - raiSetUserId(bid, eids, 'adserver.org', deepAccess(bid, `userId.tdid`)); - } - - return eids; -} - -function raiSetUserId(bid, eids, source, value) { - if (isStr(value)) { - eids.push({ - userId: value, - source: source - }); - } -} - function renderer(bid) { bid.renderer.push(() => { renderAd(bid) diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 26d226b65f3..e4fd11d8604 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -246,6 +246,66 @@ describe('Richaudience adapter tests', function () { transactionId: '29df2112-348b-4961-8863-1b33684d95e6' }]; + var BID_PARAMS_EIDS = [{ + 'bidder': 'richaudience', + 'params': { + 'pid': 'IHOhChZNuI', + 'supplyType': 'site' + }, + 'userIdAsEids': [], + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + }] + + var id5 = { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'id5-string-cookie', + 'atype': 1, + 'ext': { + 'linkType': 2, + 'pba': 'id5-pba', + 'abTestingControlGroup': false + } + } + ] + } + + var first_id = { + 'source': 'first-id.fr', + 'uids': [ + { + 'id': 'value read from cookie or local storage', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + } + + var three_party_provided = { + 'source': '3rdpartyprovided.com', + 'uids': [ + { + 'id': 'value read from cookie or local storage', + 'atype': 3, + 'ext': { + 'stype': 'dmp' + } + } + ] + } + var BID_RESPONSE = { body: { cpm: 1.50, @@ -289,7 +349,7 @@ describe('Richaudience adapter tests', function () { } } - it('Referer undefined', function() { + it('Referer undefined', function () { config.setConfig({ 'currency': {'adServerCurrency': 'USD'} }) @@ -306,7 +366,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('referer').and.to.equal(null); }) - it('Verify build request to prebid 3.0 display test', function() { + it('Verify build request to prebid 3.0 display test', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -348,7 +408,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) - it('Verify build request to prebid video inestream', function() { + it('Verify build request to prebid video inestream', function () { const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -367,7 +427,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); }) - it('Verify build request to prebid video outstream', function() { + it('Verify build request to prebid video outstream', function () { const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -467,8 +527,8 @@ describe('Richaudience adapter tests', function () { pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this }, storage: { - type: 'html5', // "html5" is the required storage type - name: 'id5id', // "id5id" is the required storage name + type: 'html5', // 'html5' is the required storage type + name: 'id5id', // 'id5id' is the required storage name expires: 90, // storage lasts for 90 days refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh } @@ -476,213 +536,55 @@ describe('Richaudience adapter tests', function () { auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules } }); - it('Verify build id5', function () { - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build pubCommonId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 'pub_common_user_id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return empty users', function () { + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user).to.deep.equal([{ - 'userId': 'pub_common_user_id', - 'source': 'pubcommon' - }]); + expect(requestContent.eids).to.deep.equal([]); + }) - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return all users', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [id5, three_party_provided, first_id] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build criteoId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 'criteo-user-id'; + expect(requestContent.eids).to.deep.equal([id5, three_party_provided, first_id]); + }) - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return first-id.fr users', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [first_id] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); + expect(requestContent.eids).to.deep.equal([first_id]); + }) - expect(requestContent.user).to.deep.equal([{ - 'userId': 'criteo-user-id', - 'source': 'criteo.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return first-id.fr users', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [first_id] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build identityLink', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + expect(requestContent.eids).to.deep.equal([first_id]); + }) - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return users []', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); + expect(requestContent.eids).to.deep.equal([]); + }) - expect(requestContent.user).to.deep.equal([{ - 'userId': 'identity-link-user-id', - 'source': 'liveramp.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return users null', function () { + BID_PARAMS_EIDS[0].userIdAsEids = null + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - it('Verify build liveIntentId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data) - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'identity-link-user-id', - 'source': 'liveramp.com' - }]); + expect(requestContent.eids).to.deep.equal([]); + }) - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return users {}', function () { + BID_PARAMS_EIDS[0].userIdAsEids = null + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - it('Verify build TradeDesk', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.tdid = 'tdid-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data) - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'tdid-user-id', - 'source': 'adserver.org' - }]); - - request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); + expect(requestContent.eids).to.deep.equal([]); + }) }); it('Verify interprete response', function () { @@ -900,7 +802,7 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); - it('should pass schain', function() { + it('should pass schain', function () { let schain = { 'ver': '1.0', 'complete': 1, @@ -940,7 +842,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('schain').to.deep.equal(schain); }) - it('should pass DSA', function() { + it('should pass DSA', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_DSA, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -955,7 +857,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent.dsa.transparency[0]).to.have.property('domain').and.to.equal('richaudience.com'); }) - it('should pass gpid', function() { + it('should pass gpid', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -968,11 +870,11 @@ describe('Richaudience adapter tests', function () { }) describe('onTimeout', function () { - beforeEach(function() { + beforeEach(function () { sinon.stub(utils, 'triggerPixel'); }); - afterEach(function() { + afterEach(function () { utils.triggerPixel.restore(); }); it('onTimeout exist as a function', () => { @@ -990,7 +892,7 @@ describe('Richaudience adapter tests', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); it('Verifies user syncs iframe include', function () { @@ -1002,7 +904,8 @@ describe('Richaudience adapter tests', function () { iframeEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1042,7 +945,8 @@ describe('Richaudience adapter tests', function () { iframeEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(0); @@ -1150,7 +1054,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe/image include', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'include'}, + image: {bidders: '*', filter: 'include'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1158,7 +1067,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1196,7 +1106,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe/image exclude', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'exclude'}, + image: {bidders: '*', filter: 'exclude'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1204,7 +1119,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(0); @@ -1241,7 +1157,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe exclude / image include', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'exclude'}, + image: {bidders: '*', filter: 'include'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1249,7 +1170,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); @@ -1287,7 +1209,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe include / image exclude', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'include'}, + image: {bidders: '*', filter: 'exclude'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1295,7 +1222,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1338,7 +1266,8 @@ describe('Richaudience adapter tests', function () { var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE], { gppString: 'DBABL~BVVqAAEABgA.QA', - applicableSections: [7]}, + applicableSections: [7] + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1349,17 +1278,22 @@ describe('Richaudience adapter tests', function () { var syncs = spec.getUserSyncs({pixelEnabled: true}, [BID_RESPONSE], { gppString: 'DBABL~BVVqAAEABgA.QA', - applicableSections: [7, 5]}, + applicableSections: [7, 5] + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); }); it('Verifies user syncs URL image include with GPP', function () { - const gppConsent = { gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', applicableSections: [0] }; + const gppConsent = { + gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', + applicableSections: [0] + }; const result = spec.getUserSyncs({pixelEnabled: true}, undefined, undefined, undefined, gppConsent); expect(result).to.deep.equal([{ - type: 'image', url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` + type: 'image', + url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` }]); }); }) From d7e4b7f2ada0e7f3d7f23bcf0cb3420586f1de3e Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 30 Jan 2025 04:34:18 -0800 Subject: [PATCH 0870/1097] take meta mediaType into account (#12700) --- modules/magniteAnalyticsAdapter.js | 3 +- .../modules/magniteAnalyticsAdapter_spec.js | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 7eb20439f5f..25adf2b8a3c 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -264,7 +264,8 @@ export const parseBidResponse = (bid, previousBidResponse) => { return pick(bid, [ 'bidPriceUSD', () => responsePrice, 'dealId', dealId => dealId || undefined, - 'mediaType', + 'mediaType', () => bid?.meta?.mediaType ?? bid.mediaType, + 'ogMediaType', () => bid?.meta?.mediaType && bid.mediaType !== bid?.meta?.mediaType ? bid.mediaType : undefined, 'dimensions', () => { const width = bid.width || bid.playerWidth; const height = bid.height || bid.playerHeight; diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index a5644ffc6eb..26a8f2ed313 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -759,6 +759,34 @@ describe('magnite analytics adapter', function () { expect(message.auctions[0].adUnits[0].bids[0].bidResponse.networkId).to.equal(test.expected); }); }); + + // meta mediatype handler things + [ + { input: undefined, expected: 'banner', hasOg: false }, + { input: 'banner', expected: 'banner', hasOg: false }, + { input: 'video', expected: 'video', hasOg: true } + ].forEach((test, index) => { + it(`should handle meta mediaType stuff correctly - #${index + 1}`, function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.meta = { + mediaType: test.input + }; + + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(BID_WON, MOCK.BID_WON); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.mediaType).to.equal(test.expected); + if (test.hasOg) expect(message.auctions[0].adUnits[0].bids[0].bidResponse.ogMediaType).to.equal('banner'); + else expect(message.auctions[0].adUnits[0].bids[0].bidResponse).to.not.haveOwnProperty('ogMediaType'); + }); + }); }); describe('with session handling', function () { From 17bb431591be899214c89c7cd58b307c097fdf9f Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:52:00 +0200 Subject: [PATCH 0871/1097] Attekmi: add Addigi alias (#12697) * Attekmi: add Addigi alias * linter error fix --------- Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index e5aa50bcad6..ddf76d64903 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -17,6 +17,7 @@ const ALIASES = [ {code: 'vimayx'}, {code: 'artechnology'}, {code: 'adinify'}, + {code: 'addigi'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', @@ -28,6 +29,7 @@ const BASE_URLS = { vimayx: 'https://vimayx-prebid.attekmi.com/pbjs', artechnology: 'https://artechnology-prebid.attekmi.com/pbjs', adinify: 'https://adinify-prebid.attekmi.com/pbjs', + addigi: 'https://addigi-prebid.attekmi.com/pbjs', }; const _getUrl = (partnerName) => { From 499933951b0b0cba5a1472c9e94ebbaee2726d09 Mon Sep 17 00:00:00 2001 From: ollyburns <75783836+ollyburns@users.noreply.github.com> Date: Thu, 30 Jan 2025 12:53:19 +0000 Subject: [PATCH 0872/1097] Teal Bid Adapter: initial release (#12709) * teal adapter initial commit * testMode update alignment of stored-imp and stored-response * add tests * enhanced error handling * add subAccount param * coop_sync -> false * Update tealBidAdapter.md --- modules/tealBidAdapter.js | 145 ++++++++++++ modules/tealBidAdapter.md | 46 ++++ test/spec/modules/tealBidAdapter_spec.js | 268 +++++++++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 modules/tealBidAdapter.js create mode 100644 modules/tealBidAdapter.md create mode 100644 test/spec/modules/tealBidAdapter_spec.js diff --git a/modules/tealBidAdapter.js b/modules/tealBidAdapter.js new file mode 100644 index 00000000000..03b983d25d9 --- /dev/null +++ b/modules/tealBidAdapter.js @@ -0,0 +1,145 @@ +import {deepSetValue, deepAccess, triggerPixel, deepClone, isEmpty, logError, shuffle} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import {BANNER} from '../src/mediaTypes.js'; +import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js' +const BIDDER_CODE = 'teal'; +const GVLID = 1378; +const DEFAULT_ENDPOINT = 'https://a.bids.ws/openrtb2/auction'; +const COOKIE_SYNC_ENDPOINT = 'https://a.bids.ws/cookie_sync'; +const COOKIE_SYNC_IFRAME = 'https://bids.ws/load-cookie.html'; +const MAX_SYNC_COUNT = 10; + +const converter = ortbConverter({ + processors: pbsExtensions, + context: { + netRevenue: true, + ttl: 30 + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const { placement, testMode } = bidRequest.params; + if (placement) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', placement); + } + if (testMode) { + deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', placement); + } + delete imp.ext.prebid.bidder; + return imp; + }, + overrides: { + bidResponse: { + bidderCode(orig, bidResponse, bid, { bidRequest }) { + let useSourceBidderCode = deepAccess(bidRequest, 'params.useSourceBidderCode', false); + if (useSourceBidderCode) { + orig.apply(this, [...arguments].slice(1)); + } + }, + }, + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + aliases: [], + + isBidRequestValid: function(bid) { + return Boolean(bid.params?.account); + }, + + buildRequests: function(bidRequests, bidderRequest) { + const { bidder } = bidRequests[0]; + const data = converter.toORTB({bidRequests, bidderRequest}); + const account = deepAccess(bidRequests[0], 'params.account', null); + const subAccount = deepAccess(bidRequests[0], 'params.subAccount', null); + deepSetValue(data, 'site.publisher.id', account); + deepSetValue(data, 'ext.prebid.storedrequest.id', subAccount || account); + data.ext.prebid.passthrough = { + ...data.ext.prebid.passthrough, + teal: { bidder }, + }; + data.tmax = (bidderRequest.timeout || 1500) - 100; + return { + method: 'POST', + url: deepAccess(bidRequests[0], 'params.endpoint', DEFAULT_ENDPOINT), + data + }; + }, + + interpretResponse: function(response, request) { + const resp = deepClone(response.body); + const { bidder } = request.data.ext.prebid.passthrough.teal; + const modifiers = { + responsetimemillis: (values) => Math.max(...values), + errors: (values) => [].concat(...values), + }; + Object.entries(modifiers).forEach(([field, combineFn]) => { + const obj = resp.ext?.[field]; + if (!isEmpty(obj)) { + resp.ext[field] = {[bidder]: combineFn(Object.values(obj))}; + } + }); + const bids = converter.fromORTB({response: resp, request: request.data}).bids; + return bids; + }, + + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled) { + return []; + } + const syncs = []; + const { gdprApplies, consentString } = gdprConsent || {}; + let bidders = []; + serverResponses.forEach(({ body }) => { + const newBidders = Object.keys(body.ext?.responsetimemillis || {}); + newBidders.forEach(s => { + if (bidders.indexOf(s) === -1) { + bidders.push(s); + } + }); + }); + bidders = shuffle(bidders).slice(0, MAX_SYNC_COUNT); + if (!bidders.length) { + return; + } + const params = { + endpoint: COOKIE_SYNC_ENDPOINT, + max_sync_count: MAX_SYNC_COUNT, + gdpr: gdprApplies ? 1 : 0, + gdpr_consent: consentString, + us_privacy: uspConsent, + bidders: bidders.join(','), + coop_sync: 0 + }; + const qs = Object.entries(params) + .filter(([k, v]) => ![null, undefined, ''].includes(v)) + .map(([k, v]) => `${k}=${encodeURIComponent(v.toString())}`) + .join('&'); + syncs.push({ type: 'iframe', url: `${COOKIE_SYNC_IFRAME}?${qs}` }); + return syncs; + }, + + onBidWon: function(bid) { + if (bid.pbsWurl) { + triggerPixel(bid.pbsWurl); + } + if (bid.burl) { + triggerPixel(bid.burl); + } + }, + + onBidderError: function({ error, bidderRequest }) { + if (error.responseText && error.status) { + let id = error.responseText.match(/found for id: (.*)/); + if (Array.isArray(id) && id.length > 1 && error.status == 400) { + logError(`Placement: ${id[1]} not found on ${BIDDER_CODE} server. Please contact your account manager or email prebid@teal.works`, error); + return; + } + } + logError(`${BIDDER_CODE} bidder error`, error); + } +} +registerBidder(spec); diff --git a/modules/tealBidAdapter.md b/modules/tealBidAdapter.md new file mode 100644 index 00000000000..18b654c8108 --- /dev/null +++ b/modules/tealBidAdapter.md @@ -0,0 +1,46 @@ +Overview +======== + +``` +Module Name: Teal Adapter +Module Type: Bidder Adapter +Maintainer: prebid@teal.works +``` + +Description +=========== + +This module connects web publishers to Teal's server-side banner demand. + +# Bid Parameters + +| Key | Required | Example | Description | +| --- | -------- | ------- | ----------- | +| `account` | yes | `myaccount` | account name provided by your account manager - set to `test-account` for test mode | +| `placement` | no | `mysite300x250` | placement name provided by your account manager - set to `test-placement300x250` for test mode | +| `testMode` | no | `true` | activate test mode - 100% test bids - placement needs be set to `test-placement300x250` for this option to work | +| `useSourceBidderCode` | no | `true` | use seat bidder code as hb_bidder, instead of teal (or configured alias) | +| `subAccount` | no | `mysubaccount` | subAccount name, if provided by your account manager | + +### Notes + +- Specific ads.txt entries are required for the Teal bid adapter - please contact your account manager or for more details. +- This adapter requires iframe user syncs to be enabled to support uids. +- For full functionality in GDPR territories, please ensure Teal Digital Group Ltd is configured in your CMP. + +# Test Parameters + +``` +var adUnits = [{ + code: 'test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'teal', + params: { + account: 'test-account', + placement: 'test-placement300x250', + testMode: true + }, + }] +}] +``` diff --git a/test/spec/modules/tealBidAdapter_spec.js b/test/spec/modules/tealBidAdapter_spec.js new file mode 100644 index 00000000000..189e7f90e10 --- /dev/null +++ b/test/spec/modules/tealBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { spec } from 'modules/tealBidAdapter.js'; +import { parseUrl } from 'src/utils.js'; + +const expect = require('chai').expect; + +const PBS_HOST = 'a.bids.ws'; +const PLACEMENT = 'test-placement300x250'; +const ACCOUNT = 'test-account'; +const SUB_ACCOUNT = 'test-sub-account'; +const TEST_DOMAIN = 'example.com'; +const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; +const ADUNIT_CODE = '/1234/header-bid-tag-0'; + +const BID_PARAMS = { + params: { + placement: PLACEMENT, + account: ACCOUNT, + testMode: true + } +}; + +const BID_REQUEST = { + bidder: 'teal', + ...BID_PARAMS, + ortb2Imp: { + ext: { + tid: 'e13391ea-00f3-495d-99a6-d937990d73a9' + } + }, + mediaTypes: { + banner: { + sizes: [ + [ + 300, + 250 + ], + ] + } + }, + adUnitCode: ADUNIT_CODE, + transactionId: 'e13391ea-00f3-495d-99a6-d937990d73a9', + sizes: [ + [ + 300, + 250 + ], + ], + bidId: '123456789', + bidderRequestId: '1decd098c76ed2', + auctionId: '251a6a36-a5c5-4b82-b2b3-538c148a29dd', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + site: { + page: TEST_PAGE, + domain: TEST_DOMAIN, + publisher: { + domain: 'example.com' + } + }, + device: { + w: 1848, + h: 1007, + dnt: 0, + ua: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', + language: 'en', + sua: { + source: 2, + platform: { + brand: 'Linux', + version: [ + '5', + '4', + '0' + ] + }, + browsers: [ + { + brand: 'Google Chrome', + version: [ + '111', + '0', + '5563', + '146' + ] + }, + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + } + } +}; + +const BIDDER_REQUEST = { + bidderCode: BID_REQUEST.bidder, + auctionId: BID_REQUEST.auctionId, + bidderRequestId: BID_REQUEST.bidderRequestId, + bids: [BID_REQUEST], + metrics: BID_REQUEST.metrics, + ortb2: BID_REQUEST.ortb2, + auctionStart: 1681224591370, + timeout: 1000, + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + TEST_PAGE + ], + topmostLocation: TEST_PAGE, + location: TEST_PAGE, + canonicalUrl: null, + page: TEST_PAGE, + domain: TEST_DOMAIN, + ref: null, + legacy: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + TEST_PAGE + ], + referer: TEST_PAGE, + canonicalUrl: null + } + }, + start: 1681224591375 +}; + +const BID_RESPONSE = { + seatbid: [ + { + bid: [ + { + id: '123456789', + impid: BID_REQUEST.bidId, + price: 0.286000000000000004, + adm: '', + adomain: [ + 'teal.works' + ], + crid: '684f9b94-b8b9-4c32-83da-b075ca753f65', + w: 300, + h: 250, + exp: 300, + mtype: 1, + ext: { + ct: 0, + prebid: { + type: 'banner', + targeting: { + tl_size: '300x250', + tl_bidder: 'teal', + tl_pb: '0.20' + }, + meta: { + advertiserDomains: [ + 'teal.works' + ] + } + }, + origbidcpm: 0.286000000000000004 + } + } + ], + seat: 'appnexus', + group: 0 + } + ], + cur: 'USD', + ext: { + responsetimemillis: { + appnexus: 0 + }, + tmaxrequest: 750, + prebid: { + auctiontimestamp: 1678646619765, + passthrough: { + teal: { + bidder: spec.code + } + } + } + } +}; + +const S2S_RESPONSE_BIDDER = BID_RESPONSE.seatbid[0].seat; + +const buildRequest = (params) => { + const bidRequest = { + ...BID_REQUEST, + params: { + ...BID_REQUEST.params, + ...params, + }, + }; + var response = spec.buildRequests([bidRequest], BIDDER_REQUEST); + return response; +}; + +describe('Teal Bid Adaper', function () { + describe('buildRequests', () => { + const {data, url} = buildRequest(); + it('should give the correct URL', () => { + expect(url).equal(`https://${PBS_HOST}/openrtb2/auction`); + }); + it('should set the correct stored request ids', () => { + expect(data.ext.prebid.storedrequest.id).equal(ACCOUNT); + expect(data.imp[0].ext.prebid.storedrequest.id).equal(PLACEMENT); + }); + it('should include bidder code in passthrough object', () => { + expect(data.ext.prebid.passthrough.teal.bidder).equal(spec.code); + }); + it('should set tmax to something below the timeout', () => { + expect(data.tmax).be.greaterThan(0); + expect(data.tmax).be.lessThan(BIDDER_REQUEST.timeout) + }); + }); + describe('buildRequests with subAccount', () => { + const {data} = buildRequest({ subAccount: SUB_ACCOUNT }); + it('should set the correct stored request ids', () => { + expect(data.ext.prebid.storedrequest.id).equal(SUB_ACCOUNT); + }); + }); + describe('interpreteResponse', () => { + const request = buildRequest(); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should not have S2S bidder\'s bidder code', () => { + expect(bid.bidderCode).not.equal(S2S_RESPONSE_BIDDER); + }); + it('should return the right creative content', () => { + const respBid = BID_RESPONSE.seatbid[0].bid[0]; + expect(bid.cpm).equal(respBid.price); + expect(bid.ad).equal(respBid.adm); + expect(bid.width).equal(respBid.w); + expect(bid.height).equal(respBid.h); + }); + }); + describe('interpreteResponse with useSourceBidderCode', () => { + const request = buildRequest({ useSourceBidderCode: true }); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should have S2S bidder\'s code', () => { + expect(bid.bidderCode).equal(S2S_RESPONSE_BIDDER); + }); + }); + describe('getUserSyncs with iframeEnabled', () => { + const allSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: BID_RESPONSE }], null, null); + const [{ url, type }] = allSyncs; + const { bidders, endpoint } = parseUrl(url).search; + it('should return a single sync object', () => { + expect(allSyncs.length).equal(1); + }); + it('should use iframe sync when available', () => { + expect(type).equal('iframe'); + }); + it('should sync to the right endpoint', () => { + expect(endpoint).equal(`https://${PBS_HOST}/cookie_sync`); + }); + it('should sync to at least one bidders', () => { + expect(bidders.split(',').length).be.greaterThan(0); + }); + }); +}); From b89d9e6f4ed05fc5e0f9039e12229dfd6501dfe8 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Fri, 31 Jan 2025 03:25:24 +1100 Subject: [PATCH 0873/1097] Adds native support for adnuntius. (#12708) --- modules/adnuntiusBidAdapter.js | 16 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 230 +++++++++++++++++- 2 files changed, 229 insertions(+), 17 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index ffd02e43a99..f8bf37af42a 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -12,7 +12,7 @@ const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => { const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; const MAXIMUM_DEALS_LIMIT = 5; const VALID_BID_TYPES = ['netBid', 'grossBid']; const METADATA_KEY = 'adn.metaData'; @@ -319,6 +319,9 @@ export const spec = { const adUnit = {...bidTargeting, auId: bid.params.auId, targetId: targetId}; if (mediaType === VIDEO) { adUnit.adType = 'VAST'; + } else if (mediaType === NATIVE) { + adUnit.adType = 'NATIVE'; + adUnit.nativeRequest = mediaTypeData.ortb; } const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { @@ -391,10 +394,13 @@ export const spec = { const isDeal = dealCount > 0; const renderSource = isDeal ? ad : adUnit; if (renderSource.vastXml) { - adResponse.vastXml = renderSource.vastXml - adResponse.mediaType = VIDEO + adResponse.vastXml = renderSource.vastXml; + adResponse.mediaType = VIDEO; + } else if (renderSource.nativeJson) { + adResponse.mediaType = NATIVE; + adResponse.native = renderSource.nativeJson; } else { - adResponse.ad = renderSource.html + adResponse.ad = renderSource.html; } return adResponse; } diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index c82671040d0..d9574b917ff 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -106,7 +106,33 @@ describe('adnuntiusBidAdapter', function () { } ]; - const multiBidderInResponse = { + const nativeBidderRequest = {bid: [ + { + bidId: 'adn-0000000000000551', + bidder: 'adnuntius', + params: { + auId: '0000000000000551', + network: 'adnuntius', + }, + mediaTypes: { + native: { + ortb: { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 250, + h: 250, + } + }] + } + } + }, + } + ]}; + + const multiBidAndFormatRequest = { bid: [{ bidder: 'adnuntius', bidId: '3a602680158a85', @@ -127,6 +153,7 @@ describe('adnuntiusBidAdapter', function () { }, { bidder: 'adnuntius', + bidId: 'fewwef', params: { auId: '381535', network: '1287', @@ -140,6 +167,51 @@ describe('adnuntiusBidAdapter', function () { video: { playerSize: [200, 200], context: 'instream' + }, + native: { + ortb: { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }] + } + } + } + }, + { + bidder: 'adnuntius', + bidId: 'pol', + params: { + auId: '381535', + network: '1287', + bidType: 'netBid', + targetId: 'alt', + }, + mediaTypes: { + banner: { + sizes: [[200, 200]] + }, + video: { + playerSize: [200, 200], + context: 'instream' + }, + native: { + ortb: { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }] + } } } }] @@ -172,7 +244,7 @@ describe('adnuntiusBidAdapter', function () { bidId: 'adn-0000000000000551', } ] - } + }; const videoBidRequest = { bid: videoBidderRequest, @@ -180,7 +252,7 @@ describe('adnuntiusBidAdapter', function () { params: { bidType: 'justsomestuff-error-handling' } - } + }; const deals = [ { @@ -246,6 +318,72 @@ describe('adnuntiusBidAdapter', function () { } ]; + const nativeResponseBody = [ + { + 'auId': '0000000000000551', + 'targetId': 'adn-0000000000000551', + 'nativeJson': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'url': 'http://something.com/something.png' + } + }, + { + 'url': 'http://whatever.com' + }] + }, + 'matchedAdCount': 1, + 'responseId': '', + 'ads': [ + { + 'advertiserDomains': ['adnuntius.com'], + 'creativeWidth': 640, + 'creativeHeight': 640, + 'cpm': { + 'amount': 2000, + 'currency': 'NOK' + }, + 'bid': { + 'amount': 2, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 2, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 2, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 2, + 'currency': 'NOK' + }, + 'assets': { + 'img': { + 'cdnId': 'http://localhost:8079/cdn/9urJusYWpjFDLcpOwfejrkWlLP1heM3vWIJjuHk48BQ.mp4', + 'width': '1920', + 'height': '1080' + } + }, + } + ] + } + ]; + + const nativeResponse = { + body: { + 'adUnits': nativeResponseBody + } + }; + + const nativeMultiFormatResponseBody = JSON.parse(JSON.stringify(nativeResponseBody[0])); + nativeMultiFormatResponseBody.auId = '0000000000381535'; + nativeMultiFormatResponseBody.targetId = 'alt-native'; + const multiFormatServerResponse = { body: { 'adUnits': [ @@ -258,23 +396,23 @@ describe('adnuntiusBidAdapter', function () { 'ads': [ { 'cpm': { - 'amount': 1500.0, + 'amount': 12500.0, 'currency': 'NOK' }, 'bid': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, 'grossBid': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, 'netBid': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, 'cost': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, creativeWidth: 200, @@ -321,6 +459,7 @@ describe('adnuntiusBidAdapter', function () { } ] }, + nativeMultiFormatResponseBody, { 'auId': '0000000000381535', 'targetId': '3a602680158a85', @@ -364,6 +503,42 @@ describe('adnuntiusBidAdapter', function () { 'html': '\u003Ca \'\u003E\u003C/script\u003E', } ] + }, + { + 'auId': '0000000000381535', + 'renderOption': 'DIV', + 'targetId': 'alt', + 'html': '\u003C!DOCTYPE html\u003E\n\u003C\u003E\n\u003C/html\u003E', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-1620340740', + 'ads': [ + { + 'destinationUrlEsc': '', + 'cpm': { + 'amount': 1000.0, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'bid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'html': '\u003Ca \'\u003E\u003C/script\u003E', + } + ] } ], 'network': '1287', @@ -1322,8 +1497,8 @@ describe('adnuntiusBidAdapter', function () { } }); - const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidderInResponse)); - expect(interpretedResponse).to.have.lengthOf(2); + const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidAndFormatRequest)); + expect(interpretedResponse).to.have.lengthOf(3); let ad = multiFormatServerResponse.body.adUnits[0].ads[0]; expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); @@ -1336,7 +1511,7 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].ad).to.equal(multiFormatServerResponse.body.adUnits[0].html); expect(interpretedResponse[0].ttl).to.equal(360); - ad = multiFormatServerResponse.body.adUnits[3].ads[0]; + ad = multiFormatServerResponse.body.adUnits[4].ads[0]; expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000); expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); @@ -1344,8 +1519,19 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); expect(interpretedResponse[1].currency).to.equal(ad.bid.currency); expect(interpretedResponse[1].netRevenue).to.equal(false); - expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[3].html); + expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[4].html); expect(interpretedResponse[1].ttl).to.equal(360); + + ad = multiFormatServerResponse.body.adUnits[2].ads[0]; + expect(interpretedResponse[2].native).to.not.be.undefined; + expect(interpretedResponse[2].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[2].cpm).to.equal(ad.netBid.amount * 1000); + expect(interpretedResponse[2].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[2].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[2].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[2].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[2].netRevenue).to.equal(false); + expect(interpretedResponse[2].ttl).to.equal(360); }); it('should not process valid response when passed alt bidder that is an adndeal', function () { @@ -1439,4 +1625,24 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[1].dealCount).to.equal(0); }); }); + + describe('interpretNativeResponse', function () { + it('should return valid response when passed valid server response', function () { + const interpretedResponse = spec.interpretResponse(nativeResponse, nativeBidderRequest); + const ad = nativeResponse.body.adUnits[0].ads[0] + expect(interpretedResponse).to.have.lengthOf(1); + + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(ad.bid.amount * 1000); + expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[0].vastXml).to.equal(ad.vastXml); + }); + }); }); From a1e8b121d257db7151b70423ac84d1f5ac45742b Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn Date: Thu, 30 Jan 2025 19:22:23 +0200 Subject: [PATCH 0874/1097] AirGrid RTD Provider: update TCF ID with MiQ TCF ID (#12710) * chore: update `getAudiencesAsBidderOrtb2` implementation and test * chore: use provided tag insertion method * fix: add `airgrid` to `_approvedLoadExternalJSList` * fix: use 'sdk' path if no publisherId is provided * fix: use accountId as path param for script url * fix: assign edktInitializor props before `loadExternalScript` call * fix: set `edktInitializor.invoked` before calling `loadExternalScript` * fix: restore method for setting `user.ext.data` feat: extend module data setting using `user.keywords` for appnexus * fix: rollback changes to data setting method * replace AG TCF ID with MiQ TCF ID (AirGrid acquired by MiQ) --------- Co-authored-by: naripok --- modules/airgridRtdProvider.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 300744d62fe..c547528a57e 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -17,7 +17,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; -const AG_TCF_ID = 782; +const MIQ_TCF_ID = 101; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; export const storage = getStorageManager({ @@ -76,7 +76,7 @@ export function setAudiencesAsBidderOrtb2(bidConfig, rtdConfig, audiences) { const agUserData = [ { - id: String(AG_TCF_ID), + id: String(MIQ_TCF_ID), ext: { segtax: 540, }, @@ -129,7 +129,7 @@ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: passAudiencesToBidders, - gvlid: AG_TCF_ID + gvlid: MIQ_TCF_ID }; submodule(MODULE_NAME, airgridSubmodule); From b33d77c856c2bbd7ee16df08c84c9c089368c0ec Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Thu, 30 Jan 2025 18:37:23 +0100 Subject: [PATCH 0875/1097] Module: appnexusBidAdapter. handle the case when userId is set, but userIdAsEids not (#12705) * handle the case when userId is set, but userIdAsEids not * fix linter errors * fix remaining linter error --- modules/appnexusBidAdapter.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 4270b47d91e..099dd989a1d 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -354,18 +354,23 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - bidRequests[0].userIdAsEids.forEach(eid => { - if (!eid || !eid.uids || eid.uids.length < 1) { return; } - eid.uids.forEach(uid => { - let tmp = {'source': eid.source, 'id': uid.id}; - if (eid.source == 'adserver.org') { - tmp.rti_partner = 'TDID'; - } else if (eid.source == 'uidapi.com') { - tmp.rti_partner = 'UID2'; - } - eids.push(tmp); + const processEids = (uids) => { + uids.forEach(eid => { + if (!eid || !eid.uids || eid.uids.length < 1) { return; } + eid.uids.forEach(uid => { + let tmp = {'source': eid.source, 'id': uid.id}; + if (eid.source == 'adserver.org') { + tmp.rti_partner = 'TDID'; + } else if (eid.source == 'uidapi.com') { + tmp.rti_partner = 'UID2'; + } + eids.push(tmp); + }); }); - }); + } + if (bidRequests[0].userIdAsEids) { + processEids(bidRequests[0].userIdAsEids); + } if (eids.length) { payload.eids = eids; } From c49cbc036a5da4bc4614ab39a42a2dd9db1ac841 Mon Sep 17 00:00:00 2001 From: cpcpn-emil <115714010+cpcpn-emil@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:05:59 +0100 Subject: [PATCH 0876/1097] ConceptX: bug fix (#12715) * New adapter: concepx * Syntax change * Revert syntax change * Defensive check for response from bidder server * Add better validation for the request * Merge branch 'master' of https://github.com/prebid/Prebid.js * Don't append url on every buildrequest * Add gvlId to conceptX * Update conceptxBidAdapter.js Defensice check * Update conceptxBidAdapter.js Reverse the check --- modules/conceptxBidAdapter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 47c50a4c0ad..67ebd88e4e4 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -57,6 +57,9 @@ export const spec = { return bidResponses } const firstSeat = firstBid.ads[0] + if (!firstSeat) { + return bidResponses + } const bidResponse = { requestId: firstSeat.requestId, cpm: firstSeat.cpm, From f40ff578a614ef2fdbc181a35c56b10eb20f0eaf Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 31 Jan 2025 16:39:21 +0000 Subject: [PATCH 0877/1097] Prebid 9.29.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71717e5bd94..916fac6e786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.29.0-pre", + "version": "9.29.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.29.0-pre", + "version": "9.29.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 73cb168609d..37b78a2f7d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.29.0-pre", + "version": "9.29.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 5864b98663e69d2a380f9c84c78f579cfe23c06e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 31 Jan 2025 16:39:22 +0000 Subject: [PATCH 0878/1097] Increment version to 9.30.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 916fac6e786..6ee34d1b924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.29.0", + "version": "9.30.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.29.0", + "version": "9.30.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 37b78a2f7d1..b41e9852f20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.29.0", + "version": "9.30.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 16137211505fe0934b20a16e385118e452466363 Mon Sep 17 00:00:00 2001 From: vivekyadav15 Date: Tue, 4 Feb 2025 19:24:10 +0530 Subject: [PATCH 0879/1097] ADD video placement logic (#12722) --- modules/medianetAnalyticsAdapter.js | 16 +++++++++++++++- .../modules/medianetAnalyticsAdapter_spec.js | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 1b4ccc45c88..526399014c1 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -20,6 +20,8 @@ import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../sr import {includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import {ADPOD} from '../src/mediaTypes.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -266,11 +268,23 @@ class AdSlot { tmax: this.tmax, targ: JSON.stringify(this.targeting), ismn: this.medianetPresent, - vplcmtt: this.context, + vplcmtt: this.getVideoPlacement(), }, this.adext && {'adext': JSON.stringify(this.adext)}, ); } + getVideoPlacement() { + switch (this.context) { + case INSTREAM: + return 1 + case OUTSTREAM: + return 6 + case ADPOD: + return 7 + default: + return 0 + } + } } class BidWrapper { diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 8a298acae80..85080f904e3 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -200,7 +200,7 @@ describe('Media.net Analytics Adapter', function() { medianetAnalytics.clearlogsQueue(); expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); expect(noBidLog.szs).to.have.ordered.members([encodeURIComponent('300x250|1x1|640x480'), encodeURIComponent('300x250|1x1|640x480')]); - expect(noBidLog.vplcmtt).to.equal('instream'); + expect(noBidLog.vplcmtt).to.equal('1'); }); it('twin ad units should have correct sizes', function() { From c28fe9f730544c2869790c49aab38256ed63752d Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 4 Feb 2025 12:44:59 -0500 Subject: [PATCH 0880/1097] Update ttdBidAdapter.js: hang pmp off imp (#12719) * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * whitespace --- modules/ttdBidAdapter.js | 22 +++++++++++++++------- test/spec/modules/ttdBidAdapter_spec.js | 4 ++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index e9d0d3ca9f1..082b9806da5 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -390,12 +390,13 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {BidRequest[]} an array of validBidRequests - * @param {*} bidderRequest - * @return {ServerRequest} Info describing the request to the server. + * @param {BidRequest[]} validBidRequests - An array of valid bid requests + * @param {*} bidderRequest - The current bidder request object + * @returns {ServerRequest} - Info describing the request to the server */ buildRequests: function (validBidRequests, bidderRequest) { const firstPartyData = bidderRequest.ortb2 || {}; + const firstPartyImpData = bidderRequest.ortb2Imp || {}; let topLevel = { id: bidderRequest.bidderRequestId, imp: validBidRequests.map(bidRequest => getImpression(bidRequest)), @@ -418,11 +419,18 @@ export const spec = { } if (firstPartyData && firstPartyData.app) { - topLevel.app = firstPartyData.app + topLevel.app = firstPartyData.app; } - if (firstPartyData && firstPartyData.pmp) { - topLevel.pmp = firstPartyData.pmp + if ((firstPartyData && firstPartyData.pmp) || (firstPartyImpData && firstPartyImpData.pmp)) { + topLevel.imp.forEach(imp => { + imp.pmp = utils.mergeDeep( + {}, + imp.pmp || {}, + firstPartyData?.pmp || {}, + firstPartyImpData?.pmp || {} + ); + }); } let url = selectEndpoint(bidderRequest.bids[0].params) + bidderRequest.bids[0].params.supplySourceId; @@ -457,7 +465,7 @@ export const spec = { * - vastXml * - dealId * - * @param {ttdResponseObj} bidResponse A successful response from ttd. + * @param {Object} response A successful response from ttd. * @param {ServerRequest} serverRequest The result of buildRequests() that lead to this response. * @return {Bid[]} An array of formatted bids. */ diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 9f98a734d1c..4580c514609 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -715,8 +715,8 @@ describe('ttdBidAdapter', function () { let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; - validateExtFirstPartyData(requestBody.pmp.ext) - expect(requestBody.pmp.private_auction).to.equal(1) + validateExtFirstPartyData(requestBody.imp[0].pmp.ext) + expect(requestBody.imp[0].pmp.private_auction).to.equal(1) }); }); From 2029e09d877e4d423ba46c80e3100be410ed7196 Mon Sep 17 00:00:00 2001 From: "Adserver.Online" <61009237+adserver-online@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:57:25 +0200 Subject: [PATCH 0881/1097] Add cordless alias (#12728) Co-authored-by: dev --- modules/asoBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 08612757de1..388eee86fe2 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -18,7 +18,8 @@ export const spec = { aliases: [ {code: 'bcmint'}, {code: 'bidgency'}, - {code: 'kuantyx'} + {code: 'kuantyx'}, + {code: 'cordless'} ], isBidRequestValid: bid => { From 31268d556994a6ecf1845ce2b7a068cc2a8d0ca8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 6 Feb 2025 04:29:36 -0800 Subject: [PATCH 0882/1097] Prebid Server adapter: fix bug with disabling some of multiple instances (#12727) * Prebid Server adapter: fix bug with disabling some of multiple instances * Fix validation --- modules/prebidServerBidAdapter/index.js | 93 ++++++++++--------- .../modules/prebidServerBidAdapter_spec.js | 30 ++++++ 2 files changed, 78 insertions(+), 45 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 673e92f709b..6b4d3a329e5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -105,45 +105,45 @@ config.setDefaults({ }); /** - * @param {S2SConfig} option + * @param {S2SConfig} s2sConfig * @return {boolean} */ -function updateConfigDefaultVendor(option) { - if (option.defaultVendor) { - let vendor = option.defaultVendor; - let optionKeys = Object.keys(option); +function updateConfigDefaults(s2sConfig) { + if (s2sConfig.defaultVendor) { + let vendor = s2sConfig.defaultVendor; + let optionKeys = Object.keys(s2sConfig); if (S2S_VENDORS[vendor]) { // vendor keys will be set if either: the key was not specified by user // or if the user did not set their own distinct value (ie using the system default) to override the vendor Object.keys(S2S_VENDORS[vendor]).forEach((vendorKey) => { - if (s2sDefaultConfig[vendorKey] === option[vendorKey] || !includes(optionKeys, vendorKey)) { - option[vendorKey] = S2S_VENDORS[vendor][vendorKey]; + if (s2sDefaultConfig[vendorKey] === s2sConfig[vendorKey] || !includes(optionKeys, vendorKey)) { + s2sConfig[vendorKey] = S2S_VENDORS[vendor][vendorKey]; } }); } else { logError('Incorrect or unavailable prebid server default vendor option: ' + vendor); return false; } + } else { + if (s2sConfig.adapter == null) { + s2sConfig.adapter = 'prebidServer'; + } } - // this is how we can know if user / defaultVendor has set it, or if we should default to false - return option.enabled = typeof option.enabled === 'boolean' ? option.enabled : false; + return true; } /** - * @param {S2SConfig} option + * @param {S2SConfig} s2sConfig * @return {boolean} */ -function validateConfigRequiredProps(option) { - const keys = Object.keys(option); - if (['accountId', 'endpoint'].filter(key => { - if (!includes(keys, key)) { +function validateConfigRequiredProps(s2sConfig) { + for (const key of ['accountId', 'endpoint']) { + if (s2sConfig[key] == null) { logError(key + ' missing in server to server config'); - return true; + return false; } - return false; - }).length > 0) { - return false; } + return true; } // temporary change to modify the s2sConfig for new format used for endpoint URLs; @@ -164,40 +164,43 @@ function formatUrlParams(option) { }); } -/** - * @param {(S2SConfig[]|S2SConfig)} options - */ -function setS2sConfig(options) { +export function validateConfig(options) { if (!options) { return; } - const normalizedOptions = Array.isArray(options) ? options : [options]; - - const activeBidders = []; - const optionsValid = normalizedOptions.every((option, i, array) => { - formatUrlParams(option); - const updateSuccess = updateConfigDefaultVendor(option); - if (updateSuccess !== false) { - const valid = validateConfigRequiredProps(option); - if (valid !== false) { - if (Array.isArray(option['bidders'])) { - array[i]['bidders'] = option['bidders'].filter(bidder => { - if (activeBidders.indexOf(bidder) === -1) { - activeBidders.push(bidder); - return true; - } + options = Array.isArray(options) ? options : [options]; + const activeBidders = new Set(); + return options.filter(s2sConfig => { + formatUrlParams(s2sConfig); + if ( + updateConfigDefaults(s2sConfig) && + validateConfigRequiredProps(s2sConfig) && + s2sConfig.enabled + ) { + if (Array.isArray(s2sConfig.bidders)) { + s2sConfig.bidders = s2sConfig.bidders.filter(bidder => { + if (activeBidders.has(bidder)) { return false; - }); - } - return true; + } else { + activeBidders.add(bidder); + return true; + } + }) } + return true; + } else { + logWarn('prebidServer: s2s config is disabled', s2sConfig); } - logWarn('prebidServer: s2s config is disabled'); - return false; - }); + }) +} - if (optionsValid) { - return _s2sConfigs = normalizedOptions; +/** + * @param {(S2SConfig[]|S2SConfig)} options + */ +function setS2sConfig(options) { + options = validateConfig(options); + if (options.length) { + _s2sConfigs = options; } } getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index fb1b62b9dc6..a67119e72d4 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -4,6 +4,7 @@ import { PrebidServer as Adapter, resetSyncedStatus, resetWurlMap, + validateConfig, s2sDefaultConfig } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager, {PBS_ADAPTER_NAME} from 'src/adapterManager.js'; @@ -564,6 +565,35 @@ function addFpdEnrichmentsToS2SRequest(s2sReq, bidderRequests) { } } +describe('s2s configuration', () => { + let cfg1, cfg2; + beforeEach(() => { + cfg1 = { + enabled: true, + bidders: ['bidderB'], + accountId: '123456', + endpoint: { + p1Consent: 'first.endpoint' + } + }; + cfg2 = { + enabled: true, + bidders: ['bidderA'], + accountId: '123456', + endpoint: { + p1Consent: 'second.endpoint', + } + }; + }) + it('sets prebid server adapter by default', () => { + expect(validateConfig(cfg1)[0].adapter).to.eql('prebidServer'); + }); + it('filters out disabled configs', () => { + cfg1.enabled = false; + expect(validateConfig([cfg1, cfg2])).to.eql([cfg2]); + }) +}); + describe('S2S Adapter', function () { let adapter, addBidResponse = sinon.spy(), From 824ecc00d29ba37b8c2cafede934fc09475eb065 Mon Sep 17 00:00:00 2001 From: daniel-barac <55977021+daniel-barac@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:55:39 +0200 Subject: [PATCH 0883/1097] Connatix Bid Adapter : fix consent query params & refactor post message events for user ids (#12704) * Fixed consent query params; Refactored post message events for eids * pr changes * update tests * refactor * renaming event * fix unit tests - size prop is not supported in all browsers --- modules/connatixBidAdapter.js | 40 +++++++---- test/spec/modules/connatixBidAdapter_spec.js | 73 ++++++++++++++++---- 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index ea02b49d8ed..649b4094dfa 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -5,9 +5,9 @@ import { import { percentInView } from '../libraries/percentInView/percentInView.js'; +import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; -import { ajax } from '../src/ajax.js'; import { deepAccess, deepSetValue, @@ -35,7 +35,7 @@ const CNX_IDS_LOCAL_STORAGE_COOKIES_KEY = 'cnx_user_ids'; const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000; // 30 days export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved'; -const IDENTITY_PROVIDER_RESOLVED_EVENT = 'cnx_identity_provider_resolved'; +const IDENTITY_PROVIDER_COLLECTION_UPDATED_EVENT = 'cnx_identity_provider_collection_updated'; let cnxIdsValues; const EVENTS_URL = 'https://capi.connatix.com/tr/am'; @@ -188,6 +188,15 @@ function _handleEids(payload, validBidRequests) { } } +export function hasQueryParams(url) { + try { + const urlObject = new URL(url); + return !!urlObject.search; + } catch (e) { + return false; + } +} + export function saveOnAllStorages(name, value, expirationTimeMs) { const date = new Date(); date.setTime(date.getTime() + expirationTimeMs); @@ -233,10 +242,10 @@ export const spec = { if (!isValid) { logError( `Invalid bid request: - hasBidId: ${hasBidId}, - hasMediaTypes: ${hasMediaTypes}, - isValidBanner: ${isValidBanner}, - isValidVideo: ${isValidVideo}, + hasBidId: ${hasBidId}, + hasMediaTypes: ${hasMediaTypes}, + isValidBanner: ${isValidBanner}, + isValidVideo: ${isValidVideo}, hasRequiredBidParams: ${hasRequiredBidParams}` ); } @@ -341,19 +350,20 @@ export const spec = { } window.addEventListener('message', function handler(event) { - if (!event.data || event.origin !== 'https://cds.connatix.com') { + if (!event.data || event.origin !== 'https://cds.connatix.com' || !event.data.cnx) { return; } - if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT) { + const { message, data } = event.data.cnx; + + if (message === ALL_PROVIDERS_RESOLVED_EVENT) { this.removeEventListener('message', handler); event.stopImmediatePropagation(); } - if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT || event.data.type === IDENTITY_PROVIDER_RESOLVED_EVENT) { - const response = event.data; - if (response.data) { - saveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, response.data, CNX_IDS_EXPIRY); + if (message === ALL_PROVIDERS_RESOLVED_EVENT || message === IDENTITY_PROVIDER_COLLECTION_UPDATED_EVENT) { + if (data) { + saveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, data, CNX_IDS_EXPIRY); } } }, true) @@ -361,7 +371,11 @@ export const spec = { const syncUrl = serverResponses[0].body.UserSyncEndpoint; const queryParams = Object.keys(params).length > 0 ? formatQS(params) : ''; - const url = queryParams ? `${syncUrl}?${queryParams}` : syncUrl; + let url = syncUrl; + if (queryParams) { + url += hasQueryParams(syncUrl) ? `&${queryParams}` : `?${queryParams}`; + } + return [{ type: 'iframe', url diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index af56b937f58..3d9ef742fa0 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -7,11 +7,12 @@ import { getBidFloor as connatixGetBidFloor, _getMinSize as connatixGetMinSize, _getViewability as connatixGetViewability, + hasQueryParams as connatixHasQueryParams, _isViewabilityMeasurable as connatixIsViewabilityMeasurable, - saveOnAllStorages as connatixSaveOnAllStorages, readFromAllStorages as connatixReadFromAllStorages, - storage, - spec + saveOnAllStorages as connatixSaveOnAllStorages, + spec, + storage } from '../../../modules/connatixBidAdapter.js'; import adapterManager from '../../../src/adapterManager.js'; import * as ajax from '../../../src/ajax.js'; @@ -731,6 +732,7 @@ describe('connatixBidAdapter', function () { const CustomerId = '99f20d18-c4b4-4a28-3d8e-d43e2c8cb4ac'; const PlayerId = 'e4984e88-9ff4-45a3-8b9d-33aabcad634f'; const UserSyncEndpoint = 'https://connatix.com/sync' + const UserSyncEndpointWithParams = 'https://connatix.com/sync?param1=value1' const Bid = {Cpm: 0.1, RequestId: '2f897340c4eaa3', Ttl: 86400, CustomerId, PlayerId}; const serverResponse = { @@ -740,6 +742,13 @@ describe('connatixBidAdapter', function () { }, headers: function() { } }; + const serverResponse2 = { + body: { + UserSyncEndpoint: UserSyncEndpointWithParams, + Bids: [ Bid ] + }, + headers: function() { } + }; it('Should return an empty array when iframeEnabled: false', function () { expect(spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [], {}, {}, {})).to.be.an('array').that.is.empty; @@ -829,6 +838,17 @@ describe('connatixBidAdapter', function () { const { url } = userSyncList[0]; expect(url).to.equal(`${UserSyncEndpoint}?gdpr=1&gdpr_consent=test%262&us_privacy=1YYYN`); }); + it('Should correctly append all consents to the sync url if the url contains query params', function () { + const userSyncList = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [serverResponse2], + {gdprApplies: true, consentString: 'test&2'}, + '1YYYN', + {consent: '1'} + ); + const { url } = userSyncList[0]; + expect(url).to.equal(`${UserSyncEndpointWithParams}&gdpr=1&gdpr_consent=test%262&us_privacy=1YYYN`); + }); }); describe('userIdAsEids', function() { @@ -957,26 +977,26 @@ describe('connatixBidAdapter', function () { const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved'; const mockData = { - providerName: 'nonId', data: { supplementalEids: [{ provider: 2, group: 1, eidsList: ['123', '456'] }] } }; function messageHandler(event) { - if (!event.data || event.origin !== 'https://cds.connatix.com') { + if (!event.data || event.origin !== 'https://cds.connatix.com' || !event.data.cnx) { return; } - if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT) { + const { message, data } = event.data.cnx; + + if (message === ALL_PROVIDERS_RESOLVED_EVENT) { window.removeEventListener('message', messageHandler); event.stopImmediatePropagation(); } - if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT || event.data.type === IDENTITY_PROVIDER_RESOLVED_EVENT) { - const response = event.data; - if (response.data) { - connatixSaveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, response.data, CNX_IDS_EXPIRY); + if (message === ALL_PROVIDERS_RESOLVED_EVENT || message === IDENTITY_PROVIDER_COLLECTION_UPDATED_EVENT) { + if (data) { + connatixSaveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, data, CNX_IDS_EXPIRY); } } } @@ -1000,7 +1020,7 @@ describe('connatixBidAdapter', function () { it('Should set a cookie and save to local storage when a valid message is received', () => { const fakeEvent = { - data: { type: 'cnx_all_identity_providers_resolved', data: mockData }, + data: { cnx: { message: 'cnx_all_identity_providers_resolved', data: mockData } }, origin: 'https://cds.connatix.com', stopImmediatePropagation: sinon.spy() }; @@ -1019,7 +1039,7 @@ describe('connatixBidAdapter', function () { expect(retrievedData).to.deep.equal(mockData); }); - it('Should should not do anything when there is no data in the payload', () => { + it('Should not do anything when there is no data in the payload', () => { const fakeEvent = { data: null, origin: 'https://cds.connatix.com', @@ -1034,9 +1054,9 @@ describe('connatixBidAdapter', function () { expect(storage.setDataInLocalStorage.notCalled).to.be.true; }); - it('Should should not do anything when the origin is invalid', () => { + it('Should not do anything when the origin is invalid', () => { const fakeEvent = { - data: { type: 'cnx_all_identity_providers_resolved', data: mockData }, + data: { cnx: { message: 'cnx_all_identity_providers_resolved', data: mockData } }, origin: 'https://notConnatix.com', stopImmediatePropagation: sinon.spy() }; @@ -1049,4 +1069,29 @@ describe('connatixBidAdapter', function () { expect(storage.setDataInLocalStorage.notCalled).to.be.true; }); }); + describe('connatixHasQueryParams', () => { + it('Should return false if there is no query param in the url', () => { + const url = 'http://example.com' + const result = connatixHasQueryParams(url); + expect(result).to.equal(false); + }); + + it('Should return true if there is one query param in the url', () => { + const url = 'http://example.com?query1=value1' + const result = connatixHasQueryParams(url); + expect(result).to.equal(true); + }); + + it('Should return true if there is multiple query params in the url', () => { + const url = 'http://example.com?query1=value1&query2=value2' + const result = connatixHasQueryParams(url); + expect(result).to.equal(true); + }); + + it('Should return false if the url is invalid', () => { + const url = 'example' + const result = connatixHasQueryParams(url); + expect(result).to.equal(false); + }); + }); }); From 1377d17b2566c5c366fbd7c226b9f58ce57ec30b Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Thu, 6 Feb 2025 16:11:26 +0200 Subject: [PATCH 0884/1097] Missena Bid Adapter : send viewport (#12736) --- libraries/viewport/viewport.js | 12 ++++++++++++ modules/missenaBidAdapter.js | 2 ++ test/spec/modules/missenaBidAdapter_spec.js | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/libraries/viewport/viewport.js b/libraries/viewport/viewport.js index b385e9a27ec..0a2e1688405 100644 --- a/libraries/viewport/viewport.js +++ b/libraries/viewport/viewport.js @@ -11,3 +11,15 @@ export function getViewportCoordinates() { return {}; } } + +export function getViewportSize() { + try { + const win = getWindowTop(); + let { innerHeight, innerWidth } = win; + innerHeight = innerHeight || win.document.documentElement.clientWidth || win.document.body.clientWidth; + innerWidth = innerWidth || win.document.documentElement.clientHeight || win.document.body.clientHeight + return { width: innerWidth, height: innerHeight }; + } catch (e) { + return {}; + } +} diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index c5c8678e0b1..2a160d38d28 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -13,6 +13,7 @@ import { getStorageManager } from '../src/storageManager.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js'; import { normalizeBannerSizes } from '../libraries/sizeUtils/sizeUtils.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -88,6 +89,7 @@ function toPayload(bidRequest, bidderRequest) { payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0; payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; payload.screen = { height: screen.height, width: screen.width }; + payload.viewport = getViewportSize(); payload.sizes = normalizeBannerSizes(bidRequest.mediaTypes.banner.sizes); return { diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 2ca262dcf7f..b2267c9110e 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -18,6 +18,7 @@ describe('Missena Adapter', function () { let sandbox = sinon.sandbox.create(); sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); sandbox.stub(autoplay, 'isAutoplayEnabled').returns(false); + const viewport = { width: window.top.innerWidth, height: window.top.innerHeight }; const bidId = 'abc'; const bid = { @@ -155,6 +156,11 @@ describe('Missena Adapter', function () { expect(payload.referer_canonical).to.equal('https://canonical'); }); + it('should send viewport', function () { + expect(payload.viewport.width).to.equal(viewport.width); + expect(payload.viewport.height).to.equal(viewport.height); + }); + it('should send gdpr consent information to the request', function () { expect(payload.consent_string).to.equal(consentString); expect(payload.consent_required).to.equal(true); From 09456baf2e0892e9f658a9a90b17a60824349772 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Fri, 7 Feb 2025 02:12:57 +1100 Subject: [PATCH 0885/1097] Merge user.ext.data into kv field of bid request. (#12712) --- modules/adnuntiusBidAdapter.js | 18 ++++++++++++------ test/spec/modules/adnuntiusBidAdapter_spec.js | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index f8bf37af42a..9463944a824 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -170,7 +170,7 @@ const targetingTool = (function() { const getSegmentsFromOrtb = function(bidderRequest) { const userData = deepAccess(bidderRequest.ortb2 || {}, 'user.data'); let segments = []; - if (userData) { + if (userData && Array.isArray(userData)) { userData.forEach(userdat => { if (userdat.segment) { segments.push(...userdat.segment.map((segment) => { @@ -183,8 +183,8 @@ const targetingTool = (function() { return segments }; - const getKvsFromOrtb = function(bidderRequest) { - return deepAccess(bidderRequest.ortb2 || {}, 'site.ext.data'); + const getKvsFromOrtb = function(bidderRequest, path) { + return deepAccess(bidderRequest.ortb2 || {}, path); }; return { @@ -203,15 +203,21 @@ const targetingTool = (function() { existingUrlRelatedData.segments = segments; }, mergeKvsFromOrtb: function(bidTargeting, bidderRequest) { - const kv = getKvsFromOrtb(bidderRequest || {}); - if (isEmpty(kv)) { + const siteKvs = getKvsFromOrtb(bidderRequest || {}, 'site.ext.data'); + const userKvs = getKvsFromOrtb(bidderRequest || {}, 'user.ext.data'); + if (isEmpty(siteKvs) && isEmpty(userKvs)) { return; } if (bidTargeting.kv && !Array.isArray(bidTargeting.kv)) { bidTargeting.kv = convertObjectToArray(bidTargeting.kv); } bidTargeting.kv = bidTargeting.kv || []; - bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(kv)); + if (!isEmpty(siteKvs)) { + bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(siteKvs)); + } + if (!isEmpty(userKvs)) { + bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(userKvs)); + } } } })(); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index d9574b917ff..8941f97dc0d 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -1021,8 +1021,16 @@ describe('adnuntiusBidAdapter', function () { delete bidderRequests[0].params.targeting; }); - it('should pass site data ext as key values to ad server with targeting in different format', function () { + it('should pass site.ext.data and user.ext.data as key values to ad server with targeting in different format', function () { const ortb2 = { + user: { + ext: { + data: { + 'from': 'user', + '9090': 'from-user' + } + } + }, site: { ext: { data: { @@ -1043,12 +1051,14 @@ describe('adnuntiusBidAdapter', function () { expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); + expect(countMatches(data.adUnits[0].kv, {'from': 'user'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'9090': 'from-user'})).to.equal(1); expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1); expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1); expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); - expect(data.adUnits[0].kv.length).to.equal(5); + expect(data.adUnits[0].kv.length).to.equal(7); delete bidderRequests[0].params.targeting; }); From 7c36992a69e3988cf10c9fc67734f2f85b8bbd95 Mon Sep 17 00:00:00 2001 From: Anastasiia Pankiv <153929408+anastasiiapankivFS@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:20:30 +0200 Subject: [PATCH 0886/1097] Added missing param to hadronId JS snippet load (#12737) --- modules/hadronRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 56f4861b41b..eae85db3c34 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -142,7 +142,7 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { paramOrDefault(hadronIdUrl, HADRON_JS_URL, {}), `partner_id=${partnerId}&_it=prebid` ); - loadExternalScript(scriptUrl, SUBMODULE_NAME, () => { + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, () => { logInfo(LOG_PREFIX, 'hadronId JS snippet loaded', scriptUrl); }) } From a85540092efb8db5edd4df0a8b373e47054571db Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Fri, 7 Feb 2025 11:00:06 +0000 Subject: [PATCH 0887/1097] Nodals AI RTD Module : initial release (#12649) * Nodals AI RTD Module, implementation and tests * Move iteration over ad unit array to external library * Change production endpoint hostname * Add tests for override properties * Lint fixes * Updated endpoint response payload * Fix typo * Adjustment made to deps structure * Linting fix * Lint fix * Added missing documentation file * Change case of object reference * Updated variable names * Pass user consent object to library * Fix lint errors * Use Prebid utility to set page URL fact * Remove unrequired comment * Add the module to the allow list to request external Javascript * External script loading disclosure. * Remove unrequired log statement * Updated disclosure message * Linting --------- Co-authored-by: slimkrazy --- modules/nodalsAiRtdProvider.js | 305 ++++++++++ modules/nodalsAiRtdProvider.md | 58 ++ src/adloader.js | 1 + test/spec/modules/nodalsAiRtdProvider_spec.js | 555 ++++++++++++++++++ 4 files changed, 919 insertions(+) create mode 100644 modules/nodalsAiRtdProvider.js create mode 100644 modules/nodalsAiRtdProvider.md create mode 100644 test/spec/modules/nodalsAiRtdProvider_spec.js diff --git a/modules/nodalsAiRtdProvider.js b/modules/nodalsAiRtdProvider.js new file mode 100644 index 00000000000..db4c72f1419 --- /dev/null +++ b/modules/nodalsAiRtdProvider.js @@ -0,0 +1,305 @@ +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { prefixLog } from '../src/utils.js'; + +const MODULE_NAME = 'nodalsAi'; +const GVLID = 1360; +const PUB_ENDPOINT_ORIGIN = 'https://nodals.io'; +const LOCAL_STORAGE_KEY = 'signals.nodals.ai'; +const STORAGE_TTL = 3600; // 1 hour in seconds + +const fillTemplate = (strings, ...keys) => { + return function (values) { + return strings.reduce((result, str, i) => { + const key = keys[i - 1]; + return result + (key ? values[key] || '' : '') + str; + }); + }; +}; + +const PUB_ENDPOINT_PATH = fillTemplate`/p/v1/${'propertyId'}/config?${'consentParams'}`; +const { logInfo, logWarn, logError } = prefixLog('[NodalsAiRTDProvider]'); + +class NodalsAiRtdProvider { + // Public properties + name = MODULE_NAME; + gvlid = GVLID; + + // Exposed for testing + storage = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: MODULE_NAME, + }); + + STORAGE_KEY = LOCAL_STORAGE_KEY; + + // Private properties + #propertyId = null; + #overrides = {}; + + // Public methods + + /** + * Initialises the class with the provided config and user consent. + * @param {Object} config - Configuration object for the module. + * @param {Object} userConsent - User consent object for GDPR or other purposes. + */ + init(config, userConsent) { + const params = config?.params || {}; + if ( + this.#isValidConfig(params) && + this.#hasRequiredUserConsent(userConsent) + ) { + this.#propertyId = params.propertyId; + this.#setOverrides(params); + const storedData = this.#readFromStorage( + this.#overrides?.storageKey || this.STORAGE_KEY + ); + if (storedData === null) { + this.#fetchRules(userConsent); + } else { + this.#loadAdLibraries(storedData.deps || []); + } + return true; + } else { + logWarn('Invalid configuration or missing user consent.'); + return false; + } + } + + /** + * Retrieves targeting data by fetching and processing signals. + * @param {Array} adUnitArray - Array of ad units. + * @param {Object} config - Configuration object. + * @param {Object} userConsent - User consent object. + * @returns {Object} - Targeting data. + */ + getTargetingData(adUnitArray, config, userConsent) { + let targetingData = {}; + if (!this.#hasRequiredUserConsent(userConsent)) { + return targetingData; + } + const storedData = this.#readFromStorage( + this.#overrides?.storageKey || this.STORAGE_KEY + ); + if (storedData === null) { + return targetingData; + } + const facts = Object.assign({}, storedData?.facts ?? {}); + facts['page.url'] = getRefererInfo().page; + const targetingEngine = window?.$nodals?.adTargetingEngine['latest']; + try { + targetingEngine.init(config, facts); + targetingData = targetingEngine.getTargetingData( + adUnitArray, + storedData, + userConsent + ); + } catch (error) { + logError(`Error determining targeting keys: ${error}`); + } + return targetingData; + } + + // Private methods + #setOverrides(params) { + if (params?.storage?.ttl && typeof params.storage.ttl === 'number') { + this.#overrides.storageTTL = params.storage.ttl; + } + this.#overrides.storageKey = params?.storage?.key; + this.#overrides.endpointOrigin = params?.endpoint?.origin; + } + + /** + * Validates if the provided module input parameters are valid. + * @param {Object} params - Parameters object from the module configuration. + * @returns {boolean} - True if parameters are valid, false otherwise. + */ + // eslint-disable-next-line no-dupe-class-members + #isValidConfig(params) { + // Basic validation logic + if (typeof params === 'object' && params?.propertyId) { + return true; + } + logWarn('Invalid configuration'); + return false; + } + + /** + * Checks if the user has provided the required consent. + * @param {Object} userConsent - User consent object. + * @returns {boolean} - True if the user consent is valid, false otherwise. + */ + // eslint-disable-next-line no-dupe-class-members + #hasRequiredUserConsent(userConsent) { + if (userConsent?.gdpr?.gdprApplies !== true) { + return true; + } + if ( + userConsent?.gdpr?.vendorData?.vendor?.consents?.[this.gvlid] === false + ) { + return false; + } else if (userConsent?.gdpr?.vendorData?.purpose?.consents[1] === false) { + return false; + } + return true; + } + + /** + * @param {string} key - The key of the data to retrieve. + * @returns {string|null} - The data from localStorage, or null if not found. + */ + // eslint-disable-next-line no-dupe-class-members + #readFromStorage(key) { + if ( + this.storage.hasLocalStorage() && + this.storage.localStorageIsEnabled() + ) { + try { + const entry = this.storage.getDataFromLocalStorage(key); + if (!entry) { + return null; + } + const dataEnvelope = JSON.parse(entry); + if (this.#dataIsStale(dataEnvelope)) { + this.storage.removeDataFromLocalStorage(key); + return null; + } + if (!dataEnvelope.data) { + throw new Error('Data envelope is missing \'data\' property.'); + } + return dataEnvelope.data; + } catch (error) { + logError(`Corrupted data in local storage: ${error}`); + return null; + } + } else { + logError('Local storage is not available or not enabled.'); + return null; + } + } + + /** + * Writes data to localStorage. + * @param {string} key - The key under which to store the data. + * @param {Object} data - The data to store. + */ + // eslint-disable-next-line no-dupe-class-members + #writeToStorage(key, data) { + if ( + this.storage.hasLocalStorage() && + this.storage.localStorageIsEnabled() + ) { + const storageObject = { + createdAt: Date.now(), + data, + }; + this.storage.setDataInLocalStorage(key, JSON.stringify(storageObject)); + } else { + logError('Local storage is not available or not enabled.'); + } + } + + /** + * Checks if the provided data is stale. + * @param {Object} dataEnvelope - The data envelope object. + * @returns {boolean} - True if the data is stale, false otherwise. + */ + // eslint-disable-next-line no-dupe-class-members + #dataIsStale(dataEnvelope) { + const currentTime = Date.now(); + const dataTime = dataEnvelope.createdAt || 0; + const staleThreshold = this.#overrides?.storageTTL ?? dataEnvelope?.data?.meta?.ttl ?? STORAGE_TTL; + return currentTime - dataTime >= (staleThreshold * 1000); + } + + // eslint-disable-next-line no-dupe-class-members + #getEndpointUrl(userConsent) { + const endpointOrigin = + this.#overrides.endpointOrigin || PUB_ENDPOINT_ORIGIN; + const parameterMap = { + gdpr_consent: userConsent?.gdpr?.consentString ?? '', + gdpr: userConsent?.gdpr?.gdprApplies ? '1' : '0', + us_privacy: userConsent?.uspConsent ?? '', + gpp: userConsent?.gpp?.gppString ?? '', + gpp_sid: + userConsent.gpp && Array.isArray(userConsent.gpp.applicableSections) + ? userConsent.gpp.applicableSections.join(',') + : '', + }; + const querystring = new URLSearchParams(parameterMap).toString(); + const values = { + propertyId: this.#propertyId, + consentParams: querystring, + }; + const path = PUB_ENDPOINT_PATH(values); + return `${endpointOrigin}${path}`; + } + + /** + * Initiates the request to fetch rule data from the publisher endpoint. + */ + // eslint-disable-next-line no-dupe-class-members + #fetchRules(userConsent) { + const endpointUrl = this.#getEndpointUrl(userConsent); + + const callback = { + success: (response, req) => { + this.#handleServerResponse(response, req); + }, + error: (error, req) => { + this.#handleServerError(error, req); + }, + }; + + const options = { + method: 'GET', + withCredentials: false, + }; + + logInfo(`Fetching ad rules from: ${endpointUrl}`); + ajax(endpointUrl, callback, null, options); + } + + /** + * Handles the server response, processes it and extracts relevant data. + * @param {Object} response - The server response object. + * @returns {Object} - Processed data from the response. + */ + // eslint-disable-next-line no-dupe-class-members + #handleServerResponse(response, req) { + let data; + try { + data = JSON.parse(response); + } catch (error) { + throw `Error parsing response: ${error}`; + } + this.#writeToStorage(this.#overrides?.storageKey || this.STORAGE_KEY, data); + this.#loadAdLibraries(data.deps || []); + } + + // eslint-disable-next-line no-dupe-class-members + #handleServerError(error, req) { + logError(`Publisher endpoint response error: ${error}`); + } + + // eslint-disable-next-line no-dupe-class-members + #loadAdLibraries(deps) { + // eslint-disable-next-line no-unused-vars + for (const [key, value] of Object.entries(deps)) { + if (typeof value === 'string') { + loadExternalScript(value, MODULE_TYPE_RTD, MODULE_NAME, () => { + // noop + }); + } + } + } +} + +export const nodalsAiRtdSubmodule = new NodalsAiRtdProvider(); + +submodule('realTimeData', nodalsAiRtdSubmodule); diff --git a/modules/nodalsAiRtdProvider.md b/modules/nodalsAiRtdProvider.md new file mode 100644 index 00000000000..78cfe534cef --- /dev/null +++ b/modules/nodalsAiRtdProvider.md @@ -0,0 +1,58 @@ +# Nodals AI Real-Time Data Module + +## Overview + +Module Name: Nodals AI Rtd Provider +Module Type: Rtd Provider +Maintainer: prebid-integrations@nodals.ai + +Nodals AI provides a real-time data prebid module that will analyse first-party signals present on page load, determine the value of them to Nodals’ advertisers and add a key-value to the ad server call to indicate that value. The Nodals AI RTD module loads external code as part of this process. + +In order to be able to utilise this module, please contact [info@nodals.ai](mailto:info@nodals.ai) for account setup and detailed GAM setup instructions. + +## Build + +First, ensure that you include the generic Prebid RTD Module _and_ the Nodals AI RTD module into your Prebid build: + +```bash +gulp build --modules=rtdModule,nodalsAiRtdProvider +``` + +## Configuration + +Update your Prebid configuration to enable the Nodals AI RTD module, as illustrated in the example below: + +```javascript +pbjs.setConfig({ + ..., + realTimeData: { + auctionDelay: 100, // optional auction delay + dataProviders: [{ + name: 'nodalsAi', + waitForIt: true, // should be true only if there's an `auctionDelay` + params: { + propertyId: 'c10516af' // obtain your property id from Nodals AI support + } + }] + }, + ... +}) +``` + +Configuration parameters: + +{: .table .table-bordered .table-striped } + +| Name | Scope | Description | Example | Type | +| --------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------- | +| `name` | required | Real time data module name: Always `'nodalsAi'` | `'nodalsAi'` | `String` | +| `waitForIt` | optional | Set to `true` if there's an `auctionDelay` defined (defaults to `false`) | `false` | `Boolean` | +| `params` | required | Submodule configuration parameters | `{}` | `Object` | +| `params.propertyId` | required | Publisher specific identifier, provided by Nodals AI | `'76346cf3'` | `String` | +| `params.storage` | optional | Optional storage configiration | `{}` | `Object` | +| `params.storage.key` | optional | Storage key used to store Nodals AI data in local storage | `'yourKey'` | `String` | +| `params.storage.ttl` | optional | Time in seconds to retain Nodals AI data in storage until a refresh is required | `900` | `Integer` | +| `params.ptr` | optional | Optional partner configiration | `{}` | `Object` | +| `params.ptr.permutive` | optional | Optional configiration for Permutive Audience Platform | `{}` | `Object` | +| `params.ptr.permutive.cohorts` | optional | A method for the publisher to explicitly supply Permutive Cohort IDs, disabling automatic fetching by this RTD module | `['66711', '39032', '311']` | `Array` | +| `params.ptr.permutive.storageKey` | optional | Publisher specific Permutive storage key where cohort data is held. | `'_psegs'` | `String` | diff --git a/src/adloader.js b/src/adloader.js index 8fad053f7f5..7a24cfc2954 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -36,6 +36,7 @@ const _approvedLoadExternalJSList = [ '51Degrees', 'symitridap', 'wurfl', + 'nodalsAi', // UserId Submodules 'justtag', 'tncId', diff --git a/test/spec/modules/nodalsAiRtdProvider_spec.js b/test/spec/modules/nodalsAiRtdProvider_spec.js new file mode 100644 index 00000000000..b93d48c1f5f --- /dev/null +++ b/test/spec/modules/nodalsAiRtdProvider_spec.js @@ -0,0 +1,555 @@ +import { expect } from 'chai'; +import { MODULE_TYPE_RTD } from 'src/activities/modules.js'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; +import { server } from 'test/mocks/xhr.js'; + +import { nodalsAiRtdSubmodule } from 'modules/nodalsAiRtdProvider.js'; + +const jsonResponseHeaders = { + 'Content-Type': 'application/json', +}; + +const successPubEndpointResponse = { + deps: { + '1.0.0': 'https://static.nodals.io/sdk/rule/1.0.0/engine.js', + '1.1.0': 'https://static.nodals.io/sdk/rule/1.1.0/engine.js', + }, + facts: { + 'browser.name': 'safari', + 'geo.country': 'AR', + }, + campaigns: [ + { + id: 1234, + ads: [ + { + delivery_id: '1234', + property_id: 'fd32da', + weighting: 1, + kvs: [ + { + k: 'nodals', + v: '1', + }, + ], + rules: { + engine: { + version: '1.0.0', + }, + conditions: { + ANY: [ + { + fact: 'id', + op: 'allin', + val: ['1', '2', '3'], + }, + ], + NONE: [ + { + fact: 'ua.browser', + op: 'eq', + val: 'opera', + }, + ], + }, + }, + }, + ], + }, + ], +}; + +const engineGetTargetingDataReturnValue = { + adUnit1: { + adv1: 'foobarbaz', + }, +}; + +const generateGdprConsent = (consent = {}) => { + const defaults = { + gdprApplies: true, + purpose1Consent: true, + nodalsConsent: true, + }; + const mergedConsent = Object.assign({}, defaults, consent); + return { + gdpr: { + gdprApplies: mergedConsent.gdprApplies, + consentString: mergedConsent.consentString, + vendorData: { + purpose: { + consents: { + 1: mergedConsent.purpose1Consent, + 3: true, + 4: true, + 5: true, + 6: true, + 9: true, + }, + }, + specialFeatureOptins: { + 1: true, + }, + vendor: { + consents: { + 1360: mergedConsent.nodalsConsent, + }, + }, + }, + }, + }; +}; + +const setDataInLocalStorage = (data) => { + const storageData = { ...data }; + nodalsAiRtdSubmodule.storage.setDataInLocalStorage( + nodalsAiRtdSubmodule.STORAGE_KEY, + JSON.stringify(storageData) + ); +}; + +describe('NodalsAI RTD Provider', () => { + let sandbox; + let validConfig; + + beforeEach(() => { + validConfig = { params: { propertyId: '10312dd2' } }; + + sandbox = sinon.sandbox.create(); + nodalsAiRtdSubmodule.storage.removeDataFromLocalStorage( + nodalsAiRtdSubmodule.STORAGE_KEY + ); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('Module properties', () => { + it('should have the name property set correctly', function () { + expect(nodalsAiRtdSubmodule.name).equals('nodalsAi'); + }); + + it('should expose the correct TCF Global Vendor ID', function () { + expect(nodalsAiRtdSubmodule.gvlid).equals(1360); + }); + }); + + describe('init()', () => { + describe('when initialised with empty consent data', () => { + const userConsent = {}; + + it('should return true when initialized with valid config and empty user consent', function () { + const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); + + it('should return false when initialized with invalid config', () => { + const config = { params: { invalid: true } }; + const result = nodalsAiRtdSubmodule.init(config, userConsent); + expect(result).to.be.false; + expect(server.requests.length).to.equal(0); + }); + }); + + describe('when initialised with valid config data', () => { + it('should return false when user is under GDPR jurisdiction and purpose1 has not been granted', () => { + const userConsent = generateGdprConsent({ purpose1Consent: false }); + const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + expect(result).to.be.false; + }); + + it('should return false when user is under GDPR jurisdiction and Nodals AI as a vendor has no consent', () => { + const userConsent = generateGdprConsent({ nodalsConsent: false }); + const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + expect(result).to.be.false; + }); + + it('should return true when user is under GDPR jurisdiction and all consent provided', function () { + const userConsent = generateGdprConsent(); + const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + expect(result).to.be.true; + }); + + it('should return true when user is not under GDPR jurisdiction', () => { + const userConsent = generateGdprConsent({ gdprApplies: false }); + const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + expect(result).to.be.true; + }); + }); + + describe('when initialised with valid config and data already in storage', () => { + it('should return true and not make a remote request when stored data is valid', function () { + setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: Date.now() }); + const result = nodalsAiRtdSubmodule.init(validConfig, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(0); + }); + + it('should return true and make a remote request when stored data has no TTL defined', function () { + setDataInLocalStorage({ data: { foo: 'bar' } }); + const result = nodalsAiRtdSubmodule.init(validConfig, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); + + it('should return true and make a remote request when stored data has expired', function () { + setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: 100 }); + const result = nodalsAiRtdSubmodule.init(validConfig, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); + + it('should detect stale data if override TTL is exceeded', function () { + const fiveMinutesAgoMs = Date.now() - (5 * 60 * 1000); + setDataInLocalStorage({ + data: { foo: 'bar' }, + createdAt: fiveMinutesAgoMs, + }); + const config = Object.assign({}, validConfig); + config.params.storage = { ttl: 4 * 60 }; + const result = nodalsAiRtdSubmodule.init(config, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); + + it('should detect stale data if remote defined TTL is exceeded', function () { + const fiveMinutesAgoMs = Date.now() - (5 * 60 * 1000); + setDataInLocalStorage({ + data: { foo: 'bar', meta: { ttl: 4 * 60 } }, + createdAt: fiveMinutesAgoMs, + }); + const result = nodalsAiRtdSubmodule.init(validConfig, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); + + it('should respect pub defined TTL over remote defined TTL', function () { + const fiveMinutesAgoMs = Date.now() - (5 * 60 * 1000); + setDataInLocalStorage({ + data: { foo: 'bar', meta: { ttl: 4 * 60 } }, + createdAt: fiveMinutesAgoMs, + }); + const config = Object.assign({}, validConfig); + config.params.storage = { ttl: 6 * 60 }; + const result = nodalsAiRtdSubmodule.init(config, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(0); + }); + + it('should NOT detect stale data if override TTL is not exceeded', function () { + const fiveMinutesAgoMs = Date.now() - 5 * 60 * 1000; + setDataInLocalStorage({ + data: { foo: 'bar' }, + createdAt: fiveMinutesAgoMs, + }); + const config = Object.assign({}, validConfig); + config.params.storage = { ttl: 6 * 60 }; + const result = nodalsAiRtdSubmodule.init(config, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(0); + }); + + it('should return true and make a remote request when data stored under default key, but override key specified', () => { + setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: Date.now() }); + const config = Object.assign({}, validConfig); + config.params.storage = { key: '_foobarbaz_' }; + const result = nodalsAiRtdSubmodule.init(config, {}); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); + }); + }); + + describe('when performing requests to the publisher endpoint', () => { + it('should construct the correct URL to the default origin', () => { + const userConsent = generateGdprConsent(); + nodalsAiRtdSubmodule.init(validConfig, userConsent); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.withCredentials).to.be.false; + const requestUrl = new URL(request.url); + expect(requestUrl.origin).to.equal('https://nodals.io'); + }); + + it('should construct the URL to the overridden origin when specified in the config', () => { + const config = Object.assign({}, validConfig); + config.params.endpoint = { origin: 'http://localhost:8000' }; + const userConsent = generateGdprConsent(); + nodalsAiRtdSubmodule.init(config, userConsent); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.withCredentials).to.be.false; + const requestUrl = new URL(request.url); + expect(requestUrl.origin).to.equal('http://localhost:8000'); + }); + + it('should construct the correct URL with the correct path', () => { + const userConsent = generateGdprConsent(); + nodalsAiRtdSubmodule.init(validConfig, userConsent); + + let request = server.requests[0]; + const requestUrl = new URL(request.url); + expect(requestUrl.pathname).to.equal('/p/v1/10312dd2/config'); + }); + + it('should construct the correct URL with the correct GDPR query params', () => { + const consentData = { + consentString: 'foobarbaz', + }; + const userConsent = generateGdprConsent(consentData); + nodalsAiRtdSubmodule.init(validConfig, userConsent); + + let request = server.requests[0]; + const requestUrl = new URL(request.url); + expect(requestUrl.searchParams.get('gdpr')).to.equal('1'); + expect(requestUrl.searchParams.get('gdpr_consent')).to.equal( + 'foobarbaz' + ); + expect(requestUrl.searchParams.get('us_privacy')).to.equal(''); + expect(requestUrl.searchParams.get('gpp')).to.equal(''); + expect(requestUrl.searchParams.get('gpp_sid')).to.equal(''); + }); + }); + + describe('when handling responses from the publisher endpoint', () => { + it('should store successful response data in local storage', () => { + const userConsent = generateGdprConsent(); + nodalsAiRtdSubmodule.init(validConfig, userConsent); + + let request = server.requests[0]; + request.respond( + 200, + jsonResponseHeaders, + JSON.stringify(successPubEndpointResponse) + ); + + const storedData = JSON.parse( + nodalsAiRtdSubmodule.storage.getDataFromLocalStorage( + nodalsAiRtdSubmodule.STORAGE_KEY + ) + ); + expect(request.method).to.equal('GET'); + expect(storedData).to.have.property('createdAt'); + expect(storedData.data).to.deep.equal(successPubEndpointResponse); + }); + + it('should store successful response data in local storage under the override key', () => { + const userConsent = generateGdprConsent(); + const config = Object.assign({}, validConfig); + config.params.storage = { key: '_foobarbaz_' }; + nodalsAiRtdSubmodule.init(config, userConsent); + + let request = server.requests[0]; + request.respond( + 200, + jsonResponseHeaders, + JSON.stringify(successPubEndpointResponse) + ); + + const storedData = JSON.parse( + nodalsAiRtdSubmodule.storage.getDataFromLocalStorage('_foobarbaz_') + ); + expect(request.method).to.equal('GET'); + expect(storedData).to.have.property('createdAt'); + expect(storedData.data).to.deep.equal(successPubEndpointResponse); + }); + + it('should attempt to load the referenced script libraries contained in the response payload', () => { + const userConsent = generateGdprConsent(); + nodalsAiRtdSubmodule.init(validConfig, userConsent); + + let request = server.requests[0]; + request.respond( + 200, + jsonResponseHeaders, + JSON.stringify(successPubEndpointResponse) + ); + + expect(loadExternalScriptStub.calledTwice).to.be.true; + expect( + loadExternalScriptStub.calledWith( + successPubEndpointResponse.deps['1.0.0'], + MODULE_TYPE_RTD, + nodalsAiRtdSubmodule.name + ) + ).to.be.true; + expect( + loadExternalScriptStub.calledWith( + successPubEndpointResponse.deps['1.1.0'], + MODULE_TYPE_RTD, + nodalsAiRtdSubmodule.name + ) + ).to.be.true; + }); + }); + }); + + describe('getTargetingData()', () => { + afterEach(() => { + if (window.$nodals) { + delete window.$nodals; + } + }); + + const stubVersionedTargetingEngine = (returnValue, raiseError = false) => { + const version = 'latest'; + const initStub = sinon.stub(); + const getTargetingDataStub = sinon.stub(); + if (raiseError) { + getTargetingDataStub.throws(new Error('Stubbed error')); + } else { + getTargetingDataStub.returns(returnValue); + } + window.$nodals = window.$nodals || {}; + window.$nodals.adTargetingEngine = window.$nodals.adTargetingEngine || {}; + window.$nodals.adTargetingEngine[version] = { + init: initStub, + getTargetingData: getTargetingDataStub, + }; + return window.$nodals.adTargetingEngine[version]; + }; + + it('should return an empty object when no data is available in local storage', () => { + const result = nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + validConfig, + {} + ); + expect(result).to.deep.equal({}); + }); + + it('should return an empty object when getTargetingData throws error', () => { + stubVersionedTargetingEngine({}, true); // TODO: Change the data + const userConsent = generateGdprConsent(); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const result = nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + validConfig, + userConsent + ); + expect(result).to.deep.equal({}); + }); + + it('should initialise the versioned targeting engine', () => { + const returnData = {}; + const engine = stubVersionedTargetingEngine(returnData); + const userConsent = generateGdprConsent(); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + validConfig, + userConsent + ); + + expect(engine.init.called).to.be.true; + const args = engine.init.getCall(0).args; + expect(args[0]).to.deep.equal(validConfig); + expect(args[1]).to.deep.include(successPubEndpointResponse.facts); + }); + + it('should proxy the correct data to engine.init()', () => { + const engine = stubVersionedTargetingEngine( + engineGetTargetingDataReturnValue + ); + const userConsent = generateGdprConsent(); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1', 'adUnit2'], + validConfig, + userConsent + ); + + expect(engine.init.called).to.be.true; + const args = engine.init.getCall(0).args; + expect(args[0]).to.deep.equal(validConfig); + expect(args[1]).to.be.an('object').with.keys(['browser.name', 'geo.country', 'page.url']); + }); + + it('should proxy the correct data to engine.getTargetingData()', () => { + const engine = stubVersionedTargetingEngine( + engineGetTargetingDataReturnValue + ); + const userConsent = generateGdprConsent(); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1', 'adUnit2'], + validConfig, + userConsent + ); + + expect(engine.getTargetingData.called).to.be.true; + const args = engine.getTargetingData.getCall(0).args; + expect(args[0]).to.deep.equal(['adUnit1', 'adUnit2']); + expect(args[1]).to.deep.include(successPubEndpointResponse); + expect(args[2]).to.deep.equal(userConsent); + }); + + it('should return the response from engine.getTargetingData when data is available and we have consent under GDPR jurisdiction', () => { + stubVersionedTargetingEngine(engineGetTargetingDataReturnValue); + const userConsent = generateGdprConsent(); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + const result = nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + validConfig, + userConsent + ); + + expect(result).to.deep.equal(engineGetTargetingDataReturnValue); + }); + + it('should return the response from engine.getTargetingData when data is available and we are NOT under GDPR jurisdiction', () => { + stubVersionedTargetingEngine(engineGetTargetingDataReturnValue); + const userConsent = generateGdprConsent({ gdprApplies: false }); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + const result = nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + validConfig, + userConsent + ); + + expect(result).to.deep.equal(engineGetTargetingDataReturnValue); + }); + + it('should return an empty object when data is available, but user has not provided consent to Nodals AI as a vendor', () => { + stubVersionedTargetingEngine(engineGetTargetingDataReturnValue); + const userConsent = generateGdprConsent({ nodalsConsent: false }); + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + + const result = nodalsAiRtdSubmodule.getTargetingData( + ['adUnit1'], + validConfig, + userConsent + ); + + expect(result).to.deep.equal({}); + }); + }); +}); From 2060abc994ce093b1dcdca1a9fa980bd53a7a0f2 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 7 Feb 2025 16:11:30 +0000 Subject: [PATCH 0888/1097] Prebid 9.30.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ee34d1b924..f73858437d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.30.0-pre", + "version": "9.30.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.30.0-pre", + "version": "9.30.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index b41e9852f20..a2e324d5f29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.30.0-pre", + "version": "9.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 388b31fe7353cb291b9449307531d78f039ed95a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 7 Feb 2025 16:11:31 +0000 Subject: [PATCH 0889/1097] Increment version to 9.31.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f73858437d9..50ce5492989 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.30.0", + "version": "9.31.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.30.0", + "version": "9.31.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index a2e324d5f29..a9b0d82a6aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.30.0", + "version": "9.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From f298144593650ba658e8ad6690f8e5b09c0a2f71 Mon Sep 17 00:00:00 2001 From: jansima-r2b2 Date: Sat, 8 Feb 2025 16:41:07 +0100 Subject: [PATCH 0890/1097] R2B2 Analytic Adapter : initial release (#12555) * r2b2 analytic adapter * fix import * fix lint errors * cache ttl, call depth, options warnings * callDepth doc comment * lint fix --------- Co-authored-by: jenda --- modules/r2b2AnalyticsAdapter.js | 627 ++++++++++ modules/r2b2AnalyticsAdapter.md | 32 + .../spec/modules/r2b2AnalytiscAdapter_spec.js | 1009 +++++++++++++++++ 3 files changed, 1668 insertions(+) create mode 100644 modules/r2b2AnalyticsAdapter.js create mode 100644 modules/r2b2AnalyticsAdapter.md create mode 100644 test/spec/modules/r2b2AnalytiscAdapter_spec.js diff --git a/modules/r2b2AnalyticsAdapter.js b/modules/r2b2AnalyticsAdapter.js new file mode 100644 index 00000000000..aa909225c4d --- /dev/null +++ b/modules/r2b2AnalyticsAdapter.js @@ -0,0 +1,627 @@ +import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {EVENTS} from '../src/constants.js'; +import adapterManager from '../src/adapterManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {isNumber, isPlainObject, isStr, logError, logWarn} from '../src/utils.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {config} from '../src/config.js'; + +const ADAPTER_VERSION = '1.1.0'; +const ADAPTER_CODE = 'r2b2'; +const MODULE_NAME = 'R2B2 Analytics' +const GVLID = 1235; +const analyticsType = 'endpoint'; + +const DEFAULT_SERVER = 'log.r2b2.cz'; +const DEFAULT_EVENT_PATH = 'prebid/events'; +const DEFAULT_ERROR_PATH = 'error'; +const DEFAULT_PROTOCOL = 'https'; + +const ERROR_MAX = 10; +const BATCH_SIZE = 50; +const BATCH_DELAY = 500; +const MAX_CALL_DEPTH = 20; +const REPORTED_URL = getRefererInfo().page || getRefererInfo().topmostLocation || ''; + +const START_TIME = Date.now(); + +const CACHE_TTL = 300 * 1000; + +const EVENT_MAP = {}; +EVENT_MAP[EVENTS.NO_BID] = 'noBid'; +EVENT_MAP[EVENTS.AUCTION_INIT] = 'init'; +EVENT_MAP[EVENTS.BID_REQUESTED] = 'request'; +EVENT_MAP[EVENTS.BID_TIMEOUT] = 'timeout'; +EVENT_MAP[EVENTS.BID_RESPONSE] = 'response'; +EVENT_MAP[EVENTS.BID_REJECTED] = 'reject'; +EVENT_MAP[EVENTS.BIDDER_ERROR] = 'bidError'; +EVENT_MAP[EVENTS.BIDDER_DONE] = 'bidderDone'; +EVENT_MAP[EVENTS.AUCTION_END] = 'auction'; +EVENT_MAP[EVENTS.BID_WON] = 'bidWon'; +EVENT_MAP[EVENTS.SET_TARGETING] = 'targeting'; +EVENT_MAP[EVENTS.STALE_RENDER] = 'staleRender'; +EVENT_MAP[EVENTS.AD_RENDER_SUCCEEDED] = 'render'; +EVENT_MAP[EVENTS.AD_RENDER_FAILED] = 'renderFail'; +EVENT_MAP[EVENTS.BID_VIEWABLE] = 'view'; + +/* CONFIGURATION */ +let WEBSITE = 0; +let CONFIG_ID = 0; +let CONFIG_VERSION = 0; +let LOG_SERVER = DEFAULT_SERVER; + +/* CACHED DATA */ +let latestAuction = ''; +let previousAuction = ''; +let auctionCount = 0; +let auctionsData = {}; +let bidsData = {}; +let adServerCurrency = ''; + +let flushTimer; +let eventBuffer = []; +let errors = 0; + +let callDepth = 0; +function flushEvents () { + let events = { prebid: { e: eventBuffer, c: adServerCurrency } }; + eventBuffer = []; + callDepth++; + try { + // check for recursion in case reportEvents propagates error events + // and execution doesn't finish before BATCH_SIZE is reached again + if (callDepth >= MAX_CALL_DEPTH) { + if (callDepth === MAX_CALL_DEPTH) { + logError(`${MODULE_NAME}: Maximum call depth reached, discarding events`); + } + return; + } + // clear out old data only in state without recursion + if (callDepth === 1) { + clearCache(bidsData); + clearCache(auctionsData); + } + + reportEvents(events); + } finally { + callDepth--; + } +} + +function clearCache(cache) { + const now = Date.now(); + for (const [key, { t }] of Object.entries(cache)) { + if ((t + CACHE_TTL) < now) { + delete cache[key]; + } + } +} + +export function resetAnalyticAdapter() { + latestAuction = ''; + previousAuction = ''; + auctionCount = 0; + auctionsData = {}; + bidsData = {}; + adServerCurrency = ''; + clearTimeout(flushTimer); + eventBuffer = []; + errors = 0; + callDepth = 0; + + WEBSITE = 0; + CONFIG_ID = 0; + CONFIG_VERSION = 0; + LOG_SERVER = DEFAULT_SERVER; +} +function processEvent (event) { + // console.log('process event:', event); + // console.log(JSON.stringify(event)); + if (!event) { + return + } + eventBuffer.push(event); + if (flushTimer) { + clearTimeout(flushTimer); + flushTimer = null + } + if (eventBuffer.length >= BATCH_SIZE) { + flushEvents(); + } else { + flushTimer = setTimeout(flushEvents, BATCH_DELAY); + } +} + +function processErrorParams(params) { + if (isPlainObject(params)) { + try { + return JSON.stringify(params); + } catch (e) { /* do nothing */ } + } + return null +} +function reportError (message, params) { + errors++; + if (errors > ERROR_MAX) return; + params = processErrorParams(params); + message = `[ANALYTICS-${ADAPTER_VERSION}] ${message}`; + const url = r2b2Analytics.getErrorUrl() + + `?d=${encodeURIComponent(WEBSITE)}` + + `&m=${encodeURIComponent(message)}` + + `&t=prebid` + + `&p=1` + + (params ? `&pr=${encodeURIComponent(params)}` : '') + + (CONFIG_ID ? `&conf=${encodeURIComponent(CONFIG_ID)}` : '') + + (CONFIG_VERSION ? `&conf_ver=${encodeURIComponent(CONFIG_VERSION)}` : '') + + `&u=${encodeURIComponent(REPORTED_URL)}`; + ajax(url, null, null, {}); +} +function reportEvents (events) { + try { + let data = 'events=' + JSON.stringify(events); + let url = r2b2Analytics.getUrl() + + `?v=${encodeURIComponent(ADAPTER_VERSION)}` + + `&hbDomain=${encodeURIComponent(WEBSITE)}` + + (CONFIG_ID ? `&conf=${encodeURIComponent(CONFIG_ID)}` : '') + + (CONFIG_VERSION ? `&conf_ver=${encodeURIComponent(CONFIG_VERSION)}` : '') + + `&u=${encodeURIComponent(REPORTED_URL)}`; + let headers = { + contentType: 'application/x-www-form-urlencoded' + } + data = data.replace(/&/g, '%26'); + ajax(url, null, data, headers); + } catch (e) { + const msg = `Error sending events - ${e.message}`; + logError(`${MODULE_NAME}: ${msg}`); + reportError(msg); + } +} + +function getStandardTargeting (obj) { + if (obj) { + return { + b: obj.hb_bidder || '', + sz: obj.hb_size || '', + pb: obj.hb_pb || '', + fmt: obj.hb_format || '' + } + } +} +function getEventTimestamps (eventName, auctionId) { + const timestamps = { + t: Date.now() - START_TIME + }; + if (!auctionId || !auctionsData[auctionId]) { + return timestamps + } + const auctionData = auctionsData[auctionId]; + + timestamps.to = auctionData.timeout; + timestamps.ts = auctionData.start - START_TIME; + if (auctionData.end) { + timestamps.te = auctionData.end - START_TIME; + } + if (eventName === EVENT_MAP[EVENTS.AUCTION_INIT] && auctionCount > 1) { + timestamps.tprev = auctionsData[previousAuction].start - START_TIME; + } + return timestamps +} + +function createEvent (name, data, auctionId) { + if (!auctionId || !auctionsData[auctionId]) { + reportError('No auction data when creating event', { + event: name, + auctionId: !!auctionId + }); + return null + } + if (auctionsData[auctionId] && auctionsData[auctionId].empty) { + return null + } + + data = data || {}; + data.ai = auctionId; + + return { + e: name, + d: data, + t: getEventTimestamps(name, auctionId) + } +} + +function createAuctionData (auction, empty) { + const auctionId = auction.auctionId; + previousAuction = latestAuction; + latestAuction = auctionId; + auctionCount++; + auctionsData[auctionId] = { + start: auction.timestamp, + end: auction.auctionEnd ? auction.auctionEnd : null, + timeout: auction.timeout, + empty: !!empty, + t: Date.now(), + }; +} +function handleAuctionInit (args) { + // console.log('auction init:', arguments); + createAuctionData(args); + const auctionId = args.auctionId; + const bidderRequests = args.bidderRequests || []; + const data = { + o: auctionCount, + u: bidderRequests.reduce((result, bidderRequest) => { + bidderRequest.bids.forEach((bid) => { + if (!result[bid.adUnitCode]) { + result[bid.adUnitCode] = [] + } + result[bid.adUnitCode].push(bid.bidder) + }); + return result + }, {}) + }; + const event = createEvent(EVENT_MAP[EVENTS.AUCTION_INIT], data, auctionId); + processEvent(event); +} +function handleBidRequested (args) { + // console.log('bid request:', arguments); + const data = { + b: args.bidderCode, + u: args.bids.reduce((result, bid) => { + if (!result[bid.adUnitCode]) { + result[bid.adUnitCode] = 1 + } else { + result[bid.adUnitCode]++ + } + return result + }, {}) + }; + const event = createEvent(EVENT_MAP[EVENTS.BID_REQUESTED], data, args.auctionId); + processEvent(event); +} +function handleBidTimeout (args) { + // console.log('bid timeout:', arguments); + const auctionId = args.length ? args[0].auctionId : null; + if (auctionId) { + let bidders = args.reduce((result, bid) => { + if (!result[bid.bidder]) { + result[bid.bidder] = {} + } + const bidderData = result[bid.bidder]; + if (!bidderData[bid.adUnitCode]) { + bidderData[bid.adUnitCode] = 1 + } else { + bidderData[bid.adUnitCode]++ + } + return result + }, {}); + + const data = { + b: bidders, + } + const event = createEvent(EVENT_MAP[EVENTS.BID_TIMEOUT], data, auctionId); + processEvent(event); + } +} +function handleNoBid (args) { + // console.log('no bid:', arguments); + const data = { + b: args.bidder, + u: args.adUnitCode + }; + const event = createEvent(EVENT_MAP[EVENTS.NO_BID], data, args.auctionId); + processEvent(event); +} +function handleBidResponse (args) { + // console.log('bid response:', arguments); + bidsData[args.adId] = { + id: args.requestId, + auctionId: args.auctionId, + t: Date.now(), + }; + const data = { + b: args.bidder, + u: args.adUnitCode, + p: args.cpm, + op: args.originalCpm, + c: args.currency, + oc: args.originalCurrency, + sz: args.size, + st: args.status, + rt: args.timeToRespond, + bi: args.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.BID_RESPONSE], data, args.auctionId); + processEvent(event); +} +function handleBidRejected (args) { + // console.log('bid rejected:', arguments); + const data = { + b: args.bidder, + u: args.adUnitCode, + p: args.cpm, + c: args.currency, + r: args.rejectionReason, + bi: args.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.BID_REJECTED], data, args.auctionId); + processEvent(event); +} +function handleBidderDone (args) { + // console.log('bidder done:', arguments); + const data = { + b: args.bidderCode + }; + const event = createEvent(EVENT_MAP[EVENTS.BIDDER_DONE], data, args.auctionId); + processEvent(event); +} +function getAuctionUnitsData (auctionObject) { + let unitsData = {}; + const {bidsReceived, bidsRejected} = auctionObject; + let _unitsDataBidReducer = function(data, bid, key) { + const {adUnitCode, bidder} = bid; + data[adUnitCode] = data[adUnitCode] || {}; + data[adUnitCode][key] = data[adUnitCode][key] || {}; + data[adUnitCode][key][bidder] = (data[adUnitCode][key][bidder] || 0) + 1; + return data + }; + unitsData = bidsReceived.reduce((data, bid) => { + if (!bid.cpm) return data; + return _unitsDataBidReducer(data, bid, 'b') + }, unitsData); + unitsData = bidsRejected.reduce((data, bid) => { + return _unitsDataBidReducer(data, bid, 'rj') + }, unitsData); + return unitsData +} +function handleEmptyAuction(auction) { + let auctionId = auction.auctionId; + if (!auctionsData[auctionId]) { + createAuctionData(auction, true); + } +} +function handleAuctionEnd (args) { + // console.log('auction end:', arguments); + if (!args.bidderRequests.length) { + handleEmptyAuction(args); + return + } + auctionsData[args.auctionId].end = args.auctionEnd; + let winningBids = getGlobal().getHighestCpmBids() || []; + if (winningBids.length === 0) { + winningBids = getGlobal().getAllWinningBids() || []; + } + const wins = []; + winningBids.forEach((bid) => { + if (bid.auctionId === args.auctionId) { + wins.push({ + b: bid.bidder, + u: bid.adUnitCode, + p: bid.cpm, + c: bid.currency, + sz: bid.size, + bi: bid.requestId, + }) + } + }); + const data = { + wins, + u: getAuctionUnitsData(args), + o: auctionCount, + bc: args.bidsReceived.length, + nbc: args.noBids.length, + rjc: args.bidsRejected.length, + brc: args.bidderRequests.reduce((count, bidderRequest) => { + const c = bidderRequest.bids.length || 0; + return count + c + }, 0) + }; + const event = createEvent(EVENT_MAP[EVENTS.AUCTION_END], data, args.auctionId); + processEvent(event); +} +function handleBidWon (args) { + // console.log('bid won:', arguments); + const data = { + b: args.bidder, + u: args.adUnitCode, + p: args.cpm, + op: args.originalCpm, + c: args.currency, + oc: args.originalCurrency, + sz: args.size, + mt: args.mediaType, + at: getStandardTargeting(args.adserverTargeting), + o: auctionCount, + bi: args.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.BID_WON], data, args.auctionId); + processEvent(event); +} +function handleSetTargeting (args) { + // console.log('set targeting:', arguments); + let adId; + const filteredTargetings = {}; + Object.keys(args).forEach((unit) => { + if (Object.keys(args[unit]).length) { + if (!adId) { + adId = args[unit].hb_adid + } + filteredTargetings[unit] = getStandardTargeting(args[unit]); + } + }); + if (adId) { + const auctionId = bidsData[adId].auctionId; + const data = { + u: filteredTargetings + } + const event = createEvent(EVENT_MAP[EVENTS.SET_TARGETING], data, auctionId); + processEvent(event); + } +} +function handleStaleRender (args) { + // console.log('stale render:', arguments); + const data = { + b: args.bidder, + u: args.adUnitCode, + p: args.cpm, + c: args.currency, + bi: args.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.STALE_RENDER], data, args.auctionId); + processEvent(event); +} +function handleRenderSuccess (args) { + // console.log('render success:', arguments); + const {bid} = args; + bidsData[bid.adId].renderTime = Date.now(); + const data = { + b: bid.bidder, + u: bid.adUnitCode, + p: bid.cpm, + c: bid.currency, + sz: bid.size, + mt: bid.mediaType, + bi: bid.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.AD_RENDER_SUCCEEDED], data, bid.auctionId); + processEvent(event); +} +function handleRenderFailed (args) { + // console.log('render failed:', arguments); + const {bid, reason} = args; + const data = { + b: bid.bidder, + u: bid.adUnitCode, + p: bid.cpm, + c: bid.currency, + r: reason, + bi: bid.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.AD_RENDER_FAILED], data, bid.auctionId); + processEvent(event); +} +function handleBidViewable (args) { + // console.log('bid viewable:', arguments); + const renderTime = bidsData[args.adId].renderTime; + const data = { + b: args.bidder, + u: args.adUnitCode, + rt: Date.now() - renderTime, + bi: args.requestId, + }; + const event = createEvent(EVENT_MAP[EVENTS.BID_VIEWABLE], data, args.auctionId); + processEvent(event); +} + +let baseAdapter = adapter({analyticsType}); +let r2b2Analytics = Object.assign({}, baseAdapter, { + getUrl() { + return `${DEFAULT_PROTOCOL}://${LOG_SERVER}/${DEFAULT_EVENT_PATH}` + }, + getErrorUrl() { + return `${DEFAULT_PROTOCOL}://${LOG_SERVER}/${DEFAULT_ERROR_PATH}` + }, + enableAnalytics(conf = {}) { + if (isPlainObject(conf.options)) { + const {domain, configId, configVer, server} = conf.options; + if (!domain || !isStr(domain)) { + logWarn(`${MODULE_NAME}: Mandatory parameter 'domain' not configured, analytics disabled`); + return + } + WEBSITE = domain + if (server) { + if (isStr(server)) { + LOG_SERVER = server + } else { + logWarn(`options.server must be a string`); + } + } + if (configId) { + if (isNumber(configId)) { + CONFIG_ID = configId + } else { + logWarn(`options.configId must be a number`); + } + } + if (configVer) { + if (isNumber(configVer)) { + CONFIG_VERSION = configVer + } else { + logWarn(`options.configVer must be a number`); + } + } + } + baseAdapter.enableAnalytics.call(this, conf); + }, + track(event) { + const {eventType, args} = event; + try { + if (!adServerCurrency) { + const currencyObj = config.getConfig('currency'); + adServerCurrency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; + } + switch (eventType) { + case EVENTS.NO_BID: + handleNoBid(args) + break; + case EVENTS.AUCTION_INIT: + handleAuctionInit(args) + break; + case EVENTS.BID_REQUESTED: + handleBidRequested(args) + break; + case EVENTS.BID_TIMEOUT: + handleBidTimeout(args) + break; + case EVENTS.BID_RESPONSE: + handleBidResponse(args) + break; + case EVENTS.BID_REJECTED: + handleBidRejected(args) + break; + case EVENTS.BIDDER_DONE: + handleBidderDone(args) + break; + case EVENTS.AUCTION_END: + handleAuctionEnd(args) + break; + case EVENTS.BID_WON: + handleBidWon(args) + break; + case EVENTS.SET_TARGETING: + handleSetTargeting(args) + break; + case EVENTS.STALE_RENDER: + handleStaleRender(args) + break; + case EVENTS.AD_RENDER_SUCCEEDED: + handleRenderSuccess(args) + break; + case EVENTS.AD_RENDER_FAILED: + handleRenderFailed(args) + break; + case EVENTS.BID_VIEWABLE: + handleBidViewable(args) + break; + } + } catch (e) { + reportError(`${eventType} - ${e.message}`) + } + } +}); + +// save the base class function +r2b2Analytics.originEnableAnalytics = r2b2Analytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +r2b2Analytics.enableAnalytics = function (config) { + r2b2Analytics.originEnableAnalytics(config); // call the base class function +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: r2b2Analytics, + code: ADAPTER_CODE, + gvlid: GVLID +}); + +export default r2b2Analytics; diff --git a/modules/r2b2AnalyticsAdapter.md b/modules/r2b2AnalyticsAdapter.md new file mode 100644 index 00000000000..484339be106 --- /dev/null +++ b/modules/r2b2AnalyticsAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: R2B2 Analytics Adapter +Module Type: Analytics Adapter +Maintainer: dev@r2b2.cz +``` + +## Description + +The R2B2 Analytics Adapter enables data collection for analysis and reporting purposes. Access to collected data and the ability to start data collection require prior approval from R2B2. For approval, please contact our account team on partner@r2b2.io. + +## How to configure? + +``` +pbjs.enableAnalytics({ + provider: 'r2b2', + options: { + domain: 'example.com', + configId: 1, + configVer: 1 + } +}); +``` + +### Options + +| Name | Scope | Example | Type | Description | +|-----------|----------|-------------|----------|----------------------------------------------------------------| +| `domain` | required | example.com | `string` | R2B2 approved domain where data collection occurs | +| `configId` | optional | 1 | `int` | Identifier for different configurations under the same domain (e.g., 1 for mobile, 2 for desktop) | +| `configVer` | optional | 1 | `int` | Version number for configurations sharing the same `configId` | diff --git a/test/spec/modules/r2b2AnalytiscAdapter_spec.js b/test/spec/modules/r2b2AnalytiscAdapter_spec.js new file mode 100644 index 00000000000..5658821e95e --- /dev/null +++ b/test/spec/modules/r2b2AnalytiscAdapter_spec.js @@ -0,0 +1,1009 @@ +import r2b2Analytics from '../../../modules/r2b2AnalyticsAdapter'; +import {resetAnalyticAdapter} from '../../../modules/r2b2AnalyticsAdapter'; +import { expect } from 'chai'; +import {EVENTS, AD_RENDER_FAILED_REASON, REJECTION_REASON} from 'src/constants.js'; +import * as pbEvents from 'src/events.js'; +import * as ajax from 'src/ajax.js'; +import * as utils from 'src/utils'; +import {getGlobal} from 'src/prebidGlobal'; +import * as prebidGlobal from 'src/prebidGlobal'; +let adapterManager = require('src/adapterManager').default; + +const { NO_BID, AUCTION_INIT, BID_REQUESTED, BID_TIMEOUT, BID_RESPONSE, BID_REJECTED, BIDDER_DONE, + AUCTION_END, BID_WON, SET_TARGETING, STALE_RENDER, AD_RENDER_SUCCEEDED, AD_RENDER_FAILED, BID_VIEWABLE +} = EVENTS; + +const BANNER_SETTING_1 = { 'sizes': [[300, 300], [300, 250]] }; +const BANNER_SETTING_2 = { 'sizes': [[320, 150], [320, 50]] }; + +const AD_UNIT_1_CODE = 'prebid_300x300'; +const AD_UNIT_2_CODE = 'prebid_320x150'; +const R2B2_PID_1 = 'test.cz/s2s/300x300/mobile'; +const R2B2_PID_2 = 'test.cz/s2s/320x150/mobile'; +const AD_UNIT_1_TID = '0b3464bb-d80a-490e-8367-a65201a37ba3' +const AD_UNIT_2_TID = 'c8c3643c-9de0-43ea-bcd6-cc0072ec9b45'; +const AD_UNIT_2_AD_ID = '22c828c62d44da5'; +const AD_UNIT_1 = { + 'code': AD_UNIT_1_CODE, + 'mediaTypes': { + 'banner': BANNER_SETTING_1 + }, + 'bids': [{ + 'bidder': 'r2b2', + 'params': {'pid': R2B2_PID_1} + }, { + 'bidder': 'adf', + 'params': {'mid': 1799592} + }], + 'sizes': BANNER_SETTING_1.sizes, + 'transactionId': AD_UNIT_1_TID, + 'ortb2Imp': { + 'ext': { + 'tid': AD_UNIT_1_TID + } + } +} +const AD_UNIT_2 = { + 'code': AD_UNIT_2_CODE, + 'mediaTypes': { + 'banner': BANNER_SETTING_2 + }, + 'bids': [{ + 'bidder': 'r2b2', + 'params': {'pid': R2B2_PID_2} + }, { + 'bidder': 'stroeerCore', + 'params': { 'sid': '9532ef8d-e630-45a9-88f6-3eb3eb265d58' } + }], + 'sizes': BANNER_SETTING_2.sizes, + 'transactionId': AD_UNIT_2_TID, + 'ortb2Imp': { + 'ext': { + 'tid': AD_UNIT_2_TID + } + } +}; +const AUCTION_ID = '5b912b08-ce23-463c-a6cf-1792f7344430'; +const R2B2_BIDDER_REQUEST = { + 'bidderCode': 'r2b2', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '1e5fae5d0ee471', + 'bids': [ + { + 'bidder': 'r2b2', + 'params': {'pid': R2B2_PID_1}, + 'mediaTypes': { 'banner': BANNER_SETTING_1 }, + 'adUnitCode': AD_UNIT_1_CODE, + 'transactionId': '0b3464bb-d80a-490e-8367-a65201a37ba3', + 'sizes': BANNER_SETTING_1.sizes, + 'bidId': '27434062b8cc94', + 'bidderRequestId': '1e5fae5d0ee471', + 'auctionId': AUCTION_ID, + }, + { + 'bidder': 'r2b2', + 'params': {'pid': R2B2_PID_2}, + 'mediaTypes': { 'banner': BANNER_SETTING_2 }, + 'adUnitCode': AD_UNIT_2_CODE, + 'transactionId': 'c8c3643c-9de0-43ea-bcd6-cc0072ec9b45', + 'sizes': BANNER_SETTING_2.sizes, + 'bidId': '3c296eca6b08f4', + 'bidderRequestId': '1e5fae5d0ee471', + 'auctionId': AUCTION_ID, + } + ], + 'auctionStart': 1727160493004, + 'timeout': 10000, + 'start': 1727160493009 +}; +const ADFORM_BIDDER_REQUEST = { + 'bidderCode': 'adf', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '49241b449c60b4', + 'bids': [{ + 'bidder': 'adf', + 'params': { + 'mid': 1799592 + }, + 'mediaTypes': { 'banner': BANNER_SETTING_1 }, + 'adUnitCode': AD_UNIT_1_CODE, + 'transactionId': '0b3464bb-d80a-490e-8367-a65201a37ba3', + 'sizes': BANNER_SETTING_1.sizes, + 'bidId': '54ef5ac3c45b93', + 'bidderRequestId': '49241b449c60b4', + 'auctionId': AUCTION_ID, + }], + 'auctionStart': 1727160493004, + 'timeout': 10000, + 'start': 1727160493016 +} +const STROEER_BIDDER_REQUEST = { + 'bidderCode': 'stroeerCore', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '13f374632545075', + 'bids': [ + { + 'bidder': 'stroeerCore', + 'params': { + 'sid': '9532ef8d-e630-45a9-88f6-3eb3eb265d58' + }, + 'mediaTypes': { 'banner': BANNER_SETTING_2 }, + 'adUnitCode': AD_UNIT_2_CODE, + 'transactionId': '0b3464bb-d80a-490e-8367-a65201a37ba3', + 'sizes': BANNER_SETTING_2.sizes, + 'bidId': '14fc718193b4da3', + 'bidderRequestId': '13f374632545075', + 'auctionId': AUCTION_ID, + }, + ], + 'auctionStart': 1727160493004, + 'timeout': 10000, + 'start': 1727160493023 +} +const R2B2_AD_UNIT_2_BID = { + 'bidderCode': 'r2b2', + 'width': 300, + 'height': 100, + 'statusMessage': 'Bid available', + 'adId': '22c828c62d44da5', + 'requestId': '3c296eca6b08f4', + 'transactionId': 'c8c3643c-9de0-43ea-bcd6-cc0072ec9b45', + 'auctionId': AUCTION_ID, + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 1.5, + 'creativeId': '76190558', + 'ttl': 360, + 'netRevenue': true, + 'currency': 'USD', + 'ad': '
Test creative
', + 'adapterCode': 'r2b2', + 'originalCpm': 1.5, + 'originalCurrency': 'USD', + 'meta': {}, + 'responseTimestamp': 1727160493863, + 'requestTimestamp': 1727160493009, + 'bidder': 'r2b2', + 'adUnitCode': AD_UNIT_2_CODE, + 'timeToRespond': 854, + 'size': '300x100', + 'adserverTargeting': { + 'hb_bidder': 'r2b2', + 'hb_adid': AD_UNIT_2_AD_ID, + 'hb_pb': '0.20', + 'hb_size': '300x100', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '', + 'hb_crid': '76190558' + }, + 'latestTargetedAuctionId': AUCTION_ID, + 'status': 1 +} + +const MOCK = { + AUCTION_INIT: { + adUnitCodes: [AD_UNIT_1_CODE, AD_UNIT_2_CODE], + adUnits: [AD_UNIT_1, AD_UNIT_2], + bidderRequests: [R2B2_BIDDER_REQUEST, ADFORM_BIDDER_REQUEST, STROEER_BIDDER_REQUEST], + auctionId: AUCTION_ID, + }, + BID_REQUESTED: R2B2_BIDDER_REQUEST, + BID_RESPONSE: R2B2_AD_UNIT_2_BID, + BIDDER_DONE: R2B2_BIDDER_REQUEST, + AUCTION_END: { + auctionId: AUCTION_ID, + adUnitCodes: [AD_UNIT_1_CODE, AD_UNIT_2_CODE], + adUnits: [AD_UNIT_1, AD_UNIT_2], + bidderRequests: [R2B2_BIDDER_REQUEST, ADFORM_BIDDER_REQUEST, STROEER_BIDDER_REQUEST], + bidsReceived: [R2B2_AD_UNIT_2_BID], + bidsRejected: [], + noBids: [], + auctionEnd: 1727160493104 + }, + BID_WON: R2B2_AD_UNIT_2_BID, + SET_TARGETING: { + [AD_UNIT_2_CODE]: R2B2_AD_UNIT_2_BID.adserverTargeting + }, + NO_BID: { + bidder: 'r2b2', + params: { pid: R2B2_PID_1 }, + mediaTypes: { banner: BANNER_SETTING_1 }, + adUnitCode: AD_UNIT_1_CODE, + transactionId: 'a0b9d621-6b74-47ce-b7e0-cee5f8e3c124', + adUnitId: 'b87edd48-9572-431d-a508-e7f956332cec', + sizes: BANNER_SETTING_1.sizes, + bidId: '121b6373a78e56b', + bidderRequestId: '104126936185f0b', + auctionId: AUCTION_ID, + src: 'client', + }, + BID_TIMEOUT: [ + { + bidder: 'r2b2', + mediaTypes: { 'banner': BANNER_SETTING_1 }, + adUnitCode: AD_UNIT_1_CODE, + transactionId: '5629f772-9eae-49fa-a749-119f4d6295f9', + adUnitId: 'fb3536c6-7bcd-41a2-b96a-cb1764a06675', + sizes: BANNER_SETTING_1.sizes, + bidId: '25522556ba65bb72', + bidderRequestId: '2544c8d7e5b5aba4', + auctionId: AUCTION_ID, + timeout: 1000 + }, + { + bidder: 'r2b2', + mediaTypes: { 'banner': BANNER_SETTING_1 }, + adUnitCode: AD_UNIT_1_CODE, + transactionId: '5629f772-9eae-49fa-a749-119f4d6295f9', + adUnitId: 'fb3536c6-7bcd-41a2-b96a-cb1764a06675', + sizes: BANNER_SETTING_1.sizes, + bidId: '25522556ba65bb72', + bidderRequestId: '2544c8d7e5b5aba4', + auctionId: AUCTION_ID, + timeout: 1000 + } + ], + AD_RENDER_SUCCEEDED: { + 'doc': { + 'location': { + 'href': 'http://localhost:63342/test/prebid.html', + 'protocol': 'http:', + 'host': 'localhost:63342', + 'hostname': 'localhost', + 'port': '63342', + 'pathname': '/test/prebid.html', + 'hash': '', + 'origin': 'http://localhost:63342', + 'ancestorOrigins': { + '0': 'http://localhost:63342' + } + } + }, + 'bid': R2B2_AD_UNIT_2_BID, + 'adId': R2B2_AD_UNIT_2_BID.adId + }, + AD_RENDER_FAILED: { + bidId: '3c296eca6b08f4', + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + message: 'message', + bid: R2B2_AD_UNIT_2_BID + }, + STALE_RENDER: R2B2_AD_UNIT_2_BID, + BID_VIEWABLE: R2B2_AD_UNIT_2_BID +} +function fireEvents(events) { + return events.map((ev, i) => { + ev = Array.isArray(ev) ? ev : [ev, {i: i}]; + pbEvents.emit.apply(null, ev) + return ev; + }); +} + +function expectEvents(events, sandbox) { + events = fireEvents(events); + return { + to: { + beTrackedBy(trackFn) { + events.forEach(([eventType, args]) => { + sandbox.assert.calledWithMatch(trackFn, sandbox.match({eventType, args})); + }); + }, + beBundledTo(bundleFn) { + events.forEach(([eventType, args]) => { + sandbox.assert.calledWithMatch(bundleFn, sandbox.match.any, eventType, sandbox.match(args)) + }); + }, + }, + }; +} + +function validateAndExtractEvents(ajaxStub) { + expect(ajaxStub.calledOnce).to.equal(true); + let eventArgs = ajaxStub.firstCall.args[2]; + expect(typeof eventArgs).to.be.equal('string'); + expect(eventArgs.indexOf('events=')).to.be.equal(0); + let eventsString = eventArgs.substring(7); + let events = tryParseJSON(eventsString); + expect(events).to.not.be.undefined; + + return events; +} + +function getQueryData(url, decode = false) { + const queryArgs = url.split('?')[1].split('&'); + return queryArgs.reduce((data, arg) => { + let [key, val] = arg.split('='); + if (decode) { + val = decodeURIComponent(val); + } + if (data[key] !== undefined) { + if (!Array.isArray(data[key])) { + data[key] = [data[key]]; + } + data[key].push(val); + } else { + data[key] = val; + } + return data; + }, {}); +} + +function getPrebidEvents(events) { + return events && events.prebid && events.prebid.e; +} +function getPrebidEventsByName(events, name) { + let prebidEvents = getPrebidEvents(events); + if (!prebidEvents) return []; + + let result = []; + for (let i = 0; i < prebidEvents.length; i++) { + let event = prebidEvents[i]; + if (event.e === name) { + result.push(event); + } + } + + return result; +} + +function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + } +} +describe('r2b2 Analytics', function () { + let sandbox; + let clock; + let ajaxStub; + let getGlobalStub; + let enableAnalytics; + + before(() => { + enableAnalytics = r2b2Analytics.enableAnalytics; + }) + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + sandbox.stub(pbEvents, 'getEvents').returns([]); + getGlobalStub = sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getHighestCpmBids: () => [R2B2_AD_UNIT_2_BID] + }); + ajaxStub = sandbox.stub(ajax, 'ajax'); + + adapterManager.registerAnalyticsAdapter({ + code: 'r2b2', + adapter: r2b2Analytics + }); + + r2b2Analytics.enableAnalytics = enableAnalytics; + }); + + afterEach(() => { + resetAnalyticAdapter(); + sandbox.restore(); + getGlobalStub.restore(); + ajaxStub.restore(); + r2b2Analytics.disableAnalytics(); + }); + + describe('config', () => { + it('missing domain', () => { + let logWarnStub = sandbox.stub(utils, 'logWarn'); + + adapterManager.enableAnalytics({ + provider: 'r2b2', + options: {} + }); + + expect(logWarnStub.calledOnce).to.be.true; + expect(logWarnStub.firstCall.args[0]).to.be.equal('R2B2 Analytics: Mandatory parameter \'domain\' not configured, analytics disabled'); + logWarnStub.restore(); + }); + + it('all params error reporting', () => { + adapterManager.enableAnalytics({ + provider: 'r2b2', + options: { + domain: 'test.cz', + configId: 11, + configVer: 7, + server: 'delivery.local', + } + }); + + fireEvents([ + [BID_RESPONSE, MOCK.BID_RESPONSE], + ]); + + expect(ajaxStub.calledOnce).to.be.true; + expect(typeof ajaxStub.firstCall.args[0]).to.be.equal('string'); + let query = getQueryData(ajaxStub.firstCall.args[0], true); + expect(query['d']).to.be.equal('test.cz'); + expect(query['conf']).to.be.equal('11'); + expect(query['conf_ver']).to.be.equal('7'); + }); + + it('all params events reporting', (done) => { + adapterManager.enableAnalytics({ + provider: 'r2b2', + options: { + domain: 'test.cz', + configId: 11, + configVer: 7, + server: 'delivery.local', + } + }); + + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_RESPONSE, MOCK.BID_RESPONSE], + ]); + + setTimeout(() => { + expect(ajaxStub.calledOnce).to.be.true; + expect(typeof ajaxStub.firstCall.args[0]).to.be.equal('string'); + let query = getQueryData(ajaxStub.firstCall.args[0], true); + expect(query['hbDomain']).to.be.equal('test.cz'); + expect(query['conf']).to.be.equal('11'); + expect(query['conf_ver']).to.be.equal('7'); + done(); + }, 500); + + clock.tick(500); + }); + }); + + describe('events', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'r2b2', + options: { + domain: 'test.com', + } + }); + }); + + it('should catch all events', function () { + sandbox.spy(r2b2Analytics, 'track'); + + expectEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_REQUESTED, MOCK.BID_REQUESTED], + [BID_RESPONSE, MOCK.BID_RESPONSE], + [AUCTION_END, MOCK.AUCTION_END], + [SET_TARGETING, MOCK.SET_TARGETING], + [BID_WON, MOCK.BID_WON], + ], sandbox).to.beTrackedBy(r2b2Analytics.track); + }); + + it('should send ajax after delay', (done) => { + fireEvents([[AUCTION_INIT, MOCK.AUCTION_INIT]]); + setTimeout(() => { + expect(ajaxStub.calledOnce).to.equal(true); + done(); + }, 500); + + clock.tick(500); + }) + + it('auction init content', (done) => { + fireEvents([[AUCTION_INIT, MOCK.AUCTION_INIT]]); + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let initEvents = getPrebidEventsByName(events, 'init'); + expect(initEvents.length).to.be.equal(1); + let initEvent = initEvents[0]; + expect(initEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + u: { + [AD_UNIT_1_CODE]: ['r2b2', 'adf'], + [AD_UNIT_2_CODE]: ['r2b2', 'stroeerCore'] + }, + o: 1 + }) + + done(); + }, 500); + + clock.tick(500); + }) + + it('auction multiple init', (done) => { + let auction_init = MOCK.AUCTION_INIT; + let auction_init_2 = utils.deepClone(MOCK.AUCTION_INIT); + auction_init_2.auctionId = 'different_auction_id'; + + fireEvents([[AUCTION_INIT, auction_init], [AUCTION_INIT, auction_init_2]]); + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let initEvents = getPrebidEventsByName(events, 'init'); + expect(initEvents.length).to.be.equal(2); + done(); + }, 500); + + clock.tick(500); + }); + + it('bid requested content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_REQUESTED, MOCK.BID_REQUESTED], + [BID_REQUESTED, ADFORM_BIDDER_REQUEST], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let bidRequestedEvents = getPrebidEventsByName(events, 'request'); + expect(bidRequestedEvents.length).to.be.equal(2); + let r2b2BidRequest = bidRequestedEvents[0]; + let adformBidRequest = bidRequestedEvents[1]; + expect(r2b2BidRequest.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: { + [AD_UNIT_1_CODE]: 1, + [AD_UNIT_2_CODE]: 1 + } + }); + expect(adformBidRequest.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'adf', + u: {[AD_UNIT_1_CODE]: 1} + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('no bid content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [NO_BID, MOCK.NO_BID] + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let noBidEvents = getPrebidEventsByName(events, 'noBid'); + expect(noBidEvents.length).to.be.equal(1); + let noBidEvent = noBidEvents[0]; + expect(noBidEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_1_CODE + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('bid timeout content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_TIMEOUT, MOCK.BID_TIMEOUT] + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let timeoutEvents = getPrebidEventsByName(events, 'timeout'); + expect(timeoutEvents.length).to.be.equal(1); + let timeoutEvent = timeoutEvents[0]; + expect(timeoutEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: { + r2b2: {[AD_UNIT_1_CODE]: 2} + } + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('bidder done content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BIDDER_DONE, MOCK.BIDDER_DONE] + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let bidderDoneEvents = getPrebidEventsByName(events, 'bidderDone'); + expect(bidderDoneEvents.length).to.be.equal(1); + let bidderDoneEvent = bidderDoneEvents[0]; + expect(bidderDoneEvent.d).to.be.deep.equal({ ai: AUCTION_ID, b: 'r2b2' }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('auction end content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [AUCTION_END, MOCK.AUCTION_END] + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let auctionEndEvents = getPrebidEventsByName(events, 'auction'); + expect(auctionEndEvents.length).to.be.equal(1); + let auctionEnd = auctionEndEvents[0]; + expect(auctionEnd.d).to.be.deep.equal({ + ai: AUCTION_ID, + wins: [{ + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + c: 'USD', + sz: '300x100', + bi: R2B2_AD_UNIT_2_BID.requestId, + }], + u: {[AD_UNIT_2_CODE]: {b: {r2b2: 1}}}, + o: 1, + bc: 1, + nbc: 0, + rjc: 0, + brc: 4 + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('auction end empty auction', (done) => { + let noBidderRequestsEnd = utils.deepClone(MOCK.AUCTION_END); + noBidderRequestsEnd.bidderRequests = []; + + fireEvents([ + [AUCTION_END, noBidderRequestsEnd] + ]); + + setTimeout(() => { + expect(ajaxStub.calledOnce).to.be.false; + + done(); + }, 500); + + clock.tick(500); + }); + + it('bid response content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_RESPONSE, MOCK.BID_RESPONSE], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let bidResponseEvents = getPrebidEventsByName(events, 'response'); + expect(bidResponseEvents.length).to.be.equal(1); + let bidResponseEvent = bidResponseEvents[0]; + expect(bidResponseEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + op: 1.5, + c: 'USD', + oc: 'USD', + sz: '300x100', + st: 1, + rt: 854, + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('bid rejected content', (done) => { + let rejectedBid = utils.deepClone(R2B2_AD_UNIT_2_BID); + rejectedBid.rejectionReason = REJECTION_REASON.FLOOR_NOT_MET; + + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_REJECTED, rejectedBid], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let rejectedBidsEvents = getPrebidEventsByName(events, 'reject'); + expect(rejectedBidsEvents.length).to.be.equal(1); + let rejectedBidEvent = rejectedBidsEvents[0]; + expect(rejectedBidEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + c: 'USD', + r: REJECTION_REASON.FLOOR_NOT_MET, + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('bid won content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_WON, MOCK.BID_WON], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let bidWonEvents = getPrebidEventsByName(events, 'bidWon'); + expect(bidWonEvents.length).to.be.equal(1); + let bidWonEvent = bidWonEvents[0]; + expect(bidWonEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + op: 1.5, + c: 'USD', + oc: 'USD', + sz: '300x100', + mt: 'banner', + at: { + b: 'r2b2', + sz: '300x100', + pb: '0.20', + fmt: 'banner' + }, + o: 1, + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('bid won content no targeting', (done) => { + let bidWonWithoutTargeting = utils.deepClone(MOCK.BID_WON); + bidWonWithoutTargeting.adserverTargeting = {}; + + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_WON, bidWonWithoutTargeting], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let bidWonEvents = getPrebidEventsByName(events, 'bidWon'); + expect(bidWonEvents.length).to.be.equal(1); + let bidWonEvent = bidWonEvents[0]; + expect(bidWonEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + op: 1.5, + c: 'USD', + oc: 'USD', + sz: '300x100', + mt: 'banner', + at: { + b: '', + sz: '', + pb: '', + fmt: '' + }, + o: 1, + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('targeting content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_RESPONSE, MOCK.BID_RESPONSE], + [SET_TARGETING, MOCK.SET_TARGETING] + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let setTargetingEvents = getPrebidEventsByName(events, 'targeting'); + expect(setTargetingEvents.length).to.be.equal(1); + expect(setTargetingEvents[0].d).to.be.deep.equal({ + ai: AUCTION_ID, + u: { + [AD_UNIT_2_CODE]: { + b: 'r2b2', + sz: '300x100', + pb: '0.20', + fmt: 'banner' + } + } + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('ad render succeeded content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_RESPONSE, MOCK.BID_RESPONSE], + [AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let setTargetingEvents = getPrebidEventsByName(events, 'render'); + expect(setTargetingEvents.length).to.be.equal(1); + let setTargeting = setTargetingEvents[0]; + expect(setTargeting.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + c: 'USD', + sz: '300x100', + mt: 'banner', + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('ad render failed content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_RESPONSE, MOCK.BID_RESPONSE], + [AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let renderFailedEvents = getPrebidEventsByName(events, 'renderFail'); + expect(renderFailedEvents.length).to.be.equal(1); + let renderFailed = renderFailedEvents[0]; + expect(renderFailed.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + c: 'USD', + r: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('stale render content', (done) => { + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [STALE_RENDER, MOCK.STALE_RENDER], + ]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let staleRenderEvents = getPrebidEventsByName(events, 'staleRender'); + expect(staleRenderEvents.length).to.be.equal(1); + let staleRenderEvent = staleRenderEvents[0]; + expect(staleRenderEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + p: 1.5, + c: 'USD', + bi: R2B2_AD_UNIT_2_BID.requestId, + }); + + done(); + }, 500); + + clock.tick(500); + }); + + it('bid viewable content', (done) => { + let dateStub = sandbox.stub(Date, 'now'); + dateStub.returns(100); + + fireEvents([ + [AUCTION_INIT, MOCK.AUCTION_INIT], + [BID_RESPONSE, MOCK.BID_RESPONSE], + [AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED] + ]); + + dateStub.returns(150); + + fireEvents([[BID_VIEWABLE, MOCK.BID_VIEWABLE]]); + + setTimeout(() => { + let events = validateAndExtractEvents(ajaxStub); + let bidViewableEvents = getPrebidEventsByName(events, 'view'); + expect(bidViewableEvents.length).to.be.equal(1); + let bidViewableEvent = bidViewableEvents[0]; + expect(bidViewableEvent.d).to.be.deep.equal({ + ai: AUCTION_ID, + b: 'r2b2', + u: AD_UNIT_2_CODE, + rt: 50, + bi: R2B2_AD_UNIT_2_BID.requestId + }); + + done(); + }, 500); + + clock.tick(500); + dateStub.restore(); + }); + + it('no auction data error', (done) => { + fireEvents([ + [BID_RESPONSE, MOCK.BID_RESPONSE], + ]); + + setTimeout(() => { + expect(ajaxStub.calledOnce).to.be.true; + expect(typeof ajaxStub.firstCall.args[0]).to.be.equal('string'); + let query = getQueryData(ajaxStub.firstCall.args[0], true); + expect(typeof query.m).to.be.equal('string'); + expect(query.m.indexOf('No auction data when creating event')).to.not.be.equal(-1); + + done(); + }, 500); + + clock.tick(500); + }); + + it('empty auction', (done) => { + let emptyAuctionInit = utils.deepClone(MOCK.AUCTION_INIT); + emptyAuctionInit.bidderRequests = undefined; + let emptyAuctionEnd = utils.deepClone(MOCK.AUCTION_END); + emptyAuctionEnd.bidderRequests = []; + + fireEvents([ + [AUCTION_INIT, emptyAuctionInit], + [AUCTION_END, emptyAuctionEnd], + ]) + + setTimeout(() => { + expect(ajaxStub.calledOnce).to.be.true; + let events = validateAndExtractEvents(ajaxStub); + let initEvents = getPrebidEventsByName(events, 'init'); + let auctionEndEvents = getPrebidEventsByName(events, 'auction'); + + expect(initEvents.length).to.be.equal(1); + expect(auctionEndEvents.length).to.be.equal(0); + + done(); + }, 500); + + clock.tick(500); + }); + }); +}); From 47678b42f595364b13029a50ea69f6ba0b453b85 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 10 Feb 2025 07:30:18 -0800 Subject: [PATCH 0891/1097] Rubicon Bid Adapter: Fix client Hints full ver (#12740) * Fix fake high entropy query * comment --- modules/rubiconBidAdapter.js | 5 +- test/spec/modules/rubiconBidAdapter_spec.js | 56 ++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 037e19c5d51..0fa96133e10 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1028,7 +1028,10 @@ function applyFPD(bidRequest, mediaType, data) { // reduce down into ua and full version list attributes const [ua, fullVer] = browsers.reduce((accum, browserData) => { accum[0].push(`"${browserData?.brand}"|v="${browserData?.version?.[0]}"`); - accum[1].push(`"${browserData?.brand}"|v="${browserData?.version?.join?.('.')}"`); + // only set fullVer if long enough + if (browserData.version.length > 1) { + accum[1].push(`"${browserData?.brand}"|v="${browserData?.version?.join?.('.')}"`); + } return accum; }, [[], []]); data.m_ch_ua = ua?.join?.(','); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 9821fc45126..251253a0a55 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1971,7 +1971,7 @@ describe('the rubicon adapter', function () { architecture: 'x86' } }); - it('should send m_ch_* params if ortb2.device.sua object is there', function () { + it('should send m_ch_* params if ortb2.device.sua object is there with igh entropy', function () { let bidRequestSua = utils.deepClone(bidderRequest); bidRequestSua.bids[0].ortb2 = { device: { sua: standardSuaObject } }; @@ -2041,6 +2041,60 @@ describe('the rubicon adapter', function () { // arch not sent expect(data.get('m_ch_arch')).to.be.null; }); + it('should not send high entropy if not present when it is low entropy client hints', function () { + let bidRequestSua = utils.deepClone(bidderRequest); + bidRequestSua.bids[0].ortb2 = { device: { sua: { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not A(Brand', + 'version': [ + '8' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '132' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '132' + ] + } + ], + 'mobile': 0 + } } }; + + // How should fastlane query be constructed with default SUA + let expectedValues = { + m_ch_ua: `"Not A(Brand"|v="8","Chromium"|v="132","Google Chrome"|v="132"`, + m_ch_mobile: '?0', + m_ch_platform: 'macOS', + } + + // Build Fastlane call + let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); + let data = new URLSearchParams(request.data); + + // Loop through expected values and if they do not match push an error + const errors = Object.entries(expectedValues).reduce((accum, [key, val]) => { + if (data.get(key) !== val) accum.push(`${key} - expect: ${val} - got: ${data[key]}`) + return accum; + }, []); + + // should be no errors + expect(errors).to.deep.equal([]); + + // make sure high entropy keys are not present + let highEntropyHints = ['m_ch_full_ver', 'm_ch_arch', 'm_ch_bitness', 'm_ch_platform_ver']; + highEntropyHints.forEach((hint) => { expect(data.get(hint)).to.be.null; }); + }); }); }); From 0c1a859ae29cced13f771be58bfd91eb743f2ecc Mon Sep 17 00:00:00 2001 From: Ivan Atanasov <139130065+i-atanasov-az@users.noreply.github.com> Date: Mon, 10 Feb 2025 18:11:53 +0200 Subject: [PATCH 0892/1097] Prebid Core: Fix wrong targeting being applied when multibid module is included (#12716) * 12238 - Azerion / Improve: does not properly support currency module * **Type:** Fix * **Scope:** improvedigitalBidAdapter * **Subject:** Bid floors are always converted to USD. * **Details:** * Adds `DEFAULT_CURRENCY` variable which is set to USD * Adds `convertBidFloorCurrency` function which in used to convert the bid floor when both `imp.bidfloor` and `imp.bidfloorcur` are present, and `imp.bidfloorcur` is not equal to the adapter's `DEFAULT_CURRENCY`; * **Breaks:** N/A * restored accidentally discarded change from unit test expect * * Modifies behavior to pass bid floor as is when it cannot be converted to USD; * Removes rounding of bid floor when converting its currency to USD; * remove unnecessary uses of `toUpperCase()` * * fix `convertCurrency` mock * remove redundant checks for type and NaN from `convertBidFloorCurrency` function * Restores the logic of choosing the winning bid for targeting inside getWinningBids instead of the using the hookable getHighestCpmBidsFromBidPool which has a logic to return bids unchanged, to sort them causing wrong targeting being applied when multibid module is included. * Adds unit test --------- Co-authored-by: Lyubomir Shishkov Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Catalin Ciocov --- src/targeting.js | 44 +++++++++------------------ test/spec/unit/core/targeting_spec.js | 36 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/targeting.js b/src/targeting.js index 1903524984b..3e275ba4e16 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -219,7 +219,7 @@ export function newTargeting(auctionManager) { if (enableSendAllBids || (deals && bid.dealId)) { const targetingValue = getTargetingMap(bid, standardKeys.filter( key => typeof bid.adserverTargeting[key] !== 'undefined' && - (deals || allowedSendAllBidTargeting.indexOf(key) !== -1))); + (deals || allowedSendAllBidTargeting.indexOf(key) !== -1))); if (targetingValue) { result.push({[bid.adUnitCode]: targetingValue}) @@ -290,7 +290,7 @@ export function newTargeting(auctionManager) { const adUnitBidLimit = (sendAllBids && (bidLimit || bidLimitConfigValue)) || 0; const { customKeysByUnit, filteredBids } = getfilteredBidsAndCustomKeys(adUnitCodes, bidsReceived); const bidsSorted = getHighestCpmBidsFromBidPool(filteredBids, winReducer, adUnitBidLimit, undefined, winSorter); - let targeting = getTargetingLevels(bidsSorted, customKeysByUnit); + let targeting = getTargetingLevels(bidsSorted, customKeysByUnit, adUnitCodes); const defaultKeys = Object.keys(Object.assign({}, DEFAULT_TARGETING_KEYS, NATIVE_KEYS)); let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS); @@ -337,8 +337,8 @@ export function newTargeting(auctionManager) { }); } - function getTargetingLevels(bidsSorted, customKeysByUnit) { - const targeting = getWinningBidTargeting(bidsSorted) + function getTargetingLevels(bidsSorted, customKeysByUnit, adUnitCodes) { + const targeting = getWinningBidTargeting(bidsSorted, adUnitCodes) .concat(getCustomBidTargeting(bidsSorted, customKeysByUnit)) .concat(getBidderTargeting(bidsSorted)) .concat(getAdUnitTargeting()); @@ -568,25 +568,17 @@ export function newTargeting(auctionManager) { * @return {Array} - An array of winning bids. */ targeting.getWinningBids = function(adUnitCode, bids, winReducer = getHighestCpm, winSorter = sortByHighestCpm) { - const usedCodes = []; const bidsReceived = bids || getBidsReceived(winReducer, winSorter); const adUnitCodes = getAdUnitCodes(adUnitCode); return bidsReceived - .reduce((result, bid) => { - const code = bid.adUnitCode; - const cpmEligible = bidderSettings.get(code, 'allowZeroCpmBids') === true ? bid.cpm >= 0 : bid.cpm > 0; - const isPreferredDeal = config.getConfig('targetingControls.alwaysIncludeDeals') && bid.dealId; - const eligible = includes(adUnitCodes, code) && - !includes(usedCodes, code) && - (isPreferredDeal || cpmEligible) - if (eligible) { - result.push(bid); - usedCodes.push(code); - } - - return result; - }, []); + .filter(bid => includes(adUnitCodes, bid.adUnitCode)) + .filter(bid => (bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true) ? bid.cpm >= 0 : bid.cpm > 0) + .map(bid => bid.adUnitCode) + .filter(uniques) + .map(adUnitCode => bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) + .reduce(getHighestCpm)); }; /** @@ -624,19 +616,11 @@ export function newTargeting(auctionManager) { /** * Get targeting key value pairs for winning bid. * @param {Array} bidsReceived code array + * @param {string[]} adUnitCodes code array * @return {targetingArray} winning bids targeting */ - function getWinningBidTargeting(bidsReceived) { - let usedAdUnitCodes = []; - let winners = bidsReceived - .reduce((bids, bid) => { - if (!includes(usedAdUnitCodes, bid.adUnitCode)) { - bids.push(bid); - usedAdUnitCodes.push(bid.adUnitCode); - } - return bids; - }, []); - + function getWinningBidTargeting(bidsReceived, adUnitCodes) { + let winners = targeting.getWinningBids(adUnitCodes, bidsReceived); let standardKeys = getStandardKeys(); winners = winners.map(winner => { diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 2296379ca43..01f9f93477e 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -13,7 +13,7 @@ import {auctionManager} from 'src/auctionManager.js'; import * as utils from 'src/utils.js'; import {deepClone} from 'src/utils.js'; import {createBid} from '../../../../src/bidfactory.js'; -import {hook} from '../../../../src/hook.js'; +import { hook, setupBeforeHookFnOnce } from '../../../../src/hook.js'; import {getHighestCpm} from '../../../../src/utils/reducers.js'; function mkBid(bid, status = STATUS.GOOD) { @@ -956,6 +956,40 @@ describe('targeting tests', function () { }); }); // end getAllTargeting tests + describe('getAllTargeting will work correctly when a hook raises has modified flag in getHighestCpmBidsFromBidPool', function () { + let bidsReceived; + let amGetAdUnitsStub; + let amBidsReceivedStub; + let bidExpiryStub; + + beforeEach(function () { + bidsReceived = [bid2, bid1].map(deepClone); + + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { + return bidsReceived; + }); + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + return ['/123456/header-bid-tag-0']; + }); + bidExpiryStub = sandbox.stub(filters, 'isBidNotExpired').returns(true); + + setupBeforeHookFnOnce(getHighestCpmBidsFromBidPool, function (fn, bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + fn.call(this, bidsReceived, highestCpmCallback, adUnitBidLimit, true); + }); + }); + + afterEach(function () { + getHighestCpmBidsFromBidPool.getHooks().remove(); + }) + + it('will apply correct targeting', function () { + let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.53'); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal('148018fe5e'); + }) + }); + describe('getAllTargeting without bids return empty object', function () { let amBidsReceivedStub; let amGetAdUnitsStub; From 48a6c23bdd30d2b7de0c637713ac59c8c2f693ee Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Mon, 10 Feb 2025 18:39:45 +0100 Subject: [PATCH 0893/1097] LiveIntent Analytics Adapter: Make auctionId an object field before passing it to getAuction (#12744) * Make auctionId an object field * Fix the test * Trigger test re-run --- modules/liveIntentAnalyticsAdapter.js | 2 +- test/spec/modules/liveIntentAnalyticsAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/liveIntentAnalyticsAdapter.js b/modules/liveIntentAnalyticsAdapter.js index 04b9e333e8a..b9624958592 100644 --- a/modules/liveIntentAnalyticsAdapter.js +++ b/modules/liveIntentAnalyticsAdapter.js @@ -16,7 +16,7 @@ let bidWonTimeout; function handleAuctionEnd(args) { setTimeout(() => { - const auction = auctionManager.index.getAuction(args.auctionId); + const auction = auctionManager.index.getAuction({auctionId: args.auctionId}); const winningBids = (auction) ? auction.getWinningBids() : []; const data = createAnalyticsEvent(args, winningBids); sendAnalyticsEvent(data); diff --git a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js index e833440bf03..73a8d41be72 100644 --- a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js +++ b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js @@ -285,7 +285,7 @@ describe('LiveIntent Analytics Adapter ', () => { liAnalytics.enableAnalytics(configWithSamplingAll); sandbox.stub(utils, 'generateUUID').returns(instanceId); sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); - sandbox.stub(auctionManager.index, 'getAuction').withArgs(auctionId).returns({ getWinningBids: () => winningBids }); + sandbox.stub(auctionManager.index, 'getAuction').withArgs({auctionId}).returns({ getWinningBids: () => winningBids }); events.emit(EVENTS.AUCTION_END, args); clock.tick(2000); expect(server.requests.length).to.equal(1); @@ -304,7 +304,7 @@ describe('LiveIntent Analytics Adapter ', () => { liAnalytics.enableAnalytics(configWithSamplingNone); sandbox.stub(utils, 'generateUUID').returns(instanceId); sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); - sandbox.stub(auctionManager.index, 'getAuction').withArgs(auctionId).returns({ getWinningBids: () => winningBids }); + sandbox.stub(auctionManager.index, 'getAuction').withArgs({auctionId}).returns({ getWinningBids: () => winningBids }); events.emit(EVENTS.AUCTION_END, args); clock.tick(2000); expect(server.requests.length).to.equal(0); From 789d30236e1e5c69b2019a5ee01279cfc2432025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kowalski?= <112544015+pkowalski-id5@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:41:29 +0100 Subject: [PATCH 0894/1097] Id5Id: Provisioning EIDs from server-side response (#12745) * ID5 EIDs from server side response * fix lint --- modules/id5IdSystem.js | 112 ++++--- test/spec/modules/id5IdSystem_spec.js | 443 ++++++++++++++++++-------- 2 files changed, 379 insertions(+), 176 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 65e9be46a56..6e94117614b 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -105,6 +105,49 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleNam * @property {boolean} [disableUaHints] - When true, look up of high entropy values through user agent hints is disabled. */ +const DEFAULT_EIDS = { + 'id5id': { + getValue: function (data) { + return data.uid; + }, + source: ID5_DOMAIN, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } + }, + 'euid': { + getValue: function (data) { + return data.uid; + }, + getSource: function (data) { + return data.source; + }, + atype: 3, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } + }, + 'trueLinkId': { + getValue: function (data) { + return data.uid; + }, + getSource: function (data) { + return TRUE_LINK_SOURCE; + }, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } + } +}; + /** @type {Submodule} */ export const id5IdSubmodule = { /** @@ -127,6 +170,24 @@ export const id5IdSubmodule = { * @returns {(Object|undefined)} */ decode(value, config) { + if (value && value.ids !== undefined) { + const responseObj = {}; + const eids = {}; + Object.entries(value.ids).forEach(([key, value]) => { + let eid = value.eid; + let uid = eid?.uids?.[0] + responseObj[key] = { + uid: uid?.id, + ext: uid?.ext + }; + eids[key] = function () { + return eid; + }; // register function to get eid for each id (key) decoded + }); + this.eids = eids; // overwrite global eids + return responseObj; + } + let universalUid, publisherTrueLinkId; let ext = {}; @@ -137,7 +198,7 @@ export const id5IdSubmodule = { } else { return undefined; } - + this.eids = DEFAULT_EIDS; let responseObj = { id5id: { uid: universalUid, @@ -155,7 +216,7 @@ export const id5IdSubmodule = { if (publisherTrueLinkId) { responseObj.trueLinkId = { - uid: publisherTrueLinkId, + uid: publisherTrueLinkId }; } @@ -233,53 +294,14 @@ export const id5IdSubmodule = { logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); if (cacheIdObj) { - cacheIdObj.nbPage = incrementNb(cacheIdObj) + cacheIdObj.nbPage = incrementNb(cacheIdObj); } return cacheIdObj; }, primaryIds: ['id5id', 'trueLinkId'], - eids: { - 'id5id': { - getValue: function (data) { - return data.uid; - }, - source: ID5_DOMAIN, - atype: 1, - getUidExt: function (data) { - if (data.ext) { - return data.ext; - } - } - }, - 'euid': { - getValue: function (data) { - return data.uid; - }, - getSource: function (data) { - return data.source; - }, - atype: 3, - getUidExt: function (data) { - if (data.ext) { - return data.ext; - } - } - }, - 'trueLinkId': { - getValue: function (data) { - return data.uid; - }, - getSource: function (data) { - return TRUE_LINK_SOURCE; - }, - atype: 1, - getUidExt: function (data) { - if (data.ext) { - return data.ext; - } - } - } - + eids: DEFAULT_EIDS, + _reset() { + this.eids = DEFAULT_EIDS; } }; diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 456bd92cb02..49ae813fb10 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -4,14 +4,14 @@ import { coreStorage, getConsentHash, init, - startAuctionHook, - setSubmoduleRegistry + setSubmoduleRegistry, + startAuctionHook } from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; import * as events from '../../../src/events.js'; import {EVENTS} from '../../../src/constants.js'; import * as utils from '../../../src/utils.js'; -import {uspDataHandler, gppDataHandler} from '../../../src/adapterManager.js'; +import {gppDataHandler, uspDataHandler} from '../../../src/adapterManager.js'; import '../../../src/prebid.js'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; @@ -20,8 +20,6 @@ import {expect} from 'chai'; import {GreedyPromise} from '../../../src/utils/promise.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; -const IdFetchFlow = id5System.IdFetchFlow; - describe('ID5 ID System', function () { const ID5_MODULE_NAME = 'id5Id'; const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); @@ -76,6 +74,63 @@ describe('ID5 ID System', function () { 'linkType': ID5_RESPONSE_LINK_TYPE } }; + const IDS_ID5ID = { + eid: { + source: 'id5-sync.com', + uids: [{ + id: 'ID5ID-value', + atype: 1, + ext: { + linkType: 1, + pba: 12 + } + }] + } + }; + const IDS_TRUE_LINK_ID = { + eid: { + source: 'true-link-id5-sync.com', + inserter: 'id5-sync.com', + matcher: 'id5-sync.com', + mm: 1, + uids: [{ + id: 'truelink-id-value', + atype: 1 + }] + } + }; + + const IDS_EUID = { + eid: { + source: EUID_SOURCE, + inserter: 'id5-sync.com', + matcher: 'id5-sync.com', + mm: 2, + uids: [{ + atype: 3, + id: 'euid-value', + ext: { + provider: 'id5-sync.com' + } + }] + } + }; + + const ID5_STORED_OBJ_WITH_IDS_ID5ID_ONLY = { + ...ID5_STORED_OBJ, + ids: { + id5id: IDS_ID5ID + } + }; + + const ID5_STORED_OBJ_WITH_IDS_ALL = { + ...ID5_STORED_OBJ, + ids: { + id5id: IDS_ID5ID, + trueLinkId: IDS_TRUE_LINK_ID, + euid: IDS_EUID + } + }; const ALLOWED_ID5_VENDOR_DATA = { purpose: { consents: { @@ -116,17 +171,6 @@ describe('ID5 ID System', function () { }; } - function getId5ValueConfig(value) { - return { - name: ID5_MODULE_NAME, - value: { - id5id: { - uid: value - } - } - }; - } - function getUserSyncConfig(userIds) { return { userSync: { @@ -140,10 +184,6 @@ describe('ID5 ID System', function () { return getUserSyncConfig([getId5FetchConfig()]); } - function getValueConfig(value) { - return getUserSyncConfig([getId5ValueConfig(value)]); - } - function getAdUnitMock(code = 'adUnit-code') { return { code, @@ -168,7 +208,7 @@ describe('ID5 ID System', function () { } catch (err) { done(err); } - } + }; } class XhrServerMock { @@ -231,6 +271,7 @@ describe('ID5 ID System', function () { before(() => { hook.ready(); + id5System.id5IdSubmodule._reset(); }); describe('Check for valid publisher config', function () { @@ -340,7 +381,7 @@ describe('ID5 ID System', function () { fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); const submoduleResponse = await submoduleResponsePromise; - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + expect(submoduleResponse).is.eql(ID5_JSON_RESPONSE); }); it('should call the ID5 server with gdpr data ', async function () { @@ -363,7 +404,7 @@ describe('ID5 ID System', function () { fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); const submoduleResponse = await submoduleResponsePromise; - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + expect(submoduleResponse).is.eql(ID5_JSON_RESPONSE); }); it('should call the ID5 server without gdpr data when gdpr not applies ', async function () { @@ -384,7 +425,7 @@ describe('ID5 ID System', function () { fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); const submoduleResponse = await submoduleResponsePromise; - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + expect(submoduleResponse).is.eql(ID5_JSON_RESPONSE); }); it('should call the ID5 server with us privacy consent', async function () { @@ -408,7 +449,7 @@ describe('ID5 ID System', function () { fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); const submoduleResponse = await submoduleResponsePromise; - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + expect(submoduleResponse).is.eql(ID5_JSON_RESPONSE); }); it('should call the ID5 server with no signature field when no stored object', async function () { @@ -827,7 +868,7 @@ describe('ID5 ID System', function () { return xhrServerMock.expectFetchRequest() .then(fetchRequest => { let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.true_link).is.deep.equal({booted: false}); + expect(requestBody.true_link).is.eql({booted: false}); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); return submoduleResponse; }); @@ -846,7 +887,7 @@ describe('ID5 ID System', function () { return xhrServerMock.expectFetchRequest() .then(fetchRequest => { let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.true_link).is.deep.equal(trueLinkResponse); + expect(requestBody.true_link).is.eql(trueLinkResponse); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); return submoduleResponse; }); @@ -879,7 +920,7 @@ describe('ID5 ID System', function () { fetchRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); const submoduleResponse = await submoduleResponsePromise; - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + expect(submoduleResponse).is.eql(ID5_JSON_RESPONSE); }); }); }); @@ -905,106 +946,88 @@ describe('ID5 ID System', function () { sandbox.restore(); }); - it('should add stored ID from cache to bids', function (done) { - storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - startAuctionHook(wrapAsyncExpects(done, () => { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id.uid).is.equal(ID5_STORED_ID); - expect(bid.userIdAsEids[0]).is.deep.equal({ - source: ID5_SOURCE, - uids: [{ - id: ID5_STORED_ID, - atype: 1, - ext: { - linkType: ID5_STORED_LINK_TYPE - } - }] - }); - }); - }); - done(); - }), {adUnits}); - }); - - it('should add stored EUID from cache to bids', function (done) { - storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); - - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - startAuctionHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property(`userId.euid`); - expect(bid.userId.euid.uid).is.equal(EUID_STORED_ID); - expect(bid.userIdAsEids[0].uids[0].id).is.equal(ID5_STORED_ID); - expect(bid.userIdAsEids[1]).is.deep.equal({ - source: EUID_SOURCE, - uids: [{ - id: EUID_STORED_ID, - atype: 3, - ext: { - provider: ID5_SOURCE - } - }] + describe('when old request stored', function () { + it('should add stored ID from cache to bids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(wrapAsyncExpects(done, () => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); + expect(bid.userId.id5id.uid).is.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[0]).is.eql({ + source: ID5_SOURCE, + uids: [{ + id: ID5_STORED_ID, + atype: 1, + ext: { + linkType: ID5_STORED_LINK_TYPE + } + }] + }); }); }); - }); - done(); - }, {adUnits}); - }); - - it('should add stored TRUE_LINK_ID from cache to bids', function (done) { - storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); + done(); + }), {adUnits}); + }); - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - startAuctionHook(wrapAsyncExpects(done, function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); - expect(bid.userId.trueLinkId.uid).is.equal(TRUE_LINK_STORED_ID); - expect(bid.userIdAsEids[1]).is.deep.equal({ - source: TRUE_LINK_SOURCE, - uids: [{ - id: TRUE_LINK_STORED_ID, - atype: 1, - }] + it('should add stored EUID from cache to bids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.euid`); + expect(bid.userId.euid.uid).is.equal(EUID_STORED_ID); + expect(bid.userIdAsEids[0].uids[0].id).is.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[1]).is.eql({ + source: EUID_SOURCE, + uids: [{ + id: EUID_STORED_ID, + atype: 3, + ext: { + provider: ID5_SOURCE + } + }] + }); }); }); - }); - done(); - }), {adUnits}); - }); + done(); + }, {adUnits}); + }); - it('should add config value ID to bids', function (done) { - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getValueConfig(ID5_STORED_ID)); - - startAuctionHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id.uid).is.equal(ID5_STORED_ID); - expect(bid.userIdAsEids[0]).is.deep.equal({ - source: ID5_SOURCE, - uids: [{id: ID5_STORED_ID, atype: 1}] + it('should add stored TRUE_LINK_ID from cache to bids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(wrapAsyncExpects(done, function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); + expect(bid.userId.trueLinkId.uid).is.equal(TRUE_LINK_STORED_ID); + expect(bid.userIdAsEids[1]).is.eql({ + source: TRUE_LINK_SOURCE, + uids: [{ + id: TRUE_LINK_STORED_ID, + atype: 1 + }] + }); }); }); - }); - done(); - }, {adUnits}); + done(); + }), {adUnits}); + }); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { @@ -1036,29 +1059,187 @@ describe('ID5 ID System', function () { request.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); }); }); + + describe('when request with "ids" object stored', function () { + it('should add stored ID from cache to bids - from ids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_IDS_ID5ID_ONLY), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + const id5IdEidUid = IDS_ID5ID.eid.uids[0]; + startAuctionHook(wrapAsyncExpects(done, () => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); + expect(bid.userId.id5id).is.eql({ + uid: id5IdEidUid.id, + ext: id5IdEidUid.ext + }); + expect(bid.userIdAsEids[0]).is.eql({ + source: IDS_ID5ID.eid.source, + uids: [{ + id: id5IdEidUid.id, + atype: id5IdEidUid.atype, + ext: id5IdEidUid.ext + }] + }); + }); + }); + done(); + }), {adUnits}); + }); + it('should add stored EUID from cache to bids - from ids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify({ + ...ID5_STORED_OBJ, + ids: { + id5id: IDS_ID5ID, + euid: IDS_EUID + } + }), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(wrapAsyncExpects(done, () => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.euid`); + expect(bid.userId.euid).is.eql({ + uid: IDS_EUID.eid.uids[0].id, + ext: IDS_EUID.eid.uids[0].ext + }); + expect(bid.userIdAsEids[0]).is.eql(IDS_ID5ID.eid); + expect(bid.userIdAsEids[1]).is.eql(IDS_EUID.eid); + }); + }); + done(); + }), {adUnits}); + }); + + it('should add stored TRUE_LINK_ID from cache to bids - from ids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify({ + ...ID5_STORED_OBJ, + ids: { + id5id: IDS_ID5ID, + trueLinkId: IDS_TRUE_LINK_ID + } + }), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(wrapAsyncExpects(done, function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); + expect(bid.userId.trueLinkId.uid).is.eql(IDS_TRUE_LINK_ID.eid.uids[0].id); + expect(bid.userIdAsEids[1]).is.eql(IDS_TRUE_LINK_ID.eid); + }); + }); + done(); + }), {adUnits}); + }); + + it('should add other id from cache to bids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify({ + ...ID5_STORED_OBJ, + ids: { + id5id: IDS_ID5ID, + otherId: { + pbid: { + uid: 'other-id-value' + }, + eid: { + source: 'other-id.com', + inserter: 'id5-sync.com', + uids: [{ + id: 'other-id-value', + atype: 2, + ext: { + provider: 'id5-sync.com' + } + }], + + } + } + } + }), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(wrapAsyncExpects(done, function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.otherId`); + expect(bid.userId.otherId.uid).is.eql('other-id-value'); + expect(bid.userIdAsEids[1]).is.eql({ + source: 'other-id.com', + inserter: 'id5-sync.com', + uids: [{ + id: 'other-id-value', + atype: 2, + ext: { + provider: 'id5-sync.com' + } + }] + }); + }); + }); + done(); + }), {adUnits}); + }); + }); }); describe('Decode stored object', function () { const expectedDecodedObject = {id5id: {uid: ID5_STORED_ID, ext: {linkType: ID5_STORED_LINK_TYPE}}}; it('should properly decode from a stored object', function () { - expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).is.deep.equal(expectedDecodedObject); + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).is.eql(expectedDecodedObject); }); it('should return undefined if passed a string', function () { expect(id5System.id5IdSubmodule.decode('somestring', getId5FetchConfig())).is.eq(undefined); }); it('should decode euid from a stored object with EUID', function () { - expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_EUID, getId5FetchConfig()).euid).is.deep.equal({ + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_EUID, getId5FetchConfig()).euid).is.eql({ 'source': EUID_SOURCE, 'uid': EUID_STORED_ID, 'ext': {'provider': ID5_SOURCE} }); }); it('should decode trueLinkId from a stored object with trueLinkId', function () { - expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_TRUE_LINK, getId5FetchConfig()).trueLinkId).is.deep.equal({ + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_TRUE_LINK, getId5FetchConfig()).trueLinkId).is.eql({ 'uid': TRUE_LINK_STORED_ID }); }); + + it('should decode id5id from a stored object with ids', function () { + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_IDS_ID5ID_ONLY, getId5FetchConfig()).id5id).is.eql({ + uid: IDS_ID5ID.eid.uids[0].id, + ext: IDS_ID5ID.eid.uids[0].ext + }); + }); + + it('should decode all ids from a stored object with ids', function () { + let decoded = id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_IDS_ALL, getId5FetchConfig()); + expect(decoded.id5id).is.eql({ + uid: IDS_ID5ID.eid.uids[0].id, + ext: IDS_ID5ID.eid.uids[0].ext + }); + expect(decoded.trueLinkId).is.eql({ + uid: IDS_TRUE_LINK_ID.eid.uids[0].id, + ext: IDS_TRUE_LINK_ID.eid.uids[0].ext + }); + expect(decoded.euid).is.eql({ + uid: IDS_EUID.eid.uids[0].id, + ext: IDS_EUID.eid.uids[0].ext + }); + }); }); describe('A/B Testing', function () { @@ -1101,13 +1282,13 @@ describe('ID5 ID System', function () { it('should not set abTestingControlGroup extension when A/B testing is off', function () { const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOff); + expect(decoded).is.eql(expectedDecodedObjectWithIdAbOff); }); it('should set abTestingControlGroup to false when A/B testing is on but in normal group', function () { storedObject.ab_testing = {result: 'normal'}; const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOn); + expect(decoded).is.eql(expectedDecodedObjectWithIdAbOn); }); it('should not expose ID when everyone is in control group', function () { @@ -1117,13 +1298,13 @@ describe('ID5 ID System', function () { 'linkType': 0 }; const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).is.deep.equal(expectedDecodedObjectWithoutIdAbOn); + expect(decoded).is.eql(expectedDecodedObjectWithoutIdAbOn); }); it('should log A/B testing errors', function () { storedObject.ab_testing = {result: 'error'}; const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOff); + expect(decoded).is.eql(expectedDecodedObjectWithIdAbOff); sinon.assert.calledOnce(logErrorSpy); }); }); @@ -1133,7 +1314,7 @@ describe('ID5 ID System', function () { before(() => { attachIdSystem(id5System); }); - it('does not include an ext if not provided', function() { + it('does not include an ext if not provided', function () { const userId = { id5id: { uid: 'some-random-id-value' @@ -1143,11 +1324,11 @@ describe('ID5 ID System', function () { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'id5-sync.com', - uids: [{ id: 'some-random-id-value', atype: 1 }] + uids: [{id: 'some-random-id-value', atype: 1}] }); }); - it('includes ext if provided', function() { + it('includes ext if provided', function () { const userId = { id5id: { uid: 'some-random-id-value', @@ -1169,5 +1350,5 @@ describe('ID5 ID System', function () { }] }); }); - }) + }); }); From 31057ff81042505385358f80ec00c54b566b35f4 Mon Sep 17 00:00:00 2001 From: CondorXIO Date: Tue, 11 Feb 2025 17:22:30 +0200 Subject: [PATCH 0895/1097] CondorX Bid Adapter : initial release (#12643) * Add CondorX Bid Adapter * use inIframe utils function --- modules/condorxBidAdapter.js | 167 +++++++++++++ modules/condorxBidAdapter.md | 64 +++++ test/spec/modules/condorxBidAdapter_spec.js | 257 ++++++++++++++++++++ 3 files changed, 488 insertions(+) create mode 100644 modules/condorxBidAdapter.js create mode 100644 modules/condorxBidAdapter.md create mode 100644 test/spec/modules/condorxBidAdapter_spec.js diff --git a/modules/condorxBidAdapter.js b/modules/condorxBidAdapter.js new file mode 100644 index 00000000000..1ed87407852 --- /dev/null +++ b/modules/condorxBidAdapter.js @@ -0,0 +1,167 @@ +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { createTrackPixelHtml, inIframe } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +const BIDDER_CODE = 'condorx'; +const API_URL = 'https://api.condorx.io/cxb/get.json'; +const REQUEST_METHOD = 'GET'; +const MAX_SIZE_DEVIATION = 0.05; +const SUPPORTED_AD_SIZES = [ + [100, 100], [200, 200], [300, 250], [400, 200], [300, 200], [600, 600], [650, 1168], [236, 202], [1080, 1920], [300, 374] +]; + +function getBidRequestUrl(bidRequest, bidderRequest) { + if (bidRequest.params.url && bidRequest.params.url !== 'current url') { + return bidRequest.params.url; + } + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + return bidderRequest.refererInfo.page; + } + const pageUrl = inIframe() && document.referrer ? document.referrer : window.location.href; + return encodeURIComponent(pageUrl); +} + +function getTileImageUrl(tile) { + return tile.imageUrl.indexOf('http') === -1 ? 'https:' + tile.imageUrl : tile.imageUrl; +} + +function collectImpressionTrackers(tile, response) { + const trackers = [response.widgetViewPixel]; + if (!tile.trackers) return trackers; + const impressionTrackers = tile.trackers.impressionPixels || []; + const viewTrackers = tile.trackers.viewPixels || []; + return [...impressionTrackers, ...viewTrackers, ...trackers]; +} + +function parseNativeAdResponse(tile, response) { + return { + title: tile.title, + body: '', + image: { + url: getTileImageUrl(tile), + width: response.imageWidth, + height: response.imageHeight + }, + privacyLink: '', + clickUrl: tile.clickUrl, + displayUrl: tile.url, + cta: '', + sponsoredBy: tile.displayName, + impressionTrackers: collectImpressionTrackers(tile, response), + }; +} + +function parseBannerAdResponse(tile, response) { + if (tile.tag) { + return tile.tag; + } + let style = ''; + try { + const config = JSON.parse(response.widget.config); + const css = config.css || ''; + style = css ? `` : ''; + } catch (e) { + style = ''; + } + const title = tile.title && tile.title.trim() ? `` : ''; + const displayName = tile.displayName && title ? `` : ''; + const trackers = collectImpressionTrackers(tile, response) + .map((url) => createTrackPixelHtml(url)) + .join(''); + return `${style}`; +} + +function getAdSize(bidRequest) { + if (bidRequest.sizes && bidRequest.sizes.length > 0) { + return bidRequest.sizes[0]; + } else if (bidRequest.nativeParams && bidRequest.nativeParams.image && bidRequest.nativeParams.image.sizes) { + return bidRequest.nativeParams.image.sizes; + } + return [-1, -1]; +} + +function isValidAdSize([width, height]) { + if (!width || !height) { + return false; + } + return SUPPORTED_AD_SIZES.some(([supportedWidth, supportedHeight]) => { + if (supportedWidth === width && supportedHeight === height) { + return true; + } + const supportedRatio = supportedWidth / supportedHeight; + const ratioDeviation = supportedRatio / width * height; + return Math.abs(ratioDeviation - 1) <= MAX_SIZE_DEVIATION && (supportedWidth > width || (width - supportedWidth) / width <= MAX_SIZE_DEVIATION); + }); +} + +export const bidderSpec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + + isBidRequestValid: function (bidRequest) { + return bidRequest && + bidRequest.params && + bidRequest.params.hasOwnProperty('widget') && + bidRequest.params.hasOwnProperty('website') && + !isNaN(bidRequest.params.widget) && + !isNaN(bidRequest.params.website) && + isValidAdSize(getAdSize(bidRequest)); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + if (!validBidRequests) { + return []; + } + return validBidRequests.map(bidRequest => { + if (bidRequest.params) { + const mediaType = bidRequest.hasOwnProperty('nativeParams') ? 1 : 2; + const [imageWidth, imageHeight] = getAdSize(bidRequest); + const widgetId = bidRequest.params.widget; + const websiteId = bidRequest.params.website; + const pageUrl = getBidRequestUrl(bidRequest, bidderRequest); + const bidId = bidRequest.bidId; + let apiUrl = `${API_URL}?w=${websiteId}&wg=${widgetId}&u=${pageUrl}&p=0&ireqid=${bidId}&prebid=${mediaType}&imgw=${imageWidth}&imgh=${imageHeight}`; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) { + apiUrl += `&g=1&gc=${bidderRequest.consentString}`; + } + return { + url: apiUrl, + method: REQUEST_METHOD, + data: '' + }; + } + }).filter(Boolean); + }, + + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse.body || !serverResponse.body.tiles || !serverResponse.body.tiles.length) { + return []; + } + const response = serverResponse.body; + const isNative = response.pbtypeId === 1; + return response.tiles.map(tile => { + let bid = { + requestId: response.ireqId, + width: response.imageWidth, + height: response.imageHeight, + creativeId: tile.postId, + cpm: tile.pecpm || (tile.ecpm / 100), + currency: 'USD', + netRevenue: !!tile.pecpm, + ttl: 360, + meta: { advertiserDomains: tile.domain ? [tile.domain] : [] }, + }; + if (isNative) { + bid.native = parseNativeAdResponse(tile, response); + } else { + bid.ad = parseBannerAdResponse(tile, response); + } + return bid; + }); + } +}; + +registerBidder(bidderSpec); diff --git a/modules/condorxBidAdapter.md b/modules/condorxBidAdapter.md new file mode 100644 index 00000000000..2df8be7220a --- /dev/null +++ b/modules/condorxBidAdapter.md @@ -0,0 +1,64 @@ +# Overview + +``` +Module Name: CondorX's Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@condorx.io +``` + +# Description + +Module that connects to CondorX bidder to fetch bids. + +# Test Parameters +``` + var adUnits = [{ + code: 'condorx-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: "condorx", + params: { + widget: 'widget id by CondorX', + website: 'website id by CondorX', + url:'current url' + } + }] + }, + { + code: 'condorx-container-id', + mediaTypes: { + native: { + image: { + required: true, + sizes: [236, 202] + }, + title: { + required: true, + len: 100 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + } + } + }, + bids: [{ + bidder: "condorx", + params: { + widget: 'widget id by CondorX', + website: 'website id by CondorX', + url:'current url' + } + }] + } + }]; +``` diff --git a/test/spec/modules/condorxBidAdapter_spec.js b/test/spec/modules/condorxBidAdapter_spec.js new file mode 100644 index 00000000000..cd0873dd1b0 --- /dev/null +++ b/test/spec/modules/condorxBidAdapter_spec.js @@ -0,0 +1,257 @@ +import { expect } from 'chai'; +import { bidderSpec as adapterSpec } from 'modules/condorxBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('CondorX Bid Adapter Tests', function () { + let basicBidRequests; + let nativeBidData; + const defaultRequestParams = { + widget: 274572, + website: 195491, + url: 'current url' + }; + + beforeEach(function () { + basicBidRequests = [ + { + bidder: 'condorx', + params: defaultRequestParams + } + ]; + + nativeBidData = [ + { + bidder: 'condorx', + params: defaultRequestParams, + nativeParams: { + title: { + required: true, + len: 100 + }, + image: { + required: true, + sizes: [100, 100] + }, + sponsoredBy: { + required: true + } + } + } + ]; + }); + + describe('Bid Size Validation', function () { + const bid = { + bidder: 'condorx', + params: defaultRequestParams + }; + + it('should accept 300x250 size', function () { + bid.sizes = [[300, 250]]; + const isValid = adapterSpec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + + it('should accept 100x100 size', function () { + bid.sizes = [[100, 100]]; + const isValid = adapterSpec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + + it('should accept 600x600 size', function () { + bid.sizes = [[600, 600]]; + const isValid = adapterSpec.isBidRequestValid(bid); + expect(isValid).to.be.true; + }); + }); + + describe('Bid Request Validation', function () { + it('should validate a correct bid request', function () { + const validBid = { + bidder: 'condorx', + params: defaultRequestParams, + sizes: [[300, 250]] + }; + const isValid = adapterSpec.isBidRequestValid(validBid); + expect(isValid).to.be.true; + }); + + it('should invalidate an empty params bid request', function () { + const invalidBid = { + bidder: 'condorx', + params: {} + }; + const isValid = adapterSpec.isBidRequestValid(invalidBid); + expect(isValid).to.be.false; + }); + + it('should invalidate a bid request with invalid parameters', function () { + const invalidBid = { + bidder: 'condorx', + params: { + widget: '55765a', // Invalid value for widget + website: 195491, + url: 'current url' + }, + sizes: [[300, 250]] + }; + const isValid = adapterSpec.isBidRequestValid(invalidBid); + expect(isValid).to.be.false; + }); + }); + + describe('Request Building and HTTP Calls', function () { + it('should verify the API HTTP method', function () { + const request = adapterSpec.buildRequests(basicBidRequests)[0]; + expect(request.url).to.include('https://api.condorx.io/cxb/get.json'); + expect(request.method).to.equal('GET'); + }); + + it('should not mutate the original bid object', function () { + const originalBidRequests = utils.deepClone(basicBidRequests); + const request = adapterSpec.buildRequests(basicBidRequests); + expect(basicBidRequests).to.deep.equal(originalBidRequests); + }); + + it('should maintain the integrity of the native bid object', function () { + const originalBidRequests = utils.deepClone(nativeBidData); + const request = adapterSpec.buildRequests(nativeBidData); + expect(nativeBidData).to.deep.equal(originalBidRequests); + }); + + it('should correctly extract and validate request parameters', function () { + const request = adapterSpec.buildRequests(basicBidRequests)[0]; + const urlParams = new URL(request.url).searchParams; + expect(parseInt(urlParams.get('wg'))).to.exist.and.to.equal(basicBidRequests[0].params.widget); + expect(parseInt(urlParams.get('w'))).to.exist.and.to.equal(basicBidRequests[0].params.website); + }); + + it('should validate the custom URL parameter', function () { + const customUrl = 'https://i.am.url'; + basicBidRequests[0].params.url = customUrl; + const request = adapterSpec.buildRequests(basicBidRequests)[0]; + const urlParams = new URL(request.url).searchParams; + expect(urlParams.get('u')).to.exist.and.to.equal(customUrl); + }); + }); + + describe('Response Validation', function () { + let nativeResponseData; + let bannerResponseData; + + beforeEach(() => { + const baseResponse = { + tiles: [ + { + postId: '12345', + imageUrl: 'https://cdn.condorx.io/img/condorx_logo_500.jpg', + domain: 'condorx.test', + title: 'Test title', + clickUrl: '//click.test', + pecpm: 0.5, + url: '//url.test', + displayName: 'Test sponsoredBy', + trackers: { + impressionPixels: ['//impression.test'], + viewPixels: ['//view.test'], + } + } + ], + imageWidth: 300, + imageHeight: 250, + ireqId: 'condorx121212', + widgetViewPixel: '//view.pixel', + }; + + nativeResponseData = { + ...baseResponse, + pbtypeId: 1, + }; + + bannerResponseData = { + ...baseResponse, + pbtypeId: 2, + widget: { + config: '{"css":".__condorx_banner_title{display:block!important;}"}' + }, + }; + }); + + it('should return an empty array for missing response', function () { + const result = adapterSpec.interpretResponse({}, []); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should return an empty array for no bids', function () { + const noBidsResponse = { + tiles: [], + imageWidth: 300, + imageHeight: 250, + ireqId: 'condorx121212', + pbtypeId: 2, + widgetViewPixel: '//view.pixel', + }; + const request = adapterSpec.buildRequests(basicBidRequests)[0]; + const result = adapterSpec.interpretResponse({ body: noBidsResponse }, request); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should correctly interpret a native response', function () { + const expectedNativeResult = [ + { + requestId: 'condorx121212', + cpm: 0.5, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '12345', + ttl: 360, + meta: { + advertiserDomains: ['condorx.test'] + }, + native: { + title: 'Test title', + body: '', + image: { + url: 'https://cdn.condorx.io/img/condorx_logo_500.jpg', + width: 300, + height: 250 + }, + privacyLink: '', + clickUrl: '//click.test', + displayUrl: '//url.test', + cta: '', + sponsoredBy: 'Test sponsoredBy', + impressionTrackers: ['//impression.test', '//view.test', '//view.pixel'], + }, + } + ]; + const request = adapterSpec.buildRequests(basicBidRequests)[0]; + const result = adapterSpec.interpretResponse({ body: nativeResponseData }, request); + expect(result).to.deep.equal(expectedNativeResult); + }); + + it('should correctly interpret a banner response', function () { + const expectedBannerResult = [ + { + requestId: 'condorx121212', + cpm: 0.5, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '12345', + ttl: 360, + meta: { + advertiserDomains: ['condorx.test'] + }, + ad: ``, + } + ]; + const request = adapterSpec.buildRequests(basicBidRequests)[0]; + const result = adapterSpec.interpretResponse({ body: bannerResponseData }, request); + expect(result).to.deep.equal(expectedBannerResult); + }); + }); +}); From 337c6455a4aa2081c545b9042fd9b3811ed66e6a Mon Sep 17 00:00:00 2001 From: Helly Alpern <157289260+hellyalpern@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:00:40 +0200 Subject: [PATCH 0896/1097] Vidazoo bidder: pass coppa flag on syncs (#12755) * Add COPPA support to bidder user sync logic Extend `getUserSyncs` with a COPPA parameter for compliance purposes. This ensures the generated sync URLs handle COPPA requirements appropriately during user synchronization. * Add COPPA support to user sync tests and logic Incorporate COPPA handling into `getUserSyncs` function and related unit tests. Updated sync URL generation to include the COPPA parameter for compliance, ensuring proper configuration and testing across adapters. --- libraries/vidazooUtils/bidderUtils.js | 4 ++-- test/spec/modules/excoBidAdapter_spec.js | 19 +++++++++++++++---- test/spec/modules/illuminBidAdapter_spec.js | 17 ++++++++++++++--- test/spec/modules/kueezRtbBidAdapter_spec.js | 19 +++++++++++++++---- test/spec/modules/shinezRtbBidAdapter_spec.js | 19 +++++++++++++++---- test/spec/modules/tagorasBidAdapter_spec.js | 19 +++++++++++++++---- .../modules/twistDigitalBidAdapter_spec.js | 19 +++++++++++++++---- test/spec/modules/vidazooBidAdapter_spec.js | 19 +++++++++++++++---- 8 files changed, 106 insertions(+), 29 deletions(-) diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 9de95248c74..83317f932b9 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -171,10 +171,10 @@ export function createUserSyncGetter(options = { const {iframeEnabled, pixelEnabled} = syncOptions; const {gdprApplies, consentString = ''} = gdprConsent; const {gppString, applicableSections} = gppConsent; + const coppa = config.getConfig('coppa') ? 1 : 0; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; - + let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}&coppa=${encodeURIComponent((coppa))}`; if (gppString && applicableSections?.length) { params += '&gpp=' + encodeURIComponent(gppString); params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); diff --git a/test/spec/modules/excoBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js index be85d7735d6..55acb612fe6 100644 --- a/test/spec/modules/excoBidAdapter_spec.js +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -440,7 +440,7 @@ describe('ExcoBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -448,7 +448,7 @@ describe('ExcoBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -456,10 +456,21 @@ describe('ExcoBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://cs.exco-pb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://cs.exco-pb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); - }) + }); + + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); }); describe('interpret response', function () { diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index b4578a8d61f..83f64b2bd6b 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -440,7 +440,7 @@ describe('IlluminBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -448,7 +448,7 @@ describe('IlluminBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -456,10 +456,21 @@ describe('IlluminBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.illumin.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.illumin.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); }) + + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); }); describe('interpret response', function () { diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 0bb65d0aef0..9885aecf395 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -448,7 +448,7 @@ describe('KueezRtbBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -456,7 +456,7 @@ describe('KueezRtbBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -464,11 +464,22 @@ describe('KueezRtbBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); }); + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); + it('should generate url with consent data', function () { const gdprConsent = { gdprApplies: true, @@ -483,7 +494,7 @@ describe('KueezRtbBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); expect(result).to.deep.equal([{ - 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&gpp=gpp_string&gpp_sid=7', + 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&coppa=1&gpp=gpp_string&gpp_sid=7', 'type': 'image' }]); }); diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 892d88b3b7b..883646d5c27 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -442,7 +442,7 @@ describe('ShinezRtbBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -450,7 +450,7 @@ describe('ShinezRtbBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -458,10 +458,21 @@ describe('ShinezRtbBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.sweetgum.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.sweetgum.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); - }) + }); + + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); }); describe('interpret response', function () { diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 8d8c8cb62b7..b9556d27831 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -438,7 +438,7 @@ describe('TagorasBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -446,7 +446,7 @@ describe('TagorasBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -454,11 +454,22 @@ describe('TagorasBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.tagoras.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.tagoras.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); }); + it('should have valid user sync with coppa 1 on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); + it('should generate url with consent data', function () { const gdprConsent = { gdprApplies: true, @@ -473,7 +484,7 @@ describe('TagorasBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); expect(result).to.deep.equal([{ - 'url': 'https://sync.tagoras.io/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&gpp=gpp_string&gpp_sid=7', + 'url': 'https://sync.tagoras.io/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&coppa=1&gpp=gpp_string&gpp_sid=7', 'type': 'image' }]); }); diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 4ddac3261f1..122784814c4 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -630,7 +630,7 @@ describe('TwistDigitalBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -638,7 +638,7 @@ describe('TwistDigitalBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -646,10 +646,21 @@ describe('TwistDigitalBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.twist.win/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.twist.win/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); - }) + }); + + it('should have valid user sync with coppa 1 on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); }); describe('interpret response', function () { diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 01d7aa20e53..1f60a1cffbb 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -649,7 +649,7 @@ describe('VidazooBidAdapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -657,7 +657,7 @@ describe('VidazooBidAdapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' }]); }); @@ -665,10 +665,21 @@ describe('VidazooBidAdapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', 'type': 'image' }]); - }) + }); + + it('should have valid user sync with coppa 1 on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); + }); }); describe('interpret response', function () { From d7f915eff3e305f4d0f0e399430a361dfa9ecdbb Mon Sep 17 00:00:00 2001 From: Luca Corbo Date: Wed, 12 Feb 2025 18:35:25 +0100 Subject: [PATCH 0897/1097] wurfl Rtd Provider : fix invalid types for pxratio and js in ORTB2 device data (#12721) * wurflRtdProvider: fix invalid types for pxratio and js in ORTB2 device data This commit corrects the data types for pixratio and js to match the requirements of the ORTB2 device specification. * wurflRtdProvider: apply linter suggestions --- modules/wurflRtdProvider.js | 18 ++++++++-- test/spec/modules/wurflRtdProvider_spec.js | 39 +++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js index 7bb785366bc..7dfb68f298c 100644 --- a/modules/wurflRtdProvider.js +++ b/modules/wurflRtdProvider.js @@ -185,8 +185,8 @@ export const enrichBidderRequest = (reqBidsConfigObj, bidderCode, wurflData) => enrichOrtb2DeviceData('h', wurflData.resolution_height, device, ortb2data); enrichOrtb2DeviceData('w', wurflData.resolution_width, device, ortb2data); enrichOrtb2DeviceData('ppi', wurflData.pixel_density, device, ortb2data); - enrichOrtb2DeviceData('pxratio', wurflData.density_class, device, ortb2data); - enrichOrtb2DeviceData('js', wurflData.ajax_support_javascript, device, ortb2data); + enrichOrtb2DeviceData('pxratio', toNumber(wurflData.density_class), device, ortb2data); + enrichOrtb2DeviceData('js', toNumber(wurflData.ajax_support_javascript), device, ortb2data); } ortb2data.device.ext['wurfl'] = wurflData mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidderCode]: ortb2data }); @@ -245,6 +245,20 @@ function enrichOrtb2DeviceData(key, value, device, ortb2data) { ortb2data.device[key] = value; } +/** + * toNumber converts a given value to a number. + * Returns `undefined` if the conversion results in `NaN`. + * @param {any} value - The value to convert to a number. + * @returns {number|undefined} The converted number, or `undefined` if the conversion fails. + */ +export function toNumber(value) { + if (value === '' || value === null) { + return undefined; + } + const num = Number(value); + return Number.isNaN(num) ? undefined : num; +} + /** * onAuctionEndEvent is called when the auction ends * @param {Object} auctionDetails Auction details diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js index a683be5304c..fa6ea2e642f 100644 --- a/test/spec/modules/wurflRtdProvider_spec.js +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -4,6 +4,7 @@ import { lowEntropyData, wurflSubmodule, makeOrtb2DeviceType, + toNumber, } from 'modules/wurflRtdProvider'; import * as ajaxModule from 'src/ajax'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; @@ -116,8 +117,8 @@ describe('wurflRtdProvider', function () { h: 1920, w: 1080, ppi: 443, - pxratio: '3.0', - js: true, + pxratio: 3.0, + js: 1, ext: { wurfl: { advertised_browser: 'Chrome Mobile', @@ -166,8 +167,8 @@ describe('wurflRtdProvider', function () { h: 1920, w: 1080, ppi: 443, - pxratio: '3.0', - js: true, + pxratio: 3.0, + js: 1, ext: { wurfl: { advertised_device_os: 'Android', @@ -455,4 +456,34 @@ describe('wurflRtdProvider', function () { expect(result).to.be.undefined; }); }); + + describe('toNumber', function () { + it('converts valid numbers', function () { + expect(toNumber(42)).to.equal(42); + expect(toNumber(3.14)).to.equal(3.14); + expect(toNumber('100')).to.equal(100); + expect(toNumber('3.14')).to.equal(3.14); + expect(toNumber(' 50 ')).to.equal(50); + }); + + it('converts booleans correctly', function () { + expect(toNumber(true)).to.equal(1); + expect(toNumber(false)).to.equal(0); + }); + + it('handles special cases', function () { + expect(toNumber(null)).to.be.undefined; + expect(toNumber('')).to.be.undefined; + }); + + it('returns undefined for non-numeric values', function () { + expect(toNumber('abc')).to.be.undefined; + expect(toNumber(undefined)).to.be.undefined; + expect(toNumber(NaN)).to.be.undefined; + expect(toNumber({})).to.be.undefined; + expect(toNumber([1, 2, 3])).to.be.undefined; + // WURFL.js cannot return [] so it is safe to not handle and return undefined + expect(toNumber([])).to.equal(0); + }); + }); }); From 53e9b7a8f810814a5943d2cfe657db5505ff5300 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Thu, 13 Feb 2025 05:16:02 +1100 Subject: [PATCH 0898/1097] Pass on sizes on native ad requests to ad server. (#12742) --- modules/adnuntiusBidAdapter.js | 2 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 9463944a824..6edf2fa17df 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -333,7 +333,7 @@ export const spec = { if (maxDeals > 0) { adUnit.maxDeals = maxDeals; } - if (mediaType === BANNER && mediaTypeData && mediaTypeData.sizes) { + if (mediaType !== VIDEO && mediaTypeData && mediaTypeData.sizes) { adUnit.dimensions = mediaTypeData.sizes; } networks[network].adUnits.push(adUnit); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 8941f97dc0d..4f595f81c18 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -116,6 +116,7 @@ describe('adnuntiusBidAdapter', function () { }, mediaTypes: { native: { + sizes: [[200, 200], [300, 300]], ortb: { assets: [{ id: 1, @@ -1636,7 +1637,15 @@ describe('adnuntiusBidAdapter', function () { }); }); - describe('interpretNativeResponse', function () { + describe('Native ads handling', function () { + it('should pass requests on correctly', function () { + const request = spec.buildRequests(nativeBidderRequest.bid, {}); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('bid'); + expect(request[0]).to.have.property('data'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"0000000000000551","targetId":"adn-0000000000000551","adType":"NATIVE","nativeRequest":{"assets":[{"id":1,"required":1,"img":{"type":3,"w":250,"h":250}}]},"dimensions":[[200,200],[300,300]]}]}'); + }); + it('should return valid response when passed valid server response', function () { const interpretedResponse = spec.interpretResponse(nativeResponse, nativeBidderRequest); const ad = nativeResponse.body.adUnits[0].ads[0] From a0b4c45fda88244d37e6a4a983f77cac2b46f71f Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Thu, 13 Feb 2025 05:26:24 +1100 Subject: [PATCH 0899/1097] Make adserver end points configurable from client. (#12743) --- modules/adnuntiusBidAdapter.js | 44 +++++++++++++++++-- test/spec/modules/adnuntiusBidAdapter_spec.js | 14 +++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 6edf2fa17df..cf49de6d134 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -9,8 +9,6 @@ const BIDDER_CODE_DEAL_ALIAS_BASE = 'adndeal'; const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => { return BIDDER_CODE_DEAL_ALIAS_BASE + num; }); -const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; -const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; const MAXIMUM_DEALS_LIMIT = 5; @@ -18,6 +16,42 @@ const VALID_BID_TYPES = ['netBid', 'grossBid']; const METADATA_KEY = 'adn.metaData'; const METADATA_KEY_SEPARATOR = '@@@'; +const ENVS = { + localhost: { + id: 'localhost', + as: 'localhost:8078' + }, + lcl: { + id: 'lcl', + as: 'adserver.dev.lcl.test' + }, + andemu: { + id: 'andemu', + as: '10.0.2.2:8078' + }, + dev: { + id: 'dev', + as: 'adserver.dev.adnuntius.com' + }, + staging: { + id: 'staging', + as: 'adserver.staging.adnuntius.com' + }, + production: { + id: 'production', + as: 'ads.adnuntius.delivery', + asEu: 'europe.delivery.adnuntius.com' + }, + cloudflare: { + id: 'cloudflare', + as: 'ads.adnuntius.delivery' + }, + limited: { + id: 'limited', + as: 'limited.delivery.adnuntius.com' + } +}; + export const misc = { findHighestPrice: function(arr, bidType) { return arr.reduce((highest, cur) => { @@ -344,7 +378,11 @@ export const spec = { const networkKeys = Object.keys(networks); for (let j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; - const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL + let requestURL = gdprApplies ? ENVS.production.asEu : ENVS.production.as; + if (bidderConfig.env && ENVS[bidderConfig.env]) { + requestURL = ENVS[bidderConfig.env][bidderConfig.endPointType || 'as']; + } + requestURL = (bidderConfig.protocol || 'https') + '://' + requestURL + '/i'; requests.push({ method: 'POST', url: requestURL + '?' + queryParamsAndValues.join('&'), diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 4f595f81c18..96e4b87883c 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' import { misc, spec } from 'modules/adnuntiusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { config } from 'src/config.js'; +import {config, newConfig} from 'src/config.js'; import * as utils from 'src/utils.js'; import { getStorageManager } from 'src/storageManager.js'; import { getGlobal } from '../../../src/prebidGlobal'; @@ -53,6 +53,7 @@ describe('adnuntiusBidAdapter', function () { const viewport = win.innerWidth + 'x' + win.innerHeight; const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid&screen=${screen}&viewport=${viewport}`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; + const LOCALHOST_URL = `http://localhost:8078/i?tzo=${tzo}&format=prebid&screen=${screen}&viewport=${viewport}&userId=${usi}`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&screen=${screen}&viewport=${viewport}&userId=${usi}`; @@ -854,6 +855,17 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"context":"https://canonical.com/something-else.html","canonical":"https://canonical.com/page.html"}'); }); + it('should pass for different end points in config', function () { + config.setConfig({ + env: 'localhost', + protocol: 'http' + }) + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(LOCALHOST_URL); + }); + it('Test requests with no local storage', function () { storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{}])); const request = spec.buildRequests(bidderRequests, {}); From 34f6070901324479deeb1cdee99c8a269d943d87 Mon Sep 17 00:00:00 2001 From: pm-asit-sahoo <102290803+pm-asit-sahoo@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:14:38 +0530 Subject: [PATCH 0900/1097] PubMatic Analytics Adapter: Sending 'ds' in tracker (#12735) * added code for sending 'dspid' in tracker * added unit test cases for dspid in tracker * updated name from dspid to ds --- modules/pubmaticAnalyticsAdapter.js | 4 ++++ test/spec/modules/pubmaticAnalyticsAdapter_spec.js | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 8f9dead2cba..6afafa6f21d 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -522,6 +522,10 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&origbidid=' + enc(winningBid?.bidResponse?.partnerImpId || winningBid?.bidResponse?.prebidBidId || winningBid.bidId); pixelURL += '&di=' + enc(winningBid?.bidResponse?.dealId || OPEN_AUCTION_DEAL_ID); + const ds = winningBid.bidResponse?.meta ? getMetadata(winningBid.bidResponse.meta).ds : undefined; + if (ds) { + pixelURL += '&ds=' + enc(ds); + } pg && (pixelURL += '&pb=' + enc(pg)); pixelURL += '&plt=' + enc(getDevicePlatform()); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 263d0416897..e1be6cb314c 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -75,6 +75,9 @@ const BID = { 'floorRuleValue': 1.1, 'floorValue': 1.1 }, + 'meta': { + 'demandSource': 1208, + }, getStatusCode() { return 1; } @@ -103,7 +106,8 @@ const BID2 = Object.assign({}, BID, { 'hb_source': 'server' }, meta: { - advertiserDomains: ['example.com'] + advertiserDomains: ['example.com'], + 'demandSource': 1208, } }); @@ -664,6 +668,7 @@ describe('pubmatic analytics adapter', function () { expect(data.ss).to.equal('1'); expect(data.fskp).to.equal('0'); expect(data.af).to.equal('video'); + expect(data.ds).to.equal('1208'); }); it('Logger : do not log floor fields when prebids floor shows noData in location property', function() { From b4253d9f677de640660431fd468a08ef9d538aa9 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 13 Feb 2025 10:38:12 -0500 Subject: [PATCH 0901/1097] Core: mild mergedeep speedup (#12718) * Core: mergedeep speedup uses direct assignment, loops over top level * Update utils.js * Update utils.js * linting --- src/utils.js | 71 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/utils.js b/src/utils.js index 015b1142d47..a412028b4b6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1023,38 +1023,51 @@ export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { } export function mergeDeep(target, ...sources) { - if (!sources.length) return target; - const source = sources.shift(); - - if (isPlainObject(target) && isPlainObject(source)) { - for (const key in source) { - if (isPlainObject(source[key])) { - if (!target[key]) Object.assign(target, { [key]: {} }); - mergeDeep(target[key], source[key]); - } else if (isArray(source[key])) { - if (!target[key]) { - Object.assign(target, { [key]: [...source[key]] }); - } else if (isArray(target[key])) { - source[key].forEach(obj => { - let addItFlag = 1; - for (let i = 0; i < target[key].length; i++) { - if (deepEqual(target[key][i], obj)) { - addItFlag = 0; - break; - } - } - if (addItFlag) { - target[key].push(obj); - } - }); - } + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + if (!isPlainObject(source)) { + continue; + } + mergeDeepHelper(target, source); + } + return target; +} + +function mergeDeepHelper(target, source) { + // quick check + if (!isPlainObject(target) || !isPlainObject(source)) { + return; + } + + const keys = Object.keys(source); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key === '__proto__' || key === 'constructor') { + continue; + } + const val = source[key]; + + if (isPlainObject(val)) { + if (!target[key]) { + target[key] = {}; + } + mergeDeepHelper(target[key], val); + } else if (Array.isArray(val)) { + if (!Array.isArray(target[key])) { + target[key] = [...val]; } else { - Object.assign(target, { [key]: source[key] }); + // deduplicate + val.forEach(obj => { + if (!target[key].some(item => deepEqual(item, obj))) { + target[key].push(obj); + } + }); } + } else { + // direct assignment + target[key] = val; } } - - return mergeDeep(target, ...sources); } /** @@ -1245,7 +1258,7 @@ export function hasNonSerializableProperty(obj, checkedObjects = new Set()) { * * @param {Array} collection - Array of objects. * @param {String} key - Key of nested property. - * @returns {any, undefined} - Value of nested property. + * @returns {any|undefined} - Value of nested property. */ export function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { From da36d6ae4e57d74c2d78ba7a27c9439a109d5bfb Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Thu, 13 Feb 2025 10:41:10 -0500 Subject: [PATCH 0902/1097] bugfix: looking at wrong config location for bidderSettings, update to support both (#12752) --- modules/amxBidAdapter.js | 40 +++++--- test/spec/modules/amxBidAdapter_spec.js | 127 +++++++++++++++--------- 2 files changed, 109 insertions(+), 58 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 508c4d6a0c7..9a3c61135a0 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -15,6 +15,7 @@ import { import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { fetch } from '../src/ajax.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const BIDDER_CODE = 'amx'; const storage = getStorageManager({ bidderCode: BIDDER_CODE }); @@ -161,8 +162,8 @@ function convertRequest(bid) { const au = bid.params != null && - typeof bid.params.adUnitId === 'string' && - bid.params.adUnitId !== '' + typeof bid.params.adUnitId === 'string' && + bid.params.adUnitId !== '' ? bid.params.adUnitId : bid.adUnitCode; @@ -298,6 +299,14 @@ function buildReferrerInfo(bidderRequest) { }; } +const alternateCodesAllowed = (bidderSettings, currentBidder) => + !!( + bidderSettings.amx ?? + bidderSettings[currentBidder] ?? + bidderSettings.standard ?? + {} + ).allowAlternateBidderCodes; + const isTrue = (boolValue) => boolValue === true || boolValue === 1 || boolValue === 'true'; @@ -323,8 +332,7 @@ export const spec = { : { bidderRequestsCount: 0, bidderWinsCount: 0, - bidRequestsCount: 0, - }; + bidRequestsCount: 0 }; const payload = { a: generateUUID(), @@ -418,9 +426,9 @@ export const spec = { const output = []; let hasFrame = false; - _each(serverResponses, function({ body: response }) { + _each(serverResponses, function ({ body: response }) { if (response != null && response.p != null && response.p.hreq) { - _each(response.p.hreq, function(syncPixel) { + _each(response.p.hreq, function (syncPixel) { const pixelType = syncPixel.indexOf('__st=iframe') !== -1 ? 'iframe' : 'image'; if (syncOptions.iframeEnabled || pixelType === 'image') { @@ -454,9 +462,10 @@ export const spec = { setUIDSafe(response.am); } - const bidderSettings = config.getConfig('bidderSettings'); - const settings = bidderSettings?.amx ?? bidderSettings?.standard ?? {}; - const allowAlternateBidderCodes = !!settings.allowAlternateBidderCodes; + let { bidderSettings } = getGlobal(); + const currentBidder = config.getCurrentBidder(); + const allowAlternateBidderCodes = alternateCodesAllowed(bidderSettings ?? {}, currentBidder) || + alternateCodesAllowed(config.getConfig('bidderSettings') ?? {}, currentBidder); return flatMap(Object.keys(response.r), (bidID) => { return flatMap(response.r[bidID], (siteBid) => @@ -470,10 +479,16 @@ export const spec = { const size = resolveSize(bid, request.data, bidID); const defaultExpiration = mediaType === BANNER ? 240 : 300; - const { bc: bidderCode, ds: demandSource } = bid.ext ?? {}; + const { + bc: bidderCode, + ds: demandSource, + dsp: dspCode, + } = bid.ext ?? {}; return { - ...(bidderCode != null && allowAlternateBidderCodes ? { bidderCode } : {}), + ...(bidderCode != null && allowAlternateBidderCodes + ? { bidderCode } + : {}), requestId: bidID, cpm: bid.price, width: size[0], @@ -485,6 +500,7 @@ export const spec = { meta: { advertiserDomains: bid.adomain, mediaType, + ...(dspCode != null ? { networkId: dspCode } : {}), ...(demandSource != null ? { demandSource } : {}), }, mediaType, @@ -559,7 +575,7 @@ export const spec = { body: payload, keepalive: true, withCredentials: true, - method: 'POST' + method: 'POST', }).catch((_e) => { // do nothing; ignore errors }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 5769afa1b2b..680579aa299 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -5,6 +5,7 @@ import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; +import { getGlobal } from '../../../src/prebidGlobal'; const sampleRequestId = '82c91e127a9b93e'; const sampleDisplayAd = ``; @@ -582,52 +583,86 @@ describe('AmxBidAdapter', () => { expect(parsed).to.eql([]); }); - it('will read an bidderCode override from bid.ext.prebid.meta', () => { - const currentConfig = config.getConfig(); - config.setConfig({ - ...currentConfig, - bidderSettings: { - amx: { - allowAlternateBidderCodes: true - } - } - }); - - const parsed = spec.interpretResponse( - { body: { - ...sampleServerResponse, - r: { - [sampleRequestId]: [{ - ...sampleServerResponse.r[sampleRequestId][0], - b: [{ - ...sampleServerResponse.r[sampleRequestId][0].b[0], - ext: { - bc: 'amx-pmp', - ds: 'example', - } - }] - }] - }}}, - baseRequest - ); + const cases = [ + [ + 'pbjs.bidderSettings', + (conf) => { + const before = getGlobal().bidderSettings; + getGlobal().bidderSettings = conf; + return before; + }, + (before) => { + getGlobal().bidderSettings = before; + }, + ], + [ + 'setConfig / bidderSettings (legacy)', + (conf) => { + const before = config.getConfig(); + config.setConfig({ + bidderSettings: conf, + }); + + return before; + }, + (before) => { + config.setConfig(before); + }, + ], + ]; - config.setConfig(currentConfig); - expect(parsed.length).to.equal(1); // we removed one + cases.forEach(([name, setup, teardown]) => { + it(`will read an bidderCode override from bid.ext.prebid.meta, set with ${name}`, () => { + const currentConfig = setup({ + amx: { + allowAlternateBidderCodes: true, + }, + }); + const parsed = spec.interpretResponse( + { + body: { + ...sampleServerResponse, + r: { + [sampleRequestId]: [ + { + ...sampleServerResponse.r[sampleRequestId][0], + b: [ + { + ...sampleServerResponse.r[sampleRequestId][0].b[0], + ext: { + bc: 'amx-pmp', + ds: 'example', + dsp: 'example-dsp', + }, + }, + ], + }, + ], + }, + }, + }, + baseRequest + ); - // we should have display, video, display - expect(parsed[0]).to.deep.equal({ - ...baseBidResponse, - meta: { - ...baseBidResponse.meta, + teardown(currentConfig); + expect(parsed.length).to.equal(1); // we removed one + + // we should have display, video, display + expect(parsed[0]).to.deep.equal({ + ...baseBidResponse, + meta: { + ...baseBidResponse.meta, + mediaType: BANNER, + demandSource: 'example', + networkId: 'example-dsp', + }, mediaType: BANNER, - demandSource: 'example' - }, - mediaType: BANNER, - bidderCode: 'amx-pmp', - width: 300, - height: 600, // from the bid itself - ttl: 90, - ad: sampleDisplayAd, + bidderCode: 'amx-pmp', + width: 300, + height: 600, // from the bid itself + ttl: 90, + ad: sampleDisplayAd, + }); }); }); @@ -758,14 +793,14 @@ describe('AmxBidAdapter', () => { ]); const [request] = server.requests; - request.respond(204, {'Content-Type': 'text/html'}, null); + request.respond(204, { 'Content-Type': 'text/html' }, null); expect(request.url).to.equal('https://1x1.a-mo.net/e'); if (typeof Request !== 'undefined' && 'keepalive' in Request.prototype) { expect(request.fetch.request.keepalive).to.equal(true); } - const {c: common, e: events} = JSON.parse(request.requestBody) + const { c: common, e: events } = JSON.parse(request.requestBody); expect(common).to.deep.equal({ V: '$prebid.version$', vg: '$$PREBID_GLOBAL$$', @@ -775,7 +810,7 @@ describe('AmxBidAdapter', () => { expect(events.length).to.equal(1); const [event] = events; - expect(event.n).to.equal('g_pbto') + expect(event.n).to.equal('g_pbto'); expect(event.A).to.equal('example'); expect(event.mid).to.equal('tag-id'); expect(event.cn).to.equal(300); From 756ff37633804415f652aa73751fac87c6ac4a88 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Thu, 13 Feb 2025 11:05:41 -0500 Subject: [PATCH 0903/1097] Sonobi - Changed HTTP method to POST. Sending POST data as form url encoded (#12751) --- modules/sonobiBidAdapter.js | 9 ++-- test/spec/modules/sonobiBidAdapter_spec.js | 57 +++++++++++++--------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 720ce8ee269..9f8396d93e5 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, isFn, isPlainObject } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, isFn, isPlainObject, parseQueryStringParameters } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; @@ -170,10 +170,13 @@ export const spec = { } return { - method: 'GET', + method: 'POST', url: url, + options: { + contentType: 'application/x-www-form-urlencoded' + }, withCredentials: true, - data: payload, + data: parseQueryStringParameters(payload), bidderRequests: validBidRequests }; }, diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 75da1983f0c..ad6acb6dcba 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -4,9 +4,18 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { userSync } from '../../../src/userSync.js'; import { config } from 'src/config.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; - +import { parseQS } from '../../../src/utils' describe('SonobiBidAdapter', function () { const adapter = newBidder(spec) + const originalBuildRequests = spec.buildRequests; + spec.buildRequests = (...args) => { + const result = originalBuildRequests(...args); + if (result && result.data) { + result.data = parseQS(result.data); // Translate back into a js object so we can validate it + } + + return result; + } describe('.code', function () { it('should return a bidder code of sonobi', function () { expect(spec.code).to.equal('sonobi') @@ -408,21 +417,21 @@ describe('SonobiBidAdapter', function () { } }; const bidRequests = spec.buildRequests(bidRequest, { ...bidderRequests, ortb2 }); - expect(bidRequests.data.fpd).to.equal(JSON.stringify(ortb2)); + expect(bidRequests.data.fpd).to.equal(encodeURIComponent(JSON.stringify(ortb2))); }); it('should populate coppa as 1 if set in config', function () { config.setConfig({ coppa: true }); const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.coppa).to.equal(1); + expect(bidRequests.data.coppa).to.equal(encodeURIComponent(1)); }); it('should populate coppa as 0 if set in config', function () { config.setConfig({ coppa: false }); const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.coppa).to.equal(0); + expect(bidRequests.data.coppa).to.equal(encodeURIComponent(0)); }); it('should have storageAllowed set to true', function () { @@ -433,13 +442,13 @@ describe('SonobiBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) const bidRequestsPageViewID = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') - expect(bidRequests.data.key_maker).to.deep.equal(JSON.stringify(keyMakerData)) + expect(bidRequests.method).to.equal('POST') + expect(decodeURIComponent(bidRequests.data.key_maker)).to.deep.equal(JSON.stringify((keyMakerData))) expect(bidRequests.data.ref).not.to.be.empty expect(bidRequests.data.s).not.to.be.empty expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) - expect(JSON.parse(bidRequests.data.iqid).pcid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) - expect(JSON.parse(bidRequests.data.iqid).pcidDate).to.match(/^[0-9]{13}$/) + expect(JSON.parse(decodeURIComponent(bidRequests.data.iqid)).pcid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) + expect(JSON.parse(decodeURIComponent(bidRequests.data.iqid)).pcidDate).to.match(/^[0-9]{13}$/) expect(bidRequests.data.hfa).to.not.exist expect(bidRequests.bidderRequests).to.eql(bidRequest); expect(bidRequests.data.ref).to.equal('overrides_top_window_location'); @@ -449,24 +458,24 @@ describe('SonobiBidAdapter', function () { it('should return a properly formatted request with GDPR applies set to true', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') + expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr).to.equal('true') - expect(bidRequests.data.consent_string).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') + expect(bidRequests.data.consent_string).to.equal(encodeURIComponent('BOJ/P2HOJ/P2HABABMAAAAAZ+A==')) }) it('should return a properly formatted request with referer', function () { bidRequest[0].params.referrer = '' const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.data.ref).to.equal('https://example.com') + expect(bidRequests.data.ref).to.equal(encodeURIComponent('https://example.com')) }) it('should return a properly formatted request with GDPR applies set to false', function () { bidderRequests.gdprConsent.gdprApplies = false; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') + expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr).to.equal('false') - expect(bidRequests.data.consent_string).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') + expect(bidRequests.data.consent_string).to.equal(encodeURIComponent('BOJ/P2HOJ/P2HABABMAAAAAZ+A==')) }) it('should return a properly formatted request with GDPR applies set to false with no consent_string param', function () { let bidderRequests = { @@ -484,7 +493,7 @@ describe('SonobiBidAdapter', function () { }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') + expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr).to.equal('false') expect(bidRequests.data).to.not.include.keys('consent_string') }) @@ -504,7 +513,7 @@ describe('SonobiBidAdapter', function () { }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') + expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.gdpr).to.equal('true') expect(bidRequests.data).to.not.include.keys('consent_string') }) @@ -513,7 +522,7 @@ describe('SonobiBidAdapter', function () { bidRequest[1].params.hfa = 'hfakey' const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') - expect(bidRequests.method).to.equal('GET') + expect(bidRequests.method).to.equal('POST') expect(bidRequests.data.ref).not.to.be.empty expect(bidRequests.data.s).not.to.be.empty expect(bidRequests.data.hfa).to.equal('hfakey') @@ -533,18 +542,18 @@ describe('SonobiBidAdapter', function () { it('should set ius as 0 if Sonobi cannot drop iframe pixels', function () { userSync.canBidderRegisterSync.returns(false); const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.ius).to.equal(0); + expect(bidRequests.data.ius).to.equal(encodeURIComponent(0)); }); it('should set ius as 1 if Sonobi can drop iframe pixels', function () { userSync.canBidderRegisterSync.returns(true); const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.ius).to.equal(1); + expect(bidRequests.data.ius).to.equal(encodeURIComponent(1)); }); it('should return a properly formatted request with schain defined', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) + expect(JSON.parse(decodeURIComponent(bidRequests.data.schain))).to.deep.equal(bidRequest[0].schain) }); it('should return a properly formatted request with eids as a JSON-encoded set of eids', function () { @@ -572,10 +581,10 @@ describe('SonobiBidAdapter', function () { ]; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.method).to.equal('POST'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.eids)).to.eql([ + expect(JSON.parse(decodeURIComponent(bidRequests.data.eids))).to.eql([ { 'source': 'pubcid.org', 'uids': [ @@ -593,7 +602,7 @@ describe('SonobiBidAdapter', function () { bidRequest[1].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.method).to.equal('POST'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; expect(bidRequests.data.userid).to.be.undefined; @@ -601,7 +610,7 @@ describe('SonobiBidAdapter', function () { it('should return a properly formatted request with keywrods included as a csv of strings', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.kw).to.equal('sports,news,some_other_keyword'); + expect(bidRequests.data.kw).to.equal(encodeURIComponent('sports,news,some_other_keyword')); }); it('should return a properly formatted request with us_privacy included', function () { @@ -626,7 +635,7 @@ describe('SonobiBidAdapter', function () { describe('.interpretResponse', function () { const bidRequests = { - 'method': 'GET', + 'method': 'POST', 'url': 'https://apex.go.sonobi.com/trinity.json', 'withCredentials': true, 'data': { From 7a6673324335f7291e06e3e65764c68654fda616 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 13 Feb 2025 12:24:31 -0500 Subject: [PATCH 0904/1097] Core: mild speedup on deepEqual (#12717) * Core: mild speedup on deepEqual a bit faster for arrays, also avoids repeated calls to Object.keys * Update utils.js * Update utils.js --- src/utils.js | 55 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/utils.js b/src/utils.js index a412028b4b6..e87e9a2c61a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -998,28 +998,55 @@ export function buildUrl(obj) { * @param {boolean} [options.checkTypes=false] - If set, two objects with identical properties but different constructors will *not* be considered equivalent. * @returns {boolean} - Returns `true` if the objects are equivalent, `false` otherwise. */ -export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { +export function deepEqual(obj1, obj2, { checkTypes = false } = {}) { + // Quick reference check if (obj1 === obj2) return true; - else if ( - (typeof obj1 === 'object' && obj1 !== null) && - (typeof obj2 === 'object' && obj2 !== null) && - (!checkTypes || (obj1.constructor === obj2.constructor)) + + // If either is null or not an object, do a direct equality check + if ( + typeof obj1 !== 'object' || obj1 === null || + typeof obj2 !== 'object' || obj2 === null ) { - const props1 = Object.keys(obj1); - if (props1.length !== Object.keys(obj2).length) return false; - for (let prop of props1) { - if (obj2.hasOwnProperty(prop)) { - if (!deepEqual(obj1[prop], obj2[prop], {checkTypes})) { - return false; - } - } else { + return false; + } + // Cache the Array checks + const isArr1 = Array.isArray(obj1); + const isArr2 = Array.isArray(obj2); + // Special case: both are arrays + if (isArr1 && isArr2) { + if (obj1.length !== obj2.length) return false; + for (let i = 0; i < obj1.length; i++) { + if (!deepEqual(obj1[i], obj2[i], { checkTypes })) { return false; } } return true; - } else { + } else if (isArr1 || isArr2) { + return false; + } + + // If we’re checking types, compare constructors (e.g., plain object vs. Date) + if (checkTypes && obj1.constructor !== obj2.constructor) { return false; } + + // Compare object keys. Cache keys for both to avoid repeated calls. + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + for (const key of keys1) { + // If `obj2` doesn't have this key or sub-values aren't equal, bail out. + if (!Object.prototype.hasOwnProperty.call(obj2, key)) { + return false; + } + if (!deepEqual(obj1[key], obj2[key], { checkTypes })) { + return false; + } + } + + return true; } export function mergeDeep(target, ...sources) { From af5703d06cab605ea46823c116b67e9e0fe3a43a Mon Sep 17 00:00:00 2001 From: BaronJHYu <254878848@qq.com> Date: Fri, 14 Feb 2025 02:00:53 +0800 Subject: [PATCH 0905/1097] Mediago Bid Adapter : add param publisherid (#12753) * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid * Mediago Bid Adapter : add param publisherid --- libraries/cookieSync/cookieSync.js | 42 ++++++++++++++++++ modules/discoveryBidAdapter.js | 38 +--------------- modules/mediagoBidAdapter.js | 48 ++++----------------- test/spec/modules/mediagoBidAdapter_spec.js | 7 ++- 4 files changed, 57 insertions(+), 78 deletions(-) create mode 100644 libraries/cookieSync/cookieSync.js diff --git a/libraries/cookieSync/cookieSync.js b/libraries/cookieSync/cookieSync.js new file mode 100644 index 00000000000..286c1297530 --- /dev/null +++ b/libraries/cookieSync/cookieSync.js @@ -0,0 +1,42 @@ +import { getStorageManager } from '../../src/storageManager.js'; +const COOKIE_KEY_MGUID = '__mguid_'; + +export function cookieSync(syncOptions, gdprConsent, uspConsent, bidderCode, cookieOrigin, ckIframeUrl, cookieTime) { + const storage = getStorageManager({bidderCode: bidderCode}); + const origin = encodeURIComponent(location.origin || `https://${location.host}`); + let syncParamUrl = `dm=${origin}`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncParamUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncParamUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncParamUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (syncOptions.iframeEnabled) { + window.addEventListener('message', function handler(event) { + if (!event.data || event.origin != cookieOrigin) { + return; + } + + this.removeEventListener('message', handler); + + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, cookieTime); + } + }, true); + return [ + { + type: 'iframe', + url: `${ckIframeUrl}?${syncParamUrl}` + } + ]; + } +} diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 0dbb1a3a6cb..597ae9e8582 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -7,6 +7,7 @@ import { getDevice, getScreenSize } from '../libraries/fpdUtils/deviceInfo.js'; import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; import { getHLen } from '../libraries/navigatorData/navigatorData.js'; +import { cookieSync } from '../libraries/cookieSync/cookieSync.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -484,42 +485,7 @@ export const spec = { }, getUserSyncs: function (syncOptions, serverResponse, gdprConsent, uspConsent, gppConsent) { - const origin = encodeURIComponent(location.origin || `https://${location.host}`); - let syncParamUrl = `dm=${origin}`; - - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncParamUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncParamUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncParamUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - if (syncOptions.iframeEnabled) { - window.addEventListener('message', function handler(event) { - if (!event.data || event.origin != THIRD_PARTY_COOKIE_ORIGIN) { - return; - } - - this.removeEventListener('message', handler); - - event.stopImmediatePropagation(); - - const response = event.data; - if (!response.optout && response.mguid) { - storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCookieTimeToUTCString()); - } - }, true); - return [ - { - type: 'iframe', - url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` - } - ]; - } + return cookieSync(syncOptions, gdprConsent, uspConsent, BIDDER_CODE, THIRD_PARTY_COOKIE_ORIGIN, COOKY_SYNC_IFRAME_URL, getCookieTimeToUTCString()); }, /** diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 1811f62e6ba..964ddad84ef 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -10,6 +10,7 @@ import { getDevice } from '../libraries/fpdUtils/deviceInfo.js'; import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; import { getHLen } from '../libraries/navigatorData/navigatorData.js'; +import { cookieSync } from '../libraries/cookieSync/cookieSync.js'; // import { config } from '../src/config.js'; // import { isPubcidEnabled } from './pubCommonId.js'; @@ -25,7 +26,6 @@ const BIDDER_CODE = 'mediago'; // const PROTOCOL = window.document.location.protocol; const ENDPOINT_URL = 'https://gbid.mediago.io/api/bid?tn='; // const COOKY_SYNC_URL = 'https://gtrace.mediago.io/ju/cs/eplist'; -const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; export const THIRD_PARTY_COOKIE_ORIGIN = 'https://cdn.mediago.io'; const TIME_TO_LIVE = 500; @@ -39,6 +39,7 @@ let itemMaps = {}; export const COOKIE_KEY_MGUID = '__mguid_'; const COOKIE_KEY_PMGUID = '__pmguid_'; const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year +const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; let reqTimes = 0; /** @@ -172,6 +173,7 @@ function getItems(validBidRequests, bidderRequest) { ortb2Imp: utils.deepAccess(req, 'ortb2Imp'), // 传入完整对象,分析日志数据 gpid: gpid, // 加入后无法返回广告 adslot: utils.deepAccess(req, 'ortb2Imp.ext.data.adserver.adslot', '', ''), + publisher: req.params.publisher || '', ...gdprConsent // gdpr }, tagid: req.params && req.params.tagid @@ -286,9 +288,7 @@ function getParam(validBidRequests, bidderRequest) { mobile: isMobile, cat: [], // todo publisher: { - // todo - id: domain, - name: domain + id: globals['publisher'] } }, imp: items, @@ -317,6 +317,9 @@ export const spec = { if (bid.params.token) { globals['token'] = bid.params.token; } + if (bid.params.publisher) { + globals['publisher'] = bid.params.publisher; + } return !!bid.params.token; }, @@ -382,42 +385,7 @@ export const spec = { }, getUserSyncs: function (syncOptions, serverResponse, gdprConsent, uspConsent, gppConsent) { - const origin = encodeURIComponent(location.origin || `https://${location.host}`); - let syncParamUrl = `dm=${origin}`; - - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncParamUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncParamUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncParamUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - if (syncOptions.iframeEnabled) { - window.addEventListener('message', function handler(event) { - if (!event.data || event.origin != THIRD_PARTY_COOKIE_ORIGIN) { - return; - } - - this.removeEventListener('message', handler); - - event.stopImmediatePropagation(); - - const response = event.data; - if (!response.optout && response.mguid) { - storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); - } - }, true); - return [ - { - type: 'iframe', - url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` - } - ]; - } + return cookieSync(syncOptions, gdprConsent, uspConsent, BIDDER_CODE, THIRD_PARTY_COOKIE_ORIGIN, COOKY_SYNC_IFRAME_URL, getCurrentTimeToUTCString()); }, /** diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js index 5a1545ae028..a84ffe06270 100644 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ b/test/spec/modules/mediagoBidAdapter_spec.js @@ -56,7 +56,9 @@ describe('mediago:BidAdapterTests', function () { content: { keywords: 'video, source=streaming' }, - + publisher: { + domain: 'mediago.io' + }, }, user: { ext: { @@ -158,7 +160,8 @@ describe('mediago:BidAdapterTests', function () { spec.isBidRequestValid({ bidder: 'mediago', params: { - token: ['85a6b01e41ac36d49744fad726e3655d'] + token: ['85a6b01e41ac36d49744fad726e3655d'], + publisher: ['test_publisher'] } }) ).to.equal(true); From d23c529a4cc2c54297d697587eccd0fec5b7752c Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Fri, 14 Feb 2025 13:10:34 -0500 Subject: [PATCH 0906/1097] appnexus bid adapter - fix issue with start delay (#12770) --- modules/appnexusBidAdapter.js | 2 +- test/spec/modules/appnexusBidAdapter_spec.js | 27 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 099dd989a1d..2074f1745f2 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1094,7 +1094,7 @@ function getContextFromPlacement(ortbPlacement) { } function getContextFromStartDelay(ortbStartDelay) { - if (!ortbStartDelay) { + if (typeof ortbStartDelay === 'undefined') { return; } diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index b2bdfbcdf5a..4f384eefdda 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -431,6 +431,33 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); + it('should include ORTB video values when video params is empty - case 1', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + startdelay: 0, + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: false, + context: 1 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + it('should convert and include ORTB2 device data when available', function () { const bidRequest = deepClone(bidRequests[0]); const bidderRequest = { From f9666bc35ba4aac9b4e40217ca62eb8f5872568a Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Fri, 14 Feb 2025 19:34:03 +0100 Subject: [PATCH 0907/1097] Add support for zetassp id (#12769) --- libraries/liveIntentId/shared.js | 16 ++++++++ .../liveIntentExternalIdSystem_spec.js | 5 +++ .../modules/liveIntentIdMinimalSystem_spec.js | 5 +++ test/spec/modules/liveIntentIdSystem_spec.js | 38 +++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js index ab00417ccc3..1b48fc19368 100644 --- a/libraries/liveIntentId/shared.js +++ b/libraries/liveIntentId/shared.js @@ -77,6 +77,10 @@ export function composeIdObject(value) { result.triplelift = { 'id': value.triplelift, ext: { provider: LI_PROVIDER_DOMAIN } } } + if (value.zetassp) { + result.zetassp = { 'id': value.zetassp, ext: { provider: LI_PROVIDER_DOMAIN } } + } + if (value.medianet) { result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } } @@ -274,6 +278,18 @@ export const eids = { } } }, + 'zetassp': { + source: 'zeta-ssp.liveintent.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, 'vidazoo': { source: 'liveintent.vidazoo.com', atype: 3, diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js index b751f288b33..e63eab08ea6 100644 --- a/test/spec/modules/liveIntentExternalIdSystem_spec.js +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -461,6 +461,11 @@ describe('LiveIntentExternalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a zetassp id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', zetassp: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'zetassp': 'bar'}, 'zetassp': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 810b6a23a20..553d32321f5 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -329,6 +329,11 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a zetassp id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', zetassp: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'zetassp': 'bar'}, 'zetassp': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 8f7a3465d88..b028e3e4890 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -546,6 +546,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a zetassp id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', zetassp: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'zetassp': 'bar'}, 'zetassp': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -925,6 +930,39 @@ describe('LiveIntentId', function() { }); }); + it('zetassp', function () { + const userId = { + zetassp: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'zeta-ssp.liveintent.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('zetassp with ext', function () { + const userId = { + zetassp: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'zeta-ssp.liveintent.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('vidazoo', function () { const userId = { vidazoo: { 'id': 'sample_id' } From 319dcfd8944c809198f855c7b9742d3827cdbd03 Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Fri, 14 Feb 2025 11:17:17 -0800 Subject: [PATCH 0908/1097] Send transaction Id in the data object. (#12765) --- modules/gumgumBidAdapter.js | 4 +++- test/spec/modules/gumgumBidAdapter_spec.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2911522c89b..525fae1035c 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -472,6 +472,8 @@ function buildRequests(validBidRequests, bidderRequest) { if (schain && schain.nodes) { data.schain = _serializeSupplyChainObj(schain); } + const tId = deepAccess(ortb2Imp, 'ext.tid') || deepAccess(bidderRequest, 'ortb2.source.tid') || ''; + data.tId = tId Object.assign( data, _getBrowserParams(topWindowUrl, mosttopLocation), @@ -481,7 +483,7 @@ function buildRequests(validBidRequests, bidderRequest) { bids.push({ id: bidId, tmax: timeout, - tId: ortb2Imp?.ext?.tid, + tId: tId, pi: data.pi, selector: params.selector, sizes, diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 8e7a80281ab..8202d5f4f4f 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -819,6 +819,25 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.ip).to.equal(ortb2.device.ip); expect(bidRequest.data.ipv6).to.equal(ortb2.device.ipv6); }); + + it('should set tId from ortb2Imp.ext.tid if available', function () { + const ortb2Imp = { ext: { tid: 'test-tid-1' } }; + const request = { ...bidRequests[0], ortb2Imp }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.tId).to.equal('test-tid-1'); + }); + + it('should set tId from bidderRequest.ortb2.source.tid if ortb2Imp.ext.tid is not available', function () { + const ortb2 = { source: { tid: 'test-tid-2' } }; + const fakeBidRequest = { ortb2 }; + const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; + expect(bidRequest.data.tId).to.equal('test-tid-2'); + }); + + it('should set tId to an empty string if neither ortb2Imp.ext.tid nor bidderRequest.ortb2.source.tid are available', function () { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.tId).to.equal(''); + }) }) describe('interpretResponse', function () { From a23f7f99720e064fd8ad495714aa9b30e93b749b Mon Sep 17 00:00:00 2001 From: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:31:20 +0100 Subject: [PATCH 0909/1097] SeedingAlliance Adapter: fix wrong cur value (#12759) --- modules/seedingAllianceBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index e5fbebc8ffe..10bd6183488 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -26,7 +26,7 @@ const converter = ortbConverter({ // set basic page, this might be updated later by adunit param deepSetValue(request, 'site.page', bidderRequest.refererInfo.page); deepSetValue(request, 'regs.ext.pb_ver', '$prebid.version$'); - deepSetValue(request, 'cur', [config.getConfig('currency') || DEFAULT_CUR]); + deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]); // As this is client side, we get needed info from headers delete request.device; From 5f8d61646236f6deafb8a580dbaf897994727497 Mon Sep 17 00:00:00 2001 From: Alexander Pykhteyev Date: Sat, 15 Feb 2025 02:58:34 +0700 Subject: [PATCH 0910/1097] LimelightDigital Bid Adapter : add new aliases (#12756) * Add new aliases for Limelight Adapter * Add new aliases for Limelight Adapter * Add new aliases for Limelight Adapter * Add new aliases for Limelight Adapter --------- Co-authored-by: apykhteyev --- modules/limelightDigitalBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index f69ae8f76eb..0b349f2f17d 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -32,7 +32,7 @@ function isBidResponseValid(bid) { export const spec = { code: BIDDER_CODE, - aliases: ['pll', 'iionads', 'apester', 'adsyield', 'tgm'], + aliases: ['pll', 'iionads', 'apester', 'adsyield', 'tgm', 'adtg_org', 'velonium', 'orangeclickmedia', 'streamvision'], supportedMediaTypes: [BANNER, VIDEO], /** From d27b0c2e888beb1fbd9545a37e5a7722ad2515d5 Mon Sep 17 00:00:00 2001 From: Jeff Palladino <1226357+jpalladino84@users.noreply.github.com> Date: Fri, 14 Feb 2025 13:07:39 -0700 Subject: [PATCH 0911/1097] symitriDap Rtd Provider : set content-type header in correct spot (#12766) * symitriDapRtdProvider - set content-type header in correct spot * update dapEncryptedMembership contentType Headers * add asserts to test cases that ensures tokenize requests are sent with content-type application/json --------- Co-authored-by: Jeff Palladino --- modules/symitriDapRtdProvider.js | 7 ++++--- test/spec/modules/symitriDapRtdProvider_spec.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 7befe826382..51303f772ff 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -673,7 +673,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return; } - let customHeaders = { 'Content-Type': 'application/json' }; + let customHeaders = {}; let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); if (dapSSID) { customHeaders[headerPrefix + '-DAP-SS-ID'] = dapSSID; @@ -699,7 +699,8 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { ajax(url, cb, body, { method: method, - customHeaders: customHeaders + customHeaders: customHeaders, + contentType: 'application/json' }); }, @@ -821,8 +822,8 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { ajax(url, cb, undefined, { method: 'GET', + contentType: 'application/json', customHeaders: { - 'Content-Type': 'application/json', 'Pragma': 'akamai-x-get-extracted-values' } }); diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 440949aeb52..7a773fd23c9 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -209,6 +209,7 @@ describe('symitriDapRtdProvider', function() { membershipRequest.respond(200, responseHeader, JSON.stringify(membership)); let tokenWithExpiry = 'Sample-token-with-exp' let tokenizeRequest = server.requests[1]; + tokenizeRequest.requestHeaders['Content-Type'].should.equal('application/json'); responseHeader['Symitri-DAP-Token'] = tokenWithExpiry; tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); let data = dapUtils.dapGetRtdObj(membership, cmoduleConfig.params.segtax); @@ -230,6 +231,7 @@ describe('symitriDapRtdProvider', function() { membershipRequest.respond(200, responseHeader, JSON.stringify(encMembership)); let tokenWithExpiry = 'Sample-token-with-exp' let tokenizeRequest = server.requests[1]; + tokenizeRequest.requestHeaders['Content-Type'].should.equal('application/json'); responseHeader['Symitri-DAP-Token'] = tokenWithExpiry; tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); let data = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, emoduleConfig.params.segtax); @@ -263,6 +265,7 @@ describe('symitriDapRtdProvider', function() { } ); let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); request.respond(200, responseHeader, JSON.stringify('success')); expect(submoduleCallback).to.equal(undefined); }); @@ -278,6 +281,7 @@ describe('symitriDapRtdProvider', function() { } ); let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); request.respond(400, responseHeader, JSON.stringify('error')); expect(submoduleCallback).to.equal(undefined); }); @@ -291,6 +295,7 @@ describe('symitriDapRtdProvider', function() { } ); let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); request.respond(200, responseHeader, JSON.stringify('success')); expect(submoduleCallback).to.equal(undefined); }); @@ -470,6 +475,7 @@ describe('symitriDapRtdProvider', function() { it('test dapRefreshToken success response', function () { dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone) let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); responseHeader['Symitri-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); @@ -478,6 +484,7 @@ describe('symitriDapRtdProvider', function() { it('test dapRefreshToken success response with deviceid 100', function () { dapUtils.dapRefreshToken(ortb2, esampleConfig, true, onDone) let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); responseHeader['Symitri-DAP-100'] = sampleCachedToken.token; request.respond(200, responseHeader, ''); expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); @@ -488,6 +495,7 @@ describe('symitriDapRtdProvider', function() { let request = server.requests[0]; let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' responseHeader['Symitri-DAP-Token'] = tokenWithExpiry; + request.requestHeaders['Content-Type'].should.equal('application/json'); request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); }); @@ -496,6 +504,7 @@ describe('symitriDapRtdProvider', function() { storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); 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 }); @@ -599,6 +608,7 @@ describe('symitriDapRtdProvider', function() { let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); let sampleSSID = 'Test_SSID_Spec'; responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; responseHeader['Symitri-DAP-SS-ID'] = sampleSSID; @@ -611,6 +621,7 @@ describe('symitriDapRtdProvider', function() { storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; + request.requestHeaders['Content-Type'].should.equal('application/json'); let ssidHeader = request.requestHeaders['Symitri-DAP-SS-ID']; responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; request.respond(200, responseHeader, ''); From 27aab5c16493678d5095cf4801651f4f8aaab8b9 Mon Sep 17 00:00:00 2001 From: Kai Miyamoto <157671757+hogekai@users.noreply.github.com> Date: Tue, 18 Feb 2025 04:03:08 +0900 Subject: [PATCH 0912/1097] Michao Bid Adapter: Initial release (#12507) * Michao Bid Adapter: Initial release * Michao Bid Adapter: Fix incomprehensible integration tests * Michao Bid Adapter: Explicitly specify VAST XML * Michao Bid Adapter: Support for rewarded advertising * Michao Bid Adapter: Re-run E2e test * Michao Bid Adapter: Support for native format * Michao Bid Adapter: Change renderer URL * Michao Bid Adapter: Support for blocked categories and blocked advertisers * Michao Bid Adapter: Change placement to string type * Michao Bid Adapter: Add minimum bid price * Michao Bid Adapter: Added log system validation to integration testing for parameter validation * Michao Bid Adapter: Add partner ID parameter * Michao Bid Adapter: Refactoring * Michao Bid Adapter: Change the method used by the property validation system * Michao Bid Adapter: Remove video property assertion * Michao Bid Adapter: Remove assertions on video properties that you forgot to erase. * Michao Bid Adapter: Explicitly delete native objects * Michao Bid Adapter: Rename Renderer URL to outstream renderer URL * Michao Bid Adapter: Swap the order in which bid requests are pushed * Michao Bid Adapter: Explicitly specify version of outstream renderer URL * Michao Bid Adapter: Tests are supported for out-stream renderer URL version specification * Michao Bid Adapter: Add test parameter, Remove OpenRTB parameters * Michao Bid Adapter: Re run test * Michao Bid Adapter: Re run test 2 * Michao Bid Adapter: Move ID extension parameters to michao.[parameter] namespace * Michao Bid Adapter: Change InRenderer.js version to 1.0.6 -> 1 * Michao Bid Adapter: Update billing URL handling to support multiple URLs and improve test descriptions * Michao Bid Adapter: Update renderer method to use setRender for improved functionality * Michao Bid Adapter: Rerun test --- modules/michaoBidAdapter.js | 282 +++++++++ modules/michaoBidAdapter.md | 87 +++ test/spec/modules/michaoBidAdapter_spec.js | 651 +++++++++++++++++++++ 3 files changed, 1020 insertions(+) create mode 100644 modules/michaoBidAdapter.js create mode 100644 modules/michaoBidAdapter.md create mode 100644 test/spec/modules/michaoBidAdapter_spec.js diff --git a/modules/michaoBidAdapter.js b/modules/michaoBidAdapter.js new file mode 100644 index 00000000000..56c073cddde --- /dev/null +++ b/modules/michaoBidAdapter.js @@ -0,0 +1,282 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { + deepSetValue, + isBoolean, + isNumber, + isStr, + logError, + replaceAuctionPrice, + triggerPixel, +} from '../src/utils.js'; + +const ENV = { + BIDDER_CODE: 'michao', + SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO, NATIVE], + ENDPOINT: 'https://rtb.michao-ssp.com/openrtb/prebid', + NET_REVENUE: true, + DEFAULT_CURRENCY: 'USD', + OUTSTREAM_RENDERER_URL: + 'https://cdn.jsdelivr.net/npm/in-renderer-js@1/dist/in-video-renderer.umd.min.js', +}; + +export const spec = { + code: ENV.BIDDER_CODE, + supportedMediaTypes: ENV.SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: function (bid) { + const params = bid.params; + + if (!isNumber(params?.site)) { + domainLogger.invalidSiteError(params?.site); + return false; + } + + if (!isStr(params?.placement)) { + domainLogger.invalidPlacementError(params?.placement); + return false; + } + + if (params?.partner) { + if (!isNumber(params?.partner)) { + domainLogger.invalidPartnerError(params?.partner); + return false; + } + } + + if (params?.test) { + if (!isBoolean(params?.test)) { + domainLogger.invalidTestParamError(params?.test); + return false; + } + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const bidRequests = []; + + validBidRequests.forEach((validBidRequest) => { + let bidRequestEachFormat = []; + + if (validBidRequest.mediaTypes?.banner) { + bidRequestEachFormat.push({ + ...validBidRequest, + mediaTypes: { + banner: validBidRequest.mediaTypes.banner, + }, + }); + } + + if (validBidRequest.mediaTypes?.native) { + bidRequestEachFormat.push({ + ...validBidRequest, + mediaTypes: { + native: validBidRequest.mediaTypes.native, + }, + }); + } + + if (validBidRequest.mediaTypes?.video) { + bidRequestEachFormat.push({ + ...validBidRequest, + mediaTypes: { + video: validBidRequest.mediaTypes.video, + }, + }); + } + + bidRequests.push(buildRequest(bidRequestEachFormat, bidderRequest)); + }); + + return bidRequests; + }, + + interpretResponse: function (serverResponse, request) { + return converter.fromORTB({ + response: serverResponse.body, + request: request.data, + }).bids; + }, + + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + if (syncOptions.iframeEnabled) { + return [ + { + type: 'iframe', + url: + 'https://sync.michao-ssp.com/cookie-syncs?' + + generateGdprParams(gdprConsent), + }, + ]; + } + + return []; + }, + + onBidBillable: function (bid) { + if (bid.burl && isStr(bid.burl)) { + const billingUrls = generateBillableUrls(bid); + + billingUrls.forEach((billingUrl) => { + triggerPixel(billingUrl); + }); + } + }, +}; + +export const domainLogger = { + invalidSiteError(value) { + logError( + `Michao Bid Adapter: Invalid site ID. Expected number, got ${typeof value}. Value: ${value}` + ); + }, + + invalidPlacementError(value) { + logError( + `Michao Bid Adapter: Invalid placement. Expected string, got ${typeof value}. Value: ${value}` + ); + }, + + invalidPartnerError(value) { + logError( + `Michao Bid Adapter: Invalid partner ID. Expected number, got ${typeof value}. Value: ${value}` + ); + }, + + invalidTestParamError(value) { + logError( + `Michao Bid Adapter: Invalid test parameter. Expected boolean, got ${typeof value}. Value: ${value}` + ); + }, +}; + +function buildRequest(bidRequests, bidderRequest) { + const openRTBBidRequest = converter.toORTB({ + bidRequests: bidRequests, + bidderRequest, + }); + + return { + method: 'POST', + url: ENV.ENDPOINT, + data: openRTBBidRequest, + options: { contentType: 'application/json', withCredentials: true }, + }; +} + +function generateGdprParams(gdprConsent) { + let gdprParams = ''; + + if (typeof gdprConsent === 'object') { + if (gdprConsent?.gdprApplies) { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${ + gdprConsent.consentString || '' + }`; + } + } + + return gdprParams; +} + +function generateBillableUrls(bid) { + const billingUrls = []; + const cpm = bid.originalCpm || bid.cpm; + + const billingUrl = new URL(bid.burl); + + const burlParam = billingUrl.searchParams.get('burl'); + + if (burlParam) { + billingUrl.searchParams.delete('burl'); + billingUrls.push(replaceAuctionPrice(burlParam, cpm)); + } + + billingUrls.push(replaceAuctionPrice(billingUrl.toString(), cpm)); + + return billingUrls; +} + +const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const bidRequest = context.bidRequests[0]; + const openRTBBidRequest = buildRequest(imps, bidderRequest, context); + openRTBBidRequest.cur = [ENV.DEFAULT_CURRENCY]; + openRTBBidRequest.test = bidRequest.params?.test ? 1 : 0; + + deepSetValue( + openRTBBidRequest, + 'site.ext.michao.site', + bidRequest.params.site.toString() + ); + if (bidRequest?.schain) { + deepSetValue(openRTBBidRequest, 'source.schain', bidRequest.schain); + } + + if (bidRequest.params?.partner) { + deepSetValue( + openRTBBidRequest, + 'site.publisher.ext.michao.partner', + bidRequest.params.partner.toString() + ); + } + + return openRTBBidRequest; + }, + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue( + imp, + 'ext.michao.placement', + bidRequest.params.placement.toString() + ); + + if (!bidRequest.mediaTypes?.native) { + delete imp.native; + } + + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + const { bidRequest } = context; + if ( + bidResponse.mediaType === VIDEO && + bidRequest.mediaTypes.video.context === 'outstream' + ) { + bidResponse.vastXml = bid.adm; + const renderer = Renderer.install({ + url: ENV.OUTSTREAM_RENDERER_URL, + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + }); + renderer.setRender((bid) => { + bid.renderer.push(() => { + const inRenderer = new window.InVideoRenderer(); + inRenderer.render(bid.adUnitCode, bid); + }); + }); + bidResponse.renderer = renderer; + } + + return bidResponse; + }, + + context: { + netRevenue: ENV.NET_REVENUE, + currency: ENV.DEFAULT_CURRENCY, + ttl: 360, + }, +}); + +registerBidder(spec); diff --git a/modules/michaoBidAdapter.md b/modules/michaoBidAdapter.md new file mode 100644 index 00000000000..b45e8e2b5bd --- /dev/null +++ b/modules/michaoBidAdapter.md @@ -0,0 +1,87 @@ +# Overview + +```markdown +Module Name: Michao Bidder Adapter +Module Type: Bidder Adapter +Maintainer: miyamoto.kai@lookverin.com +``` + +# Description + +Module that connects to Michao’s demand sources + +Supported Ad format: +* Banner +* Video (instream and outstream) +* Native + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'michao', + params: { + site: 1, + placement: '1', + } + }] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + minduration: 0, + maxduration: 120, + mimes: ['video/mp4'], + protocols: [7] + } + }, + bids: [{ + bidder: 'michao', + params: { + site: 1, + placement: '1', + } + }] + }, + // Native AdUnit + { + code: 'native-div', + mediaTypes: { + native: { + ortb: { + assets: [ + { + id: 1, + required: 1, + img: { + type: 3, + w: 989, + h: 742, + }, + }, + ] + } + } + }, + bids: [{ + bidder: 'michao', + params: { + site: 1, + placement: '1', + } + }] + } +]; +``` diff --git a/test/spec/modules/michaoBidAdapter_spec.js b/test/spec/modules/michaoBidAdapter_spec.js new file mode 100644 index 00000000000..4aefa0b01c0 --- /dev/null +++ b/test/spec/modules/michaoBidAdapter_spec.js @@ -0,0 +1,651 @@ +import { cloneDeep } from 'lodash'; +import { domainLogger, spec } from '../../../modules/michaoBidAdapter'; +import * as utils from '../../../src/utils.js'; + +describe('Michao Bid Adapter', () => { + let bannerBidRequest; + let videoBidRequest; + let nativeBidRequest; + let videoServerResponse; + let bannerServerResponse; + let domainLoggerMock; + let sandbox; + let triggerPixelSpy; + + beforeEach(() => { + bannerBidRequest = cloneDeep(_bannerBidRequest); + videoBidRequest = cloneDeep(_videoBidRequest); + nativeBidRequest = cloneDeep(_nativeBidRequest); + videoServerResponse = cloneDeep(_videoServerResponse); + bannerServerResponse = cloneDeep(_bannerServerResponse); + sandbox = sinon.sandbox.create(); + domainLoggerMock = sandbox.stub(domainLogger); + triggerPixelSpy = sandbox.spy(utils, 'triggerPixel'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('`isBidRequestValid`', () => { + describe('Required parameter behavior', () => { + it('passes when siteId is a number', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + site: 123, + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.true; + expect(domainLoggerMock.invalidSiteError.calledOnce).to.be.false; + }); + + it('detects invalid input when siteId is not a number', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + site: '123', + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.false; + expect(domainLoggerMock.invalidSiteError.calledOnce).to.be.true; + }); + + it('passes when placementId is a string', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + placement: '123', + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.true; + expect(domainLoggerMock.invalidPlacementError.calledOnce).to.be.false; + }); + + it('detects invalid input when placementId is not a string', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + placement: 123, + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.false; + expect(domainLoggerMock.invalidPlacementError.calledOnce).to.be.true; + }); + }); + + describe('Optional parameter behavior', () => { + it('passes when partnerId is not specified', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + partner: undefined, + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.true; + expect(domainLoggerMock.invalidPartnerError.calledOnce).to.be.false; + }); + + it('passes when partnerId is a number', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + partner: 6789, + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.true; + expect(domainLoggerMock.invalidPartnerError.calledOnce).to.be.false; + }); + + it('detects invalid input when partnerId is not a number', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + partner: '6789', + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.false; + expect(domainLoggerMock.invalidPartnerError.calledOnce).to.be.true; + }); + + it('passes when test is not specified', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + test: undefined, + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.true; + expect(domainLoggerMock.invalidTestParamError.calledOnce).to.be.false; + }); + + it('passes when test is a boolean', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + test: false, + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.true; + expect(domainLoggerMock.invalidTestParamError.calledOnce).to.be.false; + }); + + it('detects invalid input when test is not a boolean', () => { + bannerBidRequest.params = { + ...bannerBidRequest.params, + test: 'trueee', + }; + + const result = spec.isBidRequestValid(bannerBidRequest); + + expect(result).to.be.false; + expect(domainLoggerMock.invalidTestParamError.calledOnce).to.be.true; + }); + }); + }); + + describe('`buildRequest`', () => { + describe('Bid request format behavior', () => { + it('creates banner-specific bid request from bid request containing one banner format', () => { + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(1); + expect(result[0].data.imp[0]).to.have.property( + 'banner' + ); + }); + + it('creates video-specific bid request from bid request containing one video format', () => { + const bidderRequest = { + bids: [videoBidRequest], + auctionId: videoBidRequest.auctionId, + bidderRequestId: videoBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([videoBidRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(1); + }); + + it('creates native-specific bid request from bid request containing one native format', () => { + const bidderRequest = { + bids: [nativeBidRequest], + auctionId: nativeBidRequest.auctionId, + bidderRequestId: nativeBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([nativeBidRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(1); + }); + }); + + describe('Multiple format combination behavior', () => { + it('creates banner and video bid request with two impressions from bid request containing both banner and video formats', () => { + const multiFormatRequest = { + ...bannerBidRequest, + mediaTypes: { + ...bannerBidRequest.mediaTypes, + video: videoBidRequest.mediaTypes.video, + }, + }; + + const bidderRequest = { + bids: [multiFormatRequest], + auctionId: multiFormatRequest.auctionId, + bidderRequestId: multiFormatRequest.bidderRequestId, + }; + + const result = spec.buildRequests([multiFormatRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(2); + expect(result[0].data.imp[0]).to.have.property( + 'banner' + ); + }); + + it('creates banner and native bid request with two impressions from bid request containing both banner and native formats', () => { + const multiFormatRequest = { + ...bannerBidRequest, + mediaTypes: { + ...bannerBidRequest.mediaTypes, + native: nativeBidRequest.mediaTypes.native, + }, + }; + + const bidderRequest = { + bids: [multiFormatRequest], + auctionId: multiFormatRequest.auctionId, + bidderRequestId: multiFormatRequest.bidderRequestId, + }; + + const result = spec.buildRequests([multiFormatRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(2); + expect(result[0].data.imp[0]).to.have.property( + 'banner' + ); + }); + + it('creates video and native bid request with two impressions from bid request containing both video and native formats', () => { + const multiFormatRequest = { + ...videoBidRequest, + mediaTypes: { + ...videoBidRequest.mediaTypes, + native: nativeBidRequest.mediaTypes.native, + }, + }; + + const bidderRequest = { + bids: [multiFormatRequest], + auctionId: multiFormatRequest.auctionId, + bidderRequestId: multiFormatRequest.bidderRequestId, + }; + + const result = spec.buildRequests([multiFormatRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(2); + }); + + it('creates banner, video and native bid request with three impressions from bid request containing all three formats', () => { + const multiFormatRequest = { + ...bannerBidRequest, + mediaTypes: { + ...bannerBidRequest.mediaTypes, + video: videoBidRequest.mediaTypes.video, + native: nativeBidRequest.mediaTypes.native, + }, + }; + + const bidderRequest = { + bids: [multiFormatRequest], + auctionId: multiFormatRequest.auctionId, + bidderRequestId: multiFormatRequest.bidderRequestId, + }; + + const result = spec.buildRequests([multiFormatRequest], bidderRequest); + + expect(result.length).to.equal(1); + expect(result[0].data.imp.length).to.equal(3); + expect(result[0].data.imp[0]).to.have.property( + 'banner' + ); + }); + }); + + describe('Required parameter behavior', () => { + it('sets siteId in site object', () => { + bannerBidRequest.params = { + site: 456, + placement: '123', + }; + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(result[0].data.site.ext.michao.site).to.equal('456'); + }); + + it('sets placementId in impression object', () => { + bannerBidRequest.params = { + site: 456, + placement: '123', + }; + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(result[0].data.imp[0].ext.michao.placement).to.equal('123'); + }); + }); + + describe('Optional parameter behavior', () => { + it('sets partnerId in publisher when specified', () => { + bannerBidRequest.params = { + site: 456, + placement: '123', + partner: 123, + }; + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(result[0].data.site.publisher.ext.michao.partner).to.equal('123'); + }); + + it('sets test in publisher when specified', () => { + bannerBidRequest.params = { + site: 456, + placement: '123', + test: true, + }; + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + + const result = spec.buildRequests([bannerBidRequest], bidderRequest); + + expect(result[0].data.test).to.equal(1); + }); + }); + }); + + describe('`interpretResponse`', () => { + it('sets renderer for video bid response when bid request was outstream', () => { + videoBidRequest.mediaTypes.video = { + ...videoBidRequest.mediaTypes.video, + context: 'outstream', + }; + const bidderRequest = { + bids: [videoBidRequest], + auctionId: videoBidRequest.auctionId, + bidderCode: 'michao', + bidderRequestId: videoBidRequest.bidderRequestId, + }; + const request = spec.buildRequests([videoBidRequest], bidderRequest); + + const result = spec.interpretResponse(videoServerResponse, request[0]); + + expect(result[0].renderer.url).to.equal( + 'https://cdn.jsdelivr.net/npm/in-renderer-js@1/dist/in-video-renderer.umd.min.js' + ); + }); + + it('does not set renderer for video bid response when bid request was instream', () => { + videoBidRequest.mediaTypes.video = { + ...videoBidRequest.mediaTypes.video, + context: 'instream', + }; + const bidderRequest = { + bids: [videoBidRequest], + auctionId: videoBidRequest.auctionId, + bidderCode: 'michao', + bidderRequestId: videoBidRequest.bidderRequestId, + }; + const request = spec.buildRequests([videoBidRequest], bidderRequest); + + const result = spec.interpretResponse(videoServerResponse, request[0]); + + expect(result[0].renderer).to.be.undefined; + }); + + it('does not set renderer for banner bid response', () => { + const bidderRequest = { + bids: [bannerBidRequest], + auctionId: bannerBidRequest.auctionId, + bidderCode: 'michao', + bidderRequestId: bannerBidRequest.bidderRequestId, + }; + const request = spec.buildRequests([bannerBidRequest], bidderRequest); + + const result = spec.interpretResponse(bannerServerResponse, request[0]); + + expect(result[0].renderer).to.be.undefined; + }); + }); + + describe('`getUserSyncs`', () => { + it('performs iframe user sync when iframe is enabled', () => { + const syncOptions = { + iframeEnabled: true, + }; + + const result = spec.getUserSyncs(syncOptions, {}, {}, {}); + + expect(result[0].url).to.equal( + 'https://sync.michao-ssp.com/cookie-syncs?' + ); + expect(result[0].type).to.equal('iframe'); + }); + + it('does not perform iframe user sync when iframe is disabled', () => { + const syncOptions = { + iframeEnabled: false, + }; + + const result = spec.getUserSyncs(syncOptions, {}, {}, {}); + + expect(result.length).to.equal(0); + }); + + it('sets GDPR parameters in user sync URL when GDPR applies', () => { + const syncOptions = { + iframeEnabled: true, + }; + const gdprConsent = { + gdprApplies: true, + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + }; + + const result = spec.getUserSyncs(syncOptions, {}, gdprConsent, {}); + + expect(result[0].url).to.equal( + 'https://sync.michao-ssp.com/cookie-syncs?gdpr=1&gdpr_consent=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' + ); + expect(result[0].type).to.equal('iframe'); + }); + + it('does not set GDPR parameters in user sync URL when GDPR does not apply', () => { + const syncOptions = { + iframeEnabled: true, + }; + const gdrpConsent = { + gdrpApplies: false, + }; + + const result = spec.getUserSyncs(syncOptions, {}, gdrpConsent, {}); + + expect(result[0].url).to.equal( + 'https://sync.michao-ssp.com/cookie-syncs?' + ); + expect(result[0].type).to.equal('iframe'); + }); + }); + + describe('`onBidBillable`', () => { + it('does not generate billing when billing URL is not included in bid', () => { + const bid = {}; + + spec.onBidBillable(bid); + + expect(triggerPixelSpy.calledOnce).to.be.false; + }); + + it('calls billing url when billing URL is a string', () => { + const bid = { + burl: 'https://example.com/burls', + cpm: 1, + }; + + spec.onBidBillable(bid); + + expect(triggerPixelSpy.calledOnce).to.be.true; + }); + + it('calls bidder billing url when billing URL includes bidder burl', () => { + const bid = { + burl: 'https://example.com/burl?burl=https://bidder.example.com/burl', + cpm: 1, + }; + + spec.onBidBillable(bid); + + expect(triggerPixelSpy.calledTwice).to.be.true; + }); + + it('does not calls billing url when billing URL is not a string', () => { + const bid = { + burl: 123, + }; + + spec.onBidBillable(bid); + + expect(triggerPixelSpy.calledOnce).to.be.false; + }); + }); +}); + +const _bannerBidRequest = { + adUnitCode: 'test-div', + auctionId: 'banner-auction-id', + bidId: 'banner-bid-id', + bidder: 'michao', + bidderRequestId: 'banner-bidder-request-id', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: { banner: [[300, 250]] }, + params: { + site: 123, + placement: '456', + }, +}; + +const _videoBidRequest = { + adUnitCode: 'test-div', + auctionId: 'video-auction-request-id', + bidId: 'video-bid-id', + bidder: 'michao', + bidderRequestId: 'video-bidder-request-id', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + minduration: 0, + maxduration: 120, + protocols: [2] + }, + }, + params: { + site: 123, + placement: '456', + }, +}; + +const _nativeBidRequest = { + adUnitCode: 'test-div', + auctionId: 'native-auction-id', + bidId: 'native-bid-id', + bidder: 'michao', + bidderRequestId: 'native-bidder-request-id', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + mediaTypes: { + native: { + ortb: { + assets: [ + { + id: 1, + title: { + len: 30, + }, + }, + ], + }, + }, + }, + params: { + site: 123, + placement: '456', + }, +}; + +const _videoServerResponse = { + headers: null, + body: { + id: 'video-server-response-id', + seatbid: [ + { + bid: [ + { + id: 'video-bid-id', + impid: 'video-bid-id', + price: 0.18, + adm: '', + adid: '144762342', + adomain: ['https://dummydomain.com'], + iurl: 'iurl', + cid: '109', + crid: 'creativeId', + cat: [], + w: 300, + h: 250, + mtype: 2, + }, + ], + seat: 'seat', + }, + ], + cur: 'USD', + }, +}; + +const _bannerServerResponse = { + headers: null, + body: { + id: 'banner-server-response-id', + seatbid: [ + { + bid: [ + { + id: 'banner-bid-id', + impid: 'banner-bid-id', + price: 0.18, + adm: '
ad
', + adid: '144762342', + adomain: ['https://dummydomain.com'], + iurl: 'iurl', + cid: '109', + crid: 'creativeId', + cat: [], + w: 300, + h: 250, + mtype: 1, + }, + ], + seat: 'seat', + }, + ], + cur: 'USD', + }, +}; From d3be53b7056468d1c613f089b18a42417ee04d1e Mon Sep 17 00:00:00 2001 From: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:41:55 +0530 Subject: [PATCH 0913/1097] Doceree AdManager Bid Adapter : define GVLID (#12774) * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Update docereeAdManagerBidAdapter.js * added test cases for payload formation in DocereeAdManager * Added support for publisherUrl * added some parameters * Added support for TCF 2.2 * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Written test cases for new method implemented. * indentation issues resolved * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Updated DocereeAdManager Bidder Adapter * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js --------- Co-authored-by: lokesh-doceree Co-authored-by: Patrick McCann --- modules/docereeAdManagerBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index 80f70d71a8b..4d7f9e34d45 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -3,11 +3,13 @@ import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'docereeadmanager'; const END_POINT = 'https://dai.doceree.com/drs/quest'; +const GVLID = 1063; export const spec = { code: BIDDER_CODE, url: '', supportedMediaTypes: [BANNER], + gvlid: GVLID, isBidRequestValid: (bid) => { const { placementId } = bid.params; From 35e4c15e2b9b8c6a6e32ce503839c51e7db15a10 Mon Sep 17 00:00:00 2001 From: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:38:00 +0100 Subject: [PATCH 0914/1097] ID5 User Id module - add documentation about `canCookieSync` configuration parameter (#12775) --- modules/id5IdSystem.md | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index a2782b70559..68081e4b3be 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -25,13 +25,14 @@ pbjs.setConfig({ name: 'id5Id', params: { partner: 173, // change to the Partner Number you received from ID5 - externalModuleUrl: "https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js" // optional but recommended + externalModuleUrl: "https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js", // optional but recommended pd: 'MT1iNTBjY...', // optional, see table below for a link to how to generate this abTesting: { // optional enabled: true, // false by default controlGroupPct: 0.1 // valid values are 0.0 - 1.0 (inclusive) }, - disableExtensions: false // optional + disableExtensions: false,// optional + canCookieSync: true // optional, has effect only when externalModuleUrl is used }, storage: { type: 'html5', // "html5" is the required storage type @@ -45,23 +46,24 @@ pbjs.setConfig({ }); ``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | -| params | Required | Object | Details for the ID5 ID. | | -| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | -| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js -| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | -| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | -| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | -| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | -| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | -| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | -| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | -| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | -| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | -| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | +| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | +| params | Required | Object | Details for the ID5 ID. | | +| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js +| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | +| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | +| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | +| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | +| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | +| params.canCookieSync | Optional | Boolean | Set this to `true` to enable cookie syncing with other ID5 partners. See [our documentation](https://wiki.id5.io/docs/initiate-cookie-sync-to-id5) for details. Default `false` | `true` or `false` | +| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). From aa7fc978e551897cfa4a62991f905682f4c6e5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= <449563+steffenmllr@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:51:55 +0100 Subject: [PATCH 0915/1097] Allow config of ortb2 data for anayltics adapter during runtime (#12778) --- modules/agmaAnalyticsAdapter.js | 41 +++++--------- .../spec/modules/agmaAnalyticsAdapter_spec.js | 53 ++++++++++++++----- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/modules/agmaAnalyticsAdapter.js b/modules/agmaAnalyticsAdapter.js index e2e01fb4d03..7a8ecece0f5 100644 --- a/modules/agmaAnalyticsAdapter.js +++ b/modules/agmaAnalyticsAdapter.js @@ -3,6 +3,8 @@ import { generateUUID, logInfo, logError, + getWindowSelf, + getWindowTop, getPerformanceNow, isEmpty, isEmptyStr, @@ -17,19 +19,19 @@ import { config } from '../src/config.js'; const GVLID = 1122; const ModuleCode = 'agma'; const analyticsType = 'endpoint'; -const scriptVersion = '1.8.0'; +const scriptVersion = '1.9.0'; const batchDelayInMs = 1000; const agmaURL = 'https://pbc.agma-analytics.de/v1'; const pageViewId = generateUUID(); // Helper functions const getScreen = () => { - const w = window; + const win = getWindowTop(); const d = document; const e = d.documentElement; const g = d.getElementsByTagName('body')[0]; - const x = w.innerWidth || e.clientWidth || g.clientWidth; - const y = w.innerHeight || e.clientHeight || g.clientHeight; + const x = win.innerWidth || e.clientWidth || g.clientWidth; + const y = win.innerHeight || e.clientHeight || g.clientHeight; return { x, y }; }; @@ -40,32 +42,13 @@ const getUserIDs = () => { return []; }; -export const getOrtb2Data = (options) => { - let site = null; - let user = null; - - // check if data is provided via config - if (options.ortb2) { - if (options.ortb2.user) { - user = options.ortb2.user; - } - if (options.ortb2.site) { - site = options.ortb2.site; - } - if (site && user) { - return { site, user }; - } +export const getOrtb2Data = (options = {}) => { + const configData = config.getConfig(); + const win = getWindowSelf(); + return { + site: win.agma?.ortb2?.site ?? options.ortb2?.site ?? configData.ortb2?.site, + user: win.agma?.ortb2?.user ?? options.ortb2?.user ?? configData.ortb2?.user, } - try { - const configData = config.getConfig(); - // try to fallback to global config - if (configData.ortb2) { - site = site || configData.ortb2.site; - user = user || configData.ortb2.user; - } - } catch (e) {} - - return { site, user }; }; export const getTiming = () => { diff --git a/test/spec/modules/agmaAnalyticsAdapter_spec.js b/test/spec/modules/agmaAnalyticsAdapter_spec.js index 227acacde12..2e50801b559 100644 --- a/test/spec/modules/agmaAnalyticsAdapter_spec.js +++ b/test/spec/modules/agmaAnalyticsAdapter_spec.js @@ -8,7 +8,7 @@ import { gdprDataHandler } from '../../../src/adapterManager.js'; import { expect } from 'chai'; import * as events from '../../../src/events.js'; import { EVENTS } from '../../../src/constants.js'; -import { generateUUID } from '../../../src/utils.js'; +import * as utils from 'src/utils.js'; import { server } from '../../mocks/xhr.js'; import { config } from 'src/config.js'; @@ -80,7 +80,7 @@ describe('AGMA Analytics Adapter', () => { describe('getPayload', () => { it('should use non extended payload with no consent info', () => { sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => null) - const payload = getPayload([generateUUID()], { + const payload = getPayload([utils.generateUUID()], { code: 'test', }); @@ -97,7 +97,7 @@ describe('AGMA Analytics Adapter', () => { }, }, })); - const payload = getPayload([generateUUID()], { + const payload = getPayload([utils.generateUUID()], { code: 'test', }); expect(payload).to.have.all.keys([...nonExtendedKey, 'debug']); @@ -113,7 +113,7 @@ describe('AGMA Analytics Adapter', () => { }, }, })); - const payload = getPayload([generateUUID()], { + const payload = getPayload([utils.generateUUID()], { code: 'test', }); expect(payload).to.have.all.keys([...extendedKey, 'debug']); @@ -242,6 +242,35 @@ describe('AGMA Analytics Adapter', () => { }); }); + it('can be overwritten with a global agma variable', () => { + sandbox.stub(utils, 'getWindowSelf').returns({ + agma: { + ortb2: { + site: { + domain: 'overwritten.com', + }, + }, + }, + }); + + const ortb2 = { + site: { + domain: 'inital.com' + } + }; + + const result = getOrtb2Data({ + ortb2, + }); + + expect(result).to.deep.equal({ + user: undefined, + site: { + domain: 'overwritten.com', + } + }); + }); + describe('Event Payload', () => { beforeEach(() => { agmaAnalyticsAdapter.enableAnalytics({ @@ -278,26 +307,26 @@ describe('AGMA Analytics Adapter', () => { }, })); const auction = { - auctionId: generateUUID(), + auctionId: utils.generateUUID(), }; events.emit(EVENTS.AUCTION_INIT, { - auctionId: generateUUID('1'), + auctionId: utils.generateUUID('1'), auction, }); clock.tick(200); events.emit(EVENTS.AUCTION_INIT, { - auctionId: generateUUID('2'), + auctionId: utils.generateUUID('2'), auction, }); events.emit(EVENTS.AUCTION_INIT, { - auctionId: generateUUID('3'), + auctionId: utils.generateUUID('3'), auction, }); events.emit(EVENTS.AUCTION_INIT, { - auctionId: generateUUID('4'), + auctionId: utils.generateUUID('4'), auction, }); @@ -324,7 +353,7 @@ describe('AGMA Analytics Adapter', () => { }, })); const auction = { - auctionId: generateUUID(), + auctionId: utils.generateUUID(), }; events.emit(EVENTS.AUCTION_INIT, auction); @@ -348,7 +377,7 @@ describe('AGMA Analytics Adapter', () => { })); const auction = { - auctionId: generateUUID(), + auctionId: utils.generateUUID(), }; events.emit(EVENTS.AUCTION_INIT, auction); @@ -373,7 +402,7 @@ describe('AGMA Analytics Adapter', () => { }, }); const auction = { - auctionId: generateUUID(), + auctionId: utils.generateUUID(), }; events.emit(EVENTS.AUCTION_INIT, auction); From 0d6046658b6949cd7641fdf512a9f9094138294b Mon Sep 17 00:00:00 2001 From: pm-asit-sahoo <102290803+pm-asit-sahoo@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:24:15 +0530 Subject: [PATCH 0916/1097] Added logic to send pos to pubmatic adapter (#12768) Added logic to send pos to pubmatic adapter 1st draft Changes change in pos logic Update pubmaticBidAdapter.js updated test case for pos Update pubmaticAnalyticsAdapter.js changes sending pos in translator and test cases fix Update pubmaticBidAdapter.js --- modules/pubmaticBidAdapter.js | 6 ++++-- test/spec/modules/pubmaticBidAdapter_spec.js | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index ab83cfdf88a..2350633b6b3 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -61,7 +61,8 @@ const VIDEO_CUSTOM_PARAMS = { 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER, - 'skip': DATA_TYPES.NUMBER + 'skip': DATA_TYPES.NUMBER, + 'pos': DATA_TYPES.NUMBER } const NATIVE_ASSET_IMAGE_TYPE = { @@ -70,7 +71,8 @@ const NATIVE_ASSET_IMAGE_TYPE = { } const BANNER_CUSTOM_PARAMS = { - 'battr': DATA_TYPES.ARRAY + 'battr': DATA_TYPES.ARRAY, + 'pos': DATA_TYPES.NUMBER, } const NET_REVENUE = true; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 834db255e01..937e231169b 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -356,13 +356,20 @@ describe('PubMatic adapter', function () { bannerAndVideoBidRequests = [ { code: 'div-banner-video', + ortb2Imp: { + banner: { + pos: 1 + } + }, mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + pos: 2 }, banner: { - sizes: [[300, 250], [300, 600]] + sizes: [[300, 250], [300, 600]], + pos: 1 } }, bidder: 'pubmatic', @@ -2556,9 +2563,9 @@ describe('PubMatic adapter', function () { expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude + expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude + expect(data.user.geo.lat).to.equal('26.8915'); // Latitude + expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID @@ -2621,6 +2628,7 @@ describe('PubMatic adapter', function () { expect(data.banner.h).to.equal(250); expect(data.banner.format).to.exist; expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); + expect(data.banner.pos).to.equal(1); // Case: when size is not present in adslo bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; @@ -2638,6 +2646,7 @@ describe('PubMatic adapter', function () { expect(data.video).to.exist; expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.video.pos).to.equal(2); }); it('Request params - should handle banner, video and native format in single adunit', function() { @@ -2652,6 +2661,7 @@ describe('PubMatic adapter', function () { expect(data.banner.h).to.equal(250); expect(data.banner.format).to.exist; expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); + expect(data.banner.pos).to.equal(0); expect(data.video).to.exist; expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); From 8fa21f6d8f561544fc1e6fd1bc3c003cbb1a6889 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Wed, 19 Feb 2025 01:21:36 +1100 Subject: [PATCH 0917/1097] Handle legacy style native bid requests. (#12773) --- modules/adnuntiusBidAdapter.js | 36 ++++++++---- test/spec/modules/adnuntiusBidAdapter_spec.js | 55 +++++++++++++++++-- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index cf49de6d134..d8dd053d970 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,8 +1,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop} from '../src/utils.js'; +import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop, deepClone} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import {toLegacyResponse, toOrtbNativeRequest} from '../src/native.js'; const BIDDER_CODE = 'adnuntius'; const BIDDER_CODE_DEAL_ALIAS_BASE = 'adndeal'; @@ -361,7 +362,14 @@ export const spec = { adUnit.adType = 'VAST'; } else if (mediaType === NATIVE) { adUnit.adType = 'NATIVE'; - adUnit.nativeRequest = mediaTypeData.ortb; + if (!mediaTypeData.ortb) { + // assume it's using old format if ortb not specified + const oldStyleNativeRequest = deepClone(mediaTypeData); + delete oldStyleNativeRequest.sizes; + adUnit.nativeRequest = {ortb: toOrtbNativeRequest(oldStyleNativeRequest)} + } else { + adUnit.nativeRequest = {ortb: mediaTypeData.ortb}; + } } const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { @@ -409,7 +417,7 @@ export const spec = { }); } - function buildAdResponse(bidderCode, ad, adUnit, dealCount) { + function buildAdResponse(bidderCode, ad, adUnit, dealCount, bidOnRequest) { const advertiserDomains = ad.advertiserDomains || []; if (advertiserDomains.length === 0) { const destinationUrls = ad.destinationUrls || {}; @@ -442,7 +450,11 @@ export const spec = { adResponse.mediaType = VIDEO; } else if (renderSource.nativeJson) { adResponse.mediaType = NATIVE; - adResponse.native = renderSource.nativeJson; + if (!bidOnRequest.mediaTypes?.native?.ortb) { + adResponse.native = toLegacyResponse(renderSource.nativeJson); + } else { + adResponse.native = renderSource.nativeJson; + } } else { adResponse.ad = renderSource.html; } @@ -478,7 +490,7 @@ export const spec = { }); } - const bidsById = bidRequest.bid.reduce((response, bid) => { + const bidRequestsById = bidRequest.bid.reduce((response, bid) => { return { ...response, [bid.bidId]: bid @@ -486,7 +498,7 @@ export const spec = { }, {}); const hasBidAdUnits = highestYieldingAdUnits.filter((au) => { - const bid = bidsById[au.targetId]; + const bid = bidRequestsById[au.targetId]; if (bid && bid.bidder && BIDDER_CODE_DEAL_ALIASES.indexOf(bid.bidder) < 0) { return au.matchedAdCount > 0; } else { @@ -500,19 +512,19 @@ export const spec = { }); const dealAdResponses = hasDealsAdUnits.reduce((response, au) => { - const bid = bidsById[au.targetId]; - if (bid) { + const selBidRequest = bidRequestsById[au.targetId]; + if (selBidRequest) { (au.deals || []).forEach((deal, i) => { - response.push(buildAdResponse(bid.bidder, deal, au, i + 1)); + response.push(buildAdResponse(selBidRequest.bidder, deal, au, i + 1, selBidRequest)); }); } return response; }, []); const bidAdResponses = hasBidAdUnits.reduce((response, au) => { - const bid = bidsById[au.targetId]; - if (bid) { - response.push(buildAdResponse(bid.bidder, au.ads[0], au, 0)); + const selBidRequest = bidRequestsById[au.targetId]; + if (selBidRequest) { + response.push(buildAdResponse(selBidRequest.bidder, au.ads[0], au, 0, selBidRequest)); } return response; }, []); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 96e4b87883c..219793eb805 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -107,6 +107,25 @@ describe('adnuntiusBidAdapter', function () { } ]; + const legacyNativeBidderRequest = {bid: [ + { + bidId: 'adn-0000000000000551', + bidder: 'adnuntius', + params: { + auId: '0000000000000551', + network: 'adnuntius', + }, + mediaTypes: { + native: { + sizes: [[200, 200], [300, 300]], + image: { + required: true, + sizes: [250, 250] + } + } + } + }]}; + const nativeBidderRequest = {bid: [ { bidId: 'adn-0000000000000551', @@ -325,6 +344,9 @@ describe('adnuntiusBidAdapter', function () { 'auId': '0000000000000551', 'targetId': 'adn-0000000000000551', 'nativeJson': { + 'link': { + 'url': 'https://whatever.com' + }, 'assets': [ { 'id': 1, @@ -332,9 +354,6 @@ describe('adnuntiusBidAdapter', function () { 'img': { 'url': 'http://something.com/something.png' } - }, - { - 'url': 'http://whatever.com' }] }, 'matchedAdCount': 1, @@ -1655,7 +1674,7 @@ describe('adnuntiusBidAdapter', function () { expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{"adUnits":[{"auId":"0000000000000551","targetId":"adn-0000000000000551","adType":"NATIVE","nativeRequest":{"assets":[{"id":1,"required":1,"img":{"type":3,"w":250,"h":250}}]},"dimensions":[[200,200],[300,300]]}]}'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"0000000000000551","targetId":"adn-0000000000000551","adType":"NATIVE","nativeRequest":{"ortb":{"assets":[{"id":1,"required":1,"img":{"type":3,"w":250,"h":250}}]}},"dimensions":[[200,200],[300,300]]}]}'); }); it('should return valid response when passed valid server response', function () { @@ -1674,6 +1693,34 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); expect(interpretedResponse[0].vastXml).to.equal(ad.vastXml); + expect(JSON.stringify(interpretedResponse[0].native)).to.equal('{"link":{"url":"https://whatever.com"},"assets":[{"id":1,"required":1,"img":{"url":"http://something.com/something.png"}}]}'); + }); + + it('should pass legacy requests on correctly', function () { + const request = spec.buildRequests(legacyNativeBidderRequest.bid, {}); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('bid'); + expect(request[0]).to.have.property('data'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"0000000000000551","targetId":"adn-0000000000000551","adType":"NATIVE","nativeRequest":{"ortb":{"ver":"1.2","assets":[{"id":0,"required":1,"img":{"type":3,"w":250,"h":250}}]}},"dimensions":[[200,200],[300,300]]}]}'); + }); + + it('should return valid legacy response when passed valid server response', function () { + const interpretedResponse = spec.interpretResponse(nativeResponse, legacyNativeBidderRequest); + const ad = nativeResponse.body.adUnits[0].ads[0] + expect(interpretedResponse).to.have.lengthOf(1); + + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(ad.bid.amount * 1000); + expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[0].vastXml).to.equal(ad.vastXml); + expect(JSON.stringify(interpretedResponse[0].native)).to.equal('{"clickUrl":"https://whatever.com","icon":{"url":"http://something.com/something.png"},"impressionTrackers":[]}'); }); }); }); From c32889a78301f27aac40d11808dce08e342dcd3e Mon Sep 17 00:00:00 2001 From: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Tue, 18 Feb 2025 14:35:20 +0000 Subject: [PATCH 0918/1097] Alkimi Bid Adapter : added support for Imp extention object (#12750) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using * ALK-504 Multi size ad slot support * ALK-504 Multi size ad slot support * Support new OpenRTB parameters * Support new oRTB2 parameters * remove pos parameter * Add gvl_id into Alkimi adapter * Insert keywords into bid-request param * Resolve AUCTION_PRICE macro on prebid-server for VAST ads * Added support for full page auction * Added custom user object * userParams in request object * Handling user-sync url, store user id and passing custom params * Renamed the full_page_auction to fpa * Updated the review comment * Added support for Imp extention object --------- Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: mik --- modules/alkimiBidAdapter.js | 4 +++- test/spec/modules/alkimiBidAdapter_spec.js | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 3bd995cc112..f52c3ec7703 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -41,7 +41,8 @@ export const spec = { impMediaTypes: formatTypes, adUnitCode: bidRequest.adUnitCode, video: deepAccess(bidRequest, 'mediaTypes.video'), - banner: deepAccess(bidRequest, 'mediaTypes.banner') + banner: deepAccess(bidRequest, 'mediaTypes.banner'), + ext: bidRequest.ortb2Imp?.ext }) bidIds.push(bidRequest.bidId) }) @@ -81,6 +82,7 @@ export const spec = { }, at: ortb2?.at, bcat: ortb2?.bcat, + badv: ortb2?.badv, wseat: ortb2?.wseat } } diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index e551d98fa07..d642afd2b26 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -7,6 +7,12 @@ const REQUEST = { 'bidder': 'alkimi', 'sizes': [[300, 250]], 'adUnitCode': 'bannerAdUnitCode', + 'ortb2Imp': { + 'ext': { + 'gpid': '/111/banner#300x250', + 'tid': 'e64782a4-8e68-4c38-965b-80ccf115d46a' + } + }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250]] @@ -148,7 +154,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.data.requestId).to.not.equal(undefined) expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.schain).to.deep.contains({ ver: '1.0', complete: 1, nodes: [{ asi: 'alkimi-onboarding.com', sid: '00001', hp: 1 }] }) - expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', bidFloor: 0.1, sizes: [{ width: 300, height: 250 }], playerSizes: [], impMediaTypes: ['Banner'], adUnitCode: 'bannerAdUnitCode', instl: undefined, exp: undefined, banner: { sizes: [[300, 250]] }, video: undefined }) + expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', bidFloor: 0.1, sizes: [{ width: 300, height: 250 }], playerSizes: [], impMediaTypes: ['Banner'], adUnitCode: 'bannerAdUnitCode', instl: undefined, exp: undefined, banner: { sizes: [[300, 250]] }, video: undefined, ext: { gpid: '/111/banner#300x250', tid: 'e64782a4-8e68-4c38-965b-80ccf115d46a' } }) expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') expect(bidderRequest.data.signature).to.equal(undefined) From 49d665645d017dedf14cc2b6fed56c0933feac46 Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:38:46 +0200 Subject: [PATCH 0919/1097] Attekmi: add Jambojar adapter (#12754) Co-authored-by: Victor --- modules/smarthubBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index ddf76d64903..bfaf7755516 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -18,6 +18,7 @@ const ALIASES = [ {code: 'artechnology'}, {code: 'adinify'}, {code: 'addigi'}, + {code: 'jambojar'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', @@ -30,6 +31,7 @@ const BASE_URLS = { artechnology: 'https://artechnology-prebid.attekmi.com/pbjs', adinify: 'https://adinify-prebid.attekmi.com/pbjs', addigi: 'https://addigi-prebid.attekmi.com/pbjs', + jambojar: 'https://jambojar-prebid.attekmi.com/pbjs', }; const _getUrl = (partnerName) => { From b1d3f71089216f423b1c00a94bd8d9b6f7aa7513 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 18 Feb 2025 07:40:12 -0800 Subject: [PATCH 0920/1097] Core: improve some error messages (#12782) --- src/Renderer.js | 3 ++- src/adapters/bidderFactory.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Renderer.js b/src/Renderer.js index 772d8d93655..d3dd23b50af 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -24,6 +24,7 @@ export function Renderer(options) { this.handlers = {}; this.id = id; this.renderNow = renderNow; + this.adUnitCode = adUnitCode; // a renderer may push to the command queue to delay rendering until the // render function is loaded by loadExternalScript, at which point the the command @@ -101,7 +102,7 @@ Renderer.prototype.process = function() { try { this.cmd.shift().call(); } catch (error) { - logError('Error processing Renderer command: ', error); + logError(`Error processing Renderer command on ad unit '${this.adUnitCode}':`, error); } } }; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 2e3f2f3ec8f..23a1fe79c88 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -310,7 +310,7 @@ export function newBidder(spec) { } adapterManager.callBidderError(spec.code, error, bidderRequest) events.emit(EVENTS.BIDDER_ERROR, { error, bidderRequest }); - logError(`Server call for ${spec.code} failed: ${errorMessage} ${error.status}. Continuing without bids.`); + logError(`Server call for ${spec.code} failed: ${errorMessage} ${error.status}. Continuing without bids.`, {bidRequests: validBidRequests}); }, onBid: (bid) => { const bidRequest = bidRequestMap[bid.requestId]; From 73ce2666ac672ce5e866621bbe157312b3ad0c7d Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Tue, 18 Feb 2025 07:48:42 -0800 Subject: [PATCH 0921/1097] New Module: MinBidToWin Notifications: Created a new module to support sending minbidtowin notifications to bidders (#11086) * created client side loss notifications module * addressed feedback and created tests * addressed feedback * addressed feedback, updated tests * removed some console logs and unecessary changes used for local testing * removed linebreak * added publisher opt-in logic and refactored onBidWonHandler * updated tests and prevAuctionInfo init logic * detach event listeners on deactivation --------- Co-authored-by: Demetrio Girardi --- .../previousAuctionInfo.js | 135 ++++++++++++++ .../libraries/previousAuctionInfo_spec.js | 167 ++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 libraries/previousAuctionInfo/previousAuctionInfo.js create mode 100644 test/spec/libraries/previousAuctionInfo_spec.js diff --git a/libraries/previousAuctionInfo/previousAuctionInfo.js b/libraries/previousAuctionInfo/previousAuctionInfo.js new file mode 100644 index 00000000000..0b964d7abe4 --- /dev/null +++ b/libraries/previousAuctionInfo/previousAuctionInfo.js @@ -0,0 +1,135 @@ +import {on as onEvent, off as offEvent} from '../../src/events.js'; +import { EVENTS } from '../../src/constants.js'; +import { config } from '../../src/config.js'; + +export let previousAuctionInfoEnabled = false; +let enabledBidders = []; + +export let auctionState = {}; + +export const resetPreviousAuctionInfo = (cb = deinitHandlers) => { + previousAuctionInfoEnabled = false; + enabledBidders = []; + auctionState = {}; + cb(); +}; + +export const initPreviousAuctionInfo = (cb = initHandlers) => { + config.getConfig('previousAuctionInfo', (conf) => { + if (!conf.previousAuctionInfo) { + if (previousAuctionInfoEnabled) { resetPreviousAuctionInfo(); } + return; + } + + previousAuctionInfoEnabled = true; + cb(); + }); +}; + +export const enablePreviousAuctionInfo = (sspConfig) => { + const { bidderCode } = sspConfig; + const enabledBidder = enabledBidders.find(bidder => bidder.bidderCode === bidderCode); + + if (!enabledBidder) enabledBidders.push({ bidderCode, maxQueueLength: sspConfig.maxQueueLength || 10 }); +} + +export const initHandlers = () => { + onEvent(EVENTS.AUCTION_END, onAuctionEndHandler); + onEvent(EVENTS.BID_WON, onBidWonHandler); + onEvent(EVENTS.BID_REQUESTED, onBidRequestedHandler); +}; + +const deinitHandlers = () => { + offEvent(EVENTS.AUCTION_END, onAuctionEndHandler); + offEvent(EVENTS.BID_WON, onBidWonHandler); + offEvent(EVENTS.BID_REQUESTED, onBidRequestedHandler); +} + +export const onAuctionEndHandler = (auctionDetails) => { + try { + const receivedBidsMap = {}; + const rejectedBidsMap = {}; + const highestBidsByAdUnitCode = {}; + + if (auctionDetails.bidsReceived?.length) { + auctionDetails.bidsReceived.forEach((bid) => { + receivedBidsMap[bid.requestId] = bid; + if (!highestBidsByAdUnitCode[bid.adUnitCode] || bid.cpm > highestBidsByAdUnitCode[bid.adUnitCode].cpm) { + highestBidsByAdUnitCode[bid.adUnitCode] = bid; + } + }); + } + + if (auctionDetails.bidsRejected?.length) { + auctionDetails.bidsRejected.forEach(bidRejected => { + rejectedBidsMap[bidRejected.requestId] = bidRejected; + }); + } + + if (auctionDetails.bidderRequests?.length) { + auctionDetails.bidderRequests.forEach(bidderRequest => { + const enabledBidder = enabledBidders.find(bidder => bidder.bidderCode === bidderRequest.bidderCode); + + if (enabledBidder) { + auctionState[bidderRequest.bidderCode] = auctionState[bidderRequest.bidderCode] || []; + + bidderRequest.bids.forEach(bid => { + const previousAuctionInfoPayload = { + bidderRequestId: bidderRequest.bidderRequestId, + bidId: bid.bidId, + rendered: 0, + source: 'pbjs', + adUnitCode: bid.adUnitCode, + highestTargetedBidCpm: highestBidsByAdUnitCode[bid.adUnitCode]?.adserverTargeting?.hb_pb || '', + targetedBidCpm: receivedBidsMap[bid.bidId]?.adserverTargeting?.hb_pb || '', + highestBidCpm: highestBidsByAdUnitCode[bid.adUnitCode]?.cpm || 0, + bidderCpm: receivedBidsMap[bid.bidId]?.cpm || 'nobid', + bidderOriginalCpm: receivedBidsMap[bid.bidId]?.originalCpm || 'nobid', + bidderCurrency: receivedBidsMap[bid.bidId]?.currency || 'nobid', + bidderOriginalCurrency: receivedBidsMap[bid.bidId]?.originalCurrency || 'nobid', + bidderErrorCode: rejectedBidsMap[bid.bidId] ? rejectedBidsMap[bid.bidId].rejectionReason : -1, + timestamp: auctionDetails.timestamp, + transactionId: bid.transactionId, // this field gets removed before injecting previous auction info into the bid stream + } + + if (auctionState[bidderRequest.bidderCode].length > enabledBidder.maxQueueLength) { + auctionState[bidderRequest.bidderCode].shift(); + } + + auctionState[bidderRequest.bidderCode].push(previousAuctionInfoPayload); + }); + } + }); + } + } catch (error) {} +} + +export const onBidWonHandler = (winningBid) => { + const winningTid = winningBid.transactionId; + + Object.values(auctionState).flat().forEach(prevAuctPayload => { + if (prevAuctPayload.transactionId === winningTid) { + prevAuctPayload.rendered = 1; + } + }); +}; + +export const onBidRequestedHandler = (bidRequest) => { + try { + const enabledBidder = enabledBidders.find(bidder => bidder.bidderCode === bidRequest.bidderCode); + if (enabledBidder && auctionState[bidRequest.bidderCode]) { + auctionState[bidRequest.bidderCode].forEach(prevAuctPayload => { + if (prevAuctPayload.transactionId) delete prevAuctPayload.transactionId; + }); + + bidRequest.ortb2 = Object.assign({}, bidRequest.ortb2); + bidRequest.ortb2.ext = Object.assign({}, bidRequest.ortb2.ext); + bidRequest.ortb2.ext.prebid = Object.assign({}, bidRequest.ortb2.ext.prebid); + + bidRequest.ortb2.ext.prebid.previousauctioninfo = auctionState[bidRequest.bidderCode]; + delete auctionState[bidRequest.bidderCode]; + } + } catch (error) {} +} + +initPreviousAuctionInfo(); diff --git a/test/spec/libraries/previousAuctionInfo_spec.js b/test/spec/libraries/previousAuctionInfo_spec.js new file mode 100644 index 00000000000..9806ccd7f31 --- /dev/null +++ b/test/spec/libraries/previousAuctionInfo_spec.js @@ -0,0 +1,167 @@ +import * as previousAuctionInfo from 'libraries/previousAuctionInfo/previousAuctionInfo.js'; +import sinon from 'sinon'; +import { expect } from 'chai'; +import { config } from 'src/config.js'; + +describe('previous auction info', () => { + let sandbox; + let initHandlersStub; + + const auctionDetails = { + auctionId: 'auction123', + bidsReceived: [ + { requestId: 'bid123', bidderCode: 'testBidder1', cpm: 1, adUnitCode: 'adUnit1', currency: 'USD', originalCpm: 1.1, originalCurrency: 'USD' }, + { requestId: 'bidabc', bidderCode: 'testBidder2', cpm: 2, adUnitCode: 'adUnit1', currency: 'EUR', originalCpm: 2.1, originalCurrency: 'EUR' }, + { requestId: 'bidxyz', bidderCode: 'testBidder3', cpm: 3, adUnitCode: 'adUnit2', currency: 'USD', originalCpm: 3.2, originalCurrency: 'USD' } + ], + bidsRejected: [ + { requestId: 'bid456', rejectionReason: 1 }, + { requestId: 'bid789', rejectionReason: 2 } + ], + bidderRequests: [ + { + bidderCode: 'testBidder1', + bidderRequestId: 'req1', + bids: [ + { bidId: 'bid123', ortb2: { cur: ['USD'] }, ortb2Imp: { ext: { tid: 'trans123' } }, adUnitCode: 'adUnit1' } + ] + }, + { + bidderCode: 'testBidder2', + bidderRequestId: 'req2', + bids: [ + { bidId: 'bidabc', ortb2: { cur: ['EUR'] }, ortb2Imp: { ext: { tid: 'trans456' } }, adUnitCode: 'adUnit1' } + ] + }, + { + bidderCode: 'testBidder3', + bidderRequestId: 'req3', + bids: [ + { bidId: 'bidxyz', ortb2: { cur: ['USD'] }, ortb2Imp: { ext: { tid: 'trans789' } }, adUnitCode: 'adUnit2' } + ] + } + ], + timestamp: Date.now(), + }; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + previousAuctionInfo.resetPreviousAuctionInfo(); + initHandlersStub = sandbox.stub(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('config', () => { + it('should initialize the module if publisher enabled', () => { + previousAuctionInfo.initPreviousAuctionInfo(initHandlersStub); + config.setConfig({ previousAuctionInfo: true }); + sandbox.assert.calledOnce(initHandlersStub); + }); + + it('should not enable previous auction info if config.previousAuctionInfo is not set', () => { + sandbox.restore(); + previousAuctionInfo.initPreviousAuctionInfo(initHandlersStub); + config.setConfig({ previousAuctionInfo: false }); + expect(previousAuctionInfo.previousAuctionInfoEnabled).to.be.false; + }); + }); + + describe('onAuctionEndHandler', () => { + it('should store auction data for enabled bidders in auctionState', () => { + const config = { bidderCode: 'testBidder2' }; + previousAuctionInfo.enablePreviousAuctionInfo(config); + previousAuctionInfo.onAuctionEndHandler(auctionDetails); + + expect(previousAuctionInfo.auctionState).to.have.property('testBidder2'); + expect(previousAuctionInfo.auctionState['testBidder2']).to.be.an('array').with.lengthOf(1); + + const storedData = previousAuctionInfo.auctionState['testBidder2'][0]; + + expect(storedData).to.include({ + bidderRequestId: 'req2', + bidId: 'bidabc', + rendered: 0, + source: 'pbjs', + adUnitCode: 'adUnit1', + highestBidCpm: 2, + bidderCpm: 2, + bidderOriginalCpm: 2.1, + bidderCurrency: 'EUR', + bidderOriginalCurrency: 'EUR', + bidderErrorCode: -1, + timestamp: auctionDetails.timestamp + }); + }); + + it('should store auction data for multiple bidders correctly', () => { + const config1 = { bidderCode: 'testBidder1' }; + const config2 = { bidderCode: 'testBidder3' }; + previousAuctionInfo.enablePreviousAuctionInfo(config1); + previousAuctionInfo.enablePreviousAuctionInfo(config2); + previousAuctionInfo.onAuctionEndHandler(auctionDetails); + + expect(previousAuctionInfo.auctionState).to.have.property('testBidder1'); + expect(previousAuctionInfo.auctionState).to.have.property('testBidder3'); + + expect(previousAuctionInfo.auctionState['testBidder1'][0]).to.include({ + bidId: 'bid123', + highestBidCpm: 2, + adUnitCode: 'adUnit1', + bidderCpm: 1, + bidderCurrency: 'USD' + }); + + expect(previousAuctionInfo.auctionState['testBidder3'][0]).to.include({ + bidId: 'bidxyz', + highestBidCpm: 3, + adUnitCode: 'adUnit2', + bidderCpm: 3, + bidderCurrency: 'USD' + }); + }); + + it('should not store auction data for disabled bidders', () => { + previousAuctionInfo.onAuctionEndHandler(auctionDetails); + expect(previousAuctionInfo.auctionState).to.not.have.property('testBidder2'); + }); + }); + + describe('onBidWonHandler', () => { + it('should update the rendered field in auctionState when a pbjs bid wins', () => { + const config = { bidderCode: 'testBidder3' }; + previousAuctionInfo.enablePreviousAuctionInfo(config); + + previousAuctionInfo.auctionState['testBidder3'] = [ + { transactionId: 'trans789', rendered: 0 } + ]; + + const winningBid = { + transactionId: 'trans789' + }; + + previousAuctionInfo.onBidWonHandler(winningBid); + + expect(previousAuctionInfo.auctionState['testBidder3'][0]).to.include({ rendered: 1 }); + }); + + it('should not update the rendered field if no matching transactionId is found', () => { + const config = { bidderCode: 'testBidder3' }; + previousAuctionInfo.enablePreviousAuctionInfo(config); + + previousAuctionInfo.auctionState['testBidder3'] = [ + { transactionId: 'someOtherTid', rendered: 0 } + ]; + + const winningBid = { + transactionId: 'trans789' + }; + + previousAuctionInfo.onBidWonHandler(winningBid); + + expect(previousAuctionInfo.auctionState['testBidder3'][0]).to.include({ rendered: 0 }); + }); + }); +}); From 17c3b416450a57f6fd51a2d111af9925ed144c51 Mon Sep 17 00:00:00 2001 From: Richard Andersson <72393300+holidio@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:38:07 +0100 Subject: [PATCH 0922/1097] Holid Bid Adapter : enhance logic for regs.ext and updated docs (#12761) * Update Holid Bid Adapter Added GDPR/GPP/US Privacy support and updated docs * Update Holid Bid Adapter tests Fix interpretResponse and user sync expectations --- modules/holidBidAdapter.js | 229 +++++++++++++++------- modules/holidBidAdapter.md | 42 +++- test/spec/modules/holidBidAdapter_spec.js | 128 ++++++------ 3 files changed, 257 insertions(+), 142 deletions(-) diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index 90bc0c78212..abc8e0b403c 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -1,44 +1,74 @@ import { deepAccess, - deepSetValue, getBidIdParameter, + deepSetValue, + getBidIdParameter, isStr, logMessage, triggerPixel, } from '../src/utils.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'holid' -const GVLID = 1177 -const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction' -const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html' -const TIME_TO_LIVE = 300 -const TMAX = 500 -let wurlMap = {} +const BIDDER_CODE = 'holid'; +const GVLID = 1177; +const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction'; +const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html'; +const TIME_TO_LIVE = 300; +const TMAX = 500; +let wurlMap = {}; export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER], + // Validate bid: requires adUnitID parameter isBidRequestValid: function (bid) { - return !!bid.params.adUnitID + return !!bid.params.adUnitID; }, + // Build request payload including GDPR, GPP, and US Privacy data if available buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map((bid) => { const requestData = { ...bid.ortb2, - source: {schain: bid.schain}, + source: { schain: bid.schain }, id: bidderRequest.bidderRequestId, imp: [getImp(bid)], tmax: TMAX, - ...buildStoredRequest(bid) + ...buildStoredRequest(bid), + }; + + // GDPR: If available, include GDPR signals in the request + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue( + requestData, + 'regs.ext.gdpr', + bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + ); + deepSetValue( + requestData, + 'user.ext.consent', + bidderRequest.gdprConsent.consentString + ); + } + + // GPP: If available, include GPP data in regs.ext + if (bidderRequest && bidderRequest.gpp) { + deepSetValue(requestData, 'regs.ext.gpp', bidderRequest.gpp); + } + if (bidderRequest && bidderRequest.gppSids) { + deepSetValue(requestData, 'regs.ext.gpp_sid', bidderRequest.gppSids); } + // US Privacy: If available, include US Privacy signal in regs.ext + if (bidderRequest && bidderRequest.usPrivacy) { + deepSetValue(requestData, 'regs.ext.us_privacy', bidderRequest.usPrivacy); + } + + // If user IDs are available, add them under user.ext.eids if (bid.userIdAsEids) { - deepSetValue(requestData, 'user.ext.eids', bid.userIdAsEids) + deepSetValue(requestData, 'user.ext.eids', bid.userIdAsEids); } return { @@ -46,23 +76,35 @@ export const spec = { url: ENDPOINT, data: JSON.stringify(requestData), bidId: bid.bidId, - } - }) + }; + }); }, + // Interpret response: group bids by unique impid and select the highest CPM bid per imp interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = [] - - if (!serverResponse.body.seatbid) { - return [] + const bidResponsesMap = {}; // Maps impid -> highest bid object + if (!serverResponse.body || !serverResponse.body.seatbid) { + return []; } - serverResponse.body.seatbid.map((response) => { - response.bid.map((bid) => { - const requestId = bidRequest.bidId - const wurl = deepAccess(bid, 'ext.prebid.events.win') - const bidResponse = { - requestId, + serverResponse.body.seatbid.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + const impId = bid.impid; // Unique identifier matching getImp(bid).id + + // Build meta object with adomain and networkId, preserving any existing data + let meta = deepAccess(bid, 'ext.prebid.meta', {}) || {}; + const adomain = deepAccess(bid, 'adomain', []); + if (adomain.length > 0) { + meta.adomain = adomain; + } + const networkId = deepAccess(bid, 'ext.prebid.meta.networkId'); + if (networkId) { + meta.networkId = networkId; + } + deepSetValue(bid, 'ext.prebid.meta', meta); + + const currentBidResponse = { + requestId: impId, // Using imp.id as the unique request identifier cpm: bid.price, width: bid.w, height: bid.h, @@ -71,78 +113,111 @@ export const spec = { currency: serverResponse.body.cur, netRevenue: true, ttl: TIME_TO_LIVE, + meta: meta, + }; + + // For each imp, only keep the bid with the highest CPM + if ( + !bidResponsesMap[impId] || + currentBidResponse.cpm > bidResponsesMap[impId].cpm + ) { + bidResponsesMap[impId] = currentBidResponse; } - addWurl(requestId, wurl) - - bidResponses.push(bidResponse) - }) - }) + // Store win notification URL (if provided) using the impid as key + const wurl = deepAccess(bid, 'ext.prebid.events.win'); + if (wurl) { + addWurl(impId, wurl); + } + }); + }); - return bidResponses + return Object.values(bidResponsesMap); }, + // User syncs: supports both image and iframe syncing with privacy parameters if available getUserSyncs(optionsType, serverResponse, gdprConsent, uspConsent) { - const syncs = [{ - type: 'image', - url: 'https://track.adform.net/Serving/TrackPoint/?pm=2992097&lid=132720821' - }] + const syncs = [ + { + type: 'image', + url: 'https://track.adform.net/Serving/TrackPoint/?pm=2992097&lid=132720821', + }, + ]; - if (!serverResponse || serverResponse.length === 0) { - return syncs + if (!serverResponse || (Array.isArray(serverResponse) && serverResponse.length === 0)) { + return syncs; } - const bidders = getBidders(serverResponse) - // note this only does the iframe sync when gdpr consent object exists to match previous behavior (generate error on gdprconsent not existing) - if (optionsType.iframeEnabled && bidders && gdprConsent) { - const queryParams = [] - - queryParams.push('bidders=' + bidders) - queryParams.push('gdpr=' + +gdprConsent?.gdprApplies) - queryParams.push('gdpr_consent=' + gdprConsent?.consentString) - queryParams.push('usp_consent=' + (uspConsent || '')) - - let strQueryParams = queryParams.join('&') + const responses = Array.isArray(serverResponse) + ? serverResponse + : [serverResponse]; + const bidders = getBidders(responses); + + if (optionsType.iframeEnabled && bidders) { + const queryParams = []; + queryParams.push('bidders=' + bidders); + + if (gdprConsent) { + queryParams.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParams.push( + 'gdpr_consent=' + + encodeURIComponent(gdprConsent.consentString || '') + ); + } else { + queryParams.push('gdpr=0'); + } - if (strQueryParams.length > 0) { - strQueryParams = '?' + strQueryParams + if (typeof uspConsent !== 'undefined') { + queryParams.push('us_privacy=' + encodeURIComponent(uspConsent)); } + queryParams.push('type=iframe'); + const strQueryParams = '?' + queryParams.join('&'); + syncs.push({ type: 'iframe', - url: COOKIE_SYNC_ENDPOINT + strQueryParams + '&type=iframe', - }) + url: COOKIE_SYNC_ENDPOINT + strQueryParams, + }); } - return syncs + return syncs; }, + // On bid win, trigger win notification via an image pixel if available onBidWon(bid) { - const wurl = getWurl(bid.requestId) + const wurl = getWurl(bid.requestId); if (wurl) { - logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) - triggerPixel(wurl) - removeWurl(bid.requestId) + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`); + triggerPixel(wurl); + removeWurl(bid.requestId); } - } -} + }, +}; +// Create a unique impression object with bid id as the identifier function getImp(bid) { - const imp = buildStoredRequest(bid) + const imp = buildStoredRequest(bid); + imp.id = bid.bidId; // Ensure imp.id is unique to match the bid response correctly const sizes = - bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes + bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes; if (deepAccess(bid, 'mediaTypes.banner')) { imp.banner = { format: sizes.map((size) => { - return { w: size[0], h: size[1] } + return { w: size[0], h: size[1] }; }), - } + }; } - return imp + // Include bid floor if defined in bid.params + if (bid.params.floor) { + imp.bidfloor = bid.params.floor; + } + + return imp; } +// Build stored request object using bid parameters function buildStoredRequest(bid) { return { ext: { @@ -152,33 +227,35 @@ function buildStoredRequest(bid) { }, }, }, - } + }; } -function getBidders(serverResponse) { - const bidders = serverResponse - .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) - .flat(1) +// Helper: Extract unique bidders from responses for user syncs +function getBidders(responses) { + const bidders = responses + .map((res) => Object.keys(res.body.ext?.responsetimemillis || {})) + .flat(); if (bidders.length) { - return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + return encodeURIComponent(JSON.stringify([...new Set(bidders)])); } } +// Win URL helper functions function addWurl(requestId, wurl) { if (isStr(requestId)) { - wurlMap[requestId] = wurl + wurlMap[requestId] = wurl; } } function removeWurl(requestId) { - delete wurlMap[requestId] + delete wurlMap[requestId]; } function getWurl(requestId) { if (isStr(requestId)) { - return wurlMap[requestId] + return wurlMap[requestId]; } } -registerBidder(spec) +registerBidder(spec); diff --git a/modules/holidBidAdapter.md b/modules/holidBidAdapter.md index 1d83918c00a..db6396a203e 100644 --- a/modules/holidBidAdapter.md +++ b/modules/holidBidAdapter.md @@ -8,9 +8,43 @@ Maintainer: richard@holid.se # Description -Currently module supports only banner mediaType. +The Holid Bid Adapter is a Prebid.js bidder adapter designed for display (banner) ads. It fully supports TCF (GDPR) and GPP frameworks, along with US Privacy signals. Key features include: -# Test Parameters +``` +Supply Chain (schain) support ensuring transparency. +Safeframes to securely display ads. +Floors Module Support to enforce bid floors. +Comprehensive User ID integrations. +Compliance with COPPA and support for first-party data. +The adapter selects the highest bid for optimal revenue. +Additional support for Demand Chain and ORTB Blocking should be confirmed with the bidder. +``` + +# Specifications + +``` +Bidder Code: holid +Prebid.js Adapter: yes +Media Types: display (banner) +TCF-EU Support: yes +GPP Support: yes +Supply Chain Support: yes +Safeframes OK: yes +Floors Module Support: yes +User IDs: all +Privacy Sandbox: check with bidder +Prebid.org Member: no +Prebid Server Adapter: no +Multi Format Support: no +IAB GVL ID: 1177 +DSA Support: no +COPPA Support: yes +Demand Chain Support: check with bidder +Supports Deals: yes +First Party Data Support: yes +ORTB Blocking Support: check with bidder +Prebid Server App Support: no +``` ## Sample Banner Ad Unit @@ -28,9 +62,11 @@ var adUnits = [ bidder: 'holid', params: { adUnitID: '12345', + // Optional: set a bid floor if needed + floor: 0.5, }, }, ], }, -] +]; ``` diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js index ef0283d0f2c..671427d3475 100644 --- a/test/spec/modules/holidBidAdapter_spec.js +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -1,10 +1,10 @@ -import { expect } from 'chai' -import { spec } from 'modules/holidBidAdapter.js' +import { expect } from 'chai'; +import { spec } from 'modules/holidBidAdapter.js'; describe('holidBidAdapterTests', () => { const bidderRequest = { bidderRequestId: 'test-id' - } + }; const bidRequestData = { bidder: 'holid', @@ -32,59 +32,60 @@ describe('holidBidAdapterTests', () => { w: 1860, } } - } + }; describe('isBidRequestValid', () => { - const bid = JSON.parse(JSON.stringify(bidRequestData)) + const bid = JSON.parse(JSON.stringify(bidRequestData)); it('should return true', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); it('should return false when required params are not passed', () => { - const bid = JSON.parse(JSON.stringify(bidRequestData)) - delete bid.params.adUnitID + const bid = JSON.parse(JSON.stringify(bidRequestData)); + delete bid.params.adUnitID; - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - }) + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); describe('buildRequests', () => { - const bid = JSON.parse(JSON.stringify(bidRequestData)) - const request = spec.buildRequests([bid], bidderRequest) - const payload = JSON.parse(request[0].data) + const bid = JSON.parse(JSON.stringify(bidRequestData)); + const request = spec.buildRequests([bid], bidderRequest); + const payload = JSON.parse(request[0].data); it('should include id in request', () => { - expect(payload.id).to.equal('test-id') - }) + expect(payload.id).to.equal('test-id'); + }); it('should include ext in imp', () => { expect(payload.imp[0].ext).to.deep.equal({ prebid: { storedrequest: { id: '12345' } }, - }) - }) + }); + }); it('should include ext in request', () => { expect(payload.ext).to.deep.equal({ prebid: { storedrequest: { id: '12345' } }, - }) - }) + }); + }); it('should include banner format in imp', () => { expect(payload.imp[0].banner).to.deep.equal({ format: [{ w: 300, h: 250 }], - }) - }) + }); + }); it('should include ortb2 first party data', () => { - expect(payload.device.w).to.equal(1860) - expect(payload.device.h).to.equal(410) - expect(payload.user.ext.consent).to.equal('G4ll0p1ng_Un1c0rn5') - expect(payload.regs.gdpr).to.equal(1) - }) - }) + expect(payload.device.w).to.equal(1860); + expect(payload.device.h).to.equal(410); + expect(payload.user.ext.consent).to.equal('G4ll0p1ng_Un1c0rn5'); + expect(payload.regs.gdpr).to.equal(1); + }); + }); describe('interpretResponse', () => { + // Add impid: 'bid-id' so requestId matches bidRequestData.bidId const serverResponse = { body: { id: 'test-id', @@ -94,6 +95,7 @@ describe('holidBidAdapterTests', () => { bid: [ { id: 'testbidid', + impid: 'bid-id', price: 0.4, adm: 'test-ad', adid: 789456, @@ -105,40 +107,37 @@ describe('holidBidAdapterTests', () => { }, ], }, - } + }; - const interpretedResponse = spec.interpretResponse( - serverResponse, - bidRequestData - ) + const interpretedResponse = spec.interpretResponse(serverResponse, bidRequestData); it('should interpret response', () => { - expect(interpretedResponse[0].requestId).to.equal(bidRequestData.bidId) + expect(interpretedResponse[0].requestId).to.equal(bidRequestData.bidId); expect(interpretedResponse[0].cpm).to.equal( serverResponse.body.seatbid[0].bid[0].price - ) + ); expect(interpretedResponse[0].ad).to.equal( serverResponse.body.seatbid[0].bid[0].adm - ) + ); expect(interpretedResponse[0].creativeId).to.equal( serverResponse.body.seatbid[0].bid[0].crid - ) + ); expect(interpretedResponse[0].width).to.equal( serverResponse.body.seatbid[0].bid[0].w - ) + ); expect(interpretedResponse[0].height).to.equal( serverResponse.body.seatbid[0].bid[0].h - ) - expect(interpretedResponse[0].currency).to.equal(serverResponse.body.cur) - }) - }) + ); + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.cur); + }); + }); describe('getUserSyncs', () => { it('should return user sync', () => { const optionsType = { iframeEnabled: true, pixelEnabled: true, - } + }; const serverResponse = [ { body: { @@ -150,12 +149,14 @@ describe('holidBidAdapterTests', () => { }, }, }, - ] + ]; const gdprConsent = { gdprApplies: 1, consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', - } - const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + }; + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb'; + + // Updated 'usp_consent' to 'us_privacy' to match adapter code const expectedUserSyncs = [ { type: 'image', @@ -163,52 +164,53 @@ describe('holidBidAdapterTests', () => { }, { type: 'iframe', - url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig&usp_consent=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', + url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as%3A12jaf90123hufabidfy9u23brfpoig&us_privacy=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', }, - ] + ]; const userSyncs = spec.getUserSyncs( optionsType, serverResponse, gdprConsent, uspConsent - ) + ); - expect(userSyncs).to.deep.equal(expectedUserSyncs) - }) + expect(userSyncs).to.deep.equal(expectedUserSyncs); + }); it('should return base user syncs when responsetimemillis is not defined', () => { const optionsType = { iframeEnabled: true, pixelEnabled: true, - } + }; const serverResponse = [ { body: { ext: {}, }, }, - ] + ]; const gdprConsent = { gdprApplies: 1, consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', - } - const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + }; + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb'; + const expectedUserSyncs = [ { type: 'image', url: 'https://track.adform.net/Serving/TrackPoint/?pm=2992097&lid=132720821', - } - ] + }, + ]; const userSyncs = spec.getUserSyncs( optionsType, serverResponse, gdprConsent, uspConsent - ) + ); - expect(userSyncs).to.deep.equal(expectedUserSyncs) - }) - }) -}) + expect(userSyncs).to.deep.equal(expectedUserSyncs); + }); + }); +}); From c22ba5fb4c655db5630d546ce0fdc7eb5e8d57a9 Mon Sep 17 00:00:00 2001 From: xiaochang <106997162@qq.com> Date: Wed, 19 Feb 2025 22:01:21 +0800 Subject: [PATCH 0923/1097] Rixengine Bid Adapter : add "algorix" as an alias (#12789) * RixEngine Bid Adapter: Add RixEngine bid adapter * update rixengineBidAdapter_spec.js * remove the user ID opt in and provide a test endpoint * add algorix aliases --------- Co-authored-by: Yuanchang --- modules/rixengineBidAdapter.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/rixengineBidAdapter.js b/modules/rixengineBidAdapter.js index 8ffdb55f09b..b0a419d4282 100644 --- a/modules/rixengineBidAdapter.js +++ b/modules/rixengineBidAdapter.js @@ -26,6 +26,11 @@ const converter = ortbConverter({ }); export const spec = { code: BIDDER_CODE, + // Register "algorix" as an alias, also with gvlid if needed + aliases: [{ + code: 'algorix', + gvlid: 1176 + }], supportedMediaTypes: [BANNER], isBidRequestValid: function (bid) { From 6901d5f59d946b0bfc2d3abd4e6045dc07c40b00 Mon Sep 17 00:00:00 2001 From: andre-gielow-ttd <124626380+andre-gielow-ttd@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:13:55 -0500 Subject: [PATCH 0924/1097] Add integration type header (#12786) --- modules/ttdBidAdapter.js | 7 +++++-- test/spec/modules/ttdBidAdapter_spec.js | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 082b9806da5..323c76dd590 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -14,7 +14,7 @@ import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.j * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ -const BIDADAPTERVERSION = 'TTD-PREBID-2024.07.26'; +const BIDADAPTERVERSION = 'TTD-PREBID-2024.07.27'; const BIDDER_CODE = 'ttd'; const BIDDER_CODE_LONG = 'thetradedesk'; const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/'; @@ -440,7 +440,10 @@ export const spec = { url: url, data: topLevel, options: { - withCredentials: true + withCredentials: true, + customHeaders: { + 'x-integration-type': 1, + }, } }; diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 4580c514609..5f21c9c3235 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -291,6 +291,13 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.publisher.id).to.equal(baseBannerBidRequests[0].params.publisherId); }); + it('sends integration type header', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest); + expect(requestBody.options).to.be.not.null; + expect(requestBody.options.customHeaders).to.be.not.null; + expect(requestBody.options.customHeaders['x-integration-type']).to.equal(1); + }); + it('sends placement id in tagid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.imp[0].tagid).to.equal(baseBannerBidRequests[0].params.placementId); From 751634313218b42d575aa8c93e65aa19e99a28db Mon Sep 17 00:00:00 2001 From: Shiloh Annese Date: Wed, 19 Feb 2025 07:06:01 -0800 Subject: [PATCH 0925/1097] Qortex Rtd Provider : implements code version and adjusts for new expected data structure of API responses (#12771) * updates necessary files for data structure and version update * wording * arrange variables * linting error * moves qortex version data --- modules/qortexRtdProvider.js | 8 ++++-- modules/qortexRtdProvider.md | 2 +- test/spec/modules/qortexRtdProvider_spec.js | 32 +++++++++++---------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js index 1ee8f8cf5d0..8049f02be81 100644 --- a/modules/qortexRtdProvider.js +++ b/modules/qortexRtdProvider.js @@ -5,6 +5,8 @@ import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +const QX_VERSION = { v: '1.0' } + const qortexSessionInfo = {}; const QX_IN_MESSAGE = { BID_ENRICH_INITIALIZED: 'CX-BID-ENRICH-INITIALIZED', @@ -79,7 +81,7 @@ export function addContextToRequests (reqBidsConfig) { requestContextData(); } else { if (checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidBidEnrichmentPercentage)) { - const fragment = { site: {content: qortexSessionInfo.currentSiteContext} } + const fragment = qortexSessionInfo.currentSiteContext if (qortexSessionInfo.bidderArray?.length > 0) { qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment})); } else if (!qortexSessionInfo.bidderArray) { @@ -220,7 +222,7 @@ function shouldAllowBidEnrichment() { * @param {string} msg message string to be passed to CX-BID-ENRICH target on current page * @param {Object} data optional parameter object with additional data to send with post */ -function postBidEnrichmentMessage(msg, data = null) { +function postBidEnrichmentMessage(msg, data) { window.postMessage({ target: 'CX-BID-ENRICH', message: msg, @@ -241,7 +243,7 @@ export function windowPostMessageReceived(evt) { if (Boolean(data.params) && Boolean(data.params?.groupConfig)) { setGroupConfigData(data.params.groupConfig); } - postBidEnrichmentMessage(QX_OUT_MESSAGE.RTD_INITIALIZED); + postBidEnrichmentMessage(QX_OUT_MESSAGE.RTD_INITIALIZED, QX_VERSION); if (qortexSessionInfo?.auctionsEnded?.length > 0) { qortexSessionInfo.auctionsEnded.forEach(data => postBidEnrichmentMessage(QX_OUT_MESSAGE.AUCTION_END, data)); } diff --git a/modules/qortexRtdProvider.md b/modules/qortexRtdProvider.md index b9a08eb817a..027388c6f6d 100644 --- a/modules/qortexRtdProvider.md +++ b/modules/qortexRtdProvider.md @@ -12,7 +12,7 @@ Maintainer: mannese@qortex.ai The Qortex RTD module appends contextual segments to the bidding object based on the content of a page using the Qortex API. -If the `Qortex Group Id` and module parameters provided during configuration is active, the Qortex context API will attempt to generate and return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26) using indexed data from provided page content. The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data. +If the `Qortex Group Id` and module parameters provided during configuration is active, the Qortex context API will attempt to generate and return an object containing [Content data](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26) using indexed data from provided page content. The module will then merge that object into the appropriate bidders' `ortb2` object, which can be used by prebid adapters that use RTB contextual data for bid requests. ## Build diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js index b5006e45d56..b6625e297d5 100644 --- a/test/spec/modules/qortexRtdProvider_spec.js +++ b/test/spec/modules/qortexRtdProvider_spec.js @@ -88,13 +88,15 @@ describe('qortexRtdProvider', () => { 'access-control-allow-origin': '*' }; const contextResponseObj = { - content: { - id: '123456', - episode: 15, - title: 'test episode', - series: 'test show', - season: '1', - url: 'https://example.com/file.mp4' + site: { + content: { + id: '123456', + episode: 15, + title: 'test episode', + series: 'test show', + season: '1', + url: 'https://example.com/file.mp4' + } } } const contextResponse = JSON.stringify(contextResponseObj); @@ -340,34 +342,34 @@ describe('qortexRtdProvider', () => { expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); }) - it('adds site.content only to global ortb2 when bidders array is omitted', () => { + it('adds context only to global ortb2 when bidders array is omitted', () => { const omittedBidderArrayConfig = cloneDeep(validModuleConfig); delete omittedBidderArrayConfig.params.bidders; initializeModuleData(omittedBidderArrayConfig); - setContextData(contextResponseObj.content); + setContextData(contextResponseObj); addContextToRequests(reqBidsConfig); expect(reqBidsConfig.ortb2Fragments.global).to.have.property('site'); expect(reqBidsConfig.ortb2Fragments.global.site).to.have.property('content'); - expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(contextResponseObj.content); + expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(contextResponseObj.site.content); expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); }) - it('adds site.content only to bidder ortb2 when bidders array is included', () => { + it('adds only to bidder ortb2 when bidders array is included', () => { initializeModuleData(validModuleConfig); - setContextData(contextResponseObj.content); + setContextData(contextResponseObj); addContextToRequests(reqBidsConfig); const qortexOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['qortex'] expect(qortexOrtb2Fragment).to.not.be.null; expect(qortexOrtb2Fragment).to.have.property('site'); expect(qortexOrtb2Fragment.site).to.have.property('content'); - expect(qortexOrtb2Fragment.site.content).to.be.eql(contextResponseObj.content); + expect(qortexOrtb2Fragment.site.content).to.be.eql(contextResponseObj.site.content); const testOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['test'] expect(testOrtb2Fragment).to.not.be.null; expect(testOrtb2Fragment).to.have.property('site'); expect(testOrtb2Fragment.site).to.have.property('content'); - expect(testOrtb2Fragment.site.content).to.be.eql(contextResponseObj.content); + expect(testOrtb2Fragment.site.content).to.be.eql(contextResponseObj.site.content); expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({}); }) @@ -376,7 +378,7 @@ describe('qortexRtdProvider', () => { const invalidBidderArrayConfig = cloneDeep(validModuleConfig); invalidBidderArrayConfig.params.bidders = []; initializeModuleData(invalidBidderArrayConfig); - setContextData(contextResponseObj.content) + setContextData(contextResponseObj) addContextToRequests(reqBidsConfig); expect(logWarnSpy.calledWith('Config contains an empty bidders array, unable to determine which bids to enrich')).to.be.ok; From be1cee498826606c55d4d829ecf0a45ba9f6dc86 Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov <61063794+lyubomirshishkov@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:16:48 +0200 Subject: [PATCH 0926/1097] Improve Digital Bid Adapter: Added support for MultiBid (#12777) * 12238 - Azerion / Improve: does not properly support currency module * **Type:** Fix * **Scope:** improvedigitalBidAdapter * **Subject:** Bid floors are always converted to USD. * **Details:** * Adds `DEFAULT_CURRENCY` variable which is set to USD * Adds `convertBidFloorCurrency` function which in used to convert the bid floor when both `imp.bidfloor` and `imp.bidfloorcur` are present, and `imp.bidfloorcur` is not equal to the adapter's `DEFAULT_CURRENCY`; * **Breaks:** N/A * restored accidentally discarded change from unit test expect * * Modifies behavior to pass bid floor as is when it cannot be converted to USD; * Removes rounding of bid floor when converting its currency to USD; * remove unnecessary uses of `toUpperCase()` * * fix `convertCurrency` mock * remove redundant checks for type and NaN from `convertBidFloorCurrency` function * ## FMSC-1894 - Add support for MultiBid in Improve Digital's Prebid Bid Adapter * **Type:** Feature * **Scope:** improvedigitalBidAdapter.js, improvedigitalBidAdapter_spec.js * **Subject:** Adds multi-bid support in Improve Digital's Bid Adapter * **Breaks:** N/A * * modified `CONVERTER.imp method` to use `bidderRequest.bidLimit`, instead of getting the multibid config. * add documentation regarding multibid as per patmmccann request * removed render config * improve adapter documentation styling and update test parameters --------- Co-authored-by: Lyubomir Shishkov Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Catalin Ciocov --- modules/improvedigitalBidAdapter.js | 2 + modules/improvedigitalBidAdapter.md | 89 +++++++++---------- .../modules/improvedigitalBidAdapter_spec.js | 22 +++++ 3 files changed, 68 insertions(+), 45 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index a0347382dd1..ed6278de6eb 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -164,6 +164,8 @@ export const CONVERTER = ortbConverter({ } deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); + context.bidderRequest.bidLimit && deepSetValue(imp, 'ext.max_bids', context.bidderRequest.bidLimit); + return imp; }, request(buildRequest, imps, bidderRequest, context) { diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md index 7206dd8ba7b..4614ea550b4 100644 --- a/modules/improvedigitalBidAdapter.md +++ b/modules/improvedigitalBidAdapter.md @@ -1,53 +1,52 @@ # Overview -**Module Name**: Improve Digital Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: hb@azerion.com +```text +Module Name: Improve Digital Bidder Adapter +Module Type: Bidder Adapter +Maintainer: hb@azerion.com +``` # Description -Module that connects to Improve Digital's demand sources +This module connects publishers to Improve Digital's demand sources through Prebid.js. # Test Parameters -``` - var adUnits = [{ - code: 'div-gpt-ad-1499748733608-0', - sizes: [[600, 290]], - bids: [ - { - bidder: 'improvedigital', - params: { - publisherId: 123, - placementId:1053688 - } - } - ] - }, { - code: 'div-gpt-ad-1499748833901-0', - sizes: [[250, 250]], - bids: [{ - bidder: 'improvedigital', - params: { - publisherId: 123, - placementId:1053689, - keyValues: { - testKey: ["testValue"] - } - } - }] - }, { - code: 'div-gpt-ad-1499748913322-0', - sizes: [[300, 300]], - bids: [{ - bidder: 'improvedigital', - params: { - publisherId: 123, - placementId:1053687, - size: { - w:300, - h:300 - } - } - }] - }]; + +```javascript +const adUnits = [{ + code: 'banner-ad-unit', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'improvedigital', + params: { + publisherId: 950, + placementId: 22135702, + } + } + ] +}, { + code: 'video-ad-unit', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + plcmt: 2, + } + }, + bids: [{ + bidder: 'improvedigital', + params: { + publisherId: 950, + placementId: 22137694, + keyValues: { + testKey: ["testValue"], + }, + }, + }], +}]; ``` diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index f6266503297..dba12dee7ae 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -708,6 +708,28 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal('123456'); }); + it('should add max_bids param in imp.ext objects when bidLimit is specified in the bidderRequest', function () { + const bidderRequestDeepClone = deepClone(bidderRequest); + bidderRequestDeepClone.bidLimit = 3; + const requests = spec.buildRequests([simpleBidRequest, instreamBidRequest], bidderRequestDeepClone); + // banner + let payload = JSON.parse(requests[0].data); + expect(payload.imp[0].ext.max_bids).to.equal(3); + // video + payload = JSON.parse(requests[1].data); + expect(payload.imp[0].ext.max_bids).to.equal(3); + }); + + it('should not add max_bids param in imp.ext objects when bidLimit is not specified in the bidderRequest', function () { + const requests = spec.buildRequests([simpleBidRequest, instreamBidRequest], bidderRequest); + // banner + let payload = JSON.parse(requests[0].data); + expect(payload.imp[0].ext.max_bids).to.not.exist; + // video + payload = JSON.parse(requests[1].data); + expect(payload.imp[0].ext.max_bids).to.not.exist; + }); + it('should set extend url when extend mode enabled in adunit params', function () { const bidRequest = deepClone(extendBidRequest); let request = spec.buildRequests([bidRequest], { bids: [bidRequest] })[0]; From 53288b69d1b30b2edbc13de423650ebb4234ca40 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 19 Feb 2025 07:18:35 -0800 Subject: [PATCH 0927/1097] UserId & multiple userId modules: pass all consent, not just TCF, to ID modules (#12783) * UserID: pass all consent instead of just tcf to submodules * submodules: a-connectId * submodules: ftrack * submodules: identityLink * submodules: lotamePanorama * submodules: publink * submodules: tnc * submodules: z * fix lint --- modules/33acrossIdSystem.js | 2 +- modules/admixerIdSystem.js | 2 +- modules/adtelligentIdSystem.js | 2 +- modules/amxIdSystem.js | 2 +- modules/connectIdSystem.js | 9 ++-- modules/euidIdSystem.js | 4 +- modules/fabrickIdSystem.js | 4 +- modules/ftrackIdSystem.js | 8 ++-- modules/id5IdSystem.js | 7 ++-- modules/identityLinkIdSystem.js | 7 ++-- modules/justIdSystem.js | 2 +- modules/kinessoIdSystem.js | 10 ++--- modules/lockrAIMIdSystem.js | 5 +-- modules/lotamePanoramaIdSystem.js | 6 +-- modules/merkleIdSystem.js | 2 +- modules/mygaruIdSystem.js | 4 +- modules/publinkIdSystem.js | 9 ++-- modules/sharedIdSystem.js | 8 +--- modules/tapadIdSystem.js | 5 +-- modules/teadsIdSystem.js | 7 ++-- modules/tncIdSystem.js | 4 +- modules/uid2IdSystem.js | 2 +- modules/userId/index.js | 19 ++++----- modules/verizonMediaIdSystem.js | 2 +- test/spec/modules/33acrossIdSystem_spec.js | 10 +++-- test/spec/modules/admixerIdSystem_spec.js | 2 +- test/spec/modules/connectIdSystem_spec.js | 41 +++++++++---------- test/spec/modules/ftrackIdSystem_spec.js | 29 ++++--------- test/spec/modules/id5IdSystem_spec.js | 18 ++++---- .../spec/modules/identityLinkIdSystem_spec.js | 23 ++++------- test/spec/modules/justIdSystem_spec.js | 2 +- .../modules/lotamePanoramaIdSystem_spec.js | 16 +++++--- test/spec/modules/merkleIdSystem_spec.js | 2 +- test/spec/modules/mygaruIdSystem_spec.js | 6 ++- test/spec/modules/publinkIdSystem_spec.js | 15 ++----- test/spec/modules/sharedIdSystem_spec.js | 13 +----- test/spec/modules/teadsIdSystem_spec.js | 6 ++- test/spec/modules/tncIdSystem_spec.js | 8 ++-- test/spec/modules/uid2IdSystem_spec.js | 2 +- test/spec/modules/userId_spec.js | 3 +- .../spec/modules/verizonMediaIdSystem_spec.js | 6 +-- 41 files changed, 145 insertions(+), 189 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 277cb8b2f6d..823025826f5 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -222,7 +222,7 @@ export const thirtyThreeAcrossIdSubmodule = { * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId({ params = { }, enabledStorageTypes = [], storage: storageConfig = {} }, gdprConsentData) { + getId({ params = { }, enabledStorageTypes = [], storage: storageConfig = {} }, {gdpr: gdprConsentData} = {}) { if (typeof params.pid !== 'string') { logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); diff --git a/modules/admixerIdSystem.js b/modules/admixerIdSystem.js index cb7248c9537..04628d2356e 100644 --- a/modules/admixerIdSystem.js +++ b/modules/admixerIdSystem.js @@ -49,7 +49,7 @@ export const admixerIdSubmodule = { * @param {ConsentData} [consentData] * @returns {IdResponse|undefined} */ - getId(config, consentData) { + getId(config, {gdpr: consentData} = {}) { const {e, p, pid} = (config && config.params) || {}; if (!pid || typeof pid !== 'string') { logError('admixerId submodule requires partner id to be defined'); diff --git a/modules/adtelligentIdSystem.js b/modules/adtelligentIdSystem.js index 76713f29775..bb5e1f82129 100644 --- a/modules/adtelligentIdSystem.js +++ b/modules/adtelligentIdSystem.js @@ -72,7 +72,7 @@ export const adtelligentIdModule = { * @param {ConsentData} [consentData] * @returns {IdResponse} */ - getId(config, consentData) { + getId(config, {gdpr: consentData} = {}) { const gdpr = consentData && consentData.gdprApplies ? 1 : 0; const gdprConsent = gdpr ? consentData.consentString : ''; const url = buildUrl({ diff --git a/modules/amxIdSystem.js b/modules/amxIdSystem.js index 184eff76c34..aafb4b99182 100644 --- a/modules/amxIdSystem.js +++ b/modules/amxIdSystem.js @@ -101,7 +101,7 @@ export const amxIdSubmodule = { return undefined; } - const consent = consentData || { gdprApplies: false, consentString: '' }; + const consent = consentData?.gdpr || { gdprApplies: false, consentString: '' }; const client = ajaxBuilder(AJAX_TIMEOUT); const usp = uspDataHandler.getConsentData(); const ref = getRefererInfo(); diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 6926c69d453..58c19895b81 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -11,7 +11,6 @@ import {includes} from '../src/polyfill.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {formatQS, isNumber, isPlainObject, logError, parseUrl} from '../src/utils.js'; -import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; /** @@ -219,16 +218,16 @@ export const connectIdSubmodule = { } } - const uspString = uspDataHandler.getConsentData() || ''; + const uspString = consentData.usp || ''; const data = { v: '1', '1p': includes([1, '1', true], params['1p']) ? '1' : '0', - gdpr: connectIdSubmodule.isEUConsentRequired(consentData) ? '1' : '0', - gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.consentString : '', + gdpr: connectIdSubmodule.isEUConsentRequired(consentData?.gdpr) ? '1' : '0', + gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData?.gdpr) ? consentData.gdpr.consentString : '', us_privacy: uspString }; - const gppConsent = gppDataHandler.getConsentData(); + const gppConsent = consentData.gpp; if (gppConsent) { data.gpp = `${gppConsent.gppString ? gppConsent.gppString : ''}`; if (Array.isArray(gppConsent.applicableSections)) { diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js index 281fa04a12a..b97dc91effb 100644 --- a/modules/euidIdSystem.js +++ b/modules/euidIdSystem.js @@ -87,11 +87,11 @@ export const euidIdSubmodule = { * @returns {IdResponse} */ getId(config, consentData) { - if (consentData?.gdprApplies !== true) { + if (consentData?.gdpr?.gdprApplies !== true) { logWarn('EUID is intended for use within the EU. The module will not run when GDPR does not apply.'); return; } - if (!hasWriteToDeviceConsent(consentData)) { + if (!hasWriteToDeviceConsent(consentData?.gdpr)) { // The module cannot operate without this permission. _logWarn(`Unable to use EUID module due to insufficient consent. The EUID module requires storage permission.`) return; diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index f62bafcf637..a11bd50b4f9 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -51,7 +51,7 @@ export const fabrickIdSubmodule = { try { const configParams = (config && config.params) || {}; if (window.fabrickMod1) { - window.fabrickMod1(configParams, consentData, cacheIdObj); + window.fabrickMod1(configParams, consentData?.gdpr, cacheIdObj); } if (!configParams || !configParams.apiKey || typeof configParams.apiKey !== 'string') { logError('fabrick submodule requires an apiKey.'); @@ -96,7 +96,7 @@ export const fabrickIdSubmodule = { success: response => { if (window.fabrickMod2) { return window.fabrickMod2( - callback, response, configParams, consentData, cacheIdObj); + callback, response, configParams, consentData?.gdpr, cacheIdObj); } else { let responseObj; if (response) { diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index fa6c7d4050c..7474703974d 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -8,7 +8,6 @@ import * as utils from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {uspDataHandler} from '../src/adapterManager.js'; import {loadExternalScript} from '../src/adloader.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; @@ -192,18 +191,18 @@ export const ftrackIdSubmodule = { isThereConsent: function(consentData) { let consentValue = true; - + const {gdpr, usp} = consentData ?? {}; /* * Scenario 1: GDPR * if GDPR Applies is true|1, we do not have consent * if GDPR Applies does not exist or is false|0, we do not NOT have consent */ - if (consentData && consentData.gdprApplies && (consentData.gdprApplies === true || consentData.gdprApplies === 1)) { + if (gdpr?.gdprApplies === true || gdpr?.gdprApplies === 1) { consentInfo.gdpr.applies = 1; consentValue = false; } // If consentString exists, then we store it even though we are not using it - if (consentData && consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) { + if (typeof gdpr?.consentString !== 'undefined' && !utils.isEmpty(gdpr.consentString) && !utils.isEmptyStr(gdpr.consentString)) { consentInfo.gdpr.consentString = consentData.consentString; } @@ -213,7 +212,6 @@ export const ftrackIdSubmodule = { * parse the us_privacy string to see if we have consent * for version 1 of us_privacy strings, if 'Opt-Out Sale' is 'Y' we do not track */ - const usp = uspDataHandler.getConsentData(); let usPrivacyVersion; // let usPrivacyOptOut; let usPrivacyOptOutSale; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 6e94117614b..741c6ef3063 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -19,7 +19,6 @@ import {fetch} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; -import {gppDataHandler, uspDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {GreedyPromise} from '../src/utils/promise.js'; import {loadExternalScript} from '../src/adloader.js'; @@ -256,13 +255,13 @@ export const id5IdSubmodule = { return undefined; } - if (!hasWriteConsentToLocalStorage(consentData)) { + if (!hasWriteConsentToLocalStorage(consentData?.gdpr)) { logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.'); return undefined; } const resp = function (cbFunction) { - const fetchFlow = new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData(), gppDataHandler.getConsentData()); + const fetchFlow = new IdFetchFlow(submoduleConfig, consentData?.gdpr, cacheIdObj, consentData?.usp, consentData?.gpp); fetchFlow.execute() .then(response => { cbFunction(response); @@ -287,7 +286,7 @@ export const id5IdSubmodule = { * @return {IdResponse} A response object that contains id. */ extendId(config, consentData, cacheIdObj) { - if (!hasWriteConsentToLocalStorage(consentData)) { + if (!hasWriteConsentToLocalStorage(consentData?.gdpr)) { logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.'); return cacheIdObj; } diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index a9d88110308..45bdd4a51c7 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -10,7 +10,6 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -import { gppDataHandler } from '../src/adapterManager.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -59,14 +58,14 @@ export const identityLinkSubmodule = { utils.logError('identityLink: requires partner id to be defined'); return; } - const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const gdprConsentString = hasGdpr ? consentData.consentString : ''; + const {gdpr, gpp: gppData} = consentData ?? {}; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdpr.consentString : ''; // use protocol relative urls for http or https if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { utils.logInfo('identityLink: Consent string is required to call envelope API.'); return; } - const gppData = gppDataHandler.getConsentData(); const gppString = gppData && gppData.gppString ? gppData.gppString : false; const gppSectionId = gppData && gppData.gppString && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== -1 ? gppData.applicableSections[0] : false; const hasGpp = gppString && gppSectionId; diff --git a/modules/justIdSystem.js b/modules/justIdSystem.js index e4f1828c573..c71654a845b 100644 --- a/modules/justIdSystem.js +++ b/modules/justIdSystem.js @@ -80,7 +80,7 @@ export const justIdSubmodule = { utils.logInfo(LOG_PREFIX, 'fetching uid...'); var uidProvider = configWrapper.isCombinedMode() - ? new CombinedUidProvider(configWrapper, consentData, cacheIdObj) + ? new CombinedUidProvider(configWrapper, consentData?.gdpr, cacheIdObj) : new BasicUidProvider(configWrapper); uidProvider.getUid(justId => { diff --git a/modules/kinessoIdSystem.js b/modules/kinessoIdSystem.js index 35b8dcc182d..56cf7353b71 100644 --- a/modules/kinessoIdSystem.js +++ b/modules/kinessoIdSystem.js @@ -8,7 +8,6 @@ import { logError, logInfo } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; -import {coppaDataHandler, uspDataHandler} from '../src/adapterManager.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -183,14 +182,14 @@ function encodeId(value) { * @return {string} */ function kinessoSyncUrl(accountId, consentData) { - const usPrivacyString = uspDataHandler.getConsentData(); + const {gdpr, usp: usPrivacyString} = consentData ?? {}; let kinessoSyncUrl = `${ID_SVC}?accountid=${accountId}`; if (usPrivacyString) { kinessoSyncUrl = `${kinessoSyncUrl}&us_privacy=${usPrivacyString}`; } - if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return kinessoSyncUrl; + if (!gdpr || typeof gdpr.gdprApplies !== 'boolean' || !gdpr.gdprApplies) return kinessoSyncUrl; - kinessoSyncUrl = `${kinessoSyncUrl}&gdpr=1&gdpr_consent=${consentData.consentString}`; + kinessoSyncUrl = `${kinessoSyncUrl}&gdpr=1&gdpr_consent=${gdpr.consentString}`; return kinessoSyncUrl } @@ -226,8 +225,7 @@ export const kinessoIdSubmodule = { logError('User ID - KinessoId submodule requires a valid accountid to be defined'); return; } - const coppa = coppaDataHandler.getCoppa(); - if (coppa) { + if (consentData?.coppa) { logInfo('KinessoId: IDs not provided for coppa requests, exiting KinessoId'); return; } diff --git a/modules/lockrAIMIdSystem.js b/modules/lockrAIMIdSystem.js index de0435a2255..2c08b04d4df 100644 --- a/modules/lockrAIMIdSystem.js +++ b/modules/lockrAIMIdSystem.js @@ -10,7 +10,6 @@ import { ajax } from '../src/ajax.js'; import { logInfo, logWarn } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -import { gppDataHandler } from '../src/adapterManager.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -57,12 +56,12 @@ export const lockrAIMSubmodule = { * @returns {lockrAIMId} */ getId(config, consentData) { - if (consentData?.gdprApplies === true) { + if (consentData?.gdpr?.gdprApplies === true) { _logWarn('lockrAIM is not intended for use where GDPR applies. The lockrAIM module will not run'); return undefined; } - const gppConsent = gppDataHandler.getConsentData(); + const gppConsent = consentData?.gpp; let gppString = ''; if (gppConsent) { gppString = gppConsent.gppString; diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 3be7b261723..13cb19d9d93 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -297,10 +297,10 @@ export const lotamePanoramaIdSubmodule = { let consentString; if (consentData) { - if (isBoolean(consentData.gdprApplies)) { - queryParams.gdpr_applies = consentData.gdprApplies; + if (isBoolean(consentData.gdpr?.gdprApplies)) { + queryParams.gdpr_applies = consentData.gdpr.gdprApplies; } - consentString = consentData.consentString; + consentString = consentData.gdpr?.consentString; } if (consentString) { queryParams.gdpr_consent = consentString; diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 3f3a90c3c49..a64fb8a7e20 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -143,7 +143,7 @@ export const merkleIdSubmodule = { return; } - if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { + if (consentData?.gdpr?.gdprApplies === true) { logError('User ID - merkleId submodule does not currently handle consent strings'); return; } diff --git a/modules/mygaruIdSystem.js b/modules/mygaruIdSystem.js index 9133480477b..e05bcba1ecb 100644 --- a/modules/mygaruIdSystem.js +++ b/modules/mygaruIdSystem.js @@ -77,8 +77,8 @@ export const mygaruIdSubmodule = { * @returns {{id: string | undefined}} */ getId(config, consentData) { - const gdprApplies = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies ? 1 : 0; - const gdprConsentString = gdprApplies ? consentData.consentString : undefined; + const gdprApplies = consentData?.gdpr?.gdprApplies === true ? 1 : 0; + const gdprConsentString = gdprApplies ? consentData.gdpr.consentString : undefined; const url = buildUrl({ gdprApplies, gdprConsentString diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index e8eb90cd02a..875f6592c04 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -9,7 +9,6 @@ import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import { parseUrl, buildUrl, logError } from '../src/utils.js'; -import {uspDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; /** @@ -39,9 +38,9 @@ function publinkIdUrl(params, consentData, storedId) { mpv: '$prebid.version$', }; - if (consentData) { - url.search.gdpr = (consentData.gdprApplies) ? 1 : 0; - url.search.gdpr_consent = consentData.consentString; + if (consentData?.gdpr) { + url.search.gdpr = (consentData.gdpr.gdprApplies) ? 1 : 0; + url.search.gdpr_consent = consentData.gdpr.consentString; } if (params) { @@ -60,7 +59,7 @@ function publinkIdUrl(params, consentData, storedId) { url.search.publink = storedId; } - const usPrivacyString = uspDataHandler.getConsentData(); + const usPrivacyString = consentData?.usp; if (usPrivacyString && typeof usPrivacyString === 'string') { url.search.us_privacy = usPrivacyString; } diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index a96681f749c..d045587262f 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -7,7 +7,6 @@ import {parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID} from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import {coppaDataHandler} from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; @@ -118,9 +117,7 @@ export const sharedIdSystemSubmodule = { logInfo('PubCommonId: Has opted-out'); return; } - const coppa = coppaDataHandler.getCoppa(); - - if (coppa) { + if (consentData?.coppa) { logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); return; } @@ -164,8 +161,7 @@ export const sharedIdSystemSubmodule = { logInfo('PubCommonId: Has opted-out'); return {id: undefined}; } - const coppa = coppaDataHandler.getCoppa(); - if (coppa) { + if (consentData?.coppa) { logInfo('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); return; } diff --git a/modules/tapadIdSystem.js b/modules/tapadIdSystem.js index 2c24f062791..6860b408ffc 100644 --- a/modules/tapadIdSystem.js +++ b/modules/tapadIdSystem.js @@ -1,5 +1,4 @@ import { logMessage } from '../src/utils.js'; -import { uspDataHandler } from '../src/adapterManager.js'; import { submodule } from '../src/hook.js'; import * as ajax from '../src/ajax.js' @@ -22,8 +21,8 @@ export const tapadIdSubmodule = { * @param {ConsentData} [consentData] * @returns {IdResponse }} */ - getId(config) { - const uspData = uspDataHandler.getConsentData(); + getId(config, consentData) { + const uspData = consentData?.usp; if (uspData && uspData !== '1---') { return { id: undefined }; } diff --git a/modules/teadsIdSystem.js b/modules/teadsIdSystem.js index 8026fe77f87..16b8e97820a 100644 --- a/modules/teadsIdSystem.js +++ b/modules/teadsIdSystem.js @@ -9,7 +9,6 @@ import {isStr, isNumber, logError, logInfo, isEmpty, timestamp} from '../src/uti import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {uspDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; /** @@ -108,9 +107,9 @@ export const teadsIdSubmodule = { export function buildAnalyticsTagUrl(submoduleConfig, consentData) { const pubId = getPublisherId(submoduleConfig); const teadsViewerId = getTeadsViewerId(); - const status = getGdprStatus(consentData); - const gdprConsentString = getGdprConsentString(consentData); - const ccpaConsentString = getCcpaConsentString(uspDataHandler?.getConsentData()); + const status = getGdprStatus(consentData?.gdpr); + const gdprConsentString = getGdprConsentString(consentData?.gdpr); + const ccpaConsentString = getCcpaConsentString(consentData?.usp); const gdprReason = getGdprReasonFromStatus(status); const params = { analytics_tag_id: pubId, diff --git a/modules/tncIdSystem.js b/modules/tncIdSystem.js index 3e1d1e9b926..198cedd26bd 100644 --- a/modules/tncIdSystem.js +++ b/modules/tncIdSystem.js @@ -128,8 +128,8 @@ export const tncidSubModule = { * @returns {IdResponse} */ getId(config, consentData) { - const gdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const consentString = gdpr ? consentData.consentString : ''; + const gdpr = (consentData?.gdpr?.gdprApplies === true) ? 1 : 0; + const consentString = gdpr ? consentData.gdpr.consentString : ''; if (gdpr && !consentString) { logInfo('Consent string is required for TNCID module'); diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index afdde5f0a7f..4b5c4da7396 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -73,7 +73,7 @@ export const uid2IdSubmodule = { * @returns {uid2Id} */ getId(config, consentData) { - if (consentData?.gdprApplies === true) { + if (consentData?.gdpr?.gdprApplies === true) { _logWarn('UID2 is not intended for use where GDPR applies. The UID2 module will not run.'); return; } diff --git a/modules/userId/index.js b/modules/userId/index.js index 69988e00f3b..3ee078f8b1c 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -101,9 +101,10 @@ /** * @typedef {Object} ConsentData - * @property {(string|undefined)} consentString - * @property {(Object|undefined)} vendorData - * @property {(boolean|undefined)} gdprApplies + * @property {Object} gdpr + * @property {Object} gpp + * @property {Object} usp + * @property {Object} coppa */ /** @@ -120,7 +121,7 @@ import {find} from '../../src/polyfill.js'; import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; -import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; +import adapterManager from '../../src/adapterManager.js'; import {EVENTS} from '../../src/constants.js'; import {module, ready as hooksReady} from '../../src/hook.js'; import {EID_CONFIG, getEids} from './eids.js'; @@ -866,9 +867,7 @@ function consentChanged(submodule) { } function populateSubmoduleId(submodule, forceRefresh) { - // TODO: the ID submodule API only takes GDPR consent; it should be updated now that GDPR - // is only a tiny fraction of a vast consent universe - const gdprConsent = gdprDataHandler.getConsentData(); + const consentData = allConsent.getConsentData(); // There are two submodule configuration types to handle: storage or value // 1. storage: retrieve user id data from cookie/html storage or with the submodule's getId method @@ -887,10 +886,10 @@ function populateSubmoduleId(submodule, forceRefresh) { const extendedConfig = Object.assign({ enabledStorageTypes: submodule.enabledStorageTypes }, submodule.config); // No id previously saved, or a refresh is needed, or consent has changed. Request a new id from the submodule. - response = submodule.submodule.getId(extendedConfig, gdprConsent, storedId); + response = submodule.submodule.getId(extendedConfig, consentData, storedId); } else if (typeof submodule.submodule.extendId === 'function') { // If the id exists already, give submodule a chance to decide additional actions that need to be taken - response = submodule.submodule.extendId(submodule.config, gdprConsent, storedId); + response = submodule.submodule.extendId(submodule.config, consentData, storedId); } if (isPlainObject(response)) { @@ -914,7 +913,7 @@ function populateSubmoduleId(submodule, forceRefresh) { // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.config.value; } else { - const response = submodule.submodule.getId(submodule.config, gdprConsent, undefined); + const response = submodule.submodule.getId(submodule.config, consentData); if (isPlainObject(response)) { if (typeof response.callback === 'function') { submodule.callback = response.callback; } if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 26fa89cfe03..f2ab448aec8 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -67,7 +67,7 @@ export const verizonMediaIdSubmodule = { he: params.he, gdpr: isEUConsentRequired(consentData) ? '1' : '0', gdpr_consent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', - us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' + us_privacy: consentData && consentData.usp ? consentData.usp : '' }; if (params.pixelId) { diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 99fe2d4921d..cd70dfd9e25 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -642,7 +642,7 @@ describe('33acrossIdSystem', () => { pid: '12345' } }, { - gdprApplies: true + gdpr: {gdprApplies: true} }); expect(logWarnSpy.calledOnceWithExactly('33acrossId: Submodule cannot be used where GDPR applies')).to.be.true; @@ -660,7 +660,7 @@ describe('33acrossIdSystem', () => { pid: '12345' } }, { - gdprApplies: false + gdpr: {gdprApplies: false} }); callback(completeCallback); @@ -678,8 +678,10 @@ describe('33acrossIdSystem', () => { pid: '12345' } }, { - gdprApplies: false, - consentString: 'foo' + gdpr: { + gdprApplies: false, + consentString: 'foo' + } }); callback(completeCallback); diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js index 753b1e3c2d5..56c356a153e 100644 --- a/test/spec/modules/admixerIdSystem_spec.js +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -30,7 +30,7 @@ describe('admixerId tests', function () { }); it('should NOT call the admixer id endpoint if gdpr applies but consent string is missing', function () { - let submoduleCallback = admixerIdSubmodule.getId(getIdParams, { gdprApplies: true }); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams, {gdpr: { gdprApplies: true }}); expect(submoduleCallback).to.be.undefined; }); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 3009ecef769..8b846a5fc72 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -2,7 +2,6 @@ import {expect} from 'chai'; import {connectIdSubmodule, storage} from 'modules/connectIdSystem.js'; import {server} from '../../mocks/xhr'; import {parseQS, parseUrl} from 'src/utils.js'; -import {uspDataHandler, gppDataHandler} from 'src/adapterManager.js'; import * as refererDetection from '../../../src/refererDetection'; const TEST_SERVER_URL = 'http://localhost:9876/'; @@ -38,8 +37,6 @@ describe('Yahoo ConnectID Submodule', () => { let cookiesEnabledStub; let localStorageEnabledStub; let removeLocalStorageDataStub; - let uspConsentDataStub; - let gppConsentDataStub; let consentData; beforeEach(() => { @@ -53,17 +50,19 @@ describe('Yahoo ConnectID Submodule', () => { setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); localStorageEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); removeLocalStorageDataStub = sinon.stub(storage, 'removeDataFromLocalStorage'); - uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); - gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); cookiesEnabledStub.returns(true); localStorageEnabledStub.returns(true); - uspConsentDataStub.returns(USP_DATA); - gppConsentDataStub.returns(GPP_DATA); consentData = { - gdprApplies: 1, - consentString: 'GDPR_CONSENT_STRING' + gdpr: { + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' + }, + gpp: { + ...GPP_DATA + }, + usp: USP_DATA }; }); @@ -76,8 +75,6 @@ describe('Yahoo ConnectID Submodule', () => { cookiesEnabledStub.restore(); localStorageEnabledStub.restore(); removeLocalStorageDataStub.restore(); - uspConsentDataStub.restore(); - gppConsentDataStub.restore(); }); function invokeGetIdAPI(configParams, consentData) { @@ -383,7 +380,7 @@ describe('Yahoo ConnectID Submodule', () => { pixelId: PIXEL_ID, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA, @@ -418,7 +415,7 @@ describe('Yahoo ConnectID Submodule', () => { pixelId: PIXEL_ID, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA, @@ -453,7 +450,7 @@ describe('Yahoo ConnectID Submodule', () => { he: HASHED_EMAIL, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA, @@ -584,7 +581,7 @@ describe('Yahoo ConnectID Submodule', () => { pixelId: PIXEL_ID, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA, @@ -610,7 +607,7 @@ describe('Yahoo ConnectID Submodule', () => { gdpr: '1', puid: PUBLISHER_USER_ID, pixelId: PIXEL_ID, - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, url: TEST_SERVER_URL, us_privacy: USP_DATA, gpp: GPP_DATA.gppString, @@ -636,7 +633,7 @@ describe('Yahoo ConnectID Submodule', () => { pixelId: PIXEL_ID, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA, @@ -660,7 +657,7 @@ describe('Yahoo ConnectID Submodule', () => { he: HASHED_EMAIL, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA, @@ -675,7 +672,7 @@ describe('Yahoo ConnectID Submodule', () => { }); it('Makes an ajax GET request to the specified override API endpoint without GPP', () => { - gppConsentDataStub.returns(undefined); + consentData.gpp = undefined; invokeGetIdAPI({ he: HASHED_EMAIL, endpoint: OVERRIDE_ENDPOINT @@ -685,7 +682,7 @@ describe('Yahoo ConnectID Submodule', () => { he: HASHED_EMAIL, '1p': '0', gdpr: '1', - gdpr_consent: consentData.consentString, + gdpr_consent: consentData.gdpr.consentString, v: '1', url: TEST_SERVER_URL, us_privacy: USP_DATA @@ -714,11 +711,11 @@ describe('Yahoo ConnectID Submodule', () => { const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.gdpr_consent).to.equal(consentData.consentString); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); }); it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { - consentData.gdprApplies = false; + consentData.gdpr.gdprApplies = false; invokeGetIdAPI({ he: HASHED_EMAIL, diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index 12e18ed5354..ea254eb0c6f 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -105,41 +105,28 @@ describe('FTRACK ID System', () => { }); describe(`ftrackIdSubmodule.isThereConsent():`, () => { - let uspDataHandlerStub; - beforeEach(() => { - uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); - }); - - afterEach(() => { - uspDataHandlerStub.restore(); - }); - describe(`returns 'false' if:`, () => { it(`GDPR: if gdprApplies is truthy`, () => { - expect(ftrackIdSubmodule.isThereConsent({gdprApplies: 1})).to.not.be.ok; - expect(ftrackIdSubmodule.isThereConsent({gdprApplies: true})).to.not.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdpr: {gdprApplies: 1}})).to.not.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdpr: {gdprApplies: true}})).to.not.be.ok; }); it(`US_PRIVACY version 1: if 'Opt Out Sale' is 'Y'`, () => { - uspDataHandlerStub.returns('1YYY'); - expect(ftrackIdSubmodule.isThereConsent({})).to.not.be.ok; + expect(ftrackIdSubmodule.isThereConsent({usp: '1YYY'})).to.not.be.ok; }); }); describe(`returns 'true' if`, () => { it(`GDPR: if gdprApplies is undefined, false or 0`, () => { - expect(ftrackIdSubmodule.isThereConsent({gdprApplies: 0})).to.be.ok; - expect(ftrackIdSubmodule.isThereConsent({gdprApplies: false})).to.be.ok; - expect(ftrackIdSubmodule.isThereConsent({gdprApplies: null})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdpr: {gdprApplies: 0}})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdpr: {gdprApplies: false}})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({gdpr: {gdprApplies: null}})).to.be.ok; expect(ftrackIdSubmodule.isThereConsent({})).to.be.ok; }); it(`US_PRIVACY version 1: if 'Opt Out Sale' is not 'Y' ('N','-')`, () => { - uspDataHandlerStub.returns('1NNN'); - expect(ftrackIdSubmodule.isThereConsent(null)).to.be.ok; - - uspDataHandlerStub.returns('1---'); - expect(ftrackIdSubmodule.isThereConsent(null)).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({usp: '1NNN'})).to.be.ok; + expect(ftrackIdSubmodule.isThereConsent({usp: '1---'})).to.be.ok; }); }); }); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 49ae813fb10..f0b73da919e 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -11,7 +11,6 @@ import {config} from '../../../src/config.js'; import * as events from '../../../src/events.js'; import {EVENTS} from '../../../src/constants.js'; import * as utils from '../../../src/utils.js'; -import {gppDataHandler, uspDataHandler} from '../../../src/adapterManager.js'; import '../../../src/prebid.js'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; @@ -333,11 +332,11 @@ describe('ID5 ID System', function () { } }; expect(id5System.id5IdSubmodule.getId(config)).is.eq(undefined); - expect(id5System.id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); + expect(id5System.id5IdSubmodule.getId(config, {gdpr: dataConsent})).is.eq(undefined); const cacheIdObject = 'cacheIdObject'; expect(id5System.id5IdSubmodule.extendId(config)).is.eq(undefined); - expect(id5System.id5IdSubmodule.extendId(config, dataConsent, cacheIdObject)).is.eq(cacheIdObject); + expect(id5System.id5IdSubmodule.extendId(config, {gdpr: dataConsent}, cacheIdObject)).is.eq(cacheIdObject); }); }); }); @@ -350,7 +349,6 @@ describe('ID5 ID System', function () { }); afterEach(function () { - uspDataHandler.reset(); gppStub?.restore(); }); @@ -393,7 +391,7 @@ describe('ID5 ID System', function () { }; // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), {gdpr: consentData}, undefined); const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); @@ -430,7 +428,6 @@ describe('ID5 ID System', function () { it('should call the ID5 server with us privacy consent', async function () { const usPrivacyString = '1YN-'; - uspDataHandler.setConsentData(usPrivacyString); const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: true, @@ -439,7 +436,7 @@ describe('ID5 ID System', function () { }; // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), {gdpr: consentData, usp: usPrivacyString}, undefined); const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); @@ -828,13 +825,12 @@ describe('ID5 ID System', function () { it('should pass gpp_string and gpp_sid to ID5 server', function () { let xhrServerMock = new XhrServerMock(server); - gppStub = sinon.stub(gppDataHandler, 'getConsentData'); - gppStub.returns({ + const gppData = { ready: true, gppString: 'GPP_STRING', applicableSections: [2] - }); - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + }; + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), {gpp: gppData}, ID5_STORED_OBJ); return xhrServerMock.expectFetchRequest() .then(fetchRequest => { diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 5efe9794f92..95480e57bd1 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -3,7 +3,6 @@ import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; import {getCoreStorageManager} from '../../../src/storageManager.js'; import {stub} from 'sinon'; -import { gppDataHandler } from '../../../src/adapterManager.js'; import {attachIdSystem} from '../../../modules/userId/index.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; import {expect} from 'chai/index.mjs'; @@ -68,13 +67,13 @@ describe('IdentityLinkId tests', function () { gdprApplies: true, consentString: '' }; - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, {gdpr: consentData}); expect(submoduleCallback).to.be.undefined; }); it('should NOT call the LiveRamp envelope endpoint if gdpr applies but consent string is missing', function () { let consentData = { gdprApplies: true }; - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, {gdpr: consentData}); expect(submoduleCallback).to.be.undefined; }); @@ -87,7 +86,7 @@ describe('IdentityLinkId tests', function () { tcfPolicyVersion: 2 } }; - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData).callback; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, {gdpr: consentData}).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&ct=4&cv=CO4VThZO4VTiuADABBENAzCgAP_AAEOAAAAAAwwAgAEABhAAgAgAAA.YAAAAAAAAAA'); @@ -100,14 +99,13 @@ describe('IdentityLinkId tests', function () { }); it('should call the LiveRamp envelope endpoint with GPP consent string', function() { - gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); - gppConsentDataStub.returns({ + const gppData = { ready: true, gppString: 'DBABLA~BVVqAAAACqA.QA', applicableSections: [7] - }); + }; let callBackSpy = sinon.spy(); - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, {gpp: gppData}).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&gpp=DBABLA~BVVqAAAACqA.QA&gpp_sid=7'); @@ -117,18 +115,16 @@ describe('IdentityLinkId tests', function () { JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; - gppConsentDataStub.restore(); }); it('should call the LiveRamp envelope endpoint without GPP consent string if consent string is not provided', function () { - gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); - gppConsentDataStub.returns({ + const gppData = { ready: true, gppString: '', applicableSections: [7] - }); + }; let callBackSpy = sinon.spy(); - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, {gpp: gppData}).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); @@ -138,7 +134,6 @@ describe('IdentityLinkId tests', function () { JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; - gppConsentDataStub.restore(); }); it('should not throw Uncaught TypeError when envelope endpoint returns empty response', function () { diff --git a/test/spec/modules/justIdSystem_spec.js b/test/spec/modules/justIdSystem_spec.js index 95d964d807c..abdf2d39644 100644 --- a/test/spec/modules/justIdSystem_spec.js +++ b/test/spec/modules/justIdSystem_spec.js @@ -190,7 +190,7 @@ describe('JustIdSystem', function () { const b = { y: 'y' } const c = { z: 'z' } - justIdSubmodule.getId(a, b, c).callback(callbackSpy); + justIdSubmodule.getId(a, {gdpr: b}, c).callback(callbackSpy); scriptTagCallback(); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 27efef9df50..0fa90cc6278 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -417,8 +417,10 @@ describe('LotameId', function() { beforeEach(function () { let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, { - gdprApplies: true, - consentString: 'consentGiven' + gdpr: { + gdprApplies: true, + consentString: 'consentGiven' + } }).callback; submoduleCallback(callBackSpy); @@ -451,8 +453,10 @@ describe('LotameId', function() { let request; let callBackSpy = sinon.spy(); let consentData = { - gdprApplies: true, - consentString: undefined + gdpr: { + gdprApplies: true, + consentString: undefined + } }; beforeEach(function () { @@ -773,7 +777,9 @@ describe('LotameId', function() { }, }, { - gdprApplies: false, + gdpr: { + gdprApplies: false, + } } ).callback; submoduleCallback(callBackSpy); diff --git a/test/spec/modules/merkleIdSystem_spec.js b/test/spec/modules/merkleIdSystem_spec.js index b12bb365e5b..a74bbefdca7 100644 --- a/test/spec/modules/merkleIdSystem_spec.js +++ b/test/spec/modules/merkleIdSystem_spec.js @@ -159,7 +159,7 @@ describe('Merkle System', function () { storage: STORAGE_PARAMS }; - let submoduleCallback = merkleIdSubmodule.getId(config, { gdprApplies: true }); + let submoduleCallback = merkleIdSubmodule.getId(config, {gdpr: {gdprApplies: true}}); expect(submoduleCallback).to.be.undefined; expect(utils.logError.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule does not currently handle consent strings'); }); diff --git a/test/spec/modules/mygaruIdSystem_spec.js b/test/spec/modules/mygaruIdSystem_spec.js index 2bfb5fdd4af..1d8035277d8 100644 --- a/test/spec/modules/mygaruIdSystem_spec.js +++ b/test/spec/modules/mygaruIdSystem_spec.js @@ -53,8 +53,10 @@ describe('MygaruID module', function () { }) it('should buildUrl with consent data', () => { const result = mygaruIdSubmodule.getId({}, { - gdprApplies: true, - consentString: 'consentString' + gdpr: { + gdprApplies: true, + consentString: 'consentString' + } }); expect(result.url).to.eq('https://ident.mygaru.com/v2/id?gdprApplies=1&gdprConsentString=consentString'); diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index 5ad58ea1a37..65f4f312676 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -2,7 +2,6 @@ import {publinkIdSubmodule} from 'modules/publinkIdSystem.js'; import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager'; import {server} from 'test/mocks/xhr.js'; import sinon from 'sinon'; -import {uspDataHandler} from '../../../src/adapterManager'; import {parseUrl} from '../../../src/utils'; const storage = getCoreStorageManager(); @@ -117,9 +116,9 @@ describe('PublinkIdSystem', () => { expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink); }); - it('Fetch with consent data', () => { + it('Fetch with GDPR consent data', () => { const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7', site_id: '102030'}}; - const consentData = {gdprApplies: 1, consentString: 'myconsentstring'}; + const consentData = {gdpr: {gdprApplies: 1, consentString: 'myconsentstring'}}; let submoduleCallback = publinkIdSubmodule.getId(config, consentData).callback; submoduleCallback(callbackSpy); @@ -170,18 +169,10 @@ describe('PublinkIdSystem', () => { describe('usPrivacy', () => { let callbackSpy = sinon.spy(); - const oldPrivacy = uspDataHandler.getConsentData(); - before(() => { - uspDataHandler.setConsentData('1YNN'); - }); - after(() => { - uspDataHandler.setConsentData(oldPrivacy); - callbackSpy.resetHistory(); - }); it('Fetch with usprivacy data', () => { const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7', api_key: 'abcdefg'}}; - let submoduleCallback = publinkIdSubmodule.getId(config).callback; + let submoduleCallback = publinkIdSubmodule.getId(config, {usp: '1YNN'}).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 71a531aa6c5..7fc3068513f 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -1,5 +1,4 @@ import {sharedIdSystemSubmodule, storage} from 'modules/sharedIdSystem.js'; -import {coppaDataHandler} from 'src/adapterManager'; import {config} from 'src/config.js'; import sinon from 'sinon'; @@ -25,14 +24,11 @@ describe('SharedId System', function () { describe('SharedId System getId()', function () { const callbackSpy = sinon.spy(); - let coppaDataHandlerDataStub let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); - coppaDataHandlerDataStub = sandbox.stub(coppaDataHandler, 'getCoppa'); sandbox.stub(utils, 'hasDeviceAccess').returns(true); - coppaDataHandlerDataStub.returns(''); callbackSpy.resetHistory(); }); @@ -55,22 +51,18 @@ describe('SharedId System', function () { expect(callbackSpy.lastCall.lastArg).to.equal(UUID); }); it('should abort if coppa is set', function () { - coppaDataHandlerDataStub.returns('true'); - const result = sharedIdSystemSubmodule.getId({}); + const result = sharedIdSystemSubmodule.getId({}, {coppa: true}); expect(result).to.be.undefined; }); }); describe('SharedId System extendId()', function () { const callbackSpy = sinon.spy(); - let coppaDataHandlerDataStub; let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); - coppaDataHandlerDataStub = sandbox.stub(coppaDataHandler, 'getCoppa'); sandbox.stub(utils, 'hasDeviceAccess').returns(true); callbackSpy.resetHistory(); - coppaDataHandlerDataStub.returns(''); }); afterEach(function () { sandbox.restore(); @@ -90,8 +82,7 @@ describe('SharedId System', function () { expect(pubcommId).to.equal('TestId'); }); it('should abort if coppa is set', function () { - coppaDataHandlerDataStub.returns('true'); - const result = sharedIdSystemSubmodule.extendId({params: {extend: true}}, undefined, 'TestId'); + const result = sharedIdSystemSubmodule.extendId({params: {extend: true}}, {coppa: true}, 'TestId'); expect(result).to.be.undefined; }); }); diff --git a/test/spec/modules/teadsIdSystem_spec.js b/test/spec/modules/teadsIdSystem_spec.js index 1959b990957..8b7847e15aa 100644 --- a/test/spec/modules/teadsIdSystem_spec.js +++ b/test/spec/modules/teadsIdSystem_spec.js @@ -25,8 +25,10 @@ describe('TeadsIdSystem', function () { }; const consentData = { - gdprApplies: true, - consentString: 'abc123==' + gdpr: { + gdprApplies: true, + consentString: 'abc123==' + } } const result = buildAnalyticsTagUrl(submoduleConfig, consentData); diff --git a/test/spec/modules/tncIdSystem_spec.js b/test/spec/modules/tncIdSystem_spec.js index 9fbffdddd68..c681970c27d 100644 --- a/test/spec/modules/tncIdSystem_spec.js +++ b/test/spec/modules/tncIdSystem_spec.js @@ -3,8 +3,10 @@ import { attachIdSystem } from '../../../modules/userId/index.js'; import { createEidsArray } from '../../../modules/userId/eids.js'; const consentData = { - gdprApplies: true, - consentString: 'GDPR_CONSENT_STRING' + gdpr: { + gdprApplies: true, + consentString: 'GDPR_CONSENT_STRING' + } }; describe('TNCID tests', function () { @@ -35,7 +37,7 @@ describe('TNCID tests', function () { }); it('Should NOT give TNCID if GDPR applies but consent string is missing', function () { - const res = tncidSubModule.getId({}, { gdprApplies: true }); + const res = tncidSubModule.getId({}, { gdpr: {gdprApplies: true} }); expect(res).to.be.undefined; }); diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 434bca17416..7fe27dc4eb5 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -223,7 +223,7 @@ describe(`UID2 module`, function () { coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry()); const consentConfig = setGdprApplies(); let configObj = makePrebidConfig(legacyConfigParams); - const result = uid2IdSubmodule.getId(configObj.userSync.userIds[0], consentConfig.consentData); + const result = uid2IdSubmodule.getId(configObj.userSync.userIds[0], {gdpr: consentConfig.consentData}); expect(result?.id).to.not.exist; }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 066f00feb8f..be192642288 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1681,6 +1681,7 @@ describe('User ID', function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); sandbox.restore(); + coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); }); it('delays auction if auctionDelay is set, timing out at auction delay', function () { @@ -2150,7 +2151,7 @@ describe('User ID', function () { 'mid': value['MOCKID'] }; }, - getId: function (config, storedId) { + getId: function (config, consentData, storedId) { if (storedId) return {}; return {id: {'MOCKID': '1234'}}; } diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index 10054b5674c..623097b48ce 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -30,7 +30,7 @@ describe('Verizon Media ID Submodule', () => { gdprApplies: 1, consentString: 'GDPR_CONSENT_STRING' }, - uspConsent: 'USP_CONSENT_STRING' + usp: 'USP_CONSENT_STRING' }; }); @@ -88,7 +88,7 @@ describe('Verizon Media ID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.usp }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -108,7 +108,7 @@ describe('Verizon Media ID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.usp }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); From c64cbbd21a63c66b99760f5872271ff0663c3775 Mon Sep 17 00:00:00 2001 From: akshat-vijaywargiya <154324666+akshat-vijaywargiya@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:02:16 +0530 Subject: [PATCH 0928/1097] Targeting.js: Fixed Slot Targeting Bug when RTD is Enabled (#12780) * fixed passing all adunit adserverTargetings when setTargetingForGPTAsync() is called. * test for #12780 * use property access rather than deepAccess --------- Co-authored-by: Demetrio Girardi --- src/targeting.js | 8 ++++---- test/spec/unit/core/targeting_spec.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/targeting.js b/src/targeting.js index 3e275ba4e16..22c161378e0 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -341,7 +341,7 @@ export function newTargeting(auctionManager) { const targeting = getWinningBidTargeting(bidsSorted, adUnitCodes) .concat(getCustomBidTargeting(bidsSorted, customKeysByUnit)) .concat(getBidderTargeting(bidsSorted)) - .concat(getAdUnitTargeting()); + .concat(getAdUnitTargeting(adUnitCodes)); targeting.forEach(adUnitCode => { updatePBTargetingKeys(adUnitCode); @@ -697,9 +697,9 @@ export function newTargeting(auctionManager) { }, []); } - function getAdUnitTargeting() { + function getAdUnitTargeting(adUnitCodes) { function getTargetingObj(adUnit) { - return deepAccess(adUnit, JSON_MAPPING.ADSERVER_TARGETING); + return adUnit?.[JSON_MAPPING.ADSERVER_TARGETING]; } function getTargetingValues(adUnit) { @@ -714,7 +714,7 @@ export function newTargeting(auctionManager) { } return auctionManager.getAdUnits() - .filter(adUnit => getTargetingObj(adUnit)) + .filter(adUnit => adUnitCodes.includes(adUnit.code) && getTargetingObj(adUnit)) .reduce((result, adUnit) => { const targetingValues = getTargetingValues(adUnit); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 01f9f93477e..f9ab54db2fc 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -452,6 +452,20 @@ describe('targeting tests', function () { expect(logErrorStub.calledOnce).to.be.true; }); + it('does not include adunit targeting for ad units that are not requested', () => { + sandbox.stub(auctionManager, 'getAdUnits').callsFake(() => ([ + { + code: 'au1', + [JSON_MAPPING.ADSERVER_TARGETING]: {'aut': 'v1'} + }, + { + code: 'au2', + [JSON_MAPPING.ADSERVER_TARGETING]: {'aut': 'v2'} + } + ])); + expect(targetingInstance.getAllTargeting('au1').au2).to.not.exist; + }) + describe('when bidLimit is present in setConfig', function () { let bid4; From 01a73c1e7c96c500654efe6965c0cdc05060ad29 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 19 Feb 2025 08:02:25 -0800 Subject: [PATCH 0929/1097] Core & PBS Adapter: support `eventtrackers`, and normalize `burl` / `ext.prebid.events.win` into it (#12711) * Extract native event tracker parsing logic * ortbConverter: set response eventtrackers and translate PBS burl, events.win * fire impression trackers on billing, win trackers on render * clean up pbs wurl logic * more cleanup * rename analytics to events in markWinningBidAsUsed * lint fixes * try to appease jsdoc * add PBS test case --------- Co-authored-by: mkomorski --- libraries/ortbConverter/processors/default.js | 3 + .../pbsExtensions/processors/eventTrackers.js | 18 ++++++ libraries/pbsExtensions/processors/pbs.js | 12 ++-- modules/prebidServerBidAdapter/index.js | 64 ------------------- src/adRendering.js | 5 +- src/adapterManager.js | 6 +- src/eventTrackers.js | 22 +++++++ src/native.js | 35 +++------- src/prebid.js | 6 +- .../modules/prebidServerBidAdapter_spec.js | 57 ++++++++--------- .../pbsExtensions/trackers_spec.js | 51 +++++++++++++++ test/spec/unit/adRendering_spec.js | 31 +++++++++ test/spec/unit/core/adapterManager_spec.js | 38 +++++++---- test/spec/unit/core/eventTrackers_spec.js | 61 ++++++++++++++++++ test/spec/unit/pbjs_api_spec.js | 16 +++-- 15 files changed, 268 insertions(+), 157 deletions(-) create mode 100644 libraries/pbsExtensions/processors/eventTrackers.js create mode 100644 src/eventTrackers.js create mode 100644 test/spec/ortbConverter/pbsExtensions/trackers_spec.js create mode 100644 test/spec/unit/core/eventTrackers_spec.js diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index f9c7e3baefc..d0c48ad4235 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -113,6 +113,9 @@ export const DEFAULT_PROCESSORS = { if (bid.attr) { bidResponse.meta.attr = bid.attr; } + if (bid.ext?.eventtrackers) { + bidResponse.eventtrackers = (bidResponse.eventtrackers ?? []).concat(bid.ext.eventtrackers); + } } } } diff --git a/libraries/pbsExtensions/processors/eventTrackers.js b/libraries/pbsExtensions/processors/eventTrackers.js new file mode 100644 index 00000000000..287084a3e21 --- /dev/null +++ b/libraries/pbsExtensions/processors/eventTrackers.js @@ -0,0 +1,18 @@ +import {EVENT_TYPE_IMPRESSION, EVENT_TYPE_WIN, TRACKER_METHOD_IMG} from '../../../src/eventTrackers.js'; + +export function addEventTrackers(bidResponse, bid) { + bidResponse.eventtrackers = bidResponse.eventtrackers || []; + [ + [bid.burl, EVENT_TYPE_IMPRESSION], // core used to fire burl directly, but only for bids coming from PBS + [bid?.ext?.prebid?.events?.win, EVENT_TYPE_WIN] + ].filter(([winUrl, type]) => winUrl && bidResponse.eventtrackers.find( + ({method, event, url}) => event === type && method === TRACKER_METHOD_IMG && url === winUrl + ) == null) + .forEach(([url, event]) => { + bidResponse.eventtrackers.push({ + method: TRACKER_METHOD_IMG, + event, + url + }) + }) +} diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js index 0ed2d12fad8..8e08c3f3027 100644 --- a/libraries/pbsExtensions/processors/pbs.js +++ b/libraries/pbsExtensions/processors/pbs.js @@ -6,6 +6,7 @@ import {setImpBidParams} from './params.js'; import {setImpAdUnitCode} from './adUnitCode.js'; import {setRequestExtPrebid, setRequestExtPrebidChannel} from './requestExtPrebid.js'; import {setBidResponseVideoCache} from './video.js'; +import {addEventTrackers} from './eventTrackers.js'; export const PBS_PROCESSORS = { [REQUEST]: { @@ -74,14 +75,9 @@ export const PBS_PROCESSORS = { bidResponse.meta = mergeDeep({}, deepAccess(bid, 'ext.prebid.meta'), bidResponse.meta); } }, - pbsWurl: { - // sets bidResponse.pbsWurl from ext.prebid.events.win - fn(bidResponse, bid) { - const wurl = deepAccess(bid, 'ext.prebid.events.win'); - if (isStr(wurl)) { - bidResponse.pbsWurl = wurl; - } - } + pbsWinTrackers: { + // converts "legacy" burl and ext.prebid.events.win into eventtrackers + fn: addEventTrackers }, }, [RESPONSE]: { diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 6b4d3a329e5..faf280d68b4 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -367,64 +367,6 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) { }); } -/** - * map wurl to auction id and adId for use in the BID_WON event - */ -let wurlMap = {}; - -/** - * @param {string} auctionId - * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() - * @param {string} wurl events.winurl passed from prebidServer as wurl - */ -function addWurl(auctionId, adId, wurl) { - if ([auctionId, adId].every(isStr)) { - wurlMap[`${auctionId}${adId}`] = wurl; - } -} - -/** - * @param {string} auctionId - * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() - */ -function removeWurl(auctionId, adId) { - if ([auctionId, adId].every(isStr)) { - wurlMap[`${auctionId}${adId}`] = undefined; - } -} -/** - * @param {string} auctionId - * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() - * @return {(string|undefined)} events.winurl which was passed as wurl - */ -function getWurl(auctionId, adId) { - if ([auctionId, adId].every(isStr)) { - return wurlMap[`${auctionId}${adId}`]; - } -} - -/** - * remove all cached wurls - */ -export function resetWurlMap() { - wurlMap = {}; -} - -/** - * BID_WON event to request the wurl - * @param {Bid} bid the winning bid object - */ -function bidWonHandler(bid) { - const wurl = getWurl(bid.auctionId, bid.adId); - if (isStr(wurl)) { - logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`); - triggerPixel(wurl); - - // remove from wurl cache, since the wurl url was called - removeWurl(bid.auctionId, bid.adId); - } -} - function getMatchingConsentUrl(urlProp, gdprConsent) { const hasPurpose = hasPurpose1Consent(gdprConsent); const url = hasPurpose ? urlProp.p1Consent : urlProp.noP1Consent @@ -519,9 +461,6 @@ export function PrebidServer() { } else { if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) { addBidResponse(adUnit, bid); - if (bid.pbsWurl) { - addWurl(bid.auctionId, bid.adId, bid.pbsWurl); - } } else { addBidResponse.reject(adUnit, bid, REJECTION_REASON.INVALID); } @@ -536,9 +475,6 @@ export function PrebidServer() { } }; - // Listen for bid won to call wurl - events.on(EVENTS.BID_WON, bidWonHandler); - return Object.assign(this, { callBids: baseAdapter.callBids, setBidderCode: baseAdapter.setBidderCode, diff --git a/src/adRendering.js b/src/adRendering.js index 502721dea38..18ee2f3a598 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -5,7 +5,7 @@ import { insertElement, logError, logWarn, - replaceMacros + replaceMacros, triggerPixel } from './utils.js'; import * as events from './events.js'; import {AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS, MESSAGES, PB_LOCATOR} from './constants.js'; @@ -20,6 +20,7 @@ import {GreedyPromise} from './utils/promise.js'; import adapterManager from './adapterManager.js'; import {useMetrics} from './utils/perfMetrics.js'; import {filters} from './targeting.js'; +import {EVENT_TYPE_WIN, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTrackers.js'; const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON, EXPIRED_RENDER } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; @@ -31,6 +32,8 @@ export const getBidToRender = hook('sync', function (adId, forRender = true, ove }) export const markWinningBid = hook('sync', function (bid) { + (parseEventTrackers(bid.eventtrackers)[EVENT_TYPE_WIN]?.[TRACKER_METHOD_IMG] || []) + .forEach(url => triggerPixel(url)); events.emit(BID_WON, bid); auctionManager.addWinningBid(bid); }) diff --git a/src/adapterManager.js b/src/adapterManager.js index 04bac6eb273..ef712af907f 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -49,6 +49,7 @@ import {isActivityAllowed} from './activities/rules.js'; import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from './activities/activities.js'; import {ACTIVITY_PARAM_ANL_CONFIG, ACTIVITY_PARAM_S2S_NAME, activityParamsBuilder} from './activities/params.js'; import {redactor} from './activities/redactor.js'; +import {EVENT_TYPE_IMPRESSION, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTrackers.js'; export {gdprDataHandler, gppDataHandler, uspDataHandler, coppaDataHandler} from './consentHandler.js'; @@ -689,9 +690,8 @@ adapterManager.triggerBilling = (() => { return (bid) => { if (!BILLED.has(bid)) { BILLED.add(bid); - if (bid.source === S2S.SRC && bid.burl) { - internal.triggerPixel(bid.burl); - } + (parseEventTrackers(bid.eventtrackers)[EVENT_TYPE_IMPRESSION]?.[TRACKER_METHOD_IMG] || []) + .forEach((url) => internal.triggerPixel(url)); tryCallBidderMethod(bid.bidder, 'onBidBillable', bid); } } diff --git a/src/eventTrackers.js b/src/eventTrackers.js new file mode 100644 index 00000000000..b0c06cf0f1b --- /dev/null +++ b/src/eventTrackers.js @@ -0,0 +1,22 @@ +export const TRACKER_METHOD_IMG = 1; +export const TRACKER_METHOD_JS = 2; +export const EVENT_TYPE_IMPRESSION = 1; +export const EVENT_TYPE_WIN = 500; + +/** + * Returns a map from event type (EVENT_TYPE_*) + * to a map from tracker method (TRACKER_METHOD_*) + * to an array of tracking URLs + * + * @param {{}[]} eventTrackers an array of "Event Tracker Response Object" as defined + * in the ORTB native 1.2 spec (https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf, section 5.8) + * @returns {{[type: string]: {[method: string]: string[]}}} + */ +export function parseEventTrackers(eventTrackers) { + return (eventTrackers ?? []).reduce((tally, {event, method, url}) => { + const trackersForType = tally[event] = tally[event] ?? {}; + const trackersForMethod = trackersForType[method] = trackersForType[method] ?? []; + trackersForMethod.push(url); + return tally; + }, {}) +} diff --git a/src/native.js b/src/native.js index d1ac4ea17d7..b8a31bd6297 100644 --- a/src/native.js +++ b/src/native.js @@ -16,6 +16,7 @@ import {NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, import {NATIVE} from './mediaTypes.js'; import {getRenderingData} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; +import {EVENT_TYPE_IMPRESSION, parseEventTrackers, TRACKER_METHOD_IMG, TRACKER_METHOD_JS} from './eventTrackers.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -89,20 +90,6 @@ const SUPPORTED_TYPES = { const PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE = inverse(PREBID_NATIVE_DATA_KEYS_TO_ORTB); const NATIVE_ASSET_TYPES_INVERSE = inverse(NATIVE_ASSET_TYPES); -const TRACKER_METHODS = { - img: 1, - js: 2, - 1: 'img', - 2: 'js' -} - -const TRACKER_EVENTS = { - impression: 1, - 'viewable-mrc50': 2, - 'viewable-mrc100': 3, - 'viewable-video50': 4, -} - export function isNativeResponse(bidResponse) { // check for native data and not mediaType; it's possible // to treat banner responses as native @@ -289,15 +276,9 @@ export function fireNativeTrackers(message, bidResponse) { } export function fireImpressionTrackers(nativeResponse, {runMarkup = (mkup) => insertHtmlIntoIframe(mkup), fetchURL = triggerPixel} = {}) { - const impTrackers = (nativeResponse.eventtrackers || []) - .filter(tracker => tracker.event === TRACKER_EVENTS.impression); - - let {img, js} = impTrackers.reduce((tally, tracker) => { - if (TRACKER_METHODS.hasOwnProperty(tracker.method)) { - tally[TRACKER_METHODS[tracker.method]].push(tracker.url) - } - return tally; - }, {img: [], js: []}); + let {[TRACKER_METHOD_IMG]: img = [], [TRACKER_METHOD_JS]: js = []} = parseEventTrackers( + nativeResponse.eventtrackers || [] + )[EVENT_TYPE_IMPRESSION] || {}; if (nativeResponse.imptrackers) { img = img.concat(nativeResponse.imptrackers); @@ -726,8 +707,8 @@ export function legacyPropertiesToOrtbNative(legacyNative) { case 'impressionTrackers': (Array.isArray(value) ? value : [value]).forEach(url => { response.eventtrackers.push({ - event: TRACKER_EVENTS.impression, - method: TRACKER_METHODS.img, + event: EVENT_TYPE_IMPRESSION, + method: TRACKER_METHOD_IMG, url }); }); @@ -830,10 +811,10 @@ export function toLegacyResponse(ortbResponse, ortbRequest) { legacyResponse.impressionTrackers.push(...ortbResponse.imptrackers); } for (const eventTracker of ortbResponse?.eventtrackers || []) { - if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.img) { + if (eventTracker.event === EVENT_TYPE_IMPRESSION && eventTracker.method === TRACKER_METHOD_IMG) { legacyResponse.impressionTrackers.push(eventTracker.url); } - if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.js) { + if (eventTracker.event === EVENT_TYPE_IMPRESSION && eventTracker.method === TRACKER_METHOD_JS) { jsTrackers.push(eventTracker.url); } } diff --git a/src/prebid.js b/src/prebid.js index a536add9a96..505b43e661a 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -929,10 +929,12 @@ if (FEATURES.VIDEO) { * @typedef {Object} MarkBidRequest * @property {string} adUnitCode The ad unit code * @property {string} adId The id representing the ad we want to mark + * @property {boolean} events If true, fires tracking pixels and BID_WON handlers + * @property {boolean} analytics alias of `events` (for backwards compat) * * @alias module:pbjs.markWinningBidAsUsed */ - pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode, analytics = false}) { + pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode, analytics = false, events = false}) { let bids; if (adUnitCode && adId == null) { bids = targeting.getWinningBids(adUnitCode); @@ -942,7 +944,7 @@ if (FEATURES.VIDEO) { logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); } if (bids.length > 0) { - if (analytics) { + if (analytics || events) { markWinningBid(bids[0]); } else { auctionManager.addWinningBid(bids[0]); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a67119e72d4..24ae3d9402d 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3,7 +3,6 @@ import {expect} from 'chai'; import { PrebidServer as Adapter, resetSyncedStatus, - resetWurlMap, validateConfig, s2sDefaultConfig } from 'modules/prebidServerBidAdapter/index.js'; @@ -43,6 +42,7 @@ import { extractEids, getPBSBidderConfig } from '../../../modules/prebidServerBidAdapter/bidderConfig.js'; +import {markWinningBid} from '../../../src/adRendering.js'; let CONFIG = { accountId: '1', @@ -3807,7 +3807,6 @@ describe('S2S Adapter', function () { }); beforeEach(function () { - resetWurlMap(); sinon.stub(utils, 'insertUserSyncIframe'); sinon.stub(utils, 'logError'); sinon.stub(utils, 'getUniqueIdentifierStr').callsFake(() => { @@ -3837,26 +3836,28 @@ describe('S2S Adapter', function () { triggerPixelStub.restore(); }); - it('should call triggerPixel if wurl is defined', function () { - const clonedResponse = utils.deepClone(RESPONSE_OPENRTB); - clonedResponse.seatbid[0].bid[0].ext.prebid.events = { - win: 'https://wurl.org' - }; + it('should translate wurl and burl into eventtrackers', () => { + const burlEvent = {event: 1, method: 1, url: 'burl'}; + const winEvent = {event: 500, method: 1, url: 'events.win'}; + const trackerEvent = {event: 500, method: 1, url: 'eventtracker'}; + const resp = utils.deepClone(RESPONSE_OPENRTB); + resp.seatbid[0].bid[0].ext.eventtrackers = [ + trackerEvent, + burlEvent + ] + resp.seatbid[0].bid[0].ext.prebid.events = { + win: winEvent.url + }; + resp.seatbid[0].bid[0].burl = burlEvent.url; adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); - - events.emit(EVENTS.BID_WON, { - auctionId: '173afb6d132ba3', - adId: '1000' - }); - - sinon.assert.calledOnce(addBidResponse); - expect(utils.triggerPixel.called).to.be.true; - expect(utils.triggerPixel.getCall(0).args[0]).to.include('https://wurl.org'); - }); + server.requests[0].respond(200, {}, JSON.stringify(resp)); + expect(addBidResponse.getCall(0).args[1].eventtrackers).to.have.deep.members([ + burlEvent, trackerEvent, winEvent + ]); + }) - it('should not call triggerPixel if the wurl cache does not contain the winning bid', function () { + it('should call triggerPixel if wurl is defined', function () { const clonedResponse = utils.deepClone(RESPONSE_OPENRTB); clonedResponse.seatbid[0].bid[0].ext.prebid.events = { win: 'https://wurl.org' @@ -3865,13 +3866,11 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); - events.emit(EVENTS.BID_WON, { - auctionId: '173afb6d132ba3', - adId: 'missingAdId' - }); + sinon.assert.calledOnce(addBidResponse); + markWinningBid(addBidResponse.getCall(0).args[1]); - sinon.assert.calledOnce(addBidResponse) - expect(utils.triggerPixel.called).to.be.false; + expect(utils.triggerPixel.called).to.be.true; + expect(utils.triggerPixel.getCall(0).args[0]).to.include('https://wurl.org'); }); it('should not call triggerPixel if wurl is undefined', function () { @@ -3881,12 +3880,8 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); - events.emit(EVENTS.BID_WON, { - auctionId: '173afb6d132ba3', - adId: '1060' - }); - - sinon.assert.calledOnce(addBidResponse) + sinon.assert.calledOnce(addBidResponse); + markWinningBid(addBidResponse.getCall(0).args[1]); expect(utils.triggerPixel.called).to.be.false; }); }) diff --git a/test/spec/ortbConverter/pbsExtensions/trackers_spec.js b/test/spec/ortbConverter/pbsExtensions/trackers_spec.js new file mode 100644 index 00000000000..d645814f793 --- /dev/null +++ b/test/spec/ortbConverter/pbsExtensions/trackers_spec.js @@ -0,0 +1,51 @@ +import {EVENT_TYPE_IMPRESSION, EVENT_TYPE_WIN, TRACKER_METHOD_IMG} from '../../../../src/eventTrackers.js'; +import {addEventTrackers} from '../../../../libraries/pbsExtensions/processors/eventTrackers.js'; + +describe('PBS event trackers', () => { + let bidResponse; + beforeEach(() => { + bidResponse = {}; + }); + + Object.entries({ + 'burl': { + bid: { + burl: 'tracker' + }, + type: EVENT_TYPE_IMPRESSION + }, + 'ext.prebid.events.win': { + bid: { + ext: { + prebid: { + events: { + win: 'tracker' + } + } + } + }, + type: EVENT_TYPE_WIN + } + }).forEach(([t, {type, bid}]) => { + function getTracker() { + return bidResponse.eventtrackers?.find(({event, method, url}) => url === 'tracker' && method === TRACKER_METHOD_IMG && event === type) + } + + it(`should add ${t}`, () => { + addEventTrackers(bidResponse, bid); + expect(getTracker()).to.exist; + }); + it(`should append ${t}`, () => { + bidResponse.eventtrackers = [{method: 123, event: 321, url: 'other-tracker'}]; + addEventTrackers(bidResponse, bid); + expect(getTracker()).to.exist; + expect(bidResponse.eventtrackers.length).to.eql(2); + }); + it('should NOT add a duplicate tracker', () => { + bidResponse.eventtrackers = [{method: TRACKER_METHOD_IMG, event: type, url: 'tracker'}]; + addEventTrackers(bidResponse, bid); + expect(getTracker()).to.exist; + expect(bidResponse.eventtrackers.length).to.eql(1); + }) + }) +}) diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index 373dba95b3a..334475ab9f5 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -17,6 +17,12 @@ import {VIDEO} from '../../../src/mediaTypes.js'; import {auctionManager} from '../../../src/auctionManager.js'; import adapterManager from '../../../src/adapterManager.js'; import {filters} from 'src/targeting.js'; +import { + EVENT_TYPE_IMPRESSION, + EVENT_TYPE_WIN, + TRACKER_METHOD_IMG, + TRACKER_METHOD_JS +} from '../../../src/eventTrackers.js'; describe('adRendering', () => { let sandbox; @@ -179,6 +185,31 @@ describe('adRendering', () => { }) }); + describe('markWinningBid', () => { + let bid; + beforeEach(() => { + bid = {adId: '123'}; + sandbox.stub(utils, 'triggerPixel'); + }); + it('should fire BID_WON', () => { + markWinningBid(bid); + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bid); + }) + it('should fire win tracking pixels', () => { + bid.eventtrackers = [{event: EVENT_TYPE_WIN, method: TRACKER_METHOD_IMG, url: 'tracker'}]; + markWinningBid(bid); + sinon.assert.calledWith(utils.triggerPixel, 'tracker'); + }); + it('should NOT fire non-win or non-pixel trackers', () => { + bid.eventtrackers = [ + {event: EVENT_TYPE_WIN, method: TRACKER_METHOD_JS, url: 'ignored'}, + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'ignored'} + ]; + markWinningBid(bid); + sinon.assert.notCalled(utils.triggerPixel); + }); + }) + describe('deferRendering', () => { let fn, markWin; function markWinHook(next, bidResponse) { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 1aacc518190..6e7280abfa3 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -27,6 +27,12 @@ import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from '../../../../src/activit import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from '../../../../src/activities/activities.js'; import {reset as resetAdUnitCounters} from '../../../../src/adUnits.js'; import {deepClone} from 'src/utils.js'; +import { + EVENT_TYPE_IMPRESSION, + EVENT_TYPE_WIN, + TRACKER_METHOD_IMG, + TRACKER_METHOD_JS +} from '../../../../src/eventTrackers.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -409,11 +415,26 @@ describe('adapterManager tests', function () { criteoSpec.onBidBillable = sinon.spy(); sandbox.stub(utils.internal, 'triggerPixel'); }); + it('should fire impression pixels from eventtrackers', () => { + bids[0].eventtrackers = [ + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'tracker'}, + ] + adapterManager.triggerBilling(bids[0]); + sinon.assert.calledWith(utils.internal.triggerPixel, 'tracker'); + }); + + it('should NOT fire non-impression or non-pixel trackers', () => { + bids[0].eventtrackers = [ + {event: EVENT_TYPE_WIN, method: TRACKER_METHOD_IMG, url: 'ignored'}, + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_JS, url: 'ignored'}, + ] + adapterManager.triggerBilling(bids[0]); + sinon.assert.notCalled(utils.internal.triggerPixel); + }) describe('on client bids', () => { - it('should call bidder\'s onBidBillable, and ignore burl', () => { + it('should call bidder\'s onBidBillable', () => { adapterManager.triggerBilling(bids[0]); sinon.assert.called(criteoSpec.onBidBillable); - sinon.assert.notCalled(utils.internal.triggerPixel) }); it('should not call again on second trigger', () => { adapterManager.triggerBilling(bids[0]); @@ -425,21 +446,10 @@ describe('adapterManager tests', function () { beforeEach(() => { bids[0].source = S2S.SRC; }); - it('should call burl and not onBidBillable', () => { + it('should not call onBidBillable', () => { bids[0].burl = 'burl'; adapterManager.triggerBilling(bids[0]); sinon.assert.notCalled(criteoSpec.onBidBillable); - sinon.assert.calledWith(utils.internal.triggerPixel, 'burl'); - }); - it('should not call burl if not present', () => { - adapterManager.triggerBilling(bids[0]); - sinon.assert.notCalled(utils.internal.triggerPixel); - }); - it('should not call burl again on second triggerBilling', () => { - bids[0].burl = 'burl'; - adapterManager.triggerBilling(bids[0]); - adapterManager.triggerBilling(bids[0]); - sinon.assert.calledOnce(utils.internal.triggerPixel) }); }); }) diff --git a/test/spec/unit/core/eventTrackers_spec.js b/test/spec/unit/core/eventTrackers_spec.js new file mode 100644 index 00000000000..2bf2acdc9eb --- /dev/null +++ b/test/spec/unit/core/eventTrackers_spec.js @@ -0,0 +1,61 @@ +import { + EVENT_TYPE_IMPRESSION, EVENT_TYPE_WIN, + parseEventTrackers, + TRACKER_METHOD_IMG, + TRACKER_METHOD_JS +} from '../../../../src/eventTrackers.js'; + +describe('event trackers', () => { + describe('parseEventTrackers', () => { + Object.entries({ + 'null': { + eventtrackers: null, + expected: {} + }, + 'undef': { + expected: {} + }, + 'empty array': { + eventtrackers: [], + expected: {} + }, + 'unsupported methods and events': { + eventtrackers: [ + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'u1'}, + {event: 999, method: TRACKER_METHOD_IMG, url: 'u2'}, + {event: EVENT_TYPE_IMPRESSION, method: 999, url: 'u3'}, + ], + expected: { + [EVENT_TYPE_IMPRESSION]: { + [TRACKER_METHOD_IMG]: ['u1'], + 999: ['u3'] + }, + 999: { + [TRACKER_METHOD_IMG]: ['u2'] + } + } + }, + 'mixed methods and events': { + eventtrackers: [ + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_JS, url: 'u1'}, + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_JS, url: 'u2'}, + {event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'u3'}, + {event: EVENT_TYPE_WIN, method: TRACKER_METHOD_IMG, url: 'u4'} + ], + expected: { + [EVENT_TYPE_IMPRESSION]: { + [TRACKER_METHOD_JS]: ['u1', 'u2'], + [TRACKER_METHOD_IMG]: ['u3'], + }, + [EVENT_TYPE_WIN]: { + [TRACKER_METHOD_IMG]: ['u4'] + } + } + }, + }).forEach(([t, {eventtrackers, expected}]) => { + it(`can parse ${t}`, () => { + expect(parseEventTrackers(eventtrackers)).to.eql(expected); + }) + }) + }) +}) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index c86742d25a4..1cb87440e80 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1381,17 +1381,19 @@ describe('Unit: Prebid Module', function () { }); }); - it('fires billing url if present on s2s bid', function () { - const burl = 'http://www.example.com/burl'; + it('fires impression trackers if present', function () { + const url = 'http://www.example.com/burl'; pushBidResponseToAuction({ ad: '
ad
', source: 's2s', - burl + eventtrackers: [ + {event: 1, method: 1, url} + ] }); return renderAd(doc, bidId).then(() => { sinon.assert.calledOnce(triggerPixelStub); - sinon.assert.calledWith(triggerPixelStub, burl); + sinon.assert.calledWith(triggerPixelStub, url); }); }); @@ -3599,15 +3601,15 @@ describe('Unit: Prebid Module', function () { } Object.entries({ - 'analytics=true': { + 'events=true': { mark(options = {}) { - $$PREBID_GLOBAL$$.markWinningBidAsUsed(Object.assign({analytics: true}, options)) + $$PREBID_GLOBAL$$.markWinningBidAsUsed(Object.assign({events: true}, options)) }, checkBidWon() { sinon.assert.calledWith(events.emit, EVENTS.BID_WON, markedBid); } }, - 'analytics=false': { + 'events=false': { mark(options = {}) { $$PREBID_GLOBAL$$.markWinningBidAsUsed(options) }, From ed011eafaf7e9b21b6aa3fe45ae9c86bc64a413b Mon Sep 17 00:00:00 2001 From: pm-asit-sahoo <102290803+pm-asit-sahoo@users.noreply.github.com> Date: Thu, 20 Feb 2025 00:16:19 +0530 Subject: [PATCH 0930/1097] PubMatic Analytics Adapter : sending 'ffs' floors param in tracker and optional chaining in logger and tracker (#12739) * Added 'ffs' to tracker call * Added optional chaining to logger and tracker * Added fix for ds * added safe check for floor root values --- modules/pubmaticAnalyticsAdapter.js | 46 +++++++++---------- .../modules/pubmaticAnalyticsAdapter_spec.js | 1 + 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 6afafa6f21d..b5d5a930397 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -400,19 +400,15 @@ function getFloorFetchStatus(floorData) { function executeBidsLoggerCall(e, highestCpmBids) { let auctionId = e.auctionId; - let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; + let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId]?.referer || ''; let auctionCache = cache.auctions[auctionId]; let wiid = auctionCache?.wiid || auctionId; let floorData = auctionCache?.floorData; - let floorFetchStatus = getFloorFetchStatus(auctionCache?.floorData); + let floorFetchStatus = getFloorFetchStatus(floorData); let outputObj = { s: [] }; let pixelURL = END_POINT_BID_LOGGER; - if (!auctionCache) { - return; - } - - if (auctionCache.sent) { + if (!auctionCache || auctionCache.sent) { return; } @@ -479,8 +475,8 @@ function executeBidsLoggerCall(e, highestCpmBids) { } function executeBidWonLoggerCall(auctionId, adUnitId) { - const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; - const winningBids = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; + const winningBidId = cache.auctions[auctionId]?.adUnitCodes[adUnitId]?.bidWon; + const winningBids = cache.auctions[auctionId]?.adUnitCodes[adUnitId]?.bids[winningBidId]; if (!winningBids) { logWarn(LOG_PRE_FIX + 'Could not find winningBids for : ', auctionId); return; @@ -488,26 +484,26 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { let winningBid = winningBids[0]; if (winningBids.length > 1) { - winningBid = winningBids.filter(bid => bid.adId === cache.auctions[auctionId].adUnitCodes[adUnitId].bidWonAdId)[0]; + winningBid = winningBids.filter(bid => bid.adId === cache.auctions[auctionId]?.adUnitCodes[adUnitId]?.bidWonAdId)[0]; } const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder); if (isOWPubmaticBid(adapterName) && isS2SBidder(winningBid.bidder)) { return; } - let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, adUnitId) || {}; + let origAdUnit = getAdUnit(cache.auctions[auctionId]?.origAdUnits, adUnitId) || {}; let owAdUnitId = origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId; let auctionCache = cache.auctions[auctionId]; - let floorData = auctionCache.floorData; + let floorData = auctionCache?.floorData; let wiid = cache.auctions[auctionId]?.wiid || auctionId; - let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; + let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId]?.referer || ''; let adv = winningBid.bidResponse ? getAdDomain(winningBid.bidResponse) || undefined : undefined; let fskp = floorData ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined; let pg = window.parseFloat(Number(winningBid?.bidResponse?.adserverTargeting?.hb_pb || winningBid?.bidResponse?.adserverTargeting?.pwtpb)) || undefined; let pixelURL = END_POINT_WIN_BID_LOGGER; pixelURL += 'pubid=' + publisherId; - pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); + pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId]?.referer || ''); pixelURL += '&tst=' + Math.round((new window.Date()).getTime() / 1000); pixelURL += '&iid=' + enc(wiid); pixelURL += '&bidid=' + enc(winningBidId); @@ -517,12 +513,12 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&au=' + enc(owAdUnitId); pixelURL += '&pn=' + enc(adapterName); pixelURL += '&bc=' + enc(winningBid.bidderCode || winningBid.bidder); - pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); - pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); + pixelURL += '&en=' + enc(winningBid.bidResponse?.bidPriceUSD); + pixelURL += '&eg=' + enc(winningBid.bidResponse?.bidGrossCpmUSD); pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&origbidid=' + enc(winningBid?.bidResponse?.partnerImpId || winningBid?.bidResponse?.prebidBidId || winningBid.bidId); pixelURL += '&di=' + enc(winningBid?.bidResponse?.dealId || OPEN_AUCTION_DEAL_ID); - const ds = winningBid.bidResponse?.meta ? getMetadata(winningBid.bidResponse.meta).ds : undefined; + const ds = winningBid.bidResponse?.meta ? getMetadata(winningBid.bidResponse.meta)?.ds : undefined; if (ds) { pixelURL += '&ds=' + enc(ds); } @@ -538,13 +534,15 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { (fskp != undefined) && (pixelURL += '&fskp=' + enc(fskp)); if (floorData) { const floorRootValues = getFloorsCommonField(floorData.floorRequestData); - const { fsrc, fp, mv } = floorRootValues || {}; - const params = { fsrc, fp, fmv: mv }; - Object.entries(params).forEach(([key, value]) => { - if (value !== undefined) { - pixelURL += `&${key}=${enc(value)}`; - } - }); + if (floorRootValues) { + const { ffs, fsrc, fp, mv } = floorRootValues || {}; + const params = { ffs, fsrc, fp, fmv: mv }; + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + pixelURL += `&${key}=${enc(value)}`; + } + }); + } const floorType = getFloorType(floorData.floorResponseData); if (floorType !== undefined) { pixelURL += '&ft=' + enc(floorType); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index e1be6cb314c..d3c39225843 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -668,6 +668,7 @@ describe('pubmatic analytics adapter', function () { expect(data.ss).to.equal('1'); expect(data.fskp).to.equal('0'); expect(data.af).to.equal('video'); + expect(data.ffs).to.equal('1'); expect(data.ds).to.equal('1208'); }); From e04bfc27d9821a9ea61ee3754af22bd6a1f57819 Mon Sep 17 00:00:00 2001 From: zeeye <56828723+zeeye@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:17:54 +0000 Subject: [PATCH 0931/1097] Mobkoi Bidder Adapter: Initial Release (#12647) * Mobkoi Bidder Adapter: Initialise implementation * feat: max-970: Prebid.js Bidder Adapter: Retrieve Adapter Parameters from Bid Configuration Object (#8) Configuration Object](https://mobkoi.atlassian.net/browse/MAX-970) At this stage, we are only focused on bid win events, so there is no need for analytics adapter integration yet. To streamline the publisher's configuration for our custom bid adapter integration, we retrieve adapter parameters directly from the bid configuration object instead of using "bidderConfiguration." updated bid adapter doc * feat: max-956: We need the placement ID from Tag and HB Connector to be past to the AdServer (#9) ### [We need the placement ID from Tag and HB Connector to be past to the AdServer](https://mobkoi.atlassian.net/browse/MAX-956) * set user.id if available --- modules/mobkoiBidAdapter.js | 282 ++++++++++++++++++ modules/mobkoiBidAdapter.md | 53 ++++ test/spec/modules/mobkoiBidAdapter_spec.js | 315 +++++++++++++++++++++ 3 files changed, 650 insertions(+) create mode 100644 modules/mobkoiBidAdapter.js create mode 100644 modules/mobkoiBidAdapter.md create mode 100644 test/spec/modules/mobkoiBidAdapter_spec.js diff --git a/modules/mobkoiBidAdapter.js b/modules/mobkoiBidAdapter.js new file mode 100644 index 00000000000..09528d2f7df --- /dev/null +++ b/modules/mobkoiBidAdapter.js @@ -0,0 +1,282 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { _each, replaceMacros, deepAccess, deepSetValue, logError } from '../src/utils.js'; + +const BIDDER_CODE = 'mobkoi'; +const GVL_ID = 898; +export const DEFAULT_AD_SERVER_BASE_URL = 'https://adserver.maximus.mobkoi.com'; + +const PUBLISHER_PARAMS = { + /** + * !IMPORTANT: This value must match the value in mobkoiAnalyticsAdapter.js + * The name of the parameter that the publisher can use to specify the ad server endpoint. + */ + PARAM_NAME_AD_SERVER_BASE_URL: 'adServerBaseUrl', + PARAM_NAME_PUBLISHER_ID: 'publisherId', + PARAM_NAME_PLACEMENT_ID: 'placementId', +} + +/** + * The list of ORTB response fields that are used in the macros. Field + * replacement is self-implemented in the adapter. Use dot-notated path for + * nested fields. For example, 'ad.ext.adomain'. For more information, visit + * https://www.npmjs.com/package/dset and https://www.npmjs.com/package/dlv. + */ +const ORTB_RESPONSE_FIELDS_SUPPORT_MACROS = ['adm', 'nurl', 'lurl']; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30, + }, + request(buildRequest, imps, bidderRequest, context) { + const ortbRequest = buildRequest(imps, bidderRequest, context); + const prebidBidRequest = context.bidRequests[0]; + + ortbRequest.id = utils.getOrtbId(prebidBidRequest); + deepSetValue(ortbRequest, 'site.publisher.id', utils.getPublisherId(prebidBidRequest)); + deepSetValue(ortbRequest, 'site.publisher.ext.adServerBaseUrl', utils.getAdServerEndpointBaseUrl(prebidBidRequest)); + // We only support one impression per request. + deepSetValue(ortbRequest, 'imp.0.tagid', utils.getPlacementId(prebidBidRequest)); + deepSetValue(ortbRequest, 'user.id', context.bidRequests[0].userId?.mobkoiId || null); + + return ortbRequest; + }, + bidResponse(buildPrebidBidResponse, ortbBidResponse, context) { + utils.replaceAllMacrosInPlace(ortbBidResponse, context); + + const prebidBid = buildPrebidBidResponse(ortbBidResponse, context); + utils.addCustomFieldsToPrebidBidResponse(prebidBid, ortbBidResponse); + return prebidBid; + }, +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + gvlid: GVL_ID, + + /** + * Determines whether or not the given bid request is valid. + */ + isBidRequestValid(bid) { + if ( + !deepAccess(bid, `params.${PUBLISHER_PARAMS.PARAM_NAME_PUBLISHER_ID}`) && + !deepAccess(bid, 'ortb2.site.publisher.id') + ) { + logError(`The ${PUBLISHER_PARAMS.PARAM_NAME_PUBLISHER_ID} field is required in the bid request. ` + + 'Please follow the setup guideline to set the publisher ID field.' + ); + return false; + } + + if ( + !deepAccess(bid, `params.${PUBLISHER_PARAMS.PARAM_NAME_PLACEMENT_ID}`) + ) { + logError(`The ${PUBLISHER_PARAMS.PARAM_NAME_PLACEMENT_ID} field is required in the bid request. ` + + 'Please follow the setup guideline to set the placement ID field.') + return false; + } + + return true; + }, + /** + * Make a server request from the list of BidRequests. + */ + buildRequests(prebidBidRequests, prebidBidderRequest) { + const adServerEndpoint = utils.getAdServerEndpointBaseUrl(prebidBidderRequest) + '/bid'; + + return { + method: 'POST', + url: adServerEndpoint, + options: { + contentType: 'application/json', + }, + data: converter.toORTB({ + bidRequests: prebidBidRequests, + bidderRequest: prebidBidderRequest + }), + }; + }, + /** + * Unpack the response from the server into a list of bids. + */ + interpretResponse(serverResponse, customBidRequest) { + if (!serverResponse.body) return []; + + const responseBody = {...serverResponse.body, seatbid: serverResponse.body.seatbid}; + const prebidBidResponse = converter.fromORTB({ + request: customBidRequest.data, + response: responseBody, + }); + return prebidBidResponse.bids; + }, +}; + +registerBidder(spec); + +export const utils = { + /** + * !IMPORTANT: Make sure the implementation of this function matches getAdServerEndpointBaseUrl + * in both adapters. + * Obtain the Ad Server Base URL from the given Prebid object. + * @param {*} bid Prebid Bidder Request Object or Prebid Bid Response/Request + * or ORTB Request/Response Object + * @returns {string} The Ad Server Base URL + */ + getAdServerEndpointBaseUrl (bid) { + // Fields that would be automatically set if the publisher set it via pbjs.setBidderConfig. + const ortbPath = `site.publisher.ext.${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}`; + const prebidPath = `ortb2.${ortbPath}`; + + // Fields that would be set by the publisher in the bid + // configuration object in ad unit. + const paramPath = `params.${PUBLISHER_PARAMS.PARAM_NAME_AD_SERVER_BASE_URL}`; + const bidRequestFirstBidParam = `bids.0.${paramPath}`; + + const adServerBaseUrl = + deepAccess(bid, paramPath) || + deepAccess(bid, bidRequestFirstBidParam) || + deepAccess(bid, prebidPath) || + deepAccess(bid, ortbPath) || + DEFAULT_AD_SERVER_BASE_URL; + + return adServerBaseUrl; + }, + + /** + * Extract the placement ID from the given object. + * @param {*} prebidBidRequestOrOrtbBidRequest + * @returns string + * @throws {Error} If the placement ID is not found in the given object. + */ + getPlacementId: function (prebidBidRequestOrOrtbBidRequest) { + // Fields that would be set by the publisher in the bid configuration object in ad unit. + const paramPath = 'params.placementId'; + const bidRequestFirstBidParam = `bids.0.${paramPath}`; + + // ORTB path for placement ID + const ortbPath = 'imp.0.tagid'; + + const placementId = + deepAccess(prebidBidRequestOrOrtbBidRequest, paramPath) || + deepAccess(prebidBidRequestOrOrtbBidRequest, bidRequestFirstBidParam) || + deepAccess(prebidBidRequestOrOrtbBidRequest, ortbPath); + + if (!placementId) { + throw new Error( + 'Failed to obtain placement ID from the given object. ' + + `Please set it via the "${paramPath}" field in the bid configuration.\n` + + 'Given object:\n' + + JSON.stringify({functionParam: prebidBidRequestOrOrtbBidRequest}, null, 3) + ); + } + + return placementId; + }, + + /** + * !IMPORTANT: Make sure the implementation of this function matches utils.getPublisherId in + * both adapters. + * Extract the publisher ID from the given object. + * @param {*} prebidBidRequestOrOrtbBidRequest + * @returns string + * @throws {Error} If the publisher ID is not found in the given object. + */ + getPublisherId: function (prebidBidRequestOrOrtbBidRequest) { + // Fields that would be automatically set if the publisher set it + // via pbjs.setBidderConfig. + const ortbPath = 'site.publisher.id'; + const prebidPath = `ortb2.${ortbPath}`; + + // Fields that would be set by the publisher in the bid + // configuration object in ad unit. + const paramPath = 'params.publisherId'; + const bidRequestFirstBidParam = `bids.0.${paramPath}`; + + const publisherId = + deepAccess(prebidBidRequestOrOrtbBidRequest, paramPath) || + deepAccess(prebidBidRequestOrOrtbBidRequest, bidRequestFirstBidParam) || + deepAccess(prebidBidRequestOrOrtbBidRequest, prebidPath) || + deepAccess(prebidBidRequestOrOrtbBidRequest, ortbPath); + + if (!publisherId) { + throw new Error( + 'Failed to obtain publisher ID from the given object. ' + + `Please set it via the "${prebidPath}" field with pbjs.setBidderConfig.\n` + + 'Given object:\n' + + JSON.stringify({functionParam: prebidBidRequestOrOrtbBidRequest}, null, 3) + ); + } + + return publisherId; + }, + + /** + * !IMPORTANT: Make sure the implementation of this function matches utils.getOrtbId in + * mobkoiAnalyticsAdapter.js. + * We use the bidderRequestId as the ortbId. We could do so because we only + * make one ORTB request per Prebid Bidder Request. + * The ID field named differently when the value passed on to different contexts. + * @param {*} bid Prebid Bidder Request Object or Prebid Bid Response/Request + * or ORTB Request/Response Object + * @returns {string} The ORTB ID + * @throws {Error} If the ORTB ID cannot be found in the given object. + */ + getOrtbId(bid) { + const ortbId = + // called bidderRequestId in Prebid Request + bid.bidderRequestId || + // called seatBidId in Prebid Bid Response Object + bid.seatBidId || + // called ortbId in Interpreted Prebid Response Object + bid.ortbId || + // called id in ORTB object + (Object.hasOwn(bid, 'imp') && bid.id); + + if (!ortbId) { + throw new Error('Unable to find the ORTB ID in the bid object. Given Object:\n' + + JSON.stringify(bid, null, 2) + ); + } + + return ortbId; + }, + + /** + * Append custom fields to the prebid bid response. so that they can be accessed + * in various event handlers. + * @param {*} prebidBidResponse + * @param {*} ortbBidResponse + */ + addCustomFieldsToPrebidBidResponse(prebidBidResponse, ortbBidResponse) { + prebidBidResponse.ortbBidResponse = ortbBidResponse; + prebidBidResponse.ortbId = ortbBidResponse.id; + }, + + replaceAllMacrosInPlace(ortbBidResponse, context) { + const macros = { + // ORTB macros + AUCTION_PRICE: ortbBidResponse.price, + AUCTION_IMP_ID: ortbBidResponse.impid, + AUCTION_CURRENCY: ortbBidResponse.cur, + AUCTION_BID_ID: context.bidderRequest.auctionId, + + // Custom macros + BIDDING_API_BASE_URL: utils.getAdServerEndpointBaseUrl(context.bidderRequest), + CREATIVE_ID: ortbBidResponse.crid, + CAMPAIGN_ID: ortbBidResponse.cid, + ORTB_ID: ortbBidResponse.id, + PUBLISHER_ID: utils.getPublisherId(context.bidderRequest), + }; + + _each(ORTB_RESPONSE_FIELDS_SUPPORT_MACROS, ortbField => { + deepSetValue( + ortbBidResponse, + ortbField, + replaceMacros(deepAccess(ortbBidResponse, ortbField), macros) + ); + }); + }, +} diff --git a/modules/mobkoiBidAdapter.md b/modules/mobkoiBidAdapter.md new file mode 100644 index 00000000000..e5c6c3734ab --- /dev/null +++ b/modules/mobkoiBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +Module Name: Mobkoi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: platformteam@mobkoi.com + +# Description + +Module that connects to Mobkoi Ad Server + +### Supported formats: +- Banner + +# Test Parameters +```js +const adUnits = [ + { + code: 'banner-ad', + mediaTypes: { + banner: { sizes: [300, 200] }, + }, + bids: [ + { + bidder: 'mobkoi', + params: { + publisherId: 'module-test-publisher-id', + placementId: 'moudle-test-placement-id', + adServerBaseUrl: 'https://not.an.adserver.endpoint.com', + } + }, + ], + }, +]; + +pbjs.que.push(function () { + pbjs.addAdUnits(adUnits); +}); +``` + + +# Serve Prebid.js Locally + +To serve Prebid.js locally with specific modules, you can use the following command: + +```sh +gulp serve-fast --modules=consentManagementTcf,tcfControl,mobkoiBidAdapter +``` + +# Run bid adapter test locally + +```sh +gulp test --file=test/spec/modules/mobkoiBidAdapter_spec.js +``` diff --git a/test/spec/modules/mobkoiBidAdapter_spec.js b/test/spec/modules/mobkoiBidAdapter_spec.js new file mode 100644 index 00000000000..31ce715992a --- /dev/null +++ b/test/spec/modules/mobkoiBidAdapter_spec.js @@ -0,0 +1,315 @@ +import { + spec, + utils, + DEFAULT_AD_SERVER_BASE_URL +} from 'modules/mobkoiBidAdapter.js'; + +describe('Mobkoi bidding Adapter', function () { + const testAdServerBaseUrl = 'http://test.adServerBaseUrl.com'; + const testRequestId = 'test-request-id'; + const testPublisherId = 'mobkoiPublisherId'; + const testPlacementId = 'mobkoiPlacementId'; + const testBidId = 'test-bid-id'; + const bidderCode = 'mobkoi'; + const testTransactionId = 'test-transaction-id'; + const testAdUnitId = 'test-ad-unit-id'; + const testAuctionId = 'test-auction-id'; + + const getOrtb2 = () => ({ + site: { + publisher: { + id: testPublisherId, + ext: { adServerBaseUrl: testAdServerBaseUrl } + } + } + }) + + const getBidRequest = () => ({ + bidder: bidderCode, + adUnitCode: 'banner-ad', + transactionId: testTransactionId, + adUnitId: testAdUnitId, + bidId: testBidId, + bidderRequestId: testRequestId, + auctionId: testAuctionId, + ortb2: getOrtb2(), + params: { + publisherId: testPublisherId, + adServerBaseUrl: testAdServerBaseUrl, + placementId: testPlacementId + } + }) + + const getBidderRequest = () => ({ + bidderCode, + auctionId: testAuctionId, + bidderRequestId: testRequestId, + bids: [getBidRequest()], + ortb2: getOrtb2() + }) + + const getConvertedBidRequest = () => ({ + id: testRequestId, + imp: [{ + id: testBidId, + tagid: testPlacementId, + }], + ...getOrtb2(), + test: 0 + }) + + const adm = '
test ad
'; + const lurl = 'test.com/loss'; + const nurl = 'test.com/win'; + + const getBidderResponse = () => ({ + body: { + id: testBidId, + cur: 'USD', + seatbid: [ + { + seat: 'mobkoi_debug', + bid: [ + { + id: testBidId, + impid: testBidId, + cid: 'campaign_1', + crid: 'creative_1', + price: 1, + cur: [ + 'USD' + ], + adomain: [ + 'advertiser.com' + ], + adm, + w: 300, + h: 250, + mtype: 1, + lurl, + nurl + } + ] + } + ], + } + }) + + describe('isBidRequestValid', function () { + let bid; + + beforeEach(function () { + bid = getBidderRequest().bids[0]; + }); + + it('should return true when publisher id only exists in ortb2', function () { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when placement id exist in ad unit params', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when publisher ID only exists in ad unit params', function () { + delete bid.ortb2.site.publisher.id; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisher id is missing both in ad unit params and ortb2', function () { + delete bid.ortb2.site.publisher.id; + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when placement id is missing in ad unit params', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisher id is empty in ad unit params and ortb2', function () { + bid.ortb2.site.publisher.id = ''; + bid.params.publisherId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidderRequest, convertedBidRequest; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + convertedBidRequest = getConvertedBidRequest(); + }); + + it('should include converted ORTB data in request', function () { + const request = spec.buildRequests(bidderRequest.bids, bidderRequest); + const ortbData = request.data; + + expect(ortbData.id).to.equal(bidderRequest.bidderRequestId); + expect(ortbData.site.publisher.id).to.equal(bidderRequest.ortb2.site.publisher.id); + }); + + it('should obtain publisher ID from ad unit params if the value does not exist in ortb2.', function () { + delete bidderRequest.ortb2.site.publisher.id; + const request = spec.buildRequests(bidderRequest.bids, bidderRequest); + const ortbData = request.data; + + expect(ortbData.site.publisher.id).to.equal(bidderRequest.bids[0].params.publisherId); + }); + + it('should obtain adServerBaseUrl from ad unit params if the value does not exist in ortb2.', function () { + delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl; + const request = spec.buildRequests(bidderRequest.bids, bidderRequest); + const ortbData = request.data; + + expect(ortbData.site.publisher.ext.adServerBaseUrl).to.equal(bidderRequest.bids[0].params.adServerBaseUrl); + }); + + it('should use the pro server url when the ad server base url is not set', function () { + delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl; + delete bidderRequest.bids[0].params.adServerBaseUrl; + + const request = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.url).to.equal(DEFAULT_AD_SERVER_BASE_URL + '/bid'); + }); + }); + + describe('interpretResponse', function () { + let bidderRequest, bidRequest, bidderResponse; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + + it('should return empty array when response is empty', function () { + expect(spec.interpretResponse({}, {})).to.deep.equal([]); + }); + + it('should interpret valid bid response', function () { + const bidsResponse = spec.interpretResponse(bidderResponse, bidRequest); + expect(bidsResponse).to.not.be.empty; + const bid = bidsResponse[0]; + + expect(bid.ad).to.include(adm); + expect(bid.requestId).to.equal(bidderResponse.body.seatbid[0].bid[0].impid); + expect(bid.cpm).to.equal(bidderResponse.body.seatbid[0].bid[0].price); + expect(bid.width).to.equal(bidderResponse.body.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidderResponse.body.seatbid[0].bid[0].h); + expect(bid.creativeId).to.equal(bidderResponse.body.seatbid[0].bid[0].crid); + expect(bid.currency).to.equal(bidderResponse.body.cur); + expect(bid.netRevenue).to.be.true; + expect(bid.ttl).to.equal(30); + }); + }) + + describe('utils', function () { + let bidderRequest; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + }); + + describe('getAdServerEndpointBaseUrl', function () { + it('should return the adServerBaseUrl from the given object', function () { + expect(utils.getAdServerEndpointBaseUrl(bidderRequest)) + .to.equal(testAdServerBaseUrl); + }); + + it('should return default prod ad server url when adServerBaseUrl is missing in params and ortb2', function () { + delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl; + delete bidderRequest.bids[0].params.adServerBaseUrl; + + expect(utils.getAdServerEndpointBaseUrl(bidderRequest)).to.equal(DEFAULT_AD_SERVER_BASE_URL); + }); + }) + + describe('getPublisherId', function () { + it('should return the publisherId from the given object', function () { + expect(utils.getPublisherId(bidderRequest)).to.equal(bidderRequest.ortb2.site.publisher.id); + }); + + it('should throw error when publisherId is missing', function () { + delete bidderRequest.ortb2.site.publisher.id; + delete bidderRequest.bids[0].params.publisherId; + expect(() => { + utils.getPublisherId(bidderRequest); + }).to.throw(); + }); + }) + + describe('getOrtbId', function () { + it('should return the ortbId from the prebid request object (i.e bidderRequestId)', function () { + expect(utils.getOrtbId(bidderRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should return the ortbId from the prebid response object (i.e seatBidId)', function () { + const customBidRequest = { ...bidderRequest, seatBidId: bidderRequest.bidderRequestId }; + delete customBidRequest.bidderRequestId; + expect(utils.getOrtbId(customBidRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should return the ortbId from the interpreted prebid response object (i.e ortbId)', function () { + const customBidRequest = { ...bidderRequest, ortbId: bidderRequest.bidderRequestId }; + delete customBidRequest.bidderRequestId; + expect(utils.getOrtbId(customBidRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should return the ortbId from the ORTB request object (i.e has imp)', function () { + const customBidRequest = { ...bidderRequest, imp: {}, id: bidderRequest.bidderRequestId }; + delete customBidRequest.bidderRequestId; + expect(utils.getOrtbId(customBidRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should throw error when ortbId is missing', function () { + delete bidderRequest.bidderRequestId; + expect(() => { + utils.getOrtbId(bidderRequest); + }).to.throw(); + }); + }) + + describe('replaceAllMacrosInPlace', function () { + let bidderResponse, bidRequest, bidderRequest; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + + it('should replace all macros in adm, nurl, and lurl fields', function () { + const bid = bidderResponse.body.seatbid[0].bid[0]; + bid.nurl = '${BIDDING_API_BASE_URL}/win?price=${AUCTION_PRICE}&impressionId=${AUCTION_IMP_ID}¤cy=${AUCTION_CURRENCY}&campaignId=${CAMPAIGN_ID}&creativeId=${CREATIVE_ID}&publisherId=${PUBLISHER_ID}&ortbId=${ORTB_ID}'; + bid.lurl = '${BIDDING_API_BASE_URL}/loss?price=${AUCTION_PRICE}&impressionId=${AUCTION_IMP_ID}¤cy=${AUCTION_CURRENCY}&campaignId=${CAMPAIGN_ID}&creativeId=${CREATIVE_ID}&publisherId=${PUBLISHER_ID}&ortbId=${ORTB_ID}'; + bid.adm = '
${AUCTION_PRICE}${AUCTION_CURRENCY}${AUCTION_IMP_ID}${AUCTION_BID_ID}${CAMPAIGN_ID}${CREATIVE_ID}${PUBLISHER_ID}${ORTB_ID}${BIDDING_API_BASE_URL}
'; + + const BIDDING_API_BASE_URL = testAdServerBaseUrl; + const AUCTION_CURRENCY = bidderResponse.body.cur; + const AUCTION_BID_ID = bidderRequest.auctionId; + const AUCTION_PRICE = bid.price; + const AUCTION_IMP_ID = bid.impid; + const CREATIVE_ID = bid.crid; + const CAMPAIGN_ID = bid.cid; + const PUBLISHER_ID = bidderRequest.ortb2.site.publisher.id; + const ORTB_ID = bidderResponse.body.id; + + const context = { + bidRequest, + bidderRequest + } + utils.replaceAllMacrosInPlace(bid, context); + + expect(bid.adm).to.equal(`
${AUCTION_PRICE}${AUCTION_CURRENCY}${AUCTION_IMP_ID}${AUCTION_BID_ID}${CAMPAIGN_ID}${CREATIVE_ID}${PUBLISHER_ID}${ORTB_ID}${BIDDING_API_BASE_URL}
`); + expect(bid.lurl).to.equal( + `${BIDDING_API_BASE_URL}/loss?price=${AUCTION_PRICE}&impressionId=${AUCTION_IMP_ID}¤cy=${AUCTION_CURRENCY}&campaignId=${CAMPAIGN_ID}&creativeId=${CREATIVE_ID}&publisherId=${PUBLISHER_ID}&ortbId=${ORTB_ID}` + ); + expect(bid.nurl).to.equal( + `${BIDDING_API_BASE_URL}/win?price=${AUCTION_PRICE}&impressionId=${AUCTION_IMP_ID}¤cy=${AUCTION_CURRENCY}&campaignId=${CAMPAIGN_ID}&creativeId=${CREATIVE_ID}&publisherId=${PUBLISHER_ID}&ortbId=${ORTB_ID}` + ); + }); + }) + }) +}) From ff56019f9fa376fb8369afc245a0b21d2082b991 Mon Sep 17 00:00:00 2001 From: zeeye <56828723+zeeye@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:25:00 +0000 Subject: [PATCH 0932/1097] Mobkoi User ID module: Initial Release (#12733) * feat: max-848 Prebid: setup development harness. max-849: Prebid: Make prebid RTB ORTB request to /bid (#1) harness](https://mobkoi.atlassian.net/browse/MAX-848) Set up a local development environment for testing and iterating on Prebid customization changes. Sub-tasks: Install Prebid.js dependencies. Create a custom Prebid.js Adapter (mobkoiBidAdapter) and build a custom Prebid.js package to serve locally (the custom Prebid.js package is available to serve to a local webpage). Initialize Ad Service Bid endpoint, ensuring it can serve dummy bid objects to the client. Initialize Ad Server Ad endpoint to serve dummy ads/creatives that display on the sample website. Set up a sample website for end-to-end testing, including page load, Prebid.js, Ad Service Bid endpoint, returning bids to the front-end, Ad Server Ad endpoint, and loading ads on the page. /bid](https://mobkoi.atlassian.net/browse/MAX-849) Update Prebid.js to create ORTB-formatted bid requests for the /bid endpoint. Sub-tasks: Modify Prebid request formatting to ORTB. Validate bid responses from /bid with ORTB formatting. Integrate the new ORTB bid request structure in the /bid endpoint base on the data provided by Prebid.js. Create unit tests. feat: max-852: Prebid: Log bid win to adserver (#3) > Related PRs https://github.com/mobkoi/adserver/pull/6 adserver](https://mobkoi.atlassian.net/browse/MAX-852) Implement logging of bid wins directly to the ad server. Sub-tasks: Capture winning bid events in the Prebid.js custom adapter in various steps of biding process. feat: max-853: Prebid: Log bid loss to adserver (#4) adserver](https://mobkoi.atlassian.net/browse/MAX-853) Implement logging of failed bid events for monitoring purposes. Sub-tasks: Initialise a Prebid custom analytic adapter. Capture bid failure events within Prebid.js during various steps of the bidding process Initialise the endpoint for receiving bid loss signals. Logs will log into Grafana, but this will be done in a separate ticket feat: max-876: Prebid: Analytic Adapter Log debug info to adserver (#5) > Related PR: https://github.com/mobkoi/adserver/pull/10 adserver](https://mobkoi.atlassian.net/browse/MAX-876) Add logging for debugging information to assist with monitoring and troubleshooting. Sub-tasks Record events at different stages of bid processing on the client side via the custom analytic adapter Save event messages locally on the client. Tag each message with one of three levels: info, warn, or debug. feat: writing unit tests for mobkoi adapters (#6) Co-authored-by: nvkftw updated doc description added the missing mobkoiBidAdapter md small fix for our unit test added intergration with mobkoi getuid and setuid endpoint double encoded the setuid callback added mobkoiIdSystem module got a working smartadserver sync url from provided example got a working equativ url before rollback to ajax call for equativ intergration WIP testing droping pixel in iframe feat: max-970: Prebid.js Bidder Adapter: Retrieve Adapter Parameters from Bid Configuration Object (#8) Configuration Object](https://mobkoi.atlassian.net/browse/MAX-970) At this stage, we are only focused on bid win events, so there is no need for analytics adapter integration yet. To streamline the publisher's configuration for our custom bid adapter integration, we retrieve adapter parameters directly from the bid configuration object instead of using "bidderConfiguration." updated bid adapter doc wip equativ pixel in an iframe approvated concept cookie sync work on client side code tidy up for the working cookie solution removed the need for cookieName param matches the backend endpoint name changes feat: max-956: We need the placement ID from Tag and HB Connector to be past to the AdServer (#9) the AdServer](https://mobkoi.atlassian.net/browse/MAX-956) removed unexpected code transfer the user id to ortb2 request body and set the field to null if not avaiable fixed a minor bug enabed consent string added unit tests for mobkoi ID system module fixed a minor bug removed the code that wrapping URL in URL objects. It just make things complicated fixed the publisher ID in macro issue clean up branch for offical PR pass the expiration value when setting storage using the storage manager updated id system module md pass expire setting to storage * code tidy up * switch to getStorageManager instead of getCoreStorageManager * add mobkoiIdSystem to modules/.submodules.json file --- modules/.submodules.json | 1 + modules/mobkoiIdSystem.js | 144 +++++++++++++++ modules/mobkoiIdSystem.md | 38 ++++ test/spec/modules/mobkoiIdSystem_spec.js | 224 +++++++++++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 modules/mobkoiIdSystem.js create mode 100644 modules/mobkoiIdSystem.md create mode 100644 test/spec/modules/mobkoiIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 6dac11bf0ed..9e5b28bd2d7 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -29,6 +29,7 @@ "lockrAIMIdSystem", "lotamePanoramaIdSystem", "merkleIdSystem", + "mobkoiIdSystem", "mwOpenLinkIdSystem", "mygaruIdSystem", "naveggIdSystem", diff --git a/modules/mobkoiIdSystem.js b/modules/mobkoiIdSystem.js new file mode 100644 index 00000000000..d95b5effa77 --- /dev/null +++ b/modules/mobkoiIdSystem.js @@ -0,0 +1,144 @@ +/** + * This module adds mobkoiId support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/mobkoiIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { logError, logInfo, deepAccess, insertUserSyncIframe } from '../src/utils.js'; + +const GVL_ID = 898; +const MODULE_NAME = 'mobkoiId'; +export const PROD_AD_SERVER_BASE_URL = 'https://adserver.maximus.mobkoi.com'; +export const EQUATIV_BASE_URL = 'https://sync.smartadserver.com'; +export const EQUATIV_NETWORK_ID = '5290'; +/** + * !IMPORTANT: This value must match the value in mobkoiAnalyticsAdapter.js + * The name of the parameter that the publisher can use to specify the ad server endpoint. + */ +const PARAM_NAME_AD_SERVER_BASE_URL = 'adServerBaseUrl'; + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +export const mobkoiIdSubmodule = { + name: MODULE_NAME, + gvlid: GVL_ID, + + decode(value) { + return value ? { [MODULE_NAME]: value } : undefined; + }, + + getId(userSyncOptions, gdprConsent) { + logInfo('Getting Equativ SAS ID.'); + + if (!storage.cookiesAreEnabled()) { + logError('Cookies are not enabled. Module will not work.'); + return { + id: null + }; + } + + const storageName = userSyncOptions && userSyncOptions.storage && userSyncOptions.storage.name; + if (!storageName) { + logError('Storage name is not defined. Module will not work.'); + return { + id: null + }; + } + + const existingId = storage.getCookie(storageName); + + if (existingId) { + logInfo(`Found "${storageName}" from local cookie: "${existingId}"`); + return { id: existingId }; + } + + logInfo(`Cannot found "${storageName}" in local cookie with name.`); + return { + callback: () => { + return new Promise((resolve, _reject) => { + utils.requestEquativSasId( + userSyncOptions, + gdprConsent, + (sasId) => { + if (!sasId) { + logError('Equativ SAS ID is empty'); + resolve({ id: null }); + return; + } + + logInfo(`Fetched Equativ SAS ID: "${sasId}"`); + storage.setCookie(storageName, sasId, userSyncOptions.storage.expires); + logInfo(`Stored Equativ SAS ID in local cookie with name: "${storageName}"`); + resolve({ id: sasId }); + } + ); + }); + } + }; + }, +}; + +submodule('userId', mobkoiIdSubmodule); + +export const utils = { + requestEquativSasId(syncUserOptions, gdprConsent, onCompleteCallback) { + logInfo('Start requesting Equativ SAS ID'); + const adServerBaseUrl = deepAccess( + syncUserOptions, + `params.${PARAM_NAME_AD_SERVER_BASE_URL}`) || PROD_AD_SERVER_BASE_URL; + + const equativPixelUrl = utils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + logInfo('Equativ SAS ID request URL:', equativPixelUrl); + + const url = adServerBaseUrl + '/pixeliframe?' + + 'pixelUrl=' + encodeURIComponent(equativPixelUrl) + + '&cookieName=sas_uid'; + + /** + * Listen for messages from the iframe + */ + window.addEventListener('message', function(event) { + switch (event.data.type) { + case 'MOBKOI_PIXEL_SYNC_COMPLETE': + const sasUid = event.data.syncData; + logInfo('Parent window Sync completed. SAS ID:', sasUid); + onCompleteCallback(sasUid); + break; + case 'MOBKOI_PIXEL_SYNC_ERROR': + logError('Parent window Sync failed:', event.data.error); + onCompleteCallback(null); + break; + } + }); + + insertUserSyncIframe(url, () => { + logInfo('insertUserSyncIframe loaded'); + }); + + // Return the URL for testing purposes + return url; + }, + + /** + * Build a pixel URL that will be placed in an iframe to fetch the Equativ SAS ID + */ + buildEquativPixelUrl(syncUserOptions, gdprConsent) { + logInfo('Generating Equativ SAS ID request URL'); + const adServerBaseUrl = + deepAccess( + syncUserOptions, + `params.${PARAM_NAME_AD_SERVER_BASE_URL}`) || PROD_AD_SERVER_BASE_URL; + + const gdprConsentString = gdprConsent && gdprConsent.gdprApplies ? gdprConsent.consentString : ''; + const smartServerUrl = EQUATIV_BASE_URL + '/getuid?' + + `url=` + encodeURIComponent(`${adServerBaseUrl}/getPixel?value=`) + '[sas_uid]' + + `&gdpr_consent=${gdprConsentString}` + + `&nwid=${EQUATIV_NETWORK_ID}`; + + return smartServerUrl; + } +}; diff --git a/modules/mobkoiIdSystem.md b/modules/mobkoiIdSystem.md new file mode 100644 index 00000000000..b122cad213e --- /dev/null +++ b/modules/mobkoiIdSystem.md @@ -0,0 +1,38 @@ +## Mobkoi User ID Submodule + +For assistance setting up your module please contact us at platformteam@mobkoi.com. + +### Prebid Params + +Individual params may be set for the IDx Submodule. +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'mobkoiId', + storage: { + name : 'mobkoi_uid', + type : 'cookie', + expires : 30 + } + }] + } +}); +``` +## Parameter Descriptions for the `userSync` Configuration Section +The below parameters apply only to the Mobkoi integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID of the module - `"mobkoiId"` | `"mobkoiId"` | +| storage.name | Required | String | The name of the cookie local storage where the user ID will be stored. | `"mobkoi_uid"` | +| storage.type | Required | String | Must be "`cookie`". This is where the results of the user ID will be stored. | `"cookie"` | +| storage.expires | Required | Integer | How long (in days) the user ID information will be stored. | `30` | + +## Serving the Custom Build Locally + +To serve the custom build locally, use the following command: + +```sh +gulp serve-fast --modules=consentManagementTcf,tcfControl,mobkoiBidAdapter,mobkoiIdSystem,userId +``` diff --git a/test/spec/modules/mobkoiIdSystem_spec.js b/test/spec/modules/mobkoiIdSystem_spec.js new file mode 100644 index 00000000000..4ca5acec686 --- /dev/null +++ b/test/spec/modules/mobkoiIdSystem_spec.js @@ -0,0 +1,224 @@ +import sinon from 'sinon'; +import { + mobkoiIdSubmodule, + storage, + PROD_AD_SERVER_BASE_URL, + EQUATIV_NETWORK_ID, + utils as mobkoiUtils +} from 'modules/mobkoiIdSystem'; +import * as prebidUtils from 'src/utils'; + +const TEST_SAS_ID = 'test-sas-id'; +const TEST_AD_SERVER_BASE_URL = 'https://mocha.test.adserver.com'; +const TEST_CONSENT_STRING = 'test-consent-string'; + +function decodeFullUrl(url) { + return decodeURIComponent(url); +} + +describe('mobkoiIdSystem', function () { + let sandbox, + getCookieStub, + setCookieStub, + cookiesAreEnabledStub, + insertUserSyncIframeStub; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(prebidUtils, 'logInfo'); + sandbox.stub(prebidUtils, 'logError'); + + insertUserSyncIframeStub = sandbox.stub(prebidUtils, 'insertUserSyncIframe'); + getCookieStub = sandbox.stub(storage, 'getCookie'); + setCookieStub = sandbox.stub(storage, 'setCookie'); + cookiesAreEnabledStub = sandbox.stub(storage, 'cookiesAreEnabled'); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('decode', function () { + it('should return undefined if value is empty', function () { + expect(mobkoiIdSubmodule.decode()).to.be.undefined; + }); + + it('should return an object with the module name as key if value is provided', function () { + const value = 'test-value'; + expect(mobkoiIdSubmodule.decode(value)).to.deep.equal({ mobkoiId: value }); + }); + }); + + describe('getId', function () { + const userSyncOptions = { + storage: { + type: 'cookie', + name: '_mobkoi_Id', + expires: 30, // days + } + }; + + it('should return null id if cookies are not enabled', function () { + cookiesAreEnabledStub.returns(false); + const result = mobkoiIdSubmodule.getId(userSyncOptions); + expect(result).to.deep.equal({ id: null }); + }); + + it('should return existing id from cookie if available in cookie', function () { + const testId = 'existing-id'; + cookiesAreEnabledStub.returns(true); + getCookieStub.returns(testId); + const result = mobkoiIdSubmodule.getId(userSyncOptions); + + expect(result).to.deep.equal({ id: testId }); + }); + + it('should return a callback function if id is not available in cookie', function () { + cookiesAreEnabledStub.returns(true); + getCookieStub.returns(null); + const result = mobkoiIdSubmodule.getId(userSyncOptions); + + expect(result).to.have.property('callback').that.is.a('function'); + }); + + it('should the callback function should return a SAS ID', function () { + cookiesAreEnabledStub.returns(true); + getCookieStub.returns(null); + + const requestEquativSasIdStub = sandbox.stub(mobkoiUtils, 'requestEquativSasId') + .callsFake((_syncUserOptions, _gdprConsent, onCompleteCallback) => { + onCompleteCallback(TEST_SAS_ID); + return TEST_SAS_ID; + }); + + const callback = mobkoiIdSubmodule.getId(userSyncOptions).callback; + return callback().then(result => { + expect(setCookieStub.calledOnce).to.be.true; + expect(result).to.deep.equal({ id: TEST_SAS_ID }); + expect(requestEquativSasIdStub.calledOnce).to.be.true; + }); + }); + }); + + describe('utils.requestEquativSasId', function () { + let buildEquativPixelUrlStub; + + beforeEach(function () { + buildEquativPixelUrlStub = sandbox.stub(mobkoiUtils, 'buildEquativPixelUrl'); + }); + + it('should call insertUserSyncIframe with the correctly encoded URL', function () { + const syncUserOptions = {}; + const gdprConsent = {}; + const onCompleteCallback = sinon.spy(); + const testPixelUrl = 'https://equativ.test.pixel.url?uid=[sas_uid]'; + buildEquativPixelUrlStub.returns(testPixelUrl); + + mobkoiUtils.requestEquativSasId(syncUserOptions, gdprConsent, onCompleteCallback); + + const expectedEncodedUrl = encodeURIComponent(testPixelUrl); + expect(insertUserSyncIframeStub.calledOnce).to.be.true; + expect(insertUserSyncIframeStub.firstCall.args[0]).to.include('pixelUrl=' + expectedEncodedUrl); + }); + }); + + describe('utils.buildEquativPixelUrl', function () { + it('should use the provided adServerBaseUrl URL from syncUserOptions', function () { + const gdprConsent = { + gdprApplies: true, + consentString: TEST_CONSENT_STRING + }; + const syncUserOptions = { + params: { + adServerBaseUrl: TEST_AD_SERVER_BASE_URL + } + }; + + const url = mobkoiUtils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + const decodedUrl = decodeFullUrl(url); + + expect(decodedUrl).to.include(TEST_AD_SERVER_BASE_URL); + }); + + it('should use the PROD ad server endpoint if adServerBaseUrl is not provided', function () { + const syncUserOptions = {}; + const gdprConsent = { + gdprApplies: true, + consentString: TEST_CONSENT_STRING + }; + + const url = mobkoiUtils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + const decodedUrl = decodeFullUrl(url); + + expect(decodedUrl).to.include(PROD_AD_SERVER_BASE_URL); + }); + + it('should contains the Equativ network ID', function () { + const syncUserOptions = {}; + const gdprConsent = { + gdprApplies: true, + consentString: TEST_CONSENT_STRING + }; + + const url = mobkoiUtils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + + expect(url).to.include(`nwid=${EQUATIV_NETWORK_ID}`); + }); + + it('should contain a consent string', function () { + const syncUserOptions = { + params: { + adServerBaseUrl: TEST_AD_SERVER_BASE_URL + } + }; + const gdprConsent = { + gdprApplies: true, + consentString: TEST_CONSENT_STRING + }; + + const url = mobkoiUtils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + const decodedUrl = decodeFullUrl(url); + + expect(decodedUrl).to.include( + `gdpr_consent=${TEST_CONSENT_STRING}` + ); + }); + + it('should set empty string to gdpr_consent when GDPR is not applies', function () { + const syncUserOptions = { + params: { + adServerBaseUrl: TEST_AD_SERVER_BASE_URL + } + }; + const gdprConsent = { + gdprApplies: true, + consentString: TEST_CONSENT_STRING + }; + + const url = mobkoiUtils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + + expect(url).to.include( + 'gdpr_consent=' // no value + ); + }); + + it('should contain SAS ID marco', function () { + const syncUserOptions = { + params: { + adServerBaseUrl: TEST_AD_SERVER_BASE_URL + } + }; + const gdprConsent = { + gdprApplies: true, + consentString: TEST_CONSENT_STRING + }; + + const url = mobkoiUtils.buildEquativPixelUrl(syncUserOptions, gdprConsent); + const decodedUrl = decodeFullUrl(url); + + expect(decodedUrl).to.include( + 'value=[sas_uid]' + ); + }); + }); +}); From fd9e031e36718b5aa46272f9e7946f320e8ef98b Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Thu, 20 Feb 2025 04:54:03 -0800 Subject: [PATCH 0933/1097] Fix when dataItem.name is undefined (#12734) --- modules/gumgumBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 525fae1035c..6b11c16c425 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -497,8 +497,8 @@ function buildRequests(validBidRequests, bidderRequest) { export function getCids(site) { if (site.content && Array.isArray(site.content.data)) { for (const dataItem of site.content.data) { - if (dataItem.name.includes('iris.com') || dataItem.name.includes('iris.tv')) { - return dataItem.ext.cids.join(','); + if (typeof dataItem?.name === 'string' && (dataItem.name.includes('iris.com') || dataItem.name.includes('iris.tv'))) { + return Array.isArray(dataItem.ext?.cids) ? dataItem.ext.cids.join(',') : ''; } } } From 4c3c9271d01c5d9991dece191805f18e59cf1eb1 Mon Sep 17 00:00:00 2001 From: dmytro-po Date: Thu, 20 Feb 2025 15:06:08 +0200 Subject: [PATCH 0934/1097] IntentIq ID & Analytics Modules: GAM reporting (#12785) * AGT-399: GAM reporting integration * AGT-399: Description for new parameters * AGT-399: Some fixes after review, gamParameterName test * AGT-399: Change version * AGT-399: Fix linter --- .../intentIqConstants/intentIqConstants.js | 2 +- libraries/intentIqUtils/detectBrowserUtils.js | 2 +- modules/intentIqIdSystem.js | 24 +++++- modules/intentIqIdSystem.md | 2 + test/spec/modules/intentIqIdSystem_spec.js | 84 ++++++++++++++++++- 5 files changed, 109 insertions(+), 5 deletions(-) diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index 2cc9acc1844..7f3b6fd94f7 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -7,4 +7,4 @@ export const OPT_OUT = 'O'; export const BLACK_LIST = 'L'; export const CLIENT_HINTS_KEY = '_iiq_ch'; export const EMPTY = 'EMPTY' -export const VERSION = 0.25 +export const VERSION = 0.26 diff --git a/libraries/intentIqUtils/detectBrowserUtils.js b/libraries/intentIqUtils/detectBrowserUtils.js index c7004c77ae9..37a935bda28 100644 --- a/libraries/intentIqUtils/detectBrowserUtils.js +++ b/libraries/intentIqUtils/detectBrowserUtils.js @@ -64,7 +64,7 @@ export function detectBrowserFromUserAgent(userAgent) { /** * Detects the browser from the NavigatorUAData object - * @param {NavigatorUAData} userAgentData - The user agent data object from the browser + * @param {Object} userAgentData - The user agent data object from the browser * @return {string} The name of the detected browser or 'unknown' if unable to detect */ export function detectBrowserFromUserAgentData(userAgentData) { diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 4a7d0f47b18..48210e49c19 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,7 +5,7 @@ * @requires module:modules/userId */ -import {logError, logInfo} from '../src/utils.js'; +import {logError, logInfo, isPlainObject} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; @@ -159,6 +159,23 @@ function tryParse(data) { } } +/** + * Configures and updates A/B testing group in Google Ad Manager (GAM). + * + * @param {object} gamObjectReference - Reference to the GAM object, expected to have a `cmd` queue and `pubads()` API. + * @param {string} gamParameterName - The name of the GAM targeting parameter where the group value will be stored. + * @param {string} userGroup - The A/B testing group assigned to the user (e.g., 'A', 'B', or a custom value). + */ +export function setGamReporting(gamObjectReference, gamParameterName, userGroup) { + if (isPlainObject(gamObjectReference) && Array.isArray(gamObjectReference.cmd)) { + gamObjectReference.cmd.push(() => { + gamObjectReference + .pubads() + .setTargeting(gamParameterName, userGroup || NOT_YET_DEFINED); + }); + } +} + /** * Processes raw client hints data into a structured format. * @param {object} clientHints - Raw client hints data @@ -218,11 +235,14 @@ export const intentIqIdSubmodule = { let decryptedData, callbackTimeoutID; let callbackFired = false; let runtimeEids = { eids: [] }; + let gamObjectReference = isPlainObject(configParams.gamObjectReference) ? configParams.gamObjectReference : undefined; + let gamParameterName = configParams.gamParameterName ? configParams.gamParameterName : 'intent_iq_group'; const allowedStorage = defineStorageType(config.enabledStorageTypes); let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); const isGroupB = firstPartyData?.group === WITHOUT_IIQ; + setGamReporting(gamObjectReference, gamParameterName, firstPartyData?.group) const firePartnerCallback = () => { if (configParams.callback && !callbackFired) { @@ -403,9 +423,11 @@ export const intentIqIdSubmodule = { firstPartyData.group = WITHOUT_IIQ; storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); defineEmptyDataAndFireCallback(); + if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group); return } else { firstPartyData.group = WITH_IIQ; + if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group); } } if ('isOptedOut' in respJson) { diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 34fd495d625..0103d403503 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -43,6 +43,8 @@ Please find below list of paramters that could be used in configuring Intent IQ | params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | | params.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true`| | params.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages. | `"currentDomain.com"` | +| params.gamObjectReference | Optional | Object | This is a reference to the Google Ad Manager (GAM) object, which will be used to set targeting. If this parameter is not provided, the group reporting will not be configured. | `googletag` | +| params.gamParameterName | Optional | String | The name of the targeting parameter that will be used to pass the group. If not specified, the default value is `intent_iq_group`. | `"intent_iq_group"` | ### Configuration example diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index b95c3603e3a..356ddcb6e2a 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,12 +2,12 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import { decryptData, handleClientHints, readData } from '../../../modules/intentIqIdSystem'; +import { decryptData, handleClientHints, readData, setGamReporting } from '../../../modules/intentIqIdSystem'; import {getGppValue} from '../../../libraries/intentIqUtils/getGppValue.js'; import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; import { clearAllCookies } from '../../helpers/cookies'; import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils'; -import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY} from '../../../libraries/intentIqConstants/intentIqConstants.js'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, NOT_YET_DEFINED, WITH_IIQ} from '../../../libraries/intentIqConstants/intentIqConstants.js'; const partner = 10; const pai = '11'; @@ -48,6 +48,24 @@ export const testClientHints = { wow64: false }; +const mockGAM = () => { + const targetingObject = {}; + return { + cmd: [], + pubads: () => ({ + setTargeting: (key, value) => { + targetingObject[key] = value; + }, + getTargeting: (key) => { + return [targetingObject[key]]; + }, + getTargetingKeys: () => { + return Object.keys(targetingObject); + } + }) + }; +}; + describe('IntentIQ tests', function () { let logErrorStub; let testLSValue = { @@ -199,6 +217,68 @@ describe('IntentIQ tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); + it('should set GAM targeting to U initially and update to A after server response', function () { + let callBackSpy = sinon.spy(); + let mockGamObject = mockGAM(); + let expectedGamParameterName = 'intent_iq_group'; + + const originalPubads = mockGamObject.pubads; + let setTargetingSpy = sinon.spy(); + mockGamObject.pubads = function () { + const obj = { ...originalPubads.apply(this, arguments) }; + const originalSetTargeting = obj.setTargeting; + obj.setTargeting = function (...args) { + setTargetingSpy(...args); + return originalSetTargeting.apply(this, args); + }; + return obj; + }; + + defaultConfigParams.params.gamObjectReference = mockGamObject; + + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + + submoduleCallback(callBackSpy); + let request = server.requests[0]; + + mockGamObject.cmd.forEach(cb => cb()); + mockGamObject.cmd = [] + + let groupBeforeResponse = mockGamObject.pubads().getTargeting(expectedGamParameterName); + + request.respond( + 200, + responseHeader, + JSON.stringify({ group: 'A', tc: 20 }) + ); + + mockGamObject.cmd.forEach(item => item()); + + let groupAfterResponse = mockGamObject.pubads().getTargeting(expectedGamParameterName); + + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39'); + expect(groupBeforeResponse).to.deep.equal([NOT_YET_DEFINED]); + expect(groupAfterResponse).to.deep.equal([WITH_IIQ]); + + expect(setTargetingSpy.calledTwice).to.be.true; + }); + + it('should use the provided gamParameterName from configParams', function () { + let callBackSpy = sinon.spy(); + let mockGamObject = mockGAM(); + let customParamName = 'custom_gam_param'; + + defaultConfigParams.params.gamObjectReference = mockGamObject; + defaultConfigParams.params.gamParameterName = customParamName; + + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + mockGamObject.cmd.forEach(cb => cb()); + let targetingKeys = mockGamObject.pubads().getTargetingKeys(); + + expect(targetingKeys).to.include(customParamName); + }); + it('should not throw Uncaught TypeError when IntentIQ endpoint returns empty response', function () { let callBackSpy = sinon.spy(); let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; From 589f8c0dbfdbe9012dcccd2db4d54d031ecc0712 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 20 Feb 2025 14:58:14 +0100 Subject: [PATCH 0935/1097] Debugging Module: Bid responses for various media types (+ TestBidder) (#12720) * 12512 Mock bid responses for various media types * lint fix * resolvers execution fix * + basic examples * comments * adapt native to ortb & refactor video example * moving renderer to bid response --- .../testBidder/testBidderBannerExample.html | 79 +++++++++ .../testBidder/testBidderNativeExample.html | 151 ++++++++++++++++++ .../testBidder/testBidderVideoExample.html | 75 +++++++++ modules/debugging/bidInterceptor.js | 58 ++++++- modules/debugging/responses.js | 58 +++++++ src/prebid.js | 7 +- 6 files changed, 418 insertions(+), 10 deletions(-) create mode 100644 integrationExamples/testBidder/testBidderBannerExample.html create mode 100644 integrationExamples/testBidder/testBidderNativeExample.html create mode 100644 integrationExamples/testBidder/testBidderVideoExample.html create mode 100644 modules/debugging/responses.js diff --git a/integrationExamples/testBidder/testBidderBannerExample.html b/integrationExamples/testBidder/testBidderBannerExample.html new file mode 100644 index 00000000000..665625b5044 --- /dev/null +++ b/integrationExamples/testBidder/testBidderBannerExample.html @@ -0,0 +1,79 @@ + + + Prebid Test Bidder Example + + + + +

Prebid Test Bidder Example

+
Banner ad
+ + + \ No newline at end of file diff --git a/integrationExamples/testBidder/testBidderNativeExample.html b/integrationExamples/testBidder/testBidderNativeExample.html new file mode 100644 index 00000000000..d9b51674e2e --- /dev/null +++ b/integrationExamples/testBidder/testBidderNativeExample.html @@ -0,0 +1,151 @@ + + + Prebid Test Bidder Example + + + + + +

Prebid Test Bidder Example

+
Native ad
+
+ + \ No newline at end of file diff --git a/integrationExamples/testBidder/testBidderVideoExample.html b/integrationExamples/testBidder/testBidderVideoExample.html new file mode 100644 index 00000000000..b5f027d99b4 --- /dev/null +++ b/integrationExamples/testBidder/testBidderVideoExample.html @@ -0,0 +1,75 @@ + + + Prebid Test Bidder Example + + + + +

Prebid Test Bidder Example

+
Video ad
+
+ + \ No newline at end of file diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 102d1723eec..390cd1735dc 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -1,3 +1,6 @@ +import { Renderer } from '../../src/Renderer.js'; +import { auctionManager } from '../../src/auctionManager.js'; +import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; import { deepAccess, deepClone, @@ -5,6 +8,7 @@ import { mergeDeep, hasNonSerializableProperty } from '../../src/utils.js'; +import responseResolvers from './responses.js'; /** * @typedef {Number|String|boolean|null|undefined} Scalar @@ -143,9 +147,7 @@ Object.assign(BidInterceptor.prototype, { return (bid, ...args) => { const response = this.responseDefaults(bid); mergeDeep(response, replFn({args: [bid, ...args]})); - if (!response.hasOwnProperty('ad') && !response.hasOwnProperty('adUrl')) { - response.ad = this.defaultAd(bid, response); - } + this.setDefaultAd(bid, response); response.isDebug = true; return response; } @@ -169,20 +171,60 @@ Object.assign(BidInterceptor.prototype, { }, responseDefaults(bid) { - return { + const response = { requestId: bid.bidId, cpm: 3.5764, currency: 'EUR', - width: 300, - height: 250, + width: 600, + height: 500, ttl: 360, creativeId: 'mock-creative-id', netRevenue: false, meta: {} }; + + if (!bid.mediaType) { + const adUnit = auctionManager.index.getAdUnit({adUnitId: bid.adUnitId}) || {mediaTypes: {}}; + response.mediaType = Object.keys(adUnit.mediaTypes)[0] || 'banner'; + } + + return response; }, - defaultAd(bid, bidResponse) { - return ``; + setDefaultAd(bid, bidResponse) { + switch (bidResponse.mediaType) { + case VIDEO: + if (!bidResponse.hasOwnProperty('vastXml') && !bidResponse.hasOwnProperty('vastUrl')) { + bidResponse.vastXml = responseResolvers[VIDEO](); + bidResponse.renderer = Renderer.install({ + url: 'https://cdn.jwplayer.com/libraries/l5MchIxB.js', + }); + bidResponse.renderer.setRender(function (bid) { + const player = window.jwplayer('player').setup({ + width: 640, + height: 360, + advertising: { + client: 'vast', + outstream: true, + endstate: 'close' + }, + }); + player.on('ready', function() { + player.loadAdXml(bid.vastXml); + }); + }) + } + break; + case NATIVE: + if (!bidResponse.hasOwnProperty('native')) { + bidResponse.native = responseResolvers[NATIVE](bid); + } + break; + case BANNER: + default: + if (!bidResponse.hasOwnProperty('ad') && !bidResponse.hasOwnProperty('adUrl')) { + bidResponse.ad = responseResolvers[BANNER](); + } + } }, /** * Match a candidate bid against all registered rules. diff --git a/modules/debugging/responses.js b/modules/debugging/responses.js new file mode 100644 index 00000000000..f9950fac17d --- /dev/null +++ b/modules/debugging/responses.js @@ -0,0 +1,58 @@ +import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; + +const ORTB_NATIVE_ASSET_TYPES = ['img', 'video', 'link', 'data', 'title']; + +export default { + [BANNER]: () => ``, + [VIDEO]: () => 'GDFPDemo00:00:11', + [NATIVE]: (bid) => { + return { + ortb: { + link: { + url: 'https://www.link.example', + clicktrackers: ['https://impression.example'] + }, + assets: bid.nativeOrtbRequest.assets.map(mapDefaultNativeOrtbAsset) + } + } + } +} + +function mapDefaultNativeOrtbAsset(asset) { + const assetType = ORTB_NATIVE_ASSET_TYPES.find(type => asset.hasOwnProperty(type)); + switch (assetType) { + case 'img': + return { + ...asset, + img: { + type: 3, + w: 600, + h: 500, + url: 'https://vcdn.adnxs.com/p/creative-image/27/c0/52/67/27c05267-5a6d-4874-834e-18e218493c32.png', + } + } + case 'video': + return { + ...asset, + video: { + vasttag: 'GDFPDemo00:00:11' + } + } + case 'data': { + return { + ...asset, + data: { + value: '5 stars' + } + } + } + case 'title': { + return { + ...asset, + title: { + text: 'Prebid Native Example' + } + } + } + } +} diff --git a/src/prebid.js b/src/prebid.js index 505b43e661a..4bfa7745680 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -50,6 +50,7 @@ import {getHighestCpm} from './utils/reducers.js'; import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js'; import { ORTB_BANNER_PARAMS } from './banner.js'; import { BANNER, VIDEO } from './mediaTypes.js'; +import { newBidder } from './adapters/bidderFactory.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -769,12 +770,14 @@ pbjsInstance.getEvents = function () { * Wrapper to register bidderAdapter externally (adapterManager.registerBidAdapter()) * @param {Function} bidderAdaptor [description] * @param {string} bidderCode [description] + * @param {object} spec [description] * @alias module:pbjs.registerBidAdapter */ -pbjsInstance.registerBidAdapter = function (bidderAdaptor, bidderCode) { +pbjsInstance.registerBidAdapter = function (bidderAdaptor, bidderCode, spec) { logInfo('Invoking $$PREBID_GLOBAL$$.registerBidAdapter', arguments); try { - adapterManager.registerBidAdapter(bidderAdaptor(), bidderCode); + const bidder = spec ? newBidder(spec) : bidderAdaptor(); + adapterManager.registerBidAdapter(bidder, bidderCode); } catch (e) { logError('Error registering bidder adapter : ' + e.message); } From cacab4f4a8db5a166847a469f9a0d661bed4ab0b Mon Sep 17 00:00:00 2001 From: hasanideepak <80700939+hasanideepak@users.noreply.github.com> Date: Thu, 20 Feb 2025 19:39:13 +0530 Subject: [PATCH 0936/1097] Lane4 Bid Adapter : initial release (#12749) * Audiencelogy Bid Adapter : Initial Release * removed duplicates * removed duplicates * added common code in library libraries/audiencelogyUtils * solved linter issues * imported getRequest from library and solved linting issue * solved trailing space issue * new adapter : lane4 * new adapter : lane4 * removed linter errors * removed trailing space --- libraries/audUtils/bidderUtils.js | 6 + modules/lane4BidAdapter.js | 41 +++ modules/lane4BidAdapter.md | 61 ++++ test/spec/modules/lane4BidAdapter_spec.js | 322 ++++++++++++++++++++++ 4 files changed, 430 insertions(+) create mode 100644 modules/lane4BidAdapter.js create mode 100644 modules/lane4BidAdapter.md create mode 100644 test/spec/modules/lane4BidAdapter_spec.js diff --git a/libraries/audUtils/bidderUtils.js b/libraries/audUtils/bidderUtils.js index 0d3392ec951..12b4ed04374 100644 --- a/libraries/audUtils/bidderUtils.js +++ b/libraries/audUtils/bidderUtils.js @@ -44,6 +44,10 @@ export const getBannerRequest = (bidRequests, bidderRequest, ENDPOINT) => { deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); } req.MediaType = getMediaType(bidReq); + // Adding eids if passed + if (bidReq.userIdAsEids) { + req.user.ext.eids = bidReq.userIdAsEids; + } request.push(req); }); // Return the array of request @@ -167,11 +171,13 @@ const getUserDetails = (bidReq) => { user.buyeruid = bidReq.ortb2.user.buyeruid ? bidReq.ortb2.user.buyeruid : ''; user.keywords = bidReq.ortb2.user.keywords ? bidReq.ortb2.user.keywords : ''; user.customdata = bidReq.ortb2.user.customdata ? bidReq.ortb2.user.customdata : ''; + user.ext = bidReq.ortb2.user.ext ? bidReq.ortb2.user.ext : ''; } else { user.id = ''; user.buyeruid = ''; user.keywords = ''; user.customdata = ''; + user.ext = {}; } return user; } diff --git a/modules/lane4BidAdapter.js b/modules/lane4BidAdapter.js new file mode 100644 index 00000000000..243dabb0bef --- /dev/null +++ b/modules/lane4BidAdapter.js @@ -0,0 +1,41 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, + getNativeResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const EP = 'https://rtb.lane4.io/hb'; +// Export const spec +export const spec = { + code: 'lane4', + supportedMediaTypes: [BANNER, NATIVE], + // Determines whether or not the given bid request is valid + isBidRequestValid: (bidRequestParam) => { + return !!(bidRequestParam.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: (bidR, serverR) => { + // Get Requests based on media types + return getBannerRequest(bidR, serverR, EP); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: (bidRS, bidRQ) => { + let Response = {}; + const mediaType = JSON.parse(bidRQ.data)[0].MediaType; + if (mediaType == BANNER) { + Response = getBannerResponse(bidRS, BANNER); + } else if (mediaType == NATIVE) { + Response = getNativeResponse(bidRS, bidRQ, NATIVE); + } + return Response; + } +} + +registerBidder(spec); diff --git a/modules/lane4BidAdapter.md b/modules/lane4BidAdapter.md new file mode 100644 index 00000000000..af6ee255ad7 --- /dev/null +++ b/modules/lane4BidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Lane4 Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adsupport@lane4.io +``` + +# Description + +Lane4 currently supports the BANNER and NATIVE type ads through prebid js + +Module that connects to Lane4's demand sources. + +# Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'lane4', + params: { + placement_id: 5550, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + }, + { + code: 'native-ad-container', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + bids: [ + { + bidder: 'lane4', + params: { + placement_id: 5551, // Required parameter + bid_floor: 1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/lane4BidAdapter_spec.js b/test/spec/modules/lane4BidAdapter_spec.js new file mode 100644 index 00000000000..49dc3aad6a4 --- /dev/null +++ b/test/spec/modules/lane4BidAdapter_spec.js @@ -0,0 +1,322 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/lane4BidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('lane4 adapter', function () { + let bannerRequest, nativeRequest; + let bannerResponse, nativeResponse, invalidBannerResponse, invalidNativeResponse; + + beforeEach(function () { + bannerRequest = [ + { + bidder: 'lane4', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placement_id: 110044, + bid_floor: 0.5 + } + } + ]; + nativeRequest = [ + { + bidder: 'lane4', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + params: { + placement_id: 5551, + bid_floor: 1 + } + } + ]; + bannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': "", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.lane4.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'lane4', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + nativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': '{\"native\":{\"ver\":\"1.1\",\"assets\": [{\"id\":1,\"required\":1,\"title\":{\"text\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":2,\"required\":1,\"img\":{\"url\":\"https://cdn.lane4.com/1_368852_0.png\",\"w\":500,\"h\":300,\"type\":\"3\"}},{\"id\":3,\"required\":0,\"data\":{\"value\":\"Diabetes In Control. A free weekly diabetes newsletter for Medical Professionals.\"}},{\"id\":4,\"required\":1,\"data\":{\"value\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":6,\"required\":1,\"data\":{\"value\":\"URL\"}}],\"link\":{\"url\":\"https://r.lane4.com/adx-rtb-d/servlet/WebF_AdManager.AdLinkManager?qs=H4sIAAAAAAAAAx2U2QHDIAxDVwKDr3F84P1HqNLPtMSRpSeG9RiPXH+5a474KzO/47YX7UoP50m61fLujlNjb76/8ZiblkimHq5nL/ZRedp3031x1tnk55LjSNN6h9/Zq+qmaLLuWTl74m1ZJKnb+m2OtQm/3L4sb933pM92qMOgjJ41MYmPXKnndRVKs+9bfSEumoZIFpTXuXbCP+WXuzl725E3O+9odi5OJrnBzhwjx9+UnFN3nTNt1/HY5aeljKtvZYpoJHNXr8BWa8ysKQY7ZmNA3DHK2qRwY7+zLu+xm9z5eheJ4Pv2usSptvO3p7JHrnXn0T5yVWdccp9Yz7hhoz2iu2zqsXsGFZ9hh14J6yU4TkJ0BgnOY8tY3tS+n2qsw7xZfKuanSNbAo+9nkJ83i20+FwhfbJeDVOllXsdxmDWauYcSRgS9+yG5qHwUDjAxxA0iZnOjlsnI+y09+ATeTEwbAVGgp0Qu/ceP0kjUvpu1Ty7O9MoegfrmLPxdjUh3mJL+XhARby+Ax8iBckf6BQdn9W+DMlvmlzYLuLlIy7YociFOIvXvEiYYCMboVk8BLHbnw3Zmr5us3xbjtXL67L96F15acJXkM5BOmTaUbBkYGdCI+Et8XmlpbuE3xVQwmxryc2y4wP3ByuuP8GogPZz8OpPaBv8diWWUTrC2nnLhdNUrJRTKc9FepDvwHTDwfbbMCTSb4LhUIFkyFrw/i7GtkPi6NCCai6N47TgNsTnzZWRoVtOSLq7FsLiF29y0Gj0GHVPVYG3QOPS7Swc3UuiFAQZJx3YvpHA2geUgVBASMEL4vcDi2Dw3NPtBSC4EQEvH/uMILu6WyUwraywTeVpoqoHTqOoD84FzReKoWemJy6jyuiBieGlQIe6wY2elTaMOwEUFF5NagzPj6nauc0+aXzQN3Q72hxFAgtfORK60RRAHYZLYymIzSJcXLgRFsqrb1UoD+5Atq7TWojaLTfOyUvH9EeJvZEOilQAXrf/ALoI8ZhABQAA\"},\"imptrackers\":[\"https://i.lane4.com/adx-rtb-d/servlet/WebF_AdManager.ImpCounter?price=${AUCTION_PRICE}&ids=5551,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=https%3A%2F%2Fqa-jboss.audiencelogy.com%2Ftn_native_prod.html\",\"https://i.lane4.com/adx-rtb-d/servlet/WebF_AdManager.ImpTracker?price=${AUCTION_PRICE}&ids=5551,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=\",\"https://rtb-east.lane4.com:9001/beacon?uid=44636f6605b06ec6d4389d6efb7e5054&cc=468132&fccap=5&nid=1\"]}}', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.lane4.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'lane4', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidBannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.lane4.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'lane4', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidNativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': 'invalid response', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.lane4.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'lane4', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'lane4', + params: { + placement_id: 110044 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'lane4', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Banner Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(bannerRequest), + bidRequest = spec.buildRequests(bannerRequest); + expect(bannerRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(bannerRequest); + expect(_Request.url).to.equal('https://rtb.lane4.io/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(bannerRequest[0].bidId); + expect(data[0].placementId).to.equal(110044); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(bannerRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate banner response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(bannerRequest); + let bResponse = spec.interpretResponse(bannerResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(bannerRequest); + let response = spec.interpretResponse(invalidBannerResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('Validate Native Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(nativeRequest), + bidRequest = spec.buildRequests(nativeRequest); + expect(nativeRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(nativeRequest); + expect(_Request.url).to.equal('https://rtb.lane4.io/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(nativeRequest[0].bidId); + expect(data[0].placementId).to.equal(5551); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(nativeRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate native response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(nativeRequest); + let bResponse = spec.interpretResponse(nativeResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(nativeResponse.body.seatbid[0].bid[0].impid); + // expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + // expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('native'); + expect(bResponse[0].native.clickUrl).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.impressionTrackers).to.be.an('array').with.length.above(0); + expect(bResponse[0].native.title).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.image.url).to.be.a('string').and.not.be.empty; + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['www.diabetesincontrol.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(nativeResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(nativeResponse.body.seatbid[0].bid[0].dealId); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); From 3ac1ba229b40d0ec35f7fc5176108c33bb67a44e Mon Sep 17 00:00:00 2001 From: Rupesh Lakhani <35333377+AskRupert-DM@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:10:59 +0000 Subject: [PATCH 0937/1097] Ozone Bid Adapter : support for multi-size adUnits (#12680) * Update ozoneBidAdapter_spec.js spectests updated * Update ozoneBidAdapter.js - fixed a bug with getFloors - we need to take the first index [0] of sizes - changed instances of Array.isArray to isArray * Update ozoneBidAdapter.js * Update ozoneBidAdapter_spec.js * Update ozoneBidAdapter.js removed trailing space * Update ozoneBidAdapter.js --- modules/ozoneBidAdapter.js | 217 ++++++++++------------ test/spec/modules/ozoneBidAdapter_spec.js | 159 +++++++++++++--- 2 files changed, 238 insertions(+), 138 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index d554c0afeda..9ca47c575c5 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -8,7 +8,7 @@ import { contains, mergeDeep, parseUrl, - generateUUID, isInteger, deepClone + generateUUID, isInteger, deepClone, getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -17,15 +17,15 @@ import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; import {getRefererInfo} from '../src/refererDetection.js'; const BIDDER_CODE = 'ozone'; -const ORIGIN = 'https://elb.the-ozone-project.com' // applies only to auction & cookie +const ORIGIN = 'https://elb.the-ozone-project.com'; // applies only to auction & cookie const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.4'; +const OZONEVERSION = '2.9.5'; export const spec = { gvlid: 524, - aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], + aliases: [{code: 'venatus', gvlid: 524}], version: OZONEVERSION, code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], @@ -79,7 +79,7 @@ export const spec = { if (this.batchValueIsValid(bidderConfig.batchRequests)) { this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; } else { - logError('bidderConfig.batchRequests must be boolean or a number. Found & ignored data type: ' + typeof bidderConfig.batchRequests); + logError('invalid config: batchRequest'); } } if (bidderConfig.hasOwnProperty('videoParams')) { @@ -90,7 +90,7 @@ export const spec = { if (this.batchValueIsValid(getBatch)) { this.propertyBag.whitelabel.batchRequests = getBatch; } else { - logError('Ignoring query param: batchRequests - this must be a positive number'); + logError('invalid GET: batchRequests'); } } try { @@ -103,7 +103,7 @@ export const spec = { this.propertyBag.whitelabel.cookieSyncUrl = ORIGIN_DEV + OZONECOOKIESYNC; } } catch (e) {} - logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); + logInfo('whitelabel: ', this.propertyBag.whitelabel); }, batchValueIsValid(batch) { return typeof batch === 'boolean' || (typeof batch === 'number' && batch > 0); @@ -118,11 +118,10 @@ export const spec = { return this.propertyBag.whitelabel.rendererUrl; }, getVideoPlacementValue: function(context) { - if (['instream', 'outstream'].indexOf(context) < 0) return null; + if (['instream', 'outstream'].indexOf(context) < 0) return null; /* do not allow arbitrary strings */ return deepAccess(this.propertyBag, `whitelabel.videoParams.${context}`, null); }, getBatchRequests() { - logInfo('getBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); if (this.propertyBag.whitelabel.batchRequests === true) { return 10; } if (typeof this.propertyBag.whitelabel.batchRequests === 'number' && this.propertyBag.whitelabel.batchRequests > 0) { return this.propertyBag.whitelabel.batchRequests; @@ -130,63 +129,64 @@ export const spec = { return false; }, isBidRequestValid(bid) { + let vf = 'VALIDATION FAILED'; this.loadWhitelabelData(bid); logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code - let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED' - if (!(bid.params.hasOwnProperty('placementId'))) { + let err1 = `${vf} : missing {param} : siteId, placementId and publisherId are REQUIRED`; + if (!(getBidIdParameter('placementId', bid.params))) { logError(err1.replace('{param}', 'placementId'), adUnitCode); return false; } if (!this.isValidPlacementId(bid.params.placementId)) { - logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); + logError(`${vf} : placementId must be exactly 10 numbers`, adUnitCode); return false; } - if (!(bid.params.hasOwnProperty('publisherId'))) { + if (!(getBidIdParameter('publisherId', bid.params))) { logError(err1.replace('{param}', 'publisherId'), adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumeric characters including hyphens', adUnitCode); + logError(`${vf} : publisherId must be /^[a-zA-Z0-9\\-]{12}$/`, adUnitCode); return false; } - if (!(bid.params.hasOwnProperty('siteId'))) { + if (!(getBidIdParameter('siteId', bid.params))) { logError(err1.replace('{param}', 'siteId'), adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + logError(`${vf} : siteId must be /^[0-9]{10}$/`, adUnitCode); return false; } if (bid.params.hasOwnProperty('customParams')) { - logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); + logError(`${vf} : customParams should be renamed: customData`, adUnitCode); return false; } if (bid.params.hasOwnProperty('customData')) { - if (!Array.isArray(bid.params.customData)) { - logError('VALIDATION FAILED : customData is not an Array', adUnitCode); + if (!isArray(bid.params.customData)) { + logError(`${vf} : customData is not an Array`, adUnitCode); return false; } if (bid.params.customData.length < 1) { - logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + logError(`${vf} : empty customData`, adUnitCode); return false; } if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { - logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + logError(`${vf} :no customData[0].targeting`, adUnitCode); return false; } if (typeof bid.params.customData[0]['targeting'] != 'object') { - logError('VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); + logError(`${vf} : customData[0].targeting is not an Object`, adUnitCode); return false; } } if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { - if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - logError('No video context key/value in bid. Rejecting bid: ', bid); + if (!bid.mediaTypes?.[VIDEO]?.context) { + logError(`${vf} No video context key/value`); return false; } - if (bid.mediaTypes[VIDEO].context !== 'instream' && bid.mediaTypes[VIDEO].context !== 'outstream') { - logError('video.context is invalid. Only instream/outstream video is supported. Rejecting bid: ', bid); + if (['instream', 'outstream'].indexOf(bid.mediaTypes?.[VIDEO]?.context) < 0) { + logError(`${vf} video.context is invalid.`); return false; } } @@ -207,7 +207,7 @@ export const spec = { let fledgeEnabled = !!bidderRequest.fledgeEnabled; // IF true then this is added as each bid[].ext.ae=1 let htmlParams = {'publisherId': '', 'siteId': ''}; if (validBidRequests.length > 0) { - this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIdsFromEids(validBidRequests[0])); + Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIdsFromEids(validBidRequests[0])); this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; @@ -215,11 +215,9 @@ export const spec = { logInfo('cookie sync bag', this.cookieSyncBag); let singleRequest = this.getWhitelabelConfigItem('ozone.singleRequest'); singleRequest = singleRequest !== false; // undefined & true will be true - logInfo(`config ${whitelabelBidder}.singleRequest : `, singleRequest); let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params - logInfo('going to get ortb2 from bidder request...'); let fpd = deepAccess(bidderRequest, 'ortb2', null); - logInfo('got fpd: ', fpd); + logInfo('got ortb2 fpd: ', fpd); if (fpd && deepAccess(fpd, 'user')) { logInfo('added FPD user object'); ozoneRequest.user = fpd.user; @@ -227,7 +225,7 @@ export const spec = { const getParams = this.getGetParametersAsObject(); const wlOztestmodeKey = whitelabelPrefix + 'testmode'; const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads - ozoneRequest.device = bidderRequest?.ortb2?.device || {}; + ozoneRequest.device = bidderRequest?.ortb2?.device || {}; // 20240925 rupesh changed this let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string let schain = null; var auctionId = deepAccess(validBidRequests, '0.ortb2.source.tid'); @@ -239,20 +237,18 @@ export const spec = { let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring." obj.tagid = placementId; - let parsed = parseUrl(this.getRefererInfo().page); - obj.secure = parsed.protocol === 'https' ? 1 : 0; + obj.secure = parseUrl(getRefererInfo().page).protocol === 'https' ? 1 : 0; let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { - logInfo('no mediaTypes detected - will use the sizes array in the config root'); arrBannerSizes = ozoneBidRequest.sizes; } else { - logInfo('no mediaTypes detected, no sizes array in the config root either. Cannot set sizes for banner type'); + logInfo('no mediaTypes or sizes array. Cannot set sizes for banner type'); } } else { if (ozoneBidRequest.mediaTypes.hasOwnProperty(BANNER)) { arrBannerSizes = ozoneBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + logInfo('setting banner size from mediaTypes.banner for bidId ' + obj.id + ': ', arrBannerSizes); } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { logInfo('openrtb 2.5 compliant video'); @@ -262,31 +258,31 @@ export const spec = { obj.video = this.addVideoDefaults(obj.video, ozoneBidRequest.mediaTypes[VIDEO], childConfig); } let wh = getWidthAndHeightFromVideoObject(obj.video); - logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); + logInfo(`setting video object ${obj.id} from mediaTypes.video: `, obj.video, 'wh=', wh); + let settingToBe = 'setting obj.video.format to be '; // partial, reusable phrase if (wh && typeof wh === 'object') { obj.video.w = wh['w']; obj.video.h = wh['h']; if (playerSizeIsNestedArray(obj.video)) { // this should never happen; it was in the original spec for this change though. - logInfo('setting obj.video.format to be an array of objects'); + logInfo(`${settingToBe} an array of objects`); obj.video.ext.format = [wh]; } else { - logInfo('setting obj.video.format to be an object'); + logInfo(`${settingToBe} an object`); obj.video.ext.format = wh; } } else { - logWarn('cannot set w, h & format values for video; the config is not right'); + logWarn(`Failed ${settingToBe} anything - bad config`); } } if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; - logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + logInfo(`setting native object ${obj.id} from mediaTypes.native element:`, obj.native); } if (ozoneBidRequest.hasOwnProperty('getFloor')) { - logInfo('This bidRequest object has property: getFloor'); obj.floor = this.getFloorObjectForAuction(ozoneBidRequest); logInfo('obj.floor is : ', obj.floor); } else { - logInfo('This bidRequest object DOES NOT have property: getFloor'); + logInfo('no getFloor property'); } } if (arrBannerSizes.length > 0) { @@ -306,9 +302,17 @@ export const spec = { if (ozoneBidRequest.params.hasOwnProperty('customData')) { obj.ext[whitelabelBidder].customData = ozoneBidRequest.params.customData; } + if (ozoneBidRequest.params.hasOwnProperty('ozFloor')) { + let ozFloorParsed = parseFloat(ozoneBidRequest.params.ozFloor); + if (!isNaN(ozFloorParsed)) { + obj.ext[whitelabelBidder].ozFloor = ozFloorParsed; + } else { + logError(`Ignoring invalid ozFloor value for adunit code: ${ozoneBidRequest.adUnitCode}`); + } + } logInfo(`obj.ext.${whitelabelBidder} is `, obj.ext[whitelabelBidder]); if (isTestMode != null) { - logInfo('setting isTestMode to ', isTestMode); + logInfo(`setting isTestMode: ${isTestMode}`); if (obj.ext[whitelabelBidder].hasOwnProperty('customData')) { for (let i = 0; i < obj.ext[whitelabelBidder].customData.length; i++) { obj.ext[whitelabelBidder].customData[i]['targeting'][wlOztestmodeKey] = isTestMode; @@ -320,10 +324,10 @@ export const spec = { } if (fpd && deepAccess(fpd, 'site')) { logInfo('adding fpd.site'); - if (deepAccess(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', false)) { - obj.ext[whitelabelBidder].customData[0].targeting = Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); + if (deepAccess(obj, `ext.${whitelabelBidder}.customData.0.targeting`, false)) { + Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); } else { - deepSetValue(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', fpd.site); + deepSetValue(obj, `ext.${whitelabelBidder}.customData.0.targeting`, fpd.site); } } if (!schain && deepAccess(ozoneBidRequest, 'schain')) { @@ -345,15 +349,15 @@ export const spec = { if (isInteger(auctionEnvironment)) { deepSetValue(obj, 'ext.ae', auctionEnvironment); } else { - logError('ortb2Imp.ext.ae is not an integer - ignoring it for obj.id=' + obj.id); + logError(`ignoring ortb2Imp.ext.ae - not an integer for obj.id=${obj.id}`); } } return obj; }); let extObj = {}; extObj[whitelabelBidder] = {}; - extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; - extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; + extObj[whitelabelBidder][`${whitelabelPrefix}_pb_v`] = OZONEVERSION; + extObj[whitelabelBidder][`${whitelabelPrefix}_rw`] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info if (userIds.hasOwnProperty('pubcid.org')) { @@ -364,9 +368,9 @@ export const spec = { let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); if (typeof ozOmpFloorDollars === 'number') { - extObj[whitelabelBidder][whitelabelPrefix + '_omp_floor'] = ozOmpFloorDollars; + extObj[whitelabelBidder][`${whitelabelPrefix}_omp_floor`] = ozOmpFloorDollars; } else if (typeof ozOmpFloorDollars !== 'undefined') { - logError(`${whitelabelPrefix}_omp_floor is invalid - IF SET then this must be a number, representing dollar value eg. ${whitelabelPrefix}_omp_floor: 1.55. You have it set as a ` + (typeof ozOmpFloorDollars)); + logError(`IF set, ${whitelabelPrefix}_omp_floor must be a number eg. 1.55. Found:` + (typeof ozOmpFloorDollars)); } let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; @@ -375,26 +379,22 @@ export const spec = { logInfo('setting aliases object'); extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; } - if (getParams.hasOwnProperty('ozf')) { extObj[whitelabelBidder]['ozf'] = getParams.ozf === 'true' || getParams.ozf === '1' ? 1 : 0; } - if (getParams.hasOwnProperty('ozpf')) { extObj[whitelabelBidder]['ozpf'] = getParams.ozpf === 'true' || getParams.ozpf === '1' ? 1 : 0; } - if (getParams.hasOwnProperty('ozrp') && getParams.ozrp.match(/^[0-3]$/)) { extObj[whitelabelBidder]['ozrp'] = parseInt(getParams.ozrp); } - if (getParams.hasOwnProperty('ozip') && getParams.ozip.match(/^\d+$/)) { extObj[whitelabelBidder]['ozip'] = parseInt(getParams.ozip); } if (this.propertyBag.endpointOverride != null) { extObj[whitelabelBidder]['origin'] = this.propertyBag.endpointOverride; } let userExtEids = deepAccess(validBidRequests, '0.userIdAsEids', []); // generate the UserIDs in the correct format for UserId module ozoneRequest.site = { 'publisher': {'id': htmlParams.publisherId}, - 'page': this.getRefererInfo().page, + 'page': getRefererInfo().page, 'id': htmlParams.siteId }; ozoneRequest.test = config.getConfig('debug') ? 1 : 0; if (bidderRequest && bidderRequest.gdprConsent) { - logInfo('ADDING GDPR info'); + logInfo('ADDING GDPR'); let apiVersion = deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1); ozoneRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, apiVersion: apiVersion}}; if (deepAccess(ozoneRequest, 'regs.ext.gdpr')) { deepSetValue(ozoneRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } else { - logInfo('**** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); + logWarn('**** Strange CMP info: bidderRequest.gdprConsent exists BUT bidderRequest.gdprConsent.gdprApplies is false. See bidderRequest logged above. ****'); } } else { logInfo('WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object'); @@ -417,10 +417,10 @@ export const spec = { deepSetValue(ozoneRequest, 'regs.coppa', 1); } extObj[whitelabelBidder].cookieDeprecationLabel = deepAccess(bidderRequest, 'ortb2.device.ext.cdep', 'none'); - logInfo('cookieDeprecationLabel from bidderRequest object = ' + extObj[whitelabelBidder].cookieDeprecationLabel); + logInfo(`cookieDeprecationLabel ortb2.device.ext.cdep = ${extObj[whitelabelBidder].cookieDeprecationLabel}`); let batchRequestsVal = this.getBatchRequests(); // false|numeric if (typeof batchRequestsVal === 'number') { - logInfo('going to batch the requests'); + logInfo(`Batching = ${batchRequestsVal}`); let arrRet = []; // return an array of objects containing data describing max 10 bids for (let i = 0; i < tosendtags.length; i += batchRequestsVal) { ozoneRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) @@ -442,9 +442,8 @@ export const spec = { logInfo('batch request going to return : ', arrRet); return arrRet; } - logInfo('requests will not be batched.'); if (singleRequest) { - logInfo('buildRequests starting to generate response for a single request'); + logInfo('single request starting'); ozoneRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; @@ -458,13 +457,12 @@ export const spec = { data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - logInfo('buildRequests request data for single = ', deepClone(ozoneRequest)); this.propertyBag.buildRequestsEnd = new Date().getTime(); - logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); + logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, deepClone(ret)); return ret; } let arrRet = tosendtags.map(imp => { - logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + logInfo('non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); ozoneRequestSingle.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) ozoneRequestSingle.imp = [imp]; @@ -473,7 +471,6 @@ export const spec = { if (auctionId) { deepSetValue(ozoneRequestSingle, 'source.tid', auctionId); } - logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', url: this.getAuctionUrl(), @@ -494,13 +491,13 @@ export const spec = { logInfo('getFloorObjectForAuction mediaTypesSizes : ', mediaTypesSizes); let ret = {}; if (mediaTypesSizes.banner) { - ret.banner = bidRequestRef.getFloor({mediaType: 'banner', currency: 'USD', size: mediaTypesSizes.banner}) || {}; + ret.banner = bidRequestRef.getFloor({mediaType: 'banner', currency: 'USD', size: mediaTypesSizes.banner[0]}); } if (mediaTypesSizes.video) { - ret.video = bidRequestRef.getFloor({mediaType: 'video', currency: 'USD', size: mediaTypesSizes.video}) || {}; + ret.video = bidRequestRef.getFloor({mediaType: 'video', currency: 'USD', size: mediaTypesSizes.video[0]}); } if (mediaTypesSizes.native) { - ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native}) || {}; + ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native[0]}); } logInfo('getFloorObjectForAuction returning : ', deepClone(ret)); return ret; @@ -521,6 +518,7 @@ export const spec = { return []; } let arrAllBids = []; + let labels; let enhancedAdserverTargeting = this.getWhitelabelConfigItem('ozone.enhancedAdserverTargeting'); logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); if (typeof enhancedAdserverTargeting == 'undefined') { @@ -544,17 +542,17 @@ export const spec = { let videoContext = null; let isVideo = false; let bidType = deepAccess(thisBid, 'ext.prebid.type'); - logInfo(`this bid type is : ${bidType}`, j); + logInfo(`this bid type is : ${bidType}`); let adserverTargeting = {}; if (bidType === VIDEO) { isVideo = true; this.setBidMediaTypeIfNotExist(thisBid, VIDEO); videoContext = this.getVideoContextForBidId(thisBid.bidId, request.bidderRequest.bids); // should be instream or outstream (or null if error) if (videoContext === 'outstream') { - logInfo('going to set thisBid.mediaType = VIDEO & attach a renderer to OUTSTREAM video : ', j); + logInfo('setting thisBid.mediaType = VIDEO & attach a renderer to OUTSTREAM video'); thisBid.renderer = newRenderer(thisBid.bidId); } else { - logInfo('bid is not an outstream video, will set thisBid.mediaType = VIDEO and thisBid.vastUrl and not attach a renderer: ', j); + logInfo('not an outstream video, will set thisBid.mediaType = VIDEO and thisBid.vastUrl and not attach a renderer'); thisBid.vastUrl = `https://${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'missing_host')}${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'missing_path')}?id=${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_id', 'missing_id')}`; // need to see if this works ok for ozone adserverTargeting['hb_cache_host'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'no-host'); adserverTargeting['hb_cache_path'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'no-path'); @@ -600,12 +598,17 @@ export const spec = { if (bidderName.match(/^ozappnexus/)) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_sid'] = String(allBidsForThisBidid[bidderName].cid); } + labels = deepAccess(allBidsForThisBidid[bidderName], 'ext.prebid.labels', null); + if (labels) { + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_labels'] = labels.join(','); + } }); } else { + let perBidInfo = `${whitelabelBidder}.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.`; if (useOzWhitelistAdserverKeys) { - logWarn(`You have set a whitelist of adserver keys but this will be ignored because ${whitelabelBidder}.enhancedAdserverTargeting is set to false. No per-bid keys will be sent to adserver.`); + logWarn(`Your adserver keys whitelist will be ignored - ${perBidInfo}`); } else { - logInfo(`${whitelabelBidder}.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.`); + logInfo(perBidInfo); } } let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); @@ -616,6 +619,10 @@ export const spec = { adserverTargeting[whitelabelPrefix + '_cache_id'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_id', 'no-id'); adserverTargeting[whitelabelPrefix + '_uuid'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_uuid', 'no-id'); if (enhancedAdserverTargeting) { + labels = deepAccess(winningBid, 'ext.prebid.labels', null); + if (labels) { + adserverTargeting[whitelabelPrefix + '_labels'] = labels.join(','); + } adserverTargeting[whitelabelPrefix + '_imp_id'] = String(winningBid.impid); adserverTargeting[whitelabelPrefix + '_pb_v'] = OZONEVERSION; adserverTargeting[whitelabelPrefix + '_pb'] = winningBid.price; @@ -624,7 +631,7 @@ export const spec = { adserverTargeting[whitelabelPrefix + '_size'] = `${winningBid.width}x${winningBid.height}`; } if (useOzWhitelistAdserverKeys) { // delete any un-whitelisted keys - logInfo('Going to filter out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); + logInfo('Filtering out adserver targeting keys not in the whitelist: ', ozWhitelistAdserverKeys); Object.keys(adserverTargeting).forEach(function(key) { if (ozWhitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); } thisBid.adserverTargeting = adserverTargeting; @@ -633,10 +640,10 @@ export const spec = { } let ret = arrAllBids; let fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.igi') || []; // 20240606 standardising - if (Array.isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { + if (isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { fledgeAuctionConfigs = fledgeAuctionConfigs.filter(config => { if (!this.isValidAuctionConfig(config)) { - logWarn('Malformed auction config detected:', config); + logWarn('Removing malformed fledge auction config:', config); return false; } return true; @@ -648,7 +655,7 @@ export const spec = { } let endTime = new Date().getTime(); logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); - logInfo('interpretResponse arrAllBids (serialised): ', deepClone(ret)); // this is ok to log because the renderer has not been attached yet + logInfo('will return: ', deepClone(ret)); // this is ok to log because the renderer has not been attached yet return ret; }, isValidAuctionConfig(config) { @@ -706,7 +713,7 @@ export const spec = { arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); arrQueryString.push('usp_consent=' + (usPrivacy || '')); arrQueryString.push('gpp=' + gppString); - if (Array.isArray(applicableSections)) { + if (isArray(applicableSections)) { arrQueryString.push(`gpp_sid=${applicableSections.join()}`); } for (let keyname in this.cookieSyncBag.userIdObject) { @@ -745,7 +752,7 @@ export const spec = { findAllUserIdsFromEids(bidRequest) { let ret = {}; if (!bidRequest.hasOwnProperty('userIdAsEids')) { - logInfo('findAllUserIdsFromEids - no bidRequest.userIdAsEids object - will quit'); + logInfo('findAllUserIdsFromEids - no bidRequest.userIdAsEids object was found on the bid!'); this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy return ret; } @@ -771,7 +778,7 @@ export const spec = { let arr = this.getGetParametersAsObject(); if (arr.hasOwnProperty(whitelabelPrefix + 'storedrequest')) { if (this.isValidPlacementId(arr[whitelabelPrefix + 'storedrequest'])) { - logInfo(`using GET ${whitelabelPrefix}storedrequest ` + arr[whitelabelPrefix + 'storedrequest'] + ' to replace placementId'); + logInfo(`using GET ${whitelabelPrefix}storedrequest=` + arr[whitelabelPrefix + 'storedrequest'] + ' to replace placementId'); return arr[whitelabelPrefix + 'storedrequest']; } else { logError(`GET ${whitelabelPrefix}storedrequest FAILED VALIDATION - will not use it`); @@ -780,33 +787,14 @@ export const spec = { return null; }, getGetParametersAsObject() { - let parsed = parseUrl(this.getRefererInfo().location); + let parsed = parseUrl(getRefererInfo().location); logInfo('getGetParametersAsObject found:', parsed.search); return parsed.search; }, - getRefererInfo() { - if (getRefererInfo().hasOwnProperty('location')) { - logInfo('FOUND location on getRefererInfo OK (prebid >= 7); will use getRefererInfo for location & page'); - return getRefererInfo(); - } else { - logInfo('DID NOT FIND location on getRefererInfo (prebid < 7); will use legacy code that ALWAYS worked reliably to get location & page ;-)'); - try { - return { - page: top.location.href, - location: top.location.href - }; - } catch (e) { - return { - page: window.location.href, - location: window.location.href - }; - } - } - }, blockTheRequest() { let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); - if (typeof ozRequest == 'boolean' && !ozRequest) { - logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); + if (ozRequest === false) { + logWarn(`Will not allow the auction : ${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); return true; } return false; @@ -843,7 +831,7 @@ export const spec = { } } if (objConfig.hasOwnProperty('ext') && typeof objConfig.ext === 'object') { - if (objConfig.hasOwnProperty('ext')) { + if (ret.hasOwnProperty('ext')) { ret.ext = mergeDeep(ret.ext, objConfig.ext); } else { ret.ext = objConfig.ext; @@ -864,7 +852,7 @@ export const spec = { let skippable = deepAccess(objConfig, 'skippable', null); if (skippable == null) { if (addIfMissing && !objRet.hasOwnProperty('skip')) { - objRet.skip = skippable ? 1 : 0; + objRet.skip = 0; } } else { objRet.skip = skippable ? 1 : 0; @@ -915,8 +903,8 @@ export function injectAdIdsIntoAllBidResponses(seatbid) { return seatbid; } export function checkDeepArray(Arr) { - if (Array.isArray(Arr)) { - if (Array.isArray(Arr[0])) { + if (isArray(Arr)) { + if (isArray(Arr[0])) { return Arr[0]; } else { return Arr; @@ -1047,12 +1035,12 @@ export function getWidthAndHeightFromVideoObject(objVideo) { logInfo('getWidthAndHeightFromVideoObject found nested array inside playerSize.', playerSize[0]); playerSize = playerSize[0]; if (typeof playerSize[0] !== 'number' && typeof playerSize[0] !== 'string') { - logInfo('getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); + logError('getWidthAndHeightFromVideoObject found non-number/string type inside the INNER array in playerSize. This is totally wrong - cannot continue.', playerSize[0]); return null; } } if (playerSize.length !== 2) { - logInfo('getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); + logError('getWidthAndHeightFromVideoObject found playerSize with length of ' + playerSize.length + '. This is totally wrong - cannot continue.'); return null; } return ({'w': playerSize[0], 'h': playerSize[1]}); @@ -1085,7 +1073,7 @@ function getPlayerSizeFromObject(objVideo) { } function newRenderer(adUnitCode, rendererOptions = {}) { let isLoaded = window.ozoneVideo; - logInfo(`newRenderer going to set loaded to ${isLoaded ? 'true' : 'false'}`); + logInfo(`newRenderer will set loaded to ${isLoaded ? 'true' : 'false'}`); const renderer = Renderer.install({ url: spec.getRendererUrl(), config: rendererOptions, @@ -1095,16 +1083,15 @@ function newRenderer(adUnitCode, rendererOptions = {}) { try { renderer.setRender(outstreamRender); } catch (err) { - logError('Prebid Error when calling setRender on renderer', renderer, err); + logError('Prebid Error calling renderer.setRender', renderer, err); } logInfo('returning renderer object'); return renderer; } function outstreamRender(bid) { - logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid = (first static, then reference)'); - logInfo(deepClone(spec.getLoggableBidObject(bid))); + logInfo('outstreamRender got', deepClone(spec.getLoggableBidObject(bid))); bid.renderer.push(() => { - logInfo('Going to execute window.ozoneVideo.outstreamRender'); + logInfo('outstreamRender: Going to execute window.ozoneVideo.outstreamRender'); window.ozoneVideo.outstreamRender(bid); }); } diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index f504af91df5..b2b494b04c6 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -7,6 +7,12 @@ import * as utils from '../../../src/utils.js'; import {deepSetValue} from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; +spec.getGetParametersAsObject = function() { + return { + page: 'https://www.ardm.io/sometestPage/?qsParam1=123', + location: 'https://www.ardm.io/sometestPage/?qsParam1=123' + }; +} var validBidRequests = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -2748,18 +2754,18 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest); const data = JSON.parse(request.data); expect(data.ext.ozone).to.haveOwnProperty('test_rw'); - config.setConfig({'ozone': {'kvpPrefix': null}}); + config.resetConfig(); spec.propertyBag.whitelabel = null; }); it('handles an alias ', function () { spec.propertyBag.whitelabel = null; - config.setConfig({'lmc': {'kvpPrefix': 'test'}}); + config.setConfig({'venatus': {'kvpPrefix': 've'}}); let br = JSON.parse(JSON.stringify(validBidRequests)); - br[0]['bidder'] = 'lmc'; + br[0]['bidder'] = 'venatus'; const request = spec.buildRequests(br, validBidderRequest); const data = JSON.parse(request.data); - expect(data.ext.lmc).to.haveOwnProperty('test_rw'); - config.setConfig({'lmc': {'kvpPrefix': null}}); // I cant remove the key so set the value to null + expect(data.ext.venatus).to.haveOwnProperty('ve_rw'); + config.resetConfig(); spec.propertyBag.whitelabel = null; }); it('should use oztestmode GET value if set', function() { @@ -2772,29 +2778,14 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); - it('should pass through GET params if present: ozf, ozpf, ozrp, ozip', function() { + it('should ignore these GET params if present (removed 202410): ozf, ozpf, ozrp, ozip', function() { var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { - return {ozf: '1', ozpf: '0', ozrp: '2', ozip: '123'}; + return {ozf: '1', ozpf: '10', ozrp: '2', ozip: '123'}; }; const request = specMock.buildRequests(validBidRequests, validBidderRequest); const data = JSON.parse(request.data); - expect(data.ext.ozone.ozf).to.equal(1); - expect(data.ext.ozone.ozpf).to.equal(0); - expect(data.ext.ozone.ozrp).to.equal(2); - expect(data.ext.ozone.ozip).to.equal(123); - }); - it('should pass through GET params if present: ozf, ozpf, ozrp, ozip with alternative values', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {ozf: 'false', ozpf: 'true', ozrp: 'xyz', ozip: 'hello'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.ozone.ozf).to.equal(0); - expect(data.ext.ozone.ozpf).to.equal(1); - expect(data.ext.ozone).to.not.haveOwnProperty('ozrp'); - expect(data.ext.ozone).to.not.haveOwnProperty('ozip'); + expect(data.ext.ozone).to.not.have.any.keys('zf', 'ozpf', 'ozrp', 'ozip'); }); it('should use oztestmode GET value if set, even if there is no customdata in config', function() { var specMock = utils.deepClone(spec); @@ -2950,24 +2941,49 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.regs).to.include.keys('coppa'); expect(payload.regs.coppa).to.equal(1); + config.resetConfig(); }); it('should pick up the config value of coppa & only set it in the request if its true', function () { config.setConfig({'coppa': false}); const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); const payload = JSON.parse(request.data); expect(utils.deepAccess(payload, 'regs.coppa')).to.be.undefined; + config.resetConfig(); }); it('should handle oz_omp_floor correctly', function () { config.setConfig({'ozone': {'oz_omp_floor': 1.56}}); const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); const payload = JSON.parse(request.data); expect(utils.deepAccess(payload, 'ext.ozone.oz_omp_floor')).to.equal(1.56); + config.resetConfig(); }); it('should ignore invalid oz_omp_floor values', function () { config.setConfig({'ozone': {'oz_omp_floor': '1.56'}}); const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); const payload = JSON.parse(request.data); expect(utils.deepAccess(payload, 'ext.ozone.oz_omp_floor')).to.be.undefined; + config.resetConfig(); + }); + it('should handle a valid ozFloor string value in the adunit correctly', function () { + let cloneBidRequests = JSON.parse(JSON.stringify(validBidRequests)); + cloneBidRequests[0].params.ozFloor = '0.1234'; // string or float - doesnt matter + const request = spec.buildRequests(cloneBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'imp.0.ext.ozone.ozFloor')).to.equal(0.1234); + }); + it('should handle a valid ozFloor float value in the adunit correctly', function () { + let cloneBidRequests = JSON.parse(JSON.stringify(validBidRequests)); + cloneBidRequests[0].params.ozFloor = 0.1234; // string or float - doesnt matter + const request = spec.buildRequests(cloneBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'imp.0.ext.ozone.ozFloor')).to.equal(0.1234); + }); + it('should ignore an invalid ozFloor string value in the adunit correctly', function () { + let cloneBidRequests = JSON.parse(JSON.stringify(validBidRequests)); + cloneBidRequests[0].params.ozFloor = 'this is no good!'; // string or float - doesnt matter + const request = spec.buildRequests(cloneBidRequests, validBidderRequest); + const payload = JSON.parse(request.data); + expect(utils.deepAccess(payload, 'imp.0.ext.ozone.ozFloor', null)).to.be.null; }); it('should should contain a unique page view id in the auction request which persists across calls', function () { let request = spec.buildRequests(validBidRequests, validBidderRequest); @@ -3098,6 +3114,30 @@ describe('ozone Adapter', function () { expect(utils.deepAccess(payload, 'imp.0.floor.banner.currency')).to.equal('USD'); expect(utils.deepAccess(payload, 'imp.0.floor.banner.floor')).to.equal(0.8); }); + it(' (getFloorObjectForAuction) should handle advanced/custom floor config function correctly (note you cant fully test floor functionality because it relies on the floor module - only our code that interacts with it; we must extract the first w/h pair)', function () { + let testBidObject = { + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + playerSize: [[640, 360]] + }, + native: { + image: { + sizes: [[300, 250], [640, 480]] + } + } + }, + getFloor: function(obj) { + return obj.size; // we just want to look at the size that was sent + } + }; + let floorObject = spec.getFloorObjectForAuction(testBidObject); + expect(floorObject.banner).to.deep.equal([300, 250]); + expect(floorObject.video).to.deep.equal([640, 360]); + expect(floorObject.native).to.deep.equal([300, 250]); + }); it('handles schain object in each bidrequest (will be the same in each br)', function () { let br = JSON.parse(JSON.stringify(validBidRequests)); let schainConfigObject = { @@ -3178,6 +3218,27 @@ describe('ozone Adapter', function () { expect(payload.imp[0].ext.ozone.transactionId).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); config.resetConfig(); }); + it('should handle ortb2 device data', function () { + const bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }, + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.device).to.deep.equal(bidderRequest.ortb2.device); + }); }); describe('interpretResponse', function () { beforeEach(function () { @@ -3279,6 +3340,14 @@ describe('ozone Adapter', function () { expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr')).to.equal(1); expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid')).to.equal('ZjbsYE1q'); }); + it('Alias venatus: should handle ext.bidder.venatus.floor correctly, setting flr & rid as necessary', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + let vres = JSON.parse(JSON.stringify(validResponse)); + vres.body.seatbid[0].bid[0].ext.bidder.ozone = {floor: 1, ruleId: 'ZjbsYE1q'}; + const result = spec.interpretResponse(vres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_flr')).to.equal(1); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_rid')).to.equal('ZjbsYE1q'); + }); it('should handle ext.bidder.ozone.floor correctly, inserting 0 as necessary', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest); let vres = JSON.parse(JSON.stringify(validResponse)); @@ -3406,6 +3475,50 @@ describe('ozone Adapter', function () { expect(result).to.be.an('object'); expect(result.fledgeAuctionConfigs[0]['impid']).to.equal('1'); }); + it('should add labels in the adserver request if they are present in the auction response', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid.push(JSON.parse(JSON.stringify(validres.body.seatbid[0]))); // add another bidder + validres.body.seatbid[1].seat = 'marktest'; + validres.body.seatbid[1].bid[0].ext.prebid.labels = ['b1', 'b2', 'b3']; + validres.body.seatbid[1].bid[0].price = 10; // will win + validres.body.seatbid[1].bid[1].price = 0; // will lose + validres.body.seatbid[0].bid[0].ext.prebid.labels = ['bid1label1', 'bid1label2', 'bid1label3']; + validres.body.seatbid[0].bid[1].ext.prebid.labels = ['bid2label']; + const result = spec.interpretResponse(validres, request); + expect(result.length).to.equal(4); // 4 bids will be returned; 2 from each bidder. All will have the winning keys attached. + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_winner')).to.equal('marktest'); // the first bid + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_labels')).to.equal('b1,b2,b3'); // the winner + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_labels')).to.equal('bid1label1,bid1label2,bid1label3'); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_winner')).to.equal('appnexus'); // the second bid + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_appnexus_labels')).to.equal('bid2label'); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_labels')).to.equal('bid2label'); // the second adslot winning label + expect(utils.deepAccess(result[2].adserverTargeting, 'oz_labels')).to.equal('b1,b2,b3'); // we're back to the first of the 2 bids again + expect(utils.deepAccess(result[3].adserverTargeting, 'oz_labels')).to.equal('bid2label'); // the second adslot winning label + }); + it('should not add labels in the adserver request if they are present in the auction response when config contains ozone.enhancedAdserverTargeting', function () { + config.setConfig({'ozone': {'enhancedAdserverTargeting': false}}); + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid.push(JSON.parse(JSON.stringify(validres.body.seatbid[0]))); // add another bidder + validres.body.seatbid[1].seat = 'marktest'; + validres.body.seatbid[1].bid[0].ext.prebid.labels = ['b1', 'b2', 'b3']; + validres.body.seatbid[1].bid[0].price = 10; // will win + validres.body.seatbid[1].bid[1].price = 0; // will lose + validres.body.seatbid[0].bid[0].ext.prebid.labels = ['bid1label1', 'bid1label2', 'bid1label3']; + validres.body.seatbid[0].bid[1].ext.prebid.labels = ['bid2label']; + const result = spec.interpretResponse(validres, request); + expect(result.length).to.equal(4); // 4 bids will be returned; 2 from each bidder. All will have the winning keys attached. + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_winner')).to.equal('marktest'); // the first bid + expect(result[0].adserverTargeting).to.not.have.property('oz_labels'); + expect(result[0].adserverTargeting).to.not.have.property('oz_appnexus_labels'); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_winner')).to.equal('appnexus'); // the second bid + expect(result[1].adserverTargeting).to.not.have.property('oz_appnexus_labels'); + expect(result[1].adserverTargeting).to.not.have.property('oz_labels'); // the second adslot winning label + expect(result[2].adserverTargeting).to.not.have.property('oz_labels'); // we're back to the first of the 2 bids again + expect(result[3].adserverTargeting).to.not.have.property('oz_labels'); // the second adslot winning label + config.resetConfig(); + }); }); describe('userSyncs', function () { it('should fail gracefully if no server response', function () { From 5216f367ee6ea65a3fbf95d081b8ec10b3fe7b3e Mon Sep 17 00:00:00 2001 From: John Ivan Bauzon Date: Thu, 20 Feb 2025 22:28:35 +0800 Subject: [PATCH 0938/1097] GumGum Bid Adapter: Send content url and additional vid params (#12741) * ADTS-530-send-content-url-from-prebid-adapter-to-hbid-endpoint * added tests for new params and converted array params to strings * changed comment for rebuild * added support for playbackmethod for video ads * added support for playbackend * modified curl to only be added if present --------- Co-authored-by: John Bauzon --- modules/gumgumBidAdapter.js | 25 ++++++++++++++-- test/spec/modules/gumgumBidAdapter_spec.js | 33 +++++++++++++++++++--- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 6b11c16c425..fa117137e86 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -190,7 +190,12 @@ function _getVidParams(attributes) { placement: pt, plcmt, protocols = [], - playerSize = [] + playerSize = [], + skip, + api, + mimes, + playbackmethod, + playbackend: pbe } = attributes; const sizes = parseSizesInput(playerSize); const [viw, vih] = sizes[0] && sizes[0].split('x'); @@ -208,12 +213,24 @@ function _getVidParams(attributes) { pt, pr, viw, - vih + vih, + skip, + pbe }; - // Add vplcmt property to the result object if plcmt is available + if (plcmt !== undefined && plcmt !== null) { result.vplcmt = plcmt; } + if (api && api.length) { + result.api = api.join(','); + } + if (mimes && mimes.length) { + result.mimes = mimes.join(','); + } + if (playbackmethod && playbackmethod.length) { + result.pbm = playbackmethod.join(','); + } + return result; } @@ -416,6 +433,8 @@ function buildRequests(validBidRequests, bidderRequest) { } if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site) { setIrisId(data, bidderRequest.ortb2.site, params); + const curl = bidderRequest.ortb2.site.content?.url; + if (curl) data.curl = curl; } if (params.iriscat && typeof params.iriscat === 'string') { data.iriscat = params.iriscat; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 8202d5f4f4f..9318b33697d 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -110,7 +110,8 @@ describe('gumgumAdapter', function () { segtax: 500, cids: ['iris_c73g5jq96mwso4d8'] } - }] + }], + url: 'http://pub.com/news', }, page: 'http://pub.com/news', ref: 'http://google.com', @@ -286,7 +287,11 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request], bidderRequest)[0]; expect(bidRequest.data).to.have.property('irisid', 'iris_c73g5jq96mwso4d8'); }); - + it('should set the curl param if present', function() { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data).to.have.property('curl', 'http://pub.com/news'); + }); it('should not set the iriscat param when not found', function () { const request = { ...bidRequests[0] } const bidRequest = spec.buildRequests([request])[0]; @@ -517,7 +522,12 @@ describe('gumgumAdapter', function () { startdelay: 1, placement: 123456, plcmt: 3, - protocols: [1, 2] + protocols: [1, 2], + skip: 1, + api: [1, 2], + mimes: ['video/mp4', 'video/webm'], + playbackmethod: [1, 2], + playbackend: 2 }; const request = Object.assign({}, bidRequests[0]); delete request.params; @@ -539,6 +549,11 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.pr).to.eq(videoVals.protocols.join(',')); expect(bidRequest.data.viw).to.eq(videoVals.playerSize[0].toString()); expect(bidRequest.data.vih).to.eq(videoVals.playerSize[1].toString()); + expect(bidRequest.data.skip).to.eq(videoVals.skip); + expect(bidRequest.data.api).to.eq(videoVals.api.join(',')); + expect(bidRequest.data.mimes).to.eq(videoVals.mimes.join(',')); + expect(bidRequest.data.pbm).to.eq(videoVals.playbackmethod.join(',')); + expect(bidRequest.data.pbe).to.eq(videoVals.playbackend); }); it('should add parameters associated with invideo if invideo request param is found', function () { const inVideoVals = { @@ -550,7 +565,12 @@ describe('gumgumAdapter', function () { startdelay: 1, placement: 123456, plcmt: 3, - protocols: [1, 2] + protocols: [1, 2], + skip: 1, + api: [1, 2], + mimes: ['video/mp4', 'video/webm'], + playbackmethod: [6], + playbackend: 1 }; const request = Object.assign({}, bidRequests[0]); delete request.params; @@ -572,6 +592,11 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.pr).to.eq(inVideoVals.protocols.join(',')); expect(bidRequest.data.viw).to.eq(inVideoVals.playerSize[0].toString()); expect(bidRequest.data.vih).to.eq(inVideoVals.playerSize[1].toString()); + expect(bidRequest.data.skip).to.eq(inVideoVals.skip); + expect(bidRequest.data.api).to.eq(inVideoVals.api.join(',')); + expect(bidRequest.data.mimes).to.eq(inVideoVals.mimes.join(',')); + expect(bidRequest.data.pbm).to.eq(inVideoVals.playbackmethod.join(',')); + expect(bidRequest.data.pbe).to.eq(inVideoVals.playbackend); }); it('should not add additional parameters depending on params field', function () { const request = spec.buildRequests(bidRequests)[0]; From a34418895d4106f175a8f53b5615d9dfb176774a Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:37:58 +0200 Subject: [PATCH 0939/1097] Added support for IAB segtax 7 in Rubicon bid adapter; Added unit test to verify segtax 7 functionality (#12794) --- modules/rubiconBidAdapter.js | 2 +- test/spec/modules/rubiconBidAdapter_spec.js | 23 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 0fa96133e10..e7450c057b7 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -922,7 +922,7 @@ function applyFPD(bidRequest, mediaType, data) { const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); const dsa = deepAccess(fpd, 'regs.ext.dsa'); - const SEGTAX = {user: [4], site: [1, 2, 5, 6]}; + const SEGTAX = {user: [4], site: [1, 2, 5, 6, 7]}; const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key, parentName) { if (key === 'data' && Array.isArray(prop)) { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 251253a0a55..b175f645adf 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2916,6 +2916,29 @@ describe('the rubicon adapter', function () { expect(slotParams['tg_v.tax404']).is.equal(undefined); }); + it('should support IAB segtax 7 in site segments', () => { + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.refererInfo = {domain: 'bob'}; + config.setConfig({ + rubicon: { + sendUserSegtax: [4], + sendSiteSegtax: [1, 2, 5, 6, 7] + } + }); + localBidderRequest.ortb2.site = { + content: { + data: [{ + ext: { + segtax: '7' + }, + segment: [{id: 8}, {id: 9}] + }] + } + }; + const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); + expect(slotParams['tg_i.tax7']).to.equal('8,9'); + }); + it('should add p_site.mobile if mobile is a number in ortb2.site', function () { // Set up a bidRequest with mobile property as a number const localBidderRequest = Object.assign({}, bidderRequest); From ebc06e2c4bf39c0071d5d91a7a57b1e9318c8585 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 20 Feb 2025 18:08:16 +0000 Subject: [PATCH 0940/1097] Prebid 9.31.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 50ce5492989..b35dc0fd6cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.31.0-pre", + "version": "9.31.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.31.0-pre", + "version": "9.31.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index a9b0d82a6aa..80de5037ce2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.31.0-pre", + "version": "9.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 34e0c844f00d1af19f4306c7632a33a31093bcc4 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 20 Feb 2025 18:08:16 +0000 Subject: [PATCH 0941/1097] Increment version to 9.32.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b35dc0fd6cd..e5c33313b9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.31.0", + "version": "9.32.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.31.0", + "version": "9.32.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 80de5037ce2..35d4e7e1a06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.31.0", + "version": "9.32.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 8813b404a59da1ac7a5d908d088c9c4561b8febe Mon Sep 17 00:00:00 2001 From: dmytro-po Date: Thu, 20 Feb 2025 20:12:57 +0200 Subject: [PATCH 0942/1097] Bugfix for GAM cmd (#12795) --- modules/intentIqIdSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 48210e49c19..fcd0d53f936 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -167,7 +167,7 @@ function tryParse(data) { * @param {string} userGroup - The A/B testing group assigned to the user (e.g., 'A', 'B', or a custom value). */ export function setGamReporting(gamObjectReference, gamParameterName, userGroup) { - if (isPlainObject(gamObjectReference) && Array.isArray(gamObjectReference.cmd)) { + if (isPlainObject(gamObjectReference) && gamObjectReference.cmd) { gamObjectReference.cmd.push(() => { gamObjectReference .pubads() From 13b18fa8333f6541d738ccd5aa46b3036d13b1f3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 20 Feb 2025 10:41:16 -0800 Subject: [PATCH 0943/1097] Core: suspend auctions during prerendering (#12763) * Core: suspend auctions during prerendering * Delay only auctions by default * add option to delay queue --------- Co-authored-by: Patrick McCann --- src/prebid.js | 10 +++--- src/utils/prerendering.js | 22 ++++++++++++ test/spec/unit/pbjs_api_spec.js | 27 ++++++++++---- test/spec/utils/prerendering_spec.js | 53 ++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 src/utils/prerendering.js create mode 100644 test/spec/utils/prerendering_spec.js diff --git a/src/prebid.js b/src/prebid.js index 4bfa7745680..684464eb2b1 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -50,6 +50,7 @@ import {getHighestCpm} from './utils/reducers.js'; import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js'; import { ORTB_BANNER_PARAMS } from './banner.js'; import { BANNER, VIDEO } from './mediaTypes.js'; +import {delayIfPrerendering} from './utils/prerendering.js'; import { newBidder } from './adapters/bidderFactory.js'; const pbjsInstance = getGlobal(); @@ -570,13 +571,14 @@ pbjsInstance.requestBids = (function() { }) }, 'requestBids'); - return wrapHook(delegate, function requestBids(req = {}) { + return wrapHook(delegate, delayIfPrerendering(() => !config.getConfig('allowPrerendering'), function requestBids(req = {}) { // unlike the main body of `delegate`, this runs before any other hook has a chance to; // it's also not restricted in its return value in the way `async` hooks are. // if the request does not specify adUnits, clone the global adUnit array; // otherwise, if the caller goes on to use addAdUnits/removeAdUnits, any asynchronous logic // in any hook might see their effects. + let adUnits = req.adUnits || pbjsInstance.adUnits; req.adUnits = (isArray(adUnits) ? adUnits.slice() : [adUnits]); @@ -585,7 +587,7 @@ pbjsInstance.requestBids = (function() { req.defer = defer({promiseFactory: (r) => new Promise(r)}) delegate.call(this, req); return req.defer.promise; - }); + })); })(); export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, ttlBuffer, adUnitCodes, labels, auctionId, ortb2Fragments, metrics, defer } = {}) { @@ -1026,13 +1028,13 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -pbjsInstance.processQueue = function () { +pbjsInstance.processQueue = delayIfPrerendering(() => getGlobal().delayPrerendering, function () { pbjsInstance.que.push = pbjsInstance.cmd.push = quePush; insertLocatorFrame(); hook.ready(); processQueue(pbjsInstance.que); processQueue(pbjsInstance.cmd); -}; +}); /** * @alias module:pbjs.triggerBilling diff --git a/src/utils/prerendering.js b/src/utils/prerendering.js new file mode 100644 index 00000000000..b89b8d895eb --- /dev/null +++ b/src/utils/prerendering.js @@ -0,0 +1,22 @@ +import {logInfo} from '../utils.js'; + +/** + * Returns a wrapper around fn that delays execution until the page if activated, if it was prerendered and isDelayEnabled returns true. + * https://developer.chrome.com/docs/web-platform/prerender-pages + */ +export function delayIfPrerendering(isDelayEnabled, fn) { + return function () { + if (document.prerendering && isDelayEnabled()) { + const that = this; + const args = Array.from(arguments); + return new Promise((resolve) => { + document.addEventListener('prerenderingchange', () => { + logInfo(`Auctions were suspended while page was prerendering`) + resolve(fn.apply(that, args)) + }, {once: true}) + }) + } else { + return Promise.resolve(fn.apply(this, arguments)); + } + } +} diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 1cb87440e80..378f15e35bf 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -244,14 +244,27 @@ describe('Unit: Prebid Module', function () { }); ['cmd', 'que'].forEach(prop => { - it(`should patch ${prop}.push`, () => { - $$PREBID_GLOBAL$$[prop].push = false; - $$PREBID_GLOBAL$$.processQueue(); - let ran = false; - $$PREBID_GLOBAL$$[prop].push(() => { ran = true; }); - expect(ran).to.be.true; + describe(`using .${prop}`, () => { + let queue, ran; + beforeEach(() => { + ran = false; + queue = $$PREBID_GLOBAL$$[prop] = []; + }); + after(() => { + $$PREBID_GLOBAL$$.processQueue(); + }) + + function pushToQueue() { + queue.push(() => { ran = true }); + } + + it(`should patch .push`, () => { + $$PREBID_GLOBAL$$.processQueue(); + pushToQueue(); + expect(ran).to.be.true; + }); }) - }) + }); }) describe('and global adUnits', () => { diff --git a/test/spec/utils/prerendering_spec.js b/test/spec/utils/prerendering_spec.js new file mode 100644 index 00000000000..409edb93747 --- /dev/null +++ b/test/spec/utils/prerendering_spec.js @@ -0,0 +1,53 @@ +import {delayIfPrerendering} from '../../../src/utils/prerendering.js'; + +describe('delayIfPrerendering', () => { + let sandbox, enabled, ran; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + enabled = true; + ran = false; + }); + + afterEach(() => { + sandbox.restore(); + }) + + const delay = delayIfPrerendering(() => enabled, () => { + ran = true; + }) + + it('should not delay if page is not prerendering', () => { + delay(); + expect(ran).to.be.true; + }) + + describe('when page is prerendering', () => { + before(() => { + if (!('prerendering' in document)) { + document.prerendering = null; + after(() => { + delete document.prerendering; + }) + } + }) + beforeEach(() => { + sandbox.stub(document, 'prerendering').get(() => true); + }); + function prerenderingDone() { + document.dispatchEvent(new Event('prerenderingchange')); + } + + it('should run fn only after prerenderingchange event', async () => { + delay(); + expect(ran).to.be.false; + prerenderingDone(); + expect(ran).to.be.true; + }); + + it('should not delay if not enabled', () => { + enabled = false; + delay(); + expect(ran).to.be.true; + }) + }) +}) From 866b18e74fc6eff5d5380504a8f3fc8d4afa4fdc Mon Sep 17 00:00:00 2001 From: Alexandr Kim <47887567+alexandr-kim-vl@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:25:43 +0500 Subject: [PATCH 0944/1097] SemantIQ RTD Provider: initial release (#12668) Co-authored-by: Alexandr Kim --- modules/.submodules.json | 1 + modules/semantiqRtdProvider.js | 222 ++++++++++ modules/semantiqRtdProvider.md | 46 ++ test/spec/modules/semantiqRtdProvider_spec.js | 400 ++++++++++++++++++ 4 files changed, 669 insertions(+) create mode 100644 modules/semantiqRtdProvider.js create mode 100644 modules/semantiqRtdProvider.md create mode 100644 test/spec/modules/semantiqRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 9e5b28bd2d7..24cb831d2b8 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -102,6 +102,7 @@ "qortexRtdProvider", "reconciliationRtdProvider", "relevadRtdProvider", + "semantiqRtdProvider", "sirdataRtdProvider", "symitriDapRtdProvider", "timeoutRtdProvider", diff --git a/modules/semantiqRtdProvider.js b/modules/semantiqRtdProvider.js new file mode 100644 index 00000000000..dd79ac3c96f --- /dev/null +++ b/modules/semantiqRtdProvider.js @@ -0,0 +1,222 @@ +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { getWindowLocation, logError, logInfo, logWarn, mergeDeep } from '../src/utils.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'semantiq'; + +const LOG_PREFIX = '[SemantIQ RTD Module]: '; +const KEYWORDS_URL = 'https://api.adnz.co/api/ws-semantiq/page-keywords'; +const STORAGE_KEY = `adnz_${SUBMODULE_NAME}`; +const AUDIENZZ_COMPANY_ID = 1; +const REQUIRED_TENANT_IDS = [AUDIENZZ_COMPANY_ID]; +const AUDIENZZ_GLOBAL_VENDOR_ID = 783; + +const DEFAULT_TIMEOUT = 1000; + +export const storage = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: SUBMODULE_NAME, +}); + +/** + * Gets SemantIQ keywords from local storage. + * @param {string} pageUrl + * @returns {Object.} + */ +const getStorageKeywords = (pageUrl) => { + try { + const storageValue = JSON.parse(storage.getDataFromLocalStorage(STORAGE_KEY)); + + if (storageValue?.url === pageUrl) { + return storageValue.keywords; + } + + return null; + } catch (error) { + logError('Unable to get SemantiQ keywords from local storage', error); + + return null; + } +}; + +/** + * Gets URL of the current page. + * @returns {string} + */ +const getPageUrl = () => getWindowLocation().href; + +/** + * Gets tenant IDs based on the customer company ID + * @param {number | number[] | undefined} companyId + * @returns {number[]} + */ +const getTenantIds = (companyId) => { + if (!companyId) { + return REQUIRED_TENANT_IDS; + } + + const companyIdArray = Array.isArray(companyId) ? companyId : [companyId]; + + return [...REQUIRED_TENANT_IDS, ...companyIdArray]; +}; + +/** + * Gets keywords from cache or SemantIQ service. + * @param {Object} params + * @returns {Promise>} + */ +const getKeywords = (params) => new Promise((resolve, reject) => { + const pageUrl = getPageUrl(); + const storageKeywords = getStorageKeywords(pageUrl); + + if (storageKeywords) { + return resolve(storageKeywords); + } + + const { companyId } = params; + const tenantIds = getTenantIds(companyId); + const searchParams = new URLSearchParams(); + + searchParams.append('url', pageUrl); + searchParams.append('tenantIds', tenantIds.join(',')); + + const requestUrl = `${KEYWORDS_URL}?${searchParams.toString()}`; + + const callbacks = { + success(responseText, response) { + try { + if (response.status !== 200) { + throw new Error('Invalid response status'); + } + + const data = JSON.parse(responseText); + + if (!data) { + throw new Error('Failed to parse the response'); + } + + storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify({ url: pageUrl, keywords: data })); + resolve(data); + } catch (error) { + reject(error); + } + }, + error(error) { + reject(error); + } + } + + ajax(requestUrl, callbacks); +}); + +/** + * Converts a single key-value pair to an ORTB keyword string. + * @param {string} key + * @param {string | string[]} value + * @returns {string} + */ +export const convertSemantiqKeywordToOrtb = (key, value) => { + if (!value || !value.length) { + return ''; + } + + if (Array.isArray(value)) { + return value.map((valueItem) => `${key}=${valueItem}`).join(','); + } + + return `${key}=${value}`; +}; + +/** + * Converts SemantIQ keywords to ORTB format. + * @param {Object.} keywords + * @returns {string} + */ +export const getOrtbKeywords = (keywords) => Object.entries(keywords).reduce((acc, entry) => { + const [key, values] = entry; + const ortbKeywordString = convertSemantiqKeywordToOrtb(key, values); + + return ortbKeywordString ? [...acc, ortbKeywordString] : acc; +}, []).join(','); + +/** + * Module init + * @param {Object} config + * @param {Object} userConsent + * @return {boolean} + */ +const init = (config, userConsent) => true; + +/** + * Receives real-time data from SemantIQ service. + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} moduleConfig + */ +const getBidRequestData = ( + reqBidsConfigObj, + onDone, + moduleConfig, +) => { + let isDone = false; + + const { params = {} } = moduleConfig || {}; + const { timeout = DEFAULT_TIMEOUT } = params; + + try { + logInfo(LOG_PREFIX, { reqBidsConfigObj }); + + const { adUnits = [] } = reqBidsConfigObj; + + if (!adUnits.length) { + logWarn(LOG_PREFIX, 'No ad units found in the request'); + isDone = true; + onDone(); + } + + getKeywords(params) + .then((keywords) => { + const ortbKeywords = getOrtbKeywords(keywords); + const siteKeywords = reqBidsConfigObj.ortb2Fragments?.global?.site?.keywords; + const updatedGlobalOrtb = { site: { keywords: [siteKeywords, ortbKeywords].filter(Boolean).join(',') } }; + + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, updatedGlobalOrtb); + }) + .catch((error) => { + logError(LOG_PREFIX, error); + }) + .finally(() => { + isDone = true; + onDone(); + }); + } catch (error) { + logError(LOG_PREFIX, error); + isDone = true; + onDone(); + } + + setTimeout(() => { + if (!isDone) { + logWarn(LOG_PREFIX, 'Timeout exceeded'); + isDone = true; + onDone(); + } + }, timeout); +} + +/** @type {RtdSubmodule} */ +export const semantiqRtdSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData, + init, + gvlid: AUDIENZZ_GLOBAL_VENDOR_ID, +}; + +submodule(MODULE_NAME, semantiqRtdSubmodule); diff --git a/modules/semantiqRtdProvider.md b/modules/semantiqRtdProvider.md new file mode 100644 index 00000000000..8949ad00ece --- /dev/null +++ b/modules/semantiqRtdProvider.md @@ -0,0 +1,46 @@ +# Overview + +**Module Name:** Semantiq Rtd Provider +**Module Type:** Rtd Provider +**Maintainer:** [Audienzz](https://audienzz.com) + +## Description + +This module retrieves real-time data from the SemantIQ service and populates ORTB data. + +You need to obtain a company ID from [Audienzz](https://audienzz.com) for the module to function properly. Contact [service@audienzz.ch](mailto:service@audienzz.ch) for details. + +## Integration + +1. Include the module into your `Prebid.js` build. + + ```sh + gulp build --modules='rtdModule,semantiqRtdProvider,...' + ``` + +1. Configure the module via `pbjs.setConfig`. + + ```js + pbjs.setConfig({ + ... + realTimeData: { + dataProviders: [ + { + name: 'semantiq', + waitForIt: true, + params: { + companyId: 12345, + timeout: 1000, + }, + }, + ], + }, + }); + ``` + +## Parameters + +| Name | Required | Description | Type | Default value | Example | +| ---------- | -------- | ----------------------------------------------------------------- | ------------------ | ------------- | --------------------- | +| companyId | No | Company ID or IDs obtained from [Audienzz](https://audienzz.com). | number \| number[] | - | 12345 | +| timeout | No | The maximum time to wait for a response in milliseconds. | number | 1000 | 3000 | diff --git a/test/spec/modules/semantiqRtdProvider_spec.js b/test/spec/modules/semantiqRtdProvider_spec.js new file mode 100644 index 00000000000..16f82b8c274 --- /dev/null +++ b/test/spec/modules/semantiqRtdProvider_spec.js @@ -0,0 +1,400 @@ +import { convertSemantiqKeywordToOrtb, getOrtbKeywords, semantiqRtdSubmodule, storage } from '../../../modules/semantiqRtdProvider'; +import { expect } from 'chai'; +import { server } from '../../mocks/xhr.js'; +import * as utils from '../../../src/utils.js'; + +describe('semantiqRtdProvider', () => { + let clock; + let getDataFromLocalStorageStub; + let getWindowLocationStub; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').returns(null); + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns(new URL('https://example.com/article')); + }); + + afterEach(() => { + clock.restore(); + getDataFromLocalStorageStub.restore(); + getWindowLocationStub.restore(); + }); + + describe('init', () => { + it('returns true on initialization', () => { + const initResult = semantiqRtdSubmodule.init(); + expect(initResult).to.be.true; + }); + }); + + describe('convertSemantiqKeywordToOrtb', () => { + it('converts SemantIQ keywords properly', () => { + expect(convertSemantiqKeywordToOrtb('foo', 'bar')).to.be.equal('foo=bar'); + expect(convertSemantiqKeywordToOrtb('foo', ['bar', 'baz'])).to.be.equal('foo=bar,foo=baz'); + }); + + it('returns an empty string if keyword value is empty', () => { + expect(convertSemantiqKeywordToOrtb('foo', '')).to.be.equal(''); + expect(convertSemantiqKeywordToOrtb('foo', [])).to.be.equal(''); + }); + }); + + describe('getOrtbKeywords', () => { + it('returns an empty string if no keywords are provided', () => { + expect(getOrtbKeywords({})).to.be.equal(''); + }); + + it('converts keywords to ORTB format', () => { + expect(getOrtbKeywords({ foo: 'bar', fizz: ['buzz', 'quz'] })).to.be.equal('foo=bar,fizz=buzz,fizz=quz'); + }); + + it('ignores keywords with no value', () => { + expect(getOrtbKeywords({ foo: 'bar', fizz: ['buzz', 'quz'], baz: '', xyz: [], quz: undefined, buzz: null })).to.be.equal('foo=bar,fizz=buzz,fizz=quz'); + }); + }); + + describe('getBidRequestData', () => { + it('requests data with correct parameters', async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + }; + + semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => undefined, + { params: {} }, + {} + ); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + + expect(requestUrl.host).to.be.equal('api.adnz.co'); + expect(requestUrl.searchParams.get('url')).to.be.equal('https://example.com/article'); + expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('1'); + }); + + it('allows to specify company ID as a parameter', async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + }; + + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => { + onDoneSpy(); + resolve(); + }, + { params: { companyId: 13 } }, + {} + )); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + await promise; + + const requestUrl = new URL(server.requests[0].url); + + expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('1,13'); + }); + + it('allows to specify multiple company IDs as a parameter', async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + }; + + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => { + onDoneSpy(); + resolve(); + }, + { params: { companyId: [13, 23] } }, + {} + )); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + await promise; + + const requestUrl = new URL(server.requests[0].url); + + expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('1,13,23'); + }); + + it('gets keywords from the cache if the data is present in the storage', async () => { + getDataFromLocalStorageStub.returns(JSON.stringify({ url: 'https://example.com/article', keywords: { sentiment: 'negative', ctx_segment: ['C001', 'C002'] } })); + + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: { + global: {}, + } + }; + + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => { + onDoneSpy(); + resolve(); + }, + { params: {} }, + {} + )); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(server.requests).to.have.lengthOf(0); + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal({ site: { keywords: 'sentiment=negative,ctx_segment=C001,ctx_segment=C002' } }); + }); + + it('requests keywords from the server if the URL of the page is different from the cached one', async () => { + getDataFromLocalStorageStub.returns(JSON.stringify({ url: 'https://example.com/article', keywords: { cached: 'true' } })); + getWindowLocationStub.returns(new URL('https://example.com/another-article')); + + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: { + global: {}, + } + }; + + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => { + onDoneSpy(); + resolve(); + }, + { params: {} }, + {} + )); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ server: 'true' }) + ); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal({ site: { keywords: 'server=true' } }); + }); + + it('requests keywords from the server if the cached data is missing in the storage', async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: { + global: {}, + }, + }; + + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => { + onDoneSpy(); + resolve(); + }, + { params: {} }, + {} + )); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ sentiment: 'negative', ctx_segment: ['C001', 'C002'] }) + ); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal({ site: { keywords: 'sentiment=negative,ctx_segment=C001,ctx_segment=C002' } }); + }); + + it('merges ORTB site keywords if they are present', async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: { + global: { + site: { + keywords: 'iab_category=politics', + } + }, + }, + }; + + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData( + reqBidsConfigObj, + () => { + onDoneSpy(); + resolve(); + }, + { params: {} }, + {} + )); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ sentiment: 'negative', ctx_segment: ['C001', 'C002'] }) + ); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal({ site: { keywords: 'iab_category=politics,sentiment=negative,ctx_segment=C001,ctx_segment=C002' } }); + }); + + it("won't modify ortb2 if if no ad units are provided", async () => { + const reqBidsConfigObj = { + adUnits: [], + ortb2Fragments: {} + }; + + const onDoneSpy = sinon.spy(); + + semantiqRtdSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, { params: {} }, {}); + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj).to.deep.equal({ + adUnits: [], + ortb2Fragments: {} + }); + }); + + it("won't modify ortb2 if response is broken", async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {} + }; + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => { + semantiqRtdSubmodule.getBidRequestData(reqBidsConfigObj, () => { + onDoneSpy(); + resolve(); + }, { params: {} }, {}); + }); + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + '{' + ); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj).to.deep.equal({ + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {} + }); + }); + + it("won't modify ortb2 if response status is not 200", async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {} + }; + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => { + semantiqRtdSubmodule.getBidRequestData(reqBidsConfigObj, () => { + onDoneSpy(); + resolve(); + }, { params: {} }, {}); + }); + + server.requests[0].respond( + 204, + { 'Content-Type': 'application/json' }, + ); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj).to.deep.equal({ + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {} + }); + }); + + it("won't modify ortb2 if an error occurs during the request", async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {} + }; + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => { + semantiqRtdSubmodule.getBidRequestData(reqBidsConfigObj, () => { + onDoneSpy(); + resolve(); + }, { params: {} }, {}); + }); + + server.requests[0].respond( + 500, + { 'Content-Type': 'application/json' }, + '{}' + ); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj).to.deep.equal({ + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {}, + }); + }); + + it("won't modify ortb2 if response time hits timeout", async () => { + const reqBidsConfigObj = { + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {}, + }; + const onDoneSpy = sinon.spy(); + + const promise = new Promise((resolve) => semantiqRtdSubmodule.getBidRequestData(reqBidsConfigObj, () => { + onDoneSpy(); + resolve(); + }, { params: { timeout: 500 } }, {})); + + clock.tick(510); + + await promise; + + expect(onDoneSpy.calledOnce).to.be.true; + expect(reqBidsConfigObj).to.deep.equal({ + adUnits: [{ bids: [{ bidder: 'appnexus' }] }], + ortb2Fragments: {}, + }); + }); + }); +}); From 86dccc4f021ee5154bfaa0bfea53ce2591715a1e Mon Sep 17 00:00:00 2001 From: andre-gielow-ttd <124626380+andre-gielow-ttd@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:25:19 -0500 Subject: [PATCH 0945/1097] revert integration type header due to CORS error (#12800) --- modules/ttdBidAdapter.js | 3 --- test/spec/modules/ttdBidAdapter_spec.js | 7 ------- 2 files changed, 10 deletions(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 323c76dd590..e1e86494d1c 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -441,9 +441,6 @@ export const spec = { data: topLevel, options: { withCredentials: true, - customHeaders: { - 'x-integration-type': 1, - }, } }; diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 5f21c9c3235..4580c514609 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -291,13 +291,6 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.publisher.id).to.equal(baseBannerBidRequests[0].params.publisherId); }); - it('sends integration type header', function () { - const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest); - expect(requestBody.options).to.be.not.null; - expect(requestBody.options.customHeaders).to.be.not.null; - expect(requestBody.options.customHeaders['x-integration-type']).to.equal(1); - }); - it('sends placement id in tagid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.imp[0].tagid).to.equal(baseBannerBidRequests[0].params.placementId); From b60dca8c9d1c3300734f73b9132459d4fe0e89fd Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 24 Feb 2025 19:39:05 +0000 Subject: [PATCH 0946/1097] Prebid 9.32.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e5c33313b9c..b39ab459b08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.32.0-pre", + "version": "9.32.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.32.0-pre", + "version": "9.32.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 35d4e7e1a06..f8cb1c1098b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.32.0-pre", + "version": "9.32.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From da0a7fed497ac9f8aa1f310f0b5a11aa0357a9ac Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 24 Feb 2025 19:39:06 +0000 Subject: [PATCH 0947/1097] Increment version to 9.33.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b39ab459b08..4888d2f11a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.32.0", + "version": "9.33.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.32.0", + "version": "9.33.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index f8cb1c1098b..7e1f8368aec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.32.0", + "version": "9.33.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From f86d267a655bee722cac6bafbcc9ae6f35efa305 Mon Sep 17 00:00:00 2001 From: zeeye <56828723+zeeye@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:49:04 +0000 Subject: [PATCH 0948/1097] Mobkoi Analystics Adapter: Initial Implementation (#12648) --- modules/mobkoiAnalyticsAdapter.js | 1338 +++++++++++++++++ modules/mobkoiAnalyticsAdapter.md | 9 + .../modules/mobkoiAnalyticsAdapter_spec.js | 508 +++++++ 3 files changed, 1855 insertions(+) create mode 100644 modules/mobkoiAnalyticsAdapter.js create mode 100644 modules/mobkoiAnalyticsAdapter.md create mode 100644 test/spec/modules/mobkoiAnalyticsAdapter_spec.js diff --git a/modules/mobkoiAnalyticsAdapter.js b/modules/mobkoiAnalyticsAdapter.js new file mode 100644 index 00000000000..93913c19d64 --- /dev/null +++ b/modules/mobkoiAnalyticsAdapter.js @@ -0,0 +1,1338 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { ajax } from '../src/ajax.js'; +import { + logInfo, + logWarn, + logError, + _each, + pick, + triggerPixel, + debugTurnedOn, + mergeDeep, + isEmpty, + deepClone, + deepAccess, +} from '../src/utils.js'; + +const BIDDER_CODE = 'mobkoi'; +const analyticsType = 'endpoint'; +const GVL_ID = 898; +/** + * !IMPORTANT: Must match the value in the mobkoiBidAdapter.js + * The name of the parameter that the publisher can use to specify the ad server endpoint. + */ +const PARAM_NAME_AD_SERVER_BASE_URL = 'adServerBaseUrl'; + +/** + * Order by events lifecycle + */ +const { + // Order events + AUCTION_INIT, + BID_RESPONSE, + AUCTION_END, + AD_RENDER_SUCCEEDED, + BID_WON, + BIDDER_DONE, + + // Error events (Not in order) + AUCTION_TIMEOUT, + NO_BID, + BID_REJECTED, + BIDDER_ERROR, + AD_RENDER_FAILED, +} = EVENTS; + +const CUSTOM_EVENTS = { + BID_LOSS: 'bidLoss', +}; + +export const DEBUG_EVENT_LEVELS = { + info: 'info', + warn: 'warn', + error: 'error', +}; + +/** + * Some fields contain large data that are not useful for debugging. This + * constant contains the fields that should be omitted from the payload and in + * error messages. + */ +const COMMON_FIELDS_TO_OMIT = ['ad', 'adm']; + +export class LocalContext { + /** + * A map of impression ID (ORTB terms) to BidContext object + */ + bidContexts = {}; + + /** + * Shouldn't be accessed directly. Use getPayloadByImpId method instead. + * Payload are indexed by impression ID. + */ + _impressionPayloadCache = { + // [impid]: { ... } + }; + /** + * The payload that is common to all bid contexts. The payload will be + * submitted to the server along with the debug events. + */ + getImpressionPayload(impid) { + if (!impid) { + throw new Error(`Impression ID is required. Given: "${impid}".`); + } + + return this._impressionPayloadCache[impid] || {}; + } + /** + * Update the payload for all impressions. The new values will be merged to + * the existing payload. + * @param {*} subPayloads Object containing new values to be merged indexed by SUB_PAYLOAD_TYPES + */ + mergeToAllImpressionsPayload(subPayloads) { + // Create clone for each impression ID and update the payload cache + _each(this.getAllBidderRequestImpIds(), currentImpid => { + // Avoid modifying the original object + const cloneSubPayloads = deepClone(subPayloads); + + // Initialise the payload cache if it doesn't exist + if (!this._impressionPayloadCache[currentImpid]) { + this._impressionPayloadCache[currentImpid] = {}; + } + + // Merge the new values to the existing payload + utils.mergePayloadAndAddCustomFields( + this._impressionPayloadCache[currentImpid], + cloneSubPayloads, + // Add the identity fields to all sub payloads + { + impid: currentImpid, + publisherId: this.publisherId, + } + ); + }); + } + + /** + * The Prebid auction object but only contains the key fields that we + * interested in. + */ + auction = null; + + /** + * Auction.bidderRequests object + */ + bidderRequests = null; + + get publisherId() { + if (!this.bidderRequests) { + throw new Error('Bidder requests are not available. Accessing before assigning.'); + } + return utils.getPublisherId(this.bidderRequests[0]); + } + + get adServerBaseUrl() { + if ( + !Array.isArray(this.bidderRequests) && + this.bidderRequests.length > 0 + ) { + throw new Error('Bidder requests are not available. Accessing before assigning.' + + JSON.stringify(this.bidderRequests, null, 2) + ); + } + + return utils.getAdServerEndpointBaseUrl(this.bidderRequests[0]); + } + + /** + * Extract all impression IDs from all bid requests. + */ + getAllBidderRequestImpIds() { + if (!Array.isArray(this.bidderRequests)) { + return []; + } + return this.bidderRequests.flatMap(br => br.bids.map(bid => utils.getImpId(bid))); + } + + /** + * Cache the debug events that are common to all bid contexts. + * When a new bid context is created, the events will be pushed to the new + * context. + */ + commonBidContextEvents = []; + + initialise(auction) { + this.auction = pick(auction, ['auctionId', 'auctionEnd']); + this.bidderRequests = auction.bidderRequests; + } + + /** + * Retrieve the BidContext object by the bid object. If the bid context is not + * available, it will create a new one. The new bid context will returned. + * @param {*} bid can be a prebid bid response or ortb bid response + * @returns BidContext object + */ + retrieveBidContext(bid) { + const ortbId = (() => { + try { + const id = utils.getOrtbId(bid); + if (!id) { + throw new Error( + 'ORTB ID is not available in the given bid object:' + + JSON.stringify(utils.omitRecursive(bid, COMMON_FIELDS_TO_OMIT), null, 2)); + } + return id; + } catch (error) { + throw new Error( + 'Failed to retrieve ORTB ID from bid object. Please ensure the given object contains an ORTB ID field.\n' + + `Sub Error: ${error.message}` + ); + } + })(); + const bidContext = this.bidContexts[ortbId]; + + if (bidContext) { + return bidContext; + } + + /** + * Create a new context object and return it. + */ + let newBidContext = new BidContext({ + localContext: this, + prebidOrOrtbBidResponse: bid, + }); + + /** + * Add the data that store in local context to the new bid context. + */ + _each( + this.commonBidContextEvents, + event => newBidContext.pushEvent( + { + eventInstance: event, + subPayloads: null, // Merge the payload later + }) + ); + // Merge cached payloads to the new bid context + newBidContext.mergePayload(this.getImpressionPayload(newBidContext.impid)); + + this.bidContexts[ortbId] = newBidContext; + return newBidContext; + } + + /** + * Immediately trigger the loss beacon for all bids (bid contexts) that haven't won the auction. + */ + triggerAllLossBidLossBeacon() { + _each(this.bidContexts, (bidContext) => { + const { ortbBidResponse, bidWin, lurlTriggered } = bidContext; + if (ortbBidResponse.lurl && !bidWin && !lurlTriggered) { + logInfo('TriggerLossBeacon. impid:', ortbBidResponse.impid); + utils.sendGetRequest(ortbBidResponse.lurl); + // Update the flog. Don't wait for the response to continue to avoid race conditions + bidContext.lurlTriggered = true; + } + }); + } + + /** + * Push an debug event to all bid contexts. This is useful for events that are + * related to all bids in the auction. + * @param {Object} params Object containing the event details + * @param {*} params.eventType Prebid event type or custom event type + * @param {*} params.level Debug level of the event. It can be one of the following: + * - info + * - warn + * - error + * @param {*} params.timestamp Default to current timestamp if not provided. + * @param {*} params.note Optional field. Additional information about the event. + * @param {*} params.subPayloads Objects containing additional data that are + * obtain from to the Prebid events indexed by SUB_PAYLOAD_TYPES. + */ + pushEventToAllBidContexts({eventType, level, timestamp, note, subPayloads}) { + // Create one event for each impression ID + _each(this.getAllBidderRequestImpIds(), impid => { + const eventClone = new Event({ + eventType, + impid, + publisherId: this.publisherId, + level, + timestamp, + note, + }); + // Save to the LocalContext + this.commonBidContextEvents.push(eventClone); + this.mergeToAllImpressionsPayload(subPayloads); + }); + + // If there are no bid contexts, push the event to the common events list + if (isEmpty(this.bidContexts)) { + this._commonBidContextEventsFlushed = false; + return; + } + + // Once the bid contexts are available, push the event to all bid contexts + _each(this.bidContexts, (bidContext) => { + bidContext.pushEvent({ + eventInstance: new Event({ + eventType, + impid: bidContext.impid, + publisherId: this.publisherId, + level, + timestamp, + note, + }), + subPayloads: this.getImpressionPayload(bidContext.impid), + }); + }); + } + + /** + * A flag to indicate if the common events have been flushed to the server. + * This is useful to avoid submitting the same events multiple times. + */ + _commonBidContextEventsFlushed = false; + + /** + * Flush all debug events in all bid contexts as well as the common events (in + * Local Context) to the server. + */ + async flushAllDebugEvents() { + if (this.commonBidContextEvents.length < 0 && isEmpty(this.bidContexts)) { + logInfo('No debug events to flush'); + return; + } + + const flushPromises = []; + const debugEndpoint = `${this.adServerBaseUrl}/debug`; + + // If there are no bid contexts, and there are error events, submit the + // common events to the server + if ( + isEmpty(this.bidContexts) && + !this._commonBidContextEventsFlushed && + this.commonBidContextEvents.some( + event => event.level === DEBUG_EVENT_LEVELS.error || + event.level === DEBUG_EVENT_LEVELS.warn + ) + ) { + logInfo('Flush common events to the server'); + const debugReports = this.bidderRequests.flatMap(currentBidderRequest => { + return currentBidderRequest.bids.map(bid => { + const impid = utils.getImpId(bid); + return { + impid: impid, + events: this.commonBidContextEvents, + bidWin: null, + // Unroll the payload object to the top level to make it easier for + // Grafana to process the data. + ...this.getImpressionPayload(impid), + }; + }); + }); + + _each(debugReports, debugReport => { + flushPromises.push(utils.postAjax( + debugEndpoint, + debugReport + )); + }); + + this._commonBidContextEventsFlushed = true; + } + + flushPromises.push( + ...Object.values(this.bidContexts) + .map(async (currentBidContext) => { + logInfo('Flush bid context events to the server', currentBidContext); + return utils.postAjax( + debugEndpoint, + { + impid: currentBidContext.impid, + bidWin: currentBidContext.bidWin, + events: currentBidContext.events, + // Unroll the payload object to the top level to make it easier for + // Grafana to process the data. + ...currentBidContext.subPayloads, + } + ); + })); + + await Promise.all(flushPromises); + } +} + +/** + * Select key fields from the given object based on the object type. This is + * useful for debugging to reduce the size of the API call payload. + * @param {*} objType The custom type of the object. Return by determineObjType function. + * @param {*} eventArgs The args object that is passed in to the event handler + * or any supported object. + * @returns the clone of the given object but only contains the key fields + */ +function pickKeyFields(objType, eventArgs) { + switch (objType) { + case SUB_PAYLOAD_TYPES.AUCTION: { + return pick(eventArgs, [ + 'auctionId', + 'adUnitCodes', + 'auctionStart', + 'auctionEnd', + 'auctionStatus', + 'bidderRequestId', + 'timeout', + 'timestamp', + ]); + } + case SUB_PAYLOAD_TYPES.BIDDER_REQUEST: { + return pick(eventArgs, [ + 'auctionId', + 'bidId', + 'bidderCode', + 'bidderRequestId', + 'timeout' + ]); + } + case SUB_PAYLOAD_TYPES.ORTB_BID: { + return pick(eventArgs, [ + 'impid', 'id', 'price', 'cur', 'crid', 'cid', 'lurl', 'cpm' + ]); + } + case SUB_PAYLOAD_TYPES.PREBID_RESPONSE_INTERPRETED: { + return { + ...pick(eventArgs, [ + 'requestId', + 'creativeId', + 'cpm', + 'currency', + 'bidderCode', + 'adUnitCode', + 'ttl', + 'adId', + 'width', + 'height', + 'requestTimestamp', + 'responseTimestamp', + 'seatBidId', + 'statusMessage', + 'timeToRespond', + 'rejectionReason', + 'ortbId', + 'auctionId', + 'mediaType', + 'bidderRequestId', + ]), + }; + } + case SUB_PAYLOAD_TYPES.PREBID_BID_REQUEST: { + return { + ...pick(eventArgs, [ + 'bidderRequestId' + ]), + bids: eventArgs.bids.map( + bid => pickKeyFields(SUB_PAYLOAD_TYPES.PREBID_RESPONSE_NOT_INTERPRETED, bid) + ), + }; + } + case SUB_PAYLOAD_TYPES.AD_DOC_AND_PREBID_BID: { + return { + // bid: 'Not included to reduce payload size', + doc: pick(eventArgs.doc, ['visibilityState', 'readyState', 'hidden']), + }; + } + case SUB_PAYLOAD_TYPES.AD_DOC_AND_PREBID_BID_WITH_ERROR: { + return { + // bid: 'Not included to reduce payload size', + reason: eventArgs.reason, + message: eventArgs.message, + doc: pick(eventArgs.doc, ['visibilityState', 'readyState', 'hidden']), + } + } + case SUB_PAYLOAD_TYPES.BIDDER_ERROR_ARGS: { + return { + bidderRequest: pickKeyFields(SUB_PAYLOAD_TYPES.BIDDER_REQUEST, eventArgs.bidderRequest), + error: eventArgs.error?.toJSON ? eventArgs.error?.toJSON() + : (eventArgs.error || 'Failed to convert error object to JSON'), + }; + } + default: { + // Include the entire object for debugging + return { eventArgs }; + } + } +} + +let mobkoiAnalytics = Object.assign(adapter({analyticsType}), { + localContext: new LocalContext(), + async track({ + eventType, + args: prebidEventArgs + }) { + try { + switch (eventType) { + case AUCTION_INIT: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const auction = prebidEventArgs; + this.localContext.initialise(auction); + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.info, + timestamp: auction.timestamp, + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + } + case BID_RESPONSE: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const prebidBid = prebidEventArgs; + const bidContext = this.localContext.retrieveBidContext(prebidBid); + bidContext.pushEvent({ + eventInstance: new Event({ + eventType, + impid: bidContext.impid, + publisherId: this.localContext.publisherId, + level: DEBUG_EVENT_LEVELS.info, + timestamp: prebidEventArgs.timestamp || Date.now(), + }), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs), + [SUB_PAYLOAD_TYPES.ORTB_BID]: pickKeyFields(SUB_PAYLOAD_TYPES.ORTB_BID, prebidEventArgs.ortbBidResponse), + } + }); + break; + } + case BID_WON: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const prebidBid = prebidEventArgs; + if (utils.isMobkoiBid(prebidBid)) { + this.localContext.retrieveBidContext(prebidBid).bidWin = true; + } + // Notify the server that the bidding results. + this.localContext.triggerAllLossBidLossBeacon(); + // Append the bid win/loss event to all bid contexts + _each(this.localContext.bidContexts, (currentBidContext) => { + currentBidContext.pushEvent({ + eventInstance: new Event({ + eventType: currentBidContext.bidWin ? eventType : CUSTOM_EVENTS.BID_LOSS, + impid: currentBidContext.impid, + publisherId: this.localContext.publisherId, + level: DEBUG_EVENT_LEVELS.info, + timestamp: prebidEventArgs.timestamp || Date.now(), + }), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs), + } + }); + }); + break; + } + case AUCTION_TIMEOUT: + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const auction = prebidEventArgs; + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.error, + timestamp: auction.timestamp, + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + case NO_BID: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.warn, + timestamp: prebidEventArgs.timestamp || Date.now(), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + } + case BID_REJECTED: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const prebidBid = prebidEventArgs; + const bidContext = this.localContext.retrieveBidContext(prebidBid); + bidContext.pushEvent({ + eventInstance: new Event({ + eventType, + impid: bidContext.impid, + publisherId: this.localContext.publisherId, + level: DEBUG_EVENT_LEVELS.error, + timestamp: prebidEventArgs.timestamp || Date.now(), + note: prebidEventArgs.rejectionReason, + }), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + }; + case BIDDER_ERROR: { + utils.logTrackEvent(eventType, prebidEventArgs) + const argsType = utils.determineObjType(prebidEventArgs); + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.warn, + timestamp: prebidEventArgs.timestamp || Date.now(), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + } + case AD_RENDER_FAILED: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const {bid: prebidBid} = prebidEventArgs; + const bidContext = this.localContext.retrieveBidContext(prebidBid); + bidContext.pushEvent({ + eventInstance: new Event({ + eventType, + impid: bidContext.impid, + publisherId: this.localContext.publisherId, + level: DEBUG_EVENT_LEVELS.error, + timestamp: prebidEventArgs.timestamp || Date.now(), + }), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + } + case AD_RENDER_SUCCEEDED: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const prebidBid = prebidEventArgs.bid; + const bidContext = this.localContext.retrieveBidContext(prebidBid); + bidContext.pushEvent({ + eventInstance: new Event({ + eventType, + impid: bidContext.impid, + publisherId: this.localContext.publisherId, + level: DEBUG_EVENT_LEVELS.info, + timestamp: prebidEventArgs.timestamp || Date.now(), + }), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + } + case AUCTION_END: { + utils.logTrackEvent(eventType, prebidEventArgs); + const argsType = utils.determineObjType(prebidEventArgs); + const auction = prebidEventArgs; + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.info, + timestamp: auction.timestamp, + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + break; + } + case BIDDER_DONE: { + utils.logTrackEvent(eventType, prebidEventArgs) + const argsType = utils.determineObjType(prebidEventArgs); + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.info, + timestamp: prebidEventArgs.timestamp || Date.now(), + subPayloads: { + [argsType]: pickKeyFields(argsType, prebidEventArgs) + } + }); + this.localContext.triggerAllLossBidLossBeacon(); + await this.localContext.flushAllDebugEvents(); + break; + } + default: + // Do nothing in other events + break; + } + } catch (error) { + // If there is an unexpected error, such as a syntax error, we log + // log the error and submit the error to the server for debugging. + this.localContext.pushEventToAllBidContexts({ + eventType, + level: DEBUG_EVENT_LEVELS.error, + timestamp: prebidEventArgs.timestamp || Date.now(), + note: 'Error occurred when processing this event.', + subPayloads: { + // Include the entire object for debugging + [`errorInEvent_${eventType}`]: { + // Some fields contain large data. Omits them to reduce API call payload size + eventArgs: utils.omitRecursive(prebidEventArgs, COMMON_FIELDS_TO_OMIT), + error: error.message, + } + } + }); + // Throw the error to skip the current Prebid event + throw error; + } + } +}); + +// save the base class function +mobkoiAnalytics.originEnableAnalytics = mobkoiAnalytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +mobkoiAnalytics.enableAnalytics = function (config) { + mobkoiAnalytics.originEnableAnalytics(config); // call the base class function +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: mobkoiAnalytics, + code: BIDDER_CODE, + gvlid: GVL_ID +}); + +export default mobkoiAnalytics; + +class BidContext { + /** + * The impression ID (ORTB term) of the bid. This ID is initialised in Prebid + * bid requests. The ID is reserved in requests and responses but have + * different names from object to object. + */ + get impid() { + if (this.ortbBidResponse) { + return this.ortbBidResponse.impid; + } else if (this.prebidBidResponse) { + return this.prebidBidResponse.requestId; + } else if (this.prebidBidRequest) { + return this.prebidBidRequest.bidId; + } else if ( + this.subPayloads && + utils.getImpId(this.subPayloads) + ) { + return utils.getImpId(this.subPayloads); + } else { + throw new Error('ORTB bid response and Prebid bid response are not available for extracting Impression ID'); + } + } + + /** + * ORTB ID generated by Ad Server + */ + get ortbId() { + if (this.ortbBidResponse) { + return utils.getOrtbId(this.ortbBidResponse); + } else if (this.prebidBidResponse) { + return utils.getOrtbId(this.prebidBidResponse); + } else if (this.subPayloads) { + return utils.getOrtbId(this.subPayloads); + } else { + throw new Error('ORTB bid response and Prebid bid response are not available for extracting ORTB ID'); + } + }; + + get publisherId() { + if (this.prebidBidRequest) { + return utils.getPublisherId(this.prebidBidRequest); + } else { + throw new Error('ORTB bid response and Prebid bid response are not available for extracting Publisher ID'); + } + } + + /** + * The prebid bid request object before converted to ORTB request in our + * custom adapter. + */ + get prebidBidRequest() { + if (!this.prebidBidResponse) { + throw new Error('Prebid bid response is not available. Accessing before assigning.'); + } + + return this.localContext.bidderRequests.flatMap(br => br.bids) + .find(bidRequest => bidRequest.bidId === this.prebidBidResponse.requestId); + } + + /** + * To avoid overriding the subPayloads object, we merge the new values to the + * existing subPayloads object. + */ + _subPayloads = null; + /** + * A group of payloads that are useful for debugging. The payloads are indexed + * by SUB_PAYLOAD_TYPES. + */ + get subPayloads() { + return this._subPayloads; + } + /** + * To avoid overriding the subPayloads object, we merge the new values to the + * existing subPayloads object. Identity fields will automatically added to the + * new values. + * @param {*} newSubPayloads Object containing new values to be merged + */ + mergePayload(newSubPayloads) { + utils.mergePayloadAndAddCustomFields( + this._subPayloads, + newSubPayloads, + // Add the identity fields to all sub payloads + { + impid: this.impid, + publisherId: this.publisherId, + } + ); + } + + /** + * The prebid bid response object after converted from ORTB response in our + * custom adapter. + */ + prebidBidResponse = null; + + /** + * The raw ORTB bid response object from the server. + */ + ortbBidResponse = null; + + /** + * A flag to indicate if the bid has won the auction. It only updated to true + * if the winning bid is from Mobkoi in the BID_WON event. + */ + bidWin = false; + + /** + * A flag to indicate if the loss beacon has been triggered. + */ + lurlTriggered = false; + + /** + * A list of DebugEvent objects + */ + events = []; + + /** + * Keep the reference of LocalContext object for easy accessing data. + */ + localContext = null; + + /** + * A object to store related data of a bid for easy access. + * i.e. bid request and bid response. + * @param {*} param0 + */ + constructor({ + localContext, + prebidOrOrtbBidResponse: bidResponse, + }) { + this.localContext = localContext; + this._subPayloads = {}; + + if (!bidResponse) { + throw new Error('prebidOrOrtbBidResponse field is required'); + } + + const objType = utils.determineObjType(bidResponse); + if (![SUB_PAYLOAD_TYPES.ORTB_BID, SUB_PAYLOAD_TYPES.PREBID_RESPONSE_INTERPRETED].includes(objType)) { + throw new Error( + 'Unable to create a new Bid Context as the given object is not a bid response object. ' + + 'Expect a Prebid Bid Object or ORTB Bid Object. Given object:\n' + + JSON.stringify(utils.omitRecursive(bidResponse, COMMON_FIELDS_TO_OMIT), null, 2) + ); + } + + if (objType === SUB_PAYLOAD_TYPES.ORTB_BID) { + this.ortbBidResponse = bidResponse; + this.prebidBidResponse = null; + } else if (objType === SUB_PAYLOAD_TYPES.PREBID_RESPONSE_INTERPRETED) { + this.ortbBidResponse = bidResponse.ortbBidResponse; + this.prebidBidResponse = bidResponse; + } else { + throw new Error('Expect a Prebid Bid Object or ORTB Bid Object. Given object:\n' + + JSON.stringify(utils.omitRecursive(bidResponse, COMMON_FIELDS_TO_OMIT), null, 2)); + } + } + + /** + * Push a debug event to the context which will be submitted to the server for debugging. + * @param {Object} params Object containing the following properties: + * @param {Event} params.eventInstance - DebugEvent object. If it does not contain the same impid as the BidContext, the event will be ignored. + * @param {Object|null} params.subPayloads - Object containing various payloads obtained from the Prebid Event args. The payloads will be merged into the existing subPayloads. + */ + pushEvent({eventInstance, subPayloads}) { + if (!(eventInstance instanceof Event)) { + throw new Error('bugEvent must be an instance of DebugEvent'); + } + if (eventInstance.impid != this.impid) { + // Ignore the event if the impression ID is not matched. + return; + } + // Accept only object or null + if (subPayloads !== null && typeof subPayloads !== 'object') { + throw new Error('subPayloads must be an object or null'); + } + + this.events.push(eventInstance); + + if (subPayloads !== null) { + this.mergePayload(subPayloads); + } + } +} + +/** + * A class to represent an event happened in the bid processing lifecycle. + */ +class Event { + /** + * Impression ID must set before appending to event lists. + */ + impid = null; + + /** + * Publisher ID. It is a unique identifier for the publisher. + */ + publisherId = null; + + /** + * Prebid Event Type or Custom Event Type + */ + eventType = null; + /** + * Debug level of the event. It can be one of the following: + * - info + * - warn + * - error + */ + level = null; + /** + * Timestamp of the event. It represents the time when the event occurred. + */ + timestamp = null; + + constructor({eventType, impid, publisherId, level, timestamp, note = undefined}) { + if (!eventType) { + throw new Error('eventType is required'); + } + if (!impid) { + throw new Error(`Impression ID is required. Given: "${impid}"`); + } + + if (typeof publisherId !== 'string') { + throw new Error(`Publisher ID must be a string. Given: "${publisherId}"`); + } + + if (!DEBUG_EVENT_LEVELS[level]) { + throw new Error(`Event level must be one of ${Object.keys(DEBUG_EVENT_LEVELS).join(', ')}. Given: "${level}"`); + } + if (typeof timestamp !== 'number') { + throw new Error('Timestamp must be a number'); + } + this.eventType = eventType; + this.impid = impid; + this.publisherId = publisherId; + this.level = level; + this.timestamp = timestamp; + if (note) { + this.note = note; + } + + if ( + debugTurnedOn() && + ( + level === DEBUG_EVENT_LEVELS.error || + level === DEBUG_EVENT_LEVELS.warn + )) { + logWarn(`New Debug Event - Type: ${eventType} Level: ${level}.`); + } + } +} + +/** + * Various types of payloads that are submitted to the server for debugging. + * Mostly they are obtain from the Prebid event args. + */ +export const SUB_PAYLOAD_TYPES = { + AUCTION: 'prebid_auction', + BIDDER_REQUEST: 'bidder_request', + ORTB_BID: 'ortb_bid', + PREBID_RESPONSE_INTERPRETED: 'prebid_bid_interpreted', + PREBID_RESPONSE_NOT_INTERPRETED: 'prebid_bid_not_interpreted', + PREBID_BID_REQUEST: 'prebid_bid_request', + AD_DOC_AND_PREBID_BID: 'ad_doc_and_prebid_bid', + AD_DOC_AND_PREBID_BID_WITH_ERROR: 'ad_doc_and_prebid_bid_with_error', + BIDDER_ERROR_ARGS: 'bidder_error_args', +}; + +/** + * Fields that are unique to objects used to identify the sub-payload type. + */ +export const SUB_PAYLOAD_UNIQUE_FIELDS_LOOKUP = { + [SUB_PAYLOAD_TYPES.AUCTION]: ['auctionStatus'], + [SUB_PAYLOAD_TYPES.BIDDER_REQUEST]: ['bidderRequestId'], + [SUB_PAYLOAD_TYPES.ORTB_BID]: ['adm', 'impid'], + [SUB_PAYLOAD_TYPES.PREBID_RESPONSE_INTERPRETED]: ['requestId', 'ortbBidResponse'], + [SUB_PAYLOAD_TYPES.PREBID_RESPONSE_NOT_INTERPRETED]: ['requestId'], // This must be paste under PREBID_RESPONSE_INTERPRETED + [SUB_PAYLOAD_TYPES.PREBID_BID_REQUEST]: ['bidId'], + [SUB_PAYLOAD_TYPES.AD_DOC_AND_PREBID_BID]: ['doc', 'bid'], + [SUB_PAYLOAD_TYPES.AD_DOC_AND_PREBID_BID_WITH_ERROR]: ['bid', 'reason', 'message'], + [SUB_PAYLOAD_TYPES.BIDDER_ERROR_ARGS]: ['error', 'bidderRequest'], +}; + +/** + * Required fields for the sub payloads. The property value defines the type of the required field. + */ +const PAYLOAD_REQUIRED_FIELDS = { + impid: 'string', + publisherId: 'string', +} + +export const utils = { + /** + * Make a POST request to the given URL with the given data. + * @param {*} url + * @param {*} data JSON data + * @returns + */ + postAjax: async function (url, data) { + return new Promise((resolve, reject) => { + try { + logInfo('postAjax:', url, data); + ajax(url, resolve, JSON.stringify(data), { + contentType: 'application/json', + method: 'POST', + withCredentials: false, // No user-specific data is tied to the request + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); + } catch (error) { + reject(new Error( + `Failed to make post request to endpoint "${url}". With data: ` + + JSON.stringify(utils.omitRecursive(data, COMMON_FIELDS_TO_OMIT), null, 2), + { error: error.message } + )); + } + }); + }, + + /** + * Make a GET request to the given URL. If the request fails, it will fall back + * to AJAX request. + * @param {*} url URL with the query string + * @returns + */ + sendGetRequest: async function(url) { + return new Promise((resolve, reject) => { + try { + logInfo('triggerPixel', url); + triggerPixel(url, resolve); + } catch (error) { + try { + logWarn(`triggerPixel failed. URL: (${url}) Falling back to ajax. Error: `, error); + ajax(url, resolve, null, { + contentType: 'application/json', + method: 'GET', + withCredentials: false, // No user-specific data is tied to the request + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); + } catch (error) { + // If failed with both methods, reject the promise + reject(error); + } + } + }); + }, + + /** + * Check if the given Prebid bid is from Mobkoi. + * @param {*} prebidBid + * @returns + */ + isMobkoiBid: function (prebidBid) { + return prebidBid && prebidBid.bidderCode === BIDDER_CODE; + }, + + /** + * !IMPORTANT: Make sure the implementation of this function matches utils.getOrtbId in + * mobkoiAnalyticsAdapter.js. + * We use the bidderRequestId as the ortbId. We could do so because we only + * make one ORTB request per Prebid Bidder Request. + * The ID field named differently when the value passed on to different contexts. + * @param {*} bid Prebid Bidder Request Object or Prebid Bid Response/Request + * or ORTB Request/Response Object + * @returns {string} The ORTB ID + * @throws {Error} If the ORTB ID cannot be found in the given object. + */ + getOrtbId(bid) { + const ortbId = + // called bidderRequestId in Prebid Request + bid.bidderRequestId || + // called seatBidId in Prebid Bid Response Object + bid.seatBidId || + // called ortbId in Interpreted Prebid Response Object + bid.ortbId || + // called id in ORTB object + (Object.hasOwn(bid, 'imp') && bid.id); + + if (!ortbId) { + throw new Error( + 'Failed to obtain ORTB ID from the given object. Given object:\n' + + JSON.stringify(utils.omitRecursive(bid, COMMON_FIELDS_TO_OMIT), null, 2) + ); + } + + return ortbId; + }, + + /** + * Impression ID is named differently in different objects. This function will + * return the impression ID from the given bid object. + * @param {*} bid ORTB bid response or Prebid bid response or Prebid bid request + * @returns string | null + */ + getImpId: function (bid) { + return (bid && (bid.impid || bid.requestId || bid.bidId)) || null; + }, + + /** + * !IMPORTANT: Make sure the implementation of this function matches utils.getPublisherId in + * both adapters. + * Extract the publisher ID from the given object. + * @param {*} bid Prebid Bidder Request Object or Prebid Bid Response/Request + * or ORTB Request/Response Object + * @returns string + * @throws {Error} If the publisher ID is not found in the given object. + */ + getPublisherId: function (bid) { + const ortbPath = 'site.publisher.id'; + const prebidPath = `ortb2.${ortbPath}`; + + const publisherId = + deepAccess(bid, prebidPath) || + deepAccess(bid, ortbPath); + + if (!publisherId) { + throw new Error( + 'Failed to obtain publisher ID from the given object. ' + + `Please set it via the "${prebidPath}" field with pbjs.setBidderConfig.\n` + + 'Given object:\n' + + JSON.stringify(bid, null, 2) + ); + } + + return publisherId; + }, + + /** + * !IMPORTANT: Make sure the implementation of this function matches getAdServerEndpointBaseUrl + * in both adapters. + * Obtain the Ad Server Base URL from the given Prebid object. + * @param {*} bid Prebid Bidder Request Object or Prebid Bid Response/Request + * or ORTB Request/Response Object + * @returns {string} The Ad Server Base URL + * @throws {Error} If the ORTB ID cannot be found in the given + */ + getAdServerEndpointBaseUrl (bid) { + const path = `site.publisher.ext.${PARAM_NAME_AD_SERVER_BASE_URL}`; + const preBidPath = `ortb2.${path}`; + + const adServerBaseUrl = + // For Prebid Bid objects + deepAccess(bid, preBidPath) || + // For ORTB objects + deepAccess(bid, path); + + if (!adServerBaseUrl) { + throw new Error('Failed to find the Ad Server Base URL in the given object. ' + + `Please set it via the "${preBidPath}" field with pbjs.setBidderConfig.\n` + + 'Given Object:\n' + + JSON.stringify(bid, null, 2) + ); + } + + return adServerBaseUrl; + }, + + logTrackEvent: function (eventType, eventArgs) { + if (!debugTurnedOn()) { + return; + } + const argsType = (() => { + try { + return utils.determineObjType(eventArgs); + } catch (error) { + logError(`Error when logging track event: [${eventType}]\n`, error); + return 'Unknown'; + } + })(); + logInfo(`Track event: [${eventType}]. Args Type Determination: ${argsType}`, eventArgs); + }, + + /** + * Determine the type of the given object based on the object's fields. + * This is useful for identifying the type of object that is passed in to the + * handler functions. + * @param {*} eventArgs + * @returns string + */ + determineObjType: function (eventArgs) { + if (typeof eventArgs !== 'object' || eventArgs === null) { + throw new Error( + 'determineObjType: Expect an object. Given object is not an object or null. Given object:' + + JSON.stringify(utils.omitRecursive(eventArgs, COMMON_FIELDS_TO_OMIT), null, 2) + ); + } + + let objType = null; + for (const type of Object.values(SUB_PAYLOAD_TYPES)) { + const identifyFields = SUB_PAYLOAD_UNIQUE_FIELDS_LOOKUP[type]; + if (!identifyFields) { + throw new Error( + `Identify fields for type "${type}" is not defined in COMMON_OBJECT_UNIT_FIELDS.` + ); + } + + // If all fields are available in the object, then it's the type we are looking for + if (identifyFields.every(field => eventArgs.hasOwnProperty(field))) { + objType = type; + break; + } + } + + if (!objType) { + throw new Error( + 'Unable to determine track args type. Please update COMMON_OBJECT_UNIT_FIELDS for the new object type.\n' + + 'Given object:\n' + + JSON.stringify(utils.omitRecursive(eventArgs, COMMON_FIELDS_TO_OMIT), null, 2) + ); + } + + return objType; + }, + + /** + * Merge a Payload object with new values. The payload object must be in + * specific format where root level keys are SUB_PAYLOAD_TYPES values and the + * property values must be an object of the given type. + * @param {*} targetPayload + * @param {*} newSubPayloads + * @param {*} customFields Custom fields that are required for the sub payloads. + */ + mergePayloadAndAddCustomFields: function (targetPayload, newSubPayloads, customFields = undefined) { + if (typeof targetPayload !== 'object') { + throw new Error('Target must be an object'); + } + + if (typeof newSubPayloads !== 'object') { + throw new Error('New values must be an object'); + } + + // Ensure all the required custom fields are available + if (customFields) { + _each(customFields, (fieldType, fieldName) => { + if (fieldType === 'string' && typeof newSubPayloads[fieldName] !== 'string') { + throw new Error( + `Field "${fieldName}" must be a string. Given: ${newSubPayloads[fieldName]}` + ); + } + }); + } + + mergeDeep(targetPayload, newSubPayloads); + + // Add the custom fields to the sub-payloads just added to the target payload + if (customFields) { + utils.addCustomFieldsToSubPayloads(targetPayload, customFields); + } + }, + + /** + * Should not use this function directly. Use mergePayloadAndCustomFields + * instead. This function add custom fields to the sub-payloads. The provided + * custom fields will be validated. + * @param {*} subPayloads A group of payloads that are useful for debugging. Indexed by SUB_PAYLOAD_TYPES. + * @param {*} customFields Custom fields that are required for the sub + * payloads. Fields are defined in PAYLOAD_REQUIRED_FIELDS. + */ + addCustomFieldsToSubPayloads: function (subPayloads, customFields) { + _each(subPayloads, (currentSubPayload, subPayloadType) => { + if (!Object.values(SUB_PAYLOAD_TYPES).includes(subPayloadType)) { + return; + } + + // Add the custom fields to the sub-payloads + mergeDeep(currentSubPayload, customFields); + }); + + // Before leaving the function, validate the payload to ensure all + // required fields are available. + utils.validateSubPayloads(subPayloads); + }, + + /** + * Recursively omit the given keys from the object. + * @param {*} subPayloads - The payload objects index by their payload types. + * @throws {Error} - If the given object is not valid. + */ + validateSubPayloads: function (subPayloads) { + _each(subPayloads, (currentSubPayload, subPayloadType) => { + if (!Object.values(SUB_PAYLOAD_TYPES).includes(subPayloadType)) { + return; + } + + const validationErrors = []; + // Validate the required fields + _each(PAYLOAD_REQUIRED_FIELDS, (requiredFieldType, requiredFieldName) => { + // eslint-disable-next-line valid-typeof + if (typeof currentSubPayload[requiredFieldName] !== requiredFieldType) { + validationErrors.push(new Error( + `Field "${requiredFieldName}" in "${subPayloadType}" must be a ${requiredFieldType}. Given: ${currentSubPayload[requiredFieldName]}` + )); + } + }); + + if (validationErrors.length > 0) { + throw new Error( + `Validation failed for "${subPayloadType}".\n` + + `Object: ${JSON.stringify(utils.omitRecursive(currentSubPayload, COMMON_FIELDS_TO_OMIT), null, 2)}\n` + + validationErrors.map(error => `Error: ${error.message}`).join('\n') + ); + } + }); + }, + + /** + * Recursively omit the given keys from the object. + * @param {*} obj - The object to process. + * @param {Array} keysToOmit - The keys to omit from the object. + * @param {*} [placeholder='OMITTED'] - The placeholder value to use for omitted keys. + * @returns {Object} - A clone of the given object with the specified keys omitted. + */ + omitRecursive: function (obj, keysToOmit, placeholder = 'OMITTED') { + return Object.keys(obj).reduce((acc, currentKey) => { + // If the current key is in the keys to omit, replace the value with the placeholder + if (keysToOmit.includes(currentKey)) { + acc[currentKey] = placeholder; + return acc; + } + + // If the current value is an object and not null, recursively omit keys + if (typeof obj[currentKey] === 'object' && obj[currentKey] !== null) { + acc[currentKey] = utils.omitRecursive(obj[currentKey], keysToOmit, placeholder); + } else { + // Otherwise, directly assign the value to the accumulator object + acc[currentKey] = obj[currentKey]; + } + return acc; + }, {}); + } +}; diff --git a/modules/mobkoiAnalyticsAdapter.md b/modules/mobkoiAnalyticsAdapter.md new file mode 100644 index 00000000000..07e10be184b --- /dev/null +++ b/modules/mobkoiAnalyticsAdapter.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: Mobkoi Analytics Adapter +Module Type: Analytics Adapter +Maintainer: platformteam@mobkoi.com + +# Description + +Analytics adapter for Mobkoi. Contact platformteam@mobkoi.com for information. \ No newline at end of file diff --git a/test/spec/modules/mobkoiAnalyticsAdapter_spec.js b/test/spec/modules/mobkoiAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..9122b5e49f4 --- /dev/null +++ b/test/spec/modules/mobkoiAnalyticsAdapter_spec.js @@ -0,0 +1,508 @@ +import mobkoiAnalyticsAdapter, { DEBUG_EVENT_LEVELS, utils, SUB_PAYLOAD_UNIQUE_FIELDS_LOOKUP, SUB_PAYLOAD_TYPES } from 'modules/mobkoiAnalyticsAdapter.js'; +import {internal} from '../../../src/utils.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as events from 'src/events.js'; +import { EVENTS } from 'src/constants.js'; +import sinon from 'sinon'; + +const defaultTimeout = 10000; +const requestId = 'test-request-id' +const publisherId = 'mobkoiPublisherId' +const bidId = 'test-bid-id' +const bidderCode = 'mobkoi' +const transactionId = 'test-transaction-id' +const impressionId = 'test-impression-id' +const adUnitId = 'test-ad-unit-id' +const auctionId = 'test-auction-id' +const adServerBaseUrl = 'http://adServerBaseUrl'; + +const adm = '
test ad
'; +const lurl = 'test.com/loss'; +const nurl = 'test.com/win'; + +const performStandardAuction = (auctionEvents) => { + auctionEvents.forEach(auctionEvent => { + events.emit(auctionEvent.event, auctionEvent.data); + }); +} + +const getOrtb2 = () => ({ + site: { + publisher: { + id: publisherId, + ext: { adServerBaseUrl } + } + } +}) + +const getBidderResponse = () => ({ + body: { + id: bidId, + cur: 'USD', + seatbid: [ + { + seat: 'mobkoi_debug', + bid: [ + { + id: bidId, + impid: impressionId, + cid: 'campaign_1', + crid: 'creative_1', + price: 1, + cur: [ + 'USD' + ], + adomain: [ + 'advertiser.com' + ], + adm, + w: 300, + h: 250, + mtype: 1, + lurl, + nurl + } + ] + } + ], + } +}) + +const getMockEvents = () => { + const sizes = [800, 300]; + const timestamp = Date.now(); + const auctionOrBidError = {timestamp, error: 'error', bidderRequest: { bidderRequestId: requestId }} + + return { + AUCTION_TIMEOUT: auctionOrBidError, + AUCTION_INIT: { + timestamp, + auctionId, + auctionStatus: 'inProgress', + adUnits: [{ + adUnitId: adUnitId, + code: 'banner-ad', + mediaTypes: { banner: { sizes: [sizes] } }, + transactionId, + }], + bidderRequests: [{ + bidderRequestId: requestId, + bids: [getBidRequest()], + ortb2: getOrtb2() + }] + }, + BID_RESPONSE: { + auctionId, + timestamp, + requestId: bidId, + bidId, + ortbId: requestId, + cpm: 1.5, + currency: 'USD', + ortbBidResponse: { + id: requestId, + impid: bidId, + price: 1.5 + } + }, + NO_BID: auctionOrBidError, + BIDDER_DONE: { + timestamp, + auctionId, + bidderRequestId: requestId, + bids: [getBidRequest()], + ortb2: getOrtb2() + }, + BID_WON: { + timestamp, + auctionId, + requestId, + bidId, + ortbBidResponse: { + id: requestId, + impid: bidId + } + }, + AUCTION_END: { + timestamp, + auctionId, + auctionStatus: 'completed' + }, + AD_RENDER_SUCCEEDED: { + bid: { + timestamp, + requestId: bidId, + bidId, + ortbId: requestId, + ad: '
test ad
' + }, + doc: { visibilityState: 'visible' } + }, + AD_RENDER_FAILED: { + bid: { + timestamp, + requestId: bidId, + bidId, + ortbId: requestId, + ad: '
test ad
' + }, + reason: 'error', + message: 'error' + }, + BIDDER_ERROR: auctionOrBidError, + BID_REJECTED: { + timestamp, + error: 'error', + bidderRequestId: requestId + } + } +} + +const getBidRequest = () => ({ + bidder: bidderCode, + adUnitCode: 'banner-ad', + transactionId, + adUnitId, + bidId: bidId, + bidderRequestId: requestId, + auctionId, + ortb2: getOrtb2() +}) + +const getBidderRequest = () => ({ + bidderCode, + auctionId, + bidderRequestId: requestId, + bids: [getBidRequest()], + ortb2: getOrtb2() +}) + +describe('mobkoiAnalyticsAdapter', function () { + it('should registers with the adapter manager', function () { + // should refer to the BIDDER_CODE in the mobkoiAnalyticsAdapter + const adapter = adapterManager.getAnalyticsAdapter('mobkoi'); + expect(adapter).to.exist; + // should refer to the GVL_ID in the mobkoiAnalyticsAdapter + expect(adapter.gvlid).to.equal(898); + expect(adapter.adapter).to.equal(mobkoiAnalyticsAdapter); + }); + + describe('Tracks events', function () { + let adapter; + let sandbox; + let pushEventSpy; + let flushEventsSpy; + let triggerBeaconSpy; + let postAjaxStub; + let sendGetRequestStub; + + beforeEach(function () { + adapter = mobkoiAnalyticsAdapter; + sandbox = sinon.createSandbox({ + useFakeTimers: { + now: new Date(2025, 0, 8, 0, 1, 33, 425), + }, + }); + + // Disable then reenable the adapter in order to have a fresh context + adapter.disableAnalytics(); + adapter.enableAnalytics({ + options: { + endpoint: adServerBaseUrl, + pid: 'test-pid', + timeout: defaultTimeout, + } + }); + + sandbox.stub(internal, 'logInfo'); + sandbox.stub(internal, 'logWarn'); + sandbox.stub(internal, 'logError'); + + // Create spies after enabling analytics to ensure localContext exists + postAjaxStub = sandbox.stub(utils, 'postAjax'); + sendGetRequestStub = sandbox.stub(utils, 'sendGetRequest'); + pushEventSpy = sandbox.spy(adapter.localContext, 'pushEventToAllBidContexts'); + flushEventsSpy = sandbox.spy(adapter.localContext, 'flushAllDebugEvents'); + triggerBeaconSpy = sandbox.spy(adapter.localContext, 'triggerAllLossBidLossBeacon'); + }); + + afterEach(function () { + adapter.disableAnalytics(); + sandbox.restore(); + postAjaxStub.reset(); + sendGetRequestStub.reset(); + }); + + it('should call sendGetRequest while tracking BIDDER_DONE / BID_WON events', function () { + const { AUCTION_INIT, BID_RESPONSE, BID_WON } = getMockEvents(); + const bidResponse = { + ...BID_RESPONSE, + ortbBidResponse: { + ...BID_RESPONSE.ortbBidResponse, + lurl, + bidWin: false, + lurlTriggered: false + } + }; + const eventSequence = [ + { event: EVENTS.AUCTION_INIT, data: AUCTION_INIT }, + { event: EVENTS.BID_RESPONSE, data: bidResponse }, + { event: EVENTS.BID_WON, data: BID_WON }, + ] + + performStandardAuction(eventSequence); + + expect(sendGetRequestStub.callCount).to.equal(1); + expect(sendGetRequestStub.firstCall.args[0]).to.equal(lurl); + }) + + it('should call postAjax while tracking BIDDER_DONE event', function () { + const { AUCTION_INIT, BID_RESPONSE, BIDDER_DONE } = getMockEvents(); + + const eventSequence = [ + { event: EVENTS.AUCTION_INIT, data: AUCTION_INIT }, + { event: EVENTS.BID_RESPONSE, data: BID_RESPONSE }, + { event: EVENTS.BIDDER_DONE, data: BIDDER_DONE } + ]; + + performStandardAuction(eventSequence); + + expect(postAjaxStub.calledOnce).to.be.true; + expect(postAjaxStub.firstCall.args[0]).to.equal(`${adServerBaseUrl}/debug`); + }) + + it('should track complete auction workflow in correct sequence and trigger a loss beacon', function () { + const { AUCTION_INIT, BID_RESPONSE, AUCTION_END, AD_RENDER_SUCCEEDED, BIDDER_DONE } = getMockEvents(); + + const eventSequence = [ + { event: EVENTS.AUCTION_INIT, data: AUCTION_INIT }, + { event: EVENTS.BID_RESPONSE, data: BID_RESPONSE }, + { event: EVENTS.AD_RENDER_SUCCEEDED, data: AD_RENDER_SUCCEEDED }, + { event: EVENTS.AUCTION_END, data: AUCTION_END }, + { event: EVENTS.BIDDER_DONE, data: BIDDER_DONE } + ]; + + performStandardAuction(eventSequence); + expect(pushEventSpy.callCount).to.equal(3); // AUCTION_INIT, AUCTION_END, BIDDER_DONE + expect(flushEventsSpy.callCount).to.equal(1); + expect(triggerBeaconSpy.callCount).to.equal(1); // BIDDER_DONE + }); + + it('should track errors events', function () { + const { AUCTION_TIMEOUT, NO_BID, BID_REJECTED, BIDDER_ERROR, AD_RENDER_FAILED } = getMockEvents(); + + const eventSequence = [ + { event: EVENTS.AUCTION_TIMEOUT, data: AUCTION_TIMEOUT }, + { event: EVENTS.NO_BID, data: NO_BID }, + { event: EVENTS.BID_REJECTED, data: BID_REJECTED }, + { event: EVENTS.BIDDER_ERROR, data: BIDDER_ERROR }, + { event: EVENTS.AD_RENDER_FAILED, data: AD_RENDER_FAILED } + ]; + + performStandardAuction(eventSequence); + + expect(pushEventSpy.callCount).to.equal(3); // AUCTION_TIMEOUT, NO_BID, BIDDER_ERROR + }); + + it('should push unexpected error events to the localContext', async function () { + const { AUCTION_INIT } = getMockEvents(); + delete AUCTION_INIT.auctionStatus; + try { + await adapter.track({ + eventType: EVENTS.AUCTION_INIT, + args: AUCTION_INIT + }); + } catch { + expect(pushEventSpy.calledOnce).to.be.true; + const errorEventCall = pushEventSpy.getCall(0); + + expect(errorEventCall.args[0]).to.deep.include({ + eventType: EVENTS.AUCTION_INIT, + level: DEBUG_EVENT_LEVELS.error, + note: 'Error occurred when processing this event.' + }); + + const errorPayload = errorEventCall.args[0].subPayloads[`errorInEvent_${EVENTS.AUCTION_INIT}`]; + expect(errorPayload).to.exist; + expect(errorPayload.error).to.include('Unable to determine track args type'); + } + }); + }) + + describe('utils', function () { + let bidderRequest; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + }); + + describe('isMobkoiBid', function () { + it('should return true when the bid is from mobkoi', function () { + expect(utils.isMobkoiBid(bidderRequest)).to.be.true; + }); + it('should return false when the bid is not from mobkoi', function () { + bidderRequest.bidderCode = 'anything'; + expect(utils.isMobkoiBid(bidderRequest)).to.be.false; + }); + }); + + describe('getOrtbId', function () { + it('should return the ortbId from the prebid request object (i.e bidderRequestId)', function () { + expect(utils.getOrtbId(bidderRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should return the ortbId from the prebid response object (i.e seatBidId)', function () { + const customBidRequest = { ...bidderRequest, seatBidId: bidderRequest.bidderRequestId }; + delete customBidRequest.bidderRequestId; + expect(utils.getOrtbId(customBidRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should return the ortbId from the interpreted prebid response object (i.e ortbId)', function () { + const customBidRequest = { ...bidderRequest, ortbId: bidderRequest.bidderRequestId }; + delete customBidRequest.bidderRequestId; + expect(utils.getOrtbId(customBidRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should return the ortbId from the ORTB request object (i.e has imp)', function () { + const customBidRequest = { ...bidderRequest, imp: {}, id: bidderRequest.bidderRequestId }; + delete customBidRequest.bidderRequestId; + expect(utils.getOrtbId(customBidRequest)).to.equal(bidderRequest.bidderRequestId); + }); + + it('should throw error when ortbId is missing', function () { + delete bidderRequest.bidderRequestId; + expect(() => { + utils.getOrtbId(bidderRequest); + }).to.throw(); + }); + }) + + describe('getImpId', function () { + let bidResponse; + beforeEach(function () { + const bidderResponse = getBidderResponse(); + bidResponse = bidderResponse.body.seatbid[0].bid[0]; + }); + + it('should return the impId from the impid field', function () { + expect(utils.getImpId(bidResponse)).to.equal(bidResponse.impid); + }); + + it('should return the impId from the requestId field', function () { + const customBidResponse = { ...bidResponse, requestId: bidResponse.impid }; + delete customBidResponse.impid; + expect(utils.getImpId(customBidResponse)).to.equal(bidResponse.impid); + }); + + it('should return the impId from the bidId field', function () { + const customBidResponse = { ...bidResponse, bidId: bidResponse.impid }; + delete customBidResponse.impid; + expect(utils.getImpId(customBidResponse)).to.equal(bidResponse.impid); + }); + + it('should return null if impId is missing', function () { + expect(utils.getImpId({})).to.be.null; + }); + }) + + describe('getPublisherId', function () { + it('should return the publisherId from the given object', function () { + expect(utils.getPublisherId(bidderRequest)).to.equal(bidderRequest.ortb2.site.publisher.id); + }); + + it('should throw error when publisherId is missing', function () { + delete bidderRequest.ortb2.site.publisher.id; + expect(() => { + utils.getPublisherId(bidderRequest); + }).to.throw(); + }); + }) + + describe('getAdServerEndpointBaseUrl', function () { + it('should return the adServerBaseUrl from the given object', function () { + expect(utils.getAdServerEndpointBaseUrl(bidderRequest)) + .to.equal(adServerBaseUrl); + }); + + it('should throw error when adServerBaseUrl is missing', function () { + delete bidderRequest.ortb2.site.publisher.ext.adServerBaseUrl; + + expect(() => { + utils.getAdServerEndpointBaseUrl(bidderRequest); + }).to.throw(); + }); + }) + + describe('determineObjType', function () { + [null, undefined, 123, 'string', true].forEach(value => { + it(`should throw an error when input is ${value}`, function() { + expect(() => { + utils.determineObjType(value); + }).to.throw(); + }); + }); + + it('should throw an error if the object type could not be determined', function () { + expect(() => { + utils.determineObjType({dumbAttribute: 'bid'}) + }).to.throw(); + }); + + Object.values(SUB_PAYLOAD_TYPES).forEach(type => { + it(`should return the ${type} type`, function () { + const eventArgs = {} + const uniqueFields = SUB_PAYLOAD_UNIQUE_FIELDS_LOOKUP[type] + uniqueFields.forEach(field => { + eventArgs[field] = 'random-value' + }) + expect(utils.determineObjType(eventArgs)).to.equal(type); + }) + }) + }) + + describe('mergePayloadAndCustomFields', function () { + it('should throw an error when the target is not an object', function () { + expect(() => { + utils.mergePayloadAndCustomFields(123, {}) + }).to.throw(); + }) + + it('should throw an error when the new values are not an object', function () { + expect(() => { + utils.mergePayloadAndCustomFields({}, 123) + }).to.throw(); + }) + + it('should throw an error if custom fields are provided and one of them is not a string', () => { + const customFields = {impid: 'bid-123', bidId: 123} + expect(() => { + utils.mergePayloadAndCustomFields({}, customFields) + }).to.throw(); + }) + }) + + describe('validateSubPayloads', function () { + it('should throw an error if the sub payloads required fields are not the correct type', function () { + expect(() => { + utils.validateSubPayloads({ + [SUB_PAYLOAD_TYPES.ORTB_BID]: { + impid: 123, + publisherId: 456 + } + }) + }).to.throw(); + }); + + it('should not throw when sub payloads have valid required fields', function () { + expect(() => { + utils.validateSubPayloads({ + [SUB_PAYLOAD_TYPES.ORTB_BID]: { + impid: '123', + publisherId: 'publisher-123' + } + }) + }).to.not.throw(); + }); + }); + }) +}); From 9079ca82de7fdb405336718137443e6d36e74cdb Mon Sep 17 00:00:00 2001 From: iagoBMS Date: Mon, 24 Feb 2025 16:53:59 -0300 Subject: [PATCH 0949/1097] BMS Bid Adapter : initial release (#12621) * wip * chore: update ENDPOINT_URL * chore: update permission for localstorage * feat(bmsBidAdapter): implement bid floor logic and update request structure * test(bmsBidAdapter): remove commented-out tests for interpretResponse * wip * wip * Refactor geolocation implementationn * chore: minor adjustments * feat: add bidWon * update test * chore: Change double quotes to single quotes * Update creativeId and creative_id values * refactor: remove unused cookie ID handling from bid request * wip * Remove deprecated BMS sample HTML and update BMS bid adapter to use JSON.stringify for requests and sendBeacon for bid won notifications * wip * Update creative.html * Update creative.html --------- Co-authored-by: Patrick McCann --- modules/bmsBidAdapter.js | 158 +++++++++++++++++++++++ modules/bmsBidAdapter.md | 48 +++++++ test/spec/modules/bmsBidAdapter_spec.js | 165 ++++++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 modules/bmsBidAdapter.js create mode 100644 modules/bmsBidAdapter.md create mode 100755 test/spec/modules/bmsBidAdapter_spec.js diff --git a/modules/bmsBidAdapter.js b/modules/bmsBidAdapter.js new file mode 100644 index 00000000000..52a309d4072 --- /dev/null +++ b/modules/bmsBidAdapter.js @@ -0,0 +1,158 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { sendBeacon } from '../src/ajax.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + replaceAuctionPrice, + isFn, + isPlainObject, + deepSetValue, + isEmpty, +} from '../src/utils.js'; +const BIDDER_CODE = 'bms'; +const ENDPOINT_URL = + 'https://api.prebid.int.us-east-1.bluems.com/v1/bid?exchangeId=prebid'; +const GVLID = 1105; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_BID_TTL = 1200; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +function getBidFloor(bid) { + if (isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: BANNER, + size: '*', + }); + if ( + isPlainObject(floor) && + !isNaN(floor.floor) && + floor.currency === DEFAULT_CURRENCY + ) { + return floor.floor; + } + } + return null; +} + +const converter = ortbConverter({ + context: { + netRevenue: true, // Default net revenue configuration + ttl: 100, // Default time-to-live for bid responses + }, + imp, + request, +}); + +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + + // Add publisher ID + deepSetValue(request, 'site.publisher.id', context.publisherId); + return request; +} + +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const floor = getBidFloor(bidRequest); + imp.tagid = bidRequest.params.placementId; + + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } + + return imp; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + // Validate bid request + isBidRequestValid: function (bid) { + return !!bid.params.placementId && !!bid.params.publisherId; + }, + + // Build OpenRTB requests using `ortbConverter` + buildRequests: function (validBidRequests, bidderRequest) { + const context = { + publisherId: validBidRequests.find( + (bidRequest) => bidRequest.params?.publisherId + )?.params.publisherId, + }; + + const ortbRequest = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context, + }); + + // Add extensions to the request + ortbRequest.ext = ortbRequest.ext || {}; + deepSetValue(ortbRequest, 'ext.gvlid', GVLID); + + return [ + { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'text/plain', + withCredentials: false, + }, + }, + ]; + }, + + interpretResponse: (serverResponse) => { + if (!serverResponse || isEmpty(serverResponse.body)) return []; + + let bids = []; + serverResponse.body.seatbid.forEach((response) => { + response.bid.forEach((bid) => { + const mediaType = bid.ext?.mediaType || 'banner'; + bids.push({ + ad: replaceAuctionPrice(bid.adm, bid.price), + adapterCode: BIDDER_CODE, + cpm: bid.price, + creativeId: bid.ext.bms.adId, + currency: serverResponse.body.cur || 'USD', + deferBilling: false, + deferRendering: false, + width: bid.w, + height: bid.h, + mediaType, + netRevenue: true, + originalCpm: bid.price, + originalCurrency: serverResponse.body.cur || 'USD', + requestId: bid.impid, + seatBidId: bid.id, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + meta: { + advertiserDomains: bid.adomain || [], + networkId: bid.ext?.networkId || 1105, + networkName: bid.ext?.networkName || 'BMS', + } + }); + }); + }); + return bids; + }, + + onBidWon: function (bid) { + const { burl, nurl } = bid || {}; + if (nurl) { + sendBeacon(nurl); + } + + if (burl) { + sendBeacon(burl); + } + }, +}; + +registerBidder(spec); diff --git a/modules/bmsBidAdapter.md b/modules/bmsBidAdapter.md new file mode 100644 index 00000000000..f6a04c5402a --- /dev/null +++ b/modules/bmsBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +Module Name: bms Bidder Adapter +Module Type: Bidder Adapter +Maintainer: celsooliveira@getbms.io + +# Description + +Module that connects to bms's demand sources. + +# Test Parameters + +``` + var adUnits = [ + { + code: "test-div", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + floors: { + currency: "USD", + schema: { + delimiter: "|", + fields: ["mediaType", "size"], + }, + values: { + "banner|300x250": 1.1, + "banner|300x600": 1.35, + "banner|*": 2, + }, + }, + bids: [ + { + bidder: "bms", + params: { + placementId: 13144370, + publisherId: 13144370, + }, + }, + ], + }, + ]; +``` diff --git a/test/spec/modules/bmsBidAdapter_spec.js b/test/spec/modules/bmsBidAdapter_spec.js new file mode 100755 index 00000000000..44112032def --- /dev/null +++ b/test/spec/modules/bmsBidAdapter_spec.js @@ -0,0 +1,165 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec, storage } from 'modules/bmsBidAdapter.js'; + +const BIDDER_CODE = 'bms'; +const ENDPOINT_URL = + 'https://api.prebid.int.us-east-1.bluems.com/v1/bid?exchangeId=prebid'; +const GVLID = 1105; +const CURRENCY = 'USD'; + +describe('bmsBidAdapter:', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid:', function () { + it('should return true for valid bid requests', function () { + const validBid = { + params: { + placementId: '12345', + publisherId: '67890', + }, + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false for invalid bid requests', function () { + const invalidBid = { + params: { + placementId: '12345', + }, + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests:', function () { + let validBidRequests; + let bidderRequest; + + beforeEach(function () { + validBidRequests = [ + { + bidId: 'bid1', + params: { + placementId: '12345', + publisherId: '67890', + }, + getFloor: () => ({ currency: CURRENCY, floor: 1.5 }), + }, + ]; + + bidderRequest = { + refererInfo: { + page: 'https://example.com', + }, + }; + + sandbox.stub(storage, 'getDataFromLocalStorage').returns('testBuyerId'); + }); + + it('should build a valid OpenRTB request', function () { + const [request] = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.options.contentType).to.equal('text/plain'); + const ortbRequest = JSON.parse(request.data); + + expect(ortbRequest.ext.gvlid).to.equal(GVLID); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.5); + expect(ortbRequest.imp[0].bidfloorcur).to.equal(CURRENCY); + }); + + it('should omit bidfloor if getFloor is not implemented', function () { + validBidRequests[0].getFloor = undefined; + + const [request] = spec.buildRequests(validBidRequests, bidderRequest); + const ortbRequest = JSON.parse(request.data); + + expect(ortbRequest.imp[0].bidfloor).to.be.undefined; + }); + + it('should convert from fromORTB', function () { + const response = { + id: 'response-id-123456', + cur: 'USD', + bidid: '2rgRKcbHfDyX6ZU4zuPuf38h000', + seatbid: [ + { + bid: [ + { + id: '2rgRKcbHfDyX6ZU4zuPuf521444:0', + impid: '3b948a96652621', + price: 2, + adomain: ['example.com'], + adid: '0', + adm: '', + iurl: 'https://ads.bluemsusercontent.com/v1/ad-container?acc=306850905425&ad=2pMGbaJioMDwMIESvwlCUekrdNA', + h: 600, + w: 300, + nurl: 'https://bid-notice.rtb.bluems.com/v1/bid:won?winPrice=${AUCTION_PRICE}&accountId=306850905425&adId=2pMGbaJioMDwMIESvwlCUekrdNA&campaignId=2Xzb0pyfcOibtp9A5XsUSkzj2IT&exchangeId=prebid&tagId=13144370&impressionId=3b948a96652621&bidId=2rgRKcbHfDyX6ZU4zuPuf38hDB8%3A0&bidPrice=2&bidFloor=2&height=600&width=300®ion=us-east-1&targetId=2rgH24MVckzSou4IpTyUa5l3Ija¤cy=USD&campaignCurrency=USD&campaignCurrencyConversionFactor=1&publisherId=13144370&domain=d3fef36u6k6muh.cloudfront.net', + lurl: 'https://bid-notice.rtb.bluems.com/v1/bid:lost?winPrice=${AUCTION_PRICE}&marketBidRatio=${AUCTION_MBR}&lossReasonCode=${AUCTION_LOSS}&accountId=306850905425&adId=2pMGbaJioMDwMIESvwlCUekrdNA&campaignId=2Xzb0pyfcOibtp9A5XsUSkzj2IT&exchangeId=prebid&tagId=13144370&impressionId=3b948a96652621&bidId=2rgRKcbHfDyX6ZU4zuPuf38hDB8%3A0&bidPrice=2&bidFloor=2&height=600&width=300®ion=us-east-1&targetId=2rgH24MVckzSou4IpTyUa5l3Ija¤cy=USD&campaignCurrency=USD&campaignCurrencyConversionFactor=1&publisherId=13144370&domain=d3fef36u6k6muh.cloudfront.net', + burl: 'https://bid-notice.rtb.bluems.com/v1/bid:charged?winPrice=${AUCTION_PRICE}&accountId=306850905425&adId=2pMGbaJioMDwMIESvwlCUekrdNA&campaignId=2Xzb0pyfcOibtp9A5XsUSkzj2IT&exchangeId=prebid&tagId=13144370&impressionId=3b948a96652621&bidId=2rgRKcbHfDyX6ZU4zuPuf38hDB8%3A0&bidPrice=2&bidFloor=2&height=600&width=300®ion=us-east-1&targetId=2rgH24MVckzSou4IpTyUa5l3Ija¤cy=USD&campaignCurrency=USD&campaignCurrencyConversionFactor=1&publisherId=13144370&domain=d3fef36u6k6muh.cloudfront.net', + exp: 60, + ext: { + bms: { + accountId: '306850900000', + campaignId: '2Xzb0pyfcOibtp9A5X8546254528', + adId: '2pMGbaJioMDwMIESvwlCUlkojug', + region: 'us-east-1', + targetId: '2rgH24MVckzSou4IpTyUalakush', + }, + }, + }, + ], + seat: '1', + }, + ], + }; + const request = { + id: '10bb57ee-712f-43e9-9769-b26d03lklkih', + bidder: BIDDER_CODE, + params: { + source: 886409, + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-1460505iosakju-0', + transactionId: '7d79850b-70aa-4c0f-af95-c1d524sskjkjh', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '2eb89f0f062afe', + bidderRequestId: '1ae6c8e18f8462', + auctionId: '1286637c-51bc-4fdd-8e35-2435elklklka', + ortb2: {}, + }; + + const [ortbReq] = spec.buildRequests([request], { + bids: [request], + }); + + const ortbResponse = spec.interpretResponse( + { body: response }, + { data: ortbReq.data } + ); + + expect(ortbResponse.length).to.eq(1); + expect(ortbResponse[0].mediaType).to.eq('banner'); + }); + }); +}); From 882a9920700d5435a28cfe6e46be98c6d3c8a0af Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Tue, 25 Feb 2025 06:58:04 +1100 Subject: [PATCH 0950/1097] Adding Adnuntius as an analytics adapter (#12496) --- modules/adnuntiusAnalyticsAdapter.js | 407 +++++++++++++ modules/adnuntiusAnalyticsAdapter.md | 22 + .../modules/adnuntiusAnalyticsAdapter_spec.js | 534 ++++++++++++++++++ 3 files changed, 963 insertions(+) create mode 100644 modules/adnuntiusAnalyticsAdapter.js create mode 100644 modules/adnuntiusAnalyticsAdapter.md create mode 100644 test/spec/modules/adnuntiusAnalyticsAdapter_spec.js diff --git a/modules/adnuntiusAnalyticsAdapter.js b/modules/adnuntiusAnalyticsAdapter.js new file mode 100644 index 00000000000..ed5535d96d1 --- /dev/null +++ b/modules/adnuntiusAnalyticsAdapter.js @@ -0,0 +1,407 @@ +import { timestamp, logInfo } from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import { EVENTS, STATUS } from '../src/constants.js'; +import adapterManager from '../src/adapterManager.js'; + +const URL = 'https://analytics.adnuntius.com/prebid'; +const REQUEST_SENT = 1; +const RESPONSE_SENT = 2; +const WIN_SENT = 4; +const TIMEOUT_SENT = 8; +const AD_RENDER_FAILED_SENT = 16; + +let initOptions; +export const BID_WON_TIMEOUT = 500; + +const cache = { + auctions: {} +}; + +const adnAnalyticsAdapter = Object.assign(adapter({url: '', analyticsType: 'endpoint'}), { + track({eventType, args}) { + const time = timestamp(); + logInfo('ADN_EVENT:', [eventType, args]); + + switch (eventType) { + case EVENTS.AUCTION_INIT: + logInfo('ADN_AUCTION_INIT:', args); + cache.auctions[args.auctionId] = {bids: {}, bidAdUnits: {}}; + break; + case EVENTS.BID_REQUESTED: + logInfo('ADN_BID_REQUESTED:', args); + cache.auctions[args.auctionId].timeStamp = args.start; + + args.bids.forEach(function(bidReq) { + cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined; + cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined; + + const container = document.getElementById(bidReq.adUnitCode); + const containerAttr = container ? container.getAttribute('data-adunitid') : undefined; + const adUnitId = containerAttr || undefined; + + cache.auctions[args.auctionId].bids[bidReq.bidId] = { + bidder: bidReq.bidder, + adUnit: bidReq.adUnitCode, + adUnitId: adUnitId, + isBid: false, + won: false, + timeout: false, + sendStatus: 0, + readyToSend: 0, + start: args.start, + auc: bidReq.auc, + buc: bidReq.buc, + lw: bidReq.lw + }; + + logInfo(bidReq); + }); + logInfo(adnAnalyticsAdapter.requestEvents); + break; + case EVENTS.BID_RESPONSE: + logInfo('ADN_BID_RESPONSE:', args); + + const bidResp = cache.auctions[args.auctionId].bids[args.requestId]; + bidResp.isBid = args.getStatusCode() === STATUS.GOOD; + bidResp.width = args.width; + bidResp.height = args.height; + bidResp.cpm = args.cpm; + bidResp.currency = args.currency; + bidResp.originalCpm = args.originalCpm; + bidResp.originalCurrency = args.originalCurrency; + bidResp.ttr = args.timeToRespond; + bidResp.readyToSend = 1; + bidResp.mediaType = args.mediaType === 'native' ? 2 : (args.mediaType === 'video' ? 4 : 1); + bidResp.meta = args.meta; + + if (!bidResp.ttr) { + bidResp.ttr = time - bidResp.start; + } + if (!cache.auctions[args.auctionId].bidAdUnits[bidResp.adUnit]) { + cache.auctions[args.auctionId].bidAdUnits[bidResp.adUnit] = + { + sent: 0, + lw: bidResp.lw, + adUnitId: bidResp.adUnitId, + timeStamp: cache.auctions[args.auctionId].timeStamp + }; + } + break; + case EVENTS.BIDDER_DONE: + logInfo('ADN_BIDDER_DONE:', args); + args.bids.forEach(doneBid => { + let bid = cache.auctions[doneBid.auctionId].bids[doneBid.bidId || doneBid.requestId]; + if (!bid.ttr) { + bid.ttr = time - bid.start; + } + bid.readyToSend = 1; + }); + break; + case EVENTS.BID_WON: + logInfo('ADN_BID_WON:', args); + const wonBid = cache.auctions[args.auctionId].bids[args.requestId]; + wonBid.won = true; + wonBid.rUp = args.rUp; + wonBid.meta = args.meta; + wonBid.dealId = args.dealId; + if (wonBid.sendStatus !== 0) { + adnAnalyticsAdapter.sendEvents(); + } + break; + case EVENTS.AD_RENDER_SUCCEEDED: + logInfo('ADN_AD_RENDER_SUCCEEDED:', args); + const adRenderSucceeded = cache.auctions[args.bid.auctionId].bids[args.bid.requestId]; + adRenderSucceeded.renderedTimestamp = Date.now(); + break; + case EVENTS.AD_RENDER_FAILED: + logInfo('ADN_AD_RENDER_FAILED:', args); + const adRenderFailedBid = cache.auctions[args.bid.auctionId].bids[args.bid.requestId]; + adRenderFailedBid.adRenderFailed = true; + adRenderFailedBid.reason = args.reason; + adRenderFailedBid.message = args.message; + if (adRenderFailedBid.sendStatus !== 0) { + adnAnalyticsAdapter.sendEvents(); + } + break; + case EVENTS.BID_TIMEOUT: + logInfo('ADN_BID_TIMEOUT:', args); + args.forEach(timeout => { + cache.auctions[timeout.auctionId].bids[timeout.bidId].timeout = true; + }); + break; + case EVENTS.AUCTION_END: + logInfo('ADN_AUCTION_END:', args); + setTimeout(() => { + adnAnalyticsAdapter.sendEvents(); + }, BID_WON_TIMEOUT); + break; + } + } +}); + +// save the base class function +adnAnalyticsAdapter.originEnableAnalytics = adnAnalyticsAdapter.enableAnalytics; +adnAnalyticsAdapter.allRequestEvents = []; + +// override enableAnalytics so we can get access to the config passed in from the page +adnAnalyticsAdapter.enableAnalytics = function (config) { + initOptions = config.options; + adnAnalyticsAdapter.originEnableAnalytics(config); +}; + +adnAnalyticsAdapter.sendEvents = function() { + const sentRequests = getSentRequests(); + const events = { + publisherId: initOptions.publisherId, + gdpr: sentRequests.gdpr, + auctionIds: sentRequests.auctionIds, + requests: sentRequests.sentRequests, + responses: getResponses(sentRequests.gdpr, sentRequests.auctionIds), + wins: getWins(sentRequests.gdpr, sentRequests.auctionIds), + timeouts: getTimeouts(sentRequests.gdpr, sentRequests.auctionIds), + bidAdUnits: getBidAdUnits(), + rf: getAdRenderFailed(sentRequests.auctionIds), + ext: initOptions.ext + }; + + if (events.requests.length === 0 && events.responses.length === 0 && events.wins.length === 0 && events.timeouts.length === 0 && events.rf.length === 0) { + return; + } + + ajax(initOptions.endPoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); +}; + +function getSentRequests() { + const sentRequests = []; + const gdpr = []; + const auctionIds = []; + + Object.keys(cache.auctions).forEach(auctionId => { + const auction = cache.auctions[auctionId]; + const gdprPos = getGdprPos(gdpr, auction); + const auctionIdPos = getAuctionIdPos(auctionIds, auctionId); + + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + let bid = auction.bids[bidId]; + if (!(bid.sendStatus & REQUEST_SENT)) { + bid.sendStatus |= REQUEST_SENT; + + sentRequests.push({ + adUnit: bid.adUnit, + adUnitId: bid.adUnitId, + bidder: bid.bidder, + timeStamp: auction.timeStamp, + gdpr: gdprPos, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw + }); + } + }); + }); + + return {gdpr: gdpr, auctionIds: auctionIds, sentRequests: sentRequests}; +} + +function getResponses(gdpr, auctionIds) { + const responses = []; + + Object.keys(cache.auctions).forEach(auctionId => { + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + let auction = cache.auctions[auctionId]; + let gdprPos = getGdprPos(gdpr, auction); + let auctionIdPos = getAuctionIdPos(auctionIds, auctionId) + let bid = auction.bids[bidId]; + if (bid.readyToSend && !(bid.sendStatus & RESPONSE_SENT) && !bid.timeout) { + bid.sendStatus |= RESPONSE_SENT; + + let response = getResponseObject(auction, bid, gdprPos, auctionIdPos); + + responses.push(response); + } + }); + }); + + return responses; +} + +function getWins(gdpr, auctionIds) { + const wins = []; + + Object.keys(cache.auctions).forEach(auctionId => { + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + const auction = cache.auctions[auctionId]; + const gdprPos = getGdprPos(gdpr, auction); + const auctionIdPos = getAuctionIdPos(auctionIds, auctionId); + const bid = auction.bids[bidId]; + + if (!(bid.sendStatus & WIN_SENT) && bid.won) { + bid.sendStatus |= WIN_SENT; + + wins.push({ + adUnit: bid.adUnit, + adUnitId: bid.adUnitId, + bidder: bid.bidder, + timeStamp: auction.timeStamp, + width: bid.width, + height: bid.height, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + mediaType: bid.mediaType, + gdpr: gdprPos, + auctionId: auctionIdPos, + auc: bid.auc, + lw: bid.lw, + buc: bid.buc, + rUp: bid.rUp, + meta: bid.meta, + dealId: bid.dealId + }); + } + }); + }); + + return wins; +} + +function getGdprPos(gdpr, auction) { + let gdprPos; + for (gdprPos = 0; gdprPos < gdpr.length; gdprPos++) { + if (gdpr[gdprPos].gdprApplies === auction.gdprApplies && + gdpr[gdprPos].gdprConsent === auction.gdprConsent) { + break; + } + } + + if (gdprPos === gdpr.length) { + gdpr[gdprPos] = {gdprApplies: auction.gdprApplies, gdprConsent: auction.gdprConsent}; + } + + return gdprPos; +} + +function getAuctionIdPos(auIds, auId) { + let auctionIdPos; + for (auctionIdPos = 0; auctionIdPos < auIds.length; auctionIdPos++) { + if (auIds[auctionIdPos] === auId) { + break; + } + } + + if (auctionIdPos === auIds.length) { + auIds[auctionIdPos] = auId; + } + + return auctionIdPos; +} + +function getResponseObject(auction, bid, gdprPos, auctionIdPos) { + return { + adUnit: bid.adUnit, + adUnitId: bid.adUnitId, + bidder: bid.bidder, + timeStamp: auction.timeStamp, + renderedTimestamp: bid.renderedTimestamp, + width: bid.width, + height: bid.height, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + ttr: bid.ttr, + isBid: bid.isBid, + mediaType: bid.mediaType, + gdpr: gdprPos, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw, + meta: bid.meta + }; +} + +function getTimeouts(gdpr, auctionIds) { + const timeouts = []; + + Object.keys(cache.auctions).forEach(auctionId => { + const auctionIdPos = getAuctionIdPos(auctionIds, auctionId); + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + const auction = cache.auctions[auctionId]; + const gdprPos = getGdprPos(gdpr, auction); + const bid = auction.bids[bidId]; + if (!(bid.sendStatus & TIMEOUT_SENT) && bid.timeout) { + bid.sendStatus |= TIMEOUT_SENT; + + let timeout = getResponseObject(auction, bid, gdprPos, auctionIdPos); + + timeouts.push(timeout); + } + }); + }); + + return timeouts; +} + +function getAdRenderFailed(auctionIds) { + const adRenderFails = []; + + Object.keys(cache.auctions).forEach(auctionId => { + const auctionIdPos = getAuctionIdPos(auctionIds, auctionId); + Object.keys(cache.auctions[auctionId].bids).forEach(bidId => { + const auction = cache.auctions[auctionId]; + const bid = auction.bids[bidId]; + if (!(bid.sendStatus & AD_RENDER_FAILED_SENT) && bid.adRenderFailed) { + bid.sendStatus |= AD_RENDER_FAILED_SENT; + + adRenderFails.push({ + bidder: bid.bidder, + adUnit: bid.adUnit, + adUnitId: bid.adUnitId, + timeStamp: auction.timeStamp, + auctionId: auctionIdPos, + auc: bid.auc, + buc: bid.buc, + lw: bid.lw, + rsn: bid.reason, + msg: bid.message + }); + } + }); + }); + + return adRenderFails; +} + +function getBidAdUnits() { + const bidAdUnits = []; + + Object.keys(cache.auctions).forEach(auctionId => { + const auction = cache.auctions[auctionId]; + Object.keys(auction.bidAdUnits).forEach(adUnit => { + const bidAdUnit = auction.bidAdUnits[adUnit]; + if (!bidAdUnit.sent) { + bidAdUnit.sent = 1; + + bidAdUnits.push({ + adUnit: adUnit, + adUnitId: bidAdUnit.adUnitId, + timeStamp: bidAdUnit.timeStamp, + lw: bidAdUnit.lw + }); + } + }); + }); + + return bidAdUnits; +} + +adapterManager.registerAnalyticsAdapter({ + adapter: adnAnalyticsAdapter, + code: 'adnuntius' +}); + +export default adnAnalyticsAdapter; diff --git a/modules/adnuntiusAnalyticsAdapter.md b/modules/adnuntiusAnalyticsAdapter.md new file mode 100644 index 00000000000..a0d931529ad --- /dev/null +++ b/modules/adnuntiusAnalyticsAdapter.md @@ -0,0 +1,22 @@ +# Overview +Module Name: Adnuntius Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: ops@adnuntius.com + +# Description + +Analytics adapter for Adnuntius AS. Contact Adnuntius AS in order to use the adapter. + +# Test Parameters + +``` +{ + provider: 'adnuntius', + options : { + publisherId: "contact-adnuntius-for-this-id" + } +} + +``` diff --git a/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js b/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..09bf55db146 --- /dev/null +++ b/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js @@ -0,0 +1,534 @@ +import adnAnalyticsAdapter, { BID_WON_TIMEOUT } from 'modules/adnuntiusAnalyticsAdapter.js'; +import { AD_RENDER_FAILED_REASON, EVENTS, STATUS } from 'src/constants.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; +import { setConfig } from 'modules/currency.js'; + +let events = require('src/events'); +let utils = require('src/utils'); +let adapterManager = require('src/adapterManager').default; + +const { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING, + AD_RENDER_FAILED +} = EVENTS; + +const BID1 = { + width: 980, + height: 240, + cpm: 1.1, + originalCpm: 12.0, + currency: 'USD', + originalCurrency: 'AUX', + timeToRespond: 200, + bidId: '2ec0db240757', + requestId: '2ec240757', + adId: '2ec240757', + auctionId: '1234-4567-7890', + mediaType: 'banner', + meta: { + data: 'value1' + }, + dealId: 'dealid', + getStatusCode() { + return STATUS.GOOD; + } +}; + +const BID2 = Object.assign({}, BID1, { + width: 300, + height: 250, + cpm: 2.2, + originalCpm: 23.0, + currency: 'USD', + originalCurrency: 'AUX', + timeToRespond: 300, + bidId: '30db240757', + requestId: '30db240757', + adId: '30db240757', + meta: { + data: 'value2' + }, + dealId: undefined +}); + +const BID3 = { + bidId: '4ecff0db240757', + requestId: '4ecff0db240757', + adId: '4ecff0db240757', + auctionId: '1234-4567-7890', + mediaType: 'banner', + getStatusCode() { + return STATUS.GOOD; + } +}; + +const MOCK = { + AUCTION_INIT: { + 'auctionId': '1234-4567-7890', + }, + BID_REQUESTED: { + 'bidder': 'adnuntius', + 'auctionId': '1234-4567-7890', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'adnuntius', + 'adUnitCode': 'panorama_d_1', + 'bidId': '2ec240757', + }, + { + 'bidder': 'adnuntius', + 'adUnitCode': 'box_d_1', + 'bidId': '30db240757', + }, + { + 'bidder': 'adnuntius', + 'adUnitCode': 'box_d_2', + 'bidId': '4ecff0db240757', + } + ], + 'start': 1519149562216 + }, + BID_RESPONSE: [ + BID1, + BID2 + ], + AUCTION_END: { + }, + BID_WON: [ + Object.assign({}, BID1, { + 'status': 'rendered', + 'requestId': '2ec240757' + }), + Object.assign({}, BID2, { + 'status': 'rendered', + 'requestId': '30db240757' + }) + ], + BIDDER_DONE: { + 'bidderCode': 'adnuntius', + 'bids': [ + BID1, + BID2, + BID3 + ] + }, + BID_TIMEOUT: [ + { + 'bidId': '2ec240757', + 'auctionId': '1234-4567-7890' + } + ], + AD_RENDER_FAILED: [ + { + 'bidId': '2ec240757', + 'reason': AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + 'message': 'message', + 'bid': BID1 + } + ] +}; + +const ANALYTICS_MESSAGE = { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + gdpr: [{}], + auctionIds: ['1234-4567-7890'], + bidAdUnits: [ + { + adUnit: 'panorama_d_1', + adUnitId: 'adunitid', + timeStamp: 1519149562216 + }, + { + adUnit: 'box_d_1', + adUnitId: 'adunitid', + timeStamp: 1519149562216 + } + ], + requests: [ + { + adUnit: 'panorama_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + gdpr: 0, + auctionId: 0 + }, + { + adUnit: 'box_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + gdpr: 0, + auctionId: 0 + }, + { + adUnit: 'box_d_2', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + gdpr: 0, + auctionId: 0 + } + ], + responses: [ + { + adUnit: 'panorama_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + width: 980, + height: 240, + cpm: 1.1, + currency: 'USD', + originalCpm: 12, + originalCurrency: 'AUX', + ttr: 200, + isBid: true, + mediaType: 1, + gdpr: 0, + auctionId: 0, + meta: { + data: 'value1' + } + }, + { + adUnit: 'box_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + width: 300, + height: 250, + cpm: 2.2, + currency: 'USD', + originalCpm: 23, + originalCurrency: 'AUX', + ttr: 300, + isBid: true, + mediaType: 1, + gdpr: 0, + auctionId: 0, + meta: { + data: 'value2' + } + } + ], + timeouts: [], + wins: [ + { + adUnit: 'panorama_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + width: 980, + height: 240, + cpm: 1.1, + currency: 'USD', + originalCpm: 12, + originalCurrency: 'AUX', + mediaType: 1, + gdpr: 0, + auctionId: 0, + meta: { + data: 'value1' + }, + dealId: 'dealid' + }, + { + adUnit: 'box_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + width: 300, + height: 250, + cpm: 2.2, + currency: 'USD', + originalCpm: 23, + originalCurrency: 'AUX', + mediaType: 1, + gdpr: 0, + auctionId: 0, + meta: { + data: 'value2' + } + } + ], + rf: [ + { + adUnit: 'panorama_d_1', + adUnitId: 'adunitid', + bidder: 'adnuntius', + timeStamp: 1519149562216, + auctionId: 0, + rsn: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + msg: 'message' + }, + ] +}; + +function performStandardAuction() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + events.emit(AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED[0]); +} + +describe('Adnuntius analytics adapter', function () { + let sandbox; + let clock; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + let element = { + getAttribute: function() { + return 'adunitid'; + } + } + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(utils, 'timestamp').returns(1519149562416); + sandbox.stub(document, 'getElementById').returns(element); + + clock = sandbox.useFakeTimers(1519767013781); + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + describe('when handling events', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'adnuntius', + adapter: adnAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'adnuntius', + options: { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7' + } + }); + }); + + afterEach(function () { + adnAnalyticsAdapter.disableAnalytics(); + }); + + it('should build a batched message from prebid events', function () { + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.equal('https://analytics.adnuntius.com/prebid'); + + const message = JSON.parse(request.requestBody); + expect(message).to.deep.equal(ANALYTICS_MESSAGE); + }); + + it('should send batched message without BID_WON AND AD_RENDER_FAILED if necessary and further BID_WON and AD_RENDER_FAILED events individually', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(BID_WON_TIMEOUT + 1000); + + events.emit(BID_WON, MOCK.BID_WON[1]); + events.emit(AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED[0]); + + expect(server.requests.length).to.equal(3); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.wins.length).to.equal(1); + expect(message.requests).to.deep.equal(ANALYTICS_MESSAGE.requests); + expect(message.wins[0]).to.deep.equal(ANALYTICS_MESSAGE.wins[0]); + + message = JSON.parse(server.requests[1].requestBody); + expect(message.wins.length).to.equal(1); + expect(message.wins[0]).to.deep.equal(ANALYTICS_MESSAGE.wins[1]); + + message = JSON.parse(server.requests[2].requestBody); + expect(message.rf.length).to.equal(1); + expect(message.rf[0]).to.deep.equal(ANALYTICS_MESSAGE.rf[0]); + }); + + it('should properly mark bids as timed out', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.timeouts.length).to.equal(1); + expect(message.timeouts[0].bidder).to.equal('adnuntius'); + expect(message.timeouts[0].adUnit).to.equal('panorama_d_1'); + }); + + it('should forward GDPR data', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, { + 'bidder': 'adnuntius', + 'auctionId': '1234-4567-7890', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'adnuntius', + 'adUnitCode': 'panorama_d_1', + 'bidId': '2ec240757', + }, + { + 'bidder': 'adnuntius', + 'adUnitCode': 'box_d_1', + 'bidId': '30db240757', + } + ], + 'start': 1519149562216, + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': 'consentstring' + } + }, + ); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.gdpr.length).to.equal(1); + expect(message.gdpr[0].gdprApplies).to.equal(true); + expect(message.gdpr[0].gdprConsent).to.equal('consentstring'); + expect(message.requests.length).to.equal(2); + expect(message.requests[0].gdpr).to.equal(0); + expect(message.requests[1].gdpr).to.equal(0); + + expect(message.responses.length).to.equal(1); + expect(message.responses[0].gdpr).to.equal(0); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0].gdpr).to.equal(0); + }); + + it('should forward runner-up data as given by Adnuntius wrapper', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_WON, Object.assign({}, + MOCK.BID_WON[0], + { + 'rUp': 'rUpObject' + })); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0].rUp).to.equal('rUpObject'); + }); + }); + + describe('when given other endpoint', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'adnuntius', + adapter: adnAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'adnuntius', + options: { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + endPoint: 'https://whitelabeled.com/analytics/10' + } + }); + }); + + afterEach(function () { + adnAnalyticsAdapter.disableAnalytics(); + }); + + it('should call the endpoint', function () { + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.equal('https://whitelabeled.com/analytics/10'); + }); + }); + + describe('when given extended options', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'adnuntius', + adapter: adnAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'adnuntius', + options: { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + ext: { + testparam: 123 + } + } + }); + }); + + afterEach(function () { + adnAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward the extended options', function () { + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.ext).to.not.equal(null); + expect(message.ext.testparam).to.equal(123); + }); + }); +}); From f50b606919d0340a43f2072234cfda9c66fa8132 Mon Sep 17 00:00:00 2001 From: teads-antoine-azar Date: Tue, 25 Feb 2025 16:00:54 +0100 Subject: [PATCH 0951/1097] Teads Bid Adapter: add extra information to request payload (#12802) * Add new features to HB request * Add test case for domComplexity feature * add default value domComplexity * Teads Bid Adapter: use getTimeToFirstByte from library --- modules/teadsBidAdapter.js | 34 ++++++----------------- test/spec/modules/teadsBidAdapter_spec.js | 30 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index cbf7a989e91..976c81fc633 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js'; import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; +import {getTimeToFirstByte} from '../libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -60,8 +61,10 @@ export const spec = { pageTitle: getPageTitle().slice(0, 300), pageDescription: getPageDescription().slice(0, 300), networkBandwidth: getConnectionDownLink(window.navigator), + networkQuality: getNetworkQuality(window.navigator), timeToFirstByte: getTimeToFirstByte(window), data: bids, + domComplexity: getDomComplexity(document), device: bidderRequest?.ortb2?.device || {}, deviceWidth: screen.width, deviceHeight: screen.height, @@ -244,33 +247,14 @@ function getConnectionDownLink(nav) { return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : ''; } -function getTimeToFirstByte(win) { - const performance = win.performance || win.webkitPerformance || win.msPerformance || win.mozPerformance; - - const ttfbWithTimingV2 = performance && - typeof performance.getEntriesByType === 'function' && - Object.prototype.toString.call(performance.getEntriesByType) === '[object Function]' && - performance.getEntriesByType('navigation')[0] && - performance.getEntriesByType('navigation')[0].responseStart && - performance.getEntriesByType('navigation')[0].requestStart && - performance.getEntriesByType('navigation')[0].responseStart > 0 && - performance.getEntriesByType('navigation')[0].requestStart > 0 && - Math.round( - performance.getEntriesByType('navigation')[0].responseStart - performance.getEntriesByType('navigation')[0].requestStart - ); - - if (ttfbWithTimingV2) { - return ttfbWithTimingV2.toString(); - } +function getNetworkQuality(navigator) { + const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - const ttfbWithTimingV1 = performance && - performance.timing.responseStart && - performance.timing.requestStart && - performance.timing.responseStart > 0 && - performance.timing.requestStart > 0 && - performance.timing.responseStart - performance.timing.requestStart; + return connection?.effectiveType ?? ''; +} - return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; +function getDomComplexity(document) { + return document?.querySelectorAll('*')?.length ?? -1; } function findGdprStatus(gdprApplies, gdprData) { diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 71ed9a21efb..cb7c46a55ad 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -300,6 +300,36 @@ describe('teadsBidAdapter', () => { } }); + it('should add networkQuality info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + const networkQuality = window.navigator && window.navigator.connection && window.navigator.connection.effectiveType; + + expect(payload.networkQuality).to.exist; + + if (networkQuality) { + expect(payload.networkQuality).to.deep.equal(networkQuality.toString()); + } else { + expect(payload.networkQuality).to.deep.equal(''); + } + }) + + it('should add domComplexity info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + const domComplexity = document?.querySelectorAll('*')?.length; + + expect(payload.domComplexity).to.exist; + + if (domComplexity) { + expect(payload.domComplexity).to.deep.equal(domComplexity); + } else { + expect(payload.domComplexity).to.deep.equal(-1); + } + }) + it('should add pageReferrer info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); From 90f96e302aba361238b5a68b61624b8be57f9f46 Mon Sep 17 00:00:00 2001 From: ElgarsG <65354776+eldzis@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:34:33 +0200 Subject: [PATCH 0952/1097] Fix gdprConsent undefined error (#12812) --- modules/setupadBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/setupadBidAdapter.js b/modules/setupadBidAdapter.js index 4ee6dd7c085..7687b46aab5 100644 --- a/modules/setupadBidAdapter.js +++ b/modules/setupadBidAdapter.js @@ -117,8 +117,8 @@ export const spec = { const queryParams = []; queryParams.push(`bidders=${bidders}`); - queryParams.push('gdpr=' + +gdprConsent.gdprApplies); - queryParams.push('gdpr_consent=' + gdprConsent.consentString); + queryParams.push('gdpr=' + +gdprConsent?.gdprApplies); + queryParams.push('gdpr_consent=' + gdprConsent?.consentString); queryParams.push('usp_consent=' + (uspConsent || '')); const strQueryParams = queryParams.join('&'); From f4bd46fd52f5d4bd2735571966058d5ff950e764 Mon Sep 17 00:00:00 2001 From: tososhi Date: Thu, 27 Feb 2025 22:26:00 +0900 Subject: [PATCH 0953/1097] fluct Bid Adapter : add gpp support (#12805) * add gpp support * add test cases * fix * fix null safe * fix test case * rerun ci --- modules/fluctBidAdapter.js | 12 ++++++++- test/spec/modules/fluctBidAdapter_spec.js | 30 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index c0ae55efc89..68206d4af38 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -72,7 +72,17 @@ export const spec = { if (config.getConfig('coppa') === true) { deepSetValue(data, 'regs.coppa', 1); } - + if (bidderRequest.gppConsent) { + deepSetValue(data, 'regs.gpp', { + string: bidderRequest.gppConsent.gppString, + sid: bidderRequest.gppConsent.applicableSections + }); + } else if (bidderRequest.ortb2?.regs?.gpp) { + deepSetValue(data, 'regs.gpp', { + string: bidderRequest.ortb2.regs.gpp, + sid: bidderRequest.ortb2.regs.gpp_sid + }); + } data.sizes = []; _each(request.sizes, (size) => { data.sizes.push({ diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index 32ca99ecd76..3335e8b23cc 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -402,6 +402,36 @@ describe('fluctAdapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.regs.coppa).to.eql(1); }); + + it('includes data.regs.gpp.string and data.regs.gpp.sid if bidderRequest.gppConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + gppConsent: { + gppString: 'gpp-consent-string', + applicableSections: [1, 2, 3], + }, + }), + )[0]; + expect(request.data.regs.gpp.string).to.eql('gpp-consent-string'); + expect(request.data.regs.gpp.sid).to.eql([1, 2, 3]); + }); + + it('includes data.regs.gpp.string and data.regs.gpp.sid if bidderRequest.ortb2.regs.gpp exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + ortb2: { + regs: { + gpp: 'gpp-consent-string', + gpp_sid: [1, 2, 3], + }, + }, + }), + )[0]; + expect(request.data.regs.gpp.string).to.eql('gpp-consent-string'); + expect(request.data.regs.gpp.sid).to.eql([1, 2, 3]); + }); }); describe('should interpretResponse', function() { From 49699ea4f743a91a4a7884d6c31b162c8cd48d5a Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Thu, 27 Feb 2025 16:43:29 +0300 Subject: [PATCH 0954/1097] AdMatic Bid Adapter : sync bug fixed (#12801) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * admatic sync update --- modules/admaticBidAdapter.js | 69 +++++++++------------ test/spec/modules/admaticBidAdapter_spec.js | 17 ++--- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index f90dd5a8762..9cc2182c6bf 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -2,8 +2,8 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.j import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { deepAccess, formatQS, getBidIdParameter, getValue, isArray, logError } from '../src/utils.js'; -import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; +import { deepAccess, getBidIdParameter, getValue, isArray, logError } from '../src/utils.js'; +import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js'; import { interpretNativeAd } from '../libraries/precisoUtils/bidNativeUtils.js'; /** @@ -12,7 +12,7 @@ import { interpretNativeAd } from '../libraries/precisoUtils/bidNativeUtils.js'; * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest */ -let SYNC_URL = ''; +let SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; const BIDDER_CODE = 'admatic'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -20,12 +20,12 @@ export const spec = { code: BIDDER_CODE, gvlid: 1281, aliases: [ - {code: 'admaticde', gvlid: 1281}, - {code: 'pixad', gvlid: 1281}, - {code: 'monetixads', gvlid: 1281}, - {code: 'netaddiction', gvlid: 1281}, - {code: 'adt', gvlid: 779}, - {code: 'yobee', gvlid: 1281} + { code: 'admaticde', gvlid: 1281 }, + { code: 'pixad', gvlid: 1281 }, + { code: 'monetixads', gvlid: 1281 }, + { code: 'netaddiction', gvlid: 1281 }, + { code: 'adt', gvlid: 779 }, + { code: 'yobee', gvlid: 1281 } ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -125,46 +125,35 @@ export const spec = { } if (payload) { - switch (bidderName) { - case 'netaddiction': - SYNC_URL = 'https://static.cdn.netaddiction.tech/netaddiction/sync.html'; - break; - case 'monetixads': - SYNC_URL = 'https://static.cdn.monetixads.com/monetixads/sync.html'; - break; - case 'pixad': - SYNC_URL = 'https://static.cdn.pixad.com.tr/sync.html'; - break; - case 'admaticde': - SYNC_URL = 'https://static.cdn.admatic.de/admaticde/sync.html'; - break; - case 'adt': - SYNC_URL = 'https://static.cdn.adtarget.biz/adt/sync.html'; - break; - case 'yobee': - SYNC_URL = 'https://static.cdn.yobee.it/yobee/sync.html'; - break; - case 'admatic': - default: - SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; - break; + const domain = {}; + domain.parts = host.split('rtb.'); + if (domain.parts.length > 1) { + domain.url = domain.parts[1]; } + SYNC_URL = `https://static.cdn.${domain.url}/${bidderName}/sync.html`; - host = host?.replace('https://', '')?.replace('http://', '')?.replace('/', ''); + host = host.replace('https://', '').replace('http://', '').replace('/', ''); return { method: 'POST', url: `https://${host}/pb`, data: payload, options: { contentType: 'application/json' } }; } }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (!hasSynced && syncOptions.iframeEnabled) { - // data is only assigned if params are available to pass to syncEndpoint - let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); - params = Object.keys(params).length ? `&${formatQS(params)}` : ''; + // Retrieve the sync parameters + const params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); + + // Create a URL object from SYNC_URL + const urlObj = new URL(SYNC_URL); + + // Append each parameter from the params object to the URL's search parameters + Object.keys(params).forEach(key => { + urlObj.searchParams.append(key, params[key]); + }); hasSynced = true; return { type: 'iframe', - url: SYNC_URL + params + url: urlObj.toString() }; } }, @@ -266,7 +255,7 @@ function isUrl(str) { } }; -function outstreamRender (bid) { +function outstreamRender(bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ targetId: bid.adUnitCode, @@ -363,7 +352,7 @@ function buildRequestObject(bid) { } if (bid.mediaTypes?.native) { reqObj.type = 'native'; - reqObj.size = [{w: 1, h: 1}]; + reqObj.size = [{ w: 1, h: 1 }]; reqObj.mediatype = bid.mediaTypes.native; } @@ -391,7 +380,7 @@ function concatSizes(bid) { if (isArray(bannerSizes) || isArray(playerSize) || isArray(videoSizes)) { let mediaTypesSizes = [bannerSizes, videoSizes, nativeSizes, playerSize]; return mediaTypesSizes - .reduce(function(acc, currSize) { + .reduce(function (acc, currSize) { if (isArray(currSize)) { if (isArray(currSize[0])) { currSize.forEach(function (childSize) { diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 50700a7b4e1..da4329b6405 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from 'modules/admaticBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb'; +const ENDPOINT = 'https://layer.rtb.admatic.com.tr/pb'; describe('admaticBidAdapter', () => { const adapter = newBidder(spec); @@ -15,7 +15,7 @@ describe('admaticBidAdapter', () => { 'bidder': 'admatic', 'params': { 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' + 'host': 'layer.rtb.admatic.com.tr' }, 'ortb2Imp': { 'ext': { 'instl': 1 } }, 'ortb2': { 'badv': ['admatic.com.tr'] }, @@ -250,7 +250,7 @@ describe('admaticBidAdapter', () => { 'bidder': 'admatic', 'params': { 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' + 'host': 'layer.rtb.admatic.com.tr' }, 'ortb2Imp': { 'ext': { 'instl': 1 } }, 'ortb2': { 'badv': ['admatic.com.tr'] }, @@ -564,7 +564,7 @@ describe('admaticBidAdapter', () => { 'bidder': 'admatic', 'params': { 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' + 'host': 'layer.rtb.admatic.com.tr' }, 'adUnitCode': 'adunit-code', 'mediaType': 'banner', @@ -683,7 +683,10 @@ describe('admaticBidAdapter', () => { }] } ], - params: {} + params: { + networkId: 10433394, + host: 'layer.rtb.admatic.com.tr' + } }, ]; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -724,7 +727,7 @@ describe('admaticBidAdapter', () => { 'ortb2': { 'badv': ['admatic.com.tr'] }, 'params': { 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' + 'host': 'layer.rtb.admatic.com.tr' }, getFloor: inputParams => { if (inputParams.mediaType === VIDEO && inputParams.size[0] === 300 && inputParams.size[1] === 250) { @@ -766,7 +769,7 @@ describe('admaticBidAdapter', () => { 'ortb2': { 'badv': ['admatic.com.tr'] }, 'params': { 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' + 'host': 'layer.rtb.admatic.com.tr' }, getFloor: inputParams => { if (inputParams.mediaType === NATIVE) { From e800318b7e15c3f49db471cf1b8adf75e0e64783 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 27 Feb 2025 05:57:14 -0800 Subject: [PATCH 0955/1097] Debugging module: make mock creatives respect requested sizes (#12804) * Debugging module: restore old mock creative, respect requested sizes * use repeating bg image * Fix mocking of s2s bids --- .../testBidder/testBidderVideoExample.html | 4 +- modules/debugging/bidInterceptor.js | 66 ++++--------------- modules/debugging/pbsInterceptor.js | 2 +- modules/debugging/responses.js | 61 +++++++++++++---- 4 files changed, 67 insertions(+), 66 deletions(-) diff --git a/integrationExamples/testBidder/testBidderVideoExample.html b/integrationExamples/testBidder/testBidderVideoExample.html index b5f027d99b4..72412306d82 100644 --- a/integrationExamples/testBidder/testBidderVideoExample.html +++ b/integrationExamples/testBidder/testBidderVideoExample.html @@ -70,6 +70,6 @@

Prebid Test Bidder Example

Video ad
-
+
- \ No newline at end of file + diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 390cd1735dc..828535f88b8 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -1,13 +1,5 @@ -import { Renderer } from '../../src/Renderer.js'; -import { auctionManager } from '../../src/auctionManager.js'; -import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; -import { - deepAccess, - deepClone, - delayExecution, - mergeDeep, - hasNonSerializableProperty -} from '../../src/utils.js'; +import {BANNER, VIDEO} from '../../src/mediaTypes.js'; +import {deepAccess, deepClone, delayExecution, hasNonSerializableProperty, mergeDeep} from '../../src/utils.js'; import responseResolvers from './responses.js'; /** @@ -147,7 +139,8 @@ Object.assign(BidInterceptor.prototype, { return (bid, ...args) => { const response = this.responseDefaults(bid); mergeDeep(response, replFn({args: [bid, ...args]})); - this.setDefaultAd(bid, response); + const resolver = responseResolvers[response.mediaType]; + resolver && resolver(bid, response); response.isDebug = true; return response; } @@ -175,8 +168,6 @@ Object.assign(BidInterceptor.prototype, { requestId: bid.bidId, cpm: 3.5764, currency: 'EUR', - width: 600, - height: 500, ttl: 360, creativeId: 'mock-creative-id', netRevenue: false, @@ -184,47 +175,18 @@ Object.assign(BidInterceptor.prototype, { }; if (!bid.mediaType) { - const adUnit = auctionManager.index.getAdUnit({adUnitId: bid.adUnitId}) || {mediaTypes: {}}; - response.mediaType = Object.keys(adUnit.mediaTypes)[0] || 'banner'; + response.mediaType = Object.keys(bid.mediaTypes ?? {})[0] ?? BANNER; } - - return response; - }, - setDefaultAd(bid, bidResponse) { - switch (bidResponse.mediaType) { - case VIDEO: - if (!bidResponse.hasOwnProperty('vastXml') && !bidResponse.hasOwnProperty('vastUrl')) { - bidResponse.vastXml = responseResolvers[VIDEO](); - bidResponse.renderer = Renderer.install({ - url: 'https://cdn.jwplayer.com/libraries/l5MchIxB.js', - }); - bidResponse.renderer.setRender(function (bid) { - const player = window.jwplayer('player').setup({ - width: 640, - height: 360, - advertising: { - client: 'vast', - outstream: true, - endstate: 'close' - }, - }); - player.on('ready', function() { - player.loadAdXml(bid.vastXml); - }); - }) - } - break; - case NATIVE: - if (!bidResponse.hasOwnProperty('native')) { - bidResponse.native = responseResolvers[NATIVE](bid); - } - break; - case BANNER: - default: - if (!bidResponse.hasOwnProperty('ad') && !bidResponse.hasOwnProperty('adUrl')) { - bidResponse.ad = responseResolvers[BANNER](); - } + let size; + if (response.mediaType === BANNER) { + size = bid.mediaTypes?.banner?.sizes?.[0] ?? [300, 250]; + } else if (response.mediaType === VIDEO) { + size = bid.mediaTypes?.video?.playerSize?.[0] ?? [600, 500]; } + if (Array.isArray(size)) { + ([response.width, response.height] = size); + } + return response; }, /** * Match a candidate bid against all registered rules. diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index c514031ad0e..dcde50927ad 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -17,7 +17,7 @@ export function makePbsInterceptor({createBid}) { function addBid(bid, bidRequest) { onBid({ adUnit: bidRequest.adUnitCode, - bid: Object.assign(createBid(STATUS.GOOD, bidRequest), bid) + bid: Object.assign(createBid(STATUS.GOOD, bidRequest), {requestBidder: bidRequest.bidder}, bid) }) } bidRequests = bidRequests diff --git a/modules/debugging/responses.js b/modules/debugging/responses.js index f9950fac17d..f858e3e3dc1 100644 --- a/modules/debugging/responses.js +++ b/modules/debugging/responses.js @@ -1,18 +1,57 @@ -import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js'; +import {Renderer} from '../../src/Renderer.js'; +import {getGptSlotInfoForAdUnitCode} from '../../libraries/gptUtils/gptUtils.js'; const ORTB_NATIVE_ASSET_TYPES = ['img', 'video', 'link', 'data', 'title']; export default { - [BANNER]: () => ``, - [VIDEO]: () => 'GDFPDemo00:00:11', - [NATIVE]: (bid) => { - return { - ortb: { - link: { - url: 'https://www.link.example', - clicktrackers: ['https://impression.example'] - }, - assets: bid.nativeOrtbRequest.assets.map(mapDefaultNativeOrtbAsset) + [BANNER]: (bid, bidResponse) => { + if (!bidResponse.hasOwnProperty('ad') && !bidResponse.hasOwnProperty('adUrl')) { + const [size, repeat] = bidResponse.width < bidResponse.height ? [bidResponse.width, 'repeat-y'] : [bidResponse.height, 'repeat-x']; + bidResponse.ad = `
`; + } + }, + [VIDEO]: (bid, bidResponse) => { + if (!bidResponse.hasOwnProperty('vastXml') && !bidResponse.hasOwnProperty('vastUrl')) { + bidResponse.vastXml = 'GDFPDemo00:00:11'; + bidResponse.renderer = Renderer.install({ + url: 'https://cdn.jwplayer.com/libraries/l5MchIxB.js', + }); + bidResponse.renderer.setRender(function (bid, doc) { + const parentId = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId ?? bid.adUnitCode; + const div = doc.createElement('div'); + div.id = `${parentId}-video-player`; + doc.getElementById(parentId).appendChild(div); + const player = window.jwplayer(div.id).setup({ + debug: true, + width: bidResponse.width, + height: bidResponse.height, + advertising: { + client: 'vast', + outstream: true, + endstate: 'close' + }, + }); + player.on('ready', async function () { + if (bid.vastUrl) { + player.loadAdTag(bid.vastUrl); + } else { + player.loadAdXml(bid.vastXml); + } + }); + }) + } + }, + [NATIVE]: (bid, bidResponse) => { + if (!bidResponse.hasOwnProperty('native')) { + bidResponse.native = { + ortb: { + link: { + url: 'https://www.link.example', + clicktrackers: ['https://impression.example'] + }, + assets: bid.nativeOrtbRequest.assets.map(mapDefaultNativeOrtbAsset) + } } } } From 971e8f04299ab42c18ff7f3d446b85fea877adbc Mon Sep 17 00:00:00 2001 From: furukawaTakumi <45890154+furukawaTakumi@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:12:37 +0900 Subject: [PATCH 0956/1097] Ssp_geniee Bid Adapter : add support for GPID and pbadslot (#12806) * modify adUnit infomation * fix imuid module * feat(GenieeBidAdapter): Add support for GPID and pbadslot - Add support for GPID (Global Placement ID) from ortb2Imp.ext.gpid - Add fallback support for ortb2Imp.ext.data.pbadslot - Include gpid parameter in request when GPID exists - Add test cases to verify GPID, pbadslot, and priority behavior --------- Co-authored-by: Murano Takamasa Co-authored-by: daikichiteranishi <49385718+daikichiteranishi@users.noreply.github.com> Co-authored-by: teranishi daikichi Co-authored-by: gn-daikichi <49385718+gn-daikichi@users.noreply.github.com> Co-authored-by: takumi-furukawa --- modules/ssp_genieeBidAdapter.js | 3 ++ .../spec/modules/ssp_genieeBidAdapter_spec.js | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/modules/ssp_genieeBidAdapter.js b/modules/ssp_genieeBidAdapter.js index ed15842f96b..8be3c0c58af 100644 --- a/modules/ssp_genieeBidAdapter.js +++ b/modules/ssp_genieeBidAdapter.js @@ -129,6 +129,8 @@ function hasParamsNotBlankString(params, key) { * @see https://docs.prebid.org/dev-docs/bidder-adaptor.html#location-and-referrers */ function makeCommonRequestData(bid, geparameter, refererInfo) { + const gpid = utils.deepAccess(bid, 'ortb2Imp.ext.gpid') || utils.deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + const data = { zoneid: bid.params.zoneId, cb: Math.floor(Math.random() * 99999999999), @@ -145,6 +147,7 @@ function makeCommonRequestData(bid, geparameter, refererInfo) { tpaf: 1, cks: 1, ib: 0, + ...(gpid ? { gpid } : {}), }; try { diff --git a/test/spec/modules/ssp_genieeBidAdapter_spec.js b/test/spec/modules/ssp_genieeBidAdapter_spec.js index dbf8ded5199..5dd3688561f 100644 --- a/test/spec/modules/ssp_genieeBidAdapter_spec.js +++ b/test/spec/modules/ssp_genieeBidAdapter_spec.js @@ -357,6 +357,59 @@ describe('ssp_genieeBidAdapter', function () { const request = spec.buildRequests([{...BANNER_BID, userId: {imuid}}]); expect(request[0].data.extuid).to.deep.equal(`im:${imuid}`); }); + + it('should include gpid when ortb2Imp.ext.gpid exists', function () { + const gpid = '/123/abc'; + const bidWithGpid = { + ...BANNER_BID, + ortb2Imp: { + ext: { + gpid: gpid + } + } + }; + const request = spec.buildRequests([bidWithGpid]); + expect(String(request[0].data.gpid)).to.have.string(gpid); + }); + + it('should include gpid when ortb2Imp.ext.data.pbadslot exists', function () { + const pbadslot = '/123/abc'; + const bidWithPbadslot = { + ...BANNER_BID, + ortb2Imp: { + ext: { + data: { + pbadslot: pbadslot + } + } + } + }; + const request = spec.buildRequests([bidWithPbadslot]); + expect(String(request[0].data.gpid)).to.have.string(pbadslot); + }); + + it('should prioritize ortb2Imp.ext.gpid over ortb2Imp.ext.data.pbadslot', function () { + const gpid = '/123/abc'; + const pbadslot = '/456/def'; + const bidWithBoth = { + ...BANNER_BID, + ortb2Imp: { + ext: { + gpid: gpid, + data: { + pbadslot: pbadslot + } + } + } + }; + const request = spec.buildRequests([bidWithBoth]); + expect(String(request[0].data.gpid)).to.have.string(gpid); + }); + + it('should not include gpid when neither ortb2Imp.ext.gpid nor ortb2Imp.ext.data.pbadslot exists', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('gpid'); + }); }); }); From 8dc92677e99f3a76017ee654255ebc1a6ca0b537 Mon Sep 17 00:00:00 2001 From: Erick Ruiz <105373073+ym-eruiz@users.noreply.github.com> Date: Thu, 27 Feb 2025 06:51:34 -0800 Subject: [PATCH 0957/1097] Yieldmo Bid Adapter : add tagid, divid and allow video.api greater than 0 (#12808) * add tagid, divid and allow video.api greater than 0 * add tagid, divid and allow video.api greater than 0 --- modules/yieldmoBidAdapter.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index af01ee73f09..384943ed20c 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -258,6 +258,8 @@ function hasVideoMediaType(bidRequest) { */ function addPlacement(request) { const gpid = deepAccess(request, 'ortb2Imp.ext.gpid') || deepAccess(request, 'ortb2Imp.ext.data.pbadslot'); + const tagid = deepAccess(request, 'ortb2Imp.ext.tagid'); + const divid = deepAccess(request, 'ortb2Imp.ext.divid'); const placementInfo = { placement_id: request.adUnitCode, callback_id: request.bidId, @@ -275,6 +277,12 @@ function addPlacement(request) { if (gpid) { placementInfo.gpid = gpid; } + if (tagid) { + placementInfo.tagid = tagid; + } + if (divid) { + placementInfo.divid = divid; + } // get the transaction id for the banner bid. const transactionId = deepAccess(request, 'ortb2Imp.ext.tid'); @@ -471,6 +479,8 @@ function getTopics(bidderRequest) { */ function openRtbImpression(bidRequest) { const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); + const tagid = deepAccess(bidRequest, 'ortb2Imp.ext.tagid'); + const divid = deepAccess(bidRequest, 'ortb2Imp.ext.divid'); const size = extractPlayerSize(bidRequest); const imp = { id: bidRequest.bidId, @@ -508,6 +518,12 @@ function openRtbImpression(bidRequest) { if (gpid) { imp.ext.gpid = gpid; } + if (tagid) { + imp.ext.tagid = tagid; + } + if (divid) { + imp.ext.divid = divid; + } return imp; } @@ -655,7 +671,8 @@ function validateVideoParams(bid) { validate('video.protocols', val => isDefined(val), paramRequired); validate('video.api', val => isDefined(val), paramRequired); - validate('video.api', val => isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), + // PS-6597 - Allow video.api to be any number greater than 0 + validate('video.api', val => isArrayOfNums(val) && val.every(v => (v >= 1)), paramInvalid, 'array of numbers, ex: [2,3]'); validate('video.playbackmethod', val => !isDefined(val) || isArrayOfNums(val), paramInvalid, From 43a49769f0d12445c616305d22812fb278f811f2 Mon Sep 17 00:00:00 2001 From: Oleksandr Solodovnikov Date: Thu, 27 Feb 2025 17:22:25 +0200 Subject: [PATCH 0958/1097] Pass replacements with request; Provide s2s winning ad source in Prebid bid meta (#12810) Co-authored-by: solodovnikov --- modules/aniviewBidAdapter.js | 19 ++++++++++++++++++- test/spec/modules/aniviewBidAdapter_spec.js | 12 ++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 70550e2daf9..d7705521c7d 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -48,6 +48,7 @@ const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); + const replacements = context.bidRequests[0]?.params?.replacements; mergeDeep(request, { ext: { @@ -56,7 +57,11 @@ const converter = ortbConverter({ pbv: '$prebid.version$', } } - }) + }); + + if (isPlainObject(replacements)) { + mergeDeep(request, { ext: { [BIDDER_CODE]: { replacements } } }); + } return request; }, @@ -82,6 +87,18 @@ const converter = ortbConverter({ mergeDeep(prebidBid, { meta: { advertiserDomains: bid.adomain || [] } }); + if (bid.ext?.aniview) { + prebidBid.meta.aniview = bid.ext.aniview + + if (prebidBid.meta.aniview.tag) { + try { + prebidBid.meta.aniview.tag = JSON.parse(bid.ext.aniview.tag) + } catch { + // Ignore the error + } + } + } + if (isVideoType(mediaType)) { if (bidRequest.mediaTypes.video.context === 'outstream') { prebidBid.renderer = createRenderer(bidRequest); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index a4ccdce1117..2c90eede1b4 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -37,6 +37,8 @@ const BANNER_SIZE = { width: 250, height: 250 }; const CUSTOM_RENDERER_URL = `https://${CUSTOM_DOMAIN}/script/6.1/prebidRenderer.js`; const DEFAULT_RENDERER_URL = `https://player.aniview.com/script/6.1/prebidRenderer.js`; +const REPLACEMENT_1 = '12345'; + const MOCK = { bidRequest: () => ({ bidderCode: 'aniview', @@ -67,6 +69,9 @@ const MOCK = { AV_PUBLISHERID: PUBLISHER_ID_2, AV_CHANNELID: CHANNEL_ID_2, playerDomain: CUSTOM_DOMAIN, + replacements: { + AV_CDIM1: REPLACEMENT_1, + }, }, mediaTypes: { video: { @@ -200,6 +205,13 @@ describe('Aniview Bid Adapter', function () { expect(imp.bidfloorcur).equal(CURRENCY); }); + it('should have replacements in request', function () { + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + const { replacements } = bidRequest[1].data.ext.aniview; + + expect(replacements.AV_CDIM1).equal(REPLACEMENT_1); + }); + it('should not have floor data in imp if getFloor returns empty object', function () { videoBidRequest.bids[1].getFloor = () => ({}); From 473093fb877d764040d6603a600411fd89aa562a Mon Sep 17 00:00:00 2001 From: CKBrennan <69460288+CKBrennan@users.noreply.github.com> Date: Thu, 27 Feb 2025 17:40:46 +0100 Subject: [PATCH 0959/1097] Overtone RTD Module : initial release (#12681) * overtoneRtdProvider and overtoneRtdProvider_spec * Added markdown * Updated overtoneRtdProvider.md with relevant changes * Update overtoneRtdProvider.md Updated markdown text for clarification * Update overtoneRtdProvider_spec.mjs Removed timeout and added additional tests * Modified for getBidRequestData test case --------- Co-authored-by: Subiksha --- modules/overtoneRtdProvider.js | 79 +++++++++++++++ modules/overtoneRtdProvider.md | 97 +++++++++++++++++++ .../spec/modules/overtoneRtdProvider_spec.mjs | 85 ++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 modules/overtoneRtdProvider.js create mode 100644 modules/overtoneRtdProvider.md create mode 100644 test/spec/modules/overtoneRtdProvider_spec.mjs diff --git a/modules/overtoneRtdProvider.js b/modules/overtoneRtdProvider.js new file mode 100644 index 00000000000..ab8b45f3544 --- /dev/null +++ b/modules/overtoneRtdProvider.js @@ -0,0 +1,79 @@ +import { submodule } from '../src/hook.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { safeJSONParse, logMessage as _logMessage } from '../src/utils.js'; + +export const OVERTONE_URL = 'https://prebid-1.overtone.ai/contextual'; + +const logMessage = (...args) => { + _logMessage('Overtone', ...args); +}; + +export async function fetchContextData(url = window.location.href) { + const pageUrl = encodeURIComponent(url); + const requestUrl = `${OVERTONE_URL}?URL=${pageUrl}&InApp=False`; + const request = window.ajaxBuilder || ajaxBuilder(); + + return new Promise((resolve, reject) => { + logMessage('Sending request to:', requestUrl); + request(requestUrl, { + success: (response) => { + const data = safeJSONParse(response); + logMessage('Fetched data:', data); + + if (!data || typeof data.status !== 'number') { + reject(new Error('Invalid response format')); + return; + } + + switch (data.status) { + case 1: // Success + resolve({ categories: data.categories || [] }); + break; + case 3: // Fail + case 4: // Ignore + resolve({ categories: [] }); + break; + default: + reject(new Error(`Unexpected response status: ${data.status}`)); + } + }, + error: (err) => { + logMessage('Error during request:', err); + reject(err); + }, + }); + }); +} + +function init(config) { + logMessage('init', config); + return true; +} + +export const overtoneRtdProvider = { + name: 'overtone', + init: init, + getBidRequestData: function (bidReqConfig, callback) { + fetchContextData() + .then((contextData) => { + if (contextData) { + if (!bidReqConfig.ortb2Fragments.global.site.ext) { + bidReqConfig.ortb2Fragments.global.site.ext = {}; + } + + bidReqConfig.ortb2Fragments.global.site.ext.data = contextData; + } + callback(); + }) + .catch((error) => { + logMessage('Error fetching context data', error); + callback(); + }); + }, +}; + +submodule('realTimeData', overtoneRtdProvider); + +export const overtoneModule = { + fetchContextData, +}; diff --git a/modules/overtoneRtdProvider.md b/modules/overtoneRtdProvider.md new file mode 100644 index 00000000000..aa2b3b0164a --- /dev/null +++ b/modules/overtoneRtdProvider.md @@ -0,0 +1,97 @@ +# Overtone Rtd Provider + +## Overview + +Module Name: Overtone Rtd Provider + +Module Type: Rtd Provider + +Maintainer: tech@overtone.ai + +The Overtone Real-Time Data (RTD) Module is a plug-and-play Prebid.js adapter designed to provide contextual classification results on the publisher’s page through Overtone’s contextual API. + + +## Downloading and Configuring the Overtone RTD Module + +Navigate to https://docs.prebid.org/download.html and select the box labeled Overtone Prebid Contextual Evaluation. If Prebid.js is already installed on your site, ensure other necessary modules and adapters are selected. Upon clicking the "Get Prebid.js" button, a customized Prebid.js version will be built with your selections. + +Direct link to the Overtone module in the Prebid.js repository: + +The client must provide Overtone with all the addresses using the Prebid module to whitelist those domains. Failure to whitelist addresses will result in an invalid request to the Overtone Contextual API. + + +## Functionality + +At a high level, the Overtone RTD Module makes requests to the Overtone Contextual API during page load. It fetches and categorizes content for each page, which is then available for targeting in Prebid.js. Contextual data includes content classifications, which help advertisers make informed decisions about ad placements. + + +## Available Classifications + +Content Categories: + +Key: categories + +Possible Values: Various identifiers such as ovtn_004, ovtn_104, etc. + +Description: Content Categories represent Overtone’s classification of page content based on its contextual analysis. + +Please contact tech@overtone.ai for more information about our exact categories in brand safety, type, and tone. + + +## Configuration Highlight + +The configuration for the Overtone RTD module in Prebid.js might resemble the following: + +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'overtone', + params: { + + } + }] + } +}); + + +## API Response Handling + +The Overtone RTD module processes responses from the Overtone Contextual API. A typical response might include the following: + +Status: Indicates the API request status (1 for success, 3 for fail, 4 for ignore). + +Categories: An array of classification identifiers. + +For example: + +{ + "categories": ["ovtn_004", "ovtn_104", "ovtn_309", "ovtn_202"], + "status": 1 +} + +The module ensures that these values are integrated into Prebid.js’s targeting configuration for the current page. + + +## Testing and Validation + +The functionality of the Overtone RTD module can be validated using the associated test suite provided in overtoneRtdProvider_spec.mjs. The test suite simulates different API response scenarios to verify module behavior under varied conditions. + +Example Test Cases: + +Successful Data Retrieval: + +Input: URL with valid classification data. + +Expected Output: Categories array populated with identifiers. + +Failed Request: + +Input: URL resulting in a failure. + +Expected Output: Empty categories array. + +Ignored URL: + +Input: URL to be ignored by the API. + +Expected Output: Empty categories array. diff --git a/test/spec/modules/overtoneRtdProvider_spec.mjs b/test/spec/modules/overtoneRtdProvider_spec.mjs new file mode 100644 index 00000000000..3da6cdff287 --- /dev/null +++ b/test/spec/modules/overtoneRtdProvider_spec.mjs @@ -0,0 +1,85 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { overtoneModule, overtoneRtdProvider } from '../../../modules/overtoneRtdProvider.js'; +import { logMessage } from '../../../src/utils.js'; + +const TEST_URLS = { + success: 'https://www.theguardian.com/film/2024/nov/15/duncan-cowles-silent-men-interview', + fail: 'https://www.nytimes.com', + ignore: 'https://wsj.com', +}; + +describe('Overtone RTD Submodule with Test URLs', function () { + let fetchContextDataStub; + let getBidRequestDataStub; + + beforeEach(function () { + fetchContextDataStub = sinon.stub(overtoneModule, 'fetchContextData').callsFake(async (url) => { + if (url === TEST_URLS.success) { + return { categories: ['ovtn_004', 'ovtn_104', 'ovtn_309', 'ovtn_202'], status: 1 }; + } + if (url === TEST_URLS.fail) { + return { categories: [], status: 3 }; + } + if (url === TEST_URLS.ignore) { + return { categories: [], status: 4 }; + } + throw new Error('Unexpected URL in test'); + }); + + getBidRequestDataStub = sinon.stub(overtoneRtdProvider, 'getBidRequestData').callsFake((config, callback) => { + console.log('ortb2Fragments value:', JSON.stringify(config.ortb2Fragments, null, 2)); + if (config.shouldFail) { + return; + } + callback(); + }); + }); + + afterEach(function () { + fetchContextDataStub.restore(); + getBidRequestDataStub.restore(); + }); + + it('should fetch and return categories for the success URL', async function () { + const data = await overtoneModule.fetchContextData(TEST_URLS.success); + logMessage(data); + expect(data).to.deep.equal({ + categories: ['ovtn_004', 'ovtn_104', 'ovtn_309', 'ovtn_202'], + status: 1, + }); + }); + + it('should return the expected structure for the fail URL', async function () { + const data = await overtoneModule.fetchContextData(TEST_URLS.fail); + expect(data).to.deep.equal({ + categories: [], + status: 3, + }); + }); + + it('should return the expected structure for the ignore URL', async function () { + const data = await overtoneModule.fetchContextData(TEST_URLS.ignore); + expect(data).to.deep.equal({ + categories: [], + status: 4, + }); + }); + + describe('getBidRequestData', function () { + it('should call callback function after execution', function (done) { + const bidReqConfig = { ortb2Fragments: { global: { site: { ext: {} } } } }; + overtoneRtdProvider.getBidRequestData(bidReqConfig, () => { + expect(true).to.be.true; + done(); + }); + }); + + it('should not call callback if config has shouldFail set to true', function () { + const bidReqConfig = { shouldFail: true, ortb2Fragments: { global: { site: { ext: {} } } } }; + const callbackSpy = sinon.spy(); + overtoneRtdProvider.getBidRequestData(bidReqConfig, callbackSpy); + sinon.assert.notCalled(callbackSpy); + }); + }); +}); From 3d4fd349ed42e718251857d0ed892b786d3e7e45 Mon Sep 17 00:00:00 2001 From: andre-gielow-ttd <124626380+andre-gielow-ttd@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:25:48 -0500 Subject: [PATCH 0960/1097] Re-add x-integration-type to TTD adapter (#12818) --- modules/ttdBidAdapter.js | 5 ++++- test/spec/modules/ttdBidAdapter_spec.js | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index e1e86494d1c..20c6e720dff 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -14,7 +14,7 @@ import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.j * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ -const BIDADAPTERVERSION = 'TTD-PREBID-2024.07.27'; +const BIDADAPTERVERSION = 'TTD-PREBID-2024.07.28'; const BIDDER_CODE = 'ttd'; const BIDDER_CODE_LONG = 'thetradedesk'; const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/'; @@ -441,6 +441,9 @@ export const spec = { data: topLevel, options: { withCredentials: true, + customHeaders: { + 'x-integration-type': 1, + }, } }; diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 4580c514609..5f21c9c3235 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -291,6 +291,13 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.publisher.id).to.equal(baseBannerBidRequests[0].params.publisherId); }); + it('sends integration type header', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest); + expect(requestBody.options).to.be.not.null; + expect(requestBody.options.customHeaders).to.be.not.null; + expect(requestBody.options.customHeaders['x-integration-type']).to.equal(1); + }); + it('sends placement id in tagid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.imp[0].tagid).to.equal(baseBannerBidRequests[0].params.placementId); From 3b24736a36ab3b81dfa3d22bb41f2343555c4639 Mon Sep 17 00:00:00 2001 From: rediads <123890182+rediads@users.noreply.github.com> Date: Fri, 28 Feb 2025 01:28:31 +0530 Subject: [PATCH 0961/1097] RediAds Bid Adapter : added params and bugfixes to backend (#12798) * added impression level handling * Fixes after testing * Add changes related to setting publisher id , imp ext stored request id, tagid * SSI param removed * Imp tag id set only if available * test check added * test case running fine --------- Co-authored-by: Symplor Co-authored-by: symplorpro Co-authored-by: symplorpro <161946060+symplorpro@users.noreply.github.com> Co-authored-by: symplor-alpha <112776856+symplor-alpha@users.noreply.github.com> Co-authored-by: Echo Symplor Co-authored-by: Charlie Symplor --- modules/rediadsBidAdapter.js | 16 +++++++--- modules/rediadsBidAdapter.md | 4 +-- test/spec/modules/rediadsBidAdapter_spec.js | 33 ++++++++++++++------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/modules/rediadsBidAdapter.js b/modules/rediadsBidAdapter.js index ec4b12f6e39..7f96135aace 100644 --- a/modules/rediadsBidAdapter.js +++ b/modules/rediadsBidAdapter.js @@ -53,11 +53,11 @@ export const spec = { } return isValid; }, - buildRequests(bidRequests, bidderRequest) { - const params = bidRequests[0]?.params || {}; + buildRequests(bidRequests = [], bidderRequest) { + const commonParams = bidRequests[0]?.params || {}; const siteContent = bidRequests[0]?.site?.content || null; let data = {}; - let FINAL_ENDPOINT_URL = params.endpoint || ENDPOINT_URL + let FINAL_ENDPOINT_URL = commonParams.endpoint || ENDPOINT_URL try { data = converter.toORTB({ bidRequests, bidderRequest }); const testBidsRequested = location.hash.includes('rediads-test-bids'); @@ -66,13 +66,21 @@ export const spec = { if (stagingEnvRequested) { FINAL_ENDPOINT_URL = STAGING_ENDPOINT_URL; } - deepSetValue(data, 'ext.rediads.params', params); + deepSetValue(data, 'site.publisher.id', commonParams.account_id); deepSetValue(data, 'site.content', siteContent); if (testBidsRequested) { deepSetValue(data, 'test', 1); logWarn(`${LOG_PREFIX} test bids are enabled as rediads-test-bids is present in page URL hash.`) } + + // handle impression/bid level requirements + data.imp.forEach((impression, idx) => { + const bidRequest = bidRequests[idx]; + if (bidRequest?.params?.slot) { + impression.tagid = bidRequest?.params?.slot; + } + }); } catch (err) { logError(`${LOG_PREFIX} encountered an error while building bid requests :: ${err}`) } diff --git a/modules/rediadsBidAdapter.md b/modules/rediadsBidAdapter.md index 28081177f41..abc40664de2 100644 --- a/modules/rediadsBidAdapter.md +++ b/modules/rediadsBidAdapter.md @@ -28,8 +28,8 @@ Please reach out to support@rediads.com for more information. bidder: "rediads", params: { account_id: '123', - site: 'rediads.com', - slot: '321' + slot: '321', // optional + endpoint: 'https://bidding.rediads.com/openrtb2/auction' // optional, only to be used if rediads team provides one } } ] diff --git a/test/spec/modules/rediadsBidAdapter_spec.js b/test/spec/modules/rediadsBidAdapter_spec.js index 97b810974be..5b23a14728e 100644 --- a/test/spec/modules/rediadsBidAdapter_spec.js +++ b/test/spec/modules/rediadsBidAdapter_spec.js @@ -3,7 +3,8 @@ import { spec } from '../../../modules/rediadsBidAdapter'; describe('rediads Bid Adapter', function () { const BIDDER_CODE = 'rediads'; - const STAGING_ENDPOINT_URL = 'https://stagingbidding.rediads.com/openrtb2/auction'; + const STAGING_ENDPOINT_URL = + 'https://stagingbidding.rediads.com/openrtb2/auction'; const bidRequest = { bidder: BIDDER_CODE, @@ -12,7 +13,10 @@ describe('rediads Bid Adapter', function () { }, mediaTypes: { banner: { - sizes: [[300, 250], [728, 90]], + sizes: [ + [300, 250], + [728, 90], + ], }, }, adUnitCode: 'adunit-code', @@ -34,7 +38,7 @@ describe('rediads Bid Adapter', function () { } else { history.replaceState(null, '', location.pathname + location.search); } - } + }; describe('isBidRequestValid', function () { it('should return true for valid bid requests', function () { @@ -55,8 +59,11 @@ describe('rediads Bid Adapter', function () { const request = requests[0]; expect(request.method).to.equal('POST'); expect(request.url).that.is.not.empty; - expect(request.data).to.have.property('ext'); - expect(request.data.ext.rediads.params).to.deep.equal(bidRequest.params); + + const sitePublisherId = request?.data?.site?.publisher?.id; + const appPublisherId = request?.data?.app?.publisher?.id; + const accountId = request?.data?.ext?.rediads?.params?.account_id; + expect(sitePublisherId || appPublisherId || accountId).to.be.ok; }); it('should include test flag if testBidsRequested is true', function () { @@ -92,8 +99,8 @@ describe('rediads Bid Adapter', function () { impid: '2ab03f1234', adm: '
Ad
', crid: 'creative123', - w: 300, - h: 250, + w: 123, + h: 321, }, ], }, @@ -109,8 +116,8 @@ describe('rediads Bid Adapter', function () { requestId: '2ab03f1234', cpm: 1.23, creativeId: 'creative123', - width: 300, - height: 250, + width: 123, + height: 321, ad: '
Ad
', }); expect(bid.mediaType).to.equal('banner'); @@ -118,7 +125,7 @@ describe('rediads Bid Adapter', function () { it('should return an empty array for invalid responses', function () { const invalidResponse = { body: {} }; - const updatedBidRequest = {...bidRequest, params: undefined} + const updatedBidRequest = { ...bidRequest, params: undefined }; const requestObj = spec.buildRequests([updatedBidRequest], bidderRequest); const bids = spec.interpretResponse(invalidResponse, requestObj[0]); expect(bids).to.be.an('array').that.is.empty; @@ -127,7 +134,11 @@ describe('rediads Bid Adapter', function () { describe('Miscellaneous', function () { it('should support multiple media types', function () { - expect(spec.supportedMediaTypes).to.include.members(['banner', 'native', 'video']); + expect(spec.supportedMediaTypes).to.include.members([ + 'banner', + 'native', + 'video', + ]); }); }); }); From 698408c0222cce4d4bf4561c8e3e760fef0adb2c Mon Sep 17 00:00:00 2001 From: Kevin Siow Date: Thu, 27 Feb 2025 21:24:06 +0100 Subject: [PATCH 0962/1097] Dailymotion bid adapter: add ortb converter and floor price support (#12784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stéphane Eintrazi --- modules/dailymotionBidAdapter.js | 64 ++- modules/dailymotionBidAdapter.md | 137 ++++- .../modules/dailymotionBidAdapter_spec.js | 473 +++++++++++++++++- 3 files changed, 630 insertions(+), 44 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 791fbccda5f..8b34e674fde 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -1,4 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { VIDEO } from '../src/mediaTypes.js'; import { deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; @@ -6,6 +7,37 @@ import { userSync } from '../src/userSync.js'; const DAILYMOTION_VENDOR_ID = 573; +const dailymotionOrtbConverter = ortbConverter({ + context: { + netRevenue: true, + ttl: 600, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + + if (typeof bidRequest.getFloor === 'function') { + const size = imp.w > 0 && imp.h > 0 ? [imp.w, imp.h] : '*'; + + const floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'video', // or '*' for all the mediaType + size + }) || {}; + + if (floorInfo.floor && floorInfo.currency) { + imp.bidfloor = floorInfo.floor; + imp.bidfloorcur = floorInfo.currency; + } + } + + return imp; + }, +}); + +function isArrayFilled (_array) { + return _array && Array.isArray(_array) && _array.length > 0; +} + /** * Get video metadata from bid request * @@ -23,6 +55,10 @@ function getVideoMetadata(bidRequest, bidderRequest) { // Content object is either from Object: Site or Object: App const contentObj = deepAccess(siteOrAppObj, 'content') + const contentCattax = deepAccess(contentObj, 'cattax', 0); + const isContentCattaxV1 = contentCattax === 1; + const isContentCattaxV2 = [2, 5, 6].includes(contentCattax); + const parsedContentData = { // Store as object keys to ensure uniqueness iabcat1: {}, @@ -49,14 +85,16 @@ function getVideoMetadata(bidRequest, bidderRequest) { const videoMetadata = { description: videoParams.description || '', duration: videoParams.duration || deepAccess(contentObj, 'len', 0), - iabcat1: Array.isArray(videoParams.iabcat1) + iabcat1: isArrayFilled(videoParams.iabcat1) ? videoParams.iabcat1 - : Array.isArray(deepAccess(contentObj, 'cat')) + : (isArrayFilled(deepAccess(contentObj, 'cat')) && isContentCattaxV1) ? contentObj.cat : Object.keys(parsedContentData.iabcat1), - iabcat2: Array.isArray(videoParams.iabcat2) + iabcat2: isArrayFilled(videoParams.iabcat2) ? videoParams.iabcat2 - : Object.keys(parsedContentData.iabcat2), + : (isArrayFilled(deepAccess(contentObj, 'cat')) && isContentCattaxV2) + ? contentObj.cat + : Object.keys(parsedContentData.iabcat2), id: videoParams.id || deepAccess(contentObj, 'id', ''), lang: videoParams.lang || deepAccess(contentObj, 'language', ''), livestream: typeof videoParams.livestream === 'number' @@ -153,6 +191,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests = [], bidderRequest) { + const ortbData = dailymotionOrtbConverter.toORTB({ bidRequests: validBidRequests, bidderRequest }); // check consent to be able to read user cookie const allowCookieReading = // No GDPR applies @@ -184,6 +223,7 @@ export const spec = { url: 'https://pb.dmxleo.com', data: { pbv: '$prebid.version$', + ortb: ortbData, bidder_request: { gdprConsent: { apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), @@ -206,20 +246,6 @@ export const spec = { api_key: bid.params.apiKey, ts: bid.params.dmTs, }, - // Cast boolean in any case (value should be 0 or 1) to ensure type - coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), - // In app context, we need to retrieve additional informations - ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { - appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), - appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), - } : {}), - ...(deepAccess(bidderRequest, 'ortb2.device') ? { - device: { - lmt: deepAccess(bidderRequest, 'ortb2.device.lmt', null), - ifa: deepAccess(bidderRequest, 'ortb2.device.ifa', ''), - atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), - }, - } : {}), userSyncEnabled: isUserSyncEnabled(), request: { adUnitCode: deepAccess(bid, 'adUnitCode', ''), @@ -261,7 +287,7 @@ export const spec = { * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: serverResponse => serverResponse?.body ? [serverResponse.body] : [], + interpretResponse: serverResponse => serverResponse?.body?.cpm ? [serverResponse.body] : [], /** * Retrieves user synchronization URLs based on provided options and consents. diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 21cc6c49205..12d5bc1c04d 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -11,6 +11,16 @@ Maintainer: ad-leo-engineering@dailymotion.com Dailymotion prebid adapter. Supports video ad units in instream context. +### Usage + +Make sure to have the following modules listed while building prebid : `priceFloors,dailymotionBidAdapter` + +`priceFloors` module is needed to retrieve the price floor: https://docs.prebid.org/dev-docs/modules/floors.html + +```shell +gulp build --modules=priceFloors,dailymotionBidAdapter +``` + ### Configuration options Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters: @@ -58,6 +68,116 @@ pbjs.setConfig({ }); ``` +#### Price floor + +The price floor can be set at the ad unit level, for example : + +```javascript +const adUnits = [{ + floors: { + currency: 'USD', + schema: { + fields: [ 'mediaType', 'size' ] + }, + values: { + 'video|300x250': 2.22, + 'video|*': 1 + } + }, + bids: [{ + bidder: 'dailymotion', + params: { + apiKey: 'dailymotion-testing', + } + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'instream', + }, + } +}]; + +// Do not forget to set an empty object for "floors" to active the price floor module +pbjs.setConfig({floors: {}}); +``` + +The following request will be sent to Dailymotion Prebid Service : + +```javascript +{ + "pbv": "9.23.0-pre", + "ortb": { + "imp": [ + { + ... + "bidfloor": 2.22, + "bidfloorcur": "USD" + } + ], + } + ... +} +``` + +Or the price floor can be set at the package level, for example : + +```javascript +const adUnits = [ + { + bids: [{ + bidder: 'dailymotion', + params: { + apiKey: 'dailymotion-testing', + } + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + playerSize: [1280,720], + context: 'instream', + }, + } + } +]; + +pbjs.setConfig({ + floors: { + data: { + currency: 'USD', + schema: { + fields: [ 'mediaType', 'size' ] + }, + values: { + 'video|300x250': 2.22, + 'video|*': 1 + } + } + } +}) +``` + +This will send the following bid floor in the request to Daiymotion Prebid Service : + +```javascript +{ + "pbv": "9.23.0-pre", + "ortb": { + "imp": [ + { + ... + "bidfloor": 1, + "bidfloorcur": "USD" + } + ], + ... + } +} +``` + +You can also [set dynamic floors](https://docs.prebid.org/dev-docs/modules/floors.html#bid-adapter-interface). + ### Test Parameters By setting the following bid parameters, you'll get a constant response to any request, to validate your adapter integration: @@ -142,6 +262,7 @@ const adUnits = [ private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/testvideo' topics: 'topic_1, topic_2', isCreatedForKids: false, videoViewsInSession: 1, @@ -161,7 +282,7 @@ const adUnits = [ maxduration: 30, playbackmethod: [3], plcmt: 1, - protocols: [7, 8, 11, 12, 13, 14] + protocols: [7, 8, 11, 12, 13, 14], startdelay: 0, w: 1280, h: 720, @@ -206,16 +327,4 @@ If you already specify [First-Party data](https://docs.prebid.org/features/first | `ortb2.site.content.keywords` | `tags` | | `ortb2.site.content.title` | `title` | | `ortb2.site.content.url` | `url` | -| `ortb2.app.bundle` | N/A | -| `ortb2.app.storeurl` | N/A | -| `ortb2.device.lmt` | N/A | -| `ortb2.device.ifa` | N/A | -| `ortb2.device.ext.atts` | N/A | - -### Integrating the adapter - -To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. - -You will then be able to use it within the bid parameters before making a bid request. - -This API key will ensure proper identification of your inventory and allow you to get real bids. +| `ortb2.*` | N/A | diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index 2a276a06b15..3f420c1a48a 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -191,7 +191,6 @@ describe('dailymotionBidAdapterTests', () => { }); expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); expect(reqData.config.ts).to.eql(bidRequestData[0].params.dmTs); - expect(reqData.coppa).to.be.true; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); @@ -1403,6 +1402,7 @@ describe('dailymotionBidAdapterTests', () => { it('validates buildRequests with content values from App', () => { const bidRequestData = [{ + getFloor: () => ({ currency: 'USD', floor: 3 }), auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId: 123456, adUnitCode: 'preroll', @@ -1448,6 +1448,7 @@ describe('dailymotionBidAdapterTests', () => { }]; const bidderRequestData = { + timeout: 4242, refererInfo: { page: 'https://publisher.com', }, @@ -1462,14 +1463,30 @@ describe('dailymotionBidAdapterTests', () => { applicableSections: [5], }, ortb2: { + bcat: ['IAB-1'], + badv: ['bcav-1'], regs: { coppa: 1, }, device: { lmt: 1, ifa: 'xxx', + devicetype: 2, + make: 'make', + model: 'model', + os: 'os', + osv: 'osv', + language: 'language', + geo: { + country: 'country', + region: 'region', + city: 'city', + zip: 'zip', + metro: 'metro' + }, ext: { atts: 2, + ifa_type: 'ifa_type' }, }, app: { @@ -1477,6 +1494,7 @@ describe('dailymotionBidAdapterTests', () => { storeurl: 'https://play.google.com/store/apps/details?id=app-bundle', content: { len: 556, + cattax: 3, data: [ { name: 'dataprovider.com', @@ -1516,6 +1534,117 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); expect(reqData.pbv).to.eql('$prebid.version$'); + + const expectedOrtb = { + 'app': { + 'bundle': 'app-bundle', + 'content': { + 'cattax': 3, + 'data': [ + { + 'ext': { + 'segtax': 4 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': 'IAB-1' + } + ] + }, + { + 'ext': { + 'segtax': 5 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': '200' + } + ], + } + ], + 'len': 556, + }, + 'storeurl': 'https://play.google.com/store/apps/details?id=app-bundle', + }, + 'badv': [ + 'bcav-1' + ], + 'bcat': [ + 'IAB-1' + ], + 'device': { + 'devicetype': 2, + 'ext': { + 'atts': 2, + 'ifa_type': 'ifa_type' + }, + 'geo': { + 'city': 'city', + 'country': 'country', + 'metro': 'metro', + 'region': 'region', + 'zip': 'zip', + }, + 'ifa': 'xxx', + 'language': 'language', + 'lmt': 1, + 'make': 'make', + 'model': 'model', + 'os': 'os', + 'osv': 'osv', + }, + 'imp': [{ + 'bidfloor': 3, + 'bidfloorcur': 'USD', + 'id': 123456, + 'secure': 1, + ...(FEATURES.VIDEO ? { + 'video': { + 'api': [ + 2, + 7 + ], + 'h': 720, + 'maxduration': 30, + 'mimes': [ + 'video/mp4' + ], + 'minduration': 5, + 'playbackmethod': [ + 3 + ], + 'plcmt': 1, + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'skipafter': 5, + 'skipmin': 10, + 'startdelay': 0, + 'w': 1280, + } + } : {}), + } + ], + 'regs': { + 'coppa': 1, + }, + 'test': 0, + 'tmax': 4242, + } + + expect(reqData.ortb.id).to.be.not.empty; + delete reqData.ortb.id; // ortb id is generated randomly + expect(reqData.ortb).to.eql(expectedOrtb); expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, @@ -1524,12 +1653,6 @@ describe('dailymotionBidAdapterTests', () => { gppConsent: bidderRequestData.gppConsent, }); expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); - expect(reqData.coppa).to.be.true; - expect(reqData.appBundle).to.eql(bidderRequestData.ortb2.app.bundle); - expect(reqData.appStoreUrl).to.eql(bidderRequestData.ortb2.app.storeurl); - expect(reqData.device.lmt).to.eql(bidderRequestData.ortb2.device.lmt); - expect(reqData.device.ifa).to.eql(bidderRequestData.ortb2.device.ifa); - expect(reqData.device.atts).to.eql(bidderRequestData.ortb2.device.ext.atts); expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); @@ -1601,6 +1724,7 @@ describe('dailymotionBidAdapterTests', () => { gdprApplies: true, }, ortb2: { + tmax: 31416, regs: { gpp: 'xxx', gpp_sid: [5], @@ -1616,6 +1740,7 @@ describe('dailymotionBidAdapterTests', () => { url: 'https://test.com/test', livestream: 1, cat: ['IAB-2'], + cattax: 1, data: [ undefined, // Undefined to check proper handling of edge cases {}, // Empty object to check proper handling of edge cases @@ -1686,7 +1811,133 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + const expectedOrtb = { + 'imp': [{ + 'id': 123456, + 'secure': 1, + ...(FEATURES.VIDEO ? { + 'video': { + 'api': [ + 2, + 7 + ], + 'startdelay': 0, + } + } : {}) + } + ], + 'regs': { + 'coppa': 0, + 'gpp': 'xxx', + 'gpp_sid': [ + 5 + ], + }, + 'site': { + 'cat': [ + 'IAB-1', + ], + 'content': { + 'cat': [ + 'IAB-2', + ], + 'cattax': 1, + 'data': [ + undefined, + {}, + { + 'ext': {} + }, + { + 'ext': { + 'segtax': 22 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': '400' + } + ] + }, + { + 'ext': { + 'segtax': 5 + }, + 'name': 'dataprovider.com', + 'segment': undefined + }, + { + 'ext': { + 'segtax': 4 + }, + 'name': 'dataprovider.com', + 'segment': undefined + }, + { + 'ext': { + 'segtax': 5 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': 2222 + } + ] + }, + { + 'ext': { + 'segtax': 5 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': '6' + } + ] + }, + { + 'ext': { + 'segtax': 5 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': '6' + } + ] + }, + { + 'ext': { + 'segtax': 5 + }, + 'name': 'dataprovider.com', + 'segment': [ + { + 'id': '17' + }, + { + 'id': '20' + } + ] + } + ], + 'id': '54321', + 'keywords': 'tag_1,tag_2,tag_3', + 'language': 'FR', + 'livestream': 1, + 'title': 'test video', + 'url': 'https://test.com/test', + } + }, + 'test': 0, + 'tmax': 31416 + } + expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.ortb.id).to.be.not.empty; + delete reqData.ortb.id; // ortb id is generated randomly + expect(reqData.ortb).to.eql(expectedOrtb); + expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, @@ -1698,7 +1949,6 @@ describe('dailymotionBidAdapterTests', () => { }, }); expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); - expect(reqData.coppa).to.be.false; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); @@ -1763,11 +2013,20 @@ describe('dailymotionBidAdapterTests', () => { const { data: reqData } = request; expect(request.url).to.equal('https://pb.dmxleo.com'); - expect(reqData.config.api_key).to.eql(bidRequestDataWithApi[0].params.apiKey); - expect(reqData.coppa).to.be.false; - expect(reqData.pbv).to.eql('$prebid.version$'); + + expect(reqData.ortb.id).to.be.not.empty; + delete reqData.ortb.id; // ortb id is generated randomly + expect(reqData.ortb).to.eql({ + 'imp': [ + { + 'id': undefined, + 'secure': 1 + }, + ], + 'test': 0 + }); expect(reqData.userSyncEnabled).to.be.false; expect(reqData.bidder_request).to.eql({ gdprConsent: { @@ -1834,6 +2093,187 @@ describe('dailymotionBidAdapterTests', () => { }); }); + describe('validates buildRequests for video metadata iabcat1 and iabcat2', () => { + let bidRequestData; + let bidderRequestData; + let request; + + beforeEach(() => { + bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + startdelay: 0, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + iabcat1: ['video-params-iabcat1'], + iabcat2: ['video-params-iabcat2'], + }, + }, + }]; + + bidderRequestData = { + timeout: 4242, + refererInfo: { + page: 'https://publisher.com', + }, + ortb2: { + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: [{ id: '1' }], + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '6' }], + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '17' }, { id: '20' }], + }, + ] + }, + } + } + }; + + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: ['dailymotion'], + filter: 'include' + }, + iframe: { + bidders: ['dailymotion'], + filter: 'exclude', + }, + }, + }, + }); + + [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + }); + + it('get iabcat1 and iabcat 2 from params video', () => { + expect(request.data.video_metadata.iabcat1).to.eql(bidRequestData[0].params.video.iabcat1); + expect(request.data.video_metadata.iabcat2).to.eql(bidRequestData[0].params.video.iabcat2); + }) + + it('get iabcat1 from content.cat and iabcat2 from data.segment', () => { + const iabCatTestsCases = [[], null, {}]; + + iabCatTestsCases.forEach((iabCat) => { + bidRequestData[0].params.video.iabcat1 = iabCat; + bidRequestData[0].params.video.iabcat2 = iabCat; + bidderRequestData.ortb2.site.content.cat = ['video-content-cat']; + bidderRequestData.ortb2.site.content.cattax = 1; + + [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.data.video_metadata.iabcat1).to.eql(bidderRequestData.ortb2.site.content.cat); + expect(request.data.video_metadata.iabcat2).to.eql(['6', '17', '20']); + }) + }) + + it('get iabcat2 from content.cat and iabcat1 from data.segment', () => { + const iabCatTestsCases = [[], null, {}]; + const cattaxV2 = [2, 5, 6]; + + cattaxV2.forEach((cattax) => { + iabCatTestsCases.forEach((iabCat) => { + bidRequestData[0].params.video.iabcat1 = iabCat; + bidRequestData[0].params.video.iabcat2 = iabCat; + bidderRequestData.ortb2.site.content.cat = ['video-content-cat']; + bidderRequestData.ortb2.site.content.cattax = cattax; + + [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.data.video_metadata.iabcat1).to.eql(['1']); + expect(request.data.video_metadata.iabcat2).to.eql(bidderRequestData.ortb2.site.content.cat); + }) + }) + }) + + it('get iabcat1 and iabcat2 from data.segmnet', () => { + const contentCatTestCases = [[], null, {}]; + const cattaxTestCases = [1, 2, 5, 6]; + + cattaxTestCases.forEach((cattax) => { + contentCatTestCases.forEach((contentCat) => { + bidRequestData[0].params.video.iabcat1 = []; + bidRequestData[0].params.video.iabcat2 = []; + bidderRequestData.ortb2.site.content.cat = contentCat; + bidderRequestData.ortb2.site.content.cattax = cattax; + + [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.data.video_metadata.iabcat1).to.eql(['1']); + expect(request.data.video_metadata.iabcat2).to.eql(['6', '17', '20']); + }) + }) + }) + }); + + it('validates buildRequests - with null floor as object for getFloor function', () => { + const bidRequest = [{ + params: { + apiKey: 'test_api_key', + }, + getFloor: () => null + }]; + + config.setConfig({ + userSync: { + syncEnabled: false, + } + }); + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequest, {}), + ); + + const { data: reqData } = request; + + expect(reqData.ortb.id).to.be.not.empty; + delete reqData.ortb.id; // ortb id is generated randomly + expect(reqData.ortb).to.eql({ + 'imp': [ + { + 'id': undefined, + 'secure': 1 + }, + ], + 'test': 0 + }); + }) + it('validates buildRequests - with empty/undefined validBidRequests', () => { expect(spec.buildRequests([], {})).to.have.lengthOf(0); @@ -1862,6 +2302,17 @@ describe('dailymotionBidAdapterTests', () => { expect(bid).to.eql(serverResponse.body); }); + it('validates interpretResponse - without bid (no cpm)', () => { + const serverResponse = { + body: { + requestId: 'test_requestid', + }, + }; + + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(0); + }); + it('validates interpretResponse - with empty/undefined serverResponse', () => { expect(spec.interpretResponse({})).to.have.lengthOf(0); From 4b8935de52ffa0b48af043bcc07041f3aadb8a9c Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 27 Feb 2025 19:20:19 -0500 Subject: [PATCH 0963/1097] Codepath alerts (#12809) * codepath notification workflow * adjusted paths * wordsmithing * Update send-notification-on-change.js --------- Co-authored-by: Patrick McCann --- .github/workflows/code-path-changes.yml | 37 +++++ .../workflows/scripts/codepath-notification | 17 +++ .../scripts/send-notification-on-change.js | 139 ++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 .github/workflows/code-path-changes.yml create mode 100644 .github/workflows/scripts/codepath-notification create mode 100644 .github/workflows/scripts/send-notification-on-change.js diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml new file mode 100644 index 00000000000..23e0c19db6d --- /dev/null +++ b/.github/workflows/code-path-changes.yml @@ -0,0 +1,37 @@ +name: Notify Code Path Changes + +on: + pull_request_target: + types: [opened, synchronize] + paths: + - '**' + +env: + OAUTH2_CLIENT_ID: ${{ secrets.OAUTH2_CLIENT_ID }} + OAUTH2_CLIENT_SECRET: ${{ secrets.OAUTH2_CLIENT_SECRET }} + OAUTH2_REFRESH_TOKEN: ${{ secrets.OAUTH2_REFRESH_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: read + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm install axios nodemailer + + - name: Run Notification Script + run: | + node .github/workflows/scripts/send-notification-on-change.js diff --git a/.github/workflows/scripts/codepath-notification b/.github/workflows/scripts/codepath-notification new file mode 100644 index 00000000000..081b5dde89d --- /dev/null +++ b/.github/workflows/scripts/codepath-notification @@ -0,0 +1,17 @@ +# when a changed file paths matches the regex, send an alert email +# structure of the file is: +# +# javascriptRegex : email address +# +# For example, in the Prebid.js repo, the file pattern is generally +# +# /modules/BIDDERCODE +# /spec/modules/BIDDERCODE +# +# The aim is to find a minimal set of regex patterns that matches any file in these paths + +rubicon|magnite : header-bidding@magnite.com +/modules/ix|/spec/modules/ix : pdu-supply-prebid@indexexchange.com +appnexus : prebid@microsoft.com +pubmatic : header-bidding@pubmatic.com +openx : prebid@openx.com diff --git a/.github/workflows/scripts/send-notification-on-change.js b/.github/workflows/scripts/send-notification-on-change.js new file mode 100644 index 00000000000..57079ef37cb --- /dev/null +++ b/.github/workflows/scripts/send-notification-on-change.js @@ -0,0 +1,139 @@ +// send-notification-on-change.js +// +// called by the code-path-changes.yml workflow, this script queries github for +// the changes in the current PR, checks the config file for whether any of those +// file paths are set to alert an email address, and sends email to multiple +// parties if needed + +const fs = require('fs'); +const path = require('path'); +const axios = require('axios'); +const nodemailer = require('nodemailer'); + +async function getAccessToken(clientId, clientSecret, refreshToken) { + try { + const response = await axios.post('https://oauth2.googleapis.com/token', { + client_id: clientId, + client_secret: clientSecret, + refresh_token: refreshToken, + grant_type: 'refresh_token', + }); + return response.data.access_token; + } catch (error) { + console.error('Failed to fetch access token:', error.response?.data || error.message); + process.exit(1); + } +} + +(async () => { + const configFilePath = path.join(__dirname, 'codepath-notification'); + const repo = process.env.GITHUB_REPOSITORY; + const prNumber = process.env.GITHUB_PR_NUMBER; + const token = process.env.GITHUB_TOKEN; + + // Generate OAuth2 access token + const clientId = process.env.OAUTH2_CLIENT_ID; + const clientSecret = process.env.OAUTH2_CLIENT_SECRET; + const refreshToken = process.env.OAUTH2_REFRESH_TOKEN; + + // validate params + if (!repo || !prNumber || !token || !clientId || !clientSecret || !refreshToken) { + console.error('Missing required environment variables.'); + process.exit(1); + } + + // the whole process is in a big try/catch. e.g. if the config file doesn't exist, github is down, etc. + try { + // Read and process the configuration file + const configFileContent = fs.readFileSync(configFilePath, 'utf-8'); + const configRules = configFileContent + .split('\n') + .filter(line => line.trim() !== '' && !line.trim().startsWith('#')) // Ignore empty lines and comments + .map(line => { + const [regex, email] = line.split(':').map(part => part.trim()); + return { regex: new RegExp(regex), email }; + }); + + // Fetch changed files from github + const [owner, repoName] = repo.split('/'); + const apiUrl = `https://api.github.com/repos/${owner}/${repoName}/pulls/${prNumber}/files`; + const response = await axios.get(apiUrl, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github.v3+json', + }, + }); + + const changedFiles = response.data.map(file => file.filename); + console.log('Changed files:', changedFiles); + + // match file pathnames that are in the config and group them by email address + const matchesByEmail = {}; + changedFiles.forEach(file => { + configRules.forEach(rule => { + if (rule.regex.test(file)) { + if (!matchesByEmail[rule.email]) { + matchesByEmail[rule.email] = []; + } + matchesByEmail[rule.email].push(file); + } + }); + }); + + // Exit successfully if no matches were found + if (Object.keys(matchesByEmail).length === 0) { + console.log('No matches found. Exiting successfully.'); + process.exit(0); + } + + console.log('Grouped matches by email:', matchesByEmail); + + // get ready to email the changes + const accessToken = await getAccessToken(clientId, clientSecret, refreshToken); + + // Configure Nodemailer with OAuth2 + // service: 'Gmail', + const transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: 'OAuth2', + user: 'info@prebid.org', + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + accessToken: accessToken + }, + }); + + // Send one email per recipient + for (const [email, files] of Object.entries(matchesByEmail)) { + const emailBody = ` + ${email}, +

+ Files relevant to your integration have been changed in open source ${repo}. The pull request is #${prNumber}. These are the files you monitor that have been modified: +

    + ${files.map(file => `
  • ${file}
  • `).join('')} +
+ `; + + try { + await transporter.sendMail({ + from: `"Prebid Info" `, + to: email, + subject: `Files have been changed in open source ${repo}`, + html: emailBody, + }); + + console.log(`Email sent successfully to ${email}`); + console.log(`${emailBody}`); + } catch (error) { + console.error(`Failed to send email to ${email}:`, error.message); + } + } + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +})(); From 59467c0a562b6b8ad175a16454397e984d45545a Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 28 Feb 2025 02:22:53 +0200 Subject: [PATCH 0964/1097] EXCO Bid Adapter: Support of new `publisherId`, `accountId` and `tagId` parameters. (#12781) * feat(excoBidAdapter): Update * banner size validations * remove banner size validation * exco bid adapter update * exco bid adapter tests * exco bid adapter doc * update jsdocs * feat(): exco bid adapter - test alignment * feat(): ExcoBidAdapter, added warning message for deprecated prameters. * feat(): ExcoBidAdapter trigger e2e tests --- modules/excoBidAdapter.js | 315 ++++++++- modules/excoBidAdapter.md | 10 +- test/spec/modules/excoBidAdapter_spec.js | 790 +++++------------------ 3 files changed, 456 insertions(+), 659 deletions(-) diff --git a/modules/excoBidAdapter.js b/modules/excoBidAdapter.js index 150d467a08a..eeadf4204f5 100644 --- a/modules/excoBidAdapter.js +++ b/modules/excoBidAdapter.js @@ -1,37 +1,306 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {getStorageManager} from '../src/storageManager.js'; + +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; import { - isBidRequestValid, createUserSyncGetter, createInterpretResponseFn, createBuildRequestsFn -} from '../libraries/vidazooUtils/bidderUtils.js'; + mergeDeep, + deepAccess, + deepSetValue, + logInfo, + logWarn, + insertUserSyncIframe, + logError, + isStr, + generateUUID, +} from '../src/utils.js'; + +export const SID = window.excoPid || generateUUID(); +export const ENDPOINT = '//v.ex.co/se/openrtb/hb/pbjs'; +const SYNC_URL = '//cdn.ex.co/sync/e15e216-l/cookie_sync.html'; +export const BIDDER_CODE = 'exco'; +const VERSION = '0.0.1'; +const CURRENCY = 'USD'; + +const SYNC = { + done: false, +}; + +export class AdapterHelpers { + doSync(gdprConsent = { consentString: '', gdprApplies: false }, accountId) { + insertUserSyncIframe( + this.createSyncUrl(gdprConsent, accountId) + ); + } + + createSyncUrl({ consentString, gppString, applicableSections, gdprApplies }, network) { + try { + const url = new URL(window.location.protocol + SYNC_URL); + const networks = [ '368531133' ]; + + if (network) { + networks.push(network); + } + + url.searchParams.set('network', networks.join(',')); + url.searchParams.set('gdpr', encodeURIComponent((Number(gdprApplies) || 0).toString())); + url.searchParams.set('gdpr_consent', encodeURIComponent(consentString || '')); + + if (gppString && applicableSections?.length) { + url.searchParams.set('gpp', encodeURIComponent(gppString)); + url.searchParams.set('gpp_sid', encodeURIComponent(applicableSections.join(','))); + } + + return url.toString(); + } catch (error) { /* Do nothing */ } + + return null; + } + + addOrtbFirstPartyData(data, bidRequests) { + const params = bidRequests[0].params || {}; + const key = data.app ? 'app' : 'site'; + + if (data[key] && data[key].publisher && params.publisherId) { + mergeDeep(data[key].publisher, { + id: bidRequests[0].params.publisherId + }); + } + } + + getExtData(bidRequests, bidderRequest) { + return { + version: VERSION, + pbversion: '$prebid.version$', + sid: SID, + aid: bidRequests[0]?.auctionId || bidderRequest.bidderRequestId, + rc: bidRequests[0]?.bidRequestsCount, + brc: bidRequests[0]?.bidderRequestsCount, + } + } + + createRequest(converter, bidRequests, bidderRequest, mediaType) { + const data = converter.toORTB({ bidRequests, bidderRequest, context: { mediaType } }); + + data.ext[BIDDER_CODE] = this.getExtData(bidRequests, bidderRequest); + + return { method: 'POST', url: ENDPOINT, data }; + } + + isVideoBid(bid) { + return deepAccess(bid, 'mediaTypes.video'); + } + + isBannerBid(bid) { + return deepAccess(bid, 'mediaTypes.banner') || !this.isVideoBid(bid); + } + + adoptVideoImp(imp, bidRequest) { + imp.id = bidRequest.adUnitCode; + + if (bidRequest.params) { + imp.tagId = bidRequest.params.tagId; + } + } + + adoptBannerImp(imp, bidRequest) { + if (bidRequest.params) { + imp.tagId = bidRequest.params.tagId; + } + } + + adoptBidResponse(bidResponse, bid, context) { + bidResponse.bidderCode = BIDDER_CODE; + + bidResponse.vastXml = bidResponse.ad || bid.adm; + + bidResponse.ad = bid.ad; + bidResponse.adUrl = bid.adUrl; + + bidResponse.mediaType = bid.mediaType || VIDEO; + bidResponse.meta.mediaType = bid.mediaType || VIDEO; + bidResponse.meta.advertiserDomains = bidResponse.meta.advertiserDomains || []; + + bidResponse.creativeId = bidResponse.creativeId || `creative-${Date.now()}`; + bidResponse.netRevenue = bid.netRevenue || true; + bidResponse.currency = CURRENCY; -const DEFAULT_SUB_DOMAIN = 'rtb'; -const BIDDER_CODE = 'exco'; -const BIDDER_VERSION = '1.0.0'; -const GVLID = 444; -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + bidResponse.cpm = bid.cpm; -export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { - return `https://${subDomain}.exco-pb.com`; + const { bidRequest } = context; + + bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'); + bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'); + + if (deepAccess(bid, 'ext.bidder.rp.advid')) { + deepSetValue(bidResponse, 'meta.advertiserId', bid.ext.bidder.rp.advid); + } + + return bidResponse; + } + + log(severity, message) { + const msg = `${BIDDER_CODE.toUpperCase()}: ${message}`; + + if (severity === 'warn') { + logWarn(msg); + } + if (severity === 'error') { + logError(msg); + } + if (severity === 'info') { + logInfo(msg); + } + } } -const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); +const helpers = new AdapterHelpers(); + +/** + * @description https://github.com/prebid/Prebid.js/blob/master/libraries/ortbConverter/README.md + */ +export const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const data = buildRequest(imps, bidderRequest, context); + + if (data.cur && !data.cur.includes('USD')) { + helpers.log('warn', 'Warning - EX.CO adapter is supporting USD only. processing with USD'); + } -const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); + data.cur = [CURRENCY]; + data.test = config.getConfig('debug') ? 1 : 0; -const getUserSyncs = createUserSyncGetter({ - iframeSyncUrl: 'https://cs.exco-pb.com/api/sync/iframe', imageSyncUrl: 'https://cs.exco-pb.com/api/sync/image' + helpers.addOrtbFirstPartyData(data, context.bidRequests || []); + + return data; + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + + imp.secure = window.location.protocol === 'http:' ? 0 : 1; + + if (imp.video) { + helpers.adoptVideoImp(imp, bidRequest) + } + + if (imp.banner) { + helpers.adoptBannerImp(imp, bidRequest); + } + + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + return helpers.adoptBidResponse(bidResponse, bid, context); + }, + context: { + ttl: 3000, + }, + processors: pbsExtensions }); export const spec = { code: BIDDER_CODE, - version: BIDDER_VERSION, - supportedMediaTypes: [BANNER, VIDEO], - gvlid: GVLID, - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs + supportedMediaTypes: [VIDEO, BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {import('../src/auction.js').BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + const props = ['accountId', 'publisherId', 'tagId']; + const missing = props.filter(prop => !bid.params[prop]); + const nonStr = props.filter(prop => !isStr(bid.params[prop])); + const existingLegacy = ['cId', 'pId'].filter(prop => bid.params[prop]); + const message = `Bid will not be sent for ad unit '${bid.adUnitCode}'`; + const suggestion = 'wrap it in quotes in your config'; + + if (existingLegacy.length) { + existingLegacy.forEach(prop => { + helpers.log('warn', `Warn: '${prop}' was deprecated.`); + }); + } + + if (missing.length) { + missing.forEach(prop => { + helpers.log('warn', `Error: '${prop}' is missing. ${message}`); + }); + + return false; + } + + if (nonStr.length) { + nonStr.forEach(prop => { + helpers.log('warn', `Error: '${prop}' must be a string (${suggestion}). ${message}`); + }); + + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {import('../src/auction.js').Bid[]} bids - an array of bids + * @param {import('../src/auction.js').BidderRequest} bidderRequest - bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bids, bidderRequest) { + const videoBids = bids.filter(bid => helpers.isVideoBid(bid)); + const bannerBids = bids.filter(bid => helpers.isBannerBid(bid)); + const requests = []; + + if (bannerBids.length) { + requests.push( + helpers.createRequest(converter, bannerBids, bidderRequest, BANNER) + ); + } + + if (videoBids.length) { + requests.push( + helpers.createRequest(converter, videoBids, bidderRequest, VIDEO) + ); + } + + return requests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {object} response A successful response from the server. + * @return {import('../src/auction.js').Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (response, request) { + const body = response?.body?.Result || response?.body || {}; + return converter.fromORTB({response: body, request: request?.data}).bids || []; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {import('../src/adapters/bidderFactory.js').SyncOptions} syncOptions Which user syncs are allowed? + * @param {object[]} serverResponses List of server's responses. + * @return {import('../src/adapters/bidderFactory.js').UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + if (syncOptions.iframeEnabled && !SYNC.done) { + helpers.doSync(gdprConsent); + SYNC.done = true; + } + + return []; + }, }; registerBidder(spec); diff --git a/modules/excoBidAdapter.md b/modules/excoBidAdapter.md index f171de73883..f00a3a45030 100644 --- a/modules/excoBidAdapter.md +++ b/modules/excoBidAdapter.md @@ -20,13 +20,9 @@ var adUnits = [ { bidder: 'exco', params: { - cId: '562524b21b1c1f08117fc7f9', - pId: '59ac17c192832d0011283fe3', - bidFloor: 0.0001, - ext: { - param1: 'loremipsum', - param2: 'dolorsitamet' - } + accountId: 'accountId', + publisherId: 'publisherId', + tagId: 'tagId', } } ] diff --git a/test/spec/modules/excoBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js index 55acb612fe6..f5703adcae6 100644 --- a/test/spec/modules/excoBidAdapter_spec.js +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -1,681 +1,213 @@ -import {expect} from 'chai'; -import { - spec as adapter, - createDomain, - storage -} from 'modules/excoBidAdapter'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; -import { - hashCode, - extractPID, - extractCID, - extractSubDomain, - getStorageItem, - setStorageItem, - tryParseJSON, - getUniqueDealId, -} from '../../../libraries/vidazooUtils/bidderUtils.js'; +import { expect } from 'chai'; +import { spec as adapter, SID, ENDPOINT, BIDDER_CODE } from 'modules/excoBidAdapter'; +import { BANNER } from '../../../src/mediaTypes'; +import { config } from '../../../src/config'; +import sinon from 'sinon'; -export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; - -const SUB_DOMAIN = 'rtb'; - -const BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': 'div-gpt-ad-12345-0', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '59db6b3b4ffaa70004f45cdc', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1, - 'ext': { - 'param1': 'loremipsum', - 'param2': 'dolorsitamet' - } - }, - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [[300, 250], [300, 600]], - 'bidderRequestId': '1fdb5ff1b6eaa7', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER], - 'ortb2Imp': { - 'ext': { - 'gpid': '0123456789', - 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' - } - } -}; - -const VIDEO_BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', - 'bidderRequestId': '12a8ae9ada9c13', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '635509f7ff6642d368cb9837', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1 - }, - 'sizes': [[545, 307]], - 'mediaTypes': { - 'video': { - 'playerSize': [[545, 307]], - 'context': 'instream', - 'mimes': [ - 'video/mp4', - 'application/javascript' - ], - 'protocols': [2, 3, 5, 6], - 'maxduration': 60, - 'minduration': 0, - 'startdelay': 0, - 'linearity': 1, - 'api': [2], - 'placement': 1 - } - }, - 'ortb2Imp': { - 'ext': { - 'gpid': '0123456789', - 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' - } - } -} - -const ORTB2_DEVICE = { - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] +describe('ExcoBidAdapter', function () { + const BID = { + bidId: '1731e91fa1236fd', + adUnitCode: '300x250', + bidder: BIDDER_CODE, + params: { + accountId: 'accountId', + publisherId: 'publisherId', + tagId: 'tagId', }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, -}; - -const BIDDER_REQUEST = { - 'gdprConsent': { - 'consentString': 'consent_string', - 'gdprApplies': true - }, - 'gppString': 'gpp_string', - 'gppSid': [7], - 'uspConsent': 'consent_string', - 'refererInfo': { - 'page': 'https://www.greatsite.com', - 'ref': 'https://www.somereferrer.com' - }, - 'ortb2': { - 'site': { - 'content': { - 'language': 'en' - } + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, }, - 'regs': { - 'gpp': 'gpp_string', - 'gpp_sid': [7], - 'coppa': 0 + transactionId: 'transactionId', + sizes: [[300, 250]], + bidderRequestId: '1677eaa35e64f46', + auctionId: 'auctionId', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }; + + const BIDDER_REQUEST = { + bidderCode: BIDDER_CODE, + auctionId: 'auctionId', + bidderRequestId: '1677eaa35e64f46', + bids: [BID], + gdprConsent: { + consentString: 'consent_string', + gdprApplies: true, }, - 'device': ORTB2_DEVICE, - } -}; - -const SERVER_RESPONSE = { - body: { - cid: 'testcid123', - results: [{ - 'ad': '', - 'price': 0.8, - 'creativeId': '12610997325162499419', - 'exp': 30, - 'width': 300, - 'height': 250, - 'advertiserDomains': ['securepubads.g.doubleclick.net'], - 'cookies': [{ - 'src': 'https://sync.com', - 'type': 'iframe' - }, { - 'src': 'https://sync.com', - 'type': 'img' - }] - }] - } -}; - -const VIDEO_SERVER_RESPONSE = { - body: { - 'cid': '635509f7ff6642d368cb9837', - 'results': [{ - 'ad': '', - 'advertiserDomains': ['exco.com'], - 'exp': 60, - 'width': 545, - 'height': 307, - 'mediaType': 'video', - 'creativeId': '12610997325162499419', - 'price': 2, - 'cookies': [] - }] - } -}; - -const REQUEST = { - data: { - width: 300, - height: 250, - bidId: '2d52001cabd527' - } -}; - -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - -describe('ExcoBidAdapter', function () { - describe('validtae spec', function () { - it('exists and is a function', function () { - expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.buildRequests).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); - }); + gppString: 'gpp_string', + gppSid: [7], + uspConsent: 'consent_string', + refererInfo: { + page: 'https://www.greatsite.com', + ref: 'https://www.somereferrer.com', + }, + ortb2: { + site: { + content: { + language: 'en', + }, + }, + device: { + w: 1309, + h: 1305, + dnt: 0, + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + language: 'en', + sua: { + source: 1, + platform: { brand: 'Windows' }, + browsers: [ + { brand: 'Not(A:Brand', version: ['99'] }, + { brand: 'Google Chrome', version: ['133'] }, + { brand: 'Chromium', version: ['133'] }, + ], + mobile: 0, + }, + } + }, + }; - it('exists and is a string', function () { - expect(adapter.code).to.exist.and.to.be.a('string'); - }); + let BUILT_REQ = null; - it('exists and contains media types', function () { - expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); - expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + describe('isBidRequestValid', function () { + it('should return false if accountId is missing', function () { + const bid = { ...BID, params: { publisherId: 'publisherId', tagId: 'tagId' } }; + expect(adapter.isBidRequestValid(bid)).to.be.false; }); - }); - describe('validate bid requests', function () { - it('should require cId', function () { - const isValid = adapter.isBidRequestValid({ - params: { - pId: 'pid' - } - }); - expect(isValid).to.be.false; + it('should return false if publisherId is missing', function () { + const bid = { ...BID, params: { accountId: 'accountId', tagId: 'tagId' } }; + expect(adapter.isBidRequestValid(bid)).to.be.false; }); - it('should require pId', function () { - const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid' - } - }); - expect(isValid).to.be.false; + it('should return false if tagId is missing', function () { + const bid = { ...BID, params: { accountId: 'accountId', publisherId: 'publisherId' } }; + expect(adapter.isBidRequestValid(bid)).to.be.false; }); - it('should validate correctly', function () { - const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - pId: 'pid' - } - }); - expect(isValid).to.be.true; + it('should return true if all required params are present', function () { + expect(adapter.isBidRequestValid(BID)).to.be.true; }); }); - describe('build requests', function () { + describe('buildRequests', function () { let sandbox; before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - exco: { - storageAllowed: true - } - }; - sandbox = sinon.sandbox.create(); + sandbox = sinon.createSandbox(); sandbox.stub(Date, 'now').returns(1000); }); - it('should build video request', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + it('should build request', function () { config.setConfig({ bidderTimeout: 3000, - enableTIDs: true - }); - const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); - expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, - data: { - adUnitCode: '63550ad1ff6642d368cba59dh5884270560', - bidFloor: 0.1, - bidId: '2d52001cabd527', - bidderVersion: adapter.version, - bidderRequestId: '12a8ae9ada9c13', - cb: 1000, - gdpr: 1, - gdprConsent: 'consent_string', - usPrivacy: 'consent_string', - gppString: 'gpp_string', - gppSid: [7], - transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - prebidVersion: version, - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - publisherId: '59ac17c192832d0011283fe3', - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - res: `${window.top.screen.width}x${window.top.screen.height}`, - schain: VIDEO_BID.schain, - sizes: ['545x307'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - device: ORTB2_DEVICE, - uniqueDealId: `${hashUrl}_${Date.now().toString()}`, - uqs: getTopWindowQueryParams(), - mediaTypes: { - video: { - api: [2], - context: 'instream', - linearity: 1, - maxduration: 60, - mimes: [ - 'video/mp4', - 'application/javascript' - ], - minduration: 0, - placement: 1, - playerSize: [[545, 307]], - protocols: [2, 3, 5, 6], - startdelay: 0 - } - }, - gpid: '0123456789', - cat: [], - contentData: [], - contentLang: 'en', - isStorageAllowed: true, - pagecat: [], - userData: [], - coppa: 0 - } + enableTIDs: true, }); - }); - it('should build banner request for each size', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); - config.setConfig({ - bidderTimeout: 3000, - enableTIDs: true - }); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, - data: { - gdprConsent: 'consent_string', - gdpr: 1, - gppString: 'gpp_string', - gppSid: [7], - usPrivacy: 'consent_string', - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - bidderRequestId: '1fdb5ff1b6eaa7', - transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - sizes: ['300x250', '300x600'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - device: ORTB2_DEVICE, - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - cb: 1000, - bidFloor: 0.1, - bidId: '2d52001cabd527', - adUnitCode: 'div-gpt-ad-12345-0', - publisherId: '59ac17c192832d0011283fe3', - uniqueDealId: `${hashUrl}_${Date.now().toString()}`, - bidderVersion: adapter.version, - prebidVersion: version, - schain: BID.schain, - res: `${window.top.screen.width}x${window.top.screen.height}`, - mediaTypes: [BANNER], - gpid: '0123456789', - uqs: getTopWindowQueryParams(), - 'ext.param1': 'loremipsum', - 'ext.param2': 'dolorsitamet', - cat: [], - contentData: [], - contentLang: 'en', - isStorageAllowed: true, - pagecat: [], - userData: [], - coppa: 0 - } - }); - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - sandbox.restore(); - }); - }); - describe('getUserSyncs', function () { - it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + const req = requests[0]; + expect(req.method).to.equal('POST'); + expect(req.url).to.equal(ENDPOINT); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' - }]); - }); + const ext = req.data.ext[BIDDER_CODE] || {}; + expect(ext.pbversion).to.equal('$prebid.version$'); + expect(ext.sid).to.equal(SID); - it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' - }]); + BUILT_REQ = req; }); - it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); - - expect(result).to.deep.equal([{ - 'url': 'https://cs.exco-pb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', - 'type': 'image' - }]); - }); - - it('should have valid user sync with coppa on response', function () { - config.setConfig({ - coppa: 1 - }); - const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); - expect(result).to.deep.equal([{ - type: 'iframe', - url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' - }]); + after(function () { + sandbox.restore(); }); }); - describe('interpret response', function () { - it('should return empty array when there is no response', function () { - const responses = adapter.interpretResponse(null); - expect(responses).to.be.empty; - }); - - it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({price: 1, ad: ''}); - expect(responses).to.be.empty; - }); - - it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); - expect(responses).to.be.empty; - }); + describe('interpretResponse', function () { + const SERVER_RESPONSE = { + body: { + id: 'b1ec10d0-a1af-44e5-8a85-cb7b1652fe81', + seatbid: [ + { + bid: [ + { + id: 'b7b6eddb-9924-425e-aa52-5eba56689abe', + impid: BID.bidId, + cpm: 10.56, + ad: '', + lurl: 'https://ads-ssp-stg.hit.buzz/loss?loss=${AUCTION_LOSS}&min_to_win=${AUCTION_MIN_TO_WIN}', + adomain: ['crest.com'], + iurl: 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/qrr9d3g.png', + crid: 'h6bvt3rl', + w: 300, + h: 250, + mtype: 2, + creativeId: 'h6bvt3rl', + netRevenue: true, + mediaType: BANNER, + ext: { + advid: 37981, + bidtype: 1, + dspid: 377, + origbidcpm: 0, + origbidcur: 'USD', + wDSPByrId: '3000', + }, + currency: 'USD', + }, + ], + }, + ], + } + }; it('should return an array of interpreted banner responses', function () { - const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + const responses = adapter.interpretResponse(SERVER_RESPONSE, BUILT_REQ); expect(responses).to.have.length(1); expect(responses[0]).to.deep.equal({ - requestId: '2d52001cabd527', - cpm: 0.8, + seatBidId: 'b7b6eddb-9924-425e-aa52-5eba56689abe', + mediaType: BANNER, + requestId: BID.bidId, + cpm: 10.56, width: 300, height: 250, - creativeId: '12610997325162499419', - currency: 'USD', - netRevenue: true, - ttl: 30, - ad: '', + adapterCode: BIDDER_CODE, + bidderCode: BIDDER_CODE, + creativeId: 'h6bvt3rl', + creative_id: 'h6bvt3rl', + ttl: 3000, meta: { - advertiserDomains: ['securepubads.g.doubleclick.net'] - } - }); - }); - - it('should get meta from response metaData', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - serverResponse.body.results[0].metaData = { - advertiserDomains: ['exco.com'], - agencyName: 'Agency Name', - }; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses[0].meta).to.deep.equal({ - advertiserDomains: ['exco.com'], - agencyName: 'Agency Name' - }); - }); - - it('should return an array of interpreted video responses', function () { - const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0]).to.deep.equal({ - requestId: '2d52001cabd527', - cpm: 2, - width: 545, - height: 307, - mediaType: 'video', - creativeId: '12610997325162499419', - currency: 'USD', + advertiserDomains: [ + 'crest.com' + ], + mediaType: BANNER + }, + ad: '', netRevenue: true, - ttl: 60, - vastXml: '', - meta: { - advertiserDomains: ['exco.com'] - } - }); - }); - - it('should take default TTL', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - delete serverResponse.body.results[0].exp; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0].ttl).to.equal(300); - }); - }); - - describe('user id system', function () { - TEST_ID_SYSTEMS.forEach((idSystemProvider) => { - const id = Date.now().toString(); - const bid = utils.deepClone(BID); - - const userId = (function () { - switch (idSystemProvider) { - case 'lipb': - return {lipbid: id}; - case 'id5id': - return {uid: id}; - default: - return id; - } - })(); - - bid.userId = { - [idSystemProvider]: userId - }; - - it(`should include 'uid.${idSystemProvider}' in request params`, function () { - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); - }); - }); - }); - - describe('alternate param names extractors', function () { - it('should return undefined when param not supported', function () { - const cid = extractCID({'c_id': '1'}); - const pid = extractPID({'p_id': '1'}); - const subDomain = extractSubDomain({'sub_domain': 'prebid'}); - expect(cid).to.be.undefined; - expect(pid).to.be.undefined; - expect(subDomain).to.be.undefined; - }); - - it('should return value when param supported', function () { - const cid = extractCID({'cId': '1'}); - const pid = extractPID({'pId': '2'}); - const subDomain = extractSubDomain({'subDomain': 'prebid'}); - expect(cid).to.be.equal('1'); - expect(pid).to.be.equal('2'); - expect(subDomain).to.be.equal('prebid'); - }); - }); - - describe('unique deal id', function () { - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - exco: { - storageAllowed: true - } - }; - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - const key = 'myKey'; - let uniqueDealId; - beforeEach(() => { - uniqueDealId = getUniqueDealId(storage, key, 0); - }) - - it('should get current unique deal id', function (done) { - // waiting some time so `now` will become past - setTimeout(() => { - const current = getUniqueDealId(storage, key); - expect(current).to.be.equal(uniqueDealId); - done(); - }, 200); - }); - - it('should get new unique deal id on expiration', function (done) { - setTimeout(() => { - const current = getUniqueDealId(storage, key, 100); - expect(current).to.not.be.equal(uniqueDealId); - done(); - }, 200) - }); - }); - - describe('storage utils', function () { - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - exco: { - storageAllowed: true - } - }; - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - it('should get value from storage with create param', function () { - const now = Date.now(); - const clock = useFakeTimers({ - shouldAdvanceTime: true, - now + currency: 'USD', + vastXml: undefined, + adUrl: undefined, }); - setStorageItem(storage, 'myKey', 2020); - const {value, created} = getStorageItem(storage, 'myKey'); - expect(created).to.be.equal(now); - expect(value).to.be.equal(2020); - expect(typeof value).to.be.equal('number'); - expect(typeof created).to.be.equal('number'); - clock.restore(); }); - it('should get external stored value', function () { - const value = 'superman' - window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem(storage, 'myExternalKey'); - expect(item).to.be.equal(value); + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null, BUILT_REQ); + expect(responses).to.have.length(0); }); - it('should parse JSON value', function () { - const data = JSON.stringify({event: 'send'}); - const {event} = tryParseJSON(data); - expect(event).to.be.equal('send'); + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({ price: 1, ad: '' }, BUILT_REQ); + expect(responses).to.have.length(0); }); - it('should get original value on parse fail', function () { - const value = 21; - const parsed = tryParseJSON(value); - expect(typeof parsed).to.be.equal('number'); - expect(parsed).to.be.equal(value); + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({ + price: null, + ad: 'great ad', + }, BUILT_REQ); + expect(responses).to.have.length(0); }); }); }); From 066465b19b5eacf61b3ef22eb7038f6b83df9f28 Mon Sep 17 00:00:00 2001 From: iagoBMS Date: Sat, 1 Mar 2025 14:53:44 -0300 Subject: [PATCH 0965/1097] BMS bid adapter: use triggerPixel for bid won notifications and enable withCredentials for requests (#12819) * wip * chore: update ENDPOINT_URL * chore: update permission for localstorage * feat(bmsBidAdapter): implement bid floor logic and update request structure * test(bmsBidAdapter): remove commented-out tests for interpretResponse * wip * wip * Refactor geolocation implementationn * chore: minor adjustments * feat: add bidWon * update test * chore: Change double quotes to single quotes * Update creativeId and creative_id values * refactor: remove unused cookie ID handling from bid request * wip * Remove deprecated BMS sample HTML and update BMS bid adapter to use JSON.stringify for requests and sendBeacon for bid won notifications * wip * Update creative.html * Update creative.html * Update BMS bid adapter to use triggerPixel for bid won notifications and enable withCredentials for requests --------- Co-authored-by: Patrick McCann --- modules/bmsBidAdapter.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/bmsBidAdapter.js b/modules/bmsBidAdapter.js index 52a309d4072..36c48db919e 100644 --- a/modules/bmsBidAdapter.js +++ b/modules/bmsBidAdapter.js @@ -1,6 +1,5 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { sendBeacon } from '../src/ajax.js'; import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { @@ -9,6 +8,7 @@ import { isPlainObject, deepSetValue, isEmpty, + triggerPixel, } from '../src/utils.js'; const BIDDER_CODE = 'bms'; const ENDPOINT_URL = @@ -102,7 +102,7 @@ export const spec = { data: JSON.stringify(ortbRequest), options: { contentType: 'text/plain', - withCredentials: false, + withCredentials: true, }, }, ]; @@ -132,6 +132,8 @@ export const spec = { requestId: bid.impid, seatBidId: bid.id, ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + nurl: bid.nurl || null, + burl: bid.burl || null, meta: { advertiserDomains: bid.adomain || [], networkId: bid.ext?.networkId || 1105, @@ -146,11 +148,11 @@ export const spec = { onBidWon: function (bid) { const { burl, nurl } = bid || {}; if (nurl) { - sendBeacon(nurl); + triggerPixel(nurl); } if (burl) { - sendBeacon(burl); + triggerPixel(burl); } }, }; From 9bfb0d231a701966d49a9cd5d03077a198dd0960 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Sat, 1 Mar 2025 09:55:13 -0800 Subject: [PATCH 0966/1097] Core: start yielding control of the main thread (#12025) * Stop using greedypromise * async tests: adxc, aso, blasto, criteo * async tests: improvedigital * use async * asyn tests: openx, pbs, pulsepoint * async tests: rubicon, silvermob, tpmn, trafficgate * async tests: userId * async tests: geolocation * async tests: auctions * refactor gpp * refactor tcf * wip: pbjs_api_spec * async tests: pbjs_api_spec * async tests: stragglers * async tests: passing * Use GreedyPromise instead of async * rename timeout to delay * Reinstate GreedyPromise as a library * async tests: pbjs_api * rename GreedyPromise to PbPromise * reset GPP data on each test * dupe checker fooled by whitespace? * Remove sync version of test utils * Fix cmUtils timeout bug * Extract consentManagement config parsing * Extract consent module config logic * fix greedy setTimeout * fix 8podAnalytics tests * Fix tests: userId * fix tests: escalax * Fix tests * Fix lint --- features.json | 3 +- gulpfile.js | 2 +- libraries/cmp/cmpClient.js | 6 +- libraries/consentManagement/cmUtils.js | 205 ++++- libraries/greedy/greedyPromise.js | 116 +++ modules/consentManagementGpp.js | 205 +---- modules/consentManagementTcf.js | 257 ++---- modules/fpdModule/index.js | 6 +- modules/id5IdSystem.js | 4 +- modules/topLevelPaapi.js | 4 +- modules/topicsFpdModule.js | 6 +- modules/userId/index.js | 33 +- src/adRendering.js | 4 +- src/auction.js | 4 +- src/consentHandler.js | 8 +- src/creativeRenderers.js | 4 +- src/debugging.js | 8 +- src/fpd/enrichment.js | 8 +- src/fpd/sua.js | 4 +- src/prebid.js | 4 +- src/utils.js | 4 +- src/utils/promise.js | 127 +-- src/utils/ttlCollection.js | 4 +- test/helpers/consentData.js | 7 +- test/helpers/fpd.js | 18 +- test/mocks/xhr.js | 2 +- test/spec/auctionmanager_spec.js | 645 +++++++-------- test/spec/libraries/cmUtils_spec.js | 225 ++++++ .../libraries/greedy/greedyPromise_spec.js | 209 +++++ test/spec/modules/adxcgBidAdapter_spec.js | 43 +- test/spec/modules/asoBidAdapter_spec.js | 76 +- test/spec/modules/blastoBidAdapter_spec.js | 41 +- test/spec/modules/bridgeuppBidAdapter_spec.js | 108 +-- test/spec/modules/ccxBidAdapter_spec.js | 10 +- .../spec/modules/consentManagementGpp_spec.js | 153 ++-- test/spec/modules/consentManagement_spec.js | 313 ++++---- test/spec/modules/criteoBidAdapter_spec.js | 299 +++---- test/spec/modules/escalaxBidAdapter_spec.js | 41 +- test/spec/modules/euidIdSystem_spec.js | 4 +- test/spec/modules/ftrackIdSystem_spec.js | 20 +- .../modules/geolocationRtdProvider_spec.js | 18 +- test/spec/modules/id5IdSystem_spec.js | 6 +- .../modules/improvedigitalBidAdapter_spec.js | 40 +- test/spec/modules/inmobiBidAdapter_spec.js | 188 +++-- test/spec/modules/lmpIdSystem_spec.js | 6 +- test/spec/modules/openxBidAdapter_spec.js | 58 +- .../modules/prebidServerBidAdapter_spec.js | 126 +-- .../modules/pubxaiAnalyticsAdapter_spec.js | 1 + .../spec/modules/pulsepointBidAdapter_spec.js | 61 +- test/spec/modules/rubiconBidAdapter_spec.js | 15 +- test/spec/modules/sharedIdSystem_spec.js | 1 + .../modules/showheroes-bsBidAdapter_spec.js | 10 +- test/spec/modules/silvermobBidAdapter_spec.js | 41 +- test/spec/modules/tpmnBidAdapter_spec.js | 13 +- .../modules/trafficgateBidAdapter_spec.js | 58 +- test/spec/modules/uid2IdSystem_spec.js | 6 +- test/spec/modules/userId_spec.js | 22 +- test/spec/unit/core/events_spec.js | 11 +- test/spec/unit/pbjs_api_spec.js | 740 ++++++++++-------- test/spec/unit/utils/promise_spec.js | 189 +---- 60 files changed, 2535 insertions(+), 2315 deletions(-) create mode 100644 libraries/greedy/greedyPromise.js create mode 100644 test/spec/libraries/cmUtils_spec.js create mode 100644 test/spec/libraries/greedy/greedyPromise_spec.js diff --git a/features.json b/features.json index 4d8377cda7d..ee89ea6abaf 100644 --- a/features.json +++ b/features.json @@ -1,5 +1,6 @@ [ "NATIVE", "VIDEO", - "UID2_CSTG" + "UID2_CSTG", + "GREEDY" ] diff --git a/gulpfile.js b/gulpfile.js index a32a2d11ce6..8cdbb833887 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -528,7 +528,7 @@ gulp.task('build-bundle-dev', gulp.series('build-creative-dev', makeDevpackPkg(s gulp.task('build-bundle-prod', gulp.series('build-creative-prod', makeWebpackPkg(standaloneDebuggingConfig), makeWebpackPkg(), gulpBundle.bind(null, false))); // build-bundle-verbose - prod bundle except names and comments are preserved. Use this to see the effects // of dead code elimination. -gulp.task('build-bundle-verbose', gulp.series('build-creative-dev', makeWebpackPkg(makeVerbose(standaloneDebuggingConfig)), makeWebpackPkg(makeVerbose()), gulpBundle.bind(null, true))); +gulp.task('build-bundle-verbose', gulp.series('build-creative-dev', makeWebpackPkg(makeVerbose(standaloneDebuggingConfig)), makeWebpackPkg(makeVerbose()), gulpBundle.bind(null, false))); // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('test-only', test); diff --git a/libraries/cmp/cmpClient.js b/libraries/cmp/cmpClient.js index 1d0b327cee4..9e7e225ddb4 100644 --- a/libraries/cmp/cmpClient.js +++ b/libraries/cmp/cmpClient.js @@ -1,4 +1,4 @@ -import {GreedyPromise} from '../../src/utils/promise.js'; +import {PbPromise} from '../../src/utils/promise.js'; /** * @typedef {function} CMPClient @@ -130,7 +130,7 @@ export function cmpClient( if (isDirect) { client = function invokeCMPDirect(params = {}) { - return new GreedyPromise((resolve, reject) => { + return new PbPromise((resolve, reject) => { const ret = cmpFrame[apiName](...resolveParams({ ...params, callback: (params.callback || mode === MODE_CALLBACK) ? wrapCallback(params.callback, resolve, reject) : undefined, @@ -144,7 +144,7 @@ export function cmpClient( win.addEventListener('message', handleMessage, false); client = function invokeCMPFrame(params, once = false) { - return new GreedyPromise((resolve, reject) => { + return new PbPromise((resolve, reject) => { // call CMP via postMessage const callId = Math.random().toString(); const msg = { diff --git a/libraries/consentManagement/cmUtils.js b/libraries/consentManagement/cmUtils.js index f4dbd81f493..508ee82a546 100644 --- a/libraries/consentManagement/cmUtils.js +++ b/libraries/consentManagement/cmUtils.js @@ -1,38 +1,185 @@ import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; -import {logError, logInfo, logWarn} from '../../src/utils.js'; - -export function consentManagementHook(name, getConsent, loadConsentData) { - function loadIfMissing(cb) { - if (getConsent()) { - logInfo('User consent information already known. Pulling internally stored information...'); - // eslint-disable-next-line standard/no-callback-literal - cb(false); - } else { - loadConsentData(cb); - } - } +import {isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../../src/utils.js'; +import {ConsentHandler} from '../../src/consentHandler.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; +import {PbPromise} from '../../src/utils/promise.js'; +import {buildActivityParams} from '../../src/activities/params.js'; +export function consentManagementHook(name, loadConsentData) { + const SEEN = new WeakSet(); return timedAuctionHook(name, function requestBidsHook(fn, reqBidsConfigObj) { - loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { - if (errMsg) { - let log = logWarn; - if (shouldCancelAuction) { - log = logError; - errMsg = `${errMsg} Canceling auction as per consentManagement config.`; - } - log(errMsg, ...extraArgs); + return loadConsentData().then(({consentData, error}) => { + if (error && (!consentData || !SEEN.has(error))) { + SEEN.add(error); + logWarn(error.message, ...(error.args || [])); + } else if (consentData) { + logInfo(`${name.toUpperCase()}: User consent information already known. Pulling internally stored information...`); } - - if (shouldCancelAuction) { - fn.stopTiming(); - if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { - reqBidsConfigObj.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } + fn.call(this, reqBidsConfigObj); + }).catch((error) => { + logError(`${error?.message} Canceling auction as per consentManagement config.`, ...(error?.args || [])); + fn.stopTiming(); + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); } else { - fn.call(this, reqBidsConfigObj); + logError('Error executing bidsBackHandler'); } }); }); } + +/** + * + * @typedef {Function} CmpLookupFn CMP lookup function. Should set up communication and keep consent data updated + * through consent data handlers' `setConsentData`. + * @param {SetProvisionalConsent} setProvisionalConsent optionally, the function can call this with provisional consent + * data, which will be used if the lookup times out before "proper" consent data can be retrieved. + * @returns {Promise<{void}>} a promise that resolves when the auction should be continued, or rejects if it should be canceled. + * + * @typedef {Function} SetProvisionalConsent + * @param {*} provisionalConsent + * @returns {void} + */ + +/** + * Look up consent data from CMP or config. + * + * @param {Object} options + * @param {String} options.name e.g. 'GPP'. Used only for log messages. + * @param {ConsentHandler} options.consentDataHandler consent data handler object (from src/consentHandler) + * @param {CmpLookupFn} options.setupCmp + * @param {Number?} options.cmpTimeout timeout (in ms) after which the auction should continue without consent data. + * @param {Number?} options.actionTimeout timeout (in ms) from when provisional consent is available to when the auction should continue with it + * @param {() => {}} options.getNullConsent consent data to use on timeout + * @returns {Promise<{error: Error, consentData: {}}>} + */ +export function lookupConsentData( + { + name, + consentDataHandler, + setupCmp, + cmpTimeout, + actionTimeout, + getNullConsent + } +) { + consentDataHandler.enable(); + let timeoutHandle; + + return new Promise((resolve, reject) => { + let provisionalConsent; + let cmpLoaded = false; + + function setProvisionalConsent(consentData) { + provisionalConsent = consentData; + if (!cmpLoaded) { + cmpLoaded = true; + actionTimeout != null && resetTimeout(actionTimeout); + } + } + + function resetTimeout(timeout) { + if (timeoutHandle != null) clearTimeout(timeoutHandle); + if (timeout != null) { + timeoutHandle = setTimeout(() => { + const consentData = consentDataHandler.getConsentData() ?? (cmpLoaded ? provisionalConsent : getNullConsent()); + const message = `timeout waiting for ${cmpLoaded ? 'user action on CMP' : 'CMP to load'}`; + consentDataHandler.setConsentData(consentData); + resolve({consentData, error: new Error(`${name} ${message}`)}); + }, timeout); + } else { + timeoutHandle = null; + } + } + setupCmp(setProvisionalConsent) + .then(() => resolve({consentData: consentDataHandler.getConsentData()}), reject); + cmpTimeout != null && resetTimeout(cmpTimeout); + }).finally(() => { + timeoutHandle && clearTimeout(timeoutHandle); + }).catch((e) => { + consentDataHandler.setConsentData(null); + throw e; + }); +} + +export function configParser( + { + namespace, + displayName, + consentDataHandler, + parseConsentData, + getNullConsent, + cmpHandlers, + DEFAULT_CMP = 'iab', + DEFAULT_CONSENT_TIMEOUT = 10000 + } = {} +) { + function msg(message) { + return `consentManagement.${namespace} ${message}`; + } + let requestBidsHook, consentDataLoaded, staticConsentData; + + return function getConsentConfig(config) { + config = config?.[namespace]; + if (!config || typeof config !== 'object') { + logWarn(msg(`config not defined, exiting consent manager module`)); + return {}; + } + let cmpHandler; + if (isStr(config.cmpApi)) { + cmpHandler = config.cmpApi; + } else { + cmpHandler = DEFAULT_CMP; + logInfo(msg(`config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`)); + } + let cmpTimeout; + if (isNumber(config.timeout)) { + cmpTimeout = config.timeout; + } else { + cmpTimeout = DEFAULT_CONSENT_TIMEOUT; + logInfo(msg(`config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`)); + } + const actionTimeout = isNumber(config.actionTimeout) ? config.actionTimeout : null; + let setupCmp; + if (cmpHandler === 'static') { + if (isPlainObject(config.consentData)) { + staticConsentData = config.consentData; + cmpTimeout = null; + setupCmp = () => new PbPromise(resolve => resolve(consentDataHandler.setConsentData(parseConsentData(staticConsentData)))) + } else { + logError(msg(`config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`)); + } + } else if (!cmpHandlers.hasOwnProperty(cmpHandler)) { + consentDataHandler.setConsentData(null); + logWarn(`${displayName} CMP framework (${cmpHandler}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + setupCmp = () => PbPromise.resolve(); + } else { + setupCmp = cmpHandlers[cmpHandler]; + } + consentDataLoaded = lookupConsentData({ + name: displayName, + consentDataHandler, + setupCmp, + cmpTimeout, + actionTimeout, + getNullConsent, + }) + const loadConsentData = () => consentDataLoaded.then(({error}) => ({error, consentData: consentDataHandler.getConsentData()})) + if (requestBidsHook == null) { + requestBidsHook = consentManagementHook(namespace, () => consentDataLoaded); + getGlobal().requestBids.before(requestBidsHook, 50); + buildActivityParams.before((next, params) => { + return next(Object.assign({[`${namespace}Consent`]: consentDataHandler.getConsentData()}, params)); + }); + } + logInfo(`${displayName} consentManagement module has been activated...`) + return { + cmpHandler, + cmpTimeout, + actionTimeout, + staticConsentData, + loadConsentData, + requestBidsHook + } + } +} diff --git a/libraries/greedy/greedyPromise.js b/libraries/greedy/greedyPromise.js new file mode 100644 index 00000000000..46acc29c805 --- /dev/null +++ b/libraries/greedy/greedyPromise.js @@ -0,0 +1,116 @@ +const SUCCESS = 0; +const FAIL = 1; + +/** + * A version of Promise that runs callbacks synchronously when it can (i.e. after it's been fulfilled or rejected). + */ +export class GreedyPromise { + #result; + #callbacks; + + constructor(resolver) { + if (typeof resolver !== 'function') { + throw new Error('resolver not a function'); + } + const result = []; + const callbacks = []; + let [resolve, reject] = [SUCCESS, FAIL].map((type) => { + return function (value) { + if (type === SUCCESS && typeof value?.then === 'function') { + value.then(resolve, reject); + } else if (!result.length) { + result.push(type, value); + while (callbacks.length) callbacks.shift()(); + } + } + }); + try { + resolver(resolve, reject); + } catch (e) { + reject(e); + } + this.#result = result; + this.#callbacks = callbacks; + } + + then(onSuccess, onError) { + const result = this.#result; + return new this.constructor((resolve, reject) => { + const continuation = () => { + let value = result[1]; + let [handler, resolveFn] = result[0] === SUCCESS ? [onSuccess, resolve] : [onError, reject]; + if (typeof handler === 'function') { + try { + value = handler(value); + } catch (e) { + reject(e); + return; + } + resolveFn = resolve; + } + resolveFn(value); + } + result.length ? continuation() : this.#callbacks.push(continuation); + }); + } + + catch(onError) { + return this.then(null, onError); + } + + finally(onFinally) { + let val; + return this.then( + (v) => { val = v; return onFinally(); }, + (e) => { val = this.constructor.reject(e); return onFinally() } + ).then(() => val); + } + + static #collect(promises, collector, done) { + let cnt = promises.length; + function clt() { + collector.apply(this, arguments); + if (--cnt <= 0 && done) done(); + } + promises.length === 0 && done ? done() : promises.forEach((p, i) => this.resolve(p).then( + (val) => clt(true, val, i), + (err) => clt(false, err, i) + )); + } + + static race(promises) { + return new this((resolve, reject) => { + this.#collect(promises, (success, result) => success ? resolve(result) : reject(result)); + }) + } + + static all(promises) { + return new this((resolve, reject) => { + let res = []; + this.#collect(promises, (success, val, i) => success ? res[i] = val : reject(val), () => resolve(res)); + }) + } + + static allSettled(promises) { + return new this((resolve) => { + let res = []; + this.#collect(promises, (success, val, i) => res[i] = success ? {status: 'fulfilled', value: val} : {status: 'rejected', reason: val}, () => resolve(res)) + }) + } + + static resolve(value) { + return new this(resolve => resolve(value)) + } + + static reject(error) { + return new this((resolve, reject) => reject(error)) + } +} + +export function greedySetTimeout(fn, delayMs = 0) { + if (delayMs > 0) { + return setTimeout(fn, delayMs) + } else { + fn() + } +} diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 2d6c6583257..cf916e58b13 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -4,39 +4,15 @@ * and make it available for any GPP supported adapters to read/pass this information to * their system and for various other features/modules in Prebid.js. */ -import {deepSetValue, isEmpty, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {deepSetValue, isEmpty, isPlainObject, isStr, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import {gppDataHandler} from '../src/adapterManager.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; -import {GreedyPromise, defer} from '../src/utils/promise.js'; -import {buildActivityParams} from '../src/activities/params.js'; -import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; +import {PbPromise, defer} from '../src/utils/promise.js'; +import {configParser} from '../libraries/consentManagement/cmUtils.js'; -const DEFAULT_CMP = 'iab'; -const DEFAULT_CONSENT_TIMEOUT = 10000; - -export let userCMP; -export let consentTimeout; -let staticConsentData; - -let consentData; -let addedConsentHook = false; - -function pipeCallbacks(fn, {onSuccess, onError}) { - new GreedyPromise((resolve) => resolve(fn())).then(onSuccess, (err) => { - if (err instanceof GPPError) { - onError(err.message, ...err.args); - } else { - onError(`GPP error:`, err); - } - }); -} - -function lookupStaticConsentData(callbacks) { - return pipeCallbacks(() => processCmpData(staticConsentData), callbacks); -} +export let consentConfig = {}; class GPPError { constructor(message, arg) { @@ -109,8 +85,8 @@ export class GPPClient { // however, from real world testing, at least some CMPs only trigger 'cmpDisplayStatus' // other CMPs may do something else yet; here we just look for 'signalStatus: not ready' on any event // to decide if consent data is likely to change - if (consentData != null && event?.pingData != null && !this.isCMPReady(event.pingData)) { - consentData = null; + if (gppDataHandler.getConsentData() != null && event?.pingData != null && !this.isCMPReady(event.pingData)) { + gppDataHandler.setConsentData(null); } } }); @@ -129,14 +105,15 @@ export class GPPClient { * @returns {Promise<{}>} a promise to GPP consent data */ updateConsent(pingData) { - return new GreedyPromise(resolve => { + return new PbPromise(resolve => { if (pingData == null || isEmpty(pingData)) { throw new GPPError('Received empty response from CMP', pingData); } - const consentData = processCmpData(pingData); + const consentData = parseConsentData(pingData); logInfo('Retrieved GPP consent from CMP:', consentData); + gppDataHandler.setConsentData(consentData); resolve(consentData); - }) + }); } /** @@ -166,176 +143,62 @@ export class GPPClient { } } -/** - * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. - * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function - * based on the appropriate result. - * @param {Object} options - An object containing the callbacks. - * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. - * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). - * @param {function(): Object} [mkCmp=cmpClient] - A function to create the CMP client. Defaults to `cmpClient`. - */ -export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) { - pipeCallbacks(() => GPPClient.get(mkCmp).refresh(), {onSuccess, onError}); +function lookupIabConsent() { + return new PbPromise((resolve) => resolve(GPPClient.get().refresh())) } // add new CMPs here, with their dedicated lookup function const cmpCallMap = { 'iab': lookupIabConsent, - 'static': lookupStaticConsentData }; -/** - * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler. - * - * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra - * error arguments that will be undefined if there's no error. - */ -function loadConsentData(cb) { - let isDone = false; - let timer = null; - - function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { - if (timer != null) { - clearTimeout(timer); - } - isDone = true; - gppDataHandler.setConsentData(consentData); - if (typeof cb === 'function') { - cb(shouldCancelAuction, errMsg, ...extraArgs); - } - } - - if (!cmpCallMap.hasOwnProperty(userCMP)) { - done(null, false, `GPP CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - return; - } - - const callbacks = { - onSuccess: (data) => done(data, false), - onError: function (msg, ...extraArgs) { - done(null, true, msg, ...extraArgs); - } - }; - cmpCallMap[userCMP](callbacks); - - if (!isDone) { - const onTimeout = () => { - const continueToAuction = (data) => { - done(data, false, 'GPP CMP did not load, continuing auction...'); - }; - pipeCallbacks(() => processCmpData(consentData), { - onSuccess: continueToAuction, - onError: () => continueToAuction(storeConsentData()) - }); - }; - if (consentTimeout === 0) { - onTimeout(); - } else { - timer = setTimeout(onTimeout, consentTimeout); - } - } -} - -/** - * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the - * user's encoded consent string from the supported CMP. Once obtained, the module will store this - * data as part of a gppConsent object which gets transferred to adapterManager's gppDataHandler object. - * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. - * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. - * @param {function} fn required; The next function in the chain, used by hook.js - */ -export const requestBidsHook = consentManagementHook('gpp', () => consentData, loadConsentData); - -function processCmpData(consentData) { +function parseConsentData(cmpData) { if ( - (consentData?.applicableSections != null && !Array.isArray(consentData.applicableSections)) || - (consentData?.gppString != null && !isStr(consentData.gppString)) || - (consentData?.parsedSections != null && !isPlainObject(consentData.parsedSections)) + (cmpData?.applicableSections != null && !Array.isArray(cmpData.applicableSections)) || + (cmpData?.gppString != null && !isStr(cmpData.gppString)) || + (cmpData?.parsedSections != null && !isPlainObject(cmpData.parsedSections)) ) { - throw new GPPError('CMP returned unexpected value during lookup process.', consentData); + throw new GPPError('CMP returned unexpected value during lookup process.', cmpData); } ['usnatv1', 'uscav1'].forEach(section => { - if (consentData?.parsedSections?.[section]) { - logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData); + if (cmpData?.parsedSections?.[section]) { + logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, cmpData); } }); - return storeConsentData(consentData); + return toConsentData(cmpData); } -/** - * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction - * @param {{}} gppData the result of calling a CMP's `getGPPData` (or equivalent) - */ -export function storeConsentData(gppData = {}) { - consentData = { +export function toConsentData(gppData = {}) { + return { gppString: gppData?.gppString, applicableSections: gppData?.applicableSections || [], parsedSections: gppData?.parsedSections || {}, gppData: gppData }; - gppDataHandler.setConsentData(gppData); - return consentData; } /** * Simply resets the module's consentData variable back to undefined, mainly for testing purposes */ export function resetConsentData() { - consentData = undefined; - userCMP = undefined; - consentTimeout = undefined; + consentConfig = {}; gppDataHandler.reset(); GPPClient.INST = null; } -/** - * A configuration function that initializes some module variables, as well as add a hook into the requestBids function - * @param {{cmp:string, timeout:number, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int)) - */ -export function setConsentConfig(config) { - config = config && config.gpp; - if (!config || typeof config !== 'object') { - logWarn('consentManagement.gpp config not defined, exiting consent manager module'); - return; - } - - if (isStr(config.cmpApi)) { - userCMP = config.cmpApi; - } else { - userCMP = DEFAULT_CMP; - logInfo(`consentManagement.gpp config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); - } - - if (isNumber(config.timeout)) { - consentTimeout = config.timeout; - } else { - consentTimeout = DEFAULT_CONSENT_TIMEOUT; - logInfo(`consentManagement.gpp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); - } - - if (userCMP === 'static') { - if (isPlainObject(config.consentData)) { - staticConsentData = config.consentData; - consentTimeout = 0; - } else { - logError(`consentManagement.gpp config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); - } - } - - logInfo('consentManagement.gpp module has been activated...'); +const parseConfig = configParser({ + namespace: 'gpp', + displayName: 'GPP', + consentDataHandler: gppDataHandler, + parseConsentData, + getNullConsent: () => toConsentData(null), + cmpHandlers: cmpCallMap +}); - if (!addedConsentHook) { - getGlobal().requestBids.before(requestBidsHook, 50); - buildActivityParams.before((next, params) => { - return next(Object.assign({gppConsent: gppDataHandler.getConsentData()}, params)); - }); - } - addedConsentHook = true; - gppDataHandler.enable(); - loadConsentData(); // immediately look up consent data to make it available without requiring an auction +export function setConsentConfig(config) { + consentConfig = parseConfig(config); + return consentConfig.loadConsentData?.()?.catch?.(() => null); } - config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); export function enrichFPDHook(next, fpd) { diff --git a/modules/consentManagementTcf.js b/modules/consentManagementTcf.js index 7de273c1380..6e8ed7b6cab 100644 --- a/modules/consentManagementTcf.js +++ b/modules/consentManagementTcf.js @@ -4,175 +4,73 @@ * and make it available for any GDPR supported adapters to read/pass this information to * their system. */ -import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {deepSetValue, isStr, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {gdprDataHandler} from '../src/adapterManager.js'; -import {includes} from '../src/polyfill.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {cmpClient} from '../libraries/cmp/cmpClient.js'; -import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; +import {configParser} from '../libraries/consentManagement/cmUtils.js'; -const DEFAULT_CMP = 'iab'; -const DEFAULT_CONSENT_TIMEOUT = 10000; -const CMP_VERSION = 2; - -export let userCMP; -export let consentTimeout; +export let consentConfig = {}; export let gdprScope; -export let staticConsentData; -let dsaPlatform = false; -let actionTimeout; - -let consentData; -let addedConsentHook = false; +let dsaPlatform; +const CMP_VERSION = 2; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { 'iab': lookupIabConsent, - 'static': lookupStaticConsentData }; -/** - * This function reads the consent string from the config to obtain the consent information of the user. - * @param {Object} options - An object containing the callbacks. - * @param {function(Object): void} options.onSuccess - Acts as a success callback when the value is read from config; pass along consentObject from CMP. - * @param {function(string, ...Object?): void} [options.onError] - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). Optional. - */ -function lookupStaticConsentData({onSuccess, onError}) { - processCmpData(staticConsentData, {onSuccess, onError}) -} - /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. - * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function - * based on the appropriate result. - * @param {Object} options - An object containing the callbacks. - * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. - * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). - * @param {function(Object): void} options.onEvent - Acts as an event callback for processing TCF data events from CMP. - */ -function lookupIabConsent({onSuccess, onError, onEvent}) { - function cmpResponseCallback(tcfData, success) { - logInfo('Received a response from CMP', tcfData); - if (success) { - onEvent(tcfData); - if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { - processCmpData(tcfData, {onSuccess, onError}); - } - } else { - onError('CMP unable to register callback function. Please check CMP setup.'); - } - } - - const cmp = cmpClient({ - apiName: '__tcfapi', - apiVersion: CMP_VERSION, - apiArgs: ['command', 'version', 'callback', 'parameter'], - }); - - if (!cmp) { - return onError('TCF2 CMP not found.'); - } - if (cmp.isDirect) { - logInfo('Detected CMP API is directly accessible, calling it now...'); - } else { - logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); - } - - cmp({ - command: 'addEventListener', - callback: cmpResponseCallback - }) -} - -/** - * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler. - * - * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra - * error arguments that will be undefined if there's no error. */ -function loadConsentData(cb) { - let isDone = false; - let timer = null; - let onTimeout, provisionalConsent; - let cmpLoaded = false; - - function resetTimeout(timeout) { - if (timer != null) { - clearTimeout(timer); - } - if (!isDone && timeout != null) { - if (timeout === 0) { - onTimeout() +function lookupIabConsent(setProvisionalConsent) { + return new Promise((resolve, reject) => { + function cmpResponseCallback(tcfData, success) { + logInfo('Received a response from CMP', tcfData); + if (success) { + try { + setProvisionalConsent(parseConsentData(tcfData)); + } catch (e) { + } + + if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { + try { + gdprDataHandler.setConsentData(parseConsentData(tcfData)); + resolve(); + } catch (e) { + reject(e); + } + } } else { - timer = setTimeout(onTimeout, timeout); + reject(Error('CMP unable to register callback function. Please check CMP setup.')) } } - } - function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { - resetTimeout(null); - isDone = true; - gdprDataHandler.setConsentData(consentData); - if (typeof cb === 'function') { - cb(shouldCancelAuction, errMsg, ...extraArgs); - } - } - - if (!includes(Object.keys(cmpCallMap), userCMP)) { - done(null, false, `CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - return; - } + const cmp = cmpClient({ + apiName: '__tcfapi', + apiVersion: CMP_VERSION, + apiArgs: ['command', 'version', 'callback', 'parameter'], + }); - const callbacks = { - onSuccess: (data) => done(data, false), - onError: function (msg, ...extraArgs) { - done(null, true, msg, ...extraArgs); - }, - onEvent: function (consentData) { - provisionalConsent = consentData; - if (cmpLoaded) return; - cmpLoaded = true; - if (actionTimeout != null) { - resetTimeout(actionTimeout); - } + if (!cmp) { + reject(new Error('TCF2 CMP not found.')) } - } - - onTimeout = () => { - const continueToAuction = (data) => { - done(data, false, `${cmpLoaded ? 'Timeout waiting for user action on CMP' : 'CMP did not load'}, continuing auction...`); + if (cmp.isDirect) { + logInfo('Detected CMP API is directly accessible, calling it now...'); + } else { + logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); } - processCmpData(provisionalConsent, { - onSuccess: continueToAuction, - onError: () => continueToAuction(storeConsentData(undefined)), - }) - } - cmpCallMap[userCMP](callbacks); - if (!(actionTimeout != null && cmpLoaded)) { - resetTimeout(consentTimeout); - } + cmp({ + command: 'addEventListener', + callback: cmpResponseCallback + }) + }) } -/** - * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the - * user's encoded consent string from the supported CMP. Once obtained, the module will store this - * data as part of a gdprConsent object which gets transferred to adapterManager's gdprDataHandler object. - * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. - * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. - * @param {function} fn required; The next function in the chain, used by hook.js - */ -export const requestBidsHook = consentManagementHook('gdpr', () => consentData, loadConsentData); - -/** - * This function checks the consent data provided by CMP to ensure it's in an expected state. - * If it's bad, we call `onError` - * If it's good, then we store the value and call `onSuccess` - */ -function processCmpData(consentObject, {onSuccess, onError}) { +function parseConsentData(consentObject) { function checkData() { // if CMP does not respond with a gdprApplies boolean, use defaultGdprScope (gdprScope) const gdprApplies = consentObject && typeof consentObject.gdprApplies === 'boolean' ? consentObject.gdprApplies : gdprScope; @@ -184,18 +82,14 @@ function processCmpData(consentObject, {onSuccess, onError}) { } if (checkData()) { - onError(`CMP returned unexpected value during lookup process.`, consentObject); + throw Object.assign(new Error(`CMP returned unexpected value during lookup process.`), {args: [consentObject]}) } else { - onSuccess(storeConsentData(consentObject)); + return toConsentData(consentObject); } } -/** - * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction - * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) - */ -function storeConsentData(cmpConsentObject) { - consentData = { +function toConsentData(cmpConsentObject) { + const consentData = { consentString: (cmpConsentObject) ? cmpConsentObject.tcString : undefined, vendorData: (cmpConsentObject) || undefined, gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope @@ -211,12 +105,18 @@ function storeConsentData(cmpConsentObject) { * Simply resets the module's consentData variable back to undefined, mainly for testing purposes */ export function resetConsentData() { - consentData = undefined; - userCMP = undefined; - consentTimeout = undefined; + consentConfig = {}; gdprDataHandler.reset(); } +const parseConfig = configParser({ + namespace: 'gdpr', + displayName: 'TCF', + consentDataHandler: gdprDataHandler, + cmpHandlers: cmpCallMap, + parseConsentData, + getNullConsent: () => toConsentData(null) +}) /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function * @param {{cmp:string, timeout:number, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int)) @@ -225,50 +125,13 @@ export function setConsentConfig(config) { // if `config.gdpr`, `config.usp` or `config.gpp` exist, assume new config format. // else for backward compatability, just use `config` config = config && (config.gdpr || config.usp || config.gpp ? config.gdpr : config); - if (!config || typeof config !== 'object') { - logWarn('consentManagement (gdpr) config not defined, exiting consent manager'); - return; - } - if (isStr(config.cmpApi)) { - userCMP = config.cmpApi; - } else { - userCMP = DEFAULT_CMP; - logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); - } - - if (isNumber(config.timeout)) { - consentTimeout = config.timeout; - } else { - consentTimeout = DEFAULT_CONSENT_TIMEOUT; - logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); - } - - actionTimeout = isNumber(config.actionTimeout) ? config.actionTimeout : null; - - // if true, then gdprApplies should be set to true - gdprScope = config.defaultGdprScope === true; - dsaPlatform = !!config.dsaPlatform; - - logInfo('consentManagement module has been activated...'); - - if (userCMP === 'static') { - if (isPlainObject(config.consentData)) { - staticConsentData = config.consentData; - if (staticConsentData?.getTCData != null) { - // accept static config with or without `getTCData` - see https://github.com/prebid/Prebid.js/issues/9581 - staticConsentData = staticConsentData.getTCData; - } - consentTimeout = 0; - } else { - logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); - } - } - if (!addedConsentHook) { - getGlobal().requestBids.before(requestBidsHook, 50); + if (config?.consentData?.getTCData != null) { + config.consentData = config.consentData.getTCData; } - addedConsentHook = true; - gdprDataHandler.enable(); - loadConsentData(); // immediately look up consent data to make it available without requiring an auction + gdprScope = config?.defaultGdprScope === true; + dsaPlatform = !!config?.dsaPlatform; + consentConfig = parseConfig({gdpr: config}); + return consentConfig.loadConsentData?.()?.catch?.(() => null); } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/fpdModule/index.js b/modules/fpdModule/index.js index 0b0c0906f84..608b1763f9b 100644 --- a/modules/fpdModule/index.js +++ b/modules/fpdModule/index.js @@ -5,7 +5,7 @@ import { config } from '../../src/config.js'; import { module, getHook } from '../../src/hook.js'; import {logError} from '../../src/utils.js'; -import {GreedyPromise} from '../../src/utils/promise.js'; +import {PbPromise} from '../../src/utils/promise.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; let submodules = []; @@ -20,12 +20,12 @@ export function reset() { export function processFpd({global = {}, bidder = {}} = {}) { let modConf = config.getConfig('firstPartyData') || {}; - let result = GreedyPromise.resolve({global, bidder}); + let result = PbPromise.resolve({global, bidder}); submodules.sort((a, b) => { return ((a.queue || 1) - (b.queue || 1)); }).forEach(submodule => { result = result.then( - ({global, bidder}) => GreedyPromise.resolve(submodule.processFpd(modConf, {global, bidder})) + ({global, bidder}) => PbPromise.resolve(submodule.processFpd(modConf, {global, bidder})) .catch((err) => { logError(`Error in FPD module ${submodule.name}`, err); return {}; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 741c6ef3063..abaebe8b16f 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -20,7 +20,7 @@ import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -import {GreedyPromise} from '../src/utils/promise.js'; +import {PbPromise} from '../src/utils/promise.js'; import {loadExternalScript} from '../src/adloader.js'; /** @@ -482,7 +482,7 @@ export class IdFetchFlow { } async function loadExternalModule(url) { - return new GreedyPromise((resolve, reject) => { + return new PbPromise((resolve, reject) => { if (window.id5Prebid) { // Already loaded resolve(); diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js index 86164668a90..4337fbb1e6b 100644 --- a/modules/topLevelPaapi.js +++ b/modules/topLevelPaapi.js @@ -5,7 +5,7 @@ import {auctionStore} from '../libraries/weakStore/weakStore.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {emit} from '../src/events.js'; import {BID_STATUS, EVENTS} from '../src/constants.js'; -import {GreedyPromise} from '../src/utils/promise.js'; +import {PbPromise} from '../src/utils/promise.js'; import {getBidToRender, getRenderingData, markWinningBid} from '../src/adRendering.js'; let getPAAPIConfig, expandFilters, moduleConfig; @@ -42,7 +42,7 @@ function bidIfRenderable(bid) { const forRenderStack = []; -function renderPaapiHook(next, adId, forRender = true, override = GreedyPromise.resolve()) { +function renderPaapiHook(next, adId, forRender = true, override = PbPromise.resolve()) { forRenderStack.push(forRender); const ids = parsePaapiAdId(adId); if (ids) { diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 8df01fcd599..c3b2c69394b 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -1,7 +1,7 @@ import {isEmpty, logError, logWarn, mergeDeep, safeJSONParse} from '../src/utils.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {submodule} from '../src/hook.js'; -import {GreedyPromise} from '../src/utils/promise.js'; +import {PbPromise} from '../src/utils/promise.js'; import {config} from '../src/config.js'; import {getCoreStorageManager} from '../src/storageManager.js'; import {includes} from '../src/polyfill.js'; @@ -92,13 +92,13 @@ export function getTopics(doc = document) { try { if (isTopicsSupported(doc)) { - topics = GreedyPromise.resolve(doc.browsingTopics()); + topics = PbPromise.resolve(doc.browsingTopics()); } } catch (e) { logError('Could not call topics API', e); } if (topics == null) { - topics = GreedyPromise.resolve([]); + topics = PbPromise.resolve([]); } return topics; diff --git a/modules/userId/index.js b/modules/userId/index.js index 3ee078f8b1c..589ecc9a44d 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -145,7 +145,7 @@ import { logWarn } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; -import {defer, GreedyPromise} from '../../src/utils/promise.js'; +import {defer, PbPromise, delay} from '../../src/utils/promise.js'; import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetrics.js'; import {findRootDomain} from '../../src/fpd/rootDomain.js'; import {allConsent, GDPR_GVLIDS} from '../../src/consentHandler.js'; @@ -547,7 +547,7 @@ function addIdData({adUnits, ortb2Fragments}) { if (adUnit.bids && isArray(adUnit.bids)) { adUnit.bids.forEach(bid => { const bidderIds = Object.assign({}, globalIds, getIds(initializedSubmodules.bidder[bid.bidder] ?? {})); - const bidderEids = globalEids.concat(ortb2Fragments.bidder[bid.bidder]?.user?.ext?.eids || []); + const bidderEids = globalEids.concat(ortb2Fragments.bidder?.[bid.bidder]?.user?.ext?.eids || []); if (Object.keys(bidderIds).length > 0) { bid.userId = bidderIds; } @@ -561,7 +561,7 @@ function addIdData({adUnits, ortb2Fragments}) { const INIT_CANCELED = {}; -function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { +function idSystemInitializer({mkDelay = delay} = {}) { const startInit = defer(); const startCallbacks = defer(); let cancel; @@ -574,7 +574,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { cancel.reject(INIT_CANCELED); } cancel = defer(); - return GreedyPromise.race([promise, cancel.promise]) + return PbPromise.race([promise, cancel.promise]) .finally(initMetrics.startTiming('userId.total')) } @@ -598,7 +598,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { } let done = cancelAndTry( - GreedyPromise.all([hooksReady, startInit.promise]) + PbPromise.all([hooksReady, startInit.promise]) .then(timeConsent) .then(checkRefs(() => { initSubmodules(initModules, allModules); @@ -607,7 +607,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { .then(checkRefs(() => { const modWithCb = initModules.submodules.filter(item => isFn(item.callback)); if (modWithCb.length) { - return new GreedyPromise((resolve) => processSubmoduleCallbacks(modWithCb, resolve, initModules)); + return new PbPromise((resolve) => processSubmoduleCallbacks(modWithCb, resolve, initModules)); } })) ); @@ -627,7 +627,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { } else { events.on(EVENTS.AUCTION_END, function auctionEndHandler() { events.off(EVENTS.AUCTION_END, auctionEndHandler); - delay(syncDelay).then(startCallbacks.resolve); + mkDelay(syncDelay).then(startCallbacks.resolve); }); } } @@ -645,7 +645,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { return sm.callback != null; }); if (cbModules.length) { - return new GreedyPromise((resolve) => processSubmoduleCallbacks(cbModules, resolve, initModules)); + return new PbPromise((resolve) => processSubmoduleCallbacks(cbModules, resolve, initModules)); } })) ); @@ -678,10 +678,10 @@ function getPPID(eids = getUserIdsAsEids() || []) { * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const startAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { - GreedyPromise.race([ +export const startAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {mkDelay = delay, getIds = getUserIdsAsync} = {}) { + PbPromise.race([ getIds().catch(() => null), - delay(auctionDelay) + mkDelay(auctionDelay) ]).then(() => { addIdData(reqBidsConfigObj); uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); @@ -809,7 +809,7 @@ function retryOnCancel(initParams) { return Promise.resolve().then(getUserIdsAsync) } else { logError('Error initializing userId', e) - return GreedyPromise.reject(e) + return PbPromise.reject(e) } } ); @@ -1199,12 +1199,12 @@ function normalizePromise(fn) { * so a callback is added to fire after the consentManagement module. * @param {{getConfig:function}} config */ -export function init(config, {delay = GreedyPromise.timeout} = {}) { +export function init(config, {mkDelay = delay} = {}) { ppidSource = undefined; submodules = []; configRegistry = []; initializedSubmodules = mkPriorityMaps(); - initIdSystem = idSystemInitializer({delay}); + initIdSystem = idSystemInitializer({mkDelay}); if (configListener != null) { configListener(); } @@ -1241,6 +1241,11 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { } } +export function resetUserIds() { + config.setConfig({userSync: {}}) + init(config); +} + // init config update listener to start the application init(config); diff --git a/src/adRendering.js b/src/adRendering.js index 18ee2f3a598..ca25e920d2a 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -16,7 +16,7 @@ import {auctionManager} from './auctionManager.js'; import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; -import {GreedyPromise} from './utils/promise.js'; +import {PbPromise} from './utils/promise.js'; import adapterManager from './adapterManager.js'; import {useMetrics} from './utils/perfMetrics.js'; import {filters} from './targeting.js'; @@ -25,7 +25,7 @@ import {EVENT_TYPE_WIN, parseEventTrackers, TRACKER_METHOD_IMG} from './eventTra const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON, EXPIRED_RENDER } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; -export const getBidToRender = hook('sync', function (adId, forRender = true, override = GreedyPromise.resolve()) { +export const getBidToRender = hook('sync', function (adId, forRender = true, override = PbPromise.resolve()) { return override .then(bid => bid ?? auctionManager.findBidByAdId(adId)) .catch(() => {}) diff --git a/src/auction.js b/src/auction.js index c7ccaa27b14..c85d7458a44 100644 --- a/src/auction.js +++ b/src/auction.js @@ -92,7 +92,7 @@ import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; -import {defer, GreedyPromise} from './utils/promise.js'; +import {defer, PbPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; import {getGlobal} from './prebidGlobal.js'; @@ -547,7 +547,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM return addBid; })(), adapterDone: function () { - responsesReady(GreedyPromise.resolve()).finally(() => adapterDone.call(this)); + responsesReady(PbPromise.resolve()).finally(() => adapterDone.call(this)); } } } diff --git a/src/consentHandler.js b/src/consentHandler.js index 87d1e1a6e23..69abc660f93 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -1,5 +1,5 @@ import {cyrb53Hash, isStr, timestamp} from './utils.js'; -import {defer, GreedyPromise} from './utils/promise.js'; +import {defer, PbPromise} from './utils/promise.js'; import {config} from './config.js'; /** @@ -68,7 +68,7 @@ export class ConsentHandler { */ get promise() { if (this.#ready) { - return GreedyPromise.resolve(this.#data); + return PbPromise.resolve(this.#data); } if (!this.#enabled) { this.#resolve(null); @@ -191,7 +191,7 @@ export const coppaDataHandler = (() => { getConsentMeta: getCoppa, reset() {}, get promise() { - return GreedyPromise.resolve(getCoppa()) + return PbPromise.resolve(getCoppa()) }, get hash() { return getCoppa() ? '1' : '0' @@ -218,7 +218,7 @@ export function multiHandler(handlers = ALL_HANDLERS) { return Object.assign( { get promise() { - return GreedyPromise.all(handlers.map(([name, handler]) => handler.promise.then(val => [name, val]))) + return PbPromise.all(handlers.map(([name, handler]) => handler.promise.then(val => [name, val]))) .then(entries => Object.fromEntries(entries)); }, get hash() { diff --git a/src/creativeRenderers.js b/src/creativeRenderers.js index 8331c23c8de..ee0534a72b5 100644 --- a/src/creativeRenderers.js +++ b/src/creativeRenderers.js @@ -1,4 +1,4 @@ -import {GreedyPromise} from './utils/promise.js'; +import {PbPromise} from './utils/promise.js'; import {createInvisibleIframe} from './utils.js'; import {RENDERER} from '../libraries/creative-renderer-display/renderer.js'; import {hook} from './hook.js'; @@ -12,7 +12,7 @@ export const getCreativeRenderer = (function() { return function (bidResponse) { const src = getCreativeRendererSource(bidResponse); if (!renderers.hasOwnProperty(src)) { - renderers[src] = new GreedyPromise((resolve) => { + renderers[src] = new PbPromise((resolve) => { const iframe = createInvisibleIframe(); iframe.srcdoc = ``; iframe.onload = () => resolve(iframe.contentWindow.render); diff --git a/src/debugging.js b/src/debugging.js index 69c129da9a2..663cba23e9c 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -4,7 +4,7 @@ import {getGlobal} from './prebidGlobal.js'; import {logMessage, prefixLog} from './utils.js'; import {createBid} from './bidfactory.js'; import {loadExternalScript} from './adloader.js'; -import {GreedyPromise} from './utils/promise.js'; +import {PbPromise} from './utils/promise.js'; import { MODULE_TYPE_PREBID } from './activities/modules.js'; export const DEBUG_KEY = '__$$PREBID_GLOBAL$$_debugging__'; @@ -14,7 +14,7 @@ function isDebuggingInstalled() { } function loadScript(url) { - return new GreedyPromise((resolve) => { + return new PbPromise((resolve) => { loadExternalScript(url, MODULE_TYPE_PREBID, 'debugging', resolve); }); } @@ -23,7 +23,7 @@ export function debuggingModuleLoader({alreadyInstalled = isDebuggingInstalled, let loading = null; return function () { if (loading == null) { - loading = new GreedyPromise((resolve, reject) => { + loading = new PbPromise((resolve, reject) => { // run this in a 0-delay timeout to give installedModules time to be populated setTimeout(() => { if (alreadyInstalled()) { @@ -47,7 +47,7 @@ export function debuggingControls({load = debuggingModuleLoader(), hook = getHoo let promise = null; let enabled = false; function waitForDebugging(next, ...args) { - return (promise || GreedyPromise.resolve()).then(() => next.apply(this, args)) + return (promise || PbPromise.resolve()).then(() => next.apply(this, args)) } function enable() { if (!enabled) { diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index ec5047238a2..97f9e434128 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -4,7 +4,7 @@ import {findRootDomain} from './rootDomain.js'; import {deepSetValue, getDefinedParams, getDNT, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; import {config} from '../config.js'; import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; -import {GreedyPromise} from '../utils/promise.js'; +import {PbPromise} from '../utils/promise.js'; import {CLIENT_SECTIONS, clientSectionChecker, hasSection} from './oneClient.js'; import {isActivityAllowed} from '../activities/rules.js'; import {activityParams} from '../activities/activityParams.js'; @@ -30,7 +30,7 @@ const oneClient = clientSectionChecker('FPD') export const enrichFPD = hook('sync', (fpd) => { const promArr = [fpd, getSUA().catch(() => null), tryToGetCdepLabel().catch(() => null)]; - return GreedyPromise.all(promArr) + return PbPromise.all(promArr) .then(([ortb2, sua, cdep]) => { const ri = dep.getRefererInfo(); Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { @@ -74,7 +74,7 @@ function winFallback(fn) { function getSUA() { const hints = config.getConfig('firstPartyData.uaHints'); return !Array.isArray(hints) || hints.length === 0 - ? GreedyPromise.resolve(dep.getLowEntropySUA()) + ? PbPromise.resolve(dep.getLowEntropySUA()) : dep.getHighEntropySUA(hints); } @@ -83,7 +83,7 @@ function removeUndef(obj) { } function tryToGetCdepLabel() { - return GreedyPromise.resolve('cookieDeprecationLabel' in navigator && isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep')) && navigator.cookieDeprecationLabel.getValue()); + return PbPromise.resolve('cookieDeprecationLabel' in navigator && isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep')) && navigator.cookieDeprecationLabel.getValue()); } const ENRICHMENTS = { diff --git a/src/fpd/sua.js b/src/fpd/sua.js index 565c3e1fd52..ac34951e347 100644 --- a/src/fpd/sua.js +++ b/src/fpd/sua.js @@ -1,5 +1,5 @@ import {isEmptyStr, isStr, isEmpty} from '../utils.js'; -import {GreedyPromise} from '../utils/promise.js'; +import {PbPromise} from '../utils/promise.js'; export const SUA_SOURCE_UNKNOWN = 0; export const SUA_SOURCE_LOW_ENTROPY = 1; @@ -60,7 +60,7 @@ export function highEntropySUAAccessor(uaData = window.navigator?.userAgentData) return isEmpty(result) ? null : Object.freeze(uaDataToSUA(SUA_SOURCE_HIGH_ENTROPY, result)) }).catch(() => null); } catch (e) { - cache[key] = GreedyPromise.resolve(null); + cache[key] = PbPromise.resolve(null); } } return cache[key]; diff --git a/src/prebid.js b/src/prebid.js index 684464eb2b1..55773decbeb 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -36,7 +36,7 @@ import {default as adapterManager, getS2SBidderSet} from './adapterManager.js'; import { BID_STATUS, EVENTS, NATIVE_KEYS } from './constants.js'; import * as events from './events.js'; import {newMetrics, useMetrics} from './utils/perfMetrics.js'; -import {defer, GreedyPromise} from './utils/promise.js'; +import {defer, PbPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; import { @@ -565,7 +565,7 @@ pbjsInstance.requestBids = (function() { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, deepClone(cfg.ortb2)]).filter(([_, ortb2]) => ortb2 != null)) } - return enrichFPD(GreedyPromise.resolve(ortb2Fragments.global)).then(global => { + return enrichFPD(PbPromise.resolve(ortb2Fragments.global)).then(global => { ortb2Fragments.global = global; return startAuction({bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2Fragments, metrics, defer}); }) diff --git a/src/utils.js b/src/utils.js index e87e9a2c61a..cce23e724e3 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,7 +2,7 @@ import {config} from './config.js'; import {klona} from 'klona/json'; import {includes} from './polyfill.js'; import {EVENTS} from './constants.js'; -import {GreedyPromise} from './utils/promise.js'; +import {PbPromise} from './utils/promise.js'; import {getGlobal} from './prebidGlobal.js'; import { default as deepAccess } from 'dlv/index.js'; @@ -443,7 +443,7 @@ export function insertElement(elm, doc, target, asLastChildChild) { */ export function waitForElementToLoad(element, timeout) { let timer = null; - return new GreedyPromise((resolve) => { + return new PbPromise((resolve) => { const onLoad = function() { element.removeEventListener('load', onLoad); element.removeEventListener('error', onLoad); diff --git a/src/utils/promise.js b/src/utils/promise.js index 0cf0a47eb8e..5c13c09c78d 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -1,128 +1,19 @@ -const SUCCESS = 0; -const FAIL = 1; +import {GreedyPromise, greedySetTimeout} from '../../libraries/greedy/greedyPromise.js'; +import {getGlobal} from '../prebidGlobal.js'; -/** - * A version of Promise that runs callbacks synchronously when it can (i.e. after it's been fulfilled or rejected). - */ -export class GreedyPromise { - #result; - #callbacks; - - /** - * Convenience wrapper for setTimeout; takes care of returning an already fulfilled GreedyPromise when the delay is zero. - * - * @param {Number} delayMs delay in milliseconds - * @returns {GreedyPromise} a promise that resolves (to undefined) in `delayMs` milliseconds - */ - static timeout(delayMs = 0) { - return new GreedyPromise((resolve) => { - delayMs === 0 ? resolve() : setTimeout(resolve, delayMs); - }); - } - - constructor(resolver) { - if (typeof resolver !== 'function') { - throw new Error('resolver not a function'); - } - const result = []; - const callbacks = []; - let [resolve, reject] = [SUCCESS, FAIL].map((type) => { - return function (value) { - if (type === SUCCESS && typeof value?.then === 'function') { - value.then(resolve, reject); - } else if (!result.length) { - result.push(type, value); - while (callbacks.length) callbacks.shift()(); - } - } - }); - try { - resolver(resolve, reject); - } catch (e) { - reject(e); - } - this.#result = result; - this.#callbacks = callbacks; - } - - then(onSuccess, onError) { - const result = this.#result; - return new this.constructor((resolve, reject) => { - const continuation = () => { - let value = result[1]; - let [handler, resolveFn] = result[0] === SUCCESS ? [onSuccess, resolve] : [onError, reject]; - if (typeof handler === 'function') { - try { - value = handler(value); - } catch (e) { - reject(e); - return; - } - resolveFn = resolve; - } - resolveFn(value); - } - result.length ? continuation() : this.#callbacks.push(continuation); - }); - } - - catch(onError) { - return this.then(null, onError); - } - - finally(onFinally) { - let val; - return this.then( - (v) => { val = v; return onFinally(); }, - (e) => { val = this.constructor.reject(e); return onFinally() } - ).then(() => val); - } +export const pbSetTimeout = getGlobal().setTimeout ?? (FEATURES.GREEDY ? greedySetTimeout : setTimeout) +export const PbPromise = getGlobal().Promise ?? (FEATURES.GREEDY ? GreedyPromise : Promise); - static #collect(promises, collector, done) { - let cnt = promises.length; - function clt() { - collector.apply(this, arguments); - if (--cnt <= 0 && done) done(); - } - promises.length === 0 && done ? done() : promises.forEach((p, i) => this.resolve(p).then( - (val) => clt(true, val, i), - (err) => clt(false, err, i) - )); - } - - static race(promises) { - return new this((resolve, reject) => { - this.#collect(promises, (success, result) => success ? resolve(result) : reject(result)); - }) - } - - static all(promises) { - return new this((resolve, reject) => { - let res = []; - this.#collect(promises, (success, val, i) => success ? res[i] = val : reject(val), () => resolve(res)); - }) - } - - static allSettled(promises) { - return new this((resolve) => { - let res = []; - this.#collect(promises, (success, val, i) => res[i] = success ? {status: 'fulfilled', value: val} : {status: 'rejected', reason: val}, () => resolve(res)) - }) - } - - static resolve(value) { - return new this(resolve => resolve(value)) - } - - static reject(error) { - return new this((resolve, reject) => reject(error)) - } +export function delay(delayMs = 0) { + return new PbPromise((resolve) => { + pbSetTimeout(resolve, delayMs); + }); } /** * @returns a {promise, resolve, reject} trio where `promise` is resolved by calling `resolve` or `reject`. */ -export function defer({promiseFactory = (resolver) => new GreedyPromise(resolver)} = {}) { +export function defer({promiseFactory = (resolver) => new PbPromise(resolver)} = {}) { function invoker(delegate) { return (val) => delegate(val); } diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index ffc11433d06..6a3b13c1159 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -1,4 +1,4 @@ -import {GreedyPromise} from './promise.js'; +import {PbPromise} from './promise.js'; import {binarySearch, logError, timestamp} from '../utils.js'; import {setFocusTimeout} from './focusTimeout.js'; @@ -93,7 +93,7 @@ export function ttlCollection( let currentCall; return function() { const thisCall = currentCall = {}; - GreedyPromise.resolve(getter(item)).then((val) => { + PbPromise.resolve(getter(item)).then((val) => { if (thisCall === currentCall) { values[field] = val; update(); diff --git a/test/helpers/consentData.js b/test/helpers/consentData.js index c708e397bd6..3ebb4506704 100644 --- a/test/helpers/consentData.js +++ b/test/helpers/consentData.js @@ -1,12 +1,13 @@ -import {gdprDataHandler} from 'src/adapterManager.js'; -import {GreedyPromise} from '../../src/utils/promise.js'; +import {gdprDataHandler, gppDataHandler} from 'src/adapterManager.js'; +import {PbPromise} from '../../src/utils/promise.js'; export function mockGdprConsent(sandbox, getConsentData = () => null) { sandbox.stub(gdprDataHandler, 'enabled').get(() => true) - sandbox.stub(gdprDataHandler, 'promise').get(() => GreedyPromise.resolve(getConsentData())); + sandbox.stub(gdprDataHandler, 'promise').get(() => PbPromise.resolve(getConsentData())); sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(getConsentData) } beforeEach(() => { gdprDataHandler.reset(); + gppDataHandler.reset(); }) diff --git a/test/helpers/fpd.js b/test/helpers/fpd.js index 89755f26541..7a29c9fc92d 100644 --- a/test/helpers/fpd.js +++ b/test/helpers/fpd.js @@ -1,5 +1,5 @@ import {dep, enrichFPD} from 'src/fpd/enrichment.js'; -import {GreedyPromise} from '../../src/utils/promise.js'; +import {PbPromise} from '../../src/utils/promise.js'; import {deepClone} from '../../src/utils.js'; import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; @@ -13,7 +13,7 @@ export function mockFpdEnrichments(sandbox, overrides = {}) { return window }, getHighEntropySUA() { - return GreedyPromise.resolve() + return PbPromise.resolve() } }, overrides) Object.entries(overrides) @@ -35,11 +35,9 @@ export function mockFpdEnrichments(sandbox, overrides = {}) { export function addFPDEnrichments(ortb2 = {}, overrides) { const sandbox = sinon.sandbox.create(); mockFpdEnrichments(sandbox, overrides) - return enrichFPD(GreedyPromise.resolve(deepClone(ortb2))).finally(() => sandbox.restore()); + return enrichFPD(PbPromise.resolve(deepClone(ortb2))).finally(() => sandbox.restore()); } -export const syncAddFPDEnrichments = synchronize(addFPDEnrichments); - export function addFPDToBidderRequest(bidderRequest, overrides) { overrides = Object.assign({}, { getRefererInfo() { @@ -59,13 +57,3 @@ export function addFPDToBidderRequest(bidderRequest, overrides) { } }); } - -export const syncAddFPDToBidderRequest = synchronize(addFPDToBidderRequest); - -function synchronize(fn) { - return function () { - let result; - fn.apply(this, arguments).then(res => { result = res }); - return result; - } -} diff --git a/test/mocks/xhr.js b/test/mocks/xhr.js index e7b1d96f0a4..8c0d5a27b19 100644 --- a/test/mocks/xhr.js +++ b/test/mocks/xhr.js @@ -1,5 +1,5 @@ import {getUniqueIdentifierStr} from '../../src/utils.js'; -import {GreedyPromise} from '../../src/utils/promise.js'; +import {GreedyPromise} from 'libraries/greedy/greedyPromise.js'; import {fakeXhr} from 'nise'; import {dep} from 'src/ajax.js'; diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 72a8940e4d8..d83c6c1d057 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -892,34 +892,33 @@ describe('auctionmanager.js', function () { } ]; }) - it('are dropped when stale (if minBidCacheTTL is set)', () => { + it('are dropped when stale (if minBidCacheTTL is set)', async () => { config.setConfig({ minBidCacheTTL: 30 }); auction.callBids(); - return auction.end.then(() => { - clock.tick(20 * 1000); - expect(auctionManager.getBidsReceived().length).to.equal(2); - clock.tick(50 * 1000); - expect(auctionManager.getBidsReceived().length).to.equal(1); - }); + await auction.end; + await clock.tick(20 * 1000); + expect(auctionManager.getBidsReceived().length).to.equal(2); + await clock.tick(50 * 1000); + expect(auctionManager.getBidsReceived().length).to.equal(1); }); - it('pick up updates to minBidCacheTTL that happen during bid lifetime', () => { + it('pick up updates to minBidCacheTTL that happen during bid lifetime', async () => { auction.callBids(); - return auction.end.then(() => { - clock.tick(10 * 1000); - config.setConfig({ - minBidCacheTTL: 20 - }) - clock.tick(20 * 1000); - expect(auctionManager.getBidsReceived().length).to.equal(1); - }); + await auction.edn; + clock.tick(10 * 1000); + config.setConfig({ + minBidCacheTTL: 20 + }) + await clock.tick(0); + await clock.tick(20 * 1000); + expect(auctionManager.getBidsReceived().length).to.equal(1); }) }) describe('stale auctions', () => { - it('are dropped after their last bid becomes stale (if minBidCacheTTL is set)', () => { + it('are dropped after their last bid becomes stale (if minBidCacheTTL is set)', async () => { config.setConfig({ minBidCacheTTL: 90 }); @@ -935,24 +934,23 @@ describe('auctionmanager.js', function () { } ]; auction.callBids(); - return auction.end.then(() => { - clock.tick(50 * 1000); - expect(auctionManager.getBidsReceived().length).to.equal(2); - clock.tick(56 * 1000); - expect(auctionManager.getBidsReceived()).to.eql([]); - }); + await auction.end; + clock.tick(50 * 1000); + expect(auctionManager.getBidsReceived().length).to.equal(2); + clock.tick(56 * 1000); + expect(auctionManager.getBidsReceived()).to.eql([]); }); - it('are dropped after `minBidCacheTTL` seconds if they had no bid', () => { + it('are dropped after `minBidCacheTTL` seconds if they had no bid', async () => { auction.callBids(); config.setConfig({ minBidCacheTTL: 2 }); - return auction.end.then(() => { - expect(auctionManager.getNoBids().length).to.eql(1); - clock.tick(10 * 10000); - expect(auctionManager.getNoBids().length).to.eql(0); - }) + await auction.end; + await clock.tick(0); + expect(auctionManager.getNoBids().length).to.eql(1); + clock.tick(10 * 10000); + expect(auctionManager.getNoBids().length).to.eql(0); }); it('are not dropped after `minBidCacheTTL` seconds if the page was hidden', () => { @@ -1521,17 +1519,18 @@ describe('auctionmanager.js', function () { assert.equal(length, 1); }); - it('should run auction after video bids have been cached', function () { - sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123 }]); + it('should run auction after video bids have been cached', async function () { + sinon.stub(store, 'store').callsArgWith(1, null, [{uuid: 123}]); sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url'); - const bidsCopy = [Object.assign({}, bids[0], { mediaType: 'video' })]; - const bids1Copy = [Object.assign({}, bids1[0], { mediaType: 'video' })]; + const bidsCopy = [Object.assign({}, bids[0], {mediaType: 'video'})]; + const bids1Copy = [Object.assign({}, bids1[0], {mediaType: 'video'})]; spec.interpretResponse.returns(bidsCopy); spec1.interpretResponse.returns(bids1Copy); auction.callBids(); + await auction.end; assert.equal(auction.getBidsReceived().length, 2); assert.equal(auction.getAuctionStatus(), 'completed'); @@ -1540,23 +1539,24 @@ describe('auctionmanager.js', function () { store.store.restore(); }); - it('runs auction after video responses with multiple bid objects have been cached', function () { - sinon.stub(store, 'store').callsArgWith(1, null, [{ uuid: 123 }]); + it('runs auction after video responses with multiple bid objects have been cached', async function () { + sinon.stub(store, 'store').callsArgWith(1, null, [{uuid: 123}]); sinon.stub(config, 'getConfig').withArgs('cache.url').returns('cache-url'); const bidsCopy = [ - Object.assign({}, bids[0], { mediaType: 'video' }), - Object.assign({}, bids[0], { mediaType: 'banner' }), + Object.assign({}, bids[0], {mediaType: 'video'}), + Object.assign({}, bids[0], {mediaType: 'banner'}), ]; const bids1Copy = [ - Object.assign({}, bids1[0], { mediaType: 'video' }), - Object.assign({}, bids1[0], { mediaType: 'video' }), + Object.assign({}, bids1[0], {mediaType: 'video'}), + Object.assign({}, bids1[0], {mediaType: 'video'}), ]; spec.interpretResponse.returns(bidsCopy); spec1.interpretResponse.returns(bids1Copy); auction.callBids(); + await auction.end; assert.equal(auction.getBidsReceived().length, 4); assert.equal(auction.getAuctionStatus(), 'completed'); @@ -1895,318 +1895,345 @@ describe('auctionmanager.js', function () { }); }) - describe('auctionCallbacks', function() { - let bids = TEST_BIDS; - let bidRequests; - let doneSpy; - let auction; + describe('auction options / callbacks', () => { + let ready; + function responsesReadyHook(next) { + next(ready); + } - beforeEach(() => { - const start = Date.now(); - auction = mockAuction(() => bidRequests, start); - indexAuctions = [auction]; - doneSpy = sinon.spy(); - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } - }); + before(() => { + responsesReady.before(responsesReadyHook, 100); }); - - afterEach(() => { - doneSpy.resetHistory(); - config.resetConfig(); - bidRequests = null; + after(() => { + responsesReady.getHooks({hook: responsesReadyHook}).remove(); }); - Object.entries({ - 'added to': (cbs) => cbs.addBidResponse, - 'rejected from': (cbs) => cbs.addBidResponse.reject, - }).forEach(([t, getMethod]) => { - it(`should call auction done after bid is ${t} auction for mediaType banner`, function () { - let ADUNIT_CODE2 = 'adUnitCode2'; - let BIDDER_CODE2 = 'sampleBidder2'; - - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1, adUnitId: ADUNIT_CODE1 })]; - let bids2 = [mockBid({ bidderCode: BIDDER_CODE2, adUnitId: ADUNIT_CODE2 })]; - bidRequests = [ - mockBidRequest(bids[0]), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE2 }) - ]; - let cbs = auctionCallbacks(doneSpy, auction); - const method = getMethod(cbs); - method(ADUNIT_CODE, bids[0]); - cbs.adapterDone.call(bidRequests[0]); - method(ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[1]); - method(ADUNIT_CODE2, bids2[0]); - cbs.adapterDone.call(bidRequests[2]); - assert.equal(doneSpy.callCount, 1); - }); - }) + beforeEach(() => { + ready = Promise.resolve(); + return ready; + }); - if (FEATURES.VIDEO) { - it('should call auction done after prebid cache is complete for mediaType video', function() { - bids[0].mediaType = 'video'; - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; + describe('auctionCallbacks', function() { + let bids = TEST_BIDS; + let bidRequests; + let doneSpy; + let auction; - let opts = { - mediaType: { - video: { - context: 'instream', - playerSize: [640, 480], - }, + beforeEach(() => { + const start = Date.now(); + auction = mockAuction(() => bidRequests, start); + indexAuctions = [auction]; + doneSpy = sinon.spy(); + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' } - }; - bidRequests = [ - mockBidRequest(bids[0], opts), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - ]; + }); + }); - let cbs = auctionCallbacks(doneSpy, auction); - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); - cbs.adapterDone.call(bidRequests[0]); - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[1]); - assert.equal(doneSpy.callCount, 0); - const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; - const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); - assert.equal(doneSpy.callCount, 1); + afterEach(() => { + doneSpy.resetHistory(); + config.resetConfig(); + bidRequests = null; }); - } - it('should convert cpm to number', () => { - auction.addBidReceived = sinon.spy(); - const cbs = auctionCallbacks(doneSpy, auction); - const bid = {...bids[0], cpm: '1.23'} - bidRequests = [mockBidRequest(bid)]; - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bid); - sinon.assert.calledWith(auction.addBidReceived, sinon.match({cpm: 1.23})); - }) + Object.entries({ + 'added to': (cbs) => cbs.addBidResponse, + 'rejected from': (cbs) => cbs.addBidResponse.reject, + }).forEach(([t, getMethod]) => { + it(`should call auction done after bid is ${t} auction for mediaType banner`, async function () { + let ADUNIT_CODE2 = 'adUnitCode2'; + let BIDDER_CODE2 = 'sampleBidder2'; + + let bids1 = [mockBid({bidderCode: BIDDER_CODE1, adUnitId: ADUNIT_CODE1})]; + let bids2 = [mockBid({bidderCode: BIDDER_CODE2, adUnitId: ADUNIT_CODE2})]; + bidRequests = [ + mockBidRequest(bids[0]), + mockBidRequest(bids1[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids2[0], {adUnitCode: ADUNIT_CODE2}) + ]; + let cbs = auctionCallbacks(doneSpy, auction); + const method = getMethod(cbs); + method(ADUNIT_CODE, bids[0]); + cbs.adapterDone.call(bidRequests[0]); + method(ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[1]); + method(ADUNIT_CODE2, bids2[0]); + cbs.adapterDone.call(bidRequests[2]); + await ready; + assert.equal(doneSpy.callCount, 1); + }); + }) + + if (FEATURES.VIDEO) { + it('should call auction done after prebid cache is complete for mediaType video', async function () { + bids[0].mediaType = 'video'; + let bids1 = [mockBid({bidderCode: BIDDER_CODE1})]; - describe('when responsesReady defers', () => { - let resolve, reject, promise, callbacks, bids; + let opts = { + mediaType: { + video: { + context: 'instream', + playerSize: [640, 480], + }, + } + }; + bidRequests = [ + mockBidRequest(bids[0], opts), + mockBidRequest(bids1[0], {adUnitCode: ADUNIT_CODE1}), + ]; - function hook(next, ready) { - next(ready.then(() => promise)); + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); + cbs.adapterDone.call(bidRequests[0]); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[1]); + await ready; + assert.equal(doneSpy.callCount, 0); + const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; + const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; + server.requests[0].respond(200, {'Content-Type': 'application/json'}, responseBody); + assert.equal(doneSpy.callCount, 1); + }); } - before(() => { - responsesReady.before(hook); - }); + it('should convert cpm to number', () => { + auction.addBidReceived = sinon.spy(); + const cbs = auctionCallbacks(doneSpy, auction); + const bid = {...bids[0], cpm: '1.23'} + bidRequests = [mockBidRequest(bid)]; + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bid); + sinon.assert.calledWith(auction.addBidReceived, sinon.match({cpm: 1.23})); + }) - after(() => { - responsesReady.getHooks({hook}).remove(); - }); + describe('when responsesReady defers', () => { + let resolve, reject, promise, callbacks, bids; - beforeEach(() => { - // eslint-disable-next-line promise/param-names - promise = new Promise((rs, rj) => { - resolve = rs; - reject = rj; + function hook(next, ready) { + next(ready.then(() => promise)); + } + + before(() => { + responsesReady.before(hook, 0); // higher priority than wrapping setup }); - bids = [ - mockBid({bidderCode: BIDDER_CODE1}), - mockBid({bidderCode: BIDDER_CODE}) - ] - bidRequests = bids.map((b) => mockBidRequest(b)); - callbacks = auctionCallbacks(doneSpy, auction); - Object.assign(auction, { - addNoBid: sinon.spy() + + after(() => { + responsesReady.getHooks({hook}).remove(); }); - }); - Object.entries({ - 'resolve': () => resolve(), - 'reject': () => reject(), - }).forEach(([t, resolver]) => { - it(`should wait for responsesReady to ${t} before calling auctionDone`, (done) => { - bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest)); - setTimeout(() => { - sinon.assert.notCalled(doneSpy); - resolver(); + beforeEach(() => { + // eslint-disable-next-line promise/param-names + promise = new Promise((rs, rj) => { + resolve = rs; + reject = rj; + }); + bids = [ + mockBid({bidderCode: BIDDER_CODE1}), + mockBid({bidderCode: BIDDER_CODE}) + ] + bidRequests = bids.map((b) => mockBidRequest(b)); + callbacks = auctionCallbacks(doneSpy, auction); + Object.assign(auction, { + addNoBid: sinon.spy() + }); + }); + + Object.entries({ + 'resolve': () => resolve(), + 'reject': () => reject(), + }).forEach(([t, resolver]) => { + it(`should wait for responsesReady to ${t} before calling auctionDone`, (done) => { + bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest)); setTimeout(() => { - sinon.assert.called(doneSpy); - done(); + sinon.assert.notCalled(doneSpy); + resolver(); + setTimeout(() => { + sinon.assert.called(doneSpy); + done(); + }) }) - }) + }); }); }); - }); - describe('when bids are rejected', () => { - let cbs, bid, expectedRejection; - const onBidRejected = sinon.stub(); - const REJECTION_REASON = 'Bid rejected'; - const AU_CODE = 'au'; + describe('when bids are rejected', () => { + let cbs, bid, expectedRejection; + const onBidRejected = sinon.stub(); + const REJECTION_REASON = 'Bid rejected'; + const AU_CODE = 'au'; - function rejectHook(fn, adUnitCode, bid, reject) { - reject(REJECTION_REASON); - reject(REJECTION_REASON); // second call should do nothing - } + function rejectHook(fn, adUnitCode, bid, reject) { + reject(REJECTION_REASON); + reject(REJECTION_REASON); // second call should do nothing + } + + before(() => { + addBidResponse.before(rejectHook, 999); + events.on(EVENTS.BID_REJECTED, onBidRejected); + }); + + after(() => { + addBidResponse.getHooks({hook: rejectHook}).remove(); + events.off(EVENTS.BID_REJECTED, onBidRejected); + }); + + beforeEach(() => { + onBidRejected.reset(); + bid = mockBid({bidderCode: BIDDER_CODE}); + bidRequests = [ + mockBidRequest(bid), + ]; + cbs = auctionCallbacks(doneSpy, auction); + expectedRejection = sinon.match(Object.assign({}, bid, { + cpm: parseFloat(bid.cpm), + rejectionReason: REJECTION_REASON, + adUnitCode: AU_CODE + })); + auction.addBidRejected = sinon.stub(); + }); + + Object.entries({ + 'with addBidResponse.reject': () => cbs.addBidResponse.reject(AU_CODE, deepClone(bid), REJECTION_REASON), + 'from addBidResponse hooks': () => cbs.addBidResponse(AU_CODE, deepClone(bid)) + }).forEach(([t, rejectBid]) => { + describe(t, () => { + it('should emit a BID_REJECTED event', () => { + rejectBid(); + sinon.assert.calledWith(onBidRejected, expectedRejection); + }); + + it('should pass bid to auction.addBidRejected', () => { + rejectBid(); + sinon.assert.calledWith(auction.addBidRejected, expectedRejection); + }); + }) + }); + + it('addBidResponse hooks should not be able to reject the same bid twice', () => { + cbs.addBidResponse(AU_CODE, bid); + expect(auction.addBidRejected.calledOnce).to.be.true; + }); + }) + }); + describe('auctionOptions', function() { + let bidRequests; + let doneSpy; + let clock; + let requiredBidder = BIDDER_CODE; + let requiredBidder1 = BIDDER_CODE1; + let secondaryBidder = 'doNotWaitForMe'; + let auction; - before(() => { - addBidResponse.before(rejectHook, 999); - events.on(EVENTS.BID_REJECTED, onBidRejected); + beforeEach(() => { + clock = sinon.useFakeTimers(); + doneSpy = sinon.spy(); + config.setConfig({ + 'auctionOptions': { + secondaryBidders: [ secondaryBidder ] + } + }); + + const start = Date.now(); + auction = mockAuction(() => bidRequests); + indexAuctions = [auction]; }); - after(() => { - addBidResponse.getHooks({hook: rejectHook}).remove(); - events.off(EVENTS.BID_REJECTED, onBidRejected); + afterEach(() => { + doneSpy.resetHistory(); + config.resetConfig(); + clock.restore(); }); - beforeEach(() => { - onBidRejected.reset(); - bid = mockBid({bidderCode: BIDDER_CODE}); + it('should not wait to call auction done for secondary bidders', async function () { + let bids1 = [mockBid({bidderCode: requiredBidder, transactionId: ADUNIT_CODE1})]; + let bids2 = [mockBid({bidderCode: requiredBidder1, transactionId: ADUNIT_CODE1})]; + let bids3 = [mockBid({bidderCode: secondaryBidder, transactionId: ADUNIT_CODE1})]; bidRequests = [ - mockBidRequest(bid), + mockBidRequest(bids1[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids2[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids3[0], {adUnitCode: ADUNIT_CODE1}), ]; - cbs = auctionCallbacks(doneSpy, auction); - expectedRejection = sinon.match(Object.assign({}, bid, { - cpm: parseFloat(bid.cpm), - rejectionReason: REJECTION_REASON, - adUnitCode: AU_CODE - })); - auction.addBidRejected = sinon.stub(); - }); + let cbs = auctionCallbacks(doneSpy, auction); + // required bidder responds immeaditely to auction + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + await ready; + assert.equal(doneSpy.callCount, 0); - Object.entries({ - 'with addBidResponse.reject': () => cbs.addBidResponse.reject(AU_CODE, deepClone(bid), REJECTION_REASON), - 'from addBidResponse hooks': () => cbs.addBidResponse(AU_CODE, deepClone(bid)) - }).forEach(([t, rejectBid]) => { - describe(t, () => { - it('should emit a BID_REJECTED event', () => { - rejectBid(); - sinon.assert.calledWith(onBidRejected, expectedRejection); - }); + // auction waits for second required bidder to respond + clock.tick(100); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + // auction done is reported and does not wait for secondaryBidder request + await ready; + assert.equal(doneSpy.callCount, 1); - it('should pass bid to auction.addBidRejected', () => { - rejectBid(); - sinon.assert.calledWith(auction.addBidRejected, expectedRejection); - }); - }) + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); }); - it('addBidResponse hooks should not be able to reject the same bid twice', () => { - cbs.addBidResponse(AU_CODE, bid); - expect(auction.addBidRejected.calledOnce).to.be.true; - }); - }) - }); + it('should wait for all bidders if they are all secondary', async function () { + config.setConfig({ + 'auctionOptions': { + secondaryBidders: [requiredBidder, requiredBidder1, secondaryBidder] + } + }) + let bids1 = [mockBid({bidderCode: requiredBidder})]; + let bids2 = [mockBid({bidderCode: requiredBidder1})]; + let bids3 = [mockBid({bidderCode: secondaryBidder})]; + bidRequests = [ + mockBidRequest(bids1[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids2[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids3[0], {adUnitCode: ADUNIT_CODE1}), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + clock.tick(100); + await ready; + assert.equal(doneSpy.callCount, 0) - describe('auctionOptions', function() { - let bidRequests; - let doneSpy; - let clock; - let requiredBidder = BIDDER_CODE; - let requiredBidder1 = BIDDER_CODE1; - let secondaryBidder = 'doNotWaitForMe'; - let auction; + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + clock.tick(100); + await ready; + assert.equal(doneSpy.callCount, 0); - beforeEach(() => { - clock = sinon.useFakeTimers(); - doneSpy = sinon.spy(); - config.setConfig({ - 'auctionOptions': { - secondaryBidders: [ secondaryBidder ] - } + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + await ready; + assert.equal(doneSpy.callCount, 1); }); - const start = Date.now(); - auction = mockAuction(() => bidRequests); - indexAuctions = [auction]; - }); - - afterEach(() => { - doneSpy.resetHistory(); - config.resetConfig(); - clock.restore(); - }); - - it('should not wait to call auction done for secondary bidders', function () { - let bids1 = [mockBid({ bidderCode: requiredBidder, transactionId: ADUNIT_CODE1 })]; - let bids2 = [mockBid({ bidderCode: requiredBidder1, transactionId: ADUNIT_CODE1 })]; - let bids3 = [mockBid({ bidderCode: secondaryBidder, transactionId: ADUNIT_CODE1 })]; - bidRequests = [ - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), - ]; - let cbs = auctionCallbacks(doneSpy, auction); - // required bidder responds immeaditely to auction - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[0]); - assert.equal(doneSpy.callCount, 0); - - // auction waits for second required bidder to respond - clock.tick(100); - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); - cbs.adapterDone.call(bidRequests[1]); - - // auction done is reported and does not wait for secondaryBidder request - assert.equal(doneSpy.callCount, 1); - - cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); - cbs.adapterDone.call(bidRequests[2]); - }); + it('should allow secondaryBidders to respond in auction before is is done', async function () { + let bids1 = [mockBid({bidderCode: requiredBidder})]; + let bids2 = [mockBid({bidderCode: requiredBidder1})]; + let bids3 = [mockBid({bidderCode: secondaryBidder})]; + bidRequests = [ + mockBidRequest(bids1[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids2[0], {adUnitCode: ADUNIT_CODE1}), + mockBidRequest(bids3[0], {adUnitCode: ADUNIT_CODE1}), + ]; + let cbs = auctionCallbacks(doneSpy, auction); + // secondaryBidder is first to respond + cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); + cbs.adapterDone.call(bidRequests[2]); + clock.tick(100); + await ready; + assert.equal(doneSpy.callCount, 0); - it('should wait for all bidders if they are all secondary', function () { - config.setConfig({ - 'auctionOptions': { - secondaryBidders: [requiredBidder, requiredBidder1, secondaryBidder] - } - }) - let bids1 = [mockBid({ bidderCode: requiredBidder })]; - let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; - let bids3 = [mockBid({ bidderCode: secondaryBidder })]; - bidRequests = [ - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), - ]; - let cbs = auctionCallbacks(doneSpy, auction); - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[0]); - clock.tick(100); - assert.equal(doneSpy.callCount, 0) - - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); - cbs.adapterDone.call(bidRequests[1]); - clock.tick(100); - assert.equal(doneSpy.callCount, 0); - - cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); - cbs.adapterDone.call(bidRequests[2]); - assert.equal(doneSpy.callCount, 1); - }); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); + cbs.adapterDone.call(bidRequests[1]); + clock.tick(100); + await ready; + assert.equal(doneSpy.callCount, 0); - it('should allow secondaryBidders to respond in auction before is is done', function () { - let bids1 = [mockBid({ bidderCode: requiredBidder })]; - let bids2 = [mockBid({ bidderCode: requiredBidder1 })]; - let bids3 = [mockBid({ bidderCode: secondaryBidder })]; - bidRequests = [ - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids3[0], { adUnitCode: ADUNIT_CODE1 }), - ]; - let cbs = auctionCallbacks(doneSpy, auction); - // secondaryBidder is first to respond - cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE1, bids3[0]); - cbs.adapterDone.call(bidRequests[2]); - clock.tick(100); - assert.equal(doneSpy.callCount, 0); - - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids2[0]); - cbs.adapterDone.call(bidRequests[1]); - clock.tick(100); - assert.equal(doneSpy.callCount, 0); - - // first required bidder takes longest to respond, auction isn't marked as done until this occurs - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[0]); - assert.equal(doneSpy.callCount, 1); + // first required bidder takes longest to respond, auction isn't marked as done until this occurs + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[0]); + await ready; + assert.equal(doneSpy.callCount, 1); + }); }); }); }); diff --git a/test/spec/libraries/cmUtils_spec.js b/test/spec/libraries/cmUtils_spec.js new file mode 100644 index 00000000000..9d23fb7137a --- /dev/null +++ b/test/spec/libraries/cmUtils_spec.js @@ -0,0 +1,225 @@ +import * as utils from 'src/utils.js'; +import {lookupConsentData, consentManagementHook} from '../../../libraries/consentManagement/cmUtils'; + +describe('consent management utils', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + ['logError', 'logInfo', 'logWarn'].forEach(n => sandbox.stub(utils, n)); + }); + afterEach(() => { + sandbox.restore(); + }); + + describe('consentManagementHook', () => { + let cmHook, loadResult, next; + beforeEach(() => { + next = sinon.stub(); + cmHook = consentManagementHook('test', () => loadResult); + }); + + Object.entries({ + 'an error': { + error: new Error('mock-error'), + check(logger) { + sinon.assert.calledWith(logger, sinon.match('mock-error')); + } + }, + 'an error with args': { + error: Object.assign(new Error('mock-error'), {args: ['arg1', 'arg2']}), + check(logger) { + sinon.assert.calledWith(logger, sinon.match('mock-error'), 'arg1', 'arg2'); + } + }, + 'no error': { + check() { + } + } + }).forEach(([errorDesc, {error, check: checkLogs}]) => { + describe(`when loadConsentData rejects with ${errorDesc}`, () => { + beforeEach(async () => { + loadResult = Promise.reject(error); + }); + afterEach(() => { + checkLogs(utils.logError); + }); + it('should log an error and run bidsBackHandler', async () => { + const bidsBackHandler = sinon.stub(); + cmHook(next, {bidsBackHandler}); + await loadResult.catch(() => null); + sinon.assert.calledWith(utils.logError, sinon.match('Canceling auction')); + sinon.assert.notCalled(next); + sinon.assert.called(bidsBackHandler); + }); + it('should not choke when bidsBackHandler is not present', async () => { + cmHook(next, {}); + await loadResult.catch(() => null); + sinon.assert.notCalled(next); + }); + }); + describe(`when loadConsentData resolves with ${errorDesc}`, () => { + function setupError() { + loadResult = Promise.resolve({error}); + } + function setupConsentAndError() { + loadResult = Promise.resolve({consentData: {'consent': 'data'}, error}); + } + Object.entries({ + 'with': setupConsentAndError, + 'without': setupError + }).forEach(([t, setup]) => { + describe(`${t} consent`, () => { + beforeEach(setup); + it('should log a warning and continue auction', async () => { + cmHook(next, {auction: 'args'}); + await loadResult; + sinon.assert.calledWith(next, {auction: 'args'}); + checkLogs(utils.logWarn); + }); + }); + }); + + describe('when consent data is available', () => { + beforeEach(setupConsentAndError); + it(`should not log a warning on the second call`, async () => { + cmHook(next, {}); + await loadResult; + sandbox.resetHistory(); + cmHook(next, {}); + await loadResult; + sinon.assert.notCalled(utils.logWarn) + sinon.assert.calledWith(utils.logInfo, sinon.match('already known')); + }) + }); + + describe('when consent data is not available', () => { + beforeEach(setupError); + it('should log the same warning again on the second call', async () => { + cmHook(next, {}); + await loadResult; + sandbox.resetHistory(); + cmHook(next, {}); + await loadResult; + checkLogs(utils.logWarn); + }) + }) + }); + }); + }); + describe('lookupConsentData', () => { + let name, consentDataHandler, setupCmp, cmpTimeout, actionTimeout, getNullConsent; + beforeEach(() => { + name = 'TEST'; + consentDataHandler = { + enable: sinon.stub(), + setConsentData: sinon.stub(), + getConsentData: sinon.stub() + }; + setupCmp = sinon.stub(); + cmpTimeout = 0; + actionTimeout = null; + getNullConsent = sinon.stub().returns({ + consent: null + }) + }) + + function runLookup() { + return lookupConsentData({ + name, + consentDataHandler, + setupCmp, + cmpTimeout, + actionTimeout, + getNullConsent + }); + } + + it('should enable consent data handler', () => { + runLookup().catch(() => null); + sinon.assert.calledWith(consentDataHandler.enable); + }); + + it('should reject if cmp handler rejects', async () => { + const err = new Error(); + setupCmp.returns(Promise.reject(err)); + try { + await runLookup(); + sinon.assert.fail('should throw'); + } catch (e) { + expect(e).to.equal(err); + sinon.assert.calledWith(consentDataHandler.setConsentData, null); + } + }); + + [123, 0].forEach(timeout => { + describe(`when cmpTimeout is ${timeout}`, () => { + beforeEach(() => { + cmpTimeout = timeout; + setupCmp.callsFake(() => { + consentDataHandler.getConsentData.returns({consent: 'data'}); + return Promise.resolve(); + }); + }); + + it(`should resolve if cmp handler resolves`, async () => { + const {consentData, error} = await runLookup(); + expect(consentData).to.eql({consent: 'data'}); + expect(error).to.not.exist; + }); + + it('should not time out after it resolves', async () => { + await runLookup(); + await new Promise((resolve) => setTimeout(resolve, timeout + 10)); + sinon.assert.notCalled(consentDataHandler.setConsentData); + }); + }); + }); + + describe('when cmp handler does not reply', () => { + let setProvisionalConsent; + beforeEach(() => { + setupCmp.callsFake((setPC) => { + setProvisionalConsent = setPC; + return new Promise((resolve) => { + setTimeout(resolve, 300) + }); + }); + }); + [0, 100].forEach(timeout => { + it(`should resolve with null consent after cmpTimeout ( = ${timeout}ms)`, async () => { + cmpTimeout = timeout; + const {consentData, error} = await runLookup(); + sinon.assert.calledWith(consentDataHandler.setConsentData, {consent: null}); + expect(consentData).to.eql({consent: null}) + expect(error.message).to.match(/.*CMP to load.*/) + }); + }); + [0, 100].forEach(timeout => { + it(`should resolve with provisional consent after actionTimeout (= ${timeout}) if cmpHandler provides it`, async () => { + cmpTimeout = 100; + actionTimeout = timeout; + const lookup = runLookup(); + await new Promise((resolve) => setTimeout(resolve, 10)); + setProvisionalConsent({consent: 'provisional'}); + const {consentData, error} = await lookup; + expect(consentData).to.eql({consent: 'provisional'}); + expect(error.message).to.match(/.*action.*/) + }); + }); + + it('should not reset action timeout if provisional consent is updated multiple times', (done) => { + actionTimeout = 100; + let consentData; + runLookup().then((res) => { + consentData = res.consentData; + }) + setProvisionalConsent({consent: 1}); + setTimeout(() => setProvisionalConsent({consent: 2}), 20); + setTimeout(() => { + expect(consentData).to.eql({consent: 2}); + done(); + }, 100) + }) + }); + }); +}); diff --git a/test/spec/libraries/greedy/greedyPromise_spec.js b/test/spec/libraries/greedy/greedyPromise_spec.js new file mode 100644 index 00000000000..2d442f3cf19 --- /dev/null +++ b/test/spec/libraries/greedy/greedyPromise_spec.js @@ -0,0 +1,209 @@ +import {GreedyPromise, greedySetTimeout} from '../../../../libraries/greedy/greedyPromise.js'; +import {delay} from '../../../../src/utils/promise.js'; + +describe('GreedyPromise', () => { + it('throws when resolver is not a function', () => { + expect(() => new GreedyPromise()).to.throw(); + }) + + Object.entries({ + 'resolved': (use) => new GreedyPromise((resolve) => use(resolve)), + 'rejected': (use) => new GreedyPromise((_, reject) => use(reject)) + }).forEach(([t, makePromise]) => { + it(`runs callbacks immediately when ${t}`, () => { + let cbRan = false; + const cb = () => { cbRan = true }; + let resolver; + makePromise((fn) => { resolver = fn }).then(cb, cb); + resolver(); + expect(cbRan).to.be.true; + }) + }); + + describe('idioms', () => { + let makePromise, pendingFailure, pendingSuccess; + + Object.entries({ + // eslint-disable-next-line no-throw-literal + 'resolver that throws': (P) => new P(() => { throw 'error' }), + 'resolver that resolves multiple times': (P) => new P((resolve) => { resolve('first'); resolve('second'); }), + 'resolver that rejects multiple times': (P) => new P((resolve, reject) => { reject('first'); reject('second') }), + 'resolver that resolves and rejects': (P) => new P((resolve, reject) => { reject('first'); resolve('second') }), + 'resolver that resolves with multiple arguments': (P) => new P((resolve) => resolve('one', 'two')), + 'resolver that rejects with multiple arguments': (P) => new P((resolve, reject) => reject('one', 'two')), + 'resolver that resolves to a promise': (P) => new P((resolve) => resolve(makePromise(P, 'val'))), + 'resolver that resolves to a promise that resolves to a promise': (P) => new P((resolve) => resolve(makePromise(P, makePromise(P, 'val')))), + 'resolver that resolves to a rejected promise': (P) => new P((resolve) => resolve(makePromise(P, 'err', true))), + 'simple .then': (P) => makePromise(P, 'value').then((v) => `${v} and then`), + 'chained .then': (P) => makePromise(P, 'value').then((v) => makePromise(P, `${v} and then`)), + '.then with error handler': (P) => makePromise(P, 'err', true).then(null, (e) => `${e} and then`), + '.then with chained error handler': (P) => makePromise(P, 'err', true).then(null, (e) => makePromise(P, `${e} and then`)), + '.then that throws': (P) => makePromise(P, 'value').then((v) => { throw v }), + '.then that throws in error handler': (P) => makePromise(P, 'err', true).then(null, (e) => { throw e }), + '.then with no args': (P) => makePromise(P, 'value').then(), + '.then that rejects': (P) => makePromise(P, 'value').then((v) => P.reject(v)), + '.then that rejects in error handler': (P) => makePromise(P, 'err', true).then(null, (err) => P.reject(err)), + '.then with no error handler on a rejection': (P) => makePromise(P, 'err', true).then((v) => `resolved ${v}`), + '.then with no success handler on a resolution': (P) => makePromise(P, 'value').then(null, (e) => `caught ${e}`), + 'simple .catch': (P) => makePromise(P, 'err', true).catch((err) => `caught ${err}`), + 'identity .catch': (P) => makePromise(P, 'err', true).catch((err) => err).then((v) => v), + '.catch that throws': (P) => makePromise(P, 'err', true).catch((err) => { throw err }), + 'chained .catch': (P) => makePromise(P, 'err', true).catch((err) => makePromise(P, err)), + 'chained .catch that rejects': (P) => makePromise(P, 'err', true).catch((err) => P.reject(`reject with ${err}`)), + 'simple .finally': (P) => { + let fval; + return makePromise(P, 'value') + .finally(() => fval = 'finally ran') + .then((val) => `${val} ${fval}`) + }, + 'chained .finally': (P) => { + let fval; + return makePromise(P, 'value') + .finally(() => pendingSuccess.then(() => { fval = 'finally ran' })) + .then((val) => `${val} ${fval}`) + }, + '.finally on a rejection': (P) => { + let fval; + return makePromise(P, 'error', true) + .finally(() => { fval = 'finally' }) + .catch((err) => `${err} ${fval}`) + }, + 'chained .finally on a rejection': (P) => { + let fval; + return makePromise(P, 'error', true) + .finally(() => pendingSuccess.then(() => { fval = 'finally' })) + .catch((err) => `${err} ${fval}`) + }, + // eslint-disable-next-line no-throw-literal + '.finally that throws': (P) => makePromise(P, 'value').finally(() => { throw 'error' }), + 'chained .finally that rejects': (P) => makePromise(P, 'value').finally(() => P.reject('error')), + 'scalar Promise.resolve': (P) => P.resolve('scalar'), + 'null Promise.resolve': (P) => P.resolve(null), + 'chained Promise.resolve': (P) => P.resolve(pendingSuccess), + 'chained Promise.resolve on failure': (P) => P.resolve(pendingFailure), + 'scalar Promise.reject': (P) => P.reject('scalar'), + 'chained Promise.reject': (P) => P.reject(pendingSuccess), + 'chained Promise.reject on failure': (P) => P.reject(pendingFailure), + 'simple Promise.all': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two')]), + 'empty Promise.all': (P) => P.all([]), + 'Promise.all with scalars': (P) => P.all([makePromise(P, 'one'), 'two']), + 'Promise.all with errors': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two'), makePromise(P, 'err', true)]), + 'Promise.allSettled': (P) => P.allSettled([makePromise(P, 'one', true), makePromise(P, 'two'), makePromise(P, 'three', true)]), + 'empty Promise.allSettled': (P) => P.allSettled([]), + 'Promise.allSettled with scalars': (P) => P.allSettled([makePromise(P, 'value'), 'scalar']), + 'Promise.race that succeeds': (P) => P.race([makePromise(P, 'error', true, 10), makePromise(P, 'success')]), + 'Promise.race that fails': (P) => P.race([makePromise(P, 'success', false, 10), makePromise(P, 'error', true)]), + 'Promise.race with scalars': (P) => P.race(['scalar', makePromise(P, 'success')]), + }).forEach(([t, op]) => { + describe(t, () => { + describe('when mixed with deferrals', () => { + beforeEach(() => { + makePromise = function(ctor, value, fail = false, delay = 0) { + // eslint-disable-next-line new-cap + return new ctor((resolve, reject) => { + setTimeout(() => fail ? reject(value) : resolve(value), delay) + }) + }; + pendingSuccess = makePromise(Promise, 'pending result', false, 10); + pendingFailure = makePromise(Promise, 'pending failure', true, 10); + }); + + it(`behaves like vanilla promises`, () => { + const vanilla = op(Promise); + const greedy = op(GreedyPromise); + // note that we are not using `allSettled` & co to resolve our promises, + // to avoid transformations those methods do under the hood + const {actual = {}, expected = {}} = {}; + return new Promise((resolve) => { + let pending = 2; + function collect(dest, slot) { + return function (value) { + dest[slot] = value; + pending--; + if (pending === 0) { + resolve() + } + } + } + vanilla.then(collect(expected, 'success'), collect(expected, 'failure')); + greedy.then(collect(actual, 'success'), collect(actual, 'failure')); + }).then(() => { + expect(actual).to.eql(expected); + }); + }); + + it(`once resolved, runs callbacks immediately`, () => { + const promise = op(GreedyPromise).catch(() => null); + return promise.then(() => { + let cbRan = false; + promise.then(() => { cbRan = true }); + expect(cbRan).to.be.true; + }); + }); + }); + + describe('when all promises involved are greedy', () => { + beforeEach(() => { + makePromise = function(ctor, value, fail = false, delay = 0) { + // eslint-disable-next-line new-cap + return new ctor((resolve, reject) => { + const run = () => fail ? reject(value) : resolve(value); + delay === 0 ? run() : setTimeout(run, delay); + }) + }; + pendingSuccess = makePromise(GreedyPromise, 'pending result'); + pendingFailure = makePromise(GreedyPromise, 'pending failure', true); + }); + + it('resolves immediately', () => { + let cbRan = false; + op(GreedyPromise).catch(() => null).then(() => { cbRan = true }); + expect(cbRan).to.be.true; + }); + }); + }); + }); + }); +}); + +describe('greedySetTimeout', () => { + describe('delay = 0', () => { + it('should resolve immediately', () => { + let cbRan = false; + greedySetTimeout(() => { cbRan = true }, 0); + expect(cbRan).to.be.true; + }); + + it('should not choke when cleared', () => { + clearTimeout(greedySetTimeout(() => {}, 0)); + }); + }) + + describe('delay > 0', () => { + it('should schedule timeout', (done) => { + let cbRan = false; + greedySetTimeout(() => { + cbRan = true; + }, 5); + expect(cbRan).to.be.false; + setTimeout(() => { + expect(cbRan).to.be.true; + done(); + }, 10) + }); + + it('can be cleared', (done) => { + let cbRan = false; + const handle = greedySetTimeout(() => { + cbRan = true; + }, 5); + setTimeout(() => { + clearTimeout(handle); + setTimeout(() => { + expect(cbRan).to.be.false; + done() + }, 10) + }, 0) + }) + }) +}); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 25e35a58557..14b399eaad4 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,13 +1,10 @@ // jshint esversion: 6, es3: false, node: true -import { assert } from 'chai'; -import { spec } from 'modules/adxcgBidAdapter.js'; -import { config } from 'src/config.js'; -import { createEidsArray } from 'modules/userId/eids.js'; /* eslint dot-notation:0, quote-props:0 */ -import { expect } from 'chai'; +import {assert, expect} from 'chai'; +import {spec} from 'modules/adxcgBidAdapter.js'; +import {config} from 'src/config.js'; -import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; -import { deepClone } from '../../../src/utils'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; const utils = require('src/utils'); @@ -229,15 +226,19 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, }]; - const bidderRequest = { - refererInfo: { - page: 'https://publisher.com/home', - ref: 'https://referrer' - } - }; + let bidderRequest; + + beforeEach(() => { + return addFPDToBidderRequest({ + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + } + }).then(br => { bidderRequest = br }); + }) it('Verify build request', function () { - const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(slotConfigs, bidderRequest); expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -258,7 +259,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }); it('Verify parse response', function () { - const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(slotConfigs, bidderRequest); const ortbRequest = request.data; const ortbResponse = { seatbid: [{ @@ -300,7 +301,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { if (FEATURES.NATIVE) { it('Verify Native request', function () { - const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(nativeSlotConfig, bidderRequest); expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -336,7 +337,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }); it('Verify Native response', function () { - const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(nativeSlotConfig, bidderRequest); expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -408,7 +409,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { if (FEATURES.VIDEO) { it('Verify Video request', function () { - const request = spec.buildRequests(videoSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(videoSlotConfig, bidderRequest); expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -429,7 +430,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { } it('Verify extra parameters', function () { - let request = spec.buildRequests(additionalParamsConfig, syncAddFPDToBidderRequest(bidderRequest)); + let request = spec.buildRequests(additionalParamsConfig, bidderRequest); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.imp).to.have.lengthOf(1); @@ -474,7 +475,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { } } }; - let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + let request = spec.buildRequests(slotConfigs, bidderRequest); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.user).to.not.equal(null); @@ -501,7 +502,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { } } }; - let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + let request = spec.buildRequests(slotConfigs, bidderRequest); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.site).to.not.equal(null); diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index a37e4647c7a..c7ab69bca80 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; import {spec} from 'modules/asoBidAdapter.js'; -import {BANNER, VIDEO, NATIVE} from 'src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; import {OUTSTREAM} from 'src/video.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import {addFPDToBidderRequest} from '../../helpers/fpd'; import {parseUrl} from '../../../src/utils'; import 'modules/priceFloors.js'; @@ -89,19 +89,6 @@ describe('Adserver.Online bidding adapter', function () { nativeOrtbRequest }; - const bidderRequest = { - refererInfo: { - page: 'https://example.com/page.html', - topmostLocation: 'https://example.com/page.html', - reachedTop: true, - numIframes: 1, - stack: [ - 'https://example.com/page.html', - 'https://example.com/iframe1.html' - ] - } - }; - const gdprConsent = { gdprApplies: true, consentString: 'consentString', @@ -124,6 +111,23 @@ describe('Adserver.Online bidding adapter', function () { } }; + let bidderRequest; + + beforeEach(() => { + return addFPDToBidderRequest({ + refererInfo: { + page: 'https://example.com/page.html', + topmostLocation: 'https://example.com/page.html', + reachedTop: true, + numIframes: 1, + stack: [ + 'https://example.com/page.html', + 'https://example.com/iframe1.html' + ] + } + }).then(br => { bidderRequest = br }); + }) + const uspConsent = 'usp_consent'; describe('isBidRequestValid', function () { @@ -187,7 +191,7 @@ describe('Adserver.Online bidding adapter', function () { }); it('creates a valid banner request', function () { - const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); + const requests = spec.buildRequests([bannerRequest], bidderRequest); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -216,7 +220,7 @@ describe('Adserver.Online bidding adapter', function () { if (FEATURES.VIDEO) { it('creates a valid video request', function () { - const requests = spec.buildRequests([videoRequest], syncAddFPDToBidderRequest(bidderRequest)); + const requests = spec.buildRequests([videoRequest], bidderRequest); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -245,7 +249,7 @@ describe('Adserver.Online bidding adapter', function () { if (FEATURES.NATIVE) { it('creates a valid native request', function () { - const requests = spec.buildRequests([nativeRequest], syncAddFPDToBidderRequest(bidderRequest)); + const requests = spec.buildRequests([nativeRequest], bidderRequest); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -275,34 +279,38 @@ describe('Adserver.Online bidding adapter', function () { bidderRequest.gdprConsent = gdprConsent; bidderRequest.uspConsent = uspConsent; - const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); - expect(requests).to.have.lengthOf(1); - const request = requests[0]; + return addFPDToBidderRequest(bidderRequest).then(bidderRequest => { + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; - expect(request.data).to.not.be.empty; + expect(request.data).to.not.be.empty; - const payload = request.data; + const payload = request.data; - expect(payload.user.ext.consent).to.equal('consentString'); - expect(payload.regs.ext.us_privacy).to.equal(uspConsent); - expect(payload.regs.ext.gdpr).to.equal(1); + expect(payload.user.ext.consent).to.equal('consentString'); + expect(payload.regs.ext.us_privacy).to.equal(uspConsent); + expect(payload.regs.ext.gdpr).to.equal(1); + }) }); it('should not send GDPR/USP consent data if it does not apply', function () { bidderRequest.gdprConsent = null; bidderRequest.uspConsent = null; - const requests = spec.buildRequests([bannerRequest], syncAddFPDToBidderRequest(bidderRequest)); - expect(requests).to.have.lengthOf(1); - const request = requests[0]; + return addFPDToBidderRequest(bidderRequest).then(bidderRequest => { + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + const request = requests[0]; - expect(request.data).to.not.be.empty; + expect(request.data).to.not.be.empty; - const payload = request.data; + const payload = request.data; - expect(payload).to.not.have.nested.property('regs.ext.gdpr'); - expect(payload).to.not.have.nested.property('user.ext.consent'); - expect(payload).to.not.have.nested.property('regs.ext.us_privacy'); + expect(payload).to.not.have.nested.property('regs.ext.gdpr'); + expect(payload).to.not.have.nested.property('user.ext.consent'); + expect(payload).to.not.have.nested.property('regs.ext.us_privacy'); + }); }); }); diff --git a/test/spec/modules/blastoBidAdapter_spec.js b/test/spec/modules/blastoBidAdapter_spec.js index 671f99fa938..2efdd5ad286 100644 --- a/test/spec/modules/blastoBidAdapter_spec.js +++ b/test/spec/modules/blastoBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from 'modules/blastoBidAdapter'; import 'modules/priceFloors.js'; import { newBidder } from 'src/adapters/bidderFactory'; import { config } from '../../../src/config.js'; -import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import { addFPDToBidderRequest } from '../../helpers/fpd.js'; // load modules that register ORTB processors import 'src/prebid.js'; @@ -167,23 +167,26 @@ describe('blastoAdapter', function () { }); describe('with user privacy regulations', function () { - it('should send the Coppa "required" flag set to "1" in the request', function () { + it('should send the Coppa "required" flag set to "1" in the request', async function () { sinon.stub(config, 'getConfig') .withArgs('coppa') .returns(true); - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(serverRequest.data.regs.coppa).to.equal(1); config.getConfig.restore(); }); - it('should send the GDPR Consent data in the request', function () { - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + it('should send the GDPR Consent data in the request', async function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest({ + ...bidderRequest, + gdprConsent + })); expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); }); - it('should send the CCPA data in the request', function () { - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + it('should send the CCPA data in the request', async function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest({...bidderRequest, ...{uspConsent: '1YYY'}})); expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); }); }); @@ -202,14 +205,14 @@ describe('blastoAdapter', function () { }); describe('build request', function () { - it('should return an empty array when no bid requests', function () { - const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + it('should return an empty array when no bid requests', async function () { + const bidRequest = spec.buildRequests([], await addFPDToBidderRequest(bidderRequest)); expect(bidRequest).to.be.an('array'); expect(bidRequest.length).to.equal(0); }); - it('should return a valid bid request object', function () { - const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid request object', async function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request).to.not.equal('array'); expect(request.data).to.be.an('object'); expect(request.method).to.equal('POST'); @@ -224,24 +227,24 @@ describe('blastoAdapter', function () { expect(request.data).to.have.property('device'); }); - it('should return a valid bid BANNER request object', function () { - const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid BANNER request object', async function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0].banner).to.exist; expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); }); if (FEATURES.VIDEO) { - it('should return a valid bid VIDEO request object', function () { - const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid VIDEO request object', async function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0].video).to.exist; expect(request.data.imp[0].video.w).to.be.an('number'); expect(request.data.imp[0].video.h).to.be.an('number'); }); } - it('should return a valid bid NATIVE request object', function () { - const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid NATIVE request object', async function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0]).to.be.an('object'); }); }) @@ -290,8 +293,8 @@ describe('blastoAdapter', function () { }] } }; - it('should interpret server response', function () { - const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + it('should interpret server response', async function () { + const bidRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(serverResponse, bidRequest); expect(bids).to.be.an('array'); const bid = bids[0]; diff --git a/test/spec/modules/bridgeuppBidAdapter_spec.js b/test/spec/modules/bridgeuppBidAdapter_spec.js index 0a786a7e9ec..8c4bcffdb36 100644 --- a/test/spec/modules/bridgeuppBidAdapter_spec.js +++ b/test/spec/modules/bridgeuppBidAdapter_spec.js @@ -6,7 +6,7 @@ import * as utils from 'src/utils.js'; import * as ajax from 'src/ajax.js'; import { hook } from '../../../src/hook'; import { config } from '../../../src/config.js'; -import { syncAddFPDToBidderRequest } from '../../helpers/fpd'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/consentManagementGpp.js'; @@ -85,7 +85,7 @@ describe('bridgeuppBidAdapter_spec', function () { }, }; - it('request should build with correct siteId', function () { + it('request should build with correct siteId', async function () { const bidRequests = [ { bidId: 'bidId', @@ -102,12 +102,12 @@ describe('bridgeuppBidAdapter_spec', function () { }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].ext.bidder.siteId).to.deep.equal('site-id-12'); }); - it('request should build with correct imp', function () { + it('request should build with correct imp', async function () { const expectedMetric = { url: 'https://sonarads.com' } @@ -134,7 +134,7 @@ describe('bridgeuppBidAdapter_spec', function () { bidfloorcur: 'USD' } }]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].id).to.deep.equal('bidId'); @@ -148,7 +148,7 @@ describe('bridgeuppBidAdapter_spec', function () { expect(ortbRequest.imp[0].rwdd).to.equal(1); }); - it('request should build with proper site data', function () { + it('request should build with proper site data', async function () { const bidRequests = [ { bidder: 'sonarads', @@ -179,7 +179,7 @@ describe('bridgeuppBidAdapter_spec', function () { } } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.site.domain).to.equal(SITE_DOMAIN_NAME); expect(ortbRequest.site.publisher.domain).to.equal('sonarads.com'); expect(ortbRequest.site.page).to.equal(SITE_PAGE); @@ -193,7 +193,7 @@ describe('bridgeuppBidAdapter_spec', function () { expect(ortbRequest.site.content.url).to.equal(SITE_PAGE + '/games1') }); - it('request should build with proper device data', function () { + it('request should build with proper device data', async function () { const bidRequests = [ { bidder: 'sonarads', @@ -228,7 +228,7 @@ describe('bridgeuppBidAdapter_spec', function () { } } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.device.dnt).to.equal(1); expect(ortbRequest.device.lmt).to.equal(0); expect(ortbRequest.device.js).to.equal(0); @@ -245,8 +245,8 @@ describe('bridgeuppBidAdapter_spec', function () { expect(ortbRequest.device.geo.lon).to.deep.equal(2.3522); }); - it('should properly build a request with source object', function () { - const expectedSchain = { id: 'prebid' }; + it('should properly build a request with source object', async function () { + const expectedSchain = {id: 'prebid'}; const ortb2 = { source: { pchain: 'sonarads', @@ -268,12 +268,12 @@ describe('bridgeuppBidAdapter_spec', function () { }, }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.source.schain).to.deep.equal(expectedSchain); expect(ortbRequest.source.pchain).to.equal('sonarads'); }); - it('should properly user object', function () { + it('should properly user object', async function () { const bidRequests = [ { bidder: 'sonarads', @@ -314,7 +314,7 @@ describe('bridgeuppBidAdapter_spec', function () { } } } - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(br)); const ortbRequest = request.data; expect(ortbRequest.user.yob).to.deep.equal(2012); expect(ortbRequest.user.keyowrds).to.deep.equal('test test'); @@ -333,7 +333,7 @@ describe('bridgeuppBidAdapter_spec', function () { ]); }); - it('should properly build a request regs object', function () { + it('should properly build a request regs object', async function () { const bidRequests = [ { bidder: 'sonarads', @@ -357,14 +357,14 @@ describe('bridgeuppBidAdapter_spec', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.regs.coppa).to.equal(1); expect(ortbRequest.regs.gpp).to.equal('consent_string'); expect(ortbRequest.regs.gpp_sid).to.deep.equal([0, 1, 2]); expect(ortbRequest.regs.us_privacy).to.deep.equal('yes us privacy applied'); }); - it('gdpr test', function () { + it('gdpr test', async function () { // using privacy params from global bidder Request const bidRequests = [ { @@ -380,12 +380,12 @@ describe('bridgeuppBidAdapter_spec', function () { }, }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.ext.gdpr).to.deep.equal(1); expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); }); - it('should properly set tmax if available', function () { + it('should properly set tmax if available', async function () { // using tmax from global bidder Request const bidRequests = [ { @@ -402,12 +402,12 @@ describe('bridgeuppBidAdapter_spec', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); }); - it('should properly build a request with bcat field', function () { + it('should properly build a request with bcat field', async function () { const bcat = ['IAB1', 'IAB2']; const bidRequests = [ { @@ -429,11 +429,11 @@ describe('bridgeuppBidAdapter_spec', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.bcat).to.deep.equal(bcat); }); - it('should properly build a request with badv field', function () { + it('should properly build a request with badv field', async function () { const badv = ['nike.com']; const bidRequests = [ { @@ -455,11 +455,11 @@ describe('bridgeuppBidAdapter_spec', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.badv).to.deep.equal(badv); }); - it('should properly build a request with bapp field', function () { + it('should properly build a request with bapp field', async function () { const bapp = ['nike.com']; const bidRequests = [ { @@ -481,11 +481,11 @@ describe('bridgeuppBidAdapter_spec', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.bapp).to.deep.equal(bapp); }); - it('banner request test', function () { + it('banner request test', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -510,7 +510,7 @@ describe('bridgeuppBidAdapter_spec', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].banner).not.to.be.null; expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); @@ -521,7 +521,7 @@ describe('bridgeuppBidAdapter_spec', function () { expect(ortbRequest.imp[0].banner.mimes).to.deep.equal(['image/jpg', 'image/gif']); }); - it('banner request test with sizes > 1', function () { + it('banner request test with sizes > 1', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -538,7 +538,7 @@ describe('bridgeuppBidAdapter_spec', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].banner).not.to.be.null; expect(ortbRequest.imp[0].banner.format[0].w).to.equal(336); @@ -547,32 +547,32 @@ describe('bridgeuppBidAdapter_spec', function () { expect(ortbRequest.imp[0].banner.format[1].h).to.equal(50); }); - it('should properly build a request when coppa is true', function () { + it('should properly build a request when coppa is true', async function () { const bidRequests = []; const bidderRequest = {}; - config.setConfig({ coppa: true }); + config.setConfig({coppa: true}); - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.coppa).to.equal(1); }); - it('should properly build a request when coppa is false', function () { + it('should properly build a request when coppa is false', async function () { const bidRequests = []; const bidderRequest = {}; - config.setConfig({ coppa: false }); - let buildRequests = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + config.setConfig({coppa: false}); + let buildRequests = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = buildRequests.data; expect(ortbRequest.regs.coppa).to.equal(0); }); - it('should properly build a request when coppa is not defined', function () { + it('should properly build a request when coppa is not defined', async function () { const bidRequests = []; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs?.coppa).to.be.undefined; }); - it('build a banner request with bidFloor', function () { + it('build a banner request with bidFloor', async function () { const bidRequests = [ { bidder: 'sonarads', @@ -589,12 +589,12 @@ describe('bridgeuppBidAdapter_spec', function () { } } ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1); expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD'); }); - it('build a banner request with getFloor', function () { + it('build a banner request with getFloor', async function () { const bidRequests = [ { bidder: 'sonarads', @@ -608,11 +608,11 @@ describe('bridgeuppBidAdapter_spec', function () { siteId: 'site-id-12' }, getFloor: () => { - return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + return {currency: 'USD', floor: 1.23, size: '*', mediaType: '*'}; } } ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].bidfloor).equal(1.23); expect(ortbRequest.imp[0].bidfloorcur).equal('USD'); }); @@ -680,9 +680,9 @@ describe('bridgeuppBidAdapter_spec', function () { } } - it('should returns an empty array when bid response is empty', function () { + it('should returns an empty array when bid response is empty', async function () { const bidRequests = []; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const serverResponse = { headers: { get: function () { @@ -697,16 +697,16 @@ describe('bridgeuppBidAdapter_spec', function () { expect(spec.reportEventsEnabled).to.equal(false); }); - it('should return an empty array when there is no bid response', function () { + it('should return an empty array when there is no bid response', async function () { const bidRequests = []; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const serverResponse = { headers: { get: function () { return undefined } }, - body: { seatbid: [] } + body: {seatbid: []} }; const interpretedBids = spec.interpretResponse(serverResponse, request); @@ -714,7 +714,7 @@ describe('bridgeuppBidAdapter_spec', function () { expect(spec.reportEventsEnabled).to.equal(false); }); - it('return banner response', function () { + it('return banner response', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -727,7 +727,7 @@ describe('bridgeuppBidAdapter_spec', function () { siteId: 'site-id-12', } }]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const serverResponse = { headers: { get: function () { @@ -750,7 +750,7 @@ describe('bridgeuppBidAdapter_spec', function () { expect(interpretedBids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.sonarads.com'); }); - it('should set the reportEventsEnabled to true as part of the response', function () { + it('should set the reportEventsEnabled to true as part of the response', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -779,7 +779,7 @@ describe('bridgeuppBidAdapter_spec', function () { siteId: 'site-id-12', } }]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const serverResponse = { headers: { get: function (header) { @@ -796,7 +796,7 @@ describe('bridgeuppBidAdapter_spec', function () { expect(interpretedBids).to.have.length(1); }); - it('bid response when banner wins among two ad units', function () { + it('bid response when banner wins among two ad units', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -825,7 +825,7 @@ describe('bridgeuppBidAdapter_spec', function () { siteId: 'site-id-12', } }]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const serverResponse = { headers: { get: function (header) { diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index 1e345691bcf..f207e466d3e 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import {addFPDToBidderRequest} from '../../helpers/fpd'; import { spec } from 'modules/ccxBidAdapter.js'; import * as utils from 'src/utils.js'; @@ -497,7 +497,7 @@ describe('ccxAdapter', function () { }); describe('FLEDGE', function () { - it('should properly build a request when FLEDGE is enabled', function () { + it('should properly build a request when FLEDGE is enabled', async function () { let bidderRequest = { paapi: { enabled: true @@ -528,12 +528,12 @@ describe('ccxAdapter', function () { } ]; - let ortbRequest = spec.buildRequests(bids, syncAddFPDToBidderRequest(bidderRequest)); + let ortbRequest = spec.buildRequests(bids, await addFPDToBidderRequest(bidderRequest)); let data = JSON.parse(ortbRequest.data); expect(data.imp[0].ext.ae).to.equal(1); }); - it('should properly build a request when FLEDGE is disabled', function () { + it('should properly build a request when FLEDGE is disabled', async function () { let bidderRequest = { paapi: { enabled: false @@ -564,7 +564,7 @@ describe('ccxAdapter', function () { } ]; - let ortbRequest = spec.buildRequests(bids, syncAddFPDToBidderRequest(bidderRequest)); + let ortbRequest = spec.buildRequests(bids, await addFPDToBidderRequest(bidderRequest)); let data = JSON.parse(ortbRequest.data); expect(data.imp[0].ext.ae).to.be.undefined; }); diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js index 25114ac6602..f8084627683 100644 --- a/test/spec/modules/consentManagementGpp_spec.js +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -1,10 +1,8 @@ import { - consentTimeout, + consentConfig, GPPClient, - requestBidsHook, resetConsentData, setConsentConfig, - userCMP } from 'modules/consentManagementGpp.js'; import {gppDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; @@ -15,6 +13,19 @@ let expect = require('chai').expect; describe('consentManagementGpp', function () { beforeEach(resetConsentData); + after(resetConsentData); + + async function runHook(request = {}) { + let hookRan = false; + consentConfig.requestBidsHook(() => { + hookRan = true; + }, request); + try { + await consentConfig.loadConsentData() + } catch (e) { + } + return hookRan; + } describe('setConsentConfig tests:', function () { describe('empty setConsentConfig value', function () { @@ -30,36 +41,36 @@ describe('consentManagementGpp', function () { resetConsentData(); }); - it('should use system default values', function () { - setConsentConfig({ + it('should use system default values', async function () { + await setConsentConfig({ gpp: {} }); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(10000); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(10000); sinon.assert.callCount(utils.logInfo, 3); }); - it('should exit consent manager if config is not an object', function () { - setConsentConfig(''); - expect(userCMP).to.be.undefined; + it('should exit consent manager if config is not an object', async function () { + await setConsentConfig(''); + expect(consentConfig.cmpHandler).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); - it('should exit consentManagement module if config is "undefined"', function () { - setConsentConfig(undefined); - expect(userCMP).to.be.undefined; + it('should exit consentManagement module if config is "undefined"', async function () { + await setConsentConfig(undefined); + expect(consentConfig.cmpHandler).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); - it('should not produce any consent metadata', function () { - setConsentConfig(undefined) + it('should not produce any consent metadata', async function () { + await setConsentConfig(undefined) let consentMetadata = gppDataHandler.getConsentMeta(); expect(consentMetadata).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }) - it('should immediately look up consent data', () => { - setConsentConfig({ + it('should immediately look up consent data', async () => { + await setConsentConfig({ gpp: { cmpApi: 'invalid' } @@ -73,7 +84,7 @@ describe('consentManagementGpp', function () { config.resetConfig(); }); - it('results in all user settings overriding system defaults', function () { + it('results in all user settings overriding system defaults', async function () { let allConfig = { gpp: { cmpApi: 'iab', @@ -81,22 +92,22 @@ describe('consentManagementGpp', function () { } }; - setConsentConfig(allConfig); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(7500); + await setConsentConfig(allConfig); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(7500); }); - it('should recognize config.gpp, with default cmpApi and timeout', function () { - setConsentConfig({ + it('should recognize config.gpp, with default cmpApi and timeout', async function () { + await setConsentConfig({ gpp: {} }); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(10000); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(10000); }); - it('should enable gppDataHandler', () => { - setConsentConfig({ + it('should enable gppDataHandler', async () => { + await setConsentConfig({ gpp: {} }); expect(gppDataHandler.enabled).to.be.true; @@ -108,7 +119,7 @@ describe('consentManagementGpp', function () { config.resetConfig(); }); - it('results in user settings overriding system defaults', () => { + it('results in user settings overriding system defaults', async () => { let staticConfig = { gpp: { cmpApi: 'static', @@ -130,8 +141,8 @@ describe('consentManagementGpp', function () { } }; - setConsentConfig(staticConfig); - expect(userCMP).to.be.equal('static'); + await setConsentConfig(staticConfig); + expect(consentConfig.cmpHandler).to.be.equal('static'); const consent = gppDataHandler.getConsentData(); expect(consent.gppString).to.eql(staticConfig.gpp.consentData.gppString); expect(consent.gppData).to.eql(staticConfig.gpp.consentData); @@ -371,7 +382,7 @@ describe('consentManagementGpp', function () { }); }) - describe('requestBidsHook tests:', function () { + describe('moduleConfig.requestBidsHook tests:', function () { let goodConfig = { gpp: { cmpApi: 'iab', @@ -408,86 +419,67 @@ describe('consentManagementGpp', function () { config.resetConfig(); }); - it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () { + it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', async function () { let badCMPConfig = { gpp: { cmpApi: 'bad' } }; - setConsentConfig(badCMPConfig); - expect(userCMP).to.be.equal(badCMPConfig.gpp.cmpApi); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + await setConsentConfig(badCMPConfig); + expect(consentConfig.cmpHandler).to.be.equal(badCMPConfig.gpp.cmpApi); + expect(await runHook()).to.be.true; let consent = gppDataHandler.getConsentData(); - - sinon.assert.calledOnce(utils.logWarn); - expect(didHookReturn).to.be.true; expect(consent).to.be.null; + sinon.assert.calledOnce(utils.logWarn); }); - it('should call gppDataHandler.setConsentData() when unknown CMP api is used', () => { - setConsentConfig({ + it('should call gppDataHandler.setConsentData() when unknown CMP api is used', async () => { + await setConsentConfig({ gpp: { cmpApi: 'invalid' } }); - let hookRan = false; - requestBidsHook(() => { - hookRan = true; - }, {}); - expect(hookRan).to.be.true; + expect(await runHook()).to.be.true; expect(gppDataHandler.ready).to.be.true; }) - it('should throw proper errors when CMP is not found', function () { - setConsentConfig(goodConfig); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + it('should throw proper errors when CMP is not found', async function () { + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.false; let consent = gppDataHandler.getConsentData(); // throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) sinon.assert.calledTwice(utils.logError); - expect(didHookReturn).to.be.false; expect(consent).to.be.null; expect(gppDataHandler.ready).to.be.true; }); - it('should not trip when adUnits have no size', () => { - setConsentConfig(staticConfig); - let ran = false; - requestBidsHook(() => { - ran = true; - }, { + it('should not trip when adUnits have no size', async () => { + await setConsentConfig(staticConfig); + const request = { adUnits: [{ code: 'test', mediaTypes: { video: {} } }] - }); - return gppDataHandler.promise.then(() => { - expect(ran).to.be.true; - }); + }; + expect(await runHook(request)).to.be.true; }); - it('should continue the auction immediately, without consent data, if timeout is 0', (done) => { - window.__gpp = function () {}; - setConsentConfig({ + it('should continue the auction after timeout, if cmp does not reply', async () => { + window.__gpp = function () { + }; + await setConsentConfig({ gpp: { cmpApi: 'iab', - timeout: 0 + timeout: 10 } }); try { - requestBidsHook(() => { - const consent = gppDataHandler.getConsentData(); - expect(consent.applicableSections).to.deep.equal([]); - expect(consent.gppString).to.be.undefined; - done(); - }, {}) + expect(await runHook()).to.be.true; + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; } finally { delete window.__gpp; } @@ -525,7 +517,7 @@ describe('consentManagementGpp', function () { function startHook() { let hookRan = false; - requestBidsHook(() => { + consentConfig.requestBidsHook(() => { hookRan = true; }, {}); return () => new Promise((resolve) => setTimeout(resolve(hookRan), 5)); @@ -537,29 +529,34 @@ describe('consentManagementGpp', function () { triggerCMPEvent('sectionChange', {signalStatus: 'not ready'}); expect(await didHookRun()).to.be.false; triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await consentConfig.loadConsentData(); expect(await didHookRun()).to.be.true; expect(gppDataHandler.getConsentData().gppString).to.eql('xyz'); }); it('should re-use GPP data once ready', async () => { let didHookRun = startHook(); - triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); await didHookRun(); + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await consentConfig.loadConsentData(); window.__gpp.reset(); didHookRun = startHook(); + await consentConfig.loadConsentData(); expect(await didHookRun()).to.be.true; sinon.assert.notCalled(window.__gpp); }); it('after signalStatus: ready, should wait again for signalStatus: ready', async () => { let didHookRun = startHook(); - triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); await didHookRun(); + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await consentConfig.loadConsentData(); for (let run of ['first', 'second']) { triggerCMPEvent('cmpDisplayStatus', {signalStatus: 'not ready'}); didHookRun = startHook(); expect(await didHookRun()).to.be.false; triggerCMPEvent('sectionChange', {signalStatus: 'ready', gppString: run}); + await consentConfig.loadConsentData(); expect(await didHookRun()).to.be.true; expect(gppDataHandler.getConsentData().gppString).to.eql(run); } diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 9131e6748e8..0c27c6f69d8 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,14 +1,4 @@ -import { - actionTimeout, - consentTimeout, - gdprScope, - loadConsentData, - requestBidsHook, - resetConsentData, - setConsentConfig, - staticConsentData, - userCMP -} from 'modules/consentManagementTcf.js'; +import {consentConfig, gdprScope, resetConsentData, setConsentConfig, } from 'modules/consentManagementTcf.js'; import {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; @@ -31,41 +21,41 @@ describe('consentManagement', function () { resetConsentData(); }); - it('should use system default values', function () { - setConsentConfig({}); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(10000); + it('should use system default values', async function () { + await setConsentConfig({}); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(10000); expect(gdprScope).to.be.equal(false); sinon.assert.callCount(utils.logInfo, 3); }); - it('should exit consent manager if config is not an object', function () { - setConsentConfig(''); - expect(userCMP).to.be.undefined; + it('should exit consent manager if config is not an object', async function () { + await setConsentConfig(''); + expect(consentConfig.cmpHandler).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); - it('should exit consent manager if gdpr not set with new config structure', function () { - setConsentConfig({ usp: { cmpApi: 'iab', timeout: 50 } }); - expect(userCMP).to.be.undefined; + it('should exit consent manager if gdpr not set with new config structure', async function () { + await setConsentConfig({usp: {cmpApi: 'iab', timeout: 50}}); + expect(consentConfig.cmpHandler).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); - it('should exit consentManagement module if config is "undefined"', function() { - setConsentConfig(undefined); - expect(userCMP).to.be.undefined; + it('should exit consentManagement module if config is "undefined"', async function () { + await setConsentConfig(undefined); + expect(consentConfig.cmpHandler).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }); - it('should not produce any consent metadata', function() { - setConsentConfig(undefined) + it('should not produce any consent metadata', async function () { + await setConsentConfig(undefined) let consentMetadata = gdprDataHandler.getConsentMeta(); expect(consentMetadata).to.be.undefined; sinon.assert.calledOnce(utils.logWarn); }) - it('should immediately look up consent data', () => { - setConsentConfig({gdpr: {cmpApi: 'invalid'}}); + it('should immediately look up consent data', async () => { + await setConsentConfig({gdpr: {cmpApi: 'invalid'}}); expect(gdprDataHandler.ready).to.be.true; }) }); @@ -75,71 +65,71 @@ describe('consentManagement', function () { config.resetConfig(); }); - it('results in all user settings overriding system defaults', function () { + it('results in all user settings overriding system defaults', async function () { let allConfig = { cmpApi: 'iab', timeout: 7500, defaultGdprScope: true }; - setConsentConfig(allConfig); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(7500); + await setConsentConfig(allConfig); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(7500); expect(gdprScope).to.be.true; }); - it('should use new consent manager config structure for gdpr', function () { - setConsentConfig({ - gdpr: { cmpApi: 'daa', timeout: 8700 } + it('should use new consent manager config structure for gdpr', async function () { + await setConsentConfig({ + gdpr: {cmpApi: 'daa', timeout: 8700} }); - expect(userCMP).to.be.equal('daa'); - expect(consentTimeout).to.be.equal(8700); + expect(consentConfig.cmpHandler).to.be.equal('daa'); + expect(consentConfig.cmpTimeout).to.be.equal(8700); }); - it('should ignore config.usp and use config.gdpr, with default cmpApi', function () { - setConsentConfig({ - gdpr: { timeout: 5000 }, - usp: { cmpApi: 'daa', timeout: 50 } + it('should ignore config.usp and use config.gdpr, with default cmpApi', async function () { + await setConsentConfig({ + gdpr: {timeout: 5000}, + usp: {cmpApi: 'daa', timeout: 50} }); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(5000); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(5000); }); - it('should ignore config.usp and use config.gdpr, with default cmpAip and timeout', function () { - setConsentConfig({ + it('should ignore config.usp and use config.gdpr, with default cmpAip and timeout', async function () { + await setConsentConfig({ gdpr: {}, - usp: { cmpApi: 'daa', timeout: 50 } + usp: {cmpApi: 'daa', timeout: 50} }); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(10000); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(10000); }); - it('should recognize config.gdpr, with default cmpAip and timeout', function () { - setConsentConfig({ + it('should recognize config.gdpr, with default cmpAip and timeout', async function () { + await setConsentConfig({ gdpr: {} }); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(10000); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(10000); }); - it('should fallback to old consent manager config object if no config.gdpr', function () { - setConsentConfig({ + it('should fallback to old consent manager config object if no config.gdpr', async function () { + await setConsentConfig({ cmpApi: 'iab', timeout: 3333, gdpr: false }); - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(3333); + expect(consentConfig.cmpHandler).to.be.equal('iab'); + expect(consentConfig.cmpTimeout).to.be.equal(3333); expect(gdprScope).to.be.equal(false); }); - it('should enable gdprDataHandler', () => { - setConsentConfig({gdpr: {}}); + it('should enable gdprDataHandler', async () => { + await setConsentConfig({gdpr: {}}); expect(gdprDataHandler.enabled).to.be.true; }); }); @@ -154,7 +144,7 @@ describe('consentManagement', function () { config.resetConfig(); }); - it('results in user settings overriding system defaults for v2 spec', () => { + it('results in user settings overriding system defaults for v2 spec', async () => { const consentData = { 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', 'cmpId': 92, @@ -220,18 +210,17 @@ describe('consentManagement', function () { } }; - setConsentConfig({ + await setConsentConfig({ cmpApi: 'static', timeout: 7500, consentData: packageCfg(consentData) }); - expect(userCMP).to.be.equal('static'); - expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(consentConfig.cmpHandler).to.be.equal('static'); expect(gdprScope).to.be.equal(false); const consent = gdprDataHandler.getConsentData(); expect(consent.consentString).to.eql(consentData.tcString); expect(consent.vendorData).to.eql(consentData); - expect(staticConsentData).to.be.equal(consentData); + expect(consentConfig.staticConsentData).to.be.equal(consentData); }); }); }); @@ -250,14 +239,23 @@ describe('consentManagement', function () { consentData: {} } - let didHookReturn; - beforeEach(resetConsentData); after(resetConsentData) + async function runHook(request = {}) { + let hookRan = false; + consentConfig.requestBidsHook(() => { + hookRan = true + }, request); + try { + await consentConfig.loadConsentData(); + } catch (e) { + } + return hookRan; + } + describe('error checks:', function () { beforeEach(function () { - didHookReturn = false; sinon.stub(utils, 'logWarn'); sinon.stub(utils, 'logError'); }); @@ -268,69 +266,52 @@ describe('consentManagement', function () { config.resetConfig(); }); - it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () { + it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', async function () { let badCMPConfig = { cmpApi: 'bad' }; - setConsentConfig(badCMPConfig); - expect(userCMP).to.be.equal(badCMPConfig.cmpApi); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + await setConsentConfig(badCMPConfig); + expect(consentConfig.cmpHandler).to.be.equal(badCMPConfig.cmpApi); + expect(await runHook()).to.be.true; let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logWarn); - expect(didHookReturn).to.be.true; expect(consent).to.be.null; }); - it('should call gdprDataHandler.setConsentData() when unknown CMP api is used', () => { - setConsentConfig({gdpr: {cmpApi: 'invalid'}}); - let hookRan = false; - requestBidsHook(() => { hookRan = true; }, {}); - expect(hookRan).to.be.true; + it('should call gdprDataHandler.setConsentData() when unknown CMP api is used', async () => { + await setConsentConfig({gdpr: {cmpApi: 'invalid'}}); + expect(await runHook()).to.be.true; expect(gdprDataHandler.ready).to.be.true; }) - it('should throw proper errors when CMP is not found', function () { - setConsentConfig(goodConfig); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + it('should throw proper errors when CMP is not found', async function () { + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.false; let consent = gdprDataHandler.getConsentData(); // throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) sinon.assert.calledTwice(utils.logError); - expect(didHookReturn).to.be.false; expect(consent).to.be.null; expect(gdprDataHandler.ready).to.be.true; }); - it('should not trip when adUnits have no size', () => { - setConsentConfig(staticConfig); - let ran = false; - requestBidsHook(() => { - ran = true; - }, {adUnits: [{code: 'test', mediaTypes: {video: {}}}]}); - return gdprDataHandler.promise.then(() => { - expect(ran).to.be.true; - }); + it('should not trip when adUnits have no size', async () => { + await setConsentConfig(staticConfig); + expect(await runHook({adUnits: [{code: 'test', mediaTypes: {video: {}}}]})).to.be.true; }); - it('should continue the auction immediately, without consent data, if timeout is 0', (done) => { - setConsentConfig({ + it('should continue the auction immediately, without consent data, if timeout is 0', async () => { + window.__tcfapi = function () { + }; + await setConsentConfig({ cmpApi: 'iab', timeout: 0, defaultGdprScope: true }); - window.__tcfapi = function () {}; try { - requestBidsHook(() => { - const consent = gdprDataHandler.getConsentData(); - expect(consent.gdprApplies).to.be.true; - expect(consent.consentString).to.be.undefined; - done(); - }, {}) + expect(await runHook()).to.be.true; + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; } finally { delete window.__tcfapi; } @@ -347,7 +328,6 @@ describe('consentManagement', function () { } beforeEach(function () { - didHookReturn = false; window.__tcfapi = function () { }; }); @@ -358,29 +338,25 @@ describe('consentManagement', function () { resetConsentData(); }); - it('should bypass CMP and simply use previously stored consentData', function () { + it('should bypass CMP and simply use previously stored consentData', async function () { let testConsentData = { gdprApplies: true, tcString: 'xyz', }; cmpStub = sinon.stub(window, '__tcfapi').callsFake(mockCMP(testConsentData)); - setConsentConfig(goodConfig); - requestBidsHook(() => { }, {}); + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; cmpStub.reset(); - requestBidsHook(() => { - didHookReturn = true; - }, {}); + expect(await runHook()).to.be.true; let consent = gdprDataHandler.getConsentData(); - - expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.gdprApplies).to.be.true; sinon.assert.notCalled(cmpStub); }); - it('should not set consent.gdprApplies to true if defaultGdprScope is true', function () { + it('should not set consent.gdprApplies to true if defaultGdprScope is true', async function () { let testConsentData = { gdprApplies: false, tcString: 'xyz', @@ -388,18 +364,14 @@ describe('consentManagement', function () { cmpStub = sinon.stub(window, '__tcfapi').callsFake(mockCMP(testConsentData)); - setConsentConfig({ + await setConsentConfig({ cmpApi: 'iab', timeout: 7500, defaultGdprScope: true }); - requestBidsHook(() => { - didHookReturn = true; - }, {}); - + expect(await runHook()).to.be.true; let consent = gdprDataHandler.getConsentData(); - expect(consent.gdprApplies).to.be.false; }); }); @@ -437,17 +409,15 @@ describe('consentManagement', function () { } function testIFramedPage(testName, messageFormatString, tarConsentString, ver) { - it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { + it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, async () => { stringifyResponse = messageFormatString; - setConsentConfig(goodConfig); - requestBidsHook(() => { - let consent = gdprDataHandler.getConsentData(); - sinon.assert.notCalled(utils.logError); - expect(consent.consentString).to.equal(tarConsentString); - expect(consent.gdprApplies).to.be.true; - expect(consent.apiVersion).to.equal(ver); - done(); - }, {}); + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; + let consent = gdprDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(consent.consentString).to.equal(tarConsentString); + expect(consent.gdprApplies).to.be.true; + expect(consent.apiVersion).to.equal(ver); }); } @@ -493,7 +463,6 @@ describe('consentManagement', function () { let cmpStub = sinon.stub(); beforeEach(function () { - didHookReturn = false; sinon.stub(utils, 'logError'); sinon.stub(utils, 'logWarn'); }); @@ -515,7 +484,7 @@ describe('consentManagement', function () { delete window.__tcfapi; }); - it('performs lookup check and stores consentData for a valid existing user', function () { + it('performs lookup check and stores consentData for a valid existing user', async function () { let testConsentData = { tcString: 'abc12345234', gdprApplies: true, @@ -526,20 +495,16 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig(goodConfig); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); }); - it('produces gdpr metadata', function () { + it('produces gdpr metadata', async function () { let testConsentData = { tcString: 'abc12345234', gdprApplies: true, @@ -553,11 +518,9 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig(goodConfig); + await setConsentConfig(goodConfig); - requestBidsHook(() => { - didHookReturn = true; - }, {}); + expect(await runHook()).to.be.true; let consentMeta = gdprDataHandler.getConsentMeta(); sinon.assert.notCalled(utils.logError); expect(consentMeta.consentStringSize).to.be.above(0) @@ -566,7 +529,7 @@ describe('consentManagement', function () { expect(consentMeta.generatedAt).to.be.above(1644367751709); }); - it('performs lookup check and stores consentData for a valid existing user with additional consent', function () { + it('performs lookup check and stores consentData for a valid existing user with additional consent', async function () { let testConsentData = { tcString: 'abc12345234', addtlConsent: 'superduperstring', @@ -578,21 +541,17 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig(goodConfig); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + await setConsentConfig(goodConfig); + expect(await runHook()).to.be.true; let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.addtlConsent).to.equal(testConsentData.addtlConsent); expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); }); - it('throws an error when processCmpData check fails + does not call requestBids callback', function () { + it('throws an error when processCmpData check fails + does not call requestBids callback', async function () { let testConsentData = {}; let bidsBackHandlerReturn = false; @@ -600,21 +559,18 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConsentConfig(goodConfig); + await setConsentConfig(goodConfig); sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); [utils.logWarn, utils.logError].forEach((stub) => stub.reset()); - requestBidsHook(() => { - didHookReturn = true; - }, { bidsBackHandler: () => bidsBackHandlerReturn = true }); + expect(await runHook({bidsBackHandler: () => bidsBackHandlerReturn = true})).to.be.false; let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logError); sinon.assert.notCalled(utils.logWarn); - expect(didHookReturn).to.be.false; expect(bidsBackHandlerReturn).to.be.true; expect(consent).to.be.null; expect(gdprDataHandler.ready).to.be.true; @@ -623,17 +579,18 @@ describe('consentManagement', function () { describe('when proper consent is not available', () => { let tcfStub; - function runAuction() { - setConsentConfig({ + async function runAuction() { + await setConsentConfig({ cmpApi: 'iab', timeout: 10, defaultGdprScope: true }); return new Promise((resolve, reject) => { - requestBidsHook(() => { - didHookReturn = true; + let hookRan = false; + consentConfig.requestBidsHook(() => { + hookRan = true; }, {}); - setTimeout(() => didHookReturn ? resolve() : reject(new Error('Auction did not run')), 20); + setTimeout(() => hookRan ? resolve() : reject(new Error('Auction did not run')), 20); }) } @@ -678,44 +635,42 @@ describe('consentManagement', function () { }); }); - it('should timeout after actionTimeout from the first CMP event', (done) => { + it('should timeout after actionTimeout from the first CMP event', async () => { mockTcfEvent({ eventStatus: 'cmpuishown', tcString: 'mock-consent-string', vendorData: {} }); - setConsentConfig({ + await setConsentConfig({ timeout: 1000, actionTimeout: 100, cmpApi: 'iab', defaultGdprScope: true }); let hookRan = false; - requestBidsHook(() => { + consentConfig.requestBidsHook(() => { hookRan = true; }, {}); - setTimeout(() => { - expect(hookRan).to.be.true; - done(); - }, 200) + return new Promise((resolve) => setTimeout(resolve, 200)) + .then(() => { + expect(hookRan).to.be.true; + }) }); - it('should still pick up consent data when actionTimeout is 0', (done) => { + it('should still pick up consent data when actionTimeout is 0', async () => { mockTcfEvent({ eventStatus: 'tcloaded', tcString: 'mock-consent-string', vendorData: {} }); - setConsentConfig({ + await setConsentConfig({ timeout: 1000, actionTimeout: 0, cmpApi: 'iab', defaultGdprScope: true }); - requestBidsHook(() => { - expect(gdprDataHandler.getConsentData().consentString).to.eql('mock-consent-string'); - done(); - }, {}) + expect(await runHook()).to.be.true; + expect(gdprDataHandler.getConsentData().consentString).to.eql('mock-consent-string'); }) Object.entries({ @@ -740,7 +695,7 @@ describe('consentManagement', function () { }); }); - it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () { + it('It still considers it a valid cmp response if gdprApplies is not a boolean', async function () { // gdprApplies is undefined, should just still store consent response but use whatever defaultGdprScope was let testConsentData = { tcString: 'abc12345234', @@ -751,18 +706,14 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig({ + await setConsentConfig({ cmpApi: 'iab', timeout: 7500, defaultGdprScope: true }); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); + expect(await runHook()).to.be.true; let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.gdprApplies).to.be.true; expect(consent.apiVersion).to.equal(2); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 30de96e33d0..551db4fc72d 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,14 +1,11 @@ -import { expect } from 'chai'; -import { - spec, - storage, -} from 'modules/criteoBidAdapter.js'; +import {expect} from 'chai'; +import {spec, storage} from 'modules/criteoBidAdapter.js'; import * as utils from 'src/utils.js'; import * as refererDetection from 'src/refererDetection.js'; import * as ajax from 'src/ajax.js'; -import { config } from '../../../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import {config} from '../../../src/config.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd'; import 'modules/userId/index.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; @@ -687,7 +684,7 @@ describe('The Criteo bidding adapter', function () { config.resetConfig(); }); - it('should properly build a request using random uuid as auction id', function () { + it('should properly build a request using random uuid as auction id', async function () { const generateUUIDStub = sinon.stub(utils, 'generateUUID'); generateUUIDStub.returns('def'); const bidderRequest = {}; @@ -703,13 +700,13 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.id).to.equal('def'); generateUUIDStub.restore(); }); - it('should properly transmit source.tid if available', function () { + it('should properly transmit source.tid if available', async function () { const bidderRequest = { ortb2: { source: { @@ -729,12 +726,12 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.source.tid).to.equal('abc'); }); - it('should properly transmit tmax if available', function () { + it('should properly transmit tmax if available', async function () { const bidRequests = [ { bidder: 'criteo', @@ -748,12 +745,12 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); }); - it('should properly transmit bidId if available', function () { + it('should properly transmit bidId if available', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -768,12 +765,12 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].id).to.equal('bidId'); }); - it('should properly build a zoneId request', function () { + it('should properly build a zoneId request', async function () { const bidRequests = [ { bidder: 'criteo', @@ -795,7 +792,7 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&im=1&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -811,7 +808,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.regs.ext.gdprversion).to.equal(1); }); - it('should properly forward eids', function () { + it('should properly forward eids', async function () { const bidRequests = [ { bidder: 'criteo', @@ -842,7 +839,7 @@ describe('The Criteo bidding adapter', function () { } } } - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(br)); const ortbRequest = request.data; expect(ortbRequest.user.ext.eids).to.deep.equal([ { @@ -856,7 +853,7 @@ describe('The Criteo bidding adapter', function () { }); if (FEATURES.NATIVE) { - it('should properly build a native request without assets', function () { + it('should properly build a native request without assets', async function () { const bidRequests = [ { mediaTypes: { @@ -865,7 +862,7 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].native.request_native).to.not.be.null; expect(ortbRequest.imp[0].native.request_native.assets).to.be.undefined; @@ -873,7 +870,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.NATIVE) { - it('should properly build a native request with assets', function () { + it('should properly build a native request with assets', async function () { const assets = [{ required: 1, id: 1, @@ -924,14 +921,14 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].native.request_native).to.not.be.null; expect(ortbRequest.imp[0].native.request_native.assets).to.deep.equal(assets); }); } - it('should properly build a networkId request', function () { + it('should properly build a networkId request', async function () { const bidderRequest = { refererInfo: { page: refererUrl, @@ -967,7 +964,7 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -983,7 +980,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.regs.ext.gdpr).to.equal(0); }); - it('should properly build a mixed request with both a zoneId and a networkId', function () { + it('should properly build a mixed request with both a zoneId and a networkId', async function () { const bidderRequest = { refererInfo: { page: refererUrl, @@ -1027,7 +1024,7 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -1049,7 +1046,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.user?.ext?.consent).to.equal(undefined); }); - it('should properly build a request with undefined gdpr consent fields when they are not provided', function () { + it('should properly build a request with undefined gdpr consent fields when they are not provided', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1069,12 +1066,12 @@ describe('The Criteo bidding adapter', function () { gdprConsent: {}, }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.user?.ext?.consent).to.equal(undefined); expect(ortbRequest.regs?.ext?.gdpr).to.equal(undefined); }); - it('should properly build a request with ccpa consent field', function () { + it('should properly build a request with ccpa consent field', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1094,11 +1091,11 @@ describe('The Criteo bidding adapter', function () { uspConsent: '1YNY', }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.ext.us_privacy).to.equal('1YNY'); }); - it('should properly build a request with overridden tmax', function () { + it('should properly build a request with overridden tmax', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1117,11 +1114,11 @@ describe('The Criteo bidding adapter', function () { timeout: 1234 }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.tmax).to.equal(1234); }); - it('should properly build a request with device sua field', function () { + it('should properly build a request with device sua field', async function () { const sua = { platform: { brand: 'abc' @@ -1151,12 +1148,12 @@ describe('The Criteo bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.device.ext.sua).not.to.be.null; expect(ortbRequest.device.ext.sua.platform.brand).to.equal('abc'); }); - it('should properly build a request with gpp consent field', function () { + it('should properly build a request with gpp consent field', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1178,12 +1175,12 @@ describe('The Criteo bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string'); expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); }); - it('should properly build a request with dsa object', function () { + it('should properly build a request with dsa object', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1218,11 +1215,11 @@ describe('The Criteo bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.regs.ext.dsa).to.deep.equal(dsa); }); - it('should properly build a request with schain object', function () { + it('should properly build a request with schain object', async function () { const expectedSchain = { someProperty: 'someValue' }; @@ -1242,11 +1239,11 @@ describe('The Criteo bidding adapter', function () { }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.source.ext.schain).to.equal(expectedSchain); }); - it('should properly build a request with bcat field', function () { + it('should properly build a request with bcat field', async function () { const bcat = ['IAB1', 'IAB2']; const bidRequests = [ { @@ -1268,11 +1265,11 @@ describe('The Criteo bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.bcat).to.deep.equal(bcat); }); - it('should properly build a request with badv field', function () { + it('should properly build a request with badv field', async function () { const badv = ['ford.com']; const bidRequests = [ { @@ -1294,11 +1291,11 @@ describe('The Criteo bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.badv).to.deep.equal(badv); }); - it('should properly build a request with bapp field', function () { + it('should properly build a request with bapp field', async function () { const bapp = ['com.foo.mygame']; const bidRequests = [ { @@ -1320,12 +1317,12 @@ describe('The Criteo bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.bapp).to.deep.equal(bapp); }); if (FEATURES.VIDEO) { - it('should properly build a video request', function () { + it('should properly build a video request', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1366,7 +1363,7 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -1399,7 +1396,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.VIDEO) { - it('should properly build a video request with more than one player size', function () { + it('should properly build a video request with more than one player size', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1426,7 +1423,7 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -1447,7 +1444,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.VIDEO) { - it('should properly build a video request when mediaTypes.video.skip=0', function () { + it('should properly build a video request when mediaTypes.video.skip=0', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1470,7 +1467,7 @@ describe('The Criteo bidding adapter', function () { } } ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -1487,7 +1484,7 @@ describe('The Criteo bidding adapter', function () { }); } - it('should properly build a request without first party data', function () { + it('should properly build a request without first party data', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1503,7 +1500,10 @@ describe('The Criteo bidding adapter', function () { }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2: {} })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({ + ...bidderRequest, + ortb2: {} + })).data; expect(ortbRequest.site.page).to.equal(refererUrl); expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); @@ -1516,7 +1516,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.regs.ext.gdprversion).to.equal(1); }); - it('should properly build a request with first party data', function () { + it('should properly build a request with first party data', async function () { const siteData = { keywords: ['power tools'], content: { @@ -1526,8 +1526,8 @@ describe('The Criteo bidding adapter', function () { segtax: 3 }, segment: [ - { 'id': '1001' }, - { 'id': '1002' } + {'id': '1001'}, + {'id': '1002'} ] }] }, @@ -1545,8 +1545,8 @@ describe('The Criteo bidding adapter', function () { segtax: 3 }, segment: [ - { 'id': '1001' }, - { 'id': '1002' } + {'id': '1001'}, + {'id': '1002'} ] }], ext: { @@ -1585,37 +1585,42 @@ describe('The Criteo bidding adapter', function () { user: userData }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; - expect(ortbRequest.user).to.deep.equal({ ...userData, ext: { ...userData.ext, consent: 'consentDataString' } }); - expect(ortbRequest.site).to.deep.equal({ ...siteData, page: refererUrl, domain: 'criteo.com', publisher: { ...ortbRequest.site.publisher, domain: 'criteo.com' } }); + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; + expect(ortbRequest.user).to.deep.equal({...userData, ext: {...userData.ext, consent: 'consentDataString'}}); + expect(ortbRequest.site).to.deep.equal({ + ...siteData, + page: refererUrl, + domain: 'criteo.com', + publisher: {...ortbRequest.site.publisher, domain: 'criteo.com'} + }); expect(ortbRequest.imp[0].ext.bidfloor).to.equal(0.75); expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.equal('abc') }); - it('should properly build a request when coppa flag is true', function () { + it('should properly build a request when coppa flag is true', async function () { const bidRequests = []; const bidderRequest = {}; - config.setConfig({ coppa: true }); - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + config.setConfig({coppa: true}); + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.coppa).to.equal(1); }); - it('should properly build a request when coppa flag is false', function () { + it('should properly build a request when coppa flag is false', async function () { const bidRequests = []; const bidderRequest = {}; - config.setConfig({ coppa: false }); - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + config.setConfig({coppa: false}); + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.coppa).to.equal(0); }); - it('should properly build a request when coppa flag is not defined', function () { + it('should properly build a request when coppa flag is not defined', async function () { const bidRequests = []; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs?.coppa).to.be.undefined; }); - it('should properly build a banner request with floors', function () { + it('should properly build a banner request with floors', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1647,16 +1652,16 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } + '300x250': {'currency': 'USD', 'floor': 1}, + '728x90': {'currency': 'USD', 'floor': 2} } }); }); - it('should properly build a request with static floors', function () { + it('should properly build a request with static floors', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1674,16 +1679,16 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { - '300x250': { 'currency': 'EUR', 'floor': 1 }, - '728x90': { 'currency': 'EUR', 'floor': 1 } + '300x250': {'currency': 'EUR', 'floor': 1}, + '728x90': {'currency': 'EUR', 'floor': 1} } }); }); - it('should properly build a video request with several player sizes with floors', function () { + it('should properly build a video request with several player sizes with floors', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1715,17 +1720,17 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'video': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } + '300x250': {'currency': 'USD', 'floor': 1}, + '728x90': {'currency': 'USD', 'floor': 2} } }); }); if (FEATURES.VIDEO && FEATURES.NATIVE) { - it('should properly build a multi format request with floors', function () { + it('should properly build a multi format request with floors', async function () { const bidRequests = [ { bidder: 'criteo', @@ -1778,27 +1783,27 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].banner).not.to.be.null; expect(ortbRequest.imp[0].video).not.to.be.null; expect(ortbRequest.imp[0].native.request_native).not.to.be.null; expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.deep.equal('abc'); expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } + '300x250': {'currency': 'USD', 'floor': 1}, + '728x90': {'currency': 'USD', 'floor': 2} }, 'video': { - '640x480': { 'currency': 'EUR', 'floor': 3.2 } + '640x480': {'currency': 'EUR', 'floor': 3.2} }, 'native': { - '*': { 'currency': 'YEN', 'floor': 4.99 } + '*': {'currency': 'YEN', 'floor': 4.99} } }); }); } - it('should properly build a request when imp.rwdd is present', function () { + it('should properly build a request when imp.rwdd is present', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -1818,11 +1823,11 @@ describe('The Criteo bidding adapter', function () { }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext.rwdd).to.equal(1); }); - it('should properly build a request when imp.rwdd is false', function () { + it('should properly build a request when imp.rwdd is false', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -1842,11 +1847,11 @@ describe('The Criteo bidding adapter', function () { }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext?.rwdd).to.equal(0); }); - it('should properly build a request when FLEDGE is enabled', function () { + it('should properly build a request when FLEDGE is enabled', async function () { const bidderRequest = { paapi: { enabled: true @@ -1874,11 +1879,11 @@ describe('The Criteo bidding adapter', function () { }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext.igs.ae).to.equal(1); }); - it('should properly build a request when FLEDGE is disabled', function () { + it('should properly build a request when FLEDGE is disabled', async function () { const bidderRequest = { paapi: { enabled: false @@ -1906,11 +1911,11 @@ describe('The Criteo bidding adapter', function () { }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].ext.igs?.ae).to.be.undefined; }); - it('should properly transmit the pubid and slot uid if available', function () { + it('should properly transmit the pubid and slot uid if available', async function () { const bidderRequest = { ortb2: { site: { @@ -1958,14 +1963,14 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.site.publisher.id).to.equal('pub-888'); expect(ortbRequest.imp[0].ext.bidder.uid).to.be.undefined; expect(ortbRequest.imp[1].ext.bidder.uid).to.equal(888); }); - it('should properly transmit device.ext.cdep if available', function () { + it('should properly transmit device.ext.cdep if available', async function () { const bidderRequest = { ortb2: { device: { @@ -1976,7 +1981,7 @@ describe('The Criteo bidding adapter', function () { } }; const bidRequests = []; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.device.ext.cdep).to.equal('cookieDeprecationLabel'); }); @@ -2121,23 +2126,23 @@ describe('The Criteo bidding adapter', function () { }; } - it('should return an empty array when parsing an empty bid response', function () { + it('should return an empty array when parsing an empty bid response', async function () { const bidRequests = []; const response = {}; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(0); }); - it('should return an empty array when parsing a well-formed no bid response', function () { + it('should return an empty array when parsing a well-formed no bid response', async function () { const bidRequests = []; - const response = { seatbid: [] }; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const response = {seatbid: []}; + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(0); }); - it('should properly parse a banner bid response', function () { + it('should properly parse a banner bid response', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2151,8 +2156,8 @@ describe('The Criteo bidding adapter', function () { } }]; const response = mockResponse('test-bidId', BANNER); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(BANNER); expect(bids[0].requestId).to.equal('test-bidId'); @@ -2170,7 +2175,7 @@ describe('The Criteo bidding adapter', function () { }); if (FEATURES.VIDEO) { - it('should properly parse a bid response with a video', function () { + it('should properly parse a bid response with a video', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2189,8 +2194,8 @@ describe('The Criteo bidding adapter', function () { }, }]; const response = mockResponse('test-bidId', VIDEO); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(VIDEO); expect(bids[0].requestId).to.equal('test-bidId'); @@ -2206,7 +2211,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.VIDEO) { - it('should properly parse a bid response with an outstream video', function () { + it('should properly parse a bid response with an outstream video', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2225,8 +2230,8 @@ describe('The Criteo bidding adapter', function () { }, }]; const response = mockResponse('test-bidId', VIDEO); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(VIDEO); expect(bids[0].requestId).to.equal('test-bidId'); @@ -2244,7 +2249,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.NATIVE) { - it('should properly parse a native bid response', function () { + it('should properly parse a native bid response', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2254,8 +2259,8 @@ describe('The Criteo bidding adapter', function () { native: true, }]; const response = mockResponse('test-bidId', NATIVE); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(NATIVE); expect(bids[0].requestId).to.equal('test-bidId'); @@ -2275,7 +2280,7 @@ describe('The Criteo bidding adapter', function () { }); } - it('should properly parse a bid response when banner win with twin ad units', function () { + it('should properly parse a bid response when banner win with twin ad units', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2305,8 +2310,8 @@ describe('The Criteo bidding adapter', function () { } }]; const response = mockResponse('test-bidId2', BANNER); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(BANNER); expect(bids[0].requestId).to.equal('test-bidId2'); @@ -2324,7 +2329,7 @@ describe('The Criteo bidding adapter', function () { }); if (FEATURES.VIDEO) { - it('should properly parse a bid response when video win with twin ad units', function () { + it('should properly parse a bid response when video win with twin ad units', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2354,8 +2359,8 @@ describe('The Criteo bidding adapter', function () { } }]; const response = mockResponse('test-bidId', VIDEO); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(VIDEO); expect(bids[0].requestId).to.equal('test-bidId'); @@ -2371,7 +2376,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.NATIVE) { - it('should properly parse a bid response when native win with twin ad units', function () { + it('should properly parse a bid response when native win with twin ad units', async function () { const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', @@ -2394,8 +2399,8 @@ describe('The Criteo bidding adapter', function () { } }]; const response = mockResponse('test-bidId', NATIVE); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(NATIVE); expect(bids[0].requestId).to.equal('test-bidId'); @@ -2416,7 +2421,7 @@ describe('The Criteo bidding adapter', function () { } if (FEATURES.NATIVE) { - it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { + it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', async () => { const bidRequests = [ { bidder: 'criteo', @@ -2516,16 +2521,16 @@ describe('The Criteo bidding adapter', function () { utilsMock.expects('logWarn') .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); - nativeParamsWithSendTargetingKeys.forEach(nativeParams => { - let transformedBidRequests = { ...bidRequests }; + for (const nativeParams of nativeParamsWithSendTargetingKeys) { + let transformedBidRequests = {...bidRequests}; transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; - spec.buildRequests(transformedBidRequests, syncAddFPDToBidderRequest(bidderRequest)); - }); + spec.buildRequests(transformedBidRequests, await addFPDToBidderRequest(bidderRequest)); + } utilsMock.verify(); }); } - it('should properly parse a bid response with FLEDGE auction configs', function () { + it('should properly parse a bid response with FLEDGE auction configs', async function () { let auctionConfig1 = { auctionSignals: {}, decisionLogicUrl: 'https://grid-mercury.criteo.com/fledge/decision', @@ -2655,8 +2660,8 @@ describe('The Criteo bidding adapter', function () { }] }, }; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const interpretedResponse = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const interpretedResponse = spec.interpretResponse({body: response}, request); expect(interpretedResponse).to.have.property('bids'); expect(interpretedResponse).to.have.property('paapi'); expect(interpretedResponse.bids).to.have.lengthOf(0); @@ -2693,7 +2698,7 @@ describe('The Criteo bidding adapter', function () { hasBidResponseBidLevelPafData: false, shouldContainsBidMetaPafData: false }].forEach(testCase => - it('should properly forward or not meta paf data', () => { + it('should properly forward or not meta paf data', async () => { const bidPafContentId = 'abcdef'; const pafTransmission = { version: '12' @@ -2732,8 +2737,8 @@ describe('The Criteo bidding adapter', function () { } : undefined) }; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); @@ -2754,7 +2759,7 @@ describe('The Criteo bidding adapter', function () { }); describe('when pubtag prebid adapter is not available', function () { - it('should not warn if sendId is provided on required fields for native bidRequest', () => { + it('should not warn if sendId is provided on required fields for native bidRequest', async () => { const bidderRequest = {}; const bidRequestsWithSendId = [ { @@ -2803,11 +2808,11 @@ describe('The Criteo bidding adapter', function () { ]; utilsMock.expects('logWarn').withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)').never(); - const request = spec.buildRequests(bidRequestsWithSendId, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequestsWithSendId, await addFPDToBidderRequest(bidderRequest)); utilsMock.verify(); }); - it('should warn only once if sendId is not provided on required fields for native bidRequest', () => { + it('should warn only once if sendId is not provided on required fields for native bidRequest', async () => { const bidderRequest = {}; const bidRequests = [ { @@ -2905,11 +2910,11 @@ describe('The Criteo bidding adapter', function () { utilsMock.expects('logWarn') .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') .exactly(nativeParamsWithoutSendId.length * bidRequests.length); - nativeParamsWithoutSendId.forEach(nativeParams => { - let transformedBidRequests = { ...bidRequests }; + for (const nativeParams of nativeParamsWithoutSendId) { + let transformedBidRequests = {...bidRequests}; transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; - spec.buildRequests(transformedBidRequests, syncAddFPDToBidderRequest(bidderRequest)); - }); + spec.buildRequests(transformedBidRequests, await addFPDToBidderRequest(bidderRequest)); + } utilsMock.verify(); }); }); diff --git a/test/spec/modules/escalaxBidAdapter_spec.js b/test/spec/modules/escalaxBidAdapter_spec.js index bc375ff3dae..6238e6cb208 100644 --- a/test/spec/modules/escalaxBidAdapter_spec.js +++ b/test/spec/modules/escalaxBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from 'modules/escalaxBidAdapter'; import 'modules/priceFloors.js'; import { newBidder } from 'src/adapters/bidderFactory'; import { config } from '../../../src/config.js'; -import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import { addFPDToBidderRequest } from '../../helpers/fpd.js'; import 'src/prebid.js'; import 'modules/currency.js'; @@ -162,23 +162,26 @@ describe('escalaxAdapter', function () { }); describe('with user privacy regulations', function () { - it('should send the Coppa "required" flag set to "1" in the request', function () { + it('should send the Coppa "required" flag set to "1" in the request', async function () { sinon.stub(config, 'getConfig') .withArgs('coppa') .returns(true); - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(serverRequest.data.regs.coppa).to.equal(1); config.getConfig.restore(); }); - it('should send the GDPR Consent data in the request', function () { - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + it('should send the GDPR Consent data in the request', async function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest({ + ...bidderRequest, + gdprConsent + })); expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); }); - it('should send the CCPA data in the request', function () { - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + it('should send the CCPA data in the request', async function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest({...bidderRequest, ...{uspConsent: '1YYY'}})); expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); }); }); @@ -197,14 +200,14 @@ describe('escalaxAdapter', function () { }); describe('build request', function () { - it('should return an empty array when no bid requests', function () { - const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + it('should return an empty array when no bid requests', async function () { + const bidRequest = spec.buildRequests([], await addFPDToBidderRequest(bidderRequest)); expect(bidRequest).to.be.an('array'); expect(bidRequest.length).to.equal(0); }); - it('should return a valid bid request object', function () { - const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid request object', async function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request).to.not.equal('array'); expect(request.data).to.be.an('object'); expect(request.method).to.equal('POST'); @@ -219,24 +222,24 @@ describe('escalaxAdapter', function () { expect(request.data).to.have.property('device'); }); - it('should return a valid bid BANNER request object', function () { - const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid BANNER request object', async function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0].banner).to.exist; expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); }); if (FEATURES.VIDEO) { - it('should return a valid bid VIDEO request object', function () { - const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid VIDEO request object', async function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0].video).to.exist; expect(request.data.imp[0].video.w).to.be.an('number'); expect(request.data.imp[0].video.h).to.be.an('number'); }); } - it('should return a valid bid NATIVE request object', function () { - const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid NATIVE request object', async function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0]).to.be.an('object'); }); }) @@ -285,8 +288,8 @@ describe('escalaxAdapter', function () { }] } }; - it('should interpret server response', function () { - const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + it('should interpret server response', async function () { + const bidRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(serverResponse, bidRequest); expect(bids).to.be.an('array'); const bid = bids[0]; diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index aff1e0535ae..211e08458a8 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -3,7 +3,6 @@ import {config} from 'src/config.js'; import {euidIdSubmodule} from 'modules/euidIdSystem.js'; import 'modules/consentManagementTcf.js'; import 'src/prebid.js'; -import * as utils from 'src/utils.js'; import {apiHelpers, cookieHelpers, runAuction, setGdprApplies} from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; @@ -123,10 +122,11 @@ describe('EUID module', function() { expectToken(bid, initialToken); }) - it('When an expired token is provided in config, it calls the API.', function() { + it('When an expired token is provided in config, it calls the API.', async function () { setGdprApplies(true); const euidToken = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({euidToken})); + await runAuction(); expect(server.requests[0]?.url).to.have.string('https://prod.euid.eu/'); }); diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index ea254eb0c6f..9d8c8b40bfc 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -381,7 +381,7 @@ describe('FTRACK ID System', () => { }); describe('pbjs.getUserIds()', () => { - it('should return the IDs in the correct schema', () => { + it('should return the IDs in the correct schema', async () => { config.setConfig({ userSync: { auctionDelay: 10, @@ -401,6 +401,8 @@ describe('FTRACK ID System', () => { } }); + await getGlobal().getUserIdsAsync(); + expect(getGlobal().getUserIds()).to.deep.equal({ ftrackId: { uid: 'device_test_id', @@ -415,7 +417,7 @@ describe('FTRACK ID System', () => { }); describe('pbjs.getUserIdsAsEids()', () => { - it('should return the correct EIDs schema ', () => { + it('should return the correct EIDs schema ', async () => { // Pass all three IDs config.setConfig({ userSync: { @@ -436,6 +438,8 @@ describe('FTRACK ID System', () => { } }); + await getGlobal().getUserIdsAsync(); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ source: 'flashtalking.com', uids: [{ @@ -451,7 +455,7 @@ describe('FTRACK ID System', () => { }); describe('by ID type:', () => { - it('- DeviceID', () => { + it('- DeviceID', async () => { // Pass DeviceID only config.setConfig({ userSync: { @@ -470,6 +474,8 @@ describe('FTRACK ID System', () => { } }); + await getGlobal().getUserIdsAsync(); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ source: 'flashtalking.com', uids: [{ @@ -482,7 +488,7 @@ describe('FTRACK ID System', () => { }]); }); - it('- HHID', () => { + it('- HHID', async () => { // Pass HHID only config.setConfig({ userSync: { @@ -501,6 +507,8 @@ describe('FTRACK ID System', () => { } }); + await getGlobal().getUserIdsAsync(); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ source: 'flashtalking.com', uids: [{ @@ -513,7 +521,7 @@ describe('FTRACK ID System', () => { }]); }); - it('- SingleDeviceID', () => { + it('- SingleDeviceID', async () => { // Pass SingleDeviceID only config.setConfig({ userSync: { @@ -532,6 +540,8 @@ describe('FTRACK ID System', () => { } }); + await getGlobal().getUserIdsAsync(); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ source: 'flashtalking.com', uids: [{ diff --git a/test/spec/modules/geolocationRtdProvider_spec.js b/test/spec/modules/geolocationRtdProvider_spec.js index 20f3b2f91dd..306b1657c3b 100644 --- a/test/spec/modules/geolocationRtdProvider_spec.js +++ b/test/spec/modules/geolocationRtdProvider_spec.js @@ -2,7 +2,7 @@ import {expect} from 'chai'; import {geolocationSubmodule} from 'modules/geolocationRtdProvider.js'; import * as activityRules from 'src/activities/rules.js'; import 'src/prebid.js'; -import {GreedyPromise} from '../../../src/utils/promise.js'; +import {PbPromise} from '../../../src/utils/promise.js'; import {ACTIVITY_TRANSMIT_PRECISE_GEO} from '../../../src/activities/activities.js'; describe('Geolocation RTD Provider', function () { @@ -43,7 +43,7 @@ describe('Geolocation RTD Provider', function () { }); describe('Geolocation supported', function() { - let clock, rtdConfig, permState, onDone; + let clock, rtdConfig, permState, permGiven, onDone; beforeEach(() => { onDone = sinon.stub(); @@ -54,9 +54,14 @@ describe('Geolocation RTD Provider', function () { // eslint-disable-next-line standard/no-callback-literal cb({coords: {latitude: 1, longitude: 2}, timestamp: 1000}); }); - sandbox.stub(navigator.permissions, 'query').value(() => GreedyPromise.resolve({ - state: permState, - })); + permGiven = new Promise((resolve) => { + sandbox.stub(navigator.permissions, 'query').value(() => { + permGiven = Promise.resolve({ + state: permState, + }) + return permGiven; + }); + }) geolocationSubmodule.init(rtdConfig); }); @@ -75,9 +80,10 @@ describe('Geolocation RTD Provider', function () { rtdConfig.params.requestPermission = requestPermission; }); - it(`should set geolocation`, () => { + it(`should set geolocation`, async () => { const requestBidObject = {ortb2Fragments: {global: {}}}; geolocationSubmodule.getBidRequestData(requestBidObject, onDone, rtdConfig); + await permGiven; clock.tick(300); expect(onDone.called).to.be.true; expect(requestBidObject.ortb2Fragments.global.device.geo).to.eql({ diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index f0b73da919e..48ff5223ff5 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -16,7 +16,7 @@ import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {server} from '../../mocks/xhr.js'; import {expect} from 'chai'; -import {GreedyPromise} from '../../../src/utils/promise.js'; +import {PbPromise} from '../../../src/utils/promise.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; describe('ID5 ID System', function () { @@ -193,7 +193,7 @@ describe('ID5 ID System', function () { } function callSubmoduleGetId(config, consentData, cacheIdObj) { - return new GreedyPromise((resolve) => { + return new PbPromise((resolve) => { id5System.id5IdSubmodule.getId(config, consentData, cacheIdObj).callback((response) => { resolve(response); }); @@ -250,7 +250,7 @@ describe('ID5 ID System', function () { async #waitOnRequest(index) { const server = this.server; - return new GreedyPromise((resolve) => { + return new PbPromise((resolve) => { const waitForCondition = () => { if (server.requests && server.requests.length > index) { resolve(server.requests[index]); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index dba12dee7ae..adbf30bb5f1 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -14,15 +14,15 @@ import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; import * as prebidGlobal from 'src/prebidGlobal.js'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; const AD_SERVER_BASE_URL = 'https://ad.360yield.com'; const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com'; - const PB_ENDPOINT = 'pb'; + const PB_ENDPOINT = 'pb'; [] const AD_SERVER_URL = `${AD_SERVER_BASE_URL}/${PB_ENDPOINT}`; const BASIC_ADS_URL = `${BASIC_ADS_BASE_URL}/${PB_ENDPOINT}`; const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; @@ -220,8 +220,8 @@ describe('Improve Digital Adapter Tests', function () { } }); - it('should make a well-formed request objects', function () { - const request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; + it('should make a well-formed request objects', async function () { + const request = spec.buildRequests([simpleBidRequest], await addFPDToBidderRequest(bidderRequest))[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); @@ -400,53 +400,53 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].bidfloorcur).to.equal('USD'); }); - it('should add GDPR consent string', function () { + it('should add GDPR consent string', async function () { const bidRequest = Object.assign({}, simpleBidRequest); - const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); + const payload = JSON.parse(spec.buildRequests([bidRequest], await addFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); expect(payload.user.ext.consent).to.equal('CONSENT'); expect(payload.user.ext.ConsentedProvidersSettings.consented_providers).to.exist.and.to.deep.equal('1~1.35.41.101'); }); - it('should not add consented providers when empty', function () { + it('should not add consented providers when empty', async function () { const bidderRequestGdprEmptyAddtl = deepClone(bidderRequestGdpr); bidderRequestGdprEmptyAddtl.gdprConsent.addtlConsent = '1~'; const bidRequest = Object.assign({}, simpleBidRequest); - const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdprEmptyAddtl))[0].data); + const payload = JSON.parse(spec.buildRequests([bidRequest], await addFPDToBidderRequest(bidderRequestGdprEmptyAddtl))[0].data); expect(payload.user.ext.consented_providers_settings).to.not.exist; }); - it('should add ConsentedProvidersSettings when extend mode enabled', function () { + it('should add ConsentedProvidersSettings when extend mode enabled', async function () { const bidRequest = deepClone(extendBidRequest); - const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); + const payload = JSON.parse(spec.buildRequests([bidRequest], await addFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); expect(payload.user.ext.consent).to.equal('CONSENT'); expect(payload.user.ext.ConsentedProvidersSettings.consented_providers).to.exist.and.to.equal('1~1.35.41.101'); expect(payload.user.ext.consented_providers_settings).to.not.exist; }); - it('should add CCPA consent string', function () { + it('should add CCPA consent string', async function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + const request = spec.buildRequests([bidRequest], await addFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); const payload = JSON.parse(request[0].data); expect(payload.regs.ext.us_privacy).to.equal('1YYY'); }); - it('should add COPPA flag', function () { + it('should add COPPA flag', async function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('coppa').returns(true); let bidRequest = Object.assign({}, simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); + let payload = JSON.parse(spec.buildRequests([bidRequest], await addFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.coppa).to.equal(1); getConfigStub.withArgs('coppa').returns(false); bidRequest = Object.assign({}, simpleBidRequest); - payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); + payload = JSON.parse(spec.buildRequests([bidRequest], await addFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.coppa).to.equal(0); }); - it('should add referrer', function () { + it('should add referrer', async function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; + const request = spec.buildRequests([bidRequest], await addFPDToBidderRequest(bidderRequestReferrer))[0]; const payload = JSON.parse(request.data); expect(payload.site.page).to.equal('https://blah.com/test.html'); }); @@ -657,15 +657,15 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.app.content).does.exist.and.equal('XYZ'); }); - it('should set correct site params', function () { - let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; + it('should set correct site params', async function () { + let request = spec.buildRequests([simpleBidRequest], await addFPDToBidderRequest(bidderRequestReferrer))[0]; let payload = JSON.parse(request.data); expect(payload.site.content).does.not.exist; expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); const ortb2 = {site: {content: 'ZZZ'}}; - request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest({...bidderRequestReferrer, ortb2}))[0]; + request = spec.buildRequests([simpleBidRequest], await addFPDToBidderRequest({...bidderRequestReferrer, ortb2}))[0]; payload = JSON.parse(request.data); expect(payload.site.content).does.exist.and.equal('ZZZ'); expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); diff --git a/test/spec/modules/inmobiBidAdapter_spec.js b/test/spec/modules/inmobiBidAdapter_spec.js index a7074a2eed8..ce2f3783e09 100644 --- a/test/spec/modules/inmobiBidAdapter_spec.js +++ b/test/spec/modules/inmobiBidAdapter_spec.js @@ -7,7 +7,7 @@ import * as ajax from 'src/ajax.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; import { hook } from '../../../src/hook'; import { config } from '../../../src/config.js'; -import { syncAddFPDToBidderRequest } from '../../helpers/fpd'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/consentManagementGpp.js'; @@ -478,7 +478,7 @@ describe('The inmobi bidding adapter', function () { }, }; - it('request should build with correct plc', function () { + it('request should build with correct plc', async function () { const bidRequests = [ { bidId: 'bidId', @@ -494,12 +494,12 @@ describe('The inmobi bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].ext.bidder.plc).to.deep.equal('123'); }); - it('request should build with correct imp', function () { + it('request should build with correct imp', async function () { const expectedMetric = { url: 'https://inmobi.com' } @@ -527,7 +527,7 @@ describe('The inmobi bidding adapter', function () { } }]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].id).to.deep.equal('bidId'); expect(ortbRequest.imp[0].tagid).to.deep.equal('impId'); @@ -540,7 +540,7 @@ describe('The inmobi bidding adapter', function () { expect(ortbRequest.imp[0].rwdd).to.equal(1); }); - it('request should build with proper site data', function () { + it('request should build with proper site data', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -571,7 +571,7 @@ describe('The inmobi bidding adapter', function () { } } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.site.domain).to.equal('raapchikgames.com'); expect(ortbRequest.site.publisher.domain).to.equal('inmobi'); expect(ortbRequest.site.page).to.equal('https://raapchikgames.com'); @@ -585,7 +585,7 @@ describe('The inmobi bidding adapter', function () { expect(ortbRequest.site.content.url).to.equal('https://raapchikgames.com/games1') }); - it('request should build with proper device data', function () { + it('request should build with proper device data', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -620,7 +620,7 @@ describe('The inmobi bidding adapter', function () { } } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.device.dnt).to.equal(0); expect(ortbRequest.device.lmt).to.equal(1); expect(ortbRequest.device.js).to.equal(1); @@ -637,8 +637,8 @@ describe('The inmobi bidding adapter', function () { expect(ortbRequest.device.geo.lon).to.deep.equal(-75.3009142); }); - it('should properly build a request with source object', function () { - const expectedSchain = { id: 'prebid' }; + it('should properly build a request with source object', async function () { + const expectedSchain = {id: 'prebid'}; const ortb2 = { source: { pchain: 'inmobi', @@ -660,12 +660,12 @@ describe('The inmobi bidding adapter', function () { }, }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.source.ext.schain).to.deep.equal(expectedSchain); expect(ortbRequest.source.pchain).to.equal('inmobi'); }); - it('should properly user object', function () { + it('should properly user object', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -706,7 +706,7 @@ describe('The inmobi bidding adapter', function () { } } } - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(br)); const ortbRequest = request.data; expect(ortbRequest.user.yob).to.deep.equal(2002); expect(ortbRequest.user.keyowrds).to.deep.equal('test test'); @@ -725,7 +725,7 @@ describe('The inmobi bidding adapter', function () { ]); }); - it('should properly build a request regs object', function () { + it('should properly build a request regs object', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -749,14 +749,14 @@ describe('The inmobi bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest({...bidderRequest, ortb2})).data; expect(ortbRequest.regs.coppa).to.equal(1); expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string'); expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); expect(ortbRequest.regs.ext.us_privacy).to.deep.equal('yes us privacy applied'); }); - it('gdpr test', function () { + it('gdpr test', async function () { // using privacy params from global bidder Request const bidRequests = [ { @@ -772,12 +772,12 @@ describe('The inmobi bidding adapter', function () { }, }, ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.ext.gdpr).to.deep.equal(1); expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); }); - it('should properly set tmax if available', function () { + it('should properly set tmax if available', async function () { // using tmax from global bidder Request const bidRequests = [ { @@ -794,12 +794,12 @@ describe('The inmobi bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); }); - it('should properly build a request with bcat field', function () { + it('should properly build a request with bcat field', async function () { const bcat = ['IAB1', 'IAB2']; const bidRequests = [ { @@ -821,11 +821,11 @@ describe('The inmobi bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.bcat).to.deep.equal(bcat); }); - it('should properly build a request with badv field', function () { + it('should properly build a request with badv field', async function () { const badv = ['ford.com']; const bidRequests = [ { @@ -847,11 +847,11 @@ describe('The inmobi bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.badv).to.deep.equal(badv); }); - it('should properly build a request with bapp field', function () { + it('should properly build a request with bapp field', async function () { const bapp = ['raapchik.com']; const bidRequests = [ { @@ -873,11 +873,11 @@ describe('The inmobi bidding adapter', function () { } }; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.bapp).to.deep.equal(bapp); }); - it('banner request test', function () { + it('banner request test', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -902,7 +902,7 @@ describe('The inmobi bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].banner).not.to.be.null; expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); @@ -913,7 +913,7 @@ describe('The inmobi bidding adapter', function () { expect(ortbRequest.imp[0].banner.mimes).to.deep.equal(['image/jpg', 'image/gif']); }); - it('banner request test with sizes > 1', function () { + it('banner request test with sizes > 1', async function () { const bidderRequest = {}; const bidRequests = [ { @@ -930,7 +930,7 @@ describe('The inmobi bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].banner).not.to.be.null; expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); @@ -940,7 +940,7 @@ describe('The inmobi bidding adapter', function () { }); if (FEATURES.VIDEO) { - it('video request test', function () { + it('video request test', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -979,7 +979,7 @@ describe('The inmobi bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].video.skip).to.equal(1); @@ -1006,7 +1006,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.VIDEO) { - it('video request with player size > 1 ', function () { + it('video request with player size > 1 ', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -1045,7 +1045,7 @@ describe('The inmobi bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].video.w).to.be.equal(640); @@ -1054,7 +1054,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.VIDEO) { - it('video request test when skip is 0', function () { + it('video request test when skip is 0', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -1093,7 +1093,7 @@ describe('The inmobi bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].video.skip).to.equal(0); @@ -1101,7 +1101,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.NATIVE) { - it('native request test without assests', function () { + it('native request test without assests', async function () { const bidRequests = [ { mediaTypes: { @@ -1112,14 +1112,14 @@ describe('The inmobi bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].native).to.be.undefined; }); } if (FEATURES.NATIVE) { - it('native request with assets', function () { + it('native request with assets', async function () { const assets = [{ required: 1, id: 1, @@ -1154,7 +1154,7 @@ describe('The inmobi bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.imp[0].native.request).to.not.be.null; @@ -1164,30 +1164,30 @@ describe('The inmobi bidding adapter', function () { }); } - it('should properly build a request when coppa flag is true', function () { + it('should properly build a request when coppa flag is true', async function () { const bidRequests = []; const bidderRequest = {}; - config.setConfig({ coppa: true }); - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + config.setConfig({coppa: true}); + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.coppa).to.equal(1); }); - it('should properly build a request when coppa flag is false', function () { + it('should properly build a request when coppa flag is false', async function () { const bidRequests = []; const bidderRequest = {}; - config.setConfig({ coppa: false }); - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + config.setConfig({coppa: false}); + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs.coppa).to.equal(0); }); - it('should properly build a request when coppa flag is not defined', function () { + it('should properly build a request when coppa flag is not defined', async function () { const bidRequests = []; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.regs?.coppa).to.be.undefined; }); - it('build a banner request with bidFloor', function () { + it('build a banner request with bidFloor', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -1204,12 +1204,12 @@ describe('The inmobi bidding adapter', function () { } } ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1); expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD'); }); - it('build a banner request with getFloor', function () { + it('build a banner request with getFloor', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -1223,17 +1223,17 @@ describe('The inmobi bidding adapter', function () { plc: '123a' }, getFloor: inputParams => { - return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + return {currency: 'USD', floor: 1.23, size: '*', mediaType: '*'}; } } ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].bidfloor).equal(1.23); expect(ortbRequest.imp[0].bidfloorcur).equal('USD'); }); if (FEATURES.VIDEO) { - it('build a video request with bidFloor', function () { + it('build a video request with bidFloor', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -1250,14 +1250,14 @@ describe('The inmobi bidding adapter', function () { } } ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].bidfloor).to.equal(1); expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); }); } if (FEATURES.VIDEO) { - it('build a video request with getFloor', function () { + it('build a video request with getFloor', async function () { const bidRequests = [ { bidder: 'inmobi', @@ -1271,18 +1271,18 @@ describe('The inmobi bidding adapter', function () { plc: '123a' }, getFloor: inputParams => { - return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + return {currency: 'USD', floor: 1.23, size: '*', mediaType: '*'}; } } ]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].bidfloor).to.equal(1.23); expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); }); } if (FEATURES.NATIVE && FEATURES.VIDEO) { - it('build a mutli format request with getFloor', function () { + it('build a mutli format request with getFloor', async function () { const assets = [{ required: 1, id: 1, @@ -1317,9 +1317,7 @@ describe('The inmobi bidding adapter', function () { video: { playerSize: [640, 480], }, - native: { - - } + native: {} }, nativeOrtbRequest: { assets: assets @@ -1328,12 +1326,12 @@ describe('The inmobi bidding adapter', function () { plc: '12456' }, getFloor: inputParams => { - return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + return {currency: 'USD', floor: 1.23, size: '*', mediaType: '*'}; } }, ]; const bidderRequest = {}; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp[0].banner).not.to.be.undefined; expect(ortbRequest.imp[0].video).not.to.be.undefined; expect(ortbRequest.imp[0].native.request).not.to.be.undefined; @@ -1343,7 +1341,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.VIDEO) { - it('build a multi imp request', function () { + it('build a multi imp request', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1390,7 +1388,7 @@ describe('The inmobi bidding adapter', function () { plc: '123', } }]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp).to.have.lengthOf(2); expect(ortbRequest.imp[0].video.skip).to.equal(1); expect(ortbRequest.imp[0].video.minduration).to.equal(5); @@ -1403,7 +1401,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.VIDEO) { - it('build a multi format request', function () { + it('build a multi format request', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1442,7 +1440,7 @@ describe('The inmobi bidding adapter', function () { plc: '123' }, }]; - const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; expect(ortbRequest.imp).to.have.lengthOf(1); expect(ortbRequest.imp[0].video.skip).to.equal(1); expect(ortbRequest.imp[0].video.minduration).to.equal(5); @@ -1644,23 +1642,23 @@ describe('The inmobi bidding adapter', function () { }; }; - it('returns an empty array when bid response is empty', function () { + it('returns an empty array when bid response is empty', async function () { const bidRequests = []; const response = {}; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(0); }); - it('should return an empty array when there is no bid response', function () { + it('should return an empty array when there is no bid response', async function () { const bidRequests = []; - const response = { seatbid: [] }; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const response = {seatbid: []}; + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(0); }); - it('return banner response', function () { + it('return banner response', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1674,8 +1672,8 @@ describe('The inmobi bidding adapter', function () { } }]; const response = mockResponse('bidId', 1); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.length(1); expect(bids[0].currency).to.deep.equal('USD'); expect(bids[0].mediaType).to.deep.equal('banner'); @@ -1690,7 +1688,7 @@ describe('The inmobi bidding adapter', function () { expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); }); - it('bid response when banner wins among two ad units', function () { + it('bid response when banner wins among two ad units', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1720,8 +1718,8 @@ describe('The inmobi bidding adapter', function () { } }]; const response = mockResponse('bidId2', 1); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids[0].currency).to.deep.equal('USD'); expect(bids[0].mediaType).to.deep.equal('banner'); expect(bids[0].requestId).to.deep.equal('bidId2'); @@ -1736,7 +1734,7 @@ describe('The inmobi bidding adapter', function () { }); if (FEATURES.VIDEO) { - it('return instream video response', function () { + it('return instream video response', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1755,8 +1753,8 @@ describe('The inmobi bidding adapter', function () { }, }]; const response = mockResponse('bidId', 2); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(VIDEO); expect(bids[0].currency).to.deep.equal('USD'); @@ -1775,7 +1773,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.VIDEO) { - it('return video outstream response', function () { + it('return video outstream response', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1794,8 +1792,8 @@ describe('The inmobi bidding adapter', function () { }, }]; const response = mockResponse('bidId', 2); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(VIDEO); expect(bids[0].currency).to.deep.equal('USD'); @@ -1814,7 +1812,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.VIDEO) { - it('bid response when video wins among two ad units', function () { + it('bid response when video wins among two ad units', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1844,8 +1842,8 @@ describe('The inmobi bidding adapter', function () { } }]; const response = mockResponse('bidId', 2); - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(VIDEO); expect(bids[0].currency).to.deep.equal('USD'); @@ -1864,7 +1862,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.NATIVE) { - it('should correctly parse a native bid response', function () { + it('should correctly parse a native bid response', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1886,8 +1884,8 @@ describe('The inmobi bidding adapter', function () { }]; const response = mockResponseNative('bidId', 4); const expectedAdmNativeOrtb = JSON.parse(response.seatbid[0].bid[0].adm).native; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(NATIVE); @@ -1908,7 +1906,7 @@ describe('The inmobi bidding adapter', function () { } if (FEATURES.NATIVE) { - it('should correctly parse a native bid response when there are two ad units', function () { + it('should correctly parse a native bid response when there are two ad units', async function () { const bidRequests = [{ adUnitCode: 'impId', bidId: 'bidId', @@ -1942,8 +1940,8 @@ describe('The inmobi bidding adapter', function () { }]; const response = mockResponseNative('bidId', 4); const expectedAdmNativeOrtb = JSON.parse(response.seatbid[0].bid[0].adm).native; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - const bids = spec.interpretResponse({ body: response }, request); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.have.lengthOf(1); expect(bids[0].mediaType).to.equal(NATIVE); // testing diff --git a/test/spec/modules/lmpIdSystem_spec.js b/test/spec/modules/lmpIdSystem_spec.js index 28f8ba0697d..b29195e4be9 100644 --- a/test/spec/modules/lmpIdSystem_spec.js +++ b/test/spec/modules/lmpIdSystem_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { find } from 'src/polyfill.js'; import { config } from 'src/config.js'; -import { init, startAuctionHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import {init, startAuctionHook, setSubmoduleRegistry, resetUserIds} from 'modules/userId/index.js'; import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js'; import { mockGdprConsent } from '../../helpers/consentData.js'; import 'src/prebid.js'; @@ -107,6 +107,10 @@ describe('LMPID System', () => { init(config); }) + after(() => { + resetUserIds(); + }) + it('when a stored LMPID exists it is added to bids', (done) => { startAuctionHook(() => { adUnits.forEach(unit => { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 4f172fb1195..9874733896f 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -17,7 +17,7 @@ import 'modules/paapi.js'; import {deepClone} from 'src/utils.js'; import {version} from 'package.json'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; @@ -901,15 +901,15 @@ describe('OpenxRtbAdapter', function () { config.getConfig.restore(); }); - it('should send a signal to specify that US Privacy applies to this request', function () { - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + it('should send a signal to specify that US Privacy applies to this request', async function () { + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); }); - it('should not send the regs object, when consent string is undefined', function () { + it('should not send the regs object, when consent string is undefined', async function () { delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs?.us_privacy).to.not.exist; }); }); @@ -942,49 +942,49 @@ describe('OpenxRtbAdapter', function () { config.getConfig.restore(); }); - it('should send a signal to specify that GDPR applies to this request', function () { + it('should send a signal to specify that GDPR applies to this request', async function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(1); expect(request[1].data.regs.ext.gdpr).to.equal(1); }); - it('should send the consent string', function () { + it('should send the consent string', async function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); - it('should send the addtlConsent string', function () { + it('should send the addtlConsent string', async function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); - it('should send a signal to specify that GDPR does not apply to this request', function () { + it('should send a signal to specify that GDPR does not apply to this request', async function () { bidderRequest.gdprConsent.gdprApplies = false; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(0); expect(request[1].data.regs.ext.gdpr).to.equal(0); }); it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + - 'but can send consent data, ', function () { + 'but can send consent data, ', async function () { delete bidderRequest.gdprConsent.gdprApplies; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); - it('when consent string is undefined, should not send the consent string, ', function () { + it('when consent string is undefined, should not send the consent string, ', async function () { delete bidderRequest.gdprConsent.consentString; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.imp[0].ext.consent).to.equal(undefined); expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); @@ -992,12 +992,12 @@ describe('OpenxRtbAdapter', function () { }); context('coppa', function() { - it('when there are no coppa param settings, should not send a coppa flag', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + it('when there are no coppa param settings, should not send a coppa flag', async function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.regs?.coppa).to.be.not.ok; }); - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + it('should send a coppa flag there is when there is coppa param settings in the bid requests', async function () { let mockConfig = { coppa: true }; @@ -1006,12 +1006,12 @@ describe('OpenxRtbAdapter', function () { return utils.deepAccess(mockConfig, key); }); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + it('should send a coppa flag there is when there is coppa param settings in the bid params', async function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); request.params = {coppa: true}; expect(request[0].data.regs.coppa).to.equal(1); }); @@ -1031,24 +1031,24 @@ describe('OpenxRtbAdapter', function () { doNotTrackStub.restore(); }); - it('when there is a do not track, should send a dnt', function () { + it('when there is a do not track, should send a dnt', async function () { doNotTrackStub.returns(1); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(1); }); - it('when there is not do not track, don\'t send dnt', function () { + it('when there is not do not track, don\'t send dnt', async function () { doNotTrackStub.returns(0); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); - it('when there is no defined do not track, don\'t send dnt', function () { + it('when there is no defined do not track, don\'t send dnt', async function () { doNotTrackStub.returns(null); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 24ae3d9402d..d47175d53dd 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -23,6 +23,7 @@ import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; import 'modules/schain.js'; import 'modules/paapi.js'; import * as redactor from 'src/activities/redactor.js'; @@ -33,7 +34,7 @@ import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {addPaapiConfig, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepSetValue} from '../../../src/utils.js'; import {ACTIVITY_TRANSMIT_UFPD} from '../../../src/activities/activities.js'; import {MODULE_TYPE_PREBID} from '../../../src/activities/modules.js'; @@ -555,12 +556,15 @@ const RESPONSE_OPENRTB_NATIVE = { ] }; -function addFpdEnrichmentsToS2SRequest(s2sReq, bidderRequests) { +async function addFpdEnrichmentsToS2SRequest(s2sReq, bidderRequests) { return { ...s2sReq, ortb2Fragments: { ...(s2sReq.ortb2Fragments || {}), - global: syncAddFPDToBidderRequest({...(bidderRequests?.[0] || {}), ortb2: s2sReq.ortb2Fragments?.global || {}}).ortb2 + global: (await addFPDToBidderRequest({ + ...(bidderRequests?.[0] || {}), + ortb2: s2sReq.ortb2Fragments?.global || {} + })).ortb2 } } } @@ -977,31 +981,31 @@ describe('S2S Adapter', function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); }); - it('adds gdpr consent information to ortb2 request depending on presence of module', function () { - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; + it('adds gdpr consent information to ortb2 request depending on presence of module', async function () { + let consentConfig = {consentManagement: {cmpApi: 'iab'}, s2sConfig: CONFIG}; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = mockTCF(); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, gdprBidRequest), gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, gdprBidRequest), gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.gdpr).is.equal(1); expect(requestBid.user.ext.consent).is.equal('mockConsent'); config.resetConfig(); - config.setConfig({ s2sConfig: CONFIG }); + config.setConfig({s2sConfig: CONFIG}); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); - expect(requestBid.regs).to.not.exist; - expect(requestBid.user).to.not.exist; + expect(requestBid.regs?.ext?.gdpr).to.not.exist; + expect(requestBid.user?.ext?.consent).to.not.exist; }); - it('adds additional consent information to ortb2 request depending on presence of module', function () { - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; + it('adds additional consent information to ortb2 request depending on presence of module', async function () { + let consentConfig = {consentManagement: {cmpApi: 'iab'}, s2sConfig: CONFIG}; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); @@ -1009,7 +1013,7 @@ describe('S2S Adapter', function () { addtlConsent: 'superduperconsent', }); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, gdprBidRequest), gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, gdprBidRequest), gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.gdpr).is.equal(1); @@ -1017,7 +1021,7 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.ConsentedProvidersSettings.consented_providers).is.equal('superduperconsent'); config.resetConfig(); - config.setConfig({ s2sConfig: CONFIG }); + config.setConfig({s2sConfig: CONFIG}); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); @@ -1032,24 +1036,24 @@ describe('S2S Adapter', function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); }); - it('is added to ortb2 request when in FPD', function () { - config.setConfig({ s2sConfig: CONFIG }); + it('is added to ortb2 request when in FPD', async function () { + config.setConfig({s2sConfig: CONFIG}); let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1NYN'; - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, uspBidRequest), uspBidRequest, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, uspBidRequest), uspBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); config.resetConfig(); - config.setConfig({ s2sConfig: CONFIG }); + config.setConfig({s2sConfig: CONFIG}); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); - expect(requestBid.regs).to.not.exist; + expect(requestBid.regs?.ext?.us_privacy).to.not.exist; }); }); @@ -1058,14 +1062,14 @@ describe('S2S Adapter', function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); }); - it('is added to ortb2 request when in bidRequest', function () { - config.setConfig({ s2sConfig: CONFIG }); + it('is added to ortb2 request when in bidRequest', async function () { + config.setConfig({s2sConfig: CONFIG}); let consentBidRequest = utils.deepClone(BID_REQUESTS); consentBidRequest[0].uspConsent = '1NYN'; consentBidRequest[0].gdprConsent = mockTCF(); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, consentBidRequest), consentBidRequest, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, consentBidRequest), consentBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); @@ -1073,7 +1077,7 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.consent).is.equal('mockConsent'); config.resetConfig(); - config.setConfig({ s2sConfig: CONFIG }); + config.setConfig({s2sConfig: CONFIG}); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); @@ -1105,17 +1109,17 @@ describe('S2S Adapter', function () { }); }); - it('adds device and app objects to request', function () { + it('adds device and app objects to request', async function () { const _config = { s2sConfig: CONFIG, }; config.setConfig(_config); - const s2sreq = addFpdEnrichmentsToS2SRequest({ + const s2sreq = await addFpdEnrichmentsToS2SRequest({ ...REQUEST, ortb2Fragments: { global: { - device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, - app: { bundle: 'com.test.app' }, + device: {ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC'}, + app: {bundle: 'com.test.app'}, } } }, BID_REQUESTS) @@ -1128,11 +1132,11 @@ describe('S2S Adapter', function () { }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', - publisher: { 'id': '1' } + publisher: {'id': '1'} }); }); - it('adds device and app objects to request for OpenRTB', function () { + it('adds device and app objects to request for OpenRTB', async function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' @@ -1142,12 +1146,12 @@ describe('S2S Adapter', function () { s2sConfig: s2sConfig, }; config.setConfig(_config); - const s2sReq = addFpdEnrichmentsToS2SRequest({ + const s2sReq = await addFpdEnrichmentsToS2SRequest({ ...REQUEST, ortb2Fragments: { global: { - device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, - app: { bundle: 'com.test.app' }, + device: {ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC'}, + app: {bundle: 'com.test.app'}, } } }, BID_REQUESTS) @@ -1160,7 +1164,7 @@ describe('S2S Adapter', function () { }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', - publisher: { 'id': '1' } + publisher: {'id': '1'} }); }); @@ -1539,12 +1543,12 @@ describe('S2S Adapter', function () { ] }; - it('adds device.w and device.h even if the config lacks a device object', function () { + it('adds device.w and device.h even if the config lacks a device object', async function () { const _config = { s2sConfig: CONFIG, }; config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { w: window.screen.width, @@ -1630,13 +1634,13 @@ describe('S2S Adapter', function () { }); } - it('adds site if app is not present', function () { + it('adds site if app is not present', async function () { const _config = { s2sConfig: CONFIG, }; config.setConfig(_config); - const s2sReq = addFpdEnrichmentsToS2SRequest({ + const s2sReq = await addFpdEnrichmentsToS2SRequest({ ...REQUEST, ortb2Fragments: { global: { @@ -1674,18 +1678,18 @@ describe('S2S Adapter', function () { }); }); - it('site should not be present when app is present', function () { + it('site should not be present when app is present', async function () { const _config = { s2sConfig: CONFIG, }; config.setConfig(_config); - const s2sReq = addFpdEnrichmentsToS2SRequest({ + const s2sReq = await addFpdEnrichmentsToS2SRequest({ ...REQUEST, ortb2Fragments: { global: { - app: { bundle: 'com.test.app' }, + app: {bundle: 'com.test.app'}, site: { publisher: { id: '1234', @@ -2079,18 +2083,18 @@ describe('S2S Adapter', function () { const s2sBidRequest = utils.deepClone(REQUEST); s2sBidRequest.s2sConfig = s2sConfig; - it('and overrides publisher and page', function () { + it('and overrides publisher and page', async function () { config.setConfig({ s2sConfig: s2sConfig, }); - const s2sReq = addFpdEnrichmentsToS2SRequest({ + const s2sReq = await addFpdEnrichmentsToS2SRequest({ ...s2sBidRequest, ortb2Fragments: { global: { site: { domain: 'nytimes.com', page: 'http://www.nytimes.com', - publisher: { id: '2' } + publisher: {id: '2'} }, device, } @@ -2106,11 +2110,11 @@ describe('S2S Adapter', function () { expect(requestBid.site.publisher.id).to.equal('2'); }); - it('and merges domain and page with the config site value', function () { + it('and merges domain and page with the config site value', async function () { config.setConfig({ s2sConfig: s2sConfig, }); - const s2sReq = addFpdEnrichmentsToS2SRequest({ + const s2sReq = await addFpdEnrichmentsToS2SRequest({ ...s2sBidRequest, ortb2Fragments: { global: { @@ -2647,7 +2651,7 @@ describe('S2S Adapter', function () { }); }); - it('passes first party data in request', () => { + it('passes first party data in request', async () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -2671,7 +2675,7 @@ describe('S2S Adapter', function () { }; const user = { yob: '1984', - geo: { country: 'ca' }, + geo: {country: 'ca'}, ext: { data: { registered: true, @@ -2688,7 +2692,7 @@ describe('S2S Adapter', function () { config: { ortb2: { site: { - content: { userrating: 4 }, + content: {userrating: 4}, ext: { data: { pageType: 'article', @@ -2698,7 +2702,7 @@ describe('S2S Adapter', function () { }, user: { yob: '1984', - geo: { country: 'ca' }, + geo: {country: 'ca'}, ext: { data: { registered: true, @@ -2725,7 +2729,10 @@ describe('S2S Adapter', function () { bidder: Object.fromEntries(allowedBidders.map(bidder => [bidder, {site, user, bcat, badv}])) }; - adapter.callBids(addFpdEnrichmentsToS2SRequest({...s2sBidRequest, ortb2Fragments}, bidRequests), bidRequests, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest({ + ...s2sBidRequest, + ortb2Fragments + }, bidRequests), bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); @@ -2734,9 +2741,9 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.bcat).to.deep.equal(bcat); }); - it('passes first party data in request for unknown when allowUnknownBidderCodes is true', () => { - const cfg = { ...CONFIG, allowUnknownBidderCodes: true }; - config.setConfig({ s2sConfig: cfg }); + it('passes first party data in request for unknown when allowUnknownBidderCodes is true', async () => { + const cfg = {...CONFIG, allowUnknownBidderCodes: true}; + config.setConfig({s2sConfig: cfg}); const clonedReq = {...REQUEST, s2sConfig: cfg} const s2sBidRequest = utils.deepClone(clonedReq); @@ -2762,7 +2769,7 @@ describe('S2S Adapter', function () { }; const user = { yob: '1984', - geo: { country: 'ca' }, + geo: {country: 'ca'}, ext: { data: { registered: true, @@ -2779,7 +2786,7 @@ describe('S2S Adapter', function () { config: { ortb2: { site: { - content: { userrating: 4 }, + content: {userrating: 4}, ext: { data: { pageType: 'article', @@ -2789,7 +2796,7 @@ describe('S2S Adapter', function () { }, user: { yob: '1984', - geo: { country: 'ca' }, + geo: {country: 'ca'}, ext: { data: { registered: true, @@ -2818,7 +2825,10 @@ describe('S2S Adapter', function () { // adapter.callBids({ ...REQUEST, s2sConfig: cfg }, BID_REQUESTS, addBidResponse, done, ajax); - adapter.callBids(addFpdEnrichmentsToS2SRequest({...s2sBidRequest, ortb2Fragments}, bidRequests, cfg), bidRequests, addBidResponse, done, ajax); + adapter.callBids(await addFpdEnrichmentsToS2SRequest({ + ...s2sBidRequest, + ortb2Fragments + }, bidRequests, cfg), bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); // eslint-disable-next-line no-console console.log(parsedRequestBody); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 05b32dae434..f9f4005db41 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -10,6 +10,7 @@ import adapterManager from 'src/adapterManager.js'; import { getWindowLocation } from 'src/utils.js'; import { getGlobal } from 'src/prebidGlobal.js'; import * as events from 'src/events.js' +import 'modules/userId/index.js' const readBlobSafariCompat = (blob) => { return new Promise((resolve, reject) => { diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 5b63e201e42..b6192c1acaf 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -1,11 +1,12 @@ /* eslint dot-notation:0, quote-props:0 */ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepClone} from '../../../src/utils'; -import 'modules/consentManagementTcf.js'; -import 'modules/consentManagementUsp.js'; -import 'modules/schain.js'; +import 'modules/consentManagementTcf'; +import 'modules/consentManagementUsp'; +import 'modules/userId/index'; +import 'modules/schain'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -158,8 +159,8 @@ describe('PulsePoint Adapter Tests', function () { } }; - it('Verify build request', function () { - const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify build request', async function () { + const request = spec.buildRequests(slotConfigs, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -182,8 +183,8 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner.format).to.deep.eq([{'w': 728, 'h': 90}]); }); - it('Verify parse response', function () { - const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify parse response', async function () { + const request = spec.buildRequests(slotConfigs, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; const ortbResponse = { seatbid: [{ @@ -224,8 +225,8 @@ describe('PulsePoint Adapter Tests', function () { }); if (FEATURES.NATIVE) { - it('Verify Native request', function () { - const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify Native request', async function () { + const request = spec.buildRequests(nativeSlotConfig, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -260,8 +261,8 @@ describe('PulsePoint Adapter Tests', function () { expect(nativeRequest.assets[2].data.type).to.equal(1); }); - it('Verify Native response', function () { - const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify Native response', async function () { + const request = spec.buildRequests(nativeSlotConfig, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -361,14 +362,14 @@ describe('PulsePoint Adapter Tests', function () { expect(options[0].url).to.equal('https://bh.contextweb.com/visitormatch/prebid'); }); - it('Verify GDPR', function () { + it('Verify GDPR', async function () { const bidderRequestGdpr = { gdprConsent: { gdprApplies: true, consentString: 'serialized_gdpr_data' } }; - const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestGdpr))); + const request = spec.buildRequests(slotConfigs, await addFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestGdpr))); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -382,12 +383,12 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.regs.ext.gdpr).to.equal(1); }); - it('Verify CCPA', function () { + it('Verify CCPA', async function () { const bidderRequestUSPrivacy = { uspConsent: '1YYY' }; const request = spec.buildRequests(slotConfigs, - syncAddFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestUSPrivacy))); + await addFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestUSPrivacy))); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -398,8 +399,8 @@ describe('PulsePoint Adapter Tests', function () { }); if (FEATURES.VIDEO) { - it('Verify Video request', function () { - const request = spec.buildRequests(videoSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify Video request', async function () { + const request = spec.buildRequests(videoSlotConfig, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -443,8 +444,8 @@ describe('PulsePoint Adapter Tests', function () { }); } - it('Verify extra parameters', function () { - let request = spec.buildRequests(additionalParamsConfig, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify extra parameters', async function () { + let request = spec.buildRequests(additionalParamsConfig, await addFPDToBidderRequest(bidderRequest)); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.imp).to.have.lengthOf(1); @@ -466,8 +467,8 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[0].ext).to.be.undefined; }); - it('Verify schain parameters', function () { - const request = spec.buildRequests(schainParamsSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + it('Verify schain parameters', async function () { + const request = spec.buildRequests(schainParamsSlotConfig, await addFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.source).to.not.equal(null); @@ -485,7 +486,7 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); }); - it('Verify common id parameters', function () { + it('Verify common id parameters', async function () { const bidRequests = deepClone(slotConfigs); const eids = [ { @@ -513,7 +514,7 @@ describe('PulsePoint Adapter Tests', function () { } } } - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(br)); expect(request).to.be.not.null; expect(request.data).to.be.not.null; const ortbRequest = request.data; @@ -524,7 +525,7 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.user.ext.eids).to.deep.equal(eids); }); - it('Verify user level first party data', function () { + it('Verify user level first party data', async function () { const bidderRequest = { refererInfo: { page: 'https://publisher.com/home', @@ -547,7 +548,7 @@ describe('PulsePoint Adapter Tests', function () { } } }; - let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + let request = spec.buildRequests(slotConfigs, await addFPDToBidderRequest(bidderRequest)); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.user).to.not.equal(null); @@ -564,7 +565,7 @@ describe('PulsePoint Adapter Tests', function () { }); }); - it('Verify site level first party data', function () { + it('Verify site level first party data', async function () { const bidderRequest = { ortb2: { site: { @@ -585,7 +586,7 @@ describe('PulsePoint Adapter Tests', function () { } } }; - let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + let request = spec.buildRequests(slotConfigs, await addFPDToBidderRequest(bidderRequest)); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.site).to.not.equal(null); @@ -659,7 +660,7 @@ describe('PulsePoint Adapter Tests', function () { expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) }); - it('Verify deals', function () { + it('Verify deals', async function () { const bidRequests = deepClone(slotConfigs); const deals = [{ id: 'DEAL_ONE', @@ -669,7 +670,7 @@ describe('PulsePoint Adapter Tests', function () { bidfloor: 2.2 }]; bidRequests[0].params.deals = deals; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index b175f645adf..8b36256f6a6 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -12,7 +12,6 @@ import { import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {find} from 'src/polyfill.js'; -import {createEidsArray} from 'modules/userId/eids.js'; import 'modules/schain.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; @@ -20,7 +19,7 @@ import 'modules/userId/index.js'; import 'modules/priceFloors.js'; import 'modules/multibid/index.js'; import adapterManager from 'src/adapterManager.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; import { deepClone } from '../../../src/utils.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid @@ -2100,14 +2099,14 @@ describe('the rubicon adapter', function () { if (FEATURES.VIDEO) { describe('for video requests', function () { - it('should make a well-formed video request', function () { + it('should make a well-formed video request', async function () { const bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + let [request] = spec.buildRequests(bidderRequest.bids, await addFPDToBidderRequest(bidderRequest)); let post = request.data; expect(post).to.have.property('imp'); @@ -2591,7 +2590,7 @@ describe('the rubicon adapter', function () { expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); - it('should include coppa flag in video bid request', () => { + it('should include coppa flag in video bid request', async () => { const bidderRequest = createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => @@ -2604,7 +2603,7 @@ describe('the rubicon adapter', function () { }; return config[key]; }); - const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + const [request] = spec.buildRequests(bidderRequest.bids, await addFPDToBidderRequest(bidderRequest)); expect(request.data.regs.coppa).to.equal(1); }); @@ -2734,7 +2733,7 @@ describe('the rubicon adapter', function () { expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); }); - it('should pass the user.id provided in the config', function () { + it('should pass the user.id provided in the config', async function () { config.setConfig({user: {id: '123'}}); const bidderRequest = createVideoBidderRequest(); @@ -2742,7 +2741,7 @@ describe('the rubicon adapter', function () { bidderRequest.auctionStart + 100 ); - let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + let [request] = spec.buildRequests(bidderRequest.bids, await addFPDToBidderRequest(bidderRequest)); let post = request.data; expect(post).to.have.property('imp') diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 7fc3068513f..c0fd351150b 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -117,6 +117,7 @@ describe('SharedId System', function () { }] } }); + await getGlobal().getUserIdsAsync(); const eids = getGlobal().getUserIdsAsEids(); sinon.assert.match(eids[0], { source: 'pubcid.org', diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index b03f6eb9a8a..07211ca37cc 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai' import { spec } from 'modules/showheroes-bsBidAdapter.js' -import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import { addFPDToBidderRequest } from '../../helpers/fpd.js'; import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; @@ -93,19 +93,19 @@ describe('shBidAdapter', () => { expect(spec.isBidRequestValid(bid)).to.eql(true); }); - it('passes gdpr, usp, schain, floor in ortb request', () => { + it('passes gdpr, usp, schain, floor in ortb request', async () => { const bidRequest = Object.assign({}, bidRequestVideoV2) const fullRequest = { bids: [bidRequestVideoV2], ...bidderRequest, ...gdpr, ...schain, - ...{ uspConsent: uspConsent }, + ...{uspConsent: uspConsent}, }; bidRequest.schain = schain.schain.config; - const getFloorResponse = { currency: 'EUR', floor: 3 }; + const getFloorResponse = {currency: 'EUR', floor: 3}; bidRequest.getFloor = () => getFloorResponse; - const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(fullRequest)); + const request = spec.buildRequests([bidRequest], await addFPDToBidderRequest(fullRequest)); const payload = request.data; expect(payload.regs.ext.gdpr).to.eql(Number(gdpr.gdprConsent.gdprApplies)); expect(payload.regs.ext.us_privacy).to.eql(uspConsent); diff --git a/test/spec/modules/silvermobBidAdapter_spec.js b/test/spec/modules/silvermobBidAdapter_spec.js index 3ff3dfbfe2d..b9bf32462d8 100644 --- a/test/spec/modules/silvermobBidAdapter_spec.js +++ b/test/spec/modules/silvermobBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec} from '../../../modules/silvermobBidAdapter.js'; import 'modules/priceFloors.js'; import { newBidder } from 'src/adapters/bidderFactory'; import { config } from '../../../src/config.js'; -import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import { addFPDToBidderRequest } from '../../helpers/fpd.js'; // load modules that register ORTB processors import 'src/prebid.js'; @@ -163,23 +163,26 @@ describe('silvermobAdapter', function () { }); describe('with user privacy regulations', function () { - it('should send the Coppa "required" flag set to "1" in the request', function () { + it('should send the Coppa "required" flag set to "1" in the request', async function () { sinon.stub(config, 'getConfig') .withArgs('coppa') .returns(true); - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(serverRequest.data.regs.coppa).to.equal(1); config.getConfig.restore(); }); - it('should send the GDPR Consent data in the request', function () { - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + it('should send the GDPR Consent data in the request', async function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest({ + ...bidderRequest, + gdprConsent + })); expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); }); - it('should send the CCPA data in the request', function () { - const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + it('should send the CCPA data in the request', async function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest({...bidderRequest, ...{uspConsent: '1YYY'}})); expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); }); }); @@ -197,14 +200,14 @@ describe('silvermobAdapter', function () { }); describe('build request', function () { - it('should return an empty array when no bid requests', function () { - const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + it('should return an empty array when no bid requests', async function () { + const bidRequest = spec.buildRequests([], await addFPDToBidderRequest(bidderRequest)); expect(bidRequest).to.be.an('array'); expect(bidRequest.length).to.equal(0); }); - it('should return a valid bid request object', function () { - const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid request object', async function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request).to.not.equal('array'); expect(request.data).to.be.an('object'); expect(request.method).to.equal('POST'); @@ -217,24 +220,24 @@ describe('silvermobAdapter', function () { expect(request.data).to.have.property('device'); }); - it('should return a valid bid BANNER request object', function () { - const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid BANNER request object', async function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0].banner).to.exist; expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); }); if (FEATURES.VIDEO) { - it('should return a valid bid VIDEO request object', function () { - const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid VIDEO request object', async function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0].video).to.exist; expect(request.data.imp[0].video.w).to.be.an('number'); expect(request.data.imp[0].video.h).to.be.an('number'); }); } - it('should return a valid bid NATIVE request object', function () { - const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + it('should return a valid bid NATIVE request object', async function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], await addFPDToBidderRequest(bidderRequest)); expect(request.data.imp[0]).to.be.an('object'); }); }) @@ -283,8 +286,8 @@ describe('silvermobAdapter', function () { }] } }; - it('should interpret server response', function () { - const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + it('should interpret server response', async function () { + const bidRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(serverResponse, bidRequest); expect(bids).to.be.an('array'); const bid = bids[0]; diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index 48295e6fdff..89136a00ff4 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -1,12 +1,11 @@ /* eslint-disable no-tabs */ -import { spec, storage, VIDEO_RENDERER_URL, ADAPTER_VERSION } from 'modules/tpmnBidAdapter.js'; -import { generateUUID } from '../../../src/utils.js'; -import { expect } from 'chai'; +import {spec, storage, VIDEO_RENDERER_URL} from 'modules/tpmnBidAdapter.js'; +import {generateUUID} from '../../../src/utils.js'; +import {expect} from 'chai'; import * as utils from 'src/utils'; import * as sinon from 'sinon'; import 'modules/consentManagementTcf.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; -import {mockGdprConsent} from '../../helpers/consentData.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; const BIDDER_CODE = 'tpmn'; const BANNER_BID = { @@ -172,10 +171,10 @@ describe('tpmnAdapterTests', function () { }); describe('buildRequests()', function () { - it('should have gdpr data if applicable', function () { + it('should have gdpr data if applicable', async function () { const bid = utils.deepClone(BANNER_BID); - const req = syncAddFPDToBidderRequest(Object.assign({}, BIDDER_REQUEST, { + const req = await addFPDToBidderRequest(Object.assign({}, BIDDER_REQUEST, { gdprConsent: { consentString: 'consentString', gdprApplies: true, diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 192497f9c8d..fec467309ab 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -15,7 +15,7 @@ import 'modules/schain.js'; import 'modules/paapi.js'; import {deepClone} from 'src/utils.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {addFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -727,15 +727,15 @@ describe('TrafficgateOpenxRtbAdapter', function () { config.getConfig.restore(); }); - it('should send a signal to specify that US Privacy applies to this request', function () { - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + it('should send a signal to specify that US Privacy applies to this request', async function () { + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); }); - it('should not send the regs object, when consent string is undefined', function () { + it('should not send the regs object, when consent string is undefined', async function () { delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs?.us_privacy).to.not.exist; }); }); @@ -768,49 +768,49 @@ describe('TrafficgateOpenxRtbAdapter', function () { config.getConfig.restore(); }); - it('should send a signal to specify that GDPR applies to this request', function () { + it('should send a signal to specify that GDPR applies to this request', async function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(1); expect(request[1].data.regs.ext.gdpr).to.equal(1); }); - it('should send the consent string', function () { + it('should send the consent string', async function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); - it('should send the addtlConsent string', function () { + it('should send the addtlConsent string', async function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); - it('should send a signal to specify that GDPR does not apply to this request', function () { + it('should send a signal to specify that GDPR does not apply to this request', async function () { bidderRequest.gdprConsent.gdprApplies = false; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(0); expect(request[1].data.regs.ext.gdpr).to.equal(0); }); it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + - 'but can send consent data, ', function () { + 'but can send consent data, ', async function () { delete bidderRequest.gdprConsent.gdprApplies; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); - it('when consent string is undefined, should not send the consent string, ', function () { + it('when consent string is undefined, should not send the consent string, ', async function () { delete bidderRequest.gdprConsent.consentString; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.imp[0].ext.consent).to.equal(undefined); expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); @@ -818,12 +818,12 @@ describe('TrafficgateOpenxRtbAdapter', function () { }); context('coppa', function() { - it('when there are no coppa param settings, should not send a coppa flag', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + it('when there are no coppa param settings, should not send a coppa flag', async function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.regs?.coppa).to.be.not.ok; }); - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + it('should send a coppa flag there is when there is coppa param settings in the bid requests', async function () { let mockConfig = { coppa: true }; @@ -832,12 +832,12 @@ describe('TrafficgateOpenxRtbAdapter', function () { return utils.deepAccess(mockConfig, key); }); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + it('should send a coppa flag there is when there is coppa param settings in the bid params', async function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); request.params = {coppa: true}; expect(request[0].data.regs.coppa).to.equal(1); }); @@ -857,24 +857,24 @@ describe('TrafficgateOpenxRtbAdapter', function () { doNotTrackStub.restore(); }); - it('when there is a do not track, should send a dnt', function () { + it('when there is a do not track, should send a dnt', async function () { doNotTrackStub.returns(1); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(1); }); - it('when there is not do not track, don\'t send dnt', function () { + it('when there is not do not track, don\'t send dnt', async function () { doNotTrackStub.returns(0); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); - it('when there is no defined do not track, don\'t send dnt', function () { + it('when there is no defined do not track, don\'t send dnt', async function () { doNotTrackStub.returns(null); - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); }); diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 7fe27dc4eb5..3453921da58 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -169,15 +169,17 @@ describe(`UID2 module`, function () { }); describe('Configuration', function() { - it('When no baseUrl is provided in config, the module calls the production endpoint', function() { + it('When no baseUrl is provided in config, the module calls the production endpoint', async function () { const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({uid2Token})); + await runAuction(); expect(server.requests[0]?.url).to.have.string('https://prod.uidapi.com/v2/token/refresh'); }); - it('When a baseUrl is provided in config, the module calls the provided endpoint', function() { + it('When a baseUrl is provided in config, the module calls the provided endpoint', async function () { const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({uid2Token, uid2ApiBase: 'https://operator-integ.uidapi.com'})); + await runAuction(); expect(server.requests[0]?.url).to.have.string('https://operator-integ.uidapi.com/v2/token/refresh'); }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index be192642288..e861e95f88c 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -18,7 +18,7 @@ import {UID1_EIDS} from 'libraries/uid1Eids/uid1Eids.js'; import {createEidsArray, EID_CONFIG} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import {getPrebidInternal} from 'src/utils.js'; +import {deepAccess, getPrebidInternal} from 'src/utils.js'; import * as events from 'src/events.js'; import {EVENTS} from 'src/constants.js'; import {getGlobal} from 'src/prebidGlobal.js'; @@ -139,7 +139,7 @@ describe('User ID', function () { function runBidsHook(...args) { startDelay = delay(); - const result = startAuctionHook(...args, {delay: startDelay}); + const result = startAuctionHook(...args, {mkDelay: startDelay}); return new Promise((resolve) => setTimeout(() => resolve(result))); } @@ -152,7 +152,7 @@ describe('User ID', function () { function initModule(config) { callbackDelay = delay(); - return init(config, {delay: callbackDelay}); + return init(config, {mkDelay: callbackDelay}); } before(function () { @@ -2570,15 +2570,19 @@ describe('User ID', function () { }); }); - it('should add userIdAsEids and merge ortb2.user.ext.eids even if no User ID submodules', () => { + it('should add userIdAsEids and merge ortb2.user.ext.eids even if no User ID submodules', async () => { init(config); - config.setConfig({ - ortb2: {user: {ext: {eids: [eid]}}} - }) expect(startAuction.getHooks({hook: startAuctionHook}).length).equal(0); expect(startAuction.getHooks({hook: addUserIdsHook}).length).equal(1); - $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledWith(startAuctionStub, sinon.match.hasNested('adUnits[0].bids[0].userIdAsEids[0]', eid)); + addUserIdsHook(sinon.stub(), { + adUnits, + ortb2Fragments: { + global: { + user: {ext: {eids: [eid]}} + } + } + }); + expect(adUnits[0].bids[0].userIdAsEids[0]).to.eql(eid); }); }); }); diff --git a/test/spec/unit/core/events_spec.js b/test/spec/unit/core/events_spec.js index e1451f657b5..445a57b2d23 100644 --- a/test/spec/unit/core/events_spec.js +++ b/test/spec/unit/core/events_spec.js @@ -10,22 +10,23 @@ describe('events', () => { }); afterEach(() => { clock.restore(); + config.resetConfig(); }); - it('should clear event log using eventHistoryTTL config', () => { + it('should clear event log using eventHistoryTTL config', async () => { emit('testEvent', {}); expect(getEvents().length).to.eql(1); config.setConfig({eventHistoryTTL: 1}); - clock.tick(500); + await clock.tick(500); expect(getEvents().length).to.eql(1); - clock.tick(6000); + await clock.tick(6000); expect(getEvents().length).to.eql(0); }); - it('should take history TTL in seconds', () => { + it('should take history TTL in seconds', async () => { emit('testEvent', {}); config.setConfig({eventHistoryTTL: 1000}); - clock.tick(10000); + await clock.tick(10000); expect(getEvents().length).to.eql(1); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 378f15e35bf..14b71717e06 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -17,7 +17,7 @@ import {resetAuctionState} from 'src/auction.js'; import {registerBidder} from 'src/adapters/bidderFactory.js'; import {find} from 'src/polyfill.js'; import * as pbjsModule from 'src/prebid.js'; -import $$PREBID_GLOBAL$$ from 'src/prebid.js'; +import $$PREBID_GLOBAL$$, {startAuction} from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {reset as resetDebugging} from '../../../src/debugging.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; @@ -646,30 +646,34 @@ describe('Unit: Prebid Module', function () { indexStub.restore(); }); - it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', function () { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', async function () { RESPONSE.tags[0].ads[0].cpm = 2.1234; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('2.12'); }); - it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 5 to 8', function () { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 5 to 8', async function () { RESPONSE.tags[0].ads[0].cpm = 6.78; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('6.75'); }); - it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 8 to 20', function () { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 8 to 20', async function () { RESPONSE.tags[0].ads[0].cpm = 19.5234; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('19.50'); }); - it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 20 to 25', function () { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 20 to 25', async function () { RESPONSE.tags[0].ads[0].cpm = 21.5234; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('21.00'); }); @@ -906,7 +910,7 @@ describe('Unit: Prebid Module', function () { indexStub.restore(); }); - it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', function () { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', async function () { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -916,11 +920,12 @@ describe('Unit: Prebid Module', function () { response.tags[0].ads[0].cpm = 3.4288; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); }); - it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 21 - 100', function () { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 21 - 100', async function () { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -930,11 +935,12 @@ describe('Unit: Prebid Module', function () { response.tags[0].ads[0].cpm = 43.4288; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('43.00'); }); - it('should only apply price granularity if bid media type matches', function () { + it('should only apply price granularity if bid media type matches', async function () { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -944,6 +950,7 @@ describe('Unit: Prebid Module', function () { response.tags[0].ads[0].cpm = 3.4288; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); @@ -959,6 +966,7 @@ describe('Unit: Prebid Module', function () { response.tags[0].ads[0].cpm = 3.4288; auction.callBids(cbTimeout); + await auction.end; let bidTargeting = targeting.getAllTargeting(); expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); } @@ -1570,7 +1578,7 @@ describe('Unit: Prebid Module', function () { 'start': 1000 }]; - let spec, indexStub, auction, completeAuction; + let spec, indexStub, auction, completeAuction, auctionStarted; beforeEach(function () { logMessageSpy = sinon.spy(utils, 'logMessage'); @@ -1595,12 +1603,16 @@ describe('Unit: Prebid Module', function () { completeAuction = (bidsReceived) => { bidsReceived.forEach((bid) => addBidResponse(bid.adUnitCode, Object.assign(createBid(), bid))); bidRequests.forEach((req) => adapterDone.call(req)); + return auction.end; } }) const origNewAuction = auctionModule.newAuction; - sinon.stub(auctionModule, 'newAuction').callsFake(function (opts) { - auction = origNewAuction(opts); - return auction; + auctionStarted = new Promise((resolve) => { + sinon.stub(auctionModule, 'newAuction').callsFake(function (opts) { + auction = origNewAuction(opts); + resolve(auction); + return auction; + }) }) spec = { code: BIDDER_CODE, @@ -1628,19 +1640,24 @@ describe('Unit: Prebid Module', function () { utils.logMessage.restore(); }); - it('should execute callback after timeout', function () { + async function runAuction(request = {}) { + $$PREBID_GLOBAL$$.requestBids(request); + await auctionStarted; + } + + it('should execute callback after timeout', async function () { let requestObj = { bidsBackHandler: sinon.stub(), timeout: 2000, adUnits: adUnits }; + await runAuction(requestObj); - $$PREBID_GLOBAL$$.requestBids(requestObj); let re = new RegExp('^Auction [a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12} timedOut$'); - clock.tick(requestObj.timeout - 1); + await clock.tick(requestObj.timeout - 1); assert.ok(logMessageSpy.neverCalledWith(sinon.match(re)), 'executeCallback not called'); - clock.tick(1); + await clock.tick(1); assert.ok(logMessageSpy.calledWith(sinon.match(re)), 'executeCallback called'); expect(requestObj.bidsBackHandler.getCall(0).args[1]).to.equal(true, @@ -1649,7 +1666,7 @@ describe('Unit: Prebid Module', function () { sinon.assert.called(spec.onTimeout); }); - it('should execute `onSetTargeting` after setTargetingForGPTAsync', function () { + it('should execute `onSetTargeting` after setTargetingForGPTAsync', async function () { const bidId = 1; const auctionId = 1; let adResponse = Object.assign({ @@ -1675,8 +1692,8 @@ describe('Unit: Prebid Module', function () { adUnits: adUnits }; - $$PREBID_GLOBAL$$.requestBids(requestObj); - completeAuction([adResponse]); + await runAuction(requestObj); + await completeAuction([adResponse]); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); sinon.assert.called(spec.onSetTargeting); @@ -1756,8 +1773,8 @@ describe('Unit: Prebid Module', function () { }) }) - it('should transfer ttlBuffer to adUnit.ttlBuffer', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should transfer ttlBuffer to adUnit.ttlBuffer', async () => { + await runAuction({ ttlBuffer: 123, adUnits: [adUnits[0], {...adUnits[0], ttlBuffer: 0}] }); @@ -1776,20 +1793,21 @@ describe('Unit: Prebid Module', function () { sandbox.restore(); }); describe('bidRequests is empty', function () { - it('should log warning message and execute callback if bidRequests is empty', function () { - let bidsBackHandler = function bidsBackHandlerCallback() {}; + it('should log warning message and execute callback if bidRequests is empty', async function () { + let bidsBackHandler = function bidsBackHandlerCallback() { + }; let spyExecuteCallback = sinon.spy(bidsBackHandler); let logWarnSpy = sandbox.spy(utils, 'logWarn'); - $$PREBID_GLOBAL$$.requestBids({ + await $$PREBID_GLOBAL$$.requestBids({ adUnits: [ { code: 'test1', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [], }, { code: 'test2', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [], } ], @@ -1802,11 +1820,13 @@ describe('Unit: Prebid Module', function () { }); describe('starts auction', () => { - let startAuctionStub; + let startAuctionStub, auctionStarted, __started; function saHook(fn, ...args) { + __started(); return startAuctionStub(...args); } beforeEach(() => { + auctionStarted = new Promise(resolve => { __started = resolve }); startAuctionStub = sinon.stub(); pbjsModule.startAuction.before(saHook); configObj.resetConfig(); @@ -1818,6 +1838,11 @@ describe('Unit: Prebid Module', function () { configObj.resetConfig(); }); + async function runAuction(request = {}) { + $$PREBID_GLOBAL$$.requestBids(request); + await auctionStarted; + } + describe('with FPD', () => { let globalFPD, auctionFPD, mergedFPD; beforeEach(() => { @@ -1846,9 +1871,9 @@ describe('Unit: Prebid Module', function () { }; }); - it('merged from setConfig and requestBids', () => { + it('merged from setConfig and requestBids', async () => { configObj.setConfig({ortb2: globalFPD}); - $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + await runAuction({ortb2: auctionFPD}); sinon.assert.calledWith(startAuctionStub, sinon.match({ ortb2Fragments: {global: mergedFPD} })); @@ -1877,17 +1902,18 @@ describe('Unit: Prebid Module', function () { expect(configObj.getBidderConfig().mockBidder.ortb2.value).to.eql('old'); }) - it('enriched through enrichFPD', () => { + it('enriched through enrichFPD', async () => { function enrich(next, fpd) { next.bail(fpd.then(ortb2 => { ortb2.enrich = true; return ortb2; })) } + enrichFPD.before(enrich); try { configObj.setConfig({ortb2: globalFPD}); - $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + await runAuction({ortb2: auctionFPD}); sinon.assert.calledWith(startAuctionStub, sinon.match({ ortb2Fragments: {global: {...mergedFPD, enrich: true}} })); @@ -1897,8 +1923,8 @@ describe('Unit: Prebid Module', function () { }) }); - it('filtering adUnits by adUnitCodes', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('filtering adUnits by adUnitCodes', async () => { + await runAuction({ adUnits: [{code: 'one'}, {code: 'two'}], adUnitCodes: 'two' }); @@ -1908,8 +1934,8 @@ describe('Unit: Prebid Module', function () { })); }); - it('does not repeat ad unit codes on twin ad units', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('does not repeat ad unit codes on twin ad units', async () => { + await runAuction({ adUnits: [{code: 'au1'}, {code: 'au2'}, {code: 'au1'}, {code: 'au2'}], }); sinon.assert.calledWith(startAuctionStub, sinon.match({ @@ -1917,14 +1943,14 @@ describe('Unit: Prebid Module', function () { })); }); - it('filters out repeated ad unit codes from input', () => { - $$PREBID_GLOBAL$$.requestBids({adUnitCodes: ['au1', 'au1', 'au2']}); + it('filters out repeated ad unit codes from input', async () => { + await runAuction({adUnitCodes: ['au1', 'au1', 'au2']}); sinon.assert.calledWith(startAuctionStub, sinon.match({ adUnitCodes: ['au1', 'au2'] })); }); - it('passing bidder-specific FPD as ortb2Fragments.bidder', () => { + it('passing bidder-specific FPD as ortb2Fragments.bidder', async () => { configObj.setBidderConfig({ bidders: ['bidderA', 'bidderC'], config: { @@ -1941,7 +1967,7 @@ describe('Unit: Prebid Module', function () { } } }); - $$PREBID_GLOBAL$$.requestBids({}); + await runAuction({}) sinon.assert.calledWith(startAuctionStub, sinon.match({ ortb2Fragments: { bidder: { @@ -1962,20 +1988,26 @@ describe('Unit: Prebid Module', function () { }); describe('startAuction', () => { - let sandbox, newAuctionStub; + let sandbox, newAuctionStub, auctionStarted; + beforeEach(() => { sandbox = sinon.createSandbox(); - newAuctionStub = sandbox.stub(auctionManager, 'createAuction').callsFake(() => ({ - getAuctionId: () => 'mockAuctionId', - callBids: sinon.stub() - })); + auctionStarted = new Promise(resolve => { + newAuctionStub = sandbox.stub(auctionManager, 'createAuction').callsFake(() => { + resolve(); + return { + getAuctionId: () => 'mockAuctionId', + callBids: sinon.stub() + } + }); + }); }); afterEach(() => { sandbox.restore(); }); - it('passes ortb2 fragments to createAuction', () => { + it('passes ortb2 fragments to createAuction', async () => { const ortb2Fragments = {global: {}, bidder: {}}; pbjsModule.startAuction({ adUnits: [{ @@ -1986,6 +2018,7 @@ describe('Unit: Prebid Module', function () { adUnitCodes: ['au'], ortb2Fragments }); + await auctionStarted; sinon.assert.calledWith(newAuctionStub, sinon.match({ ortb2Fragments: sinon.match.same(ortb2Fragments) })); @@ -2009,15 +2042,17 @@ describe('Unit: Prebid Module', function () { registerBidder(spec); describe('part 1', function () { - let auctionArgs; + let auctionArgs, auctionStarted; beforeEach(function () { - auctionArgs = null; adUnitsBackup = auction.getAdUnits - auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() { - auctionArgs = arguments[0]; - return auction; - }); + auctionStarted = new Promise(resolve => { + auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() { + auctionArgs = arguments[0]; + resolve(); + return auction; + }); + }) logMessageSpy = sinon.spy(utils, 'logMessage'); logInfoSpy = sinon.spy(utils, 'logInfo'); logErrorSpy = sinon.spy(utils, 'logError'); @@ -2032,27 +2067,31 @@ describe('Unit: Prebid Module', function () { resetAuction(); }); - it('should log message when adUnits not configured', function () { + function runAuction(request = {}) { + $$PREBID_GLOBAL$$.requestBids(request); + return auctionStarted; + } + + it('should log message when adUnits not configured', async function () { $$PREBID_GLOBAL$$.adUnits = []; try { - $$PREBID_GLOBAL$$.requestBids({}); + await $$PREBID_GLOBAL$$.requestBids({}); } catch (e) { - console.log(e); // eslint-disable-line } assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); }); - it('should always attach new transactionIds to adUnits passed to requestBids', function () { - $$PREBID_GLOBAL$$.requestBids({ + it('should always attach new transactionIds to adUnits passed to requestBids', async function () { + await runAuction({ adUnits: [ { code: 'test1', transactionId: 'd0676a3c-ff32-45a5-af65-8175a8e7ddca', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] }, { code: 'test2', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] } ] @@ -2065,16 +2104,16 @@ describe('Unit: Prebid Module', function () { .and.to.match(/[a-f0-9\-]{36}/i); }); - it('should use the same transactionID for ad units with the same code', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should use the same transactionID for ad units with the same code', async () => { + await runAuction({ adUnits: [ { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] }, { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] } ] @@ -2084,16 +2123,16 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[1].transactionId).to.eql(tid); }); - it('should re-use pub-provided transaction ID for ad units with the same code', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should re-use pub-provided transaction ID for ad units with the same code', async () => { + await runAuction({ adUnits: [ { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [], }, { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [], ortb2Imp: { ext: { @@ -2106,12 +2145,12 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits.map(au => au.transactionId)).to.eql(['pub-tid', 'pub-tid']); }); - it('should use pub-provided TIDs when they conflict for ad units with the same code', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should use pub-provided TIDs when they conflict for ad units with the same code', async () => { + await runAuction({ adUnits: [ { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [], ortb2Imp: { ext: { @@ -2120,7 +2159,7 @@ describe('Unit: Prebid Module', function () { } }, { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [], ortb2Imp: { ext: { @@ -2133,21 +2172,21 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits.map(au => au.transactionId)).to.eql(['t1', 't2']); }); - it('should generate unique adUnitId', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should generate unique adUnitId', async () => { + await runAuction({ adUnits: [ { code: 'single', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] }, { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] }, { code: 'twin', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] } ] @@ -2178,8 +2217,8 @@ describe('Unit: Prebid Module', function () { ] }; }); - it('should be set to ortb2Imp.ext.tid, if specified', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should be set to ortb2Imp.ext.tid, if specified', async () => { + await runAuction({ adUnits: [ {...adUnit, ortb2Imp: {ext: {tid: 'custom-tid'}}} ] @@ -2193,8 +2232,8 @@ describe('Unit: Prebid Module', function () { } }) }); - it('should be copied to ortb2Imp.ext.tid, if not specified', () => { - $$PREBID_GLOBAL$$.requestBids({ + it('should be copied to ortb2Imp.ext.tid, if not specified', async () => { + await runAuction({ adUnits: [ adUnit ] @@ -2205,16 +2244,16 @@ describe('Unit: Prebid Module', function () { }); }); - it('should always set ortb2.ext.tid same as transactionId in adUnits', function () { - $$PREBID_GLOBAL$$.requestBids({ + it('should always set ortb2.ext.tid same as transactionId in adUnits', async function () { + await runAuction({ adUnits: [ { code: 'test1', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] }, { code: 'test2', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] } ] @@ -2228,19 +2267,19 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[1].transactionId).to.equal(auctionArgs.adUnits[1].ortb2Imp.ext.tid); }); - it('should notify targeting of the latest auction for each adUnit', function () { + it('should notify targeting of the latest auction for each adUnit', async function () { let latestStub = sinon.stub(targeting, 'setLatestAuctionForAdUnit'); let getAuctionStub = sinon.stub(auction, 'getAuctionId').returns(2); - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: [ { code: 'test1', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] }, { code: 'test2', - mediaTypes: { banner: { sizes: [] } }, + mediaTypes: {banner: {sizes: []}}, bids: [] } ] @@ -2253,12 +2292,13 @@ describe('Unit: Prebid Module', function () { getAuctionStub.restore(); }); - it('should execute callback immediately if adUnits is empty', function () { - var bidsBackHandler = function bidsBackHandlerCallback() {}; + it('should execute callback immediately if adUnits is empty', async function () { + var bidsBackHandler = function bidsBackHandlerCallback() { + }; var spyExecuteCallback = sinon.spy(bidsBackHandler); $$PREBID_GLOBAL$$.adUnits = []; - $$PREBID_GLOBAL$$.requestBids({ + await $$PREBID_GLOBAL$$.requestBids({ bidsBackHandler: spyExecuteCallback }); @@ -2283,111 +2323,116 @@ describe('Unit: Prebid Module', function () { describe('checkAdUnitSetup', function() { describe('positive tests for validating adUnits', function() { - it('should maintain adUnit structure and adUnit.sizes is replaced', function () { - let fullAdUnit = [{ - code: 'test1', - sizes: [[300, 250], [300, 600]], - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [[640, 480]] - }, - native: { - image: { - sizes: [150, 150], - aspect_ratios: [140, 140] + describe('should maintain adUnit structure and adUnit.sizes is replaced', () => { + it('full ad unit', async () => { + let fullAdUnit = [{ + code: 'test1', + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250]] }, - icon: { - sizes: [75, 75] - } - } - }, - bids: [] - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: fullAdUnit - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal( - FEATURES.VIDEO ? [[640, 480]] : [[300, 250]] - ); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); - expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.deep.equal([140, 140]); - - let noOptnlFieldAdUnit = [{ - code: 'test2', - bids: [], - sizes: [[300, 250], [300, 600]], - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - context: 'outstream' - }, - native: { - image: { - required: true + video: { + playerSize: [[640, 480]] }, - icon: { - required: true + native: { + image: { + sizes: [150, 150], + aspect_ratios: [140, 140] + }, + icon: { + sizes: [75, 75] + } } - } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: noOptnlFieldAdUnit - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - - if (FEATURES.VIDEO) { - let mixedAdUnit = [{ - code: 'test3', + }, + bids: [] + }]; + await runAuction({ + adUnits: fullAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal( + FEATURES.VIDEO ? [[640, 480]] : [[300, 250]] + ); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); + expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.deep.equal([140, 140]); + }) + it('no optional field', async () => { + let noOptnlFieldAdUnit = [{ + code: 'test2', bids: [], sizes: [[300, 250], [300, 600]], mediaTypes: { + banner: { + sizes: [[300, 250]] + }, video: { - context: 'outstream', - playerSize: [[400, 350]] + context: 'outstream' }, native: { image: { - aspect_ratios: [200, 150], + required: true + }, + icon: { required: true } } } }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: mixedAdUnit + await runAuction({ + adUnits: noOptnlFieldAdUnit }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - - let altVideoPlayerSize = [{ - code: 'test4', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [640, 480] + }) + if (FEATURES.VIDEO) { + it('mixed ad unit', async () => { + let mixedAdUnit = [{ + code: 'test3', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 350]] + }, + native: { + image: { + aspect_ratios: [200, 150], + required: true + } + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: altVideoPlayerSize + }]; + await runAuction({ + adUnits: mixedAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + it('alternative video size', async () => { + let altVideoPlayerSize = [{ + code: 'test4', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [640, 480] + } + } + }]; + await runAuction({ + adUnits: altVideoPlayerSize + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }) } - }); + }) - it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', function () { + it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', async function () { let normalizeAdUnit = [{ code: 'test5', bids: [], @@ -2398,14 +2443,14 @@ describe('Unit: Prebid Module', function () { } } }]; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: normalizeAdUnit }); expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[300, 250]]); }); - it('should filter mediaType pos value if not integer', function () { + it('should filter mediaType pos value if not integer', async function () { let adUnit = [{ code: 'test5', bids: [], @@ -2417,13 +2462,13 @@ describe('Unit: Prebid Module', function () { } } }]; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: adUnit }); expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.be.undefined; }); - it('should pass mediaType pos value if integer', function () { + it('should pass mediaType pos value if integer', async function () { let adUnit = [{ code: 'test5', bids: [], @@ -2434,8 +2479,7 @@ describe('Unit: Prebid Module', function () { pos: 2 } } - }, - { + }, { code: 'test6', bids: [], sizes: [300, 250], @@ -2446,14 +2490,14 @@ describe('Unit: Prebid Module', function () { } } }]; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: adUnit }); expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.equal(2); expect(auctionArgs.adUnits[1].mediaTypes.banner.pos).to.equal(0); }); - it(`should allow no bids if 'ortb2Imp' is specified`, () => { + it(`should allow no bids if 'ortb2Imp' is specified`, async () => { const adUnit = { code: 'test', mediaTypes: { @@ -2463,7 +2507,7 @@ describe('Unit: Prebid Module', function () { }, ortb2Imp: {} }; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: [adUnit] }); sinon.assert.match(auctionArgs.adUnits[0], adUnit); @@ -2471,118 +2515,125 @@ describe('Unit: Prebid Module', function () { }); describe('negative tests for validating adUnits', function() { - it('should throw error message and delete an object/property', function () { - let badBanner = [{ - code: 'testb1', - bids: [], - sizes: [[300, 250], [300, 600]], - mediaTypes: { - banner: { - name: 'test' - } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badBanner - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250], [300, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined; - assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.')); - - if (FEATURES.VIDEO) { - let badVideo1 = [{ - code: 'testb2', + describe('should throw error message and delete an object/property', () => { + it('bad banner', async () => { + let badBanner = [{ + code: 'testb1', bids: [], - sizes: [[600, 600]], + sizes: [[300, 250], [300, 600]], mediaTypes: { - video: { - playerSize: ['600x400'] + banner: { + name: 'test' } } }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo1 + await runAuction({ + adUnits: badBanner }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); - - let badVideo2 = [{ - code: 'testb3', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [['300', '200']] + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250], [300, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined; + assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.')); + }); + if (FEATURES.VIDEO) { + it('bad video 1', async () => { + let badVideo1 = [{ + code: 'testb2', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: ['600x400'] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo2 + }]; + await runAuction({ + adUnits: badVideo1 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + it('bad video 2', async () => { + let badVideo2 = [{ + code: 'testb3', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [['300', '200']] + } + } + }]; + await runAuction({ + adUnits: badVideo2 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }) } - if (FEATURES.NATIVE) { - let badNativeImgSize = [{ - code: 'testb4', - bids: [], - mediaTypes: { - native: { - image: { - sizes: '300x250' + it('bad native img size', async () => { + let badNativeImgSize = [{ + code: 'testb4', + bids: [], + mediaTypes: { + native: { + image: { + sizes: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeImgSize + }]; + await runAuction({ + adUnits: badNativeImgSize + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); }); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); - - let badNativeImgAspRat = [{ - code: 'testb5', - bids: [], - mediaTypes: { - native: { - image: { - aspect_ratios: '300x250' + it('bad native aspect ratio', async () => { + let badNativeImgAspRat = [{ + code: 'testb5', + bids: [], + mediaTypes: { + native: { + image: { + aspect_ratios: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeImgAspRat + }]; + await runAuction({ + adUnits: badNativeImgAspRat + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); }); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); - - let badNativeIcon = [{ - code: 'testb6', - bids: [], - mediaTypes: { - native: { - icon: { - sizes: '300x250' + it('bad native icon', async () => { + let badNativeIcon = [{ + code: 'testb6', + bids: [], + mediaTypes: { + native: { + icon: { + sizes: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeIcon - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + }]; + await runAuction({ + adUnits: badNativeIcon + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + }) } - }); + }) if (FEATURES.NATIVE) { Object.entries({ @@ -2591,7 +2642,7 @@ describe('Unit: Prebid Module', function () { 'not an integer': {id: 1.23}, NaN: {id: 'garbage'} }).forEach(([t, props]) => { - it(`should reject native ortb when asset ID is ${t}`, () => { + it(`should reject native ortb when asset ID is ${t}`, async () => { const adUnit = { code: 'au', mediaTypes: { @@ -2603,7 +2654,7 @@ describe('Unit: Prebid Module', function () { }, bids: [{bidder: 'appnexus'}] }; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: [adUnit] }); expect(auctionArgs.adUnits[0].bids.length).to.equal(0); @@ -2611,7 +2662,7 @@ describe('Unit: Prebid Module', function () { }); ['sendTargetingKeys', 'types'].forEach(key => { - it(`should reject native that includes both ortb and ${key}`, () => { + it(`should reject native that includes both ortb and ${key}`, async () => { const adUnit = { code: 'au', mediaTypes: { @@ -2622,7 +2673,7 @@ describe('Unit: Prebid Module', function () { }, bids: [{bidder: 'appnexus'}] }; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: [adUnit] }); expect(auctionArgs.adUnits[0].bids.length).to.equal(0); @@ -2630,7 +2681,7 @@ describe('Unit: Prebid Module', function () { }); } - it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', function () { + it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', async function () { const adUnits = [{ code: 'ad-unit-1', mediaTypes: { @@ -2648,7 +2699,7 @@ describe('Unit: Prebid Module', function () { } }]; - $$PREBID_GLOBAL$$.requestBids({ + await runAuction({ adUnits: adUnits }); expect(auctionArgs.adUnits.length).to.equal(1); @@ -2663,7 +2714,7 @@ describe('Unit: Prebid Module', function () { if (!FEATURES.NATIVE) { return; } - let adUnits; + let adUnits, auctionStarted; beforeEach(function () { adUnits = [{ @@ -2682,15 +2733,21 @@ describe('Unit: Prebid Module', function () { }]; adUnitCodes = ['adUnit-code']; configObj.setConfig({maxRequestsPerOrigin: Number.MAX_SAFE_INTEGER || 99999999}); - sinon.spy(adapterManager, 'callBids'); + auctionStarted = new Promise(resolve => { + sinon.stub(adapterManager, 'callBids').callsFake(function() { + resolve(); + return adapterManager.callBids.wrappedMethod.apply(this, arguments); + }); + }) }) afterEach(function () { adapterManager.callBids.restore(); }); - it('bidders that support one of the declared formats are allowed to participate', function () { + it('bidders that support one of the declared formats are allowed to participate', async function () { $$PREBID_GLOBAL$$.requestBids({adUnits}); + await auctionStarted; sinon.assert.calledOnce(adapterManager.callBids); const spyArgs = adapterManager.callBids.getCall(0); @@ -2700,10 +2757,11 @@ describe('Unit: Prebid Module', function () { expect(biddersCalled.length).to.equal(2); }); - it('bidders that do not support one of the declared formats are dropped', function () { + it('bidders that do not support one of the declared formats are dropped', async function () { delete adUnits[0].mediaTypes.banner; $$PREBID_GLOBAL$$.requestBids({adUnits}); + await auctionStarted; sinon.assert.calledOnce(adapterManager.callBids); const spyArgs = adapterManager.callBids.getCall(0); @@ -2718,44 +2776,10 @@ describe('Unit: Prebid Module', function () { return; } let spyCallBids; - let createAuctionStub; - let adUnits; - - before(function () { - adUnits = [{ - code: 'adUnit-code', - mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - bids: [ - {bidder: 'appnexus', params: {placementId: '10433394'}} - ] - }]; - let adUnitCodes = ['adUnit-code']; - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - adUnits[0]['mediaTypes'] = { native: {} }; - adUnitCodes = ['adUnit-code']; - let auction1 = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - - adUnits = [{ - code: 'adUnit-code', - mediaTypes: { native: { type: 'image' } }, - sizes: [[300, 250], [300, 600]], - bids: [ - {bidder: 'appnexus', params: {placementId: 'id'}} - ] - }]; - let auction3 = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - - let createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.onCall(0).returns(auction1); - createAuctionStub.onCall(2).returns(auction3); - createAuctionStub.returns(auction); - }); - - after(function () { - auctionModule.newAuction.restore(); - }); + let adUnits, adUnitCodes; beforeEach(function () { + adUnitCodes = ['adUnit-code']; spyCallBids = sinon.spy(adapterManager, 'callBids'); }) @@ -2763,32 +2787,50 @@ describe('Unit: Prebid Module', function () { adapterManager.callBids.restore(); }) - it('should callBids if a native adUnit has all native bidders', function () { - $$PREBID_GLOBAL$$.requestBids({adUnits}); + function runAuction(request = {}) { + const auctionStarted = new Promise(resolve => { + sandbox.stub(auctionModule, 'newAuction').callsFake((...args) => { + resolve(); + return auctionModule.newAuction.wrappedMethod(...args); + }); + }) + $$PREBID_GLOBAL$$.requestBids(request); + return auctionStarted; + } + + it('should callBids if a native adUnit has all native bidders', async function () { + adUnits = [{ + code: 'adUnit-code', + mediaTypes: { native: {} }, + bids: [ + {bidder: 'appnexus', params: {placementId: '10433394'}} + ] + }]; + await runAuction({adUnits}); sinon.assert.calledOnce(adapterManager.callBids); }); - it('should call callBids function on adapterManager', function () { - let adUnits = [{ + it('should call callBids function on adapterManager', async function () { + adUnits = [{ code: 'adUnit-code', - mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, bids: [ {bidder: 'appnexus', params: {placementId: '10433394'}} ] }]; - $$PREBID_GLOBAL$$.requestBids({adUnits}); + await runAuction({adUnits}); assert.ok(spyCallBids.called, 'called adapterManager.callBids'); }); - it('splits native type to individual native assets', function () { - let adUnits = [{ + it('splits native type to individual native assets', async function () { + adUnits = [{ code: 'adUnit-code', mediaTypes: {native: {type: 'image'}}, bids: [ {bidder: 'appnexus', params: {placementId: 'id'}} ] }]; - $$PREBID_GLOBAL$$.requestBids({adUnits}); + await runAuction({adUnits}); const spyArgs = adapterManager.callBids.getCall(0); const nativeRequest = spyArgs.args[1][0].bids[0].nativeParams; expect(nativeRequest.ortb.assets).to.deep.equal([ @@ -2884,11 +2926,19 @@ describe('Unit: Prebid Module', function () { }); }; + let auctionsStarted; + beforeEach(function() { spyCallBids = sinon.spy(adapterManager, 'callBids'); auctionManagerStub = sinon.stub(auctionManager, 'createAuction'); - auctionManagerStub.onCall(0).returns(auction1); - auctionManagerStub.onCall(1).returns(auction2); + auctionsStarted = Promise.all( + [auction1, auction2].map((au, i) => new Promise((resolve) => { + auctionManagerStub.onCall(i).callsFake(() => { + resolve(); + return au; + }); + })) + ); }); afterEach(function() { @@ -2896,15 +2946,17 @@ describe('Unit: Prebid Module', function () { adapterManager.callBids.restore(); }); - it('should not queue bid requests when a previous bid request is in process', function () { + it('should not queue bid requests when a previous bid request is in process', async function () { var requestObj1 = { - bidsBackHandler: function bidsBackHandlerCallback() {}, + bidsBackHandler: function bidsBackHandlerCallback() { + }, timeout: 2000, adUnits: auction1.getAdUnits() }; var requestObj2 = { - bidsBackHandler: function bidsBackHandlerCallback() {}, + bidsBackHandler: function bidsBackHandlerCallback() { + }, timeout: 2000, adUnits: auction2.getAdUnits() }; @@ -2913,6 +2965,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids(requestObj1); $$PREBID_GLOBAL$$.requestBids(requestObj2); + await auctionsStarted; assert.ok(spyCallBids.calledTwice, 'When two requests for bids are made both should be' + ' callBids immediately'); @@ -2976,6 +3029,7 @@ describe('Unit: Prebid Module', function () { let bidRequestedHandler; let beforeRequestBidsHandler; let bidsBackHandler = function bidsBackHandler() {}; + let auctionStarted; let bidsBackSpy; let bidRequestedSpy; @@ -2985,6 +3039,10 @@ describe('Unit: Prebid Module', function () { resetAuction(); bidsBackSpy = sinon.spy(bidsBackHandler); googletag.pubads().setSlots(createSlotArrayScenario2()); + auctionStarted = new Promise((resolve) => sandbox.stub(adapterManager, 'callBids').callsFake(function() { + resolve() + return adapterManager.callBids.wrappedMethod.apply(this, arguments); + })); }); afterEach(function () { @@ -3001,7 +3059,7 @@ describe('Unit: Prebid Module', function () { } }); - it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', function () { + it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', async function () { // verify adUnits passed to handler then alter the adUnits beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) { expect(beforeRequestBidsAdUnits).to.be.a('array'); @@ -3055,11 +3113,13 @@ describe('Unit: Prebid Module', function () { bidsBackHandler: bidsBackSpy }); + await auctionStarted; + sinon.assert.calledOnce(beforeRequestBidsSpy); sinon.assert.calledOnce(bidRequestedSpy); }); - it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', function () { + it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', async function () { // verify adUnits passed to handler then alter the adUnits beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) { expect(beforeRequestBidsAdUnits).to.be.a('array'); @@ -3141,12 +3201,13 @@ describe('Unit: Prebid Module', function () { }], bidsBackHandler: bidsBackSpy }); + await auctionStarted; sinon.assert.calledOnce(beforeRequestBidsSpy); sinon.assert.calledOnce(bidRequestedSpy); }); - it('should not create a context property on adUnits if not added by handler', function () { + it('should not create a context property on adUnits if not added by handler', async function () { // verify adUnits passed to handler then alter the adUnits beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) { expect(beforeRequestBidsAdUnits).to.be.a('array'); @@ -3188,6 +3249,7 @@ describe('Unit: Prebid Module', function () { }], bidsBackHandler: bidsBackSpy }); + await auctionStarted; sinon.assert.calledOnce(beforeRequestBidsSpy); sinon.assert.calledOnce(bidRequestedSpy); diff --git a/test/spec/unit/utils/promise_spec.js b/test/spec/unit/utils/promise_spec.js index bd8b0390b2e..c0456a11747 100644 --- a/test/spec/unit/utils/promise_spec.js +++ b/test/spec/unit/utils/promise_spec.js @@ -1,191 +1,6 @@ -import {GreedyPromise, defer} from '../../../../src/utils/promise.js'; +import {defer} from '../../../../src/utils/promise.js'; -describe('GreedyPromise', () => { - it('throws when resolver is not a function', () => { - expect(() => new GreedyPromise()).to.throw(); - }) - - Object.entries({ - 'resolved': (use) => new GreedyPromise((resolve) => use(resolve)), - 'rejected': (use) => new GreedyPromise((_, reject) => use(reject)) - }).forEach(([t, makePromise]) => { - it(`runs callbacks immediately when ${t}`, () => { - let cbRan = false; - const cb = () => { cbRan = true }; - let resolver; - makePromise((fn) => { resolver = fn }).then(cb, cb); - resolver(); - expect(cbRan).to.be.true; - }) - }); - - describe('idioms', () => { - let makePromise, pendingFailure, pendingSuccess; - - Object.entries({ - // eslint-disable-next-line no-throw-literal - 'resolver that throws': (P) => new P(() => { throw 'error' }), - 'resolver that resolves multiple times': (P) => new P((resolve) => { resolve('first'); resolve('second'); }), - 'resolver that rejects multiple times': (P) => new P((resolve, reject) => { reject('first'); reject('second') }), - 'resolver that resolves and rejects': (P) => new P((resolve, reject) => { reject('first'); resolve('second') }), - 'resolver that resolves with multiple arguments': (P) => new P((resolve) => resolve('one', 'two')), - 'resolver that rejects with multiple arguments': (P) => new P((resolve, reject) => reject('one', 'two')), - 'resolver that resolves to a promise': (P) => new P((resolve) => resolve(makePromise(P, 'val'))), - 'resolver that resolves to a promise that resolves to a promise': (P) => new P((resolve) => resolve(makePromise(P, makePromise(P, 'val')))), - 'resolver that resolves to a rejected promise': (P) => new P((resolve) => resolve(makePromise(P, 'err', true))), - 'simple .then': (P) => makePromise(P, 'value').then((v) => `${v} and then`), - 'chained .then': (P) => makePromise(P, 'value').then((v) => makePromise(P, `${v} and then`)), - '.then with error handler': (P) => makePromise(P, 'err', true).then(null, (e) => `${e} and then`), - '.then with chained error handler': (P) => makePromise(P, 'err', true).then(null, (e) => makePromise(P, `${e} and then`)), - '.then that throws': (P) => makePromise(P, 'value').then((v) => { throw v }), - '.then that throws in error handler': (P) => makePromise(P, 'err', true).then(null, (e) => { throw e }), - '.then with no args': (P) => makePromise(P, 'value').then(), - '.then that rejects': (P) => makePromise(P, 'value').then((v) => P.reject(v)), - '.then that rejects in error handler': (P) => makePromise(P, 'err', true).then(null, (err) => P.reject(err)), - '.then with no error handler on a rejection': (P) => makePromise(P, 'err', true).then((v) => `resolved ${v}`), - '.then with no success handler on a resolution': (P) => makePromise(P, 'value').then(null, (e) => `caught ${e}`), - 'simple .catch': (P) => makePromise(P, 'err', true).catch((err) => `caught ${err}`), - 'identity .catch': (P) => makePromise(P, 'err', true).catch((err) => err).then((v) => v), - '.catch that throws': (P) => makePromise(P, 'err', true).catch((err) => { throw err }), - 'chained .catch': (P) => makePromise(P, 'err', true).catch((err) => makePromise(P, err)), - 'chained .catch that rejects': (P) => makePromise(P, 'err', true).catch((err) => P.reject(`reject with ${err}`)), - 'simple .finally': (P) => { - let fval; - return makePromise(P, 'value') - .finally(() => fval = 'finally ran') - .then((val) => `${val} ${fval}`) - }, - 'chained .finally': (P) => { - let fval; - return makePromise(P, 'value') - .finally(() => pendingSuccess.then(() => { fval = 'finally ran' })) - .then((val) => `${val} ${fval}`) - }, - '.finally on a rejection': (P) => { - let fval; - return makePromise(P, 'error', true) - .finally(() => { fval = 'finally' }) - .catch((err) => `${err} ${fval}`) - }, - 'chained .finally on a rejection': (P) => { - let fval; - return makePromise(P, 'error', true) - .finally(() => pendingSuccess.then(() => { fval = 'finally' })) - .catch((err) => `${err} ${fval}`) - }, - // eslint-disable-next-line no-throw-literal - '.finally that throws': (P) => makePromise(P, 'value').finally(() => { throw 'error' }), - 'chained .finally that rejects': (P) => makePromise(P, 'value').finally(() => P.reject('error')), - 'scalar Promise.resolve': (P) => P.resolve('scalar'), - 'null Promise.resolve': (P) => P.resolve(null), - 'chained Promise.resolve': (P) => P.resolve(pendingSuccess), - 'chained Promise.resolve on failure': (P) => P.resolve(pendingFailure), - 'scalar Promise.reject': (P) => P.reject('scalar'), - 'chained Promise.reject': (P) => P.reject(pendingSuccess), - 'chained Promise.reject on failure': (P) => P.reject(pendingFailure), - 'simple Promise.all': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two')]), - 'empty Promise.all': (P) => P.all([]), - 'Promise.all with scalars': (P) => P.all([makePromise(P, 'one'), 'two']), - 'Promise.all with errors': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two'), makePromise(P, 'err', true)]), - 'Promise.allSettled': (P) => P.allSettled([makePromise(P, 'one', true), makePromise(P, 'two'), makePromise(P, 'three', true)]), - 'empty Promise.allSettled': (P) => P.allSettled([]), - 'Promise.allSettled with scalars': (P) => P.allSettled([makePromise(P, 'value'), 'scalar']), - 'Promise.race that succeeds': (P) => P.race([makePromise(P, 'error', true, 10), makePromise(P, 'success')]), - 'Promise.race that fails': (P) => P.race([makePromise(P, 'success', false, 10), makePromise(P, 'error', true)]), - 'Promise.race with scalars': (P) => P.race(['scalar', makePromise(P, 'success')]), - }).forEach(([t, op]) => { - describe(t, () => { - describe('when mixed with deferrals', () => { - beforeEach(() => { - makePromise = function(ctor, value, fail = false, delay = 0) { - // eslint-disable-next-line new-cap - return new ctor((resolve, reject) => { - setTimeout(() => fail ? reject(value) : resolve(value), delay) - }) - }; - pendingSuccess = makePromise(Promise, 'pending result', false, 10); - pendingFailure = makePromise(Promise, 'pending failure', true, 10); - }); - - it(`behaves like vanilla promises`, () => { - const vanilla = op(Promise); - const greedy = op(GreedyPromise); - // note that we are not using `allSettled` & co to resolve our promises, - // to avoid transformations those methods do under the hood - const {actual = {}, expected = {}} = {}; - return new Promise((resolve) => { - let pending = 2; - function collect(dest, slot) { - return function (value) { - dest[slot] = value; - pending--; - if (pending === 0) { - resolve() - } - } - } - vanilla.then(collect(expected, 'success'), collect(expected, 'failure')); - greedy.then(collect(actual, 'success'), collect(actual, 'failure')); - }).then(() => { - expect(actual).to.eql(expected); - }); - }); - - it(`once resolved, runs callbacks immediately`, () => { - const promise = op(GreedyPromise).catch(() => null); - return promise.then(() => { - let cbRan = false; - promise.then(() => { cbRan = true }); - expect(cbRan).to.be.true; - }); - }); - }); - - describe('when all promises involved are greedy', () => { - beforeEach(() => { - makePromise = function(ctor, value, fail = false, delay = 0) { - // eslint-disable-next-line new-cap - return new ctor((resolve, reject) => { - const run = () => fail ? reject(value) : resolve(value); - delay === 0 ? run() : setTimeout(run, delay); - }) - }; - pendingSuccess = makePromise(GreedyPromise, 'pending result'); - pendingFailure = makePromise(GreedyPromise, 'pending failure', true); - }); - - it('resolves immediately', () => { - let cbRan = false; - op(GreedyPromise).catch(() => null).then(() => { cbRan = true }); - expect(cbRan).to.be.true; - }); - }); - }); - }); - }); - - describe('.timeout', () => { - const timeout = GreedyPromise.timeout; - - it('should resolve immediately when ms is 0', () => { - let cbRan = false; - timeout(0.0).then(() => { cbRan = true }); - expect(cbRan).to.be.true; - }); - - it('should schedule timeout on ms > 0', (done) => { - let cbRan = false; - timeout(5).then(() => { cbRan = true }); - expect(cbRan).to.be.false; - setTimeout(() => { - expect(cbRan).to.be.true; - done(); - }, 10) - }); - }); -}); - -describe('promiseControls', () => { +describe('defer', () => { Object.entries({ 'resolve': (p) => p, 'reject': (p) => p.then(() => 'wrong', (v) => v) From 2ad1d23452bd1804eb0fad406fc0e26de1ce8112 Mon Sep 17 00:00:00 2001 From: dmytro-po Date: Sun, 2 Mar 2025 19:06:49 +0200 Subject: [PATCH 0967/1097] IntentIq ID & Analytics Modules: GDPR support and update documentation (#12738) * AGT-389: CMP data to module * AGT-389: Analytics refactoring and changes after PR review * AGT-389: Parameters description, some edits * AGT-389: CMP data tests * AGT-389: Tests fix, export fpd * AGT-389: Fix uh parameter encoding * AGT-389: Refactoring, test for new user * fix linter * AGT-389: Minor fixes * AGT-389: Fix getIntentIqConfig method * AGT-389: Gdpr detected tests, fix gdpr requests addresses * AGT-389: Removed functionality partner to provide cmp data * AGT-389: Clear extra comments, fix test * AGT-389: Removed allow optons for cmpData * AFT-399: Change getCmpData * AFT-399: Change comment * AFT-399: Delete comment * gdprApplies, refactoring, fix tests * AGT-389: Refactoring storageUtils * AGT-389: Change version --------- Co-authored-by: DimaIntentIQ --- .../intentIqConstants/intentIqConstants.js | 7 +- libraries/intentIqUtils/getCmpData.js | 19 ++ libraries/intentIqUtils/getGppValue.js | 16 -- libraries/intentIqUtils/storageUtils.js | 89 ++++++++ modules/intentIqAnalyticsAdapter.js | 88 ++++---- modules/intentIqIdSystem.js | 205 +++++++----------- modules/intentIqIdSystem.md | 2 +- .../modules/intentIqAnalyticsAdapter_spec.js | 51 ++++- test/spec/modules/intentIqIdSystem_spec.js | 175 ++++++++------- 9 files changed, 375 insertions(+), 277 deletions(-) create mode 100644 libraries/intentIqUtils/getCmpData.js delete mode 100644 libraries/intentIqUtils/getGppValue.js create mode 100644 libraries/intentIqUtils/storageUtils.js diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index 7f3b6fd94f7..6dc16969d6c 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -1,10 +1,11 @@ export const FIRST_PARTY_KEY = '_iiq_fdata'; +export const SUPPORTED_TYPES = ['html5', 'cookie'] export const WITH_IIQ = 'A'; export const WITHOUT_IIQ = 'B'; export const NOT_YET_DEFINED = 'U'; -export const OPT_OUT = 'O'; export const BLACK_LIST = 'L'; export const CLIENT_HINTS_KEY = '_iiq_ch'; -export const EMPTY = 'EMPTY' -export const VERSION = 0.26 +export const EMPTY = 'EMPTY'; +export const GVLID = '1323'; +export const VERSION = 0.27; diff --git a/libraries/intentIqUtils/getCmpData.js b/libraries/intentIqUtils/getCmpData.js new file mode 100644 index 00000000000..b23f0fbaffe --- /dev/null +++ b/libraries/intentIqUtils/getCmpData.js @@ -0,0 +1,19 @@ +import { allConsent } from '../../src/consentHandler.js'; + +/** + * Retrieves consent data from the Consent Management Platform (CMP). + * @return {Object} An object containing the following fields: + * - `gdprString` (string): GDPR consent string if available. + * - `uspString` (string): USP consent string if available. + * - `gppString` (string): GPP consent string if available. + */ +export function getCmpData() { + const consentData = allConsent.getConsentData(); + + return { + gdprApplies: consentData?.gdpr?.gdprApplies || false, + gdprString: typeof consentData?.gdpr?.consentString === 'string' ? consentData.gdpr.consentString : null, + uspString: typeof consentData?.usp === 'string' ? consentData.usp : null, + gppString: typeof consentData?.gpp?.gppString === 'string' ? consentData.gpp.gppString : null, + }; +} diff --git a/libraries/intentIqUtils/getGppValue.js b/libraries/intentIqUtils/getGppValue.js deleted file mode 100644 index 9c538b4f753..00000000000 --- a/libraries/intentIqUtils/getGppValue.js +++ /dev/null @@ -1,16 +0,0 @@ -import {gppDataHandler} from '../../src/consentHandler.js'; - -/** - * Retrieves the GPP string value and additional GPP-related information. - * This function extracts the GPP data, encodes it, and determines specific GPP flags such as GPI and applicable sections. - * @return {Object} An object containing: - * - `gppString` (string): The encoded GPP string value. - * - `gpi` (number): An indicator representing whether GPP consent is available (0 if available, 1 if not). - */ -export function getGppValue() { - const gppData = gppDataHandler.getConsentData(); - const gppString = gppData?.gppString || ''; - const gpi = gppString ? 0 : 1; - - return { gppString, gpi }; -} diff --git a/libraries/intentIqUtils/storageUtils.js b/libraries/intentIqUtils/storageUtils.js new file mode 100644 index 00000000000..8e7bf1d12a5 --- /dev/null +++ b/libraries/intentIqUtils/storageUtils.js @@ -0,0 +1,89 @@ +import {logError, logInfo} from '../../src/utils.js'; +import {SUPPORTED_TYPES, FIRST_PARTY_KEY} from '../../libraries/intentIqConstants/intentIqConstants.js'; +import {getStorageManager} from '../../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../src/activities/modules.js'; + +const MODULE_NAME = 'intentIqId'; +const PCID_EXPIRY = 365; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +/** + * Read data from local storage or cookie based on allowed storage types. + * @param {string} key - The key to read data from. + * @param {Array} allowedStorage - Array of allowed storage types ('html5' or 'cookie'). + * @return {string|null} - The retrieved data or null if an error occurs. + */ +export function readData(key, allowedStorage) { + try { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { + return storage.getDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + return storage.getCookie(key); + } + } catch (error) { + logError(`${MODULE_NAME}: Error reading data`, error); + } + return null; +} + +/** + * Store Intent IQ data in cookie, local storage or both of them + * expiration date: 365 days + * @param {string} key - The key under which the data will be stored. + * @param {string} value - The value to be stored (e.g., IntentIQ ID). + * @param {Array} allowedStorage - An array of allowed storage types: 'html5' for Local Storage and/or 'cookie' for Cookies. + * @param {Object} firstPartyData - Contains user consent data; if isOptedOut is true, data will not be stored (except for FIRST_PARTY_KEY). + */ +export function storeData(key, value, allowedStorage, firstPartyData) { + try { + if (firstPartyData?.isOptedOut && key !== FIRST_PARTY_KEY) { + return; + } + logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); + if (value) { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { + storage.setDataInLocalStorage(key, value); + } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); + storage.setCookie(key, value, expiresStr, 'LAX'); + } + } + } catch (error) { + logError(error); + } +} + +/** + * Remove Intent IQ data from cookie or local storage + * @param key + */ + +export function removeDataByKey(key, allowedStorage) { + try { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { + storage.removeDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(key, '', expiredDate, 'LAX'); + } + } catch (error) { + logError(error); + } +} + +/** + * Determines the allowed storage types based on provided parameters. + * If no valid storage types are provided, it defaults to 'html5'. + * + * @param {Array} params - An array containing storage type preferences, e.g., ['html5', 'cookie']. + * @return {Array} - Returns an array with allowed storage types. Defaults to ['html5'] if no valid options are provided. + */ +export function defineStorageType(params) { + if (!params || !Array.isArray(params)) return ['html5']; // use locale storage be default + const filteredArr = params.filter(item => SUPPORTED_TYPES.includes(item)); + return filteredArr.length ? filteredArr : ['html5']; +} diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index e3b5625dc7b..3cc0efbdb57 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -8,15 +8,18 @@ import {EVENTS} from '../src/constants.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; import {appendVrrefAndFui, getReferrer} from '../libraries/intentIqUtils/getRefferer.js'; -import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js'; +import {getCmpData} from '../libraries/intentIqUtils/getCmpData.js' import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js'; +import {readData, defineStorageType} from '../libraries/intentIqUtils/storageUtils.js'; const MODULE_NAME = 'iiqAnalytics' const analyticsType = 'endpoint'; -const defaultUrl = 'https://reports.intentiq.com/report'; +const REPORT_ENDPOINT = 'https://reports.intentiq.com/report'; +const REPORT_ENDPOINT_GDPR = 'https://reports-gdpr.intentiq.com/report'; const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); const prebidVersion = '$prebid.version$'; export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); +const allowedStorage = defineStorageType(config.enabledStorageTypes); const PARAMS_NAMES = { abTestGroup: 'abGroup', @@ -55,7 +58,7 @@ const PARAMS_NAMES = { placementId: 'placementId' }; -let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl, analyticsType}), { +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl: REPORT_ENDPOINT, analyticsType}), { initOptions: { lsValueInitialized: false, partner: null, @@ -87,51 +90,38 @@ const { BID_REQUESTED } = EVENTS; -function readData(key) { - try { - if (storage.hasLocalStorage()) { - return storage.getDataFromLocalStorage(key); - } - if (storage.cookiesAreEnabled()) { - return storage.getCookie(key); - } - } catch (error) { - logError(error); - } +function getIntentIqConfig() { + return config.getConfig('userSync.userIds')?.find(m => m.name === 'intentIqId'); } function initLsValues() { if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; - let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId'); - if (iiqArr && iiqArr.length > 0) iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; - if (!iiqArr) iiqArr = []; - if (iiqArr.length == 0) { - iiqArr.push({ - 'params': { - 'partner': -1, - 'group': 'U' - } - }) - } - if (iiqArr && iiqArr.length > 0) { - if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) { - iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; - } - iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : ''; - iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqArr[0].params.manualWinReportEnabled || false; - iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqArr[0].params.domainName || ''; + let iiqConfig = getIntentIqConfig() + + if (iiqConfig) { + iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; + iiqAnalyticsAnalyticsAdapter.initOptions.partner = + iiqConfig.params?.partner && !isNaN(iiqConfig.params.partner) ? iiqConfig.params.partner : -1; + + iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = + typeof iiqConfig.params?.browserBlackList === 'string' ? iiqConfig.params.browserBlackList.toLowerCase() : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqConfig.params?.manualWinReportEnabled || false; + iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqConfig.params?.domainName || ''; + } else { + iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = false; + iiqAnalyticsAnalyticsAdapter.initOptions.partner = -1; } } function initReadLsIds() { try { iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; - iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData(FIRST_PARTY_KEY)); + iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData(FIRST_PARTY_KEY, allowedStorage, storage)); if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; } - const partnerData = readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); - const clientsHints = readData(CLIENT_HINTS_KEY) || ''; + const partnerData = readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, allowedStorage, storage); + const clientsHints = readData(CLIENT_HINTS_KEY, allowedStorage, storage) || ''; if (partnerData) { iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; @@ -177,18 +167,13 @@ function bidWon(args, isReportExternal) { function defineGlobalVariableName() { function reportExternalWin(args) { - return bidWon(args, true) + return bidWon(args, true); } - let partnerId = 0 - const userConfig = config.getConfig('userSync.userIds') + const iiqConfig = getIntentIqConfig(); + const partnerId = iiqConfig?.params?.partner || 0; - if (userConfig) { - const iiqArr = userConfig.filter(m => m.name == 'intentIqId'); - if (iiqArr.length) partnerId = iiqArr[0].params.partner - } - - window[`intentIqAnalyticsAdapter_${partnerId}`] = {reportExternalWin: reportExternalWin} + window[`intentIqAnalyticsAdapter_${partnerId}`] = { reportExternalWin }; } function getRandom(start, end) { @@ -197,7 +182,7 @@ function getRandom(start, end) { export function preparePayload(data) { let result = getDefaultDataObject(); - readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); + readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner, allowedStorage, storage); result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; result[PARAMS_NAMES.prebidVersion] = prebidVersion; result[PARAMS_NAMES.referrer] = getReferrer(); @@ -294,9 +279,12 @@ function constructFullUrl(data) { let report = []; data = btoa(JSON.stringify(data)); report.push(data); - const gppData = getGppValue(); - let url = defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + + const cmpData = getCmpData(); + const gdprDetected = cmpData.gdprString; + const baseUrl = gdprDetected ? REPORT_ENDPOINT_GDPR : REPORT_ENDPOINT; + + let url = baseUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + '&mct=1' + ((iiqAnalyticsAnalyticsAdapter.initOptions?.fpid) ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + @@ -305,7 +293,11 @@ function constructFullUrl(data) { '&source=pbjs' + '&payload=' + JSON.stringify(report) + '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + - (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : ''); + (cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : '') + + (cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : '') + + (cmpData.gdprString + ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1' + : '&gdpr=0'); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); return url; diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index fcd0d53f936..548af86d772 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,26 +5,24 @@ * @requires module:modules/userId */ -import {logError, logInfo, isPlainObject} from '../src/utils.js'; +import {logError, isPlainObject} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js' -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -import {uspDataHandler} from '../src/consentHandler.js'; import AES from 'crypto-js/aes.js'; import Utf8 from 'crypto-js/enc-utf8.js'; import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js'; -import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js'; +import { getCmpData } from '../libraries/intentIqUtils/getCmpData.js'; +import {readData, storeData, defineStorageType, removeDataByKey} from '../libraries/intentIqUtils/storageUtils.js'; import { FIRST_PARTY_KEY, WITH_IIQ, WITHOUT_IIQ, NOT_YET_DEFINED, - OPT_OUT, BLACK_LIST, CLIENT_HINTS_KEY, EMPTY, - VERSION + GVLID, + VERSION, } from '../libraries/intentIqConstants/intentIqConstants.js'; /** @@ -33,8 +31,6 @@ import { * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ -const PCID_EXPIRY = 365; - const MODULE_NAME = 'intentIqId'; const encoderCH = { @@ -49,9 +45,9 @@ const encoderCH = { fullVersionList: 8 }; const INVALID_ID = 'INVALID_ID'; -const SUPPORTED_TYPES = ['html5', 'cookie'] - -export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); +const ENDPOINT = 'https://api.intentiq.com'; +const GDPR_ENDPOINT = 'https://api-gdpr.intentiq.com'; +export let firstPartyData; /** * Generate standard UUID string @@ -86,66 +82,6 @@ export function decryptData(encryptedText) { return bytes.toString(Utf8); } -/** - * Read Intent IQ data from local storage or cookie - * @param key - * @return {string} - */ -export function readData(key, allowedStorage) { - try { - if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { - return storage.getDataFromLocalStorage(key); - } - if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { - return storage.getCookie(key); - } - } catch (error) { - logError(error); - } -} - -/** - * Store Intent IQ data in cookie, local storage or both of them - * expiration date: 365 days - * @param key - * @param {string} value IntentIQ ID value to sintentIqIdSystem_spec.jstore - */ -export function storeData(key, value, allowedStorage) { - try { - logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); - if (value) { - if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { - storage.setDataInLocalStorage(key, value); - } - if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { - const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - storage.setCookie(key, value, expiresStr, 'LAX'); - } - } - } catch (error) { - logError(error); - } -} - -/** - * Remove Intent IQ data from cookie or local storage - * @param key - */ - -export function removeDataByKey(key, allowedStorage) { - try { - if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { - storage.removeDataFromLocalStorage(key); - } - if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { - const expiredDate = new Date(0).toUTCString(); - storage.setCookie(key, '', expiredDate, 'LAX'); - } - } catch (error) { - logError(error); - } -} - /** * Parse json if possible, else return null * @param data @@ -202,10 +138,10 @@ export function handleClientHints(clientHints) { return Object.keys(chParams).length ? JSON.stringify(chParams) : ''; } -function defineStorageType(params) { - if (!params || !Array.isArray(params)) return ['html5']; // use locale storage be default - const filteredArr = params.filter(item => SUPPORTED_TYPES.includes(item)); - return filteredArr.length ? filteredArr : ['html5']; +export function isCMPStringTheSame(fpData, cmpData) { + const firstPartyDataCPString = `${fpData.gdprString}${fpData.gppString}${fpData.uspString}`; + const cmpDataString = `${cmpData.gdprString}${cmpData.gppString}${cmpData.uspString}`; + return firstPartyDataCPString === cmpDataString; } /** @type {Submodule} */ @@ -215,6 +151,7 @@ export const intentIqIdSubmodule = { * @type {string} */ name: MODULE_NAME, + gvlid: GVLID, /** * decode the stored id value for passing to bid requests * @function @@ -235,12 +172,19 @@ export const intentIqIdSubmodule = { let decryptedData, callbackTimeoutID; let callbackFired = false; let runtimeEids = { eids: [] }; + let gamObjectReference = isPlainObject(configParams.gamObjectReference) ? configParams.gamObjectReference : undefined; let gamParameterName = configParams.gamParameterName ? configParams.gamParameterName : 'intent_iq_group'; const allowedStorage = defineStorageType(config.enabledStorageTypes); - let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); + let rrttStrtTime = 0; + let partnerData = {}; + let shouldCallServer = false; + const FIRST_PARTY_DATA_KEY = `${FIRST_PARTY_KEY}_${configParams.partner}`; + const cmpData = getCmpData(); + const gdprDetected = cmpData.gdprString; + firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); const isGroupB = firstPartyData?.group === WITHOUT_IIQ; setGamReporting(gamObjectReference, gamParameterName, firstPartyData?.group) @@ -264,12 +208,6 @@ export const intentIqIdSubmodule = { return; } - const FIRST_PARTY_DATA_KEY = `_iiq_fdata_${configParams.partner}`; - - let rrttStrtTime = 0; - let partnerData = {}; - let shouldCallServer = false - const currentBrowserLowerCase = detectBrowser(); const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; @@ -280,23 +218,33 @@ export const intentIqIdSubmodule = { return; } - // Get consent information - const cmpData = {}; - const uspData = uspDataHandler.getConsentData(); - const gppData = getGppValue(); - - if (uspData) { - cmpData.us_privacy = uspData; + if (!firstPartyData?.pcid) { + const firstPartyId = generateGUID(); + firstPartyData = { + pcid: firstPartyId, + pcidDate: Date.now(), + group: NOT_YET_DEFINED, + cttl: 0, + uspString: EMPTY, + gppString: EMPTY, + gdprString: EMPTY, + date: Date.now() + }; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); + } else if (!firstPartyData.pcidDate) { + firstPartyData.pcidDate = Date.now(); + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); } - cmpData.gpp = gppData.gppString; - cmpData.gpi = gppData.gpi; + if (gdprDetected && !('isOptedOut' in firstPartyData)) { + firstPartyData.isOptedOut = true; + } // Read client hints from storage let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); // Get client hints and save to storage - if (navigator.userAgentData) { + if (navigator?.userAgentData?.getHighEntropyValues) { navigator.userAgentData .getHighEntropyValues([ 'brands', @@ -311,34 +259,17 @@ export const intentIqIdSubmodule = { ]) .then(ch => { clientHints = handleClientHints(ch); - storeData(CLIENT_HINTS_KEY, clientHints, allowedStorage) + storeData(CLIENT_HINTS_KEY, clientHints, allowedStorage, firstPartyData) }); } - if (!firstPartyData?.pcid) { - const firstPartyId = generateGUID(); - firstPartyData = { - pcid: firstPartyId, - pcidDate: Date.now(), - group: NOT_YET_DEFINED, - cttl: 0, - uspapi_value: EMPTY, - gpp_value: EMPTY, - date: Date.now() - }; - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); - } else if (!firstPartyData.pcidDate) { - firstPartyData.pcidDate = Date.now(); - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); - } - const savedData = tryParse(readData(FIRST_PARTY_DATA_KEY, allowedStorage)) if (savedData) { partnerData = savedData; if (partnerData.wsrvcll) { partnerData.wsrvcll = false; - storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); } } @@ -349,17 +280,16 @@ export const intentIqIdSubmodule = { } } - if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_string_value !== cmpData.gpp) { - firstPartyData.uspapi_value = cmpData.us_privacy; - firstPartyData.gpp_string_value = cmpData.gpp; - firstPartyData.isOptedOut = false - firstPartyData.cttl = 0 + if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || !isCMPStringTheSame(firstPartyData, cmpData)) { + firstPartyData.uspString = cmpData.uspString; + firstPartyData.gppString = cmpData.gppString; + firstPartyData.gdprString = cmpData.gdprString; + firstPartyData.date = Date.now(); shouldCallServer = true; - partnerData.data = {} - partnerData.eidl = -1 - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); - storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); } else if (firstPartyData.isOptedOut) { + partnerData.data = runtimeEids = { eids: [] }; firePartnerCallback() } @@ -374,7 +304,7 @@ export const intentIqIdSubmodule = { } // use protocol relative urls for http or https - let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; + let url = `${gdprDetected ? GDPR_ENDPOINT : ENDPOINT}/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : ''; url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; @@ -382,9 +312,11 @@ export const intentIqIdSubmodule = { url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : ''; url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : ''; url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : ''; - url += cmpData.us_privacy ? '&pa=' + encodeURIComponent(cmpData.us_privacy) : ''; - url += cmpData.gpp ? '&gpp=' + encodeURIComponent(cmpData.gpp) : ''; - url += cmpData.gpi ? '&gpi=' + cmpData.gpi : ''; + url += cmpData.uspString ? '&us_privacy=' + encodeURIComponent(cmpData.uspString) : ''; + url += cmpData.gppString ? '&gpp=' + encodeURIComponent(cmpData.gppString) : ''; + url += cmpData.gdprApplies + ? '&gdpr_consent=' + encodeURIComponent(cmpData.gdprString) + '&gdpr=1' + : '&gdpr=0'; url += clientHints ? '&uh=' + encodeURIComponent(clientHints) : ''; url += VERSION ? '&jsver=' + VERSION : ''; url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : ''; @@ -394,8 +326,8 @@ export const intentIqIdSubmodule = { const storeFirstPartyData = () => { partnerData.eidl = runtimeEids?.eids?.length || -1 - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); - storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); } const resp = function (callback) { @@ -421,7 +353,7 @@ export const intentIqIdSubmodule = { partnerData.terminationCause = respJson.tc; if (respJson.tc == 41) { firstPartyData.group = WITHOUT_IIQ; - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); defineEmptyDataAndFireCallback(); if (gamObjectReference) setGamReporting(gamObjectReference, gamParameterName, firstPartyData.group); return @@ -435,9 +367,18 @@ export const intentIqIdSubmodule = { firstPartyData.isOptedOut = respJson.isOptedOut; } if (respJson.isOptedOut === true) { - firstPartyData.group = OPT_OUT; - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); - defineEmptyDataAndFireCallback() + respJson.data = partnerData.data = runtimeEids = { eids: [] }; + + const keysToRemove = [ + FIRST_PARTY_DATA_KEY, + CLIENT_HINTS_KEY + ]; + + keysToRemove.forEach(key => removeDataByKey(key, allowedStorage)); + + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage, firstPartyData); + firePartnerCallback(); + callback(runtimeEids); return } } @@ -497,7 +438,7 @@ export const intentIqIdSubmodule = { rrttStrtTime = Date.now(); partnerData.wsrvcll = true; - storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage, firstPartyData); ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); }; const respObj = {callback: resp}; diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md index 0103d403503..44e4032e23d 100644 --- a/modules/intentIqIdSystem.md +++ b/modules/intentIqIdSystem.md @@ -59,7 +59,7 @@ pbjs.setConfig({ browserBlackList: "chrome", callback: (data, group) => window.pbjs.requestBids(), manualWinReportEnabled: true, - domainName: "currentDomain.com" + domainName: "currentDomain.com", }, storage: { type: "html5", diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js index 5ba0686af0b..26a70ded14e 100644 --- a/test/spec/modules/intentIqAnalyticsAdapter_spec.js +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -11,10 +11,13 @@ import { REPORTER_ID, preparePayload } from '../../../modules/intentIqAnalyticsA import {FIRST_PARTY_KEY, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js'; import * as detectBrowserUtils from '../../../libraries/intentIqUtils/detectBrowserUtils.js'; import {getReferrer, appendVrrefAndFui} from '../../../libraries/intentIqUtils/getRefferer.js'; +import { gppDataHandler, uspDataHandler, gdprDataHandler } from '../../../src/consentHandler.js'; const partner = 10; const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; const version = VERSION; +const REPORT_ENDPOINT = 'https://reports.intentiq.com/report'; +const REPORT_ENDPOINT_GDPR = 'https://reports-gdpr.intentiq.com/report'; const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); @@ -124,13 +127,29 @@ describe('IntentIQ tests all', function () { expect(server.requests.length).to.be.above(0); const request = server.requests[0]; - expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); + expect(request.url).to.contain(REPORT_ENDPOINT + '?pid=' + partner + '&mct=1'); expect(request.url).to.contain(`&jsver=${version}`); expect(request.url).to.contain(`&vrref=${expectedVrref}`); expect(request.url).to.contain('&payload='); expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); }); + it('should send report to report-gdpr address if gdpr is detected', function () { + const gppStub = sinon.stub(gppDataHandler, 'getConsentData').returns({ gppString: '{"key1":"value1","key2":"value2"}' }); + const uspStub = sinon.stub(uspDataHandler, 'getConsentData').returns('1NYN'); + const gdprStub = sinon.stub(gdprDataHandler, 'getConsentData').returns({ consentString: 'gdprConsent' }); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + + expect(request.url).to.contain(REPORT_ENDPOINT_GDPR); + gppStub.restore(); + uspStub.restore(); + gdprStub.restore(); + }); + it('should initialize with default configurations', function () { expect(iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized).to.be.false; }); @@ -160,14 +179,38 @@ describe('IntentIQ tests all', function () { const dataToSend = preparePayload(wonRequest); const base64String = btoa(JSON.stringify(dataToSend)); const payload = `[%22${base64String}%22]`; - const expectedUrl = appendVrrefAndFui( - `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&source=pbjs&payload=${payload}&uh=`, - iiqAnalyticsAnalyticsAdapter.initOptions.domainName + const expectedUrl = appendVrrefAndFui(REPORT_ENDPOINT + + `?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&source=pbjs&payload=${payload}&uh=&gdpr=0`, iiqAnalyticsAnalyticsAdapter.initOptions.domainName ); expect(request.url).to.equal(expectedUrl); expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) }); + it('should send CMP data in report if available', function () { + const uspData = '1NYN'; + const gppData = { gppString: '{"key1":"value1","key2":"value2"}' }; + const gdprData = { consentString: 'gdprConsent' }; + + const gppStub = sinon.stub(gppDataHandler, 'getConsentData').returns(gppData); + const uspStub = sinon.stub(uspDataHandler, 'getConsentData').returns(uspData); + const gdprStub = sinon.stub(gdprDataHandler, 'getConsentData').returns(gdprData); + + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + + expect(request.url).to.contain(`&us_privacy=${encodeURIComponent(uspData)}`); + expect(request.url).to.contain(`&gpp=${encodeURIComponent(gppData.gppString)}`); + expect(request.url).to.contain(`&gdpr_consent=${encodeURIComponent(gdprData.consentString)}`); + expect(request.url).to.contain(`&gdpr=1`); + gppStub.restore(); + uspStub.restore(); + gdprStub.restore(); + }); + it('should not send request if manualWinReportEnabled is true', function () { iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; events.emit(EVENTS.BID_WON, wonRequest); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index 356ddcb6e2a..d4220710c1f 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -1,10 +1,9 @@ import { expect } from 'chai'; -import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; -import { decryptData, handleClientHints, readData, setGamReporting } from '../../../modules/intentIqIdSystem'; -import {getGppValue} from '../../../libraries/intentIqUtils/getGppValue.js'; -import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; +import { intentIqIdSubmodule, decryptData, handleClientHints, firstPartyData as moduleFPD } from '../../../modules/intentIqIdSystem'; +import {storage, readData} from '../../../libraries/intentIqUtils/storageUtils.js'; +import { gppDataHandler, uspDataHandler, gdprDataHandler } from '../../../src/consentHandler'; import { clearAllCookies } from '../../helpers/cookies'; import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils'; import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, NOT_YET_DEFINED, WITH_IIQ} from '../../../libraries/intentIqConstants/intentIqConstants.js'; @@ -387,42 +386,6 @@ describe('IntentIQ tests', function () { expect(logErrorStub.called).to.be.true; }); - describe('getGppValue', function () { - const testCases = [ - { - description: 'should return gppString and gpi=0 when GPP data exists', - input: { gppString: '{"key1":"value1","key2":"value2"}' }, - expectedOutput: { gppString: '{"key1":"value1","key2":"value2"}', gpi: 0 } - }, - { - description: 'should return empty gppString and gpi=1 when GPP data does not exist', - input: null, - expectedOutput: { gppString: '', gpi: 1 } - }, - { - description: 'should return empty gppString and gpi=1 when gppString is not set', - input: {}, - expectedOutput: { gppString: '', gpi: 1 } - }, - { - description: 'should handle GPP data with empty string', - input: { gppString: '' }, - expectedOutput: { gppString: '', gpi: 1 } - } - ]; - - testCases.forEach(({ description, input, expectedOutput }) => { - it(description, function () { - sinon.stub(gppDataHandler, 'getConsentData').returns(input); - - const result = getGppValue(); - expect(result).to.deep.equal(expectedOutput); - - gppDataHandler.getConsentData.restore(); - }); - }); - }); - describe('detectBrowserFromUserAgent', function () { it('should detect Chrome browser', function () { const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'; @@ -480,84 +443,150 @@ describe('IntentIQ tests', function () { describe('IntentIQ consent management within getId', function () { let uspDataHandlerStub; let gppDataHandlerStub; + let gdprDataHandlerStub; + let uspData; + let gppData; + let gdprData; + + function mockConsentHandlers(usp, gpp, gdpr) { + uspDataHandlerStub.returns(usp); + gppDataHandlerStub.returns(gpp); + gdprDataHandlerStub.returns(gdpr); + } beforeEach(function () { localStorage.clear(); - const expiredDate = new Date(0).toUTCString(); - storage.setCookie(FIRST_PARTY_KEY, '', expiredDate, 'Lax'); - storage.setCookie(FIRST_PARTY_KEY + '_' + partner, '', expiredDate, 'Lax'); uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); gppDataHandlerStub = sinon.stub(gppDataHandler, 'getConsentData'); + gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + // Initialize data here so it can be reused in all tests + uspData = '1NYN'; + gppData = { gppString: '{"key1":"value1","key2":"value2"}' }; + gdprData = { gdprApplies: true, consentString: 'gdprConsent' }; }); afterEach(function () { uspDataHandlerStub.restore(); gppDataHandlerStub.restore(); + gdprDataHandlerStub.restore(); }); - it('should set cmpData.us_privacy if uspData exists', function () { - const uspData = '1NYN'; - uspDataHandlerStub.returns(uspData); + it('should set isOptOut to true for new users if GDPR is detected and update it upon receiving a server response', function () { + localStorage.clear(); + mockConsentHandlers(uspData, gppData, gdprData); let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; submoduleCallback(callBackSpy); + + let lsBeforeReq = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + let request = server.requests[0]; - expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); request.respond( 200, responseHeader, - JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: false }) + JSON.stringify({ isOptedOut: false }) ); - expect(callBackSpy.calledOnce).to.be.true; - // Check the local storage directly to see if cmpData.us_privacy was set correctly - const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); - expect(firstPartyData.uspapi_value).to.equal(uspData); + let updatedFirstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + + expect(lsBeforeReq).to.not.be.null; + expect(lsBeforeReq.isOptedOut).to.be.true; + expect(callBackSpy.calledOnce).to.be.true; + expect(updatedFirstPartyData).to.not.be.undefined; + expect(updatedFirstPartyData.isOptedOut).to.equal(false); }); - it('should create a request with gpp data if gppData exists and has gppString', function () { - const mockGppValue = { - gppString: '{"key1":"value1","key2":"value2"}', - gpi: 0 - }; + it('should save cmpData parameters in LS data and used it request if uspData, gppData, gdprData exists', function () { + mockConsentHandlers(uspData, gppData, gdprData); - const mockConfig = { - params: { partner: partner }, - enabledStorageTypes: ['localStorage'] - }; + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + const data = {eids: {key1: 'value1', key2: 'value2'}} - gppDataHandlerStub.returns(mockGppValue); + submoduleCallback(callBackSpy); + let request = server.requests[0]; + request.respond( + 200, + responseHeader, + JSON.stringify({ ...data, isOptedOut: false }) + ); + + expect(request.url).to.contain(`&gpp=${encodeURIComponent(gppData.gppString)}`); + expect(request.url).to.contain(`&us_privacy=${encodeURIComponent(uspData)}`); + expect(request.url).to.contain(`&gdpr_consent=${encodeURIComponent(gdprData.consentString)}`); + + const lsFirstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(lsFirstPartyData.uspString).to.equal(uspData); + expect(lsFirstPartyData.gppString).to.equal(gppData.gppString); + expect(lsFirstPartyData.gdprString).to.equal(gdprData.consentString); + + expect(moduleFPD.uspString).to.equal(uspData); + expect(moduleFPD.gppString).to.equal(gppData.gppString); + expect(moduleFPD.gdprString).to.equal(gdprData.consentString); + }); + + it('should clear localStorage, update runtimeEids and trigger callback with empty data if isOptedOut is true in response', function () { + // Save some data to localStorage for FPD and CLIENT_HINTS + const FIRST_PARTY_DATA_KEY = FIRST_PARTY_KEY + '_' + partner; + localStorage.setItem(FIRST_PARTY_DATA_KEY, JSON.stringify({terminationCause: 35, some_key: 'someValue'})); + localStorage.setItem(CLIENT_HINTS_KEY, JSON.stringify({ hint: 'someClientHintData' })); + + mockConsentHandlers(uspData, gppData, gdprData); let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(mockConfig).callback; + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); let request = server.requests[0]; - request.respond( 200, responseHeader, - JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) + JSON.stringify({isOptedOut: true}) ); - expect(request.url).to.contain(`&gpp=${encodeURIComponent(mockGppValue.gppString)}`); + // Check that the URL contains the expected consent data + expect(request.url).to.contain(`&gpp=${encodeURIComponent(gppData.gppString)}`); + expect(request.url).to.contain(`&us_privacy=${encodeURIComponent(uspData)}`); + expect(request.url).to.contain(`&gdpr_consent=${encodeURIComponent(gdprData.consentString)}`); + + const lsFirstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + + // Ensure that keys are removed if isOptedOut is true + expect(localStorage.getItem(FIRST_PARTY_DATA_KEY)).to.be.null; + expect(localStorage.getItem(CLIENT_HINTS_KEY)).to.be.null; + + expect(lsFirstPartyData.isOptedOut).to.equal(true); expect(callBackSpy.calledOnce).to.be.true; + // Get the parameter with which the callback was called + const callbackArgument = callBackSpy.args[0][0]; // The first argument from the callback call (runtimeEids) + expect(callbackArgument).to.deep.equal({ eids: [] }); // Ensure that runtimeEids was updated to { eids: [] } + }); - const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); - expect(firstPartyData.gpp_string_value).to.equal(mockGppValue.gppString); + it('should make request to correct address api-gdpr.intentiq.com if gdpr is detected', function() { + const ENDPOINT_GDPR = 'https://api-gdpr.intentiq.com'; + mockConsentHandlers(uspData, gppData, gdprData); + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId({...defaultConfigParams}).callback; + + submoduleCallback(callBackSpy); + let request = server.requests[0]; + + expect(request.url).to.contain(ENDPOINT_GDPR); }); }); - it('should get and save client hints to storge', async () => { - // Client hints are async function, thats why async/await is using + it('should get and save client hints to storage', async () => { localStorage.clear(); Object.defineProperty(navigator, 'userAgentData', { value: { getHighEntropyValues: async () => testClientHints }, configurable: true }); await intentIqIdSubmodule.getId(defaultConfigParams); - const savedClientHints = readData(CLIENT_HINTS_KEY, ['html5']); - expect(savedClientHints).to.equal(handleClientHints(testClientHints)); + + const savedClientHints = readData(CLIENT_HINTS_KEY, ['html5'], storage); + const expectedClientHints = handleClientHints(testClientHints); + expect(savedClientHints).to.equal(expectedClientHints); }); it('should run callback from params', async () => { From 7b7506e5b7898454478dbb16ac7db6dda7517da1 Mon Sep 17 00:00:00 2001 From: Pete Date: Sun, 2 Mar 2025 23:40:34 +0200 Subject: [PATCH 0968/1097] fix(): ExcoAdapter Unit tests according to latest ortb converter changes (#12823) --- test/spec/modules/excoBidAdapter_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spec/modules/excoBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js index f5703adcae6..904ca67f19a 100644 --- a/test/spec/modules/excoBidAdapter_spec.js +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -178,6 +178,7 @@ describe('ExcoBidAdapter', function () { creativeId: 'h6bvt3rl', creative_id: 'h6bvt3rl', ttl: 3000, + eventtrackers: [], meta: { advertiserDomains: [ 'crest.com' From 1f01ea45820c7eeeacfced3f0483fe01d753754d Mon Sep 17 00:00:00 2001 From: vdo-ai-tech <126867429+vdo-ai-tech@users.noreply.github.com> Date: Mon, 3 Mar 2025 11:51:31 +0530 Subject: [PATCH 0969/1097] migratin vdo.ai ad server (#12713) Co-authored-by: rishabhsehrawat1 --- modules/vdoaiBidAdapter.js | 348 +++---- modules/vdoaiBidAdapter.md | 89 +- test/spec/modules/vdoaiBidAdapter_spec.js | 1061 ++++++++++++++------- 3 files changed, 879 insertions(+), 619 deletions(-) diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index 8fda9cba593..6ef5b6ebb3e 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,8 +1,7 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; -import { deepClone, logError, deepAccess } from '../src/utils.js'; -import { config } from '../src/config.js'; +import { logMessage, groupBy, flatten, uniques } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -11,256 +10,171 @@ import { config } from '../src/config.js'; */ const BIDDER_CODE = 'vdoai'; -const ENDPOINT_URL = 'https://prebid-v2.vdo.ai/auction'; - -function getFrameNesting() { - let topmostFrame = window; - let parent = window.parent; - try { - while (topmostFrame !== topmostFrame.parent) { - parent = topmostFrame.parent; - // eslint-disable-next-line no-unused-expressions - parent.location.href; - topmostFrame = topmostFrame.parent; - } - } catch (e) { } - return topmostFrame; -} /** - * Returns information about the page needed by the server in an object to be converted in JSON + * Determines whether or not the given bid response is valid. * - * @returns {{location: *, referrer: (*|string), stack: (*|Array.), numIframes: (*|Number), wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} + * @param {object} vdoresponse The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. */ -function getPageInfo(bidderRequest) { - const topmostFrame = getFrameNesting(); - return { - referrer: deepAccess(bidderRequest, 'refererInfo.ref', null), - stack: deepAccess(bidderRequest, 'refererInfo.stack', []), - numIframes: deepAccess(bidderRequest, 'refererInfo.numIframes', 0), - wWidth: topmostFrame.innerWidth, - location: deepAccess(bidderRequest, 'refererInfo.page', null), - wHeight: topmostFrame.innerHeight, - aWidth: topmostFrame.screen.availWidth, - aHeight: topmostFrame.screen.availHeight, - oWidth: topmostFrame.outerWidth, - oHeight: topmostFrame.outerHeight, - sWidth: topmostFrame.screen.width, - sHeight: topmostFrame.screen.height, - sLeft: 'screenLeft' in topmostFrame ? topmostFrame.screenLeft : topmostFrame.screenX, - sTop: 'screenTop' in topmostFrame ? topmostFrame.screenTop : topmostFrame.screenY, - xOffset: topmostFrame.pageXOffset, - docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, - hLength: history.length, - yOffset: topmostFrame.pageYOffset, - version: { - prebid_version: '$prebid.version$', - adapter_version: '1.0.0', - vendor: '$$PREBID_GLOBAL$$', - } - }; -} - -export function isSchainValid(schain) { - let isValid = false; - const requiredFields = ['asi', 'sid', 'hp']; - if (!schain || !schain.nodes) return isValid; - isValid = schain.nodes.reduce((status, node) => { - if (!status) return status; - return requiredFields.every(field => node.hasOwnProperty(field)); - }, true); - if (!isValid) { - logError('VDO.AI: required schain params missing'); +function vdoIsBidResponseValid(vdoresponse) { + if (!vdoresponse.requestId || !vdoresponse.cpm || !vdoresponse.creativeId || !vdoresponse.ttl || !vdoresponse.currency || !vdoresponse.meta.advertiserDomains) { + return false; } - return isValid; -} - -function parseVideoSize(bid) { - const playerSize = bid.mediaTypes.video.playerSize; - if (typeof playerSize !== 'undefined' && Array.isArray(playerSize) && playerSize.length > 0) { - return getSizes(playerSize) - } - return []; -} - -function getSizes(sizes) { - const ret = []; - for (let i = 0; i < sizes.length; i++) { - const size = sizes[i]; - ret.push({ width: size[0], height: size[1] }) + switch (vdoresponse.meta.mediaType) { + case BANNER: + return Boolean(vdoresponse.width && vdoresponse.height && vdoresponse.ad); + case VIDEO: + return Boolean(vdoresponse.vastXml || vdoresponse.vastUrl); } - return ret; + return false; } export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * - * @param {BidRequest} bid The bid params to validate. + * @param {BidRequest} vdobid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function (bid) { - return !!(bid.params.placementId) && typeof bid.params.placementId === 'string'; + isBidRequestValid: (vdobid) => { + logMessage('vdobid', vdobid); + return Boolean(vdobid.bidId && vdobid.params && vdobid.params.host && vdobid.params.adUnitType && + (vdobid.params.adUnitId || vdobid.params.adUnitId === 0)); }, /** * Make a server request from the list of BidRequests. * - * @return Array Info describing the request to the server. - * @param validBidRequests - * @param bidderRequest + * @return ServerRequest Info describing the request to the server. */ - - buildRequests: function (validBidRequests, bidderRequest) { - if (validBidRequests.length === 0) { - return []; + buildRequests: (vdoValidBidRequests, bidderRequest) => { + let winTop; + try { + winTop = window.top; + winTop.location.toString(); + } catch (e) { + logMessage(e); + winTop = window; } - return validBidRequests.map(bidRequest => { - const sizes = getAdUnitSizes(bidRequest); - let payload = { - placementId: bidRequest.params.placementId, - sizes: sizes, - bidId: bidRequest.bidId, - mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner', - domain: bidderRequest.ortb2.site.domain, - publisherDomain: bidderRequest.ortb2.site.publisher.domain, - adUnitCode: bidRequest.adUnitCode, - bidder: bidRequest.bidder, - tmax: bidderRequest.timeout - }; - - payload.bidderRequestId = bidRequest.bidderRequestId; - payload.auctionId = deepAccess(bidRequest, 'ortb2.source.tid'); - payload.transactionId = deepAccess(bidRequest, 'ortb2Imp.ext.tid'); - payload.gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); - payload.ortb2Imp = deepAccess(bidRequest, 'ortb2Imp'); - - if (payload.mediaType === 'video') { - payload.context = bidRequest.mediaTypes.video.context; - payload.playerSize = parseVideoSize(bidRequest); - payload.mediaTypeInfo = deepClone(bidRequest.mediaTypes.video); - } - - if (typeof bidRequest.getFloor === 'function') { - let floor = bidRequest.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (floor && floor.floor && floor.currency === 'USD') { - payload.bidFloor = floor.floor; - } - } else if (bidRequest.params.bidFloor) { - payload.bidFloor = bidRequest.params.bidFloor; - } - - payload.pageInfo = getPageInfo(bidderRequest); + const placements = groupBy(vdoValidBidRequests.map(bidRequest => vdoBuildPlacement(bidRequest)), 'host') + return Object.keys(placements) + .map(host => vdoBuildRequest(winTop, host, placements[host].map(placement => placement.adUnit), bidderRequest)); + }, - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdprConsent = { - consentRequired: bidderRequest.gdprConsent.gdprApplies, - consentString: bidderRequest.gdprConsent.consentString, - addtlConsent: bidderRequest.gdprConsent.addtlConsent - }; - } - if (bidderRequest && bidderRequest.gppConsent) { - payload.gppConsent = { - applicableSections: bidderRequest.gppConsent.applicableSections, - consentString: bidderRequest.gppConsent.gppString, - } - } - if (bidderRequest && bidderRequest.ortb2) { - payload.ortb2 = bidderRequest.ortb2; - } - if (bidderRequest && bidderRequest.uspConsent) { - payload.usPrivacy = bidderRequest.uspConsent; - } - if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].schain && isSchainValid(validBidRequests[0].schain)) { - payload.schain = validBidRequests[0].schain; - } - if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userIdAsEids) { - payload.userId = validBidRequests[0].userIdAsEids; - } - let coppaOrtb2 = !!deepAccess(bidderRequest, 'ortb2.regs.coppa'); - let coppaConfig = config.getConfig('coppa'); - if (coppaOrtb2 === true || coppaConfig === true) { - payload.coppa = true; - } - return { - method: 'POST', - url: ENDPOINT_URL, - data: payload - }; - }); + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} vdobid The bid that won the auction + */ + onBidWon: (vdobid) => { + const cpm = vdobid.pbMg; + if (vdobid.nurl !== '') { + vdobid.nurl = vdobid.nurl.replace( + /\$\{AUCTION_PRICE\}/, + cpm + ); + ajax(vdobid.nurl, null); + } }, /** * Unpack the response from the server into a list of bids. * - * @param {ServerResponse} serverResponse A successful response from the server. - * @param bidRequest + * @param {ServerResponse} vdoServerResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, bidRequest) { + interpretResponse: (vdoServerResponse, vdoBidRequest) => { const bidResponses = []; - const response = serverResponse.body; - const creativeId = response.adid || 0; - const width = response.w; - const height = response.h; - const cpm = response.price || 0; - - const adCreative = response.vdoCreative; - - if (width !== 0 && height !== 0 && cpm !== 0 && creativeId !== 0) { - const currency = response.cur || 'USD'; - const netRevenue = true; - const bidResponse = { - requestId: response.bidId, - cpm: cpm, - width: width, - height: height, - creativeId: creativeId, - currency: currency, - netRevenue: netRevenue, - ttl: 60, - mediaType: response.mediaType - }; - - if (response.mediaType == 'video') { - bidResponse.vastXml = adCreative; - } else { - bidResponse.ad = adCreative; + const serverBody = vdoServerResponse.body; + const len = serverBody.length; + for (let i = 0; i < len; i++) { + const bidResponse = serverBody[i]; + if (vdoIsBidResponseValid(bidResponse)) { + bidResponses.push(bidResponse); } - if (response.adomain) { - bidResponse.meta = { - advertiserDomains: response.adomain - }; - } - bidResponses.push(bidResponse); } - return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponse) { - let syncUrls = serverResponse[0] && serverResponse[0].body && serverResponse[0].body.cookiesync && serverResponse[0].body.cookiesync.bidder_status; + getUserSyncs: (userSyncOptions, vdoServerResponses, userGdprConsent, UserUspConsent) => { + const allIframeSyncs = []; + const allImageSyncs = []; + for (let i = 0; i < vdoServerResponses.length; i++) { + const serverResponseHeaders = vdoServerResponses[i].headers; + const vdoImgSync = (serverResponseHeaders != null && userSyncOptions.pixelEnabled) ? serverResponseHeaders.get('X-PLL-UserSync-Image') : null + const vdoIframeSync = (serverResponseHeaders != null && userSyncOptions.iframeEnabled) ? serverResponseHeaders.get('X-PLL-UserSync-Iframe') : null + if (vdoIframeSync != null) { + allIframeSyncs.push(vdoIframeSync) + } else if (vdoImgSync != null) { + allImageSyncs.push(vdoImgSync) + } + } + return [allIframeSyncs.filter(uniques).map(it => { return { type: 'iframe', url: it } }), + allImageSyncs.filter(uniques).map(it => { return { type: 'image', url: it } })].reduce(flatten, []).filter(uniques); + } +}; + +registerBidder(spec); + +function vdoBuildRequest(windowTop, hostname, vdoAdUnits, bidderRequest) { + return { + url: `https://${hostname}/hb`, + method: 'POST', + data: { + secure: (location.protocol === 'https:'), + deviceWidth: windowTop.screen.width, + deviceHeight: windowTop.screen.height, + adUnits: vdoAdUnits, + ortb2: bidderRequest?.ortb2, + refererInfo: bidderRequest?.refererInfo, + sua: bidderRequest?.ortb2?.device?.sua, + page: bidderRequest?.ortb2?.site?.page || bidderRequest?.refererInfo?.page + } + } +} - if (syncOptions.iframeEnabled && syncUrls && syncUrls.length > 0) { - let prebidSyncUrls = syncUrls.map(syncObj => { +function vdoBuildPlacement(vdoBidRequest) { + let sizes; + if (vdoBidRequest.mediaTypes) { + switch (vdoBidRequest.params.adUnitType) { + case BANNER: + if (vdoBidRequest.mediaTypes.banner && vdoBidRequest.mediaTypes.banner.sizes) { + sizes = vdoBidRequest.mediaTypes.banner.sizes; + } + break; + case VIDEO: + if (vdoBidRequest.mediaTypes.video && vdoBidRequest.mediaTypes.video.playerSize) { + sizes = [vdoBidRequest.mediaTypes.video.playerSize]; + } + break; + } + } + sizes = (sizes || []).concat(vdoBidRequest.sizes || []); + return { + host: vdoBidRequest.params.host, + adUnit: { + id: vdoBidRequest.params.adUnitId, + bidId: vdoBidRequest.bidId, + transactionId: vdoBidRequest.ortb2Imp?.ext?.tid, + sizes: sizes.map(size => { return { - url: syncObj.usersync.url, - type: 'iframe' + width: size[0], + height: size[1] } - }) - return prebidSyncUrls; + }), + type: vdoBidRequest.params.adUnitType.toUpperCase(), + ortb2Imp: vdoBidRequest.ortb2Imp, + publisherId: vdoBidRequest.params.publisherId, + userIdAsEids: vdoBidRequest.userIdAsEids, + supplyChain: vdoBidRequest.schain, + custom1: vdoBidRequest.params.custom1, + custom2: vdoBidRequest.params.custom2, + custom3: vdoBidRequest.params.custom3, + custom4: vdoBidRequest.params.custom4, + custom5: vdoBidRequest.params.custom5 } - return []; - }, - - onTimeout: function(data) {}, - onBidWon: function(bid) {}, - onSetTargeting: function(bid) {} -}; -registerBidder(spec); + } +} diff --git a/modules/vdoaiBidAdapter.md b/modules/vdoaiBidAdapter.md index 04200cc9be0..a1f856b651a 100644 --- a/modules/vdoaiBidAdapter.md +++ b/modules/vdoaiBidAdapter.md @@ -10,48 +10,61 @@ Maintainer: arjit@z1media.com Module that connects to VDO.AI's demand sources -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250]] // a display size - } - }, - bids: [ - { - bidder: "vdoai", - params: { - placementId: 'newsdv77', - bidFloor: 0.01 // Optional - } - } - ] +# Test Parameters for banner +``` +var adUnits = [{ + code: 'placementCode', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'vdoai', + params: { + host: 'exchange-9qao.ortb.net', + adUnitId: 0, + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } - ]; + }] +}]; ``` - -# Video Test Parameters +# Test Parameters for video ``` -var videoAdUnit = { - code: 'test-div', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - }, - }, - bids: [ - { - bidder: "vdoai", +var videoAdUnit = [{ + code: 'video1', + sizes: [[300, 250]], + bids: [{ + bidder: 'vdoai', params: { - placementId: 'newsdv77' + host: 'exchange-9qao.ortb.net', + adUnitId: 0, + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } + }] +}]; +``` + +# Configuration + +The VDO.AI Bidder Adapter expects Prebid Cache(for video) to be enabled so that we can store and retrieve a single vastXml. + +``` +pbjs.setConfig({ + usePrebidCache: true, + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' } - ] -}; -``` \ No newline at end of file +}); +``` diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index e7f153fa237..1cd361730a9 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -1,411 +1,744 @@ -import {assert, expect} from 'chai'; -import {spec} from 'modules/vdoaiBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import { config } from 'src/config.js'; - -const ENDPOINT_URL = 'https://prebid-v2.vdo.ai/auction'; +import { expect } from 'chai'; +import { spec } from '../../../modules/vdoaiBidAdapter.js'; describe('vdoaiBidAdapter', function () { - const adapter = newBidder(spec); - const sandbox = sinon.sandbox.create(); - beforeEach(function() { - sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); - }); - afterEach(function() { - sandbox.restore(); - }); - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'vdoai', - 'params': { - placementId: 'testPlacementId' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - let invalidParams = { - 'bidder': 'vdoai', - 'params': { - placementId: false - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - it('should return false where required params not found', function () { - expect(spec.isBidRequestValid(invalidParams)).to.equal(false); - }); - }); - describe('buildRequests', function () { - let bidRequests = [ + const bid1 = { + bidId: '2dd581a2b6281d', + bidder: 'vdoai', + bidderRequestId: '145e1d6a7837c9', + params: { + host: 'exchange.ortb.net', + adUnitId: 123, + adUnitType: 'banner', + publisherId: 'perfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + ortb2Imp: { + ext: { + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } + } + }, + userIdAsEids: [ { - 'bidder': 'vdoai', - 'params': { - placementId: 'testPlacementId', - bidFloor: 0.1 - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - 'mediaType': 'banner', - 'adUnitCode': '1234', - 'mediaTypes': { - banner: { - sizes: [300, 250] + source: 'test1.org', + uids: [ + { + id: '123', } + ] + } + ], + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '1', + hp: 1 } - }, + ] + } + } + const bid2 = { + bidId: '58ee9870c3164a', + bidder: 'vdoai', + bidderRequestId: '209fdaf1c81649', + params: { + host: 'ads.vdo.ai', + adUnitId: 456, + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' + }, + placementCode: 'placement_1', + auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', + sizes: [[350, 200]], + ortb2Imp: { + ext: { + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } + } + }, + userIdAsEids: [ { - 'bidder': 'vdoai', - 'params': { - placementId: 'testPlacementId', - bidFloor: 0.1 - }, - 'width': '300', - 'height': '200', - 'bidId': 'bidId123', - 'referer': 'www.example.com', - 'mediaType': 'video', - 'mediaTypes': { - video: { - context: 'instream', - playerSize: [[640, 360]] + source: 'test2.org', + uids: [ + { + id: '234', } - } + ] } - ]; - - let bidderRequests = { - timeout: 3000, - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://example.com', - 'stack': ['https://example.com'], - 'page': 'example.com', - 'ref': 'example2.com' - }, - 'ortb2': { - source: { - tid: 123456789 + ], + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '1', + hp: 1 }, - 'site': { - 'domain': 'abc.com', - 'publisher': { - 'domain': 'abc.com' + { + asi: 'example1.com', + sid: '2', + hp: 1 + } + ] + } + } + const bid3 = { + bidId: '019645c7d69460', + bidder: 'vdoai', + bidderRequestId: 'f2b15f89e77ba6', + params: { + host: 'exchange.ortb.net', + adUnitId: 789, + adUnitType: 'video', + publisherId: 'secondPerfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' + }, + placementCode: 'placement_2', + auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', + sizes: [[800, 600]], + ortb2Imp: { + ext: { + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } + } + }, + userIdAsEids: [ + { + source: 'test3.org', + uids: [ + { + id: '345', + }, + { + id: '456', } + ] + } + ], + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '1', + hp: 1 } - }, - 'ortb2Imp': { - ext: { - tid: '12345', - gpid: '1234' + ] + } + } + const bid4 = { + bidId: '019645c7d69460', + bidder: 'vdoai', + bidderRequestId: 'f2b15f89e77ba6', + params: { + host: 'exchange.ortb.net', + adUnitId: 789, + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' + }, + placementCode: 'placement_2', + auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', + video: { + playerSize: [800, 600] + }, + ortb2Imp: { + ext: { + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' } - }, - gdprConsent: { - consentString: 'abc', - gdprApplies: true, - addtlConsent: 'xyz' - }, - gppConsent: { - gppString: 'abcd', - applicableSections: '' - }, - uspConsent: { - uspConsent: '12345' - }, - userIdAsEids: {}, - schain: { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + } + }, + userIdAsEids: [ + { + source: 'test.org', + uids: [ { - 'asi': 'vdo.ai', - 'sid': '4359', - 'hp': 1 + id: '111', } ] } - }; + ], + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '1', + hp: 1 + } + ] + } + } - const request = spec.buildRequests(bidRequests, bidderRequests); - it('sends bid request to our endpoint via POST', function () { - expect(request[0].method).to.equal('POST'); - }); - it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.equal(ENDPOINT_URL); - }); - it('should contain all keys', function() { - expect(request[0].data.pageInfo).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'xOffset', 'yOffset', 'version'); + describe('buildRequests', function () { + const bidderRequest = { + ortb2: { + device: { + sua: { + browsers: [], + platform: [], + mobile: 1, + architecture: 'arm' + } + } + }, + refererInfo: { + page: 'testPage' + } + } + const serverRequests = spec.buildRequests([bid1, bid2, bid3, bid4], bidderRequest) + it('Creates two ServerRequests', function() { + expect(serverRequests).to.exist + expect(serverRequests).to.have.lengthOf(2) }) - it('should return empty array if no valid bid was passed', function () { - expect(spec.buildRequests([], bidderRequests)).to.be.empty; - }); - it('should not send invalid schain', function () { - delete bidderRequests.schain.nodes[0].asi; - let result = spec.buildRequests(bidRequests, bidderRequests); - expect(result[0].data.schain).to.be.undefined; - }); - it('should not send invalid schain', function () { - delete bidderRequests.schain; - let result = spec.buildRequests(bidRequests, bidderRequests); - expect(result[0].data.schain).to.be.undefined; + serverRequests.forEach(serverRequest => { + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist + expect(serverRequest.method).to.exist + expect(serverRequest.url).to.exist + expect(serverRequest.data).to.exist + }) + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST') + }) + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'secure', + 'adUnits', + 'sua', + 'page', + 'ortb2', + 'refererInfo' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.secure).to.be.a('boolean'); + data.adUnits.forEach(adUnit => { + expect(adUnit).to.have.all.keys( + 'id', + 'bidId', + 'type', + 'sizes', + 'transactionId', + 'publisherId', + 'userIdAsEids', + 'supplyChain', + 'custom1', + 'custom2', + 'custom3', + 'custom4', + 'custom5', + 'ortb2Imp' + ); + expect(adUnit.id).to.be.a('number'); + expect(adUnit.bidId).to.be.a('string'); + expect(adUnit.type).to.be.a('string'); + expect(adUnit.transactionId).to.be.a('string'); + expect(adUnit.sizes).to.be.an('array'); + expect(adUnit.userIdAsEids).to.be.an('array'); + expect(adUnit.supplyChain).to.be.an('object'); + expect(adUnit.custom1).to.be.a('string'); + expect(adUnit.custom2).to.be.a('string'); + expect(adUnit.custom3).to.be.a('string'); + expect(adUnit.custom4).to.be.a('string'); + expect(adUnit.custom5).to.be.a('string'); + expect(adUnit.ortb2Imp).to.be.an('object'); + }) + expect(data.sua.browsers).to.be.a('array'); + expect(data.sua.platform).to.be.a('array'); + expect(data.sua.mobile).to.be.a('number'); + expect(data.sua.architecture).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.page).to.be.equal('testPage'); + expect(data.ortb2).to.be.an('object'); + }) + }) + it('Returns valid URL', function () { + expect(serverRequests[0].url).to.equal('https://exchange.ortb.net/hb') + expect(serverRequests[1].url).to.equal('https://ads.vdo.ai/hb') + }) + it('Returns valid adUnits', function () { + validateAdUnit(serverRequests[0].data.adUnits[0], bid1) + validateAdUnit(serverRequests[1].data.adUnits[0], bid2) + validateAdUnit(serverRequests[0].data.adUnits[1], bid3) + }) + it('Returns empty data if no valid requests are passed', function () { + const serverRequests = spec.buildRequests([]) + expect(serverRequests).to.be.an('array').that.is.empty + }) + it('Returns request with page field value from ortb2 object if ortb2 has page field', function () { + bidderRequest.ortb2.site = { + page: 'testSitePage' + } + const serverRequests = spec.buildRequests([bid1], bidderRequest) + expect(serverRequests).to.have.lengthOf(1) + serverRequests.forEach(serverRequest => { + expect(serverRequest.data.page).to.be.a('string'); + expect(serverRequest.data.page).to.be.equal('testSitePage'); + }) + }) + }) + describe('interpretBannerResponse', function () { + let resObject = { + body: [ { + requestId: '123', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'banner' + } + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'meta'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.meta.advertiserDomains).to.be.an('array'); + expect(dataItem.meta.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); }); - it('should check for correct sizes', function () { - delete bidRequests[1].mediaTypes.video.playerSize; - let result = spec.buildRequests(bidRequests, bidderRequests); - expect(result[1].data.playerSize).to.be.empty; + }); + describe('interpretVideoResponse', function () { + let resObject = { + body: [ { + requestId: '123', + cpm: 0.3, + width: 320, + height: 50, + vastXml: '', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'video' + } + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'meta'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.vastXml).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.meta.advertiserDomains).to.be.an('array'); + expect(dataItem.meta.mediaType).to.be.a('string'); + } + it('should return an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); }); - it('should not pass undefined in GDPR string', function () { - delete bidderRequests.gdprConsent; - let result = spec.buildRequests(bidRequests, bidderRequests); - expect(result[0].data.gdprConsent).to.be.undefined; + }); + describe('isBidRequestValid', function() { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'vdoai', + bidderRequestId: '145e1d6a7837c9', + params: { + host: 'exchange.ortb.net', + adUnitId: 123, + adUnitType: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + it('should return true when required params found', function() { + [bid, bid1, bid2, bid3].forEach(bid => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); }); - it('should not pass undefined in gppConsent', function () { - delete bidderRequests.gppConsent; - let result = spec.buildRequests(bidRequests, bidderRequests); - expect(result[0].data.gppConsent).to.be.undefined; + it('should return true when adUnitId is zero', function() { + bid.params.adUnitId = 0; + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should not pass undefined in uspConsent', function () { - delete bidderRequests.uspConsent; - let result = spec.buildRequests(bidRequests, bidderRequests); - expect(result[0].data.uspConsent).to.be.undefined; + it('should return false when required params are not passed', function() { + let bidFailed = { + bidder: 'vdoai', + bidderRequestId: '145e1d6a7837c9', + params: { + adUnitId: 123, + adUnitType: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + expect(spec.isBidRequestValid(bidFailed)).to.equal(false); }); }); - - describe('interpretResponse', function () { - let bidRequest = [ - { - 'method': 'POST', - 'url': ENDPOINT_URL, - 'data': { - 'placementId': 'testPlacementId', - 'width': '300', - 'height': '200', - 'bidId': 'bidId123', - 'referer': 'www.example.com' - } - - } - ]; - let serverResponse = { - body: - { - 'price': 2, - 'adid': 'test-ad', - 'adomain': [ - 'text.abc' - ], - 'w': 300, - 'h': 250, - 'vdoCreative': '

I am an ad

', - 'bidId': '31d1375caab87a', - 'mediaType': 'banner' + describe('interpretResponse', function() { + let resObject = { + requestId: '123', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'banner' } }; - - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '31d1375caab87a', - 'cpm': 2, - 'width': 300, - 'height': 250, - 'creativeId': 'test-ad', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 60, - 'ad': '

I am an ad

', - 'meta': { - 'advertiserDomains': ['text.abc'] - } - }]; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); - expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); + it('should skip responses which do not contain required params', function() { + let bidResponses = { + body: [ { + cpm: 0.3, + ttl: 1000, + currency: 'USD', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'banner' + } + }, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); }); - - it('handles instream video responses', function () { - let serverResponse = { - body: { - 'vdoCreative': '', - 'price': 2, - 'adid': '12345asdfg', - 'currency': 'USD', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true, - 'mediaType': 'video' + it('should skip responses which do not contain advertiser domains', function() { + let resObjectWithoutAdvertiserDomains = Object.assign({}, resObject); + resObjectWithoutAdvertiserDomains.meta = Object.assign({}, resObject.meta); + delete resObjectWithoutAdvertiserDomains.meta.advertiserDomains; + let bidResponses = { + body: [ resObjectWithoutAdvertiserDomains, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); + }); + it('should return responses which contain empty advertiser domains', function() { + let resObjectWithEmptyAdvertiserDomains = Object.assign({}, resObject); + resObjectWithEmptyAdvertiserDomains.meta = Object.assign({}, resObject.meta); + resObjectWithEmptyAdvertiserDomains.meta.advertiserDomains = []; + let bidResponses = { + body: [ resObjectWithEmptyAdvertiserDomains, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([resObjectWithEmptyAdvertiserDomains, resObject]); + }); + it('should skip responses which do not contain meta media type', function() { + let resObjectWithoutMetaMediaType = Object.assign({}, resObject); + resObjectWithoutMetaMediaType.meta = Object.assign({}, resObject.meta); + delete resObjectWithoutMetaMediaType.meta.mediaType; + let bidResponses = { + body: [ resObjectWithoutMetaMediaType, resObject ] + } + expect(spec.interpretResponse(bidResponses)).to.deep.equal([ resObject ]); + }); + }); + describe('getUserSyncs', function () { + it('should return trackers for lm(only iframe) if server responses contain lm user sync header and iframe and image enabled', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] } + ]; + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true }; - let bidRequest = [ + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ + { + type: 'iframe', + url: 'https://tracker-lm.ortb.net/sync.html' + } + ]); + }); + it('should return empty array if all sync types are disabled', function () { + const serverResponses = [ { - 'method': 'POST', - 'url': ENDPOINT_URL, - 'data': { - 'placementId': 'testPlacementId', - 'width': '300', - 'height': '200', - 'bidId': 'bidId123', - 'referer': 'www.example.com', - 'mediaType': 'video', - 'mediaTypes': { - video: { - context: 'instream', - playerSize: [[640, 360]] + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-1.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-1.ortb.net/sync.html'; } } - } + }, + body: [] } ]; - - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - it('should not return invalid responses', function() { - serverResponse.body.w = 0; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(result).to.be.empty; - }); - it('should not return invalid responses with invalid height', function() { - serverResponse.body.w = 300; - serverResponse.body.h = 0; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(result).to.be.empty; - }); - it('should not return invalid responses with invalid cpm', function() { - serverResponse.body.h = 250; - serverResponse.body.price = 0; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(result).to.be.empty; + const syncOptions = { + iframeEnabled: false, + pixelEnabled: false + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.be.an('array').that.is.empty; }); - it('should not return invalid responses with invalid creative ID', function() { - serverResponse.body.price = 2; - serverResponse.body.adid = undefined; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(result).to.be.empty; + it('should return no pixels if iframe sync is enabled and headers are blank', function () { + const serverResponses = [ + { + headers: null, + body: [] + } + ]; + const syncOptions = { + iframeEnabled: true, + pixelEnabled: false + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.be.an('array').that.is.empty; }); - }); - describe('getUserSyncs', function() { - it('should return correct sync urls', function() { - let serverResponse = [{ - body: { - 'vdoCreative': '', - 'price': 2, - 'adid': '12345asdfg', - 'currency': 'USD', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true, - 'mediaType': 'video', - 'cookiesync': { - 'status': 'no_cookie', - 'bidder_status': [ - { - 'bidder': 'vdoai', - 'no_cookie': true, - 'usersync': { - 'url': 'https://rtb.vdo.ai/setuid/', - 'type': 'iframe' - } + it('should return image sync urls for lm if pixel sync is enabled and headers have lm pixel', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; } - ] - } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] } - }]; - - let syncUrls = spec.getUserSyncs({ - iframeEnabled: true - }, serverResponse); - expect(syncUrls[0].url).to.be.equal(serverResponse[0].body.cookiesync.bidder_status[0].usersync.url); - - syncUrls = spec.getUserSyncs({ - iframeEnabled: false - }, serverResponse); - expect(syncUrls[0]).to.be.undefined; - }); - it('should not return invalid sync urls', function() { - let serverResponse = [{ - body: { - 'vdoCreative': '', - 'price': 2, - 'adid': '12345asdfg', - 'currency': 'USD', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true, - 'mediaType': 'video', - 'cookiesync': { - 'status': 'no_cookie', - 'bidder_status': [ - ] - } + ]; + const syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ + { + type: 'image', + url: 'https://tracker-lm.ortb.net/sync' } - }]; - - var syncUrls = spec.getUserSyncs({ - iframeEnabled: true - }, serverResponse); - expect(syncUrls[0]).to.be.undefined; - - delete serverResponse[0].body.cookiesync.bidder_status; - syncUrls = spec.getUserSyncs({ - iframeEnabled: true - }, serverResponse); - expect(syncUrls[0]).to.be.undefined; - - delete serverResponse[0].body.cookiesync; - syncUrls = spec.getUserSyncs({ - iframeEnabled: true - }, serverResponse); - expect(syncUrls[0]).to.be.undefined; - - delete serverResponse[0].body; - syncUrls = spec.getUserSyncs({ - iframeEnabled: true - }, serverResponse); - expect(syncUrls[0]).to.be.undefined; + ]); }); - }); - - describe('onTimeout', function() { - it('should run without errors', function() { - spec.onTimeout(); + it('should return image sync urls for client1 and clien2 if pixel sync is enabled and two responses and headers have two pixels', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-1.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-1.ortb.net/sync.html'; + } + } + }, + body: [] + }, + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-2.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-2.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; + const syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ + { + type: 'image', + url: 'https://tracker-1.ortb.net/sync' + }, + { + type: 'image', + url: 'https://tracker-2.ortb.net/sync' + } + ]); }); - }); - - describe('onBidWon', function() { - it('should run without errors', function() { - spec.onBidWon(); + it('should return image sync url for pll if pixel sync is enabled and two responses and headers have two same pixels', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] + }, + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; + const syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ + { + type: 'image', + url: 'https://tracker-lm.ortb.net/sync' + } + ]); }); - }); - - describe('onSetTargeting', function() { - it('should run without errors', function() { - spec.onSetTargeting(); + it('should return iframe sync url for pll if pixel sync is enabled and iframe is enables and headers have both iframe and img pixels', function () { + const serverResponses = [ + { + headers: { + get: function (header) { + if (header === 'X-PLL-UserSync-Image') { + return 'https://tracker-lm.ortb.net/sync'; + } + if (header === 'X-PLL-UserSync-Iframe') { + return 'https://tracker-lm.ortb.net/sync.html'; + } + } + }, + body: [] + } + ]; + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + expect(spec.getUserSyncs(syncOptions, serverResponses)).to.deep.equal([ + { + type: 'iframe', + url: 'https://tracker-lm.ortb.net/sync.html' + } + ]); }); }); }); + +function validateAdUnit(adUnit, bid) { + expect(adUnit.id).to.equal(bid.params.adUnitId); + expect(adUnit.bidId).to.equal(bid.bidId); + expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()); + expect(adUnit.transactionId).to.equal(bid.ortb2Imp.ext.tid); + let bidSizes = []; + if (bid.mediaTypes) { + if (bid.mediaTypes.video && bid.mediaTypes.video.playerSize) { + bidSizes = bidSizes.concat([bid.mediaTypes.video.playerSize]); + } + if (bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { + bidSizes = bidSizes.concat(bid.mediaTypes.banner.sizes); + } + } + if (bid.sizes) { + bidSizes = bidSizes.concat(bid.sizes || []); + } + expect(adUnit.sizes).to.deep.equal(bidSizes.map(size => { + return { + width: size[0], + height: size[1] + } + })); + expect(adUnit.publisherId).to.equal(bid.params.publisherId); + expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); + expect(adUnit.supplyChain).to.deep.equal(bid.schain); + expect(adUnit.ortb2Imp).to.deep.equal(bid.ortb2Imp); +} From 826d6108377f5b543f5ce179dc6d58081846ebd9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 3 Mar 2025 16:58:52 +0000 Subject: [PATCH 0970/1097] Prebid 9.33.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4888d2f11a7..2a317ffc66e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.33.0-pre", + "version": "9.33.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.33.0-pre", + "version": "9.33.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 7e1f8368aec..c9057cf7bd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.33.0-pre", + "version": "9.33.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From fa2634d66756d0d182c5f31d84cde993ba7b4c7e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Mon, 3 Mar 2025 16:58:53 +0000 Subject: [PATCH 0971/1097] Increment version to 9.34.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a317ffc66e..be5562d923a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.33.0", + "version": "9.34.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.33.0", + "version": "9.34.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index c9057cf7bd1..5c3a5ae92fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.33.0", + "version": "9.34.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 0e9f0707128644d06ca62eedd3a0f40df78bf9be Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Mon, 3 Mar 2025 13:17:21 -0800 Subject: [PATCH 0972/1097] PubMatic User ID Submodule: Initial Release (#12828) --- modules/.submodules.json | 1 + modules/pubmaticIdSystem.js | 156 +++++++++++++++++ modules/pubmaticIdSystem.md | 51 ++++++ test/spec/modules/pubmaticIdSystem_spec.js | 192 +++++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 modules/pubmaticIdSystem.js create mode 100644 modules/pubmaticIdSystem.md create mode 100644 test/spec/modules/pubmaticIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 24cb831d2b8..92932beb445 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -38,6 +38,7 @@ "oneKeyIdSystem", "operaadsIdSystem", "permutiveIdentityManagerIdSystem", + "pubmaticIdSystem", "pubProvidedIdSystem", "publinkIdSystem", "quantcastIdSystem", diff --git a/modules/pubmaticIdSystem.js b/modules/pubmaticIdSystem.js new file mode 100644 index 00000000000..801277f122b --- /dev/null +++ b/modules/pubmaticIdSystem.js @@ -0,0 +1,156 @@ +import { logInfo, logError, isNumber, isStr, isEmptyStr } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.js'; + +const MODULE_NAME = 'pubmaticId'; +const GVLID = 76; +export const STORAGE_NAME = 'pubmaticId'; +const STORAGE_EXPIRES = 30; // days +const STORAGE_REFRESH_IN_SECONDS = 24 * 3600; // 24 Hours +const LOG_PREFIX = 'PubMatic User ID: '; +const VERSION = '1'; +const API_URL = 'https://image6.pubmatic.com/AdServer/UCookieSetPug?oid=5&p='; + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +function generateQueryStringParams(config, consentData) { + const uspString = uspDataHandler.getConsentData(); + const coppaValue = coppaDataHandler.getCoppa(); + const gppConsent = gppDataHandler.getConsentData(); + + const params = { + publisherId: config.params.publisherId, + gdpr: (consentData && consentData?.gdprApplies) ? 1 : 0, + gdpr_consent: consentData && consentData?.consentString ? encodeURIComponent(consentData.consentString) : '', + src: 'pbjs_uid', + ver: VERSION, + coppa: Number(coppaValue), + us_privacy: uspString ? encodeURIComponent(uspString) : '', + gpp: gppConsent?.gppString ? encodeURIComponent(gppConsent.gppString) : '', + gpp_sid: gppConsent?.applicableSections?.length ? encodeURIComponent(gppConsent.applicableSections.join(',')) : '' + }; + + return params; +} + +function buildUrl(config, consentData) { + let baseUrl = `${API_URL}${config.params.publisherId}`; + const params = generateQueryStringParams(config, consentData); + + Object.keys(params).forEach((key) => { + baseUrl += `&${key}=${params[key]}`; + }); + + return baseUrl; +} + +function deleteFromAllStorages(key) { + const cKeys = [key, `${key}_cst`, `${key}_last`, `${key}_exp`]; + cKeys.forEach((cKey) => { + if (storage.getCookie(cKey)) { + storage.setCookie(cKey, '', new Date(0).toUTCString()); + } + }); + + const lsKeys = [key, `${key}_cst`, `${key}_last`, `${key}_exp`]; + lsKeys.forEach((lsKey) => { + if (storage.getDataFromLocalStorage(lsKey)) { + storage.removeDataFromLocalStorage(lsKey); + } + }); +} + +function getSuccessAndErrorHandler(callback) { + return { + success: (response) => { + let responseObj; + + try { + responseObj = JSON.parse(response); + logInfo(LOG_PREFIX + 'response received from the server', responseObj); + } catch (error) {} + + if (responseObj && isStr(responseObj.id) && !isEmptyStr(responseObj.id)) { + callback(responseObj); + } else { + deleteFromAllStorages(STORAGE_NAME); + callback(); + } + }, + error: (error) => { + deleteFromAllStorages(STORAGE_NAME); + logError(LOG_PREFIX + 'getId fetch encountered an error', error); + callback(); + } + }; +} + +function hasRequiredConfig(config) { + if (!config || !config.storage || !config.params) { + logError(LOG_PREFIX + 'config.storage and config.params should be passed.'); + return false; + } + + if (!isNumber(config.params.publisherId)) { + logError(LOG_PREFIX + 'config.params.publisherId (int) should be provided.'); + return false; + } + + if (config.storage.name !== STORAGE_NAME) { + logError(LOG_PREFIX + `config.storage.name should be '${STORAGE_NAME}'.`); + return false; + } + + if (config.storage.expires !== STORAGE_EXPIRES) { + logError(LOG_PREFIX + `config.storage.expires should be ${STORAGE_EXPIRES}.`); + return false; + } + + if (config.storage.refreshInSeconds !== STORAGE_REFRESH_IN_SECONDS) { + logError(LOG_PREFIX + `config.storage.refreshInSeconds should be ${STORAGE_REFRESH_IN_SECONDS}.`); + return false; + } + + return true; +} + +export const pubmaticIdSubmodule = { + name: MODULE_NAME, + gvlid: GVLID, + decode(value) { + if (isStr(value.id) && !isEmptyStr(value.id)) { + return { pubmaticId: value.id }; + } + return undefined; + }, + getId(config, consentData) { + if (!hasRequiredConfig(config)) { + return undefined; + } + + const resp = (callback) => { + logInfo(LOG_PREFIX + 'requesting an ID from the server'); + const url = buildUrl(config, consentData); + ajax(url, getSuccessAndErrorHandler(callback), null, { + method: 'GET', + withCredentials: true, + }); + }; + + return { callback: resp }; + }, + eids: { + 'pubmaticId': { + source: 'esp.pubmatic.com', + atype: 1, + getValue: (data) => { + return data; + } + }, + } +}; + +submodule('userId', pubmaticIdSubmodule); diff --git a/modules/pubmaticIdSystem.md b/modules/pubmaticIdSystem.md new file mode 100644 index 00000000000..ecd523dcf40 --- /dev/null +++ b/modules/pubmaticIdSystem.md @@ -0,0 +1,51 @@ +# PubMatic ID + +### Prebid Configuration + +You can configure this submodule in your `userSync.userIds[]` configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "pubmaticId", + params: { + publisherId: 123456 + }, + storage: { + name: "pubmaticId", + type: "cookie&html5", + expires: 30, + refreshInSeconds: 86400 + }, + }, + ], + }, +}); +``` + +| Parameters under `userSync.userIds[]` | Scope | Type | Description | Example | +| ---| --- | --- | --- | --- | +| name | Required | String | Name for the PubMatic ID submodule | `"pubmaticId"` | | +| storage | Required | Object | Configures how to cache User IDs locally in the browser | See [storage settings](#storage-settings) | +| params | Required | Object | Parameters for the PubMatic ID submodule | See [params](#params) | + +### Storage Settings + +The following settings are available for the `storage` property in the `userSync.userIds[]` object: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"pubmaticId"` | +| type | Required | String | `"cookie&html5"` (preferred) or `"cookie"` or `"html5"` | `"cookie&html5"` | +| expires | Required (Must be `30`) | Number | How long (in days) the user ID information will be stored | `30` | +| refreshInSeconds | Required (Must be `86400`) | Number | The interval (in seconds) for refreshing the user ID | `86400` | + +### Params + +The following settings are available in the `params` property in `userSync.userIds[]` object: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| publisherId | Required | Number | Publisher ID provided by PubMatic | `123456` | diff --git a/test/spec/modules/pubmaticIdSystem_spec.js b/test/spec/modules/pubmaticIdSystem_spec.js new file mode 100644 index 00000000000..70aa6df7337 --- /dev/null +++ b/test/spec/modules/pubmaticIdSystem_spec.js @@ -0,0 +1,192 @@ +import { pubmaticIdSubmodule, storage } from 'modules/pubmaticIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; +import { uspDataHandler, coppaDataHandler, gppDataHandler } from 'src/adapterManager.js'; +import { expect } from 'chai/index.mjs'; +import { attachIdSystem } from '../../../modules/userId/index.js'; +import { createEidsArray } from '../../../modules/userId/eids.js'; + +const validCookieConfig = { + params: { + publisherId: 12345 + }, + storage: { + type: 'cookie', + name: 'pubmaticId', + expires: 30, + refreshInSeconds: 24 * 3600 // 24 Hours + } +}; + +describe('pubmaticIdSystem', () => { + describe('name', () => { + it('should expose the name of the submodule', () => { + expect(pubmaticIdSubmodule.name).to.equal('pubmaticId'); + }); + }); + + describe('gvlid', () => { + it('should expose the vendor id', () => { + expect(pubmaticIdSubmodule.gvlid).to.equal(76); + }); + }); + + describe('getId', () => { + it('should call endpoint and handle valid response', () => { + const completeCallback = sinon.spy(function() {}); + + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig)); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + id: '6C3F0AB9-AE82-45C2-AD6F-9721E542DC4A' + })); + + expect(request.method).to.equal('GET'); + expect(request.withCredentials).to.be.true; + + const expectedURL = 'https://image6.pubmatic.com/AdServer/UCookieSetPug?oid=5&p=12345&publisherId=12345&gdpr=0&gdpr_consent=&src=pbjs_uid&ver=1&coppa=0&us_privacy=&gpp=&gpp_sid='; + expect(request.url).to.equal(expectedURL); + expect(completeCallback.calledOnceWithExactly({id: '6C3F0AB9-AE82-45C2-AD6F-9721E542DC4A'})).to.be.true; + }); + + it('should log an error if configuration is invalid', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + pubmaticIdSubmodule.getId({}); + expect(logErrorSpy.called).to.be.true; + logErrorSpy.restore(); + }); + + context('when GDPR applies', () => { + it('should call endpoint with gdpr=1 when GDPR applies and consent string is provided', () => { + const completeCallback = sinon.spy(); + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig), { + gdprApplies: true, + consentString: 'foo' + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gdpr=1'); + expect(request.url).to.contain('gdpr_consent=foo'); + }); + }); + + context('when GDPR doesn\'t apply', () => { + it('should call endpoint with \'gdpr=0\'', () => { + const completeCallback = () => {}; + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig), { + gdprApplies: false + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gdpr=0'); + }); + }); + + context('when a valid US Privacy string is given', () => { + it('should call endpoint with the US Privacy parameter', () => { + const completeCallback = () => {}; + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig)); + + sinon.stub(uspDataHandler, 'getConsentData').returns('1YYY'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('us_privacy=1YYY'); + + uspDataHandler.getConsentData.restore(); + }); + }); + + context('when coppa is enabled', () => { + it('should call endpoint with an enabled coppa signal', () => { + const completeCallback = () => {}; + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig)); + + sinon.stub(coppaDataHandler, 'getCoppa').returns(true); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('coppa=1'); + + coppaDataHandler.getCoppa.restore(); + }); + }); + + context('when a GPP consent string is given', () => { + it('should call endpoint with the GPP consent string and GPP applicable sections', () => { + const completeCallback = () => {}; + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig)); + + sinon.stub(gppDataHandler, 'getConsentData').returns({ gppString: 'foo', applicableSections: ['1', '2'] }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gpp=foo&gpp_sid=1%2C2'); + + gppDataHandler.getConsentData.restore(); + }); + + it('should call endpoint with the GPP consent and GPP applicable sections keys still if both values are not present', () => { + const completeCallback = () => {}; + const { callback } = pubmaticIdSubmodule.getId(utils.mergeDeep({}, validCookieConfig)); + + sinon.stub(gppDataHandler, 'getConsentData').returns({ gppString: undefined, applicableSections: undefined }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gpp=&gpp_sid='); + + gppDataHandler.getConsentData.restore(); + }); + }); + }); + + describe('decode', () => { + it('should wrap the given value inside an object literal', () => { + expect(pubmaticIdSubmodule.decode({ id: 'foo' })).to.deep.equal({ + [pubmaticIdSubmodule.name]: 'foo' + }); + }); + }); + + describe('eid', () => { + before(() => { + attachIdSystem(pubmaticIdSubmodule); + }); + + it('should create the correct EIDs', () => { + const userId = { + 'pubmaticId': 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'esp.pubmatic.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }); +}); From 3179bf7a261bc7d8be30320d622cfe3019799f70 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 3 Mar 2025 15:00:33 -0800 Subject: [PATCH 0973/1097] Update README with supported feature tags (#12829) --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 65dc668164e..2644419886c 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,24 @@ Or, if you are consuming Prebid through npm, with the `disableFeatures` option i } ``` -**Note**: this is still a work in progress - at the moment, `NATIVE` is the only feature that can be disabled this way, resulting in a minimal decrease in size (but you can expect that to improve over time). +Features that can be disabled this way are: + + - `VIDEO` - support for video bids; + - `NATIVE` - support for native bids; + - `UID2_CSTG` - support for UID2 client side token generation (see [Unified ID 2.0](https://docs.prebid.org/dev-docs/modules/userid-submodules/unified2.html)) + - `GREEDY` - disables the use blocking, "greedy" promises within Prebid (see below). + +#### Greedy promises + +By default, Prebid attempts to hold control of the main thread when possible, using a [custom implementation of `Promise`](https://github.com/prebid/Prebid.js/blob/master/libraries/greedy/greedyPromise.js) that does not submit callbacks to the scheduler once the promise is resolved (running them immediately instead). +Disabling this behavior instructs Prebid to use the standard `window.Promise` instead; this has the effect of breaking up task execution, making them slower overall but giving the browser more chances to run other tasks in between, which can improve UX. + +You may also override the `Promise` constructor used by Prebid through `pbjs.Promise`, for example: + +```javascript +var pbjs = pbjs || {}; +pbjs.Promise = myCustomPromiseConstructor; +``` ## Unminified code From 5310f5eb323d45d7ce4ac83795f5bd1c31266674 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 02:45:41 +0000 Subject: [PATCH 0974/1097] Bump actions/checkout from 3 to 4 (#12826) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/code-path-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml index 23e0c19db6d..30c34ba0d2e 100644 --- a/.github/workflows/code-path-changes.yml +++ b/.github/workflows/code-path-changes.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v3 From 4926374300d20ea45f9dded390d6da12140495cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 02:47:01 +0000 Subject: [PATCH 0975/1097] Bump actions/setup-node from 3 to 4 (#12827) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/code-path-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml index 30c34ba0d2e..d98ae8e2d72 100644 --- a/.github/workflows/code-path-changes.yml +++ b/.github/workflows/code-path-changes.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18' From 04c73f6c96adfb021be614bd178ad554e5798253 Mon Sep 17 00:00:00 2001 From: Dejan Grbavcic Date: Tue, 4 Mar 2025 03:48:19 +0100 Subject: [PATCH 0976/1097] TargetVideo and Brid Adapter: Fixing schain (#12821) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * Add Brid bid adapter * Brid adapter requested changes * BridBidAdapter: switching to plcmt * Brid Bid Adapter: getUserSyncs method and interpretResponse updates * Adding missing semicolon * TargetVideo Bid Adapter : user sync and response changes * TargetVideo Bid Adapter : removing duplicate code * TargetVideo and Brid video adapters: fixing schain --- modules/bridBidAdapter.js | 4 ++- modules/targetVideoBidAdapter.js | 4 ++- test/spec/modules/bridBidAdapter_spec.js | 24 +++++++++++++ .../modules/targetVideoBidAdapter_spec.js | 34 +++++++++++++++++-- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js index c822f4d5c80..c9840ad57f8 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -100,7 +100,9 @@ export const spec = { }; if (bidRequests[0].schain) { - postBody.schain = bidRequests[0].schain; + postBody.source = { + ext: { schain: bidRequests[0].schain } + }; } const params = bid.params; diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index b01e3dddab3..c149e80749d 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -99,7 +99,9 @@ export const spec = { }; if (bidRequests[0].schain) { - payload.schain = bidRequests[0].schain; + payload.source = { + ext: { schain: bidRequests[0].schain } + }; } requests.push(formatRequest({ payload, url: VIDEO_ENDPOINT_URL, bidId })); diff --git a/test/spec/modules/bridBidAdapter_spec.js b/test/spec/modules/bridBidAdapter_spec.js index fdda6d840e8..20a6542707b 100644 --- a/test/spec/modules/bridBidAdapter_spec.js +++ b/test/spec/modules/bridBidAdapter_spec.js @@ -1,5 +1,6 @@ import { spec } from '../../../modules/bridBidAdapter.js' import { SYNC_URL } from '../../../libraries/targetVideoUtils/constants.js'; +import { deepClone } from '../../../src/utils.js'; describe('Brid Bid Adapter', function() { const videoRequest = [{ @@ -37,6 +38,29 @@ describe('Brid Bid Adapter', function() { expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal(12345); }); + it('Test the request schain sending', function() { + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'examplewebsite.com', + sid: '00001', + hp: 1 + }] + }; + + let videoRequestCloned = deepClone(videoRequest); + videoRequestCloned[0].schain = globalSchain; + + const request = spec.buildRequests(videoRequestCloned, videoRequestCloned[0]); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request[0].data); + expect(payload).to.not.be.empty; + expect(payload.source.ext.schain).to.exist; + expect(payload.source.ext.schain).to.deep.equal(globalSchain); + }); + it('Test nobid responses', function () { const responseBody = { 'id': 'test-id', diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index 61df5413862..f2c59d29031 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -1,5 +1,6 @@ import { spec } from '../../../modules/targetVideoBidAdapter.js' import { SYNC_URL } from '../../../libraries/targetVideoUtils/constants.js'; +import { deepClone } from '../../../src/utils.js'; describe('TargetVideo Bid Adapter', function() { const bidder = 'targetVideo'; @@ -7,6 +8,10 @@ describe('TargetVideo Bid Adapter', function() { placementId: 12345, }; + const defaultBidderRequest = { + bidderRequestId: 'mock-uuid', + }; + const bannerRequest = [{ bidder, params, @@ -38,7 +43,7 @@ describe('TargetVideo Bid Adapter', function() { }); it('Test the BANNER request processing function', function() { - const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const request = spec.buildRequests(bannerRequest, defaultBidderRequest); expect(request).to.not.be.empty; const payload = JSON.parse(request.data); @@ -53,7 +58,7 @@ describe('TargetVideo Bid Adapter', function() { }); it('Test the VIDEO request processing function', function() { - const request = spec.buildRequests(videoRequest, videoRequest[0]); + const request = spec.buildRequests(videoRequest, defaultBidderRequest); expect(request).to.not.be.empty; const payload = JSON.parse(request[0].data); @@ -65,6 +70,29 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal(12345); }) + it('Test the VIDEO request schain sending', function() { + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'examplewebsite.com', + sid: '00001', + hp: 1 + }] + }; + + let videoRequestCloned = deepClone(videoRequest); + videoRequestCloned[0].schain = globalSchain; + + const request = spec.buildRequests(videoRequestCloned, defaultBidderRequest); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request[0].data); + expect(payload).to.not.be.empty; + expect(payload.source.ext.schain).to.exist; + expect(payload.source.ext.schain).to.deep.equal(globalSchain); + }); + it('Handle BANNER nobid responses', function() { const responseBody = { 'version': '0.0.1', @@ -170,7 +198,7 @@ describe('TargetVideo Bid Adapter', function() { expect(bid.width).to.equal(640); expect(bid.height).to.equal(480); expect(bid.currency).to.equal('USD'); - }) + }); it('Test BANNER GDPR consent information is present in the request', function() { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; From f673448ab2cce4f822b5b31e6cd75503c6b1739d Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 6 Mar 2025 17:34:18 +0100 Subject: [PATCH 0977/1097] AdagioRtdProvider: fix apntag event callback (#12837) --- modules/adagioRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index a5642fdca02..d8a77003667 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -683,7 +683,7 @@ function registerEventsForAdServers(config) { register('apntag', 'anq', ws, 'ast', () => { ws.apntag.anq.push(() => { AST_EVENTS.forEach(eventName => { - ws.apntag.onEvent(eventName, () => { + ws.apntag.onEvent(eventName, function () { _internal.getAdagioNs().queue.push({ action: 'ast-event', data: { eventName, args: arguments, _window: ws }, From 8a3aa8169b801dc947901252e6d5ef5df7098153 Mon Sep 17 00:00:00 2001 From: andre-gielow-ttd <124626380+andre-gielow-ttd@users.noreply.github.com> Date: Fri, 7 Mar 2025 06:06:10 -0500 Subject: [PATCH 0978/1097] Revert TTD integration type header due to flaky bidding (#12841) * Re-add x-integration-type to TTD adapter * Revert x-integration-type (2nd time) due to flaky bidding behavior * Revert integration type header (2nd time) until we fix flaky bidding --- modules/ttdBidAdapter.js | 3 --- test/spec/modules/ttdBidAdapter_spec.js | 7 ------- 2 files changed, 10 deletions(-) diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 20c6e720dff..4bc0a6c2ded 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -441,9 +441,6 @@ export const spec = { data: topLevel, options: { withCredentials: true, - customHeaders: { - 'x-integration-type': 1, - }, } }; diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 5f21c9c3235..4580c514609 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -291,13 +291,6 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.publisher.id).to.equal(baseBannerBidRequests[0].params.publisherId); }); - it('sends integration type header', function () { - const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest); - expect(requestBody.options).to.be.not.null; - expect(requestBody.options.customHeaders).to.be.not.null; - expect(requestBody.options.customHeaders['x-integration-type']).to.equal(1); - }); - it('sends placement id in tagid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.imp[0].tagid).to.equal(baseBannerBidRequests[0].params.placementId); From b9d13306e9fb05106fb6258743544f46c4a920d4 Mon Sep 17 00:00:00 2001 From: "Md. Soman Mia Sarker" Date: Fri, 7 Mar 2025 17:12:05 +0600 Subject: [PATCH 0979/1097] Adgrid Bid Adapter: support userSync feature (#12714) * Support userSync feature * Added unit testing for userSync --- modules/adgridBidAdapter.js | 18 +++++++++- test/spec/modules/adgridBidAdapter_spec.js | 40 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/modules/adgridBidAdapter.js b/modules/adgridBidAdapter.js index 71c55a54395..13e603c3636 100644 --- a/modules/adgridBidAdapter.js +++ b/modules/adgridBidAdapter.js @@ -178,12 +178,28 @@ function getBidData(bid) { return bidData; } +/** + * Register the user sync pixels/iframe which should be dropped after the auction. + */ +function getUserSyncs(syncOptions, response, gdprConsent, uspConsent) { + if (typeof response !== 'object' || response === null || response.length === 0) { + return []; + } + + if (response[0]?.body?.ext?.cookies && typeof response[0].body.ext.cookies === 'object') { + return response[0].body.ext.cookies.slice(0, 5); + } else { + return []; + } +}; + export const spec = { code: BIDDER.CODE, isBidRequestValid, buildRequests, interpretResponse, - supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES + supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES, + getUserSyncs }; registerBidder(spec); diff --git a/test/spec/modules/adgridBidAdapter_spec.js b/test/spec/modules/adgridBidAdapter_spec.js index 0d7ad9c245d..7e6638683a7 100644 --- a/test/spec/modules/adgridBidAdapter_spec.js +++ b/test/spec/modules/adgridBidAdapter_spec.js @@ -6,6 +6,15 @@ const globalConfig = { endPoint: 'https://api-prebid.adgrid.io/api/v1/auction' }; +const userConfig = { + gdprConsent: { + gdprApplies: true, + consentString: 'COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + uspConsent: '123456' +}; + describe('AdGrid Bid Adapter', function () { const bannerRequest = [{ bidId: 123456, @@ -128,4 +137,35 @@ describe('AdGrid Bid Adapter', function () { expect(bid.currency).to.equal(receivedBid.currency); }); }); + + describe('getUserSyncs', function () { + const response = { body: { cookies: [] } }; + + it('Validate the user sync without cookie', function () { + var syncs = spec.getUserSyncs({}, [response], userConfig.gdprConsent, userConfig.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + + it('Validate the user sync with cookie', function () { + response.body.ext = { + cookies: [{ 'type': 'image', 'url': 'https://cookie-sync.org/' }] + }; + var syncs = spec.getUserSyncs({}, [response], userConfig.gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0]).to.have.property('type').and.to.equal('image'); + expect(syncs[0]).to.have.property('url').and.to.equal('https://cookie-sync.org/'); + }); + + it('Validate the user sync with no bid', function () { + var syncs = spec.getUserSyncs({}, null, userConfig.gdprConsent, userConfig.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + + it('Validate the user sync with no bid body', function () { + var syncs = spec.getUserSyncs({}, [], userConfig.gdprConsent, userConfig.uspConsent); + expect(syncs).to.have.lengthOf(0); + var syncs = spec.getUserSyncs({}, [{}], userConfig.gdprConsent, userConfig.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + }); }); From d6b3efb71155b04f963b647c6f83de5a48796fce Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Fri, 7 Mar 2025 06:16:00 -0500 Subject: [PATCH 0980/1097] Sonobi Bid Adapter - add new video params (#12834) --- modules/sonobiBidAdapter.js | 45 ++++++++++++++++++++++ test/spec/modules/sonobiBidAdapter_spec.js | 16 +++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 9f8396d93e5..57f6b0eb51a 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -362,6 +362,51 @@ function _validateMediaType(bidRequest) { let plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt'); mediaTypeValidation = `${mediaTypeValidation}pl=${plcmt},`; } + if (deepAccess(bidRequest, 'mediaTypes.video.protocols')) { + mediaTypeValidation = `${mediaTypeValidation}protocols=${deepAccess(bidRequest, 'mediaTypes.video.protocols').join(':')},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.mimes')) { + mediaTypeValidation = `${mediaTypeValidation}mimes=${deepAccess(bidRequest, 'mediaTypes.video.mimes').join(':')},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.battr')) { + mediaTypeValidation = `${mediaTypeValidation}battr=${deepAccess(bidRequest, 'mediaTypes.video.battr').join(':')},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.api')) { + mediaTypeValidation = `${mediaTypeValidation}api=${deepAccess(bidRequest, 'mediaTypes.video.api').join(':')},`; + } + + if (deepAccess(bidRequest, 'mediaTypes.video.minduration')) { + let minduration = deepAccess(bidRequest, 'mediaTypes.video.minduration'); + mediaTypeValidation = `${mediaTypeValidation}minduration=${minduration},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.maxduration')) { + let maxduration = deepAccess(bidRequest, 'mediaTypes.video.maxduration'); + mediaTypeValidation = `${mediaTypeValidation}maxduration=${maxduration},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.skip')) { + let skip = deepAccess(bidRequest, 'mediaTypes.video.skip'); + mediaTypeValidation = `${mediaTypeValidation}skip=${skip},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.skipafter')) { + let skipafter = deepAccess(bidRequest, 'mediaTypes.video.skipafter'); + mediaTypeValidation = `${mediaTypeValidation}skipafter=${skipafter},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.startdelay')) { + let startdelay = deepAccess(bidRequest, 'mediaTypes.video.startdelay'); + mediaTypeValidation = `${mediaTypeValidation}startdelay=${startdelay},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.linearity')) { + let linearity = deepAccess(bidRequest, 'mediaTypes.video.linearity'); + mediaTypeValidation = `${mediaTypeValidation}linearity=${linearity},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.minbitrate')) { + let minbitrate = deepAccess(bidRequest, 'mediaTypes.video.minbitrate'); + mediaTypeValidation = `${mediaTypeValidation}minbitrate=${minbitrate},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.maxbitrate')) { + let maxbitrate = deepAccess(bidRequest, 'mediaTypes.video.maxbitrate'); + mediaTypeValidation = `${mediaTypeValidation}maxbitrate=${maxbitrate},`; + } } else if (mediaType === 'display') { mediaTypeValidation = 'c=d,'; } diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index ad6acb6dcba..0b0a00c75b3 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -307,7 +307,19 @@ describe('SonobiBidAdapter', function () { context: 'outstream', playbackmethod: [1, 2, 3], plcmt: 3, - placement: 2 + placement: 2, + protocols: [1, 2, 3, 4, 5], + mimes: ['video/mp4', 'video/mpeg', 'video/x-flv'], + battr: [16, 17], + api: [1, 2, 3], + minduration: 5, + maxduration: 60, + skip: 1, + skipafter: 10, + startdelay: 5, + linearity: 1, + minbitrate: 1, + maxbitrate: 2 } } }, @@ -374,7 +386,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,pm=1:2:3,p=2,pl=3,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,pm=1:2:3,p=2,pl=3,protocols=1:2:3:4:5,mimes=video/mp4:video/mpeg:video/x-flv,battr=16:17,api=1:2:3,minduration=5,maxduration=60,skip=1,skipafter=10,startdelay=5,linearity=1,minbitrate=1,maxbitrate=2,', '30b31c1838de1g': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-42,c=d,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', From b2502664a988b41998765cd4e9f84d3f08f6a4cc Mon Sep 17 00:00:00 2001 From: kiho-shige <133089869+kiho-shige@users.noreply.github.com> Date: Sat, 8 Mar 2025 00:02:03 +0900 Subject: [PATCH 0981/1097] YieldOne Bid Adapter: add UID2.0 support (#12836) * support for UID2.0 * fix: yieldoneBidAdapter.js --- modules/yieldoneBidAdapter.js | 6 ++++ test/spec/modules/yieldoneBidAdapter_spec.js | 33 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 5852663dc99..655b331c7c3 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -113,6 +113,12 @@ export const spec = { payload.id5Id = id5id; } + // UID2.0 + const uid2 = deepAccess(bidRequest, 'userId.uid2.id'); + if (isStr(uid2) && !isEmpty(uid2)) { + payload.uid2id = uid2; + } + return { method: 'GET', url: ENDPOINT_URL, diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index 983f67bcdd6..347ff417f60 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -475,6 +475,39 @@ describe('yieldoneBidAdapter', function () { expect(request[0].data.id5Id).to.equal('id5id_sample'); }); }); + + describe('UID2.0', function () { + it('dont send UID2.0 if undefined', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + userId: {}, + }, + { + params: {placementId: '2'}, + userId: undefined, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('uid2id'); + expect(request[1].data).to.not.have.property('uid2id'); + expect(request[2].data).to.not.have.property('uid2id'); + }); + + it('should send UID2.0 if available', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + userId: {uid2: {id: 'uid2_sample'}}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.uid2id).to.equal('uid2_sample'); + }); + }); }); describe('interpretResponse', function () { From 1519fdddadb0aea9da5c8cf2b56eeb3c6c39d54e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 7 Mar 2025 10:03:46 -0800 Subject: [PATCH 0982/1097] Core: support for expandable / flex slots (#12379) * Ad unit validation * response validation * allow 100% height in creatives * ortbConverter support for format/wratio/hratio * do not set expdir: undefined * Require either w/h or wratio/hratio for banner.format * Fix tests for --disable GREEDY --- creative/renderers/display/renderer.js | 6 +- .../gpt/x-domain/creative.html | 2 +- .../creative-renderer-display/renderer.js | 2 +- libraries/ortbConverter/processors/banner.js | 2 +- libraries/ortbConverter/processors/default.js | 2 + modules/debugging/responses.js | 5 +- src/adapters/bidderFactory.js | 6 ++ src/prebid.js | 39 +++++++++-- test/spec/creative/displayRenderer_spec.js | 28 +++++++- test/spec/ortbConverter/banner_spec.js | 20 ++++++ test/spec/unit/core/bidderFactory_spec.js | 23 ++++-- test/spec/unit/pbjs_api_spec.js | 70 ++++++++++++++++++- 12 files changed, 185 insertions(+), 20 deletions(-) diff --git a/creative/renderers/display/renderer.js b/creative/renderers/display/renderer.js index e031679b116..2885148abf5 100644 --- a/creative/renderers/display/renderer.js +++ b/creative/renderers/display/renderer.js @@ -7,8 +7,12 @@ export function render({ad, adUrl, width, height}, {mkFrame}, win) { message: 'Missing ad markup or URL' }; } else { + if (height == null) { + const body = win.document?.body; + [body, body?.parentElement].filter(elm => elm?.style != null).forEach(elm => elm.style.height = '100%'); + } const doc = win.document; - const attrs = {width, height}; + const attrs = {width: width ?? '100%', height: height ?? '100%'}; if (adUrl && !ad) { attrs.src = adUrl; } else { diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index f523c87b326..d7b97b93baa 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + ', + 'ttl': 300, + 'creativeId': '7806bcbb-a156-4ec4-872b-bd0d8e8bff34', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'test-pb.ust-ad.com' + ] + }, + 'originalCpm': 2.73, + 'originalCurrency': 'JPY', + 'auctionId': '75e394d9', + 'responseTimestamp': 1733709113100, + 'requestTimestamp': 1733709113000, + 'bidder': 'uniquest', + 'adUnitCode': '/12345678910/uniquest_1', + 'timeToRespond': 100, + 'pbLg': '2.50', + 'pbMg': '2.70', + 'pbHg': '2.73', + 'pbAg': '2.70', + 'pbDg': '2.73', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'uniquest', + 'hb_adid': '53c5a9c1947c57', + 'hb_pb': '2.70', + 'hb_size': '300x300', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'test-pb.ust-ad.com' + } + } + ], + 'winningBids': [], + 'timeout': 400 + }, + AD_RENDER_SUCCEEDED: { + 'doc': { + 'location': { + 'href': 'http://test-pb.ust-ad.com/banner.html', + 'protocol': 'http:', + 'host': 'test-pb.ust-ad.com', + 'hostname': 'localhost', + 'port': '80', + 'pathname': '/page_banner.html', + 'hash': '', + 'origin': 'http://test-pb.ust-ad.com', + 'ancestorOrigins': { + '0': 'http://test-pb.ust-ad.com' + } + } + }, + 'bid': { + 'bidderCode': 'uniquest', + 'width': 300, + 'height': 300, + 'statusMessage': 'Bid available', + 'adId': '53c5a9c1947c57', + 'requestId': '4d9eec3fe27a43', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': '2.73', + 'currency': 'JPY', + 'ad': 'test_ad', + 'metrics': { + 'someMetric': 0 + }, + 'ttl': 300, + 'creativeId': '7806bcbb-a156-4ec4-872b-bd0d8e8bff34', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'test-pb.ust-ad.com' + ] + }, + 'originalCpm': 2.73, + 'originalCurrency': 'JPY', + 'auctionId': '75e394d9', + 'responseTimestamp': 1733709113100, + 'requestTimestamp': 1733709113000, + 'bidder': 'uniquest', + 'adUnitCode': '12345678910/uniquest_1', + 'timeToRespond': 100, + 'size': '300x300', + 'adserverTargeting': { + 'hb_bidder': 'uniquest', + 'hb_adid': '53c5a9c1947c57', + 'hb_pb': '2.70', + 'hb_size': '300x300', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'test-pb.ust-ad.com' + }, + 'status': 'rendered', + 'params': [ + { + 'nonZetaParam': 'nonZetaValue' + } + ] + } + } +} + +describe('Uniquest Analytics Adapter', function () { + let sandbox; + let requests; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + requests = server.requests; + sandbox.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + describe('handle events', function () { + beforeEach(function () { + uniquestAnalyticsAdapter.enableAnalytics({ + options: { + sid: 'ABCDE123', + } + }); + }); + + afterEach(function () { + uniquestAnalyticsAdapter.disableAnalytics(); + }); + + it('Handle events', function () { + this.timeout(1000); + + events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); + events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); + + // bids count + expect(requests.length).to.equal(2); + const auctionEnd = JSON.parse(requests[0].requestBody); + // event_type + expect(auctionEnd.event_type).to.eql(EVENTS.AUCTION_END); + // URL + expect(auctionEnd.url).to.eql(window.top.location.href); + // bid + expect(auctionEnd.bids).to.be.deep.equal([{ + auction_id: '75e394d9', + creative_id: '7806bcbb-a156-4ec4-872b-bd0d8e8bff34', + bidder: 'uniquest', + media_type: 'banner', + size: '300x250', + cpm: '2.73', + currency: 'JPY', + original_cpm: '2.73', + original_currency: 'JPY', + hb_pb: '2.70', + bidding_time: 100, + ad_unit_code: '/12345678910/uniquest_1', + }] + ); + + const auctionSucceeded = JSON.parse(requests[1].requestBody); + // event_type + expect(auctionSucceeded.event_type).to.eql(EVENTS.AD_RENDER_SUCCEEDED); + // URL + expect(auctionSucceeded.url).to.eql(window.top.location.href); + // bid + expect(auctionSucceeded.bid).to.be.deep.equal({ + auction_id: '75e394d9', + creative_id: '7806bcbb-a156-4ec4-872b-bd0d8e8bff34', + bidder: 'uniquest', + media_type: 'banner', + size: '300x300', + cpm: '2.73', + currency: 'JPY', + original_cpm: '2.73', + original_currency: 'JPY', + hb_pb: '2.70', + bidding_time: 100, + ad_unit_code: '12345678910/uniquest_1' + }); + }); + }); +}); diff --git a/test/spec/modules/uniquestBidAdapter_spec.js b/test/spec/modules/uniquestBidAdapter_spec.js new file mode 100644 index 00000000000..57051d33a43 --- /dev/null +++ b/test/spec/modules/uniquestBidAdapter_spec.js @@ -0,0 +1,104 @@ +import { expect } from 'chai'; +import { spec } from 'modules/uniquestBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT = 'https://adpb.ust-ad.com/hb/prebid'; + +describe('UniquestAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const request = { + bidder: 'uniquest', + params: { + sid: 'sid_0001', + }, + }; + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false) + expect(spec.isBidRequestValid({ sid: '' })).to.equal(false) + }) + }) + + describe('buildRequest', function () { + const bids = [ + { + bidder: 'uniquest', + params: { + sid: 'sid_0001', + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 300], + [300, 250], + [320, 100], + ], + bidId: '259d7a594535852', + bidderRequestId: '247f62f777e5e4', + } + ]; + const bidderRequest = { + timeout: 1500, + } + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bids, bidderRequest); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('GET'); + expect(requests[0].data).to.equal('bid=259d7a594535852&sid=sid_0001&widths=300%2C300%2C320&heights=300%2C250%2C100&timeout=1500&') + }) + }) + + describe('interpretResponse', function() { + it('should return a valid bid response', function () { + const serverResponse = { + request_id: '247f62f777e5e4', + cpm: 12.3, + currency: 'JPY', + width: 300, + height: 250, + bid_id: 'bid_0001', + deal_id: '', + net_revenue: false, + ttl: 300, + ad: '
', + media_type: 'banner', + meta: { + advertiser_domains: ['advertiser.com'], + }, + }; + const expectResponse = [{ + requestId: '247f62f777e5e4', + cpm: 12.3, + currency: 'JPY', + width: 300, + height: 250, + ad: '
', + creativeId: 'bid_0001', + netRevenue: false, + mediaType: 'banner', + ttl: 300, + meta: { + advertiserDomains: ['advertiser.com'], + } + }]; + const result = spec.interpretResponse({ body: serverResponse }, {}); + expect(result).to.have.lengthOf(1); + expect(result).to.deep.have.same.members(expectResponse); + }) + + it('should return an empty array to indicate no valid bids', function () { + const result = spec.interpretResponse({ body: {} }, {}) + expect(result).is.an('array').is.empty; + }) + }) +}) From a5e6104581a2e2c87ab8db37f5c91be2361f5df0 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Tue, 25 Mar 2025 16:07:19 +0100 Subject: [PATCH 1029/1097] Core: Local cache for video bids (#12598) * Local cache for video bids * clean up * clean up * fix * error message update * revoking blobs on auction expiry * Local cache + GAM poc * ima sdk adsResponse * local cache logic * dfp function & examples & unit tests * fix order * reorganization * refactor * lint fix * introducing setAdXml * renderBid change * removing getVast from pbjs * review fixes * limiting use of prefetching vast to local cache * revert not needed * adapting to 'send all bids', adding some tests * + regexp * regexp fix * regexp fix * uuid matching refactor * Update integrationExamples/videoModule/jwplayer/localVideoCache.html Co-authored-by: Karim Mourra * remove unecessary parts * lint fix * remove not needed test --------- Co-authored-by: Karim Mourra --- integrationExamples/gpt/localCacheGam.html | 134 ++++++++++++++ .../adPlayerPro/localVideoCache.html | 167 ++++++++++++++++++ .../jwplayer/bidsBackHandlerOverride.html | 2 +- .../jwplayer/gamAdServerMediation.html | 1 - .../videoModule/jwplayer/localVideoCache.html | 135 ++++++++++++++ .../videojs/bidsBackHandlerOverride.html | 2 +- .../videoModule/videojs/localVideoCache.html | 147 +++++++++++++++ libraries/video/shared/vastXmlEditor.js | 38 +--- libraries/xmlUtils/xmlUtils.js | 35 ++++ modules/adplayerproVideoProvider.js | 5 + modules/dfpAdServerVideo.js | 72 ++++++++ modules/jwplayerVideoProvider.js | 9 + modules/videoModule/adQueue.js | 8 +- modules/videoModule/coreVideo.js | 13 ++ modules/videoModule/gamAdServerSubmodule.js | 11 +- modules/videoModule/index.js | 49 +++-- modules/videojsVideoProvider.js | 17 ++ src/auction.js | 17 +- src/video.js | 6 +- src/videoCache.js | 57 ++++-- test/spec/modules/dfpAdServerVideo_spec.js | 164 +++++++++++++++++ test/spec/modules/videoModule/adQueue_spec.js | 19 ++ .../videoModule/shared/vastXmlEditor_spec.js | 1 + test/spec/videoCache_spec.js | 18 ++ 24 files changed, 1046 insertions(+), 81 deletions(-) create mode 100644 integrationExamples/gpt/localCacheGam.html create mode 100644 integrationExamples/videoModule/adPlayerPro/localVideoCache.html create mode 100644 integrationExamples/videoModule/jwplayer/localVideoCache.html create mode 100644 integrationExamples/videoModule/videojs/localVideoCache.html create mode 100644 libraries/xmlUtils/xmlUtils.js diff --git a/integrationExamples/gpt/localCacheGam.html b/integrationExamples/gpt/localCacheGam.html new file mode 100644 index 00000000000..ce0299e7036 --- /dev/null +++ b/integrationExamples/gpt/localCacheGam.html @@ -0,0 +1,134 @@ + + + + + + + JW Player with Local Cache + + + + + + +

JW Player with Local cache

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/adPlayerPro/localVideoCache.html b/integrationExamples/videoModule/adPlayerPro/localVideoCache.html new file mode 100644 index 00000000000..24d6287f844 --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/localVideoCache.html @@ -0,0 +1,167 @@ + + + + + + + + AdPlayer.Pro with Local Cache & GAM + + + + + + +

AdPlayer.Pro with Local Cache & GAM

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html index 308809caa87..094af9c32a2 100644 --- a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html +++ b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html @@ -143,4 +143,4 @@
Div-1: Player placeholder div
- + \ No newline at end of file diff --git a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html index b8228615fae..94555dce8a2 100644 --- a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html +++ b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html @@ -65,7 +65,6 @@ // output: 'vast' // }, baseAdTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=' - //'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/137679306/HB_Dev_Center_Example&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=', }, },] }, diff --git a/integrationExamples/videoModule/jwplayer/localVideoCache.html b/integrationExamples/videoModule/jwplayer/localVideoCache.html new file mode 100644 index 00000000000..248a25c41cd --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/localVideoCache.html @@ -0,0 +1,135 @@ + + + + + + + JW Player with Local Cache & GAM + + + + + + +

JW Player with Local Cache & GAM

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html b/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html index 74217ecee2c..23ae1345d4c 100644 --- a/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html +++ b/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html @@ -168,4 +168,4 @@
Div-1: Player placeholder div
- + \ No newline at end of file diff --git a/integrationExamples/videoModule/videojs/localVideoCache.html b/integrationExamples/videoModule/videojs/localVideoCache.html new file mode 100644 index 00000000000..973a7826def --- /dev/null +++ b/integrationExamples/videoModule/videojs/localVideoCache.html @@ -0,0 +1,147 @@ + + + + + + + + + + --> + + + + VideoJS with Local Cache & GAM Ad Server Mediation + + + + + + + +

VideoJS with GAM Ad Server Mediation

+
Div-1: Player placeholder div
+ + + + diff --git a/libraries/video/shared/vastXmlEditor.js b/libraries/video/shared/vastXmlEditor.js index b586e5b4c29..d5798bfc2ac 100644 --- a/libraries/video/shared/vastXmlEditor.js +++ b/libraries/video/shared/vastXmlEditor.js @@ -1,6 +1,6 @@ -import { getErrorNode, getImpressionNode, buildVastWrapper } from './vastXmlBuilder.js'; -export const XML_MIME_TYPE = 'application/xml'; +import XMLUtil from '../../xmlUtils/xmlUtils.js'; +import { getErrorNode, getImpressionNode, buildVastWrapper } from './vastXmlBuilder.js'; export function VastXmlEditor(xmlUtil_) { const xmlUtil = xmlUtil_; @@ -76,40 +76,6 @@ export function VastXmlEditor(xmlUtil_) { } } -function XMLUtil() { - let parser; - let serializer; - - function getParser() { - if (!parser) { - // DOMParser instantiation is costly; instantiate only once throughout Prebid lifecycle. - parser = new DOMParser(); - } - return parser; - } - - function getSerializer() { - if (!serializer) { - // XMLSerializer instantiation is costly; instantiate only once throughout Prebid lifecycle. - serializer = new XMLSerializer(); - } - return serializer; - } - - function parse(xmlString) { - return getParser().parseFromString(xmlString, XML_MIME_TYPE); - } - - function serialize(xmlDoc) { - return getSerializer().serializeToString(xmlDoc); - } - - return { - parse, - serialize - }; -} - export function vastXmlEditorFactory() { return VastXmlEditor(XMLUtil()); } diff --git a/libraries/xmlUtils/xmlUtils.js b/libraries/xmlUtils/xmlUtils.js new file mode 100644 index 00000000000..b29ff2d0e2a --- /dev/null +++ b/libraries/xmlUtils/xmlUtils.js @@ -0,0 +1,35 @@ +const XML_MIME_TYPE = 'application/xml'; + +export default function XMLUtil() { + let parser; + let serializer; + + function getParser() { + if (!parser) { + // DOMParser instantiation is costly; instantiate only once throughout Prebid lifecycle. + parser = new DOMParser(); + } + return parser; + } + + function getSerializer() { + if (!serializer) { + // XMLSerializer instantiation is costly; instantiate only once throughout Prebid lifecycle. + serializer = new XMLSerializer(); + } + return serializer; + } + + function parse(xmlString) { + return getParser().parseFromString(xmlString, XML_MIME_TYPE); + } + + function serialize(xmlDoc) { + return getSerializer().serializeToString(xmlDoc); + } + + return { + parse, + serialize + }; +} diff --git a/modules/adplayerproVideoProvider.js b/modules/adplayerproVideoProvider.js index 1e4aa924d6d..ca6fb58a4b8 100644 --- a/modules/adplayerproVideoProvider.js +++ b/modules/adplayerproVideoProvider.js @@ -123,6 +123,10 @@ export function AdPlayerProProvider(config, adPlayerPro_, callbackStorage_, util setupPlayer(playerConfig, adTagUrl || options.adXml) } + function setAdXml(vastXml) { + setupPlayer(playerConfig, vastXml); + } + function onEvent(externalEventName, callback, basePayload) { if (externalEventName === SETUP_COMPLETE) { setupCompleteCallbacks.push(callback); @@ -192,6 +196,7 @@ export function AdPlayerProProvider(config, adPlayerPro_, callbackStorage_, util getOrtbVideo, getOrtbContent, setAdTagUrl, + setAdXml, onEvent, offEvent, destroy diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 773b3896270..c40ca46e593 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -18,10 +18,14 @@ import { isEmpty, isNumber, logError, + logWarn, parseSizesInput, parseUrl } from '../src/utils.js'; import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; +import { vastLocalCache } from '../src/videoCache.js'; +import { fetch } from '../src/ajax.js'; +import XMLUtil from '../libraries/xmlUtils/xmlUtils.js'; /** * @typedef {Object} DfpVideoParams * @@ -55,6 +59,8 @@ export const dep = { ri: getRefererInfo } +export const VAST_TAG_URI_TAGNAME = 'VASTAdTagURI'; + /** * Merge all the bid data and publisher-supplied options into a single URL, and then return it. * @@ -249,6 +255,72 @@ function getCustParams(bid, options, urlCustParams) { return encodedParams; } +async function getVastForLocallyCachedBids(gamVastWrapper, localCacheMap) { + try { + const xmlUtil = XMLUtil(); + const xmlDoc = xmlUtil.parse(gamVastWrapper); + const vastAdTagUriElement = xmlDoc.querySelectorAll(VAST_TAG_URI_TAGNAME)[0]; + + if (!vastAdTagUriElement || !vastAdTagUriElement.textContent) { + return gamVastWrapper; + } + + const uuidExp = new RegExp(`[A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}`, 'gi'); + const matchResult = Array.from(vastAdTagUriElement.textContent.matchAll(uuidExp)); + const uuidCandidates = matchResult + .map(([uuid]) => uuid) + .filter(uuid => localCacheMap.has(uuid)); + + if (uuidCandidates.length != 1) { + logWarn(`Unable to determine unique uuid in ${VAST_TAG_URI_TAGNAME}`); + return gamVastWrapper; + } + const uuid = uuidCandidates[0]; + + const blobUrl = localCacheMap.get(uuid); + const base64BlobContent = await getBase64BlobContent(blobUrl); + const cdata = xmlDoc.createCDATASection(base64BlobContent); + vastAdTagUriElement.textContent = ''; + vastAdTagUriElement.appendChild(cdata); + return xmlUtil.serialize(xmlDoc); + } catch (error) { + logWarn('Unable to process xml', error); + return gamVastWrapper; + } +}; + +export async function getVastXml(options, localCacheMap = vastLocalCache) { + const vastUrl = buildDfpVideoUrl(options); + const response = await fetch(vastUrl); + if (!response.ok) { + throw new Error('Unable to fetch GAM VAST wrapper'); + } + + const gamVastWrapper = await response.text(); + + if (config.getConfig('cache.useLocal')) { + const vastXml = await getVastForLocallyCachedBids(gamVastWrapper, localCacheMap); + return vastXml; + } + + return gamVastWrapper; +} + +export async function getBase64BlobContent(blobUrl) { + const response = await fetch(blobUrl); + if (!response.ok) { + logError('Unable to fetch blob'); + throw new Error('Blob not found'); + } + // Mechanism to handle cases where VAST tags are fetched + // from a context where the blob resource is not accessible. + // like IMA SDK iframe + const blobContent = await response.text(); + const dataUrl = `data://text/xml;base64,${btoa(blobContent)}`; + return dataUrl; +} + registerVideoSupport('dfp', { buildVideoUrl: buildDfpVideoUrl, + getVastXml }); diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index bca4d2db449..8b956ab1850 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -209,6 +209,14 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba player.playAd(adTagUrl || options.adXml, options); } + function setAdXml(vastXml, options) { + if (!player || !vastXml) { + return; + } + + player.loadAdXml(vastXml, options); + } + function onEvent(externalEventName, callback, basePayload) { if (externalEventName === SETUP_COMPLETE) { setupCompleteCallbacks.push(callback); @@ -496,6 +504,7 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba getOrtbVideo, getOrtbContent, setAdTagUrl, + setAdXml, onEvent, offEvent, destroy diff --git a/modules/videoModule/adQueue.js b/modules/videoModule/adQueue.js index 54cad4befc0..a98bd742294 100644 --- a/modules/videoModule/adQueue.js +++ b/modules/videoModule/adQueue.js @@ -9,7 +9,7 @@ export function AdQueueCoordinator(videoCore, pbEvents) { videoCore.onEvents([SETUP_COMPLETE], onSetupComplete, divId); } - function queueAd(adTagUrl, divId, options) { + function queueAd(adTagUrl, divId, options = {}) { const queue = storage[divId]; if (queue) { queue.push({adTagUrl, options}); @@ -53,7 +53,11 @@ export function AdQueueCoordinator(videoCore, pbEvents) { function loadAd(divId, adTagUrl, options) { triggerEvent(AUCTION_AD_LOAD_ATTEMPT, adTagUrl, options); - videoCore.setAdTagUrl(adTagUrl, divId, options); + if (options.prefetchedVastXml) { + videoCore.setAdXml(options.prefetchedVastXml, divId, options); + } else { + videoCore.setAdTagUrl(adTagUrl, divId, options); + } } function triggerEvent(eventName, adTagUrl, options) { diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js index 763ef0b25b9..1c18a77839c 100644 --- a/modules/videoModule/coreVideo.js +++ b/modules/videoModule/coreVideo.js @@ -165,6 +165,18 @@ export function VideoCore(parentModule_) { submodule && submodule.setAdTagUrl(adTagUrl, options); } + /** + * @name VideoCore#setAdXml + * @summary Requests that a player render the ad in the provided ad tag + * @param {string} vastXml - VAST content in xml format + * @param {string} divId - unique identifier of the player instance + * @param {Object} options - additional params + */ + function setAdXml(vastXml, divId, options) { + const submodule = parentModule.getSubmodule(divId); + submodule && submodule.setAdXml(vastXml, options); + } + /** * @name VideoCore#onEvents * @summary attaches event listeners @@ -216,6 +228,7 @@ export function VideoCore(parentModule_) { getOrtbVideo, getOrtbContent, setAdTagUrl, + setAdXml, onEvents, offEvents, hasProviderFor(divId) { diff --git a/modules/videoModule/gamAdServerSubmodule.js b/modules/videoModule/gamAdServerSubmodule.js index b5f5d7f4759..046ea37d5bd 100644 --- a/modules/videoModule/gamAdServerSubmodule.js +++ b/modules/videoModule/gamAdServerSubmodule.js @@ -8,12 +8,17 @@ import { getGlobal } from '../../src/prebidGlobal.js'; function GamAdServerProvider(dfpModule_) { const dfp = dfpModule_; - function getAdTagUrl(adUnit, baseAdTag, params) { - return dfp.buildVideoUrl({ adUnit: adUnit, url: baseAdTag, params }); + function getAdTagUrl(adUnit, baseAdTag, params, bid) { + return dfp.buildVideoUrl({ adUnit: adUnit, url: baseAdTag, params, bid }); + } + + async function getVastXml(adUnit, baseAdTag, params, bid) { + return dfp.getVastXml({ adUnit: adUnit, url: baseAdTag, params, bid }); } return { - getAdTagUrl + getAdTagUrl, + getVastXml } } diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index c84d98a6d5f..9e675170888 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -88,7 +88,8 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent const adUrl = bid.vastUrl; options.adXml = bid.vastXml; options.winner = bid.bidder; - loadAdTag(adUrl, divId, options); + + loadAd(adUrl, divId, options); } function getOrtbVideo(divId) { @@ -204,39 +205,49 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent return mergeDeep({}, globalVideoConfig.adServer, globalProviderConfig.adServer, adUnitVideoConfig.adServer); } - function renderWinningBid(adUnit) { + async function renderWinningBid(adUnit) { const adUnitCode = adUnit.code; - const options = { adUnitCode }; const videoConfig = adUnit.video; const divId = videoConfig.divId; + const adServerConfig = getAdServerConfig(videoConfig); - let adUrl; - if (adServerConfig) { - adUrl = gamSubmodule.getAdTagUrl(adUnit, adServerConfig.baseAdTagUrl, adServerConfig.params); + const winningBid = getWinningBid(adUnitCode); + + const options = { adUnitCode }; + + async function prefetchVast() { + const gamVastWrapper = await gamSubmodule.getVastXml( + adUnit, adServerConfig.baseAdTagUrl, adServerConfig.params, winningBid + ); + options.prefetchedVastXml = gamVastWrapper; } - if (adUrl) { - loadAdTag(adUrl, divId, options); - return; + if (adServerConfig) { + if (config.getConfig('cache.useLocal')) { + await prefetchVast(); + } else { + const adTagUrl = gamSubmodule.getAdTagUrl( + adUnit, adServerConfig.baseAdTagUrl, adServerConfig.params + ); + loadAd(adTagUrl, divId, options); + return; + } } + renderBid(divId, winningBid, options); + } + + function getWinningBid(adUnitCode) { const highestCpmBids = pbGlobal.getHighestCpmBids(adUnitCode); if (!highestCpmBids.length) { - pbEvents.emit(getExternalVideoEventName(AUCTION_AD_LOAD_ABORT), getExternalVideoEventPayload(AUCTION_AD_LOAD_ABORT, options)); + pbEvents.emit(getExternalVideoEventName(AUCTION_AD_LOAD_ABORT), getExternalVideoEventPayload(AUCTION_AD_LOAD_ABORT, {adUnitCode})); return; } - - const highestBid = highestCpmBids.shift(); - if (!highestBid) { - return; - } - - renderBid(divId, highestBid, options); + return highestCpmBids.shift(); } - // options: adXml, winner, adUnitCode, - function loadAdTag(adTagUrl, divId, options) { + function loadAd(adTagUrl, divId, options) { adQueueCoordinator.queueAd(adTagUrl, divId, options); } diff --git a/modules/videojsVideoProvider.js b/modules/videojsVideoProvider.js index 0be4c6feede..0eefe7da364 100644 --- a/modules/videojsVideoProvider.js +++ b/modules/videojsVideoProvider.js @@ -216,6 +216,22 @@ export function VideojsProvider(providerConfig, vjs_, adState_, timeState_, call } } + function setAdXml(vastXml) { + if (!player.ima || !vastXml) { + return; + } + + // The VideoJS IMA plugin version 1.11.0 will throw when the ad is empty. + try { + player.ima.controller.settings.adsResponse = vastXml; + player.ima.requestAds(); + } catch (e) { + /* + Handling is not required; ad errors are emitted automatically by video.js + */ + } + } + function onEvent(type, callback, payload) { registerSetupListeners(type, callback, payload); @@ -501,6 +517,7 @@ export function VideojsProvider(providerConfig, vjs_, adState_, timeState_, call getOrtbVideo, getOrtbContent, setAdTagUrl, + setAdXml, onEvent, offEvent, destroy diff --git a/src/auction.js b/src/auction.js index c85d7458a44..af29997230c 100644 --- a/src/auction.js +++ b/src/auction.js @@ -79,7 +79,7 @@ import { } from './utils.js'; import {getPriceBucketString} from './cpmBucketManager.js'; import {getNativeTargeting, isNativeResponse, setNativeResponseProperties} from './native.js'; -import {batchAndStore} from './videoCache.js'; +import {batchAndStore, storeLocally} from './videoCache.js'; import {Renderer} from './Renderer.js'; import {config} from './config.js'; import {userSync} from './userSync.js'; @@ -571,9 +571,17 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au })?.video; const context = videoMediaType && videoMediaType?.context; const useCacheKey = videoMediaType && videoMediaType?.useCacheKey; - - if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { - if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { + const { + useLocal, + url: cacheUrl, + ignoreBidderCacheKey + } = config.getConfig('cache') || {}; + + if (useLocal) { + // stores video bid vast as local blob in the browser + storeLocally(bidResponse); + } else if (cacheUrl && (useCacheKey || context !== OUTSTREAM)) { + if (!bidResponse.videoCacheKey || ignoreBidderCacheKey) { addBid = false; callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); } else if (!bidResponse.vastUrl) { @@ -581,6 +589,7 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au addBid = false; } } + if (addBid) { addBidToAuction(auctionInstance, bidResponse); afterBidAdded(); diff --git a/src/video.js b/src/video.js index a859fcb17bd..3df69d3aabd 100644 --- a/src/video.js +++ b/src/video.js @@ -120,10 +120,12 @@ export function isValidVideoBid(bid, {index = auctionManager.index} = {}) { export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaType, context, useCacheKey) { if (videoMediaType && (useCacheKey || context !== OUTSTREAM)) { // xml-only video bids require a prebid cache url - if (!config.getConfig('cache.url') && bid.vastXml && !bid.vastUrl) { + const { url, useLocal } = config.getConfig('cache') || {}; + if ((!url && !useLocal) && bid.vastXml && !bid.vastUrl) { logError(` This bid contains only vastXml and will not work when a prebid cache url is not specified. - Try enabling prebid cache with $$PREBID_GLOBAL$$.setConfig({ cache: {url: "..."} }); + Try enabling either prebid cache with $$PREBID_GLOBAL$$.setConfig({ cache: {url: "..."} }); + or local cache with $$PREBID_GLOBAL$$.setConfig({ cache: { useLocal: true }}); `); return false; } diff --git a/src/videoCache.js b/src/videoCache.js index cf39c1c9452..6f0076ca02b 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -12,7 +12,7 @@ import {ajaxBuilder} from './ajax.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; -import {logError, logWarn} from './utils.js'; +import {generateUUID, logError, logWarn} from './utils.js'; import {addBidToAuction} from './auction.js'; /** @@ -21,6 +21,8 @@ import {addBidToAuction} from './auction.js'; */ const ttlBufferInSeconds = 15; +export const vastLocalCache = new Map(); + /** * @typedef {object} CacheableUrlBid * @property {string} vastUrl A URL which loads some valid VAST XML. @@ -72,7 +74,7 @@ function wrapURI(uri, impTrackerURLs) { * @return {Object|null} - The payload to be sent to the prebid-server endpoints, or null if the bid can't be converted cleanly. */ function toStorageRequest(bid, {index = auctionManager.index} = {}) { - const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); + const vastValue = getVastXml(bid); const auction = index.getAuction(bid); const ttlWithBuffer = Number(bid.ttl) + ttlBufferInSeconds; let payload = { @@ -140,6 +142,10 @@ function shimStorageCallback(done) { } } +function getVastXml(bid) { + return bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); +}; + /** * If the given bid is for a Video ad, generate a unique ID and cache it somewhere server-side. * @@ -162,6 +168,22 @@ export function getCacheUrl(id) { return `${config.getConfig('cache.url')}?uuid=${id}`; } +export const storeLocally = (bid) => { + const vastXml = getVastXml(bid); + const bidVastUrl = URL.createObjectURL(new Blob([vastXml], { type: 'text/xml' })); + + assignVastUrlAndCacheId(bid, bidVastUrl); + + vastLocalCache.set(bid.videoCacheKey, bidVastUrl); +}; + +const assignVastUrlAndCacheId = (bid, vastUrl, videoCacheKey) => { + bid.videoCacheKey = videoCacheKey || generateUUID(); + if (!bid.vastUrl) { + bid.vastUrl = vastUrl; + } +} + export const _internal = { store } @@ -182,10 +204,7 @@ export function storeBatch(batch) { if (cacheId.uuid === '') { logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); } else { - bidResponse.videoCacheKey = cacheId.uuid; - if (!bidResponse.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); - } + assignVastUrlAndCacheId(bidResponse, getCacheUrl(bidResponse.videoCacheKey), cacheId.uuid); addBidToAuction(auctionInstance, bidResponse); afterBidAdded(); } @@ -194,15 +213,29 @@ export function storeBatch(batch) { }); }; -let batchSize, batchTimeout; +let batchSize, batchTimeout, cleanupHandler; if (FEATURES.VIDEO) { - config.getConfig('cache', (cacheConfig) => { - batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 - ? cacheConfig.cache.batchSize + config.getConfig('cache', ({cache}) => { + batchSize = typeof cache.batchSize === 'number' && cache.batchSize > 0 + ? cache.batchSize : 1; - batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 - ? cacheConfig.cache.batchTimeout + batchTimeout = typeof cache.batchTimeout === 'number' && cache.batchTimeout > 0 + ? cache.batchTimeout : 0; + + // removing blobs that are not going to be used + if (cache.useLocal && !cleanupHandler) { + cleanupHandler = auctionManager.onExpiry((auction) => { + auction.getBidsReceived() + .forEach((bid) => { + const vastUrl = vastLocalCache.get(bid.videoCacheKey) + if (vastUrl && vastUrl.startsWith('blob')) { + URL.revokeObjectURL(vastUrl); + } + vastLocalCache.delete(bid.videoCacheKey); + }) + }); + } }); } diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 75765771d1a..c0d5e9d5a33 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -14,6 +14,9 @@ import * as adServer from 'src/adserver.js'; import {hook} from '../../../src/hook.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {AuctionIndex} from '../../../src/auctionIndex.js'; +import { getVastXml } from '../../../modules/dfpAdServerVideo.js'; +import { server } from '../../mocks/xhr.js'; +import { generateUUID } from '../../../src/utils.js'; describe('The DFP video support module', function () { before(() => { @@ -706,4 +709,165 @@ describe('The DFP video support module', function () { expect(customParams).to.have.property('other_key', 'other_value'); expect(customParams).to.have.property('hb_rand', 'random'); }); + + it('should return unmodified fetched gam vast wrapper if local cache is not used', (done) => { + const gamWrapper = ( + `` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + server.respondWith(gamWrapper); + + getVastXml({}) + .then((finalGamWrapper) => { + expect(finalGamWrapper).to.deep.eql(gamWrapper); + done(); + }); + + server.respond(); + }); + + it('should substitue vast ad tag uri in gam wrapper with blob content in data uri format', (done) => { + config.setConfig({cache: { useLocal: true }}); + const url = 'https://pubads.g.doubleclick.net/gampad/ads' + const blobContent = '` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + + const dataUrl = `data://text/xml;base64,${btoa(blobContent)}`; + const expectedOutput = ( + `` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + + server.respondWith(/^https:\/\/pubads.*/, gamWrapper); + server.respondWith(/^blob:http:*/, blobContent); + + getVastXml({url, adUnit: {}, bid: {}}, localMap) + .then((vastXml) => { + expect(vastXml).to.deep.eql(expectedOutput); + done(); + }) + .finally(config.resetConfig); + + server.respond(); + + let timeout; + + const waitForSecondRequest = () => { + if (server.requests.length >= 2) { + server.respond(); + clearTimeout(timeout); + } else { + timeout = setTimeout(waitForSecondRequest, 50); + } + }; + + waitForSecondRequest(); + }); + + it('should return unmodified gam vast wrapper if it doesn\'nt contain locally cached uuid', (done) => { + config.setConfig({cache: { useLocal: true }}); + const uuidNotPresentInCache = '4536229c-eddb-45b3-a919-89d889e925aa'; + const uuidPresentInCache = '64fcdc86-5325-4750-bc60-02f63b23175a'; + const bidCacheUrl = 'https://prebid-test-cache-server.org/cache?uuid=' + uuidNotPresentInCache; + const gamWrapper = ( + `` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + const localCacheMap = new Map([[uuidPresentInCache, 'blob:http://localhost:9999/uri']]); + server.respondWith(gamWrapper); + + getVastXml({}, localCacheMap) + .then((finalGamWrapper) => { + expect(finalGamWrapper).to.deep.eql(gamWrapper); + done(); + }) + .finally(config.resetConfig) + + server.respond(); + }); + + it('should return unmodified gam vast wrapper if it contains more than 1 saved uuids', (done) => { + config.setConfig({cache: { useLocal: true }}); + const uuid1 = '4536229c-eddb-45b3-a919-89d889e925aa'; + const uuid2 = '64fcdc86-5325-4750-bc60-02f63b23175a'; + const bidCacheUrl = `https://prebid-test-cache-server.org/cache?uuid=${uuid1}&uuid_alt=${uuid2}` + const gamWrapper = ( + `` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + const localCacheMap = new Map([ + [uuid1, 'blob:http://localhost:9999/uri'], + [uuid2, 'blob:http://localhost:9999/uri'], + ]); + server.respondWith(gamWrapper); + + getVastXml({}, localCacheMap) + .then((finalGamWrapper) => { + expect(finalGamWrapper).to.deep.eql(gamWrapper); + done(); + }) + .finally(config.resetConfig) + + server.respond(); + }); + + it('should return returned unmodified gam vast wrapper if exception has been thrown', (done) => { + config.setConfig({cache: { useLocal: true }}); + const gamWrapper = ( + `` + + `` + + `` + + `prebid.org wrapper` + + `` + + `` + + `` + + `` + ); + server.respondWith(gamWrapper); + getVastXml({}, null) // exception thrown when passing null as localCacheMap + .then((finalGamWrapper) => { + expect(finalGamWrapper).to.deep.eql(gamWrapper); + done(); + }) + .finally(config.resetConfig); + server.respond(); + }); }); diff --git a/test/spec/modules/videoModule/adQueue_spec.js b/test/spec/modules/videoModule/adQueue_spec.js index 4002e0b6dcc..8c4ad7fd8c7 100644 --- a/test/spec/modules/videoModule/adQueue_spec.js +++ b/test/spec/modules/videoModule/adQueue_spec.js @@ -9,6 +9,7 @@ describe('Ad Queue Coordinator', function () { onEvents: sinon.spy(), offEvents: sinon.spy(), setAdTagUrl: sinon.spy(), + setAdXml: sinon.spy(), } }; @@ -58,6 +59,24 @@ describe('Ad Queue Coordinator', function () { expect(mockVideoCore.setAdTagUrl.calledOnce).to.be.true; }); + it('should run setAdXml instead of setAdTagUrl if vast has been prefetched', function () { + const mockVideoCore = mockVideoCoreFactory(); + const mockEvents = mockEventsFactory(); + let setupComplete; + mockVideoCore.onEvents = function(events, callback, id) { + if (events[0] === SETUP_COMPLETE && id === testId) { + setupComplete = callback; + } + }; + const coordinator = AdQueueCoordinator(mockVideoCore, mockEvents); + coordinator.registerProvider(testId); + coordinator.queueAd('testAdTag', testId, {prefetchedVastXml: ''}); + + setupComplete('', { divId: testId }); + expect(mockVideoCore.setAdXml.calledOnce).to.be.true; + expect(mockVideoCore.setAdTagUrl.calledOnce).to.be.false; + }); + it('should load ads without queueing', function () { const mockVideoCore = mockVideoCoreFactory(); const mockEvents = mockEventsFactory(); diff --git a/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js b/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js index 2304b2f2833..5ea75e8a6b1 100644 --- a/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js +++ b/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js @@ -1,5 +1,6 @@ import { vastXmlEditorFactory } from 'libraries/video/shared/vastXmlEditor.js'; import { expect } from 'chai'; +import { server } from '../../../../mocks/xhr'; describe('Vast XML Editor', function () { const adWrapperXml = ` diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 7d07da9de90..ae753d1794c 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -5,6 +5,7 @@ import {server} from 'test/mocks/xhr.js'; import {auctionManager} from '../../src/auctionManager.js'; import {AuctionIndex} from '../../src/auctionIndex.js'; import * as utils from 'src/utils.js'; +import { storeLocally } from '../../src/videoCache.js'; const should = chai.should(); @@ -396,6 +397,23 @@ describe('The video cache', function () { sinon.assert.called(utils.logError); }) }) + + describe('local video cache', function() { + afterEach(function () { + config.resetConfig(); + }); + + it('should store bid vast locally with blob by default', () => { + const bid = { + vastXml: `` + }; + + storeLocally(bid); + + expect(bid.vastUrl.startsWith('blob:http://')).to.be.true; + expect(bid.videoCacheKey).to.not.be.empty; + }); + }); }); describe('The getCache function', function () { From 68a91fc579d5228316985ff3725edc5e61e1e154 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 25 Mar 2025 16:31:17 +0100 Subject: [PATCH 1030/1097] PAAPI: parallel auction missing async signals (#12887) Co-authored-by: v.raybaud --- modules/paapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/paapi.js b/modules/paapi.js index d2e3d60546b..0f1144691fc 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -509,7 +509,7 @@ export function markForFledge(next, bidderRequests) { next(bidderRequests); } -export const ASYNC_SIGNALS = ['auctionSignals', 'sellerSignals', 'perBuyerSignals', 'perBuyerTimeouts', 'directFromSellerSignals']; +export const ASYNC_SIGNALS = ['auctionSignals', 'sellerSignals', 'perBuyerSignals', 'perBuyerTimeouts', 'directFromSellerSignals', 'perBuyerCurrencies', 'perBuyerCumulativeTimeouts']; const validatePartialConfig = (() => { const REQUIRED_SYNC_SIGNALS = [ From 3c59f01861f3dc0c5c4c04690d02b64f9c0194dc Mon Sep 17 00:00:00 2001 From: Nikhil <137479857+NikhilGopalChennissery@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:34:36 +0530 Subject: [PATCH 1031/1097] Endpoint updated (#12920) --- modules/precisoBidAdapter.js | 2 +- test/spec/modules/precisoBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 8c244dca040..880d1bf4b1c 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -13,7 +13,7 @@ const GVLID = 874; let precisoId = 'NA'; let sharedId = 'NA'; -const endpoint = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; +const endpoint = 'https://ssp-bidder.2trk.info/bid_request/openrtb'; let syncEndpoint = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; export const spec = { diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 9a5ce1ae330..83dea6951e5 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -154,7 +154,7 @@ describe('PrecisoAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://ssp-bidder.mndtrk.com/bid_request/openrtb'); + expect(serverRequest.url).to.equal('https://ssp-bidder.2trk.info/bid_request/openrtb'); }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; @@ -178,7 +178,7 @@ describe('PrecisoAdapter', function () { expect(ServeNativeRequest.url).to.exist; expect(ServeNativeRequest.data).to.exist; expect(ServeNativeRequest.method).to.equal('POST'); - expect(ServeNativeRequest.url).to.equal('https://ssp-bidder.mndtrk.com/bid_request/openrtb'); + expect(ServeNativeRequest.url).to.equal('https://ssp-bidder.2trk.info/bid_request/openrtb'); }); it('should extract the native params', function () { From 128c5ffb306d822a3ea44617e38735c0e75d9bc5 Mon Sep 17 00:00:00 2001 From: Tej <139129627+tej656@users.noreply.github.com> Date: Wed, 26 Mar 2025 01:23:43 +0530 Subject: [PATCH 1032/1097] PubxAI RTD Module : update docs (#12921) * send BidRejected Events to capture floored bids * fix tests * send pubx_id as query param * added extraData in analytics adapter to be sent in beacon data * added extraData in analytics adapter to be sent in beacon data * moved data read to session storage * bumped version * moving all data to localStorage again * updated test cases for pubxaiAA.js * fixing the missing logging of invalid bids * remove endpoint as optional --------- Co-authored-by: Phaneendra Hegde Co-authored-by: NikhilX Co-authored-by: Nathan Oliver --- modules/pubxaiRtdProvider.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/pubxaiRtdProvider.md b/modules/pubxaiRtdProvider.md index 2b89d3baa04..1855ebbcda7 100644 --- a/modules/pubxaiRtdProvider.md +++ b/modules/pubxaiRtdProvider.md @@ -37,7 +37,7 @@ pbjs.setConfig({ waitForIt: true, params: { pubxId: ``, - endpoint: ``, // (optional) + endpoint: ``, floorMin: ``, // (optional) enforcement: ``, // (optional) data: `` // (optional) @@ -57,7 +57,7 @@ pbjs.setConfig({ | waitForIt | Boolean | Should be `true` if an `auctionDelay` is defined (optional) | `false` | | params | Object | | | | params.pubxId | String | Publisher ID | | -| params.endpoint | String | URL to retrieve floor data (optional) | `https://floor.pbxai.com/` | +| params.endpoint | String | URL to retrieve floor data | | | params.floorMin | Number | Minimum CPM floor (optional) | `None` | | params.enforcement | Object | Enforcement behavior within the Price Floors Module (optional) | `None` | | params.data | Object | Default floor data provided by pubX.ai (optional) | `None` | From 5c178e905ca7306692cf8b0a07a88bba2f120ac0 Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:39:28 -0400 Subject: [PATCH 1033/1097] Contxtful Rtd Provider : add ad unit positions (#12792) * feat: adunitpos * fix: unused variable * doc: update * doc: space * feat: ortb2 fragment * refactor: use getBoundingClientRect * doc: js --------- Co-authored-by: rufiange --- modules/contxtfulRtdProvider.js | 240 +++++++++-- modules/contxtfulRtdProvider.md | 2 +- .../spec/modules/contxtfulRtdProvider_spec.js | 371 +++++++++++++++++- 3 files changed, 574 insertions(+), 39 deletions(-) diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 1bd977afed1..a3b92c75a79 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -16,17 +16,28 @@ import { buildUrl, isArray, generateUUID, + canAccessWindowTop, + deepAccess, + getSafeframeGeometry, + getWindowSelf, + getWindowTop, + inIframe, + isSafeFrameWindow, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +// Constants const MODULE_NAME = 'contxtful'; const MODULE = `${MODULE_NAME}RtdProvider`; const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io'; const CONTXTFUL_DEFER_DEFAULT = 0; +// Functions let _sm; function sm() { return _sm ??= generateUUID(); @@ -225,6 +236,161 @@ function getTargetingData(adUnits, config, _userConsent) { } } +function getVisibilityStateElement(domElement, windowTop) { + if ('checkVisibility' in domElement) { + return domElement.checkVisibility(); + } + + const elementCss = windowTop.getComputedStyle(domElement, null); + return elementCss.display !== 'none'; +} + +function getElementFromTopWindowRecurs(element, currentWindow) { + try { + if (getWindowTop() === currentWindow) { + return element; + } else { + const frame = currentWindow.frameElement; + const frameClientRect = getBoundingClientRect(frame); + const elementClientRect = getBoundingClientRect(element); + if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) { + return undefined; + } + return getElementFromTopWindowRecurs(frame, currentWindow.parent); + } + } catch (err) { + logError(MODULE, err); + return undefined; + } +} + +function getDivIdPosition(divId) { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { + return {}; + } + + const position = {}; + + if (isSafeFrameWindow()) { + const { self } = getSafeframeGeometry() ?? {}; + + if (!self) { + return {}; + } + + position.x = Math.round(self.t); + position.y = Math.round(self.l); + } else { + try { + // window.top based computing + const wt = getWindowTop(); + const d = wt.document; + + let domElement; + + if (inIframe() === true) { + const ws = getWindowSelf(); + const currentElement = ws.document.getElementById(divId); + domElement = getElementFromTopWindowRecurs(currentElement, ws); + } else { + domElement = wt.document.getElementById(divId); + } + + if (!domElement) { + return {}; + } + + let box = getBoundingClientRect(domElement); + const docEl = d.documentElement; + const body = d.body; + const clientTop = (d.clientTop ?? body.clientTop) ?? 0; + const clientLeft = (d.clientLeft ?? body.clientLeft) ?? 0; + const scrollTop = (wt.scrollY ?? docEl.scrollTop) ?? body.scrollTop; + const scrollLeft = (wt.scrollX ?? docEl.scrollLeft) ?? body.scrollLeft; + + position.visibility = getVisibilityStateElement(domElement, wt); + position.x = Math.round(box.left + scrollLeft - clientLeft); + position.y = Math.round(box.top + scrollTop - clientTop); + } catch (err) { + logError(MODULE, err); + return {}; + } + } + + return position; +} + +function tryGetDivIdPosition(divIdMethod) { + let divId = divIdMethod(); + if (divId) { + const divIdPosition = getDivIdPosition(divId); + if (divIdPosition.x !== undefined && divIdPosition.y !== undefined) { + return divIdPosition; + } + } + return undefined; +} + +function tryMultipleDivIdPositions(adUnit) { + let divMethods = [ + // ortb2\ + () => { + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + const ortb2Imp = deepAccess(adUnit, 'ortb2Imp'); + return deepAccess(ortb2Imp, 'ext.data.divId'); + }, + // gpt + () => getGptSlotInfoForAdUnitCode(adUnit.code).divId, + // adunit code + () => adUnit.code + ]; + + for (const divMethod of divMethods) { + let divPosition = tryGetDivIdPosition(divMethod); + if (divPosition) { + return divPosition; + } + } +} + +function tryGetAdUnitPosition(adUnit) { + let adUnitPosition = {}; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + + // try to get position with the divId + const divIdPosition = tryMultipleDivIdPositions(adUnit); + if (divIdPosition) { + adUnitPosition.p = { x: divIdPosition.x, y: divIdPosition.y }; + adUnitPosition.v = divIdPosition.visibility; + adUnitPosition.t = 'div'; + return adUnitPosition; + } + + // try to get IAB position + const iabPos = adUnit?.mediaTypes?.banner?.pos; + if (iabPos !== undefined) { + adUnitPosition.p = iabPos; + adUnitPosition.t = 'iab'; + return adUnitPosition; + } + + return undefined; +} + +function getAdUnitPositions(bidReqConfig) { + const adUnits = bidReqConfig.adUnits || []; + let adUnitPositions = {}; + + for (const adUnit of adUnits) { + let adUnitPosition = tryGetAdUnitPosition(adUnit); + if (adUnitPosition) { + adUnitPositions[adUnit.code] = adUnitPosition; + } + } + + return adUnitPositions; +} + /** * @param {Object} reqBidsConfigObj Bid request configuration object * @param {Function} onDone Called on completion @@ -235,7 +401,6 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { function onReturn() { onDone(); } - logInfo(MODULE, 'getBidRequestData'); const bidders = config?.params?.bidders || []; if (isEmpty(bidders) || !isArray(bidders)) { @@ -243,40 +408,49 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { return; } - let fromApi = rxApi?.receptivityBatched?.(bidders) || {}; - let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`)); - - let sources = [fromStorage, fromApi]; - - let rxBatch = Object.assign(...sources); - - let singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); - - bidders - .forEach(bidderCode => { - const ortb2 = { - user: { - data: [ - { - name: MODULE_NAME, - ext: { - rx: rxBatch[bidderCode], - events: singlePointEvents, - sm: sm(), - params: { - ev: config.params?.version, - ci: config.params?.customer, + let ortb2Fragment; + let getContxtfulOrtb2Fragment = rxApi?.getOrtb2Fragment; + if (typeof (getContxtfulOrtb2Fragment) == 'function') { + ortb2Fragment = getContxtfulOrtb2Fragment(bidders, reqBidsConfigObj); + } else { + const adUnitsPositions = getAdUnitPositions(reqBidsConfigObj); + + let fromApi = rxApi?.receptivityBatched?.(bidders) || {}; + let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`)); + + let sources = [fromStorage, fromApi]; + + let rxBatch = Object.assign(...sources); + + let singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); + ortb2Fragment = {}; + ortb2Fragment.bidder = Object.fromEntries( + bidders + .map(bidderCode => { + return [bidderCode, { + user: { + data: [ + { + name: MODULE_NAME, + ext: { + rx: rxBatch[bidderCode], + events: singlePointEvents, + pos: btoa(JSON.stringify(adUnitsPositions)), + sm: sm(), + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, }, - }, + ], }, - ], - }, - }; - - mergeDeep(reqBidsConfigObj.ortb2Fragments?.bidder, { - [bidderCode]: ortb2, - }); - }); + } + ] + })); + } + + mergeDeep(reqBidsConfigObj.ortb2Fragments, ortb2Fragment); onReturn(); } diff --git a/modules/contxtfulRtdProvider.md b/modules/contxtfulRtdProvider.md index de2376e782d..8fef61b3d96 100644 --- a/modules/contxtfulRtdProvider.md +++ b/modules/contxtfulRtdProvider.md @@ -8,7 +8,7 @@ The Contxtful RTD module offers a unique feature—Receptivity. Receptivity is an efficiency metric, enabling the qualification of any instant in a session in real time based on attention. The core idea is straightforward: the likelihood of an ad’s success increases when it grabs attention and is presented in the right context at the right time. -To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please reach out to [contact@contxtful.com](mailto:contact@contxtful.com). +To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please reach out to [contact@contxtful.com](mailto:contact@contxtful.com). ## Build Instructions diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index 68e38d63364..be8e13a5a58 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -5,6 +5,7 @@ import { getStorageManager } from '../../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../../src/activities/modules.js'; import * as events from '../../../src/events'; import * as utils from 'src/utils.js'; +import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js' import Sinon from 'sinon'; import { deepClone } from '../../../src/utils.js'; @@ -19,13 +20,15 @@ const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_ const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' }; const RX_API_MOCK = { receptivity: sinon.stub(), receptivityBatched: sinon.stub() }; +const RX_API_MOCK_WITH_BUNDLE = { receptivity: sinon.stub(), receptivityBatched: sinon.stub(), getOrtb2Fragment: sinon.stub() } + const RX_CONNECTOR_MOCK = { fetchConfig: sinon.stub(), rxApiBuilder: sinon.stub(), }; const TIMEOUT = 10; -const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: {[CUSTOMER]: RX_CONNECTOR_MOCK}, bubbles: true }); +const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: { [CUSTOMER]: RX_CONNECTOR_MOCK }, bubbles: true }); function buildInitConfig(version, customer) { return { @@ -40,6 +43,24 @@ function buildInitConfig(version, customer) { }; } +function fakeGetElementById(width, height, x, y) { + const obj = { x, y, width, height }; + + return { + ...obj, + getBoundingClientRect: () => { + return { + width: obj.width, + height: obj.height, + left: obj.x, + top: obj.y, + right: obj.x + obj.width, + bottom: obj.y + obj.height + }; + } + }; +} + describe('contxtfulRtdProvider', function () { let sandbox = sinon.sandbox.create(); let loadExternalScriptTag; @@ -57,6 +78,19 @@ describe('contxtfulRtdProvider', function () { RX_API_MOCK.receptivityBatched.reset(); RX_API_MOCK.receptivityBatched.callsFake((bidders) => bidders.reduce((accumulator, bidder) => { accumulator[bidder] = RX_FROM_API; return accumulator; }, {})); + RX_API_MOCK_WITH_BUNDLE.receptivity.reset(); + RX_API_MOCK_WITH_BUNDLE.receptivity.callsFake(() => RX_FROM_API); + + RX_API_MOCK_WITH_BUNDLE.receptivityBatched.reset(); + RX_API_MOCK_WITH_BUNDLE.receptivityBatched.callsFake((bidders) => bidders.reduce((accumulator, bidder) => { accumulator[bidder] = RX_FROM_API; return accumulator; }, {})); + + RX_API_MOCK_WITH_BUNDLE.getOrtb2Fragment.reset(); + RX_API_MOCK_WITH_BUNDLE.getOrtb2Fragment.callsFake((bidders, reqBidsConfigObj) => { + let bidderObj = bidders.reduce((accumulator, bidder) => { accumulator[bidder] = { user: { data: [{ name: MODULE_NAME, value: RX_FROM_API }] } }; return accumulator; }, {}); + return { global: { user: { site: { id: 'globalsiteId' } } }, bidder: bidderObj } + } + ); + RX_CONNECTOR_MOCK.fetchConfig.reset(); RX_CONNECTOR_MOCK.fetchConfig.callsFake((tagId) => new Promise((resolve, reject) => resolve({ tag_id: tagId }))); @@ -332,7 +366,7 @@ describe('contxtfulRtdProvider', function () { theories.forEach(([adUnits, expected, _description]) => { it('uses non-expired info from session storage and adds receptivity to the ad units using session storage', function (done) { // Simulate that there was a write to sessionStorage in the past. - storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE})) + storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({ exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE })) let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); @@ -365,7 +399,7 @@ describe('contxtfulRtdProvider', function () { theories.forEach(([adUnits, expected, _description]) => { it('ignores expired info from session storage and does not forward the info to ad units', function (done) { // Simulate that there was a write to sessionStorage in the past. - storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({exp: new Date().getTime() - 100, rx: RX_FROM_SESSION_STORAGE})); + storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({ exp: new Date().getTime() - 100, rx: RX_FROM_SESSION_STORAGE })); let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); @@ -466,7 +500,7 @@ describe('contxtfulRtdProvider', function () { // Simulate that there was a write to sessionStorage in the past. let bidder = config.params.bidders[0]; - storage.setDataInSessionStorage(`${config.params.customer}_${bidder}`, JSON.stringify({exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE})); + storage.setDataInSessionStorage(`${config.params.customer}_${bidder}`, JSON.stringify({ exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE })); let reqBidsConfigObj = { ortb2Fragments: { @@ -478,7 +512,7 @@ describe('contxtfulRtdProvider', function () { contxtfulSubmodule.init(config); // Since the RX_CONNECTOR_IS_READY_EVENT event was not dispatched, the RX engine is not loaded. - contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, () => {}, config); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, () => { }, config); setTimeout(() => { let ortb2BidderFragment = reqBidsConfigObj.ortb2Fragments.bidder[bidder]; @@ -659,7 +693,302 @@ describe('contxtfulRtdProvider', function () { done(); }, TIMEOUT); }); + }); + }); + + describe('when there is no ad units', function () { + it('adds empty ad unit positions', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(0); + done(); + }, TIMEOUT); + }); + }); + + describe('when there are ad units', function () { + it('return empty objects for ad units that we can\'t get position of', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + let reqBidsConfigObj = { + adUnits: [ + { code: 'code1' }, + { code: 'code2' } + ], + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(0); + done(); + }, TIMEOUT); + }); + + it('returns the IAB position if the ad unit div id cannot be bound but property pos can be found in the ad unit', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + let reqBidsConfigObj = { + adUnits: [ + { code: 'code1', mediaTypes: { banner: { pos: 4 } } }, + { code: 'code2', mediaTypes: { banner: { pos: 5 } } }, + { code: 'code3', mediaTypes: { banner: { pos: 0 } } }, + ], + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(3); + expect(pos['code1'].p).to.be.equal(4); + expect(pos['code2'].p).to.be.equal(5); + expect(pos['code3'].p).to.be.equal(0); + done(); + }, TIMEOUT); }) + + function getFakeRequestBidConfigObj() { + return { + adUnits: [ + { code: 'code1', ortb2Imp: { ext: { data: { divId: 'divId1' } } } }, + { code: 'code2', ortb2Imp: { ext: { data: { divId: 'divId2' } } } } + ], + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + } + + function InitDivStubPositions(config, withIframe, isVisible, forceGetElementById = true) { + let fakeElem = fakeGetElementById(100, 100, 30, 30); + if (isVisible) { + fakeElem.checkVisibility = function () { return true }; + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + } else { + fakeElem.checkVisibility = function () { return false }; + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'none' }); + } + + if (withIframe) { + let ws = { + frameElement: { + getBoundingClientRect: () => fakeElem.getBoundingClientRect() + }, + document: { + getElementById: (id) => fakeElem, + + } + } + sandbox.stub(utils, 'getWindowSelf').returns(window.top); + sandbox.stub(utils, 'inIframe').returns(true); + sandbox.stub(fakeElem, 'checkVisibility').returns(isVisible); + } else { + sandbox.stub(utils, 'inIframe').returns(false); + sandbox.stub(fakeElem, 'checkVisibility').returns(isVisible); + } + if (forceGetElementById) { + sandbox.stub(window.top.document, 'getElementById').returns(fakeElem); + } + contxtfulSubmodule.init(config); + } + + describe('when the div id cannot be found, we should try with GPT method', function () { + it('returns an empty list if gpt not find the div', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + let reqBidsConfigObj = { + adUnits: [ + { code: 'code1' }, + { code: 'code2' } + ], + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + InitDivStubPositions(config, false, true, false); + let fakeElem = fakeGetElementById(100, 100, 30, 30); + sandbox.stub(window.top.document, 'getElementById').returns(function (id) { + if (id == 'code1' || id == 'code2') { + return undefined; + } else { + return fakeElem; + } + }); + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(0); + done(); + }, TIMEOUT); + }) + + it('returns object visibility and position if gpt not found but the div id is the ad unit code', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + let reqBidsConfigObj = { + adUnits: [ + { code: 'code1' }, + { code: 'code2' } + ], + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + InitDivStubPositions(config, false, true); + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(2); + expect(pos['code1'].p.x).to.be.equal(30); + expect(pos['code1'].p.y).to.be.equal(30); + expect(pos['code1'].v).to.be.equal(true); + done(); + }, TIMEOUT); + }); + + it('returns object visibility and position if gpt finds the div', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + let reqBidsConfigObj = { + adUnits: [ + { code: 'code1' }, + { code: 'code2' } + ], + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + InitDivStubPositions(config, false, true); + sandbox.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ divId: 'div1' }); + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(2); + expect(pos['code1'].p.x).to.be.equal(30); + expect(pos['code1'].p.y).to.be.equal(30); + expect(pos['code1'].v).to.be.equal(true); + done(); + }, TIMEOUT); + }); + }); + + describe('when we get object visibility and position for ad units that we can get div id', function () { + let config = buildInitConfig(VERSION, CUSTOMER); + + describe('when we are not in an iframe', function () { + it('return object visibility true if element is visible', function (done) { + let reqBidsConfigObj = getFakeRequestBidConfigObj(); + InitDivStubPositions(config, false, true); + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(2); + expect(pos['code1'].p.x).to.be.equal(30); + expect(pos['code1'].p.y).to.be.equal(30); + expect(pos['code1'].v).to.be.equal(true); + done(); + }, TIMEOUT); + }); + + it('return object visibility false if element is not visible', function (done) { + let reqBidsConfigObj = getFakeRequestBidConfigObj(); + InitDivStubPositions(config, false, false); + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(2); + expect(pos['code1'].v).to.be.equal(false); + expect(pos['code2'].v).to.be.equal(false); + done(); + }, TIMEOUT); + }); + }); + + describe('when we are in an iframe', function () { + it('return object visibility true if element is visible', function (done) { + let reqBidsConfigObj = getFakeRequestBidConfigObj(); + InitDivStubPositions(config, true, true) + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(2); + expect(pos['code1'].p.x).to.be.equal(30); + expect(pos['code1'].p.y).to.be.equal(30); + expect(pos['code1'].v).to.be.equal(true); + done(); + }, TIMEOUT); + }); + + it('return object visibility false if element is not visible', function (done) { + let reqBidsConfigObj = getFakeRequestBidConfigObj(); + InitDivStubPositions(config, true, false); + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + let pos = JSON.parse(atob(ext.pos)); + + expect(Object.keys(pos).length).to.be.equal(2); + expect(pos['code1'].v).to.be.equal(false); + done(); + }, TIMEOUT); + }); + }); + }); }); describe('after rxApi is loaded', function () { @@ -688,4 +1017,36 @@ describe('contxtfulRtdProvider', function () { }, TIMEOUT); }); }) + + describe('when rxConnector contains getOrtb2Fragment function', () => { + it('should just take whatever it contains and merge to the fragment', function (done) { + RX_CONNECTOR_MOCK.rxApiBuilder.reset(); + RX_CONNECTOR_MOCK.rxApiBuilder.callsFake((_config) => new Promise((resolve, reject) => resolve(RX_API_MOCK_WITH_BUNDLE))); + + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + let global = reqBidsConfigObj.ortb2Fragments.global; + let bidder = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]; + + let globalExpected = { user: { site: { id: 'globalsiteId' } } }; + let bidderExpected = { user: { data: [{ name: MODULE_NAME, value: RX_FROM_API }] } }; + expect(RX_API_MOCK_WITH_BUNDLE.getOrtb2Fragment.callCount).to.equal(1); + expect(global).to.deep.equal(globalExpected); + expect(bidder).to.deep.equal(bidderExpected); + done(); + }, TIMEOUT); + }) + }) }); From b0e77c70981b81409f978f799024a841eb8b9204 Mon Sep 17 00:00:00 2001 From: Monis Qadri Date: Wed, 26 Mar 2025 21:36:55 +0530 Subject: [PATCH 1034/1097] added medianet in codepath-notification (#12913) --- .github/workflows/scripts/codepath-notification | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/codepath-notification b/.github/workflows/scripts/codepath-notification index 081b5dde89d..cdbc30b0e97 100644 --- a/.github/workflows/scripts/codepath-notification +++ b/.github/workflows/scripts/codepath-notification @@ -15,3 +15,4 @@ rubicon|magnite : header-bidding@magnite.com appnexus : prebid@microsoft.com pubmatic : header-bidding@pubmatic.com openx : prebid@openx.com +medianet : prebid@media.net From 46bf79d50520b2819ede8c3c087c420c5f8a9659 Mon Sep 17 00:00:00 2001 From: Sam Evans <98488535+evans-sam@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:12:33 -0600 Subject: [PATCH 1035/1097] Adds a flag to suppress losing bid custom targeting values (#12911) --- src/targeting.js | 7 ++- test/spec/unit/core/targeting_spec.js | 69 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/targeting.js b/src/targeting.js index 22c161378e0..14495c59962 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -338,11 +338,16 @@ export function newTargeting(auctionManager) { } function getTargetingLevels(bidsSorted, customKeysByUnit, adUnitCodes) { + const useAllBidsCustomTargeting = config.getConfig('targetingControls.allBidsCustomTargeting') !== false; + const targeting = getWinningBidTargeting(bidsSorted, adUnitCodes) - .concat(getCustomBidTargeting(bidsSorted, customKeysByUnit)) .concat(getBidderTargeting(bidsSorted)) .concat(getAdUnitTargeting(adUnitCodes)); + if (useAllBidsCustomTargeting) { + targeting.push(...getCustomBidTargeting(bidsSorted, customKeysByUnit)) + } + targeting.forEach(adUnitCode => { updatePBTargetingKeys(adUnitCode); }); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index f9ab54db2fc..2706de0fd5c 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -920,6 +920,75 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.allBidsCustomTargeting', function () { + beforeEach(function () { + const winningBid = deepClone(bid1); + winningBid.adserverTargeting.foobar = 'winner'; + + const losingBid = utils.deepClone(bid2); + losingBid.adserverTargeting = { + hb_deal: '4321', + hb_pb: '0.1', + hb_adid: '567891011', + hb_bidder: 'appnexus', + foobar: 'loser' + }; + losingBid.bidder = losingBid.bidderCode = 'appnexus'; + losingBid.cpm = 0.1; + + bidsReceived = [winningBid, losingBid]; + enableSendAllBids = false; + }); + + afterEach(function () { + config.resetConfig(); + }); + + it('should merge custom targeting from all bids by default', function () { + // Default behavior - no specific configuration + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Custom key values from both bids should be combined to maintain existing functionality + expect(targeting['/123456/header-bid-tag-0']).to.have.property('foobar'); + expect(targeting['/123456/header-bid-tag-0']['foobar']).to.equal('winner,loser'); + }); + + it('should only use custom targeting from winning bid when allBidsCustomTargeting=false', function () { + // Set allBidsCustomTargeting to false + config.setConfig({ + targetingControls: { + allBidsCustomTargeting: false + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Only the winning bid's custom key value should be used + expect(targeting['/123456/header-bid-tag-0']).to.have.property('foobar'); + expect(targeting['/123456/header-bid-tag-0']['foobar']).to.equal('winner'); + }); + + it('should handle multiple custom keys correctly when allBidsCustomTargeting=false', function () { + // Add another custom key to the bids + bidsReceived[0].adserverTargeting.custom1 = 'value1'; + bidsReceived[1].adserverTargeting.custom2 = 'value2'; + + config.setConfig({ + targetingControls: { + allBidsCustomTargeting: false + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Only winning bid's custom values should be present + expect(targeting['/123456/header-bid-tag-0']).to.have.property('foobar'); + expect(targeting['/123456/header-bid-tag-0'].foobar).to.equal('winner'); + expect(targeting['/123456/header-bid-tag-0']).to.have.property('custom1'); + expect(targeting['/123456/header-bid-tag-0']).to.not.have.property('custom2'); + }); + }); + it('selects the top bid when enableSendAllBids true', function () { enableSendAllBids = true; let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); From b25e340315a2d7a163b3950c640c59d2180fd329 Mon Sep 17 00:00:00 2001 From: Komal Kumari <169047654+pm-komal-kumari@users.noreply.github.com> Date: Thu, 27 Mar 2025 01:12:33 +0530 Subject: [PATCH 1036/1097] PubMatic RTD Provider - Initial Release (#12732) * Initial release PubMatic RTD * PubMatic RTD: Update browser regex mapping and add description in md file * PubMatic RTD: Add country in floor schema, use client hint for browser, log ctr in logger * PubMatic Analytics : Update browser mapping * PubMatic RTD: Update md file, browser regex, browser test cases * PubMatic Analytics: Handle null checks * Pubmatic RTD : set ext in ortb2 only when country is present * Pubmatic RTD : Update md file * Pubmatic RTD : delete endpoint property from floors --------- Co-authored-by: Komal Kumari --- modules/.submodules.json | 1 + modules/pubmaticAnalyticsAdapter.js | 33 ++ modules/pubmaticRtdProvider.js | 242 ++++++++ modules/pubmaticRtdProvider.md | 72 +++ test/spec/modules/pubmaticRtdProvider_spec.js | 531 ++++++++++++++++++ 5 files changed, 879 insertions(+) create mode 100644 modules/pubmaticRtdProvider.js create mode 100644 modules/pubmaticRtdProvider.md create mode 100644 test/spec/modules/pubmaticRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 92932beb445..b45995e4d15 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -99,6 +99,7 @@ "optimeraRtdProvider", "oxxionRtdProvider", "permutiveRtdProvider", + "pubmaticRtdProvider", "pubxaiRtdProvider", "qortexRtdProvider", "reconciliationRtdProvider", diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 6a3b402c6fe..dc08595bc7d 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -50,6 +50,22 @@ const MEDIATYPE = { NATIVE: 2 } +// TODO : Remove - Once BM calculation moves to Server Side +const BROWSER_MAP = [ + { value: /(firefox)\/([\w\.]+)/i, key: 12 }, // Firefox + { value: /\b(?:crios)\/([\w\.]+)/i, key: 1 }, // Chrome for iOS + { value: /edg(?:e|ios|a)?\/([\w\.]+)/i, key: 2 }, // Edge + { value: /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i, key: 3 }, // Opera + { value: /(?:ms|\()(ie) ([\w\.]+)/i, key: 4 }, // Internet Explorer + { value: /fxios\/([-\w\.]+)/i, key: 5 }, // Firefox for iOS + { value: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i, key: 6 }, // Facebook In-App Browser + { value: / wv\).+(chrome)\/([\w\.]+)/i, key: 7 }, // Chrome WebView + { value: /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i, key: 8 }, // Android Browser + { value: /(chrome|chromium|crios)\/v?([\w\.]+)/i, key: 9 }, // Chrome + { value: /version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i, key: 10 }, // Safari Mobile + { value: /version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i, key: 11 }, // Safari +]; + /// /////////// VARIABLES ////////////// let publisherId = DEFAULT_PUBLISHER_ID; // int: mandatory let profileId = DEFAULT_PROFILE_ID; // int: optional @@ -204,6 +220,17 @@ function getDevicePlatform() { return deviceType; } +// TODO : Remove - Once BM calculation moves to Server Side +function getBrowserType() { + const userAgent = navigator?.userAgent; + let browserIndex = userAgent == null ? -1 : 0; + + if (userAgent) { + browserIndex = BROWSER_MAP.find(({ value }) => value.test(userAgent))?.key || 0; + } + return browserIndex; +} + function getValueForKgpv(bid, adUnitId) { if (bid.params && bid.params.regexPattern) { return bid.params.regexPattern; @@ -409,6 +436,10 @@ function executeBidsLoggerCall(e, highestCpmBids) { let outputObj = { s: [] }; let pixelURL = END_POINT_BID_LOGGER; + const user = e.bidderRequests?.length > 0 + ? e.bidderRequests.find(bidder => bidder?.bidderCode === ADAPTER_CODE)?.ortb2?.user?.ext || {} + : {}; + if (!auctionCache || auctionCache.sent) { return; } @@ -426,6 +457,8 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tgid'] = getTgId(); outputObj['dm'] = DISPLAY_MANAGER; outputObj['dmv'] = '$prebid.version$' || '-1'; + outputObj['bm'] = getBrowserType(); + outputObj['ctr'] = Object.keys(user)?.length ? user.ctr : ''; if (floorData) { const floorRootValues = getFloorsCommonField(floorData?.floorRequestData); diff --git a/modules/pubmaticRtdProvider.js b/modules/pubmaticRtdProvider.js new file mode 100644 index 00000000000..3f5edf4b7d8 --- /dev/null +++ b/modules/pubmaticRtdProvider.js @@ -0,0 +1,242 @@ +import { submodule } from '../src/hook.js'; +import { logError, isStr, logMessage, isPlainObject, isEmpty, isFn, mergeDeep } from '../src/utils.js'; +import { config as conf } from '../src/config.js'; +import { getDeviceType as fetchDeviceType, getOS } from '../libraries/userAgentUtils/index.js'; +import { getLowEntropySUA } from '../src/fpd/sua.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + +/** + * This RTD module has a dependency on the priceFloors module. + * We utilize the continueAuction function from the priceFloors module to incorporate price floors data into the current auction. + */ +import { continueAuction } from './priceFloors.js'; // eslint-disable-line prebid/validate-imports + +const CONSTANTS = Object.freeze({ + SUBMODULE_NAME: 'pubmatic', + REAL_TIME_MODULE: 'realTimeData', + LOG_PRE_FIX: 'PubMatic-Rtd-Provider: ', + UTM: 'utm_', + UTM_VALUES: { + TRUE: '1', + FALSE: '0' + }, + TIME_OF_DAY_VALUES: { + MORNING: 'morning', + AFTERNOON: 'afternoon', + EVENING: 'evening', + NIGHT: 'night', + }, + ENDPOINTS: { + FLOORS_BASEURL: `https://ads.pubmatic.com/AdServer/js/pwt/floors/`, + FLOORS_ENDPOINT: `/floors.json`, + } +}); + +const BROWSER_REGEX_MAP = [ + { regex: /\b(?:crios)\/([\w\.]+)/i, id: 1 }, // Chrome for iOS + { regex: /(edg|edge)(?:e|ios|a)?(?:\/([\w\.]+))?/i, id: 2 }, // Edge + { regex: /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i, id: 3 }, // Opera + { regex: /(?:ms|\()(ie) ([\w\.]+)/i, id: 4 }, // Internet Explorer + { regex: /fxios\/([-\w\.]+)/i, id: 5 }, // Firefox for iOS + { regex: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i, id: 6 }, // Facebook In-App Browser + { regex: / wv\).+(chrome)\/([\w\.]+)/i, id: 7 }, // Chrome WebView + { regex: /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i, id: 8 }, // Android Browser + { regex: /(chrome|crios)(?:\/v?([\w\.]+))?\b/i, id: 9 }, // Chrome + { regex: /version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i, id: 10 }, // Safari Mobile + { regex: /version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i, id: 11 }, // Safari + { regex: /(firefox)\/([\w\.]+)/i, id: 12 } // Firefox +]; + +let _pubmaticFloorRulesPromise = null; +export let _country; + +// Utility Functions +export const getCurrentTimeOfDay = () => { + const currentHour = new Date().getHours(); + + return currentHour < 5 ? CONSTANTS.TIME_OF_DAY_VALUES.NIGHT + : currentHour < 12 ? CONSTANTS.TIME_OF_DAY_VALUES.MORNING + : currentHour < 17 ? CONSTANTS.TIME_OF_DAY_VALUES.AFTERNOON + : currentHour < 19 ? CONSTANTS.TIME_OF_DAY_VALUES.EVENING + : CONSTANTS.TIME_OF_DAY_VALUES.NIGHT; +} + +export const getBrowserType = () => { + const brandName = getLowEntropySUA()?.browsers + ?.map(b => b.brand.toLowerCase()) + .join(' ') || ''; + const browserMatch = brandName ? BROWSER_REGEX_MAP.find(({ regex }) => regex.test(brandName)) : -1; + + if (browserMatch?.id) return browserMatch.id.toString(); + + const userAgent = navigator?.userAgent; + let browserIndex = userAgent == null ? -1 : 0; + + if (userAgent) { + browserIndex = BROWSER_REGEX_MAP.find(({ regex }) => regex.test(userAgent))?.id || 0; + } + return browserIndex.toString(); +} + +// Getter Functions + +export const getOs = () => getOS().toString(); + +export const getDeviceType = () => fetchDeviceType().toString(); + +export const getCountry = () => _country; + +export const getUtm = () => { + const url = new URL(window.location?.href); + const urlParams = new URLSearchParams(url?.search); + return urlParams && urlParams.toString().includes(CONSTANTS.UTM) ? CONSTANTS.UTM_VALUES.TRUE : CONSTANTS.UTM_VALUES.FALSE; +} + +export const getFloorsConfig = (apiResponse) => { + let defaultFloorConfig = conf.getConfig()?.floors ?? {}; + + if (defaultFloorConfig?.endpoint) { + delete defaultFloorConfig.endpoint + } + defaultFloorConfig.data = { ...apiResponse }; + + const floorsConfig = { + floors: { + additionalSchemaFields: { + deviceType: getDeviceType, + timeOfDay: getCurrentTimeOfDay, + browser: getBrowserType, + os: getOs, + utm: getUtm, + country: getCountry, + }, + ...defaultFloorConfig + }, + }; + + return floorsConfig; +}; + +export const setFloorsConfig = (data) => { + if (data && isPlainObject(data) && !isEmpty(data)) { + conf.setConfig(getFloorsConfig(data)); + } else { + logMessage(CONSTANTS.LOG_PRE_FIX + 'The fetched floors data is empty.'); + } +}; + +export const setPriceFloors = async (publisherId, profileId) => { + const apiResponse = await fetchFloorRules(publisherId, profileId); + + if (!apiResponse) { + logError(CONSTANTS.LOG_PRE_FIX + 'Error while fetching floors: Empty response'); + } else { + setFloorsConfig(apiResponse); + } +}; + +export const fetchFloorRules = async (publisherId, profileId) => { + try { + const url = `${CONSTANTS.ENDPOINTS.FLOORS_BASEURL}${publisherId}/${profileId}${CONSTANTS.ENDPOINTS.FLOORS_ENDPOINT}`; + + const response = await fetch(url); + if (!response?.ok) { + logError(CONSTANTS.LOG_PRE_FIX + 'Error while fetching floors: No response'); + } + + const cc = response.headers?.get('country_code'); + _country = cc ? cc.split(',')?.map(code => code.trim())[0] : undefined; + + const data = await response.json(); + return data; + } catch (error) { + logError(CONSTANTS.LOG_PRE_FIX + 'Error while fetching floors:', error); + } +} + +/** + * Initialize the Pubmatic RTD Module. + * @param {Object} config + * @param {Object} _userConsent + * @returns {boolean} + */ +const init = (config, _userConsent) => { + const publisherId = config?.params?.publisherId; + const profileId = config?.params?.profileId; + + if (!publisherId || !isStr(publisherId) || !profileId || !isStr(profileId)) { + logError( + `${CONSTANTS.LOG_PRE_FIX} ${!publisherId ? 'Missing publisher Id.' + : !isStr(publisherId) ? 'Publisher Id should be a string.' + : !profileId ? 'Missing profile Id.' + : 'Profile Id should be a string.' + }` + ); + return false; + } + + if (!isFn(continueAuction)) { + logError(`${CONSTANTS.LOG_PRE_FIX} continueAuction is not a function. Please ensure to add priceFloors module.`); + return false; + } + + _pubmaticFloorRulesPromise = setPriceFloors(publisherId, profileId); + return true; +} + +/** + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} config + * @param {Object} userConsent + */ + +const getBidRequestData = (reqBidsConfigObj, callback) => { + _pubmaticFloorRulesPromise.then(() => { + const hookConfig = { + reqBidsConfigObj, + context: this, + nextFn: () => true, + haveExited: false, + timer: null + }; + continueAuction(hookConfig); + if (_country) { + const ortb2 = { + user: { + ext: { + ctr: _country, + } + } + } + + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { + [CONSTANTS.SUBMODULE_NAME]: ortb2 + }); + } + callback(); + }).catch((error) => { + logError(CONSTANTS.LOG_PRE_FIX, 'Error in updating floors :', error); + callback(); + }); +} + +/** @type {RtdSubmodule} */ +export const pubmaticSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: CONSTANTS.SUBMODULE_NAME, + init, + getBidRequestData, +}; + +export const registerSubModule = () => { + submodule(CONSTANTS.REAL_TIME_MODULE, pubmaticSubmodule); +} + +registerSubModule(); diff --git a/modules/pubmaticRtdProvider.md b/modules/pubmaticRtdProvider.md new file mode 100644 index 00000000000..c4c273de6fb --- /dev/null +++ b/modules/pubmaticRtdProvider.md @@ -0,0 +1,72 @@ +## Overview + +- Module Name: PubMatic RTD Provider +- Module Type: RTD Adapter +- Maintainer: header-bidding@pubmatic.com + +## Description + +The PubMatic RTD module fetches pricing floor data and updates the Price Floors Module based on user's context in real-time as per Price Floors Modules Floor Data Provider Interface guidelines [Dynamic Floor Data Provider](https://docs.prebid.org/dev-docs/modules/floors.html#floor-data-provider-interface). + +## Usage + +Step 1: Contact PubMatic to get a publisher ID and create your first profile. + +Step 2: Integrate the PubMatic Analytics Adapter (see Prebid Analytics modules) as well as the Price Floors module. + +Step 3: Prepare the base Prebid file. + +For example: + +To compile the Price Floors, PubMatic RTD module and PubMatic Analytics Adapter into your Prebid build: + +```shell +gulp build --modules=priceFloors,rtdModule,pubmaticRtdProvider,pubmaticAnalyticsAdapter +``` + +{: .alert.alert-info :} +Note: The PubMatic RTD module is dependent on the global real-time data module : `rtdModule`, price floor module : `priceFloors` and PubMatic Analytics Adapter : `pubmaticAnalyticsAdapter`. + +Step 4: Set configuration and enable PubMatic RTD Module using pbjs.setConfig. + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to at least 250 ms and make sure `waitForIt` is set to `true` for the `pubmatic` RTD provider. + +```js +const AUCTION_DELAY = 250; +pbjs.setConfig({ + // rest of the config + ..., + realTimeData: { + auctionDelay: AUCTION_DELAY, + dataProviders: [ + { + name: "pubmatic", + waitForIt: true, + params: { + publisherId: ``, // please contact PubMatic to get a publisherId for yourself + profileId: ``, // please contact PubMatic to get a profileId for yourself + }, + }, + ], + }, + // rest of the config + ..., +}); +``` + +## Parameters + +| Name | Type | Description | Default | +| :----------------- | :------ | :------------------------------------------------------------- | :------------------------- | +| name | String | Name of the real-time data module | Always `pubmatic` | +| waitForIt | Boolean | Should be `true` if an `auctionDelay` is defined (mandatory) | `false` | +| params | Object | | | +| params.publisherId | String | Publisher ID | | +| params.profileId | String | Profile ID | | + + +## What Should Change in the Bid Request? + +There are no direct changes in the bid request due to our RTD module, but floor configuration will be set using the price floors module. These changes will be reflected in adunit bids or bidder requests as floor data. \ No newline at end of file diff --git a/test/spec/modules/pubmaticRtdProvider_spec.js b/test/spec/modules/pubmaticRtdProvider_spec.js new file mode 100644 index 00000000000..11ec378c700 --- /dev/null +++ b/test/spec/modules/pubmaticRtdProvider_spec.js @@ -0,0 +1,531 @@ +import { expect } from 'chai'; +import * as priceFloors from '../../../modules/priceFloors'; +import * as utils from '../../../src/utils.js'; +import * as suaModule from '../../../src/fpd/sua.js'; +import { config as conf } from '../../../src/config'; +import * as hook from '../../../src/hook.js'; +import { + registerSubModule, pubmaticSubmodule, getFloorsConfig, setFloorsConfig, setPriceFloors, fetchFloorRules, + getCurrentTimeOfDay, getBrowserType, getOs, getDeviceType, getCountry, getUtm, _country +} from '../../../modules/pubmaticRtdProvider.js'; + +let sandbox; + +beforeEach(() => { + sandbox = sinon.createSandbox(); +}); + +afterEach(() => { + sandbox.restore(); +}); + +describe('Pubmatic RTD Provider', () => { + describe('registerSubModule', () => { + it('should register RTD submodule provider', () => { + let submoduleStub = sinon.stub(hook, 'submodule'); + registerSubModule(); + assert(submoduleStub.calledOnceWith('realTimeData', pubmaticSubmodule)); + submoduleStub.restore(); + }); + }); + describe('submodule', () => { + describe('name', () => { + it('should be pubmatic', () => { + expect(pubmaticSubmodule.name).to.equal('pubmatic'); + }); + }); + }); + + describe('init', () => { + let logErrorStub; + let continueAuctionStub; + + const getConfig = () => ({ + params: { + publisherId: 'test-publisher-id', + profileId: 'test-profile-id' + }, + }); + + beforeEach(() => { + logErrorStub = sandbox.stub(utils, 'logError'); + continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); + }); + + it('should return false if publisherId is missing', () => { + const config = { + params: { + profileId: 'test-profile-id' + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should return false if profileId is missing', () => { + const config = { + params: { + publisherId: 'test-publisher-id' + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should return false if publisherId is not a string', () => { + const config = { + params: { + publisherId: 123, + profileId: 'test-profile-id' + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should return false if profileId is not a string', () => { + const config = { + params: { + publisherId: 'test-publisher-id', + profileId: 345 + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should initialize successfully with valid config', () => { + expect(pubmaticSubmodule.init(getConfig())).to.be.true; + }); + + it('should handle empty config object', () => { + expect(pubmaticSubmodule.init({})).to.be.false; + expect(logErrorStub.calledWith(sinon.match(/Missing publisher Id/))).to.be.true; + }); + + it('should return false if continueAuction is not a function', () => { + continueAuctionStub.value(undefined); + expect(pubmaticSubmodule.init(getConfig())).to.be.false; + expect(logErrorStub.calledWith(sinon.match(/continueAuction is not a function/))).to.be.true; + }); + }); + + describe('getCurrentTimeOfDay', () => { + let clock; + + beforeEach(() => { + clock = sandbox.useFakeTimers(new Date('2024-01-01T12:00:00')); // Set fixed time for testing + }); + + afterEach(() => { + clock.restore(); + }); + + const testTimes = [ + { hour: 6, expected: 'morning' }, + { hour: 13, expected: 'afternoon' }, + { hour: 18, expected: 'evening' }, + { hour: 22, expected: 'night' }, + { hour: 4, expected: 'night' } + ]; + + testTimes.forEach(({ hour, expected }) => { + it(`should return ${expected} at ${hour}:00`, () => { + clock.setSystemTime(new Date().setHours(hour)); + const result = getCurrentTimeOfDay(); + expect(result).to.equal(expected); + }); + }); + }); + + describe('getBrowserType', () => { + let userAgentStub, getLowEntropySUAStub; + + const USER_AGENTS = { + chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + firefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0', + edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/91.0.864.67 Safari/537.36', + safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.6 Mobile/15E148 Safari/604.1', + ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', + opera: 'Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.16', + unknown: 'UnknownBrowser/1.0' + }; + + beforeEach(() => { + userAgentStub = sandbox.stub(navigator, 'userAgent'); + getLowEntropySUAStub = sandbox.stub(suaModule, 'getLowEntropySUA').returns(undefined); + }); + + afterEach(() => { + userAgentStub.restore(); + getLowEntropySUAStub.restore(); + }); + + it('should detect Chrome', () => { + userAgentStub.value(USER_AGENTS.chrome); + expect(getBrowserType()).to.equal('9'); + }); + + it('should detect Firefox', () => { + userAgentStub.value(USER_AGENTS.firefox); + expect(getBrowserType()).to.equal('12'); + }); + + it('should detect Edge', () => { + userAgentStub.value(USER_AGENTS.edge); + expect(getBrowserType()).to.equal('2'); + }); + + it('should detect Internet Explorer', () => { + userAgentStub.value(USER_AGENTS.ie); + expect(getBrowserType()).to.equal('4'); + }); + + it('should detect Opera', () => { + userAgentStub.value(USER_AGENTS.opera); + expect(getBrowserType()).to.equal('3'); + }); + + it('should return 0 for unknown browser', () => { + userAgentStub.value(USER_AGENTS.unknown); + expect(getBrowserType()).to.equal('0'); + }); + + it('should return -1 when userAgent is null', () => { + userAgentStub.value(null); + expect(getBrowserType()).to.equal('-1'); + }); + }); + + describe('Utility functions', () => { + it('should set browser correctly', () => { + expect(getBrowserType()).to.be.a('string'); + }); + + it('should set OS correctly', () => { + expect(getOs()).to.be.a('string'); + }); + + it('should set device type correctly', () => { + expect(getDeviceType()).to.be.a('string'); + }); + + it('should set time of day correctly', () => { + expect(getCurrentTimeOfDay()).to.be.a('string'); + }); + + it('should set country correctly', () => { + expect(getCountry()).to.satisfy(value => typeof value === 'string' || value === undefined); + }); + + it('should set UTM correctly', () => { + expect(getUtm()).to.be.a('string'); + expect(getUtm()).to.be.oneOf(['0', '1']); + }); + }); + + describe('getFloorsConfig', () => { + it('should return correct config structure', () => { + const result = getFloorsConfig({}); + + expect(result.floors.data).to.deep.equal({}); + + // Verify the additionalSchemaFields structure + expect(result.floors.additionalSchemaFields).to.have.all.keys([ + 'deviceType', + 'timeOfDay', + 'browser', + 'os', + 'country', + 'utm' + ]); + + Object.values(result.floors.additionalSchemaFields).forEach(field => { + expect(field).to.be.a('function'); + }); + }); + + it('should merge apiResponse data correctly', () => { + const apiResponse = { + currency: 'USD', + schema: { fields: ['mediaType'] }, + values: { 'banner': 1.0 } + }; + + const result = getFloorsConfig(apiResponse); + + expect(result.floors.data).to.deep.equal(apiResponse); + }); + + it('should delete endpoint field in floors', () => { + const apiResponse = { + currency: 'USD', + schema: { fields: ['mediaType'] }, + endpoint: { + url: './floors.json' + }, + values: { 'banner': 1.0 } + }; + + const result = getFloorsConfig(apiResponse); + expect(result.floors).to.not.have.property('endpoint'); + }); + + it('should maintain correct function references', () => { + const result = getFloorsConfig({}); + + expect(result.floors.additionalSchemaFields.deviceType).to.equal(getDeviceType); + expect(result.floors.additionalSchemaFields.timeOfDay).to.equal(getCurrentTimeOfDay); + expect(result.floors.additionalSchemaFields.browser).to.equal(getBrowserType); + expect(result.floors.additionalSchemaFields.os).to.equal(getOs); + expect(result.floors.additionalSchemaFields.country).to.equal(getCountry); + expect(result.floors.additionalSchemaFields.utm).to.equal(getUtm); + }); + }); + + describe('setFloorsConfig', () => { + let logMessageStub; + let confStub; + + beforeEach(() => { + logMessageStub = sandbox.stub(utils, 'logMessage'); + confStub = sandbox.stub(conf, 'setConfig'); + }); + + it('should set config when valid data is provided', () => { + const validData = { + currency: 'USD', + schema: { fields: ['mediaType'] } + }; + + setFloorsConfig(validData); + + expect(confStub.calledOnce).to.be.true; + const calledWith = confStub.getCall(0).args[0]; + expect(calledWith).to.have.nested.property('floors.data.currency', 'USD'); + expect(calledWith).to.have.nested.property('floors.data.schema.fields[0]', 'mediaType'); + }); + + it('should log message when data is null', () => { + setFloorsConfig(null); + + expect(confStub.called).to.be.false; + expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); + }); + + it('should log message when data is undefined', () => { + setFloorsConfig(undefined); + + expect(confStub.called).to.be.false; + expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); + }); + + it('should log message when data is an empty object', () => { + setFloorsConfig({}); + + expect(confStub.called).to.be.false; + expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); + }); + + it('should log message when data is an array', () => { + setFloorsConfig([]); + + expect(confStub.called).to.be.false; + expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); + }); + + it('should set config with complex floor data', () => { + const floorData = { + currency: 'USD', + schema: { + fields: ['mediaType', 'size'], + delimiter: '|' + }, + values: { + 'banner|300x250': 1.0, + 'banner|300x600': 2.0 + } + }; + + setFloorsConfig(floorData); + + expect(confStub.calledOnce).to.be.true; + const calledWith = confStub.getCall(0).args[0]; + expect(calledWith.floors.data).to.deep.equal(floorData); + }); + + it('should handle non-object data types', () => { + const invalidInputs = [ + 'string', + 123, + true, + () => { }, + Symbol('test') + ]; + + invalidInputs.forEach(input => { + setFloorsConfig(input); + expect(confStub.called).to.be.false; + expect(logMessageStub.called).to.be.true; + }); + }); + }); + + describe('Price Floor Functions', () => { + let logErrorStub; + let fetchStub; + let confStub; + + beforeEach(() => { + logErrorStub = sandbox.stub(utils, 'logError'); + fetchStub = sandbox.stub(window, 'fetch'); + confStub = sandbox.stub(conf, 'setConfig'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('fetchFloorRules', () => { + beforeEach(() => { + global._country = undefined; + }); + + it('should successfully fetch and parse floor rules', async () => { + const mockApiResponse = { + data: { + currency: 'USD', + modelGroups: [], + values: {} + } + }; + + fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200, headers: { 'country_code': 'US' } })); + + const result = await fetchFloorRules('publisherId', 'profileId'); + expect(result).to.deep.equal(mockApiResponse); + expect(_country).to.equal('US'); + }); + + it('should correctly extract the first unique country code from response headers', async () => { + fetchStub.resolves(new Response(JSON.stringify({}), { + status: 200, + headers: { 'country_code': 'US,IN,US' } + })); + + await fetchFloorRules('publisherId', 'profileId'); + expect(_country).to.equal('US'); + }); + + it('should set _country to undefined if country_code header is missing', async () => { + fetchStub.resolves(new Response(JSON.stringify({}), { + status: 200 + })); + + await fetchFloorRules('publisherId', 'profileId'); + expect(_country).to.be.undefined; + }); + + it('should log error when JSON parsing fails', async () => { + fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); + + await fetchFloorRules('publisherId', 'profileId'); + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching floors'); + }); + + it('should log error when response is not ok', async () => { + fetchStub.resolves(new Response(null, { status: 500 })); + + await fetchFloorRules('publisherId', 'profileId'); + expect(logErrorStub.calledWith(sinon.match(/Error while fetching floors: No response/))).to.be.true; + }); + + it('should log error on network failure', async () => { + fetchStub.rejects(new Error('Network Error')); + + await fetchFloorRules('publisherId', 'profileId'); + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching floors'); + }); + }); + + describe('setPriceFloors', () => { + it('should log error for empty response', async () => { + fetchStub.resolves(new Response(null, { status: 200 })); + + await setPriceFloors(); + expect(logErrorStub.calledWith(sinon.match(/Error while fetching floors/))).to.be.true; + }); + + it('should successfully process valid response', async () => { + const mockApiResponse = { + data: { + currency: 'USD', + modelGroups: [], + values: {} + } + }; + + fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200 })); + await setPriceFloors('publisherId', 'profileId'); + + expect(fetchStub.calledOnce).to.be.true; + expect(confStub.calledOnce).to.be.true; + }); + }); + }); + + describe('getBidRequestData', () => { + let _pubmaticFloorRulesPromiseMock; + let continueAuctionStub; + let callback = sinon.spy(); + + const reqBidsConfigObj = { + adUnits: [{ code: 'ad-slot-code-0' }], + auctionId: 'auction-id-0', + ortb2Fragments: { + bidder: { + user: { + ext: { + ctr: 'US', + } + } + } + } + }; + + const ortb2 = { + user: { + ext: { + ctr: 'US', + } + } + } + + const hookConfig = { + reqBidsConfigObj, + context: this, + nextFn: () => true, + haveExited: false, + timer: null + }; + + beforeEach(() => { + continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); + }); + + it('should call continueAuction once after _pubmaticFloorRulesPromise. Also getBidRequestData executed only once', async () => { + _pubmaticFloorRulesPromiseMock = Promise.resolve(); + pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); + await _pubmaticFloorRulesPromiseMock; + expect(continueAuctionStub.calledOnce); + expect( + continueAuctionStub.alwaysCalledWith( + hookConfig + ) + ); + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.include(ortb2); + }); + }); +}); From 850e44c6844f8c5640e574e3b94bdfaf0be35e07 Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Wed, 26 Mar 2025 19:44:25 +0000 Subject: [PATCH 1037/1097] NodalsAi Rtd Module : integrate with major version 1 of Nodals' JS Library (#12912) * Additional method proxying * Update version to use latest major verson pattern matching * Fix tests * Cleaned up tests * Linting * Rename parameter passed to getBidRequestData to match documentation * Removing overly verbose log messages * linting --------- Co-authored-by: slimkrazy --- modules/nodalsAiRtdProvider.js | 171 +++++-- test/spec/modules/nodalsAiRtdProvider_spec.js | 439 ++++++++++++++---- 2 files changed, 504 insertions(+), 106 deletions(-) diff --git a/modules/nodalsAiRtdProvider.js b/modules/nodalsAiRtdProvider.js index cadb9dacac7..f8db70e9218 100644 --- a/modules/nodalsAiRtdProvider.js +++ b/modules/nodalsAiRtdProvider.js @@ -4,14 +4,16 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { getStorageManager } from '../src/storageManager.js'; -import { prefixLog } from '../src/utils.js'; +import { mergeDeep, prefixLog } from '../src/utils.js'; const MODULE_NAME = 'nodalsAi'; const GVLID = 1360; +const ENGINE_VESION = '1.x.x'; const PUB_ENDPOINT_ORIGIN = 'https://nodals.io'; const LOCAL_STORAGE_KEY = 'signals.nodals.ai'; const STORAGE_TTL = 3600; // 1 hour in seconds + const fillTemplate = (strings, ...keys) => { return function (values) { return strings.reduce((result, str, i) => { @@ -40,6 +42,8 @@ class NodalsAiRtdProvider { // Private properties #propertyId = null; #overrides = {}; + #dataFetchInProgress = false; + #userConsent = null; // Public methods @@ -55,12 +59,11 @@ class NodalsAiRtdProvider { this.#hasRequiredUserConsent(userConsent) ) { this.#propertyId = params.propertyId; + this.#userConsent = userConsent; this.#setOverrides(params); - const storedData = this.#readFromStorage( - this.#overrides?.storageKey || this.STORAGE_KEY - ); + const storedData = this.#readFromStorage(); if (storedData === null) { - this.#fetchRules(userConsent); + this.#fetchData(); } else { this.#loadAdLibraries(storedData.deps || []); } @@ -83,21 +86,17 @@ class NodalsAiRtdProvider { if (!this.#hasRequiredUserConsent(userConsent)) { return targetingData; } - const storedData = this.#readFromStorage( - this.#overrides?.storageKey || this.STORAGE_KEY - ); - if (storedData === null) { + this.#userConsent = userConsent; + const storedData = this.#getData(); + const engine = this.#initialiseEngine(config); + if (!storedData || !engine) { return targetingData; } - const facts = Object.assign({}, storedData?.facts ?? {}); - facts['page.url'] = getRefererInfo().page; - const targetingEngine = window?.$nodals?.adTargetingEngine['latest']; try { - targetingEngine.init(config, facts); - targetingData = targetingEngine.getTargetingData( + targetingData = engine.getTargetingData( adUnitArray, - storedData, - userConsent + userConsent, + storedData ); } catch (error) { logError(`Error determining targeting keys: ${error}`); @@ -105,7 +104,111 @@ class NodalsAiRtdProvider { return targetingData; } + getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + if (!this.#hasRequiredUserConsent(userConsent)) { + callback(); + return; + } + this.#userConsent = userConsent; + const storedData = this.#getData(); + if (!storedData) { + callback(); + return; + } + const engine = this.#initialiseEngine(config); + if (!engine) { + this.#addToCommandQueue('getBidRequestData', {config, reqBidsConfigObj, callback, userConsent, storedData }); + } else { + try { + engine.getBidRequestData( + reqBidsConfigObj, + callback, + userConsent, + storedData + ); + } catch (error) { + logError(`Error getting bid request data: ${error}`); + callback(); + } + } + } + + onBidResponseEvent(bidResponse, config, userConsent) { + if (!this.#hasRequiredUserConsent(userConsent)) { + return; + } + this.#userConsent = userConsent; + const storedData = this.#getData(); + if (!storedData) { + return; + } + const engine = this.#initialiseEngine(config); + if (!engine) { + this.#addToCommandQueue('onBidResponseEvent', {config, bidResponse, userConsent, storedData }) + return; + } + try { + engine.onBidResponseEvent(bidResponse, userConsent, storedData); + } catch (error) { + logError(`Error processing bid response event: ${error}`); + } + } + + onAuctionEndEvent(auctionDetails, config, userConsent) { + if (!this.#hasRequiredUserConsent(userConsent)) { + return; + } + this.#userConsent = userConsent; + const storedData = this.#getData(); + if (!storedData) { + return; + } + const engine = this.#initialiseEngine(config); + if (!engine) { + this.#addToCommandQueue('onAuctionEndEvent', {config, auctionDetails, userConsent, storedData }); + return; + } + try { + engine.onAuctionEndEvent(auctionDetails, userConsent, storedData); + } catch (error) { + logError(`Error processing auction end event: ${error}`); + } + } + + // Private methods + #getData() { + const storedData = this.#readFromStorage(); + if (storedData === null) { + this.#fetchData(); + return null; + } + if (storedData.facts === undefined) { + storedData.facts = {}; + } + storedData.facts = mergeDeep(storedData.facts, this.#getRuntimeFacts()); + return storedData; + } + + #initialiseEngine(config) { + const engine = this.#getEngine(); + if (!engine) { + logInfo(`Engine v${ENGINE_VESION} not found`); + return null; + } + try { + engine.init(config); + return engine + } catch (error) { + logError(`Error initialising engine: ${error}`); + return null; + } + } + + #getEngine() { + return window?.$nodals?.adTargetingEngine[ENGINE_VESION]; + } + #setOverrides(params) { if (params?.storage?.ttl && typeof params.storage.ttl === 'number') { this.#overrides.storageTTL = params.storage.ttl; @@ -114,6 +217,13 @@ class NodalsAiRtdProvider { this.#overrides.endpointOrigin = params?.endpoint?.origin; } + #getRuntimeFacts() { + return { + 'page.url': getRefererInfo().page, + 'prebid.version': '$prebid.version$', + }; + } + /** * Validates if the provided module input parameters are valid. * @param {Object} params - Parameters object from the module configuration. @@ -149,12 +259,8 @@ class NodalsAiRtdProvider { return true; } - /** - * @param {string} key - The key of the data to retrieve. - * @returns {string|null} - The data from localStorage, or null if not found. - */ - - #readFromStorage(key) { + #readFromStorage() { + const key = this.#overrides?.storageKey || this.STORAGE_KEY; if ( this.storage.hasLocalStorage() && this.storage.localStorageIsEnabled() @@ -166,8 +272,8 @@ class NodalsAiRtdProvider { } const dataEnvelope = JSON.parse(entry); if (this.#dataIsStale(dataEnvelope)) { - this.storage.removeDataFromLocalStorage(key); - return null; + logInfo('Stale data found in storage. Refreshing data.'); + this.#fetchData(); } if (!dataEnvelope.data) { throw new Error('Data envelope is missing \'data\' property.'); @@ -244,14 +350,19 @@ class NodalsAiRtdProvider { * Initiates the request to fetch rule data from the publisher endpoint. */ - #fetchRules(userConsent) { - const endpointUrl = this.#getEndpointUrl(userConsent); - + #fetchData() { + if (this.#dataFetchInProgress) { + return; + } + this.#dataFetchInProgress = true; + const endpointUrl = this.#getEndpointUrl(this.#userConsent); const callback = { success: (response, req) => { + this.#dataFetchInProgress = false; this.#handleServerResponse(response, req); }, error: (error, req) => { + this.#dataFetchInProgress = false; this.#handleServerError(error, req); }, }; @@ -265,6 +376,12 @@ class NodalsAiRtdProvider { ajax(endpointUrl, callback, null, options); } + #addToCommandQueue(cmd, payload) { + window.$nodals = window.$nodals || {}; + window.$nodals.cmdQueue = window.$nodals.cmdQueue || []; + window.$nodals.cmdQueue.push({ cmd, runtimeFacts: this.#getRuntimeFacts(), data: payload }); + } + /** * Handles the server response, processes it and extracts relevant data. * @param {Object} response - The server response object. diff --git a/test/spec/modules/nodalsAiRtdProvider_spec.js b/test/spec/modules/nodalsAiRtdProvider_spec.js index b93d48c1f5f..69db7b5660c 100644 --- a/test/spec/modules/nodalsAiRtdProvider_spec.js +++ b/test/spec/modules/nodalsAiRtdProvider_spec.js @@ -9,6 +9,8 @@ const jsonResponseHeaders = { 'Content-Type': 'application/json', }; +const overrideLocalStorageKey = '_foobarbaz_'; + const successPubEndpointResponse = { deps: { '1.0.0': 'https://static.nodals.io/sdk/rule/1.0.0/engine.js', @@ -20,7 +22,7 @@ const successPubEndpointResponse = { }, campaigns: [ { - id: 1234, + id: '41ffa965', ads: [ { delivery_id: '1234', @@ -28,8 +30,8 @@ const successPubEndpointResponse = { weighting: 1, kvs: [ { - k: 'nodals', - v: '1', + key: 'nodals', + value: '1', }, ], rules: { @@ -104,24 +106,63 @@ const setDataInLocalStorage = (data) => { const storageData = { ...data }; nodalsAiRtdSubmodule.storage.setDataInLocalStorage( nodalsAiRtdSubmodule.STORAGE_KEY, - JSON.stringify(storageData) + JSON.stringify(storageData), ); }; +const createTargetingEngineStub = (getTargetingDataReturnValue = {}, raiseError = false) => { + const version = '1.x.x'; + const initStub = sinon.stub(); + const getTargetingDataStub = sinon.stub(); + const getBidRequestDataStub = sinon.stub(); + const onBidResponseEventStub = sinon.stub(); + const onAuctionEndEventStub = sinon.stub(); + if (raiseError) { + getTargetingDataStub.throws(new Error('Stubbed error')); + } else { + getTargetingDataStub.returns(getTargetingDataReturnValue); + } + window.$nodals = window.$nodals || {}; + window.$nodals.adTargetingEngine = window.$nodals.adTargetingEngine || {}; + window.$nodals.adTargetingEngine[version] = { + init: initStub, + getTargetingData: getTargetingDataStub, + getBidRequestData: getBidRequestDataStub, + onBidResponseEvent: onBidResponseEventStub, + onAuctionEndEvent: onAuctionEndEventStub, + }; + return window.$nodals.adTargetingEngine[version]; +}; + + describe('NodalsAI RTD Provider', () => { let sandbox; let validConfig; beforeEach(() => { + sandbox = sinon.sandbox.create(); + validConfig = { params: { propertyId: '10312dd2' } }; - sandbox = sinon.sandbox.create(); + server.respondWith([ + 200, + jsonResponseHeaders, + JSON.stringify(successPubEndpointResponse) + ]); + }); + + afterEach(() => { + if (window.$nodals) { + delete window.$nodals; + } + nodalsAiRtdSubmodule.storage.removeDataFromLocalStorage( nodalsAiRtdSubmodule.STORAGE_KEY ); - }); + nodalsAiRtdSubmodule.storage.removeDataFromLocalStorage( + overrideLocalStorageKey + ); - afterEach(() => { sandbox.restore(); }); @@ -141,6 +182,8 @@ describe('NodalsAI RTD Provider', () => { it('should return true when initialized with valid config and empty user consent', function () { const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); @@ -148,6 +191,7 @@ describe('NodalsAI RTD Provider', () => { it('should return false when initialized with invalid config', () => { const config = { params: { invalid: true } }; const result = nodalsAiRtdSubmodule.init(config, userConsent); + expect(result).to.be.false; expect(server.requests.length).to.equal(0); }); @@ -157,25 +201,36 @@ describe('NodalsAI RTD Provider', () => { it('should return false when user is under GDPR jurisdiction and purpose1 has not been granted', () => { const userConsent = generateGdprConsent({ purpose1Consent: false }); const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + server.respond(); + expect(result).to.be.false; + expect(server.requests.length).to.equal(0); }); it('should return false when user is under GDPR jurisdiction and Nodals AI as a vendor has no consent', () => { const userConsent = generateGdprConsent({ nodalsConsent: false }); const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + expect(result).to.be.false; + expect(server.requests.length).to.equal(0); }); it('should return true when user is under GDPR jurisdiction and all consent provided', function () { const userConsent = generateGdprConsent(); const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + server.respond(); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); }); it('should return true when user is not under GDPR jurisdiction', () => { const userConsent = generateGdprConsent({ gdprApplies: false }); const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + server.respond(); + expect(result).to.be.true; + expect(server.requests.length).to.equal(1); }); }); @@ -183,6 +238,7 @@ describe('NodalsAI RTD Provider', () => { it('should return true and not make a remote request when stored data is valid', function () { setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: Date.now() }); const result = nodalsAiRtdSubmodule.init(validConfig, {}); + expect(result).to.be.true; expect(server.requests.length).to.equal(0); }); @@ -190,6 +246,8 @@ describe('NodalsAI RTD Provider', () => { it('should return true and make a remote request when stored data has no TTL defined', function () { setDataInLocalStorage({ data: { foo: 'bar' } }); const result = nodalsAiRtdSubmodule.init(validConfig, {}); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); @@ -197,6 +255,8 @@ describe('NodalsAI RTD Provider', () => { it('should return true and make a remote request when stored data has expired', function () { setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: 100 }); const result = nodalsAiRtdSubmodule.init(validConfig, {}); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); @@ -210,6 +270,8 @@ describe('NodalsAI RTD Provider', () => { const config = Object.assign({}, validConfig); config.params.storage = { ttl: 4 * 60 }; const result = nodalsAiRtdSubmodule.init(config, {}); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); @@ -221,6 +283,8 @@ describe('NodalsAI RTD Provider', () => { createdAt: fiveMinutesAgoMs, }); const result = nodalsAiRtdSubmodule.init(validConfig, {}); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); @@ -234,6 +298,7 @@ describe('NodalsAI RTD Provider', () => { const config = Object.assign({}, validConfig); config.params.storage = { ttl: 6 * 60 }; const result = nodalsAiRtdSubmodule.init(config, {}); + expect(result).to.be.true; expect(server.requests.length).to.equal(0); }); @@ -254,8 +319,10 @@ describe('NodalsAI RTD Provider', () => { it('should return true and make a remote request when data stored under default key, but override key specified', () => { setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: Date.now() }); const config = Object.assign({}, validConfig); - config.params.storage = { key: '_foobarbaz_' }; + config.params.storage = { key: overrideLocalStorageKey }; const result = nodalsAiRtdSubmodule.init(config, {}); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); @@ -265,8 +332,8 @@ describe('NodalsAI RTD Provider', () => { it('should construct the correct URL to the default origin', () => { const userConsent = generateGdprConsent(); nodalsAiRtdSubmodule.init(validConfig, userConsent); - let request = server.requests[0]; + server.respond(); expect(request.method).to.equal('GET'); expect(request.withCredentials).to.be.false; @@ -279,8 +346,8 @@ describe('NodalsAI RTD Provider', () => { config.params.endpoint = { origin: 'http://localhost:8000' }; const userConsent = generateGdprConsent(); nodalsAiRtdSubmodule.init(config, userConsent); - let request = server.requests[0]; + server.respond(); expect(request.method).to.equal('GET'); expect(request.withCredentials).to.be.false; @@ -291,8 +358,9 @@ describe('NodalsAI RTD Provider', () => { it('should construct the correct URL with the correct path', () => { const userConsent = generateGdprConsent(); nodalsAiRtdSubmodule.init(validConfig, userConsent); - let request = server.requests[0]; + server.respond(); + const requestUrl = new URL(request.url); expect(requestUrl.pathname).to.equal('/p/v1/10312dd2/config'); }); @@ -303,8 +371,9 @@ describe('NodalsAI RTD Provider', () => { }; const userConsent = generateGdprConsent(consentData); nodalsAiRtdSubmodule.init(validConfig, userConsent); - let request = server.requests[0]; + server.respond(); + const requestUrl = new URL(request.url); expect(requestUrl.searchParams.get('gdpr')).to.equal('1'); expect(requestUrl.searchParams.get('gdpr_consent')).to.equal( @@ -320,19 +389,15 @@ describe('NodalsAI RTD Provider', () => { it('should store successful response data in local storage', () => { const userConsent = generateGdprConsent(); nodalsAiRtdSubmodule.init(validConfig, userConsent); - let request = server.requests[0]; - request.respond( - 200, - jsonResponseHeaders, - JSON.stringify(successPubEndpointResponse) - ); + server.respond(); const storedData = JSON.parse( nodalsAiRtdSubmodule.storage.getDataFromLocalStorage( nodalsAiRtdSubmodule.STORAGE_KEY ) ); + expect(request.method).to.equal('GET'); expect(storedData).to.have.property('createdAt'); expect(storedData.data).to.deep.equal(successPubEndpointResponse); @@ -341,19 +406,14 @@ describe('NodalsAI RTD Provider', () => { it('should store successful response data in local storage under the override key', () => { const userConsent = generateGdprConsent(); const config = Object.assign({}, validConfig); - config.params.storage = { key: '_foobarbaz_' }; + config.params.storage = { key: overrideLocalStorageKey }; nodalsAiRtdSubmodule.init(config, userConsent); - + server.respond(); let request = server.requests[0]; - request.respond( - 200, - jsonResponseHeaders, - JSON.stringify(successPubEndpointResponse) - ); - const storedData = JSON.parse( - nodalsAiRtdSubmodule.storage.getDataFromLocalStorage('_foobarbaz_') + nodalsAiRtdSubmodule.storage.getDataFromLocalStorage(overrideLocalStorageKey) ); + expect(request.method).to.equal('GET'); expect(storedData).to.have.property('createdAt'); expect(storedData.data).to.deep.equal(successPubEndpointResponse); @@ -362,13 +422,7 @@ describe('NodalsAI RTD Provider', () => { it('should attempt to load the referenced script libraries contained in the response payload', () => { const userConsent = generateGdprConsent(); nodalsAiRtdSubmodule.init(validConfig, userConsent); - - let request = server.requests[0]; - request.respond( - 200, - jsonResponseHeaders, - JSON.stringify(successPubEndpointResponse) - ); + server.respond(); expect(loadExternalScriptStub.calledTwice).to.be.true; expect( @@ -390,62 +444,43 @@ describe('NodalsAI RTD Provider', () => { }); describe('getTargetingData()', () => { - afterEach(() => { - if (window.$nodals) { - delete window.$nodals; - } - }); - - const stubVersionedTargetingEngine = (returnValue, raiseError = false) => { - const version = 'latest'; - const initStub = sinon.stub(); - const getTargetingDataStub = sinon.stub(); - if (raiseError) { - getTargetingDataStub.throws(new Error('Stubbed error')); - } else { - getTargetingDataStub.returns(returnValue); - } - window.$nodals = window.$nodals || {}; - window.$nodals.adTargetingEngine = window.$nodals.adTargetingEngine || {}; - window.$nodals.adTargetingEngine[version] = { - init: initStub, - getTargetingData: getTargetingDataStub, - }; - return window.$nodals.adTargetingEngine[version]; - }; - - it('should return an empty object when no data is available in local storage', () => { + it('should return an empty object when no data is available in local storage, but fetch data', () => { const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, {} ); + server.respond(); expect(result).to.deep.equal({}); + expect(server.requests.length).to.equal(1); }); it('should return an empty object when getTargetingData throws error', () => { - stubVersionedTargetingEngine({}, true); // TODO: Change the data + createTargetingEngineStub({adUnit1: {someKey: 'someValue'}}, true); const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), }); + const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, userConsent ); expect(result).to.deep.equal({}); + expect(server.requests.length).to.equal(0); }); - it('should initialise the versioned targeting engine', () => { + it('should initialise the versioned targeting engine if fresh data is in storage and not make a HTTP request', () => { const returnData = {}; - const engine = stubVersionedTargetingEngine(returnData); + const engine = createTargetingEngineStub(returnData); const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), }); + nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, @@ -455,32 +490,33 @@ describe('NodalsAI RTD Provider', () => { expect(engine.init.called).to.be.true; const args = engine.init.getCall(0).args; expect(args[0]).to.deep.equal(validConfig); - expect(args[1]).to.deep.include(successPubEndpointResponse.facts); + expect(server.requests.length).to.equal(0); }); - it('should proxy the correct data to engine.init()', () => { - const engine = stubVersionedTargetingEngine( - engineGetTargetingDataReturnValue - ); + it('should initialise the versioned targeting engine with stale data if data expired and fetch fresh data', function () { + const returnData = {}; + const engine = createTargetingEngineStub(returnData); const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, - createdAt: Date.now(), + createdAt: 100, }); + nodalsAiRtdSubmodule.getTargetingData( - ['adUnit1', 'adUnit2'], + ['adUnit1'], validConfig, userConsent ); + server.respond(); expect(engine.init.called).to.be.true; const args = engine.init.getCall(0).args; expect(args[0]).to.deep.equal(validConfig); - expect(args[1]).to.be.an('object').with.keys(['browser.name', 'geo.country', 'page.url']); + expect(server.requests.length).to.equal(1); }); - it('should proxy the correct data to engine.getTargetingData()', () => { - const engine = stubVersionedTargetingEngine( + it('should proxy the correct data to engine.getTargetingData() when storage data is available and we have consent under GDPR jurisdiction', () => { + const engine = createTargetingEngineStub( engineGetTargetingDataReturnValue ); const userConsent = generateGdprConsent(); @@ -488,6 +524,7 @@ describe('NodalsAI RTD Provider', () => { data: successPubEndpointResponse, createdAt: Date.now(), }); + nodalsAiRtdSubmodule.getTargetingData( ['adUnit1', 'adUnit2'], validConfig, @@ -497,12 +534,15 @@ describe('NodalsAI RTD Provider', () => { expect(engine.getTargetingData.called).to.be.true; const args = engine.getTargetingData.getCall(0).args; expect(args[0]).to.deep.equal(['adUnit1', 'adUnit2']); - expect(args[1]).to.deep.include(successPubEndpointResponse); - expect(args[2]).to.deep.equal(userConsent); + expect(args[1]).to.deep.equal(userConsent); + + expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); }); - it('should return the response from engine.getTargetingData when data is available and we have consent under GDPR jurisdiction', () => { - stubVersionedTargetingEngine(engineGetTargetingDataReturnValue); + it('should return the data from engine.getTargetingData when storage data is available and we have consent under GDPR jurisdiction', () => { + createTargetingEngineStub(engineGetTargetingDataReturnValue); const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, @@ -518,8 +558,8 @@ describe('NodalsAI RTD Provider', () => { expect(result).to.deep.equal(engineGetTargetingDataReturnValue); }); - it('should return the response from engine.getTargetingData when data is available and we are NOT under GDPR jurisdiction', () => { - stubVersionedTargetingEngine(engineGetTargetingDataReturnValue); + it('should return the data from engine.getTargetingData when storage is available and we are NOT under GDPR jurisdiction', () => { + createTargetingEngineStub(engineGetTargetingDataReturnValue); const userConsent = generateGdprConsent({ gdprApplies: false }); setDataInLocalStorage({ data: successPubEndpointResponse, @@ -536,7 +576,7 @@ describe('NodalsAI RTD Provider', () => { }); it('should return an empty object when data is available, but user has not provided consent to Nodals AI as a vendor', () => { - stubVersionedTargetingEngine(engineGetTargetingDataReturnValue); + createTargetingEngineStub(engineGetTargetingDataReturnValue); const userConsent = generateGdprConsent({ nodalsConsent: false }); setDataInLocalStorage({ data: successPubEndpointResponse, @@ -552,4 +592,245 @@ describe('NodalsAI RTD Provider', () => { expect(result).to.deep.equal({}); }); }); + + describe('getBidRequestData()', () => { + it('should invoke callback without attempting to initialise the engine if we do not have consent', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const userConsent = generateGdprConsent({ nodalsConsent: false }); + const callback = sinon.spy(); + nodalsAiRtdSubmodule.getBidRequestData( + {}, callback, validConfig, userConsent + ); + + expect(callback.called).to.be.true; + expect(engine.init.called).to.be.false; + expect(window.$nodals.cmdQueue).to.be.undefined + expect(server.requests.length).to.equal(0); + }); + + it('should not store function arguments in a queue when no data is in localstorage and make a HTTP request for data', () => { + const userConsent = generateGdprConsent(); + const callback = sinon.spy(); + const requestObj = {dummy: 'obj'} + nodalsAiRtdSubmodule.getBidRequestData( + requestObj, callback, validConfig, userConsent + ); + server.respond(); + + expect(callback.called).to.be.true; + expect(window.$nodals).to.be.undefined; + expect(server.requests.length).to.equal(1); + }); + + it('should store function arguments in a queue when data is in localstorage and engine not loaded', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const userConsent = generateGdprConsent(); + const callback = sinon.spy(); + const reqBidsConfigObj = {dummy: 'obj'} + nodalsAiRtdSubmodule.getBidRequestData( + reqBidsConfigObj, callback, validConfig, userConsent + ); + + expect(callback.called).to.be.false; + expect(window.$nodals.cmdQueue).to.be.an('array').with.length(1); + expect(window.$nodals.cmdQueue[0].cmd).to.equal('getBidRequestData'); + expect(window.$nodals.cmdQueue[0].runtimeFacts).to.have.keys(['prebid.version', 'page.url']); + expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, reqBidsConfigObj, callback, userConsent}); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('deps').that.deep.equals( + successPubEndpointResponse.deps); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('facts').that.deep.includes( + successPubEndpointResponse.facts); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('campaigns').that.deep.equals( + successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); + + it('should proxy the correct data to engine.getBidRequestData when data is in localstorage and library has loaded', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const userConsent = generateGdprConsent(); + const callback = sinon.spy(); + const reqBidsConfigObj = {dummy: 'obj'} + nodalsAiRtdSubmodule.getBidRequestData( + reqBidsConfigObj, callback, validConfig, userConsent + ); + + expect(callback.called).to.be.false; + expect(engine.init.called).to.be.true; + expect(engine.getBidRequestData.called).to.be.true; + const args = engine.getBidRequestData.getCall(0).args; + expect(args[0]).to.deep.equal(reqBidsConfigObj); + expect(args[1]).to.deep.equal(callback); + expect(args[2]).to.deep.equal(userConsent); + expect(args[3].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[3].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[3].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); + }); + + describe('onBidResponseEvent()', () => { + it('should not proxy the call if we do not have user consent', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const userConsent = generateGdprConsent({ nodalsConsent: false }); + const bidResponse = {dummy: 'obj', 'bid': 'foo'}; + nodalsAiRtdSubmodule.onBidResponseEvent( + bidResponse, validConfig, userConsent + ); + + expect(engine.init.called).to.be.false; + expect(engine.onBidResponseEvent.called).to.be.false; + expect(window.$nodals.cmdQueue).to.be.undefined + expect(server.requests.length).to.equal(0); + }); + + it('should not store function arguments in a queue when no data is in localstorage and make a HTTP request for data', () => { + const userConsent = generateGdprConsent(); + const bidResponse = {dummy: 'obj', 'bid': 'foo'}; + nodalsAiRtdSubmodule.onBidResponseEvent( + bidResponse, validConfig, userConsent + ); + server.respond(); + expect(window.$nodals).to.be.undefined; + expect(server.requests.length).to.equal(1); + }); + + it('should store function arguments in a queue when data is in localstorage and engine not loaded', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const userConsent = generateGdprConsent(); + const bidResponse = {dummy: 'obj', 'bid': 'foo'}; + nodalsAiRtdSubmodule.onBidResponseEvent( + bidResponse, validConfig, userConsent + ); + expect(window.$nodals.cmdQueue).to.be.an('array').with.length(1); + expect(window.$nodals.cmdQueue[0].cmd).to.equal('onBidResponseEvent'); + expect(window.$nodals.cmdQueue[0].runtimeFacts).to.have.keys(['prebid.version', 'page.url']); + expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, bidResponse, userConsent }); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('deps').that.deep.equals( + successPubEndpointResponse.deps); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('facts').that.deep.includes( + successPubEndpointResponse.facts); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('campaigns').that.deep.equals( + successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); + + it('should proxy the correct data to engine.onBidResponseEvent when data is in localstorage and library has loaded', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const userConsent = generateGdprConsent(); + const bidResponse = {dummy: 'obj', 'bid': 'foo'}; + nodalsAiRtdSubmodule.onBidResponseEvent( + bidResponse, validConfig, userConsent + ); + + expect(engine.init.called).to.be.true; + expect(engine.onBidResponseEvent.called).to.be.true; + const args = engine.onBidResponseEvent.getCall(0).args; + expect(args[0]).to.deep.equal(bidResponse); + expect(args[1]).to.deep.equal(userConsent); + expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); + }); + + describe('onAuctionEndEvent()', () => { + it('should not proxy the call if we do not have user consent', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const userConsent = generateGdprConsent({ nodalsConsent: false }); + const auctionDetails = {dummy: 'obj', auction: 'foo'}; + nodalsAiRtdSubmodule.onAuctionEndEvent( + auctionDetails, validConfig, userConsent + ); + + expect(engine.init.called).to.be.false; + expect(engine.onAuctionEndEvent.called).to.be.false; + expect(window.$nodals.cmdQueue).to.be.undefined + expect(server.requests.length).to.equal(0); + }); + + it('should not store function arguments in a queue when no data is in localstorage and make a HTTP request for data', () => { + const userConsent = generateGdprConsent(); + const auctionDetails = {dummy: 'obj', auction: 'foo'}; + nodalsAiRtdSubmodule.onAuctionEndEvent( + auctionDetails, validConfig, userConsent + ); + server.respond(); + expect(window.$nodals).to.be.undefined; + expect(server.requests.length).to.equal(1); + }); + + it('should store function arguments in a queue when data is in localstorage and engine not loaded', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const userConsent = generateGdprConsent(); + const auctionDetails = {dummy: 'obj', auction: 'foo'}; + nodalsAiRtdSubmodule.onAuctionEndEvent( + auctionDetails, validConfig, userConsent + ); + + expect(window.$nodals.cmdQueue).to.be.an('array').with.length(1); + expect(window.$nodals.cmdQueue[0].cmd).to.equal('onAuctionEndEvent'); + expect(window.$nodals.cmdQueue[0].runtimeFacts).to.have.keys(['prebid.version', 'page.url']); + expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, auctionDetails, userConsent }); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('deps').that.deep.equals( + successPubEndpointResponse.deps); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('facts').that.deep.includes( + successPubEndpointResponse.facts); + expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('campaigns').that.deep.equals( + successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); + + it('should proxy the correct data to engine.onAuctionEndEvent when data is in localstorage and library has loaded', () => { + setDataInLocalStorage({ + data: successPubEndpointResponse, + createdAt: Date.now(), + }); + const engine = createTargetingEngineStub(); + const userConsent = generateGdprConsent(); + const auctionDetails = {dummy: 'obj', auction: 'foo'}; + nodalsAiRtdSubmodule.onAuctionEndEvent( + auctionDetails, validConfig, userConsent + ); + + expect(engine.init.called).to.be.true; + expect(engine.onAuctionEndEvent.called).to.be.true; + const args = engine.onAuctionEndEvent.getCall(0).args; + expect(args[0]).to.deep.equal(auctionDetails); + expect(args[1]).to.deep.equal(userConsent); + expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); + expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); + expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); + expect(server.requests.length).to.equal(0); + }); + }); }); From fbe33462f0914e46e824b18effad8c31f319ebcd Mon Sep 17 00:00:00 2001 From: Miguel Date: Wed, 26 Mar 2025 15:18:39 -0700 Subject: [PATCH 1038/1097] adds generic open pair support (#12599) Co-authored-by: Miguel Morales --- modules/.submodules.json | 1 + modules/openPairIdSystem.js | 146 ++++++++++++++++ test/spec/modules/openPairIdSystem_spec.js | 187 +++++++++++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 modules/openPairIdSystem.js create mode 100644 test/spec/modules/openPairIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index b45995e4d15..d1c05428530 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -36,6 +36,7 @@ "netIdSystem", "novatiqIdSystem", "oneKeyIdSystem", + "openPairIdSystem", "operaadsIdSystem", "permutiveIdentityManagerIdSystem", "pubmaticIdSystem", diff --git a/modules/openPairIdSystem.js b/modules/openPairIdSystem.js new file mode 100644 index 00000000000..6ce4f365848 --- /dev/null +++ b/modules/openPairIdSystem.js @@ -0,0 +1,146 @@ +/** + * This module adds Open PAIR Id to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/openPairIdSystem + * @requires module:modules/userId + */ + +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js' +import {logInfo} from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + */ + +const MODULE_NAME = 'openPairId'; +const DEFAULT_PUBLISHER_ID_KEY = 'pairId'; + +const DEFAULT_STORAGE_PUBLISHER_ID_KEYS = { + liveramp: '_lr_pairId' +}; + +const DEFAULT_ATYPE = 3; +const DEFAULT_SOURCE = 'pair-protocol.com'; + +const MATCH_METHOD = 3; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +function publisherIdFromLocalStorage(key) { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(key) : null; +} + +function publisherIdFromCookie(key) { + return storage.cookiesAreEnabled() ? storage.getCookie(key) : null; +} + +/** @type {Submodule} */ +export const openPairIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * used to specify vendor id + * @type {number} + */ + gvlid: VENDORLESS_GVLID, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @returns {{pairId:string} | undefined } + */ + decode(value) { + return value && Array.isArray(value) ? {'openPairId': value} : undefined; + }, + /** + * Performs action to obtain ID and return a value in the callback's response argument. + * @function getId + * @param {Object} config - The configuration object. + * @param {Object} config.params - The parameters from the configuration. + * @returns {{id: string[] | undefined}} The obtained IDs or undefined if no IDs are found. + */ + getId(config) { + const publisherIdsString = publisherIdFromLocalStorage(DEFAULT_PUBLISHER_ID_KEY) || publisherIdFromCookie(DEFAULT_PUBLISHER_ID_KEY); + let ids = [] + + if (publisherIdsString && typeof publisherIdsString == 'string') { + try { + ids = ids.concat(JSON.parse(atob(publisherIdsString))); + } catch (error) { + logInfo(error) + } + } + + const configParams = (config && config.params) ? config.params : {}; + const cleanRooms = Object.keys(configParams); + + for (let i = 0; i < cleanRooms.length; i++) { + const cleanRoom = cleanRooms[i]; + const cleanRoomParams = configParams[cleanRoom]; + + const cleanRoomStorageLocation = cleanRoomParams.storageKey || DEFAULT_STORAGE_PUBLISHER_ID_KEYS[cleanRoom]; + const cleanRoomValue = publisherIdFromLocalStorage(cleanRoomStorageLocation) || publisherIdFromCookie(cleanRoomStorageLocation); + + if (cleanRoomValue) { + try { + const parsedValue = atob(cleanRoomValue); + + if (parsedValue) { + const obj = JSON.parse(parsedValue); + + if (obj && typeof obj === 'object' && obj.envelope) { + ids = ids.concat(obj.envelope); + } else { + logInfo('Open Pair ID: Parsed object is not valid or does not contain envelope'); + } + } else { + logInfo('Open Pair ID: Decoded value is empty'); + } + } catch (error) { + logInfo('Open Pair ID: Error parsing JSON: ', error); + } + } else { + logInfo('Open Pair ID: data clean room value for pairId from storage is empty or null'); + } + } + + if (ids.length == 0) { + logInfo('Open Pair ID: no ids found') + return undefined; + } + + return {'id': ids}; + }, + eids: { + openPairId: function(values, config = {}) { + const inserter = config.inserter; + const matcher = config.matcher; + + const source = DEFAULT_SOURCE; + const atype = DEFAULT_ATYPE; + + return [ + { + source: source, + mm: MATCH_METHOD, + inserter: inserter, + matcher: matcher, + uids: values.map(function(value) { + return { + id: value, + atype: atype + } + }) + } + ]; + } + }, +}; + +submodule('userId', openPairIdSubmodule); diff --git a/test/spec/modules/openPairIdSystem_spec.js b/test/spec/modules/openPairIdSystem_spec.js new file mode 100644 index 00000000000..421daebf9a2 --- /dev/null +++ b/test/spec/modules/openPairIdSystem_spec.js @@ -0,0 +1,187 @@ +import { storage, openPairIdSubmodule } from 'modules/openPairIdSystem.js'; +import * as utils from 'src/utils.js'; + +import { + attachIdSystem, + coreStorage, + getConsentHash, + init, + startAuctionHook, + setSubmoduleRegistry +} from '../../../modules/userId/index.js'; + +import {createEidsArray, getEids} from '../../../modules/userId/eids.js'; + +describe('openPairId', function () { + let sandbox; + let logInfoStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + logInfoStub = sandbox.stub(utils, 'logInfo'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should read publisher id from specified clean room if configured with storageKey', function() { + let publisherIds = ['dGVzdC1wYWlyLWlkMQ==', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('habu_pairId_custom').returns(btoa(JSON.stringify({'envelope': publisherIds}))); + + let id = openPairIdSubmodule.getId({ + params: { + habu: { + storageKey: 'habu_pairId_custom' + } + }}) + + expect(id).to.be.deep.equal({id: publisherIds}); + }); + + it('should read publisher id from liveramp with default storageKey and additional clean room with configured storageKey', function() { + let getDataStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + let liveRampPublisherIds = ['lr-test-pair-id1', 'lr-test-pair-id2', 'lr-test-pair-id3']; + getDataStub.withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': liveRampPublisherIds}))); + + let habuPublisherIds = ['habu-test-pair-id1', 'habu-test-pair-id2', 'habu-test-pair-id3']; + getDataStub.withArgs('habu_pairId_custom').returns(btoa(JSON.stringify({'envelope': habuPublisherIds}))); + + let id = openPairIdSubmodule.getId({ + params: { + habu: { + storageKey: 'habu_pairId_custom' + }, + liveramp: {} + }}) + + expect(id).to.be.deep.equal({id: habuPublisherIds.concat(liveRampPublisherIds)}); + }); + + it('should log an error if no ID is found when getId', function() { + openPairIdSubmodule.getId({ params: {} }); + expect(logInfoStub.calledOnce).to.be.true; + }); + + it('should read publisher id from local storage if exists', function() { + let publisherIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('pairId').returns(btoa(JSON.stringify(publisherIds))); + + let id = openPairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: publisherIds}); + }); + + it('should read publisher id from cookie if exists', function() { + let publisherIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getCookie').withArgs('pairId').returns(btoa(JSON.stringify(publisherIds))); + + let id = openPairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: publisherIds}); + }); + + it('should read publisher id from default liveramp envelope local storage key if configured', function() { + let publisherIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': publisherIds}))); + let id = openPairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: publisherIds}) + }); + + it('should read publisher id from default liveramp envelope cookie entry if configured', function() { + let publisherIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': publisherIds}))); + let id = openPairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: publisherIds}) + }); + + it('should read publisher id from specified liveramp envelope cookie entry if configured with storageKey', function() { + let publisherIds = ['test-pair-id7', 'test-pair-id8', 'test-pair-id9']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('lr_pairId_custom').returns(btoa(JSON.stringify({'envelope': publisherIds}))); + let id = openPairIdSubmodule.getId({ + params: { + liveramp: { + storageKey: 'lr_pairId_custom' + } + }}) + expect(id).to.be.deep.equal({id: publisherIds}) + }); + + it('should not get data from storage if local storage and cookies are disabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + sandbox.stub(storage, 'cookiesAreEnabled').returns(false); + let id = openPairIdSubmodule.getId({ + params: { + liveramp: { + storageKey: 'lr_pairId_custom' + } + } + }) + expect(id).to.equal(undefined) + }); + + it('honors inserter, matcher', () => { + const config = { + inserter: 'some-domain.com', + matcher: 'another-domain.com' + }; + + const result = openPairIdSubmodule.eids.openPairId(['some-random-id-value'], config); + + expect(result.length).to.equal(1); + + expect(result[0]).to.deep.equal( + { + source: 'pair-protocol.com', + mm: 3, + inserter: 'some-domain.com', + matcher: 'another-domain.com', + uids: [ + { + atype: 3, + id: 'some-random-id-value' + } + ] + } + ); + }); + + describe('encoding', () => { + it('encodes and decodes the original value with atob/btoa', function () { + const value = 'dGVzdC1wYWlyLWlkMQ=='; + + let publisherIds = [value]; + + const stored = btoa(JSON.stringify({'envelope': publisherIds})); + + const read = JSON.parse(atob(stored)); + + expect(value).to.eq(read.envelope[0]); + }); + }); + + describe('eid', () => { + before(() => { + attachIdSystem(openPairIdSubmodule); + }); + + it('generates the minimal eids', function() { + const userId = { + openPairId: 'some-random-id-value' + }; + + const newEids = createEidsArray(userId); + + expect(newEids.length).to.equal(1); + + expect(newEids[0]).to.deep.include({ + source: 'pair-protocol.com', + mm: 3, + uids: [{ id: 'some-random-id-value', atype: 3 }] + }); + }); + }); +}); From 95b1139b210abe55370b2052fb7bcb025f6521c5 Mon Sep 17 00:00:00 2001 From: "Md. Soman Mia Sarker" Date: Thu, 27 Mar 2025 18:19:59 +0600 Subject: [PATCH 1039/1097] Adgrid Bid Adapter : add new param placement (#12901) * Added new optional param placement * Placement params is required now --- modules/adgridBidAdapter.js | 3 ++- modules/adgridBidAdapter.md | 6 ++++-- test/spec/modules/adgridBidAdapter_spec.js | 10 ++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/adgridBidAdapter.js b/modules/adgridBidAdapter.js index 13e603c3636..427eada4c50 100644 --- a/modules/adgridBidAdapter.js +++ b/modules/adgridBidAdapter.js @@ -21,7 +21,7 @@ function isBidRequestValid(bid) { return false; } - return !!bid.params.domainId; + return !!bid.params.domainId && !!bid.params.placement; } /** @@ -160,6 +160,7 @@ function getBidData(bid) { deviceUa: bid.ortb2?.device?.ua, domain: bid.ortb2?.site?.publisher?.domain, domainId: bid.params.domainId, + placement: bid.params.placement, code: bid.adUnitCode }; diff --git a/modules/adgridBidAdapter.md b/modules/adgridBidAdapter.md index 94eda01e895..205c6ca31bf 100644 --- a/modules/adgridBidAdapter.md +++ b/modules/adgridBidAdapter.md @@ -23,7 +23,8 @@ var adUnits = [ bids: [{ bidder: 'adgrid', params: { - domainId: 12345 + domainId: 12345, + placement: 'leaderboard' } }] }, @@ -37,7 +38,8 @@ var adUnits = [ bids: [{ bidder: 'adgrid', params: { - domainId: 67890 + domainId: 67890, + placement: 'adhesion' } }] } diff --git a/test/spec/modules/adgridBidAdapter_spec.js b/test/spec/modules/adgridBidAdapter_spec.js index 7e6638683a7..fbc71fc7948 100644 --- a/test/spec/modules/adgridBidAdapter_spec.js +++ b/test/spec/modules/adgridBidAdapter_spec.js @@ -25,7 +25,8 @@ describe('AdGrid Bid Adapter', function () { } }, params: { - domainId: 12345 + domainId: 12345, + placement: 'leaderboard' } }]; @@ -41,17 +42,18 @@ describe('AdGrid Bid Adapter', function () { } }, params: { - domainId: 12345 + domainId: 12345, + placement: 'video1' } }]; describe('isBidRequestValid', function () { - it('Should return true when domainId exist inside params object', function () { + it('Should return true when domainId and placement exist inside params object', function () { const isBidValid = spec.isBidRequestValid(bannerRequest[0]); expect(isBidValid).to.be.true; }); - it('Should return false when domainId is not exist inside params object', function () { + it('Should return false when domainId and placement are not exist inside params object', function () { const isBidNotValid = spec.isBidRequestValid(null); expect(isBidNotValid).to.be.false; }); From dd03a73bac00c300bd01d21252183ac0b9e952f3 Mon Sep 17 00:00:00 2001 From: robustadev Date: Thu, 27 Mar 2025 05:49:29 -0700 Subject: [PATCH 1040/1097] Robusta Bid Adapter: New bid adapter (#12797) * robustaBidAdapter: New bid adapter * fix: robusta lint issues --- modules/robustaBidAdapter.js | 88 ++++++++++++ modules/robustaBidAdapter.md | 76 ++++++++++ test/spec/modules/robustaBidAdapter_spec.js | 150 ++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 modules/robustaBidAdapter.js create mode 100644 modules/robustaBidAdapter.md create mode 100644 test/spec/modules/robustaBidAdapter_spec.js diff --git a/modules/robustaBidAdapter.js b/modules/robustaBidAdapter.js new file mode 100644 index 00000000000..91ce043c034 --- /dev/null +++ b/modules/robustaBidAdapter.js @@ -0,0 +1,88 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { deepSetValue } from '../src/utils.js'; +import { config } from '../src/config.js'; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.params', bidRequest.params); + return imp; + } +}); + +const BIDDER_CODE = 'robusta'; +const VERSION = '1.0.0'; +const METHOD = 'POST'; +const DEFAULT_RTB_DOMAIN = 'pbjs.baristartb.com'; +const DEFAULT_SYNC_DOMAIN = 'sync.baristartb.com'; + +function isBidRequestValid(bidRequest) { + return !!bidRequest.params.lineItemId; +} + +function buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ bidderRequest, bidRequests }); + const domain = config.getConfig('rtbDomain') || DEFAULT_RTB_DOMAIN; + + return [{ + method: METHOD, + url: `//${domain}/api/prebid`, + data: data, + options: { + withCredentials: false + } + }] +} + +function interpretResponse(response, request) { + const bids = converter.fromORTB({ response: response.body, request: request.data }); + + return bids; +} + +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + + let syncParams = ''; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + const domain = config.getConfig('syncDomain') || DEFAULT_SYNC_DOMAIN; + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `//${domain}/api/sync?` + syncParams + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: `//${domain}/api/sync?` + syncParams + }); + } + return syncs; +} + +export const spec = { + code: BIDDER_CODE, + version: VERSION, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/robustaBidAdapter.md b/modules/robustaBidAdapter.md new file mode 100644 index 00000000000..14be3b2c40e --- /dev/null +++ b/modules/robustaBidAdapter.md @@ -0,0 +1,76 @@ +# Overview + +Module Name: Robusta Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@robustadigital.com + +# Description + +Connects to Robusta's demand sources to fetch bids. +Please use `robusta` as the bidder code. + +# Bid Params + +| Name | Scope | Description | Example | Type | +|------|--------|-------------|---------|------| +| lineItemId | Required | The Line Item ID | `'123'` | `string` | + +# Example Ad Unit Config + +```javascript +var adUnits = [ + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + bids: [{ + bidder: 'robusta', + params: { + lineItemId: '323bfac4-a3cb-40e8-a3ae-e9832b35f969' + } + }] + } +]; +``` + +# User Sync + +Robusta bid adapter supports both iframe and image-based user syncing. Configuration example: + +```javascript +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: ['robusta'], + filter: 'include' + }, + image: { + bidders: ['robusta'], + filter: 'include' + } + } + } +}); +``` + +# Additional Configuration + +The adapter supports custom RTB and sync domains through Prebid.js configuration: + +```javascript +pbjs.setBidderConfig({ + bidders: ['robusta'], + config: { + rtbDomain: 'custom.rtb.domain.com', // Optional: Override default RTB domain + syncDomain: 'custom.sync.domain.com' // Optional: Override default sync domain + } +}); +``` + +Default domains: +- RTB Domain: pbjs.baristartb.com +- Sync Domain: sync.baristartb.com \ No newline at end of file diff --git a/test/spec/modules/robustaBidAdapter_spec.js b/test/spec/modules/robustaBidAdapter_spec.js new file mode 100644 index 00000000000..811a0d1b351 --- /dev/null +++ b/test/spec/modules/robustaBidAdapter_spec.js @@ -0,0 +1,150 @@ +import { spec } from 'modules/robustaBidAdapter.js'; +import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; + +describe('robustaBidAdapter', function () { + const validBidRequest = { + bidId: 'bid123', + params: { + lineItemId: '12345' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const validBidderRequest = { + bidderCode: 'robusta', + auctionId: 'auction123', + bidderRequestId: 'req123', + timeout: 3000, + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: true + } + }; + + const validServerResponse = { + body: { + id: 'auction123', + seatbid: [{ + bid: [{ + mtype: 1, + id: 'bid123', + impid: 'bid123', + price: 0.5, + adm: '
ad
', + w: 300, + h: 250, + crid: 'creative123' + }] + }], + cur: 'USD' + } + }; + + describe('isBidRequestValid', function () { + it('should return true when lineItemId is present', function () { + expect(spec.isBidRequestValid(validBidRequest)).to.be.true; + }); + + it('should return false when lineItemId is missing', function () { + const bid = deepClone(validBidRequest); + delete bid.params.lineItemId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should create request with correct structure', function () { + const requests = spec.buildRequests([validBidRequest], validBidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('//pbjs.baristartb.com/api/prebid'); + expect(requests[0].options.withCredentials).to.be.false; + }); + + it('should use custom rtbDomain if configured', function () { + config.setBidderConfig({ bidders: ['robusta'], config: { rtbDomain: 'custom.domain.com' } }); + const requests = config.runWithBidder(spec.code, () => spec.buildRequests([validBidRequest], validBidderRequest)); + + expect(requests[0].url).to.equal('//custom.domain.com/api/prebid'); + config.resetConfig(); + }); + + it('should include bid params in imp.ext.params', function () { + const requests = spec.buildRequests([validBidRequest], validBidderRequest); + const imp = requests[0].data.imp[0]; + + expect(imp.ext.params).to.deep.equal(validBidRequest.params); + }); + }); + + describe('interpretResponse', function () { + it('should return valid bid response', function () { + const request = spec.buildRequests([validBidRequest], validBidderRequest)[0]; + const result = spec.interpretResponse(validServerResponse, request); + + expect(result.bids).to.be.an('array'); + expect(result.bids).to.have.lengthOf(1); + expect(result.bids[0]).to.include({ + requestId: 'bid123', + cpm: 0.5, + width: 300, + height: 250, + ad: '
ad
', + creativeId: 'creative123', + netRevenue: true, + ttl: 30, + currency: 'USD' + }); + }); + + it('should return empty bids array if no valid bids', function () { + const emptyResponse = { body: { id: 'auction123', seatbid: [] } }; + const request = spec.buildRequests([validBidRequest], validBidderRequest)[0]; + const result = spec.interpretResponse(emptyResponse, request); + + expect(result.bids).to.be.an('array'); + expect(result.bids).to.have.lengthOf(0); + }); + }); + + describe('getUserSyncs', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' + }; + + it('should return iframe sync when iframeEnabled', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include('//sync.baristartb.com/api/sync?'); + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.include('gdpr_consent=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'); + }); + + it('should return pixel sync when pixelEnabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [], gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('should use custom syncDomain if configured', function () { + config.setBidderConfig({ bidders: ['robusta'], config: { syncDomain: 'custom.sync.com' } }); + const syncs = config.runWithBidder(spec.code, () => spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent)); + expect(syncs[0].url).to.include('//custom.sync.com/api/sync?'); + config.resetConfig(); + }); + + it('should handle missing gdprConsent', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs[0].url).to.not.include('gdpr'); + expect(syncs[0].url).to.not.include('gdpr_consent'); + }); + }); +}); From e2582316c5a48d09ec412caae447b80ee7e7fda8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 27 Mar 2025 06:16:10 -0700 Subject: [PATCH 1041/1097] consentManagementTcf: do not require CMP API to be available when Prebid loads (#12922) --- libraries/consentManagement/cmUtils.js | 60 ++++++++++++++++----- test/spec/libraries/cmUtils_spec.js | 1 - test/spec/modules/consentManagement_spec.js | 31 ++++++++--- 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/libraries/consentManagement/cmUtils.js b/libraries/consentManagement/cmUtils.js index 508ee82a546..b2afc8c7322 100644 --- a/libraries/consentManagement/cmUtils.js +++ b/libraries/consentManagement/cmUtils.js @@ -12,8 +12,6 @@ export function consentManagementHook(name, loadConsentData) { if (error && (!consentData || !SEEN.has(error))) { SEEN.add(error); logWarn(error.message, ...(error.args || [])); - } else if (consentData) { - logInfo(`${name.toUpperCase()}: User consent information already known. Pulling internally stored information...`); } fn.call(this, reqBidsConfigObj); }).catch((error) => { @@ -117,12 +115,39 @@ export function configParser( function msg(message) { return `consentManagement.${namespace} ${message}`; } - let requestBidsHook, consentDataLoaded, staticConsentData; + let requestBidsHook, cdLoader, staticConsentData; + + function attachActivityParams(next, params) { + return next(Object.assign({[`${namespace}Consent`]: consentDataHandler.getConsentData()}, params)); + } + + function loadConsentData() { + return cdLoader().then(({error}) => ({error, consentData: consentDataHandler.getConsentData()})) + } + + function activate() { + if (requestBidsHook == null) { + requestBidsHook = consentManagementHook(namespace, () => cdLoader()); + getGlobal().requestBids.before(requestBidsHook, 50); + buildActivityParams.before(attachActivityParams); + logInfo(`${displayName} consentManagement module has been activated...`) + } + } + + function reset() { + if (requestBidsHook != null) { + getGlobal().requestBids.getHooks({hook: requestBidsHook}).remove(); + buildActivityParams.getHooks({hook: attachActivityParams}).remove(); + requestBidsHook = null; + } + } + return function getConsentConfig(config) { config = config?.[namespace]; if (!config || typeof config !== 'object') { logWarn(msg(`config not defined, exiting consent manager module`)); + reset(); return {}; } let cmpHandler; @@ -156,23 +181,30 @@ export function configParser( } else { setupCmp = cmpHandlers[cmpHandler]; } - consentDataLoaded = lookupConsentData({ + + const lookup = () => lookupConsentData({ name: displayName, consentDataHandler, setupCmp, cmpTimeout, actionTimeout, getNullConsent, - }) - const loadConsentData = () => consentDataLoaded.then(({error}) => ({error, consentData: consentDataHandler.getConsentData()})) - if (requestBidsHook == null) { - requestBidsHook = consentManagementHook(namespace, () => consentDataLoaded); - getGlobal().requestBids.before(requestBidsHook, 50); - buildActivityParams.before((next, params) => { - return next(Object.assign({[`${namespace}Consent`]: consentDataHandler.getConsentData()}, params)); - }); - } - logInfo(`${displayName} consentManagement module has been activated...`) + }); + + cdLoader = (() => { + let cd; + return function () { + if (cd == null) { + cd = lookup().catch(err => { + cd = null; + throw err; + }) + } + return cd; + } + })(); + + activate(); return { cmpHandler, cmpTimeout, diff --git a/test/spec/libraries/cmUtils_spec.js b/test/spec/libraries/cmUtils_spec.js index 9d23fb7137a..db6d05f07c3 100644 --- a/test/spec/libraries/cmUtils_spec.js +++ b/test/spec/libraries/cmUtils_spec.js @@ -88,7 +88,6 @@ describe('consent management utils', () => { cmHook(next, {}); await loadResult; sinon.assert.notCalled(utils.logWarn) - sinon.assert.calledWith(utils.logInfo, sinon.match('already known')); }) }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 85c95130af3..e511a117ba9 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -7,6 +7,12 @@ import 'src/prebid.js'; let expect = require('chai').expect; describe('consentManagement', function () { + function mockCMP(cmpResponse) { + return function(...args) { + args[2](Object.assign({eventStatus: 'tcloaded'}, cmpResponse), true); + } + } + describe('setConsentConfig tests:', function () { describe('empty setConsentConfig value', function () { beforeEach(function () { @@ -26,7 +32,6 @@ describe('consentManagement', function () { expect(consentConfig.cmpHandler).to.be.equal('iab'); expect(consentConfig.cmpTimeout).to.be.equal(10000); expect(gdprScope).to.be.equal(false); - sinon.assert.callCount(utils.logInfo, 3); }); it('should exit consent manager if config is not an object', async function () { @@ -294,6 +299,24 @@ describe('consentManagement', function () { expect(gdprDataHandler.ready).to.be.true; }); + it('should poll again to check if it appears later', async () => { + await setConsentConfig({ + cmpApi: 'iab', + timeout: 10, + }); + expect(await runHook()).to.be.false; + try { + window.__tcfapi = mockCMP({ + gdprApplies: true, + tcString: 'xyz', + }); + expect(await runHook()).to.be.true; + expect(gdprDataHandler.getConsentData().consentString).to.eql('xyz') + } finally { + delete window.__tcfapi + } + }) + it('should not trip when adUnits have no size', async () => { await setConsentConfig(staticConfig); expect(await runHook({adUnits: [{code: 'test', mediaTypes: {video: {}}}]})).to.be.true; @@ -321,12 +344,6 @@ describe('consentManagement', function () { describe('already known consentData:', function () { let cmpStub = sinon.stub(); - function mockCMP(cmpResponse) { - return function(...args) { - args[2](Object.assign({eventStatus: 'tcloaded'}, cmpResponse), true); - } - } - beforeEach(function () { window.__tcfapi = function () { }; }); From 86f32b7e916e8f5135d84b44ac0bf52523462579 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Thu, 27 Mar 2025 14:46:14 +0100 Subject: [PATCH 1042/1097] LiveIntent User ID Module And Analytics Adapter: Built-in Treatment/Holdout Mechanism And Auction Events Collection Improvements (#12856) * Adjust the analytics adapter * Trigger Build * Trigger Build --- libraries/liveIntentId/externalIdSystem.js | 6 +- libraries/liveIntentId/idSystem.js | 7 +- libraries/liveIntentId/shared.js | 26 +- modules/liveIntentAnalyticsAdapter.js | 193 +- test/fixtures/liveIntentAuctionEvents.js | 2347 +++++++++++++++++ .../liveIntentAnalyticsAdapter_spec.js | 366 +-- .../liveIntentExternalIdSystem_spec.js | 188 +- test/spec/modules/liveIntentIdSystem_spec.js | 272 +- 8 files changed, 2990 insertions(+), 415 deletions(-) create mode 100644 test/fixtures/liveIntentAuctionEvents.js diff --git a/libraries/liveIntentId/externalIdSystem.js b/libraries/liveIntentId/externalIdSystem.js index 9fcb9e6da1b..794b4c64c5f 100644 --- a/libraries/liveIntentId/externalIdSystem.js +++ b/libraries/liveIntentId/externalIdSystem.js @@ -1,7 +1,7 @@ import { logError } from '../../src/utils.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../src/adapterManager.js'; import { submodule } from '../../src/hook.js'; -import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, parseRequestedAttributes, composeIdObject, eids, GVLID, PRIMARY_IDS, makeSourceEventToSend } from './shared.js' +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, parseRequestedAttributes, composeResult, eids, GVLID, PRIMARY_IDS, makeSourceEventToSend, setUpTreatment } from './shared.js' // Reference to the client for the liQHub. let cachedClientRef @@ -149,11 +149,12 @@ export const liveIntentExternalIdSubmodule = { */ decode(value, config) { const configParams = config?.params ?? {}; + setUpTreatment(configParams); // Ensure client is initialized and we fired at least one collect request. initializeClient(configParams) - return composeIdObject(value); + return composeResult(value, configParams) }, /** @@ -162,6 +163,7 @@ export const liveIntentExternalIdSubmodule = { */ getId(config) { const configParams = config?.params ?? {}; + setUpTreatment(configParams); const clientRef = initializeClient(configParams) diff --git a/libraries/liveIntentId/idSystem.js b/libraries/liveIntentId/idSystem.js index a9b8052c752..49db1e36efa 100644 --- a/libraries/liveIntentId/idSystem.js +++ b/libraries/liveIntentId/idSystem.js @@ -11,7 +11,7 @@ import { submodule } from '../../src/hook.js'; import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { getStorageManager } from '../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../src/activities/modules.js'; -import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, GVLID, DEFAULT_DELAY, PRIMARY_IDS, parseRequestedAttributes, makeSourceEventToSend } from './shared.js' +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeResult, eids, GVLID, DEFAULT_DELAY, PRIMARY_IDS, parseRequestedAttributes, makeSourceEventToSend, setUpTreatment } from './shared.js' /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -191,13 +191,14 @@ export const liveIntentIdSubmodule = { */ decode(value, config) { const configParams = (config && config.params) || {}; + setUpTreatment(configParams); if (!liveConnect) { initializeLiveConnect(configParams); } tryFireEvent(); - return composeIdObject(value); + return composeResult(value, configParams); }, /** @@ -208,6 +209,8 @@ export const liveIntentIdSubmodule = { */ getId(config) { const configParams = (config && config.params) || {}; + setUpTreatment(configParams); + const liveConnect = initializeLiveConnect(configParams); if (!liveConnect) { return; diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js index 1b48fc19368..84067a18d2a 100644 --- a/libraries/liveIntentId/shared.js +++ b/libraries/liveIntentId/shared.js @@ -2,6 +2,7 @@ import {UID1_EIDS} from '../uid1Eids/uid1Eids.js'; import {UID2_EIDS} from '../uid2Eids/uid2Eids.js'; import { getRefererInfo } from '../../src/refererDetection.js'; import { coppaDataHandler } from '../../src/adapterManager.js'; +import { isNumber } from '../../src/utils.js' export const PRIMARY_IDS = ['libp']; export const GVLID = 148; @@ -10,6 +11,7 @@ export const DEFAULT_DELAY = 500; export const MODULE_NAME = 'liveIntentId'; export const LI_PROVIDER_DOMAIN = 'liveintent.com'; export const DEFAULT_REQUESTED_ATTRIBUTES = { 'nonId': true }; +export const DEFAULT_TREATMENT_RATE = 0.95; export function parseRequestedAttributes(overrides) { function renameAttribute(attribute) { @@ -54,7 +56,19 @@ export function makeSourceEventToSend(configParams) { } } -export function composeIdObject(value) { +export function composeResult(value, config) { + if (config.activatePartialTreatment) { + if (window.liModuleEnabled) { + return composeIdObject(value); + } else { + return {}; + } + } else { + return composeIdObject(value); + } +} + +function composeIdObject(value) { const result = {}; // old versions stored lipbid in unifiedId. Ensure that we can still read the data. @@ -134,6 +148,16 @@ export function composeIdObject(value) { return result } +export function setUpTreatment(config) { + // If the treatment decision has not been made yet + // and Prebid is configured to make this decision. + if (window.liModuleEnabled === undefined && config.activatePartialTreatment) { + const treatmentRate = isNumber(window.liTreatmentRate) ? window.liTreatmentRate : DEFAULT_TREATMENT_RATE; + window.liModuleEnabled = Math.random() < treatmentRate; + window.liTreatmentRate = treatmentRate; + }; +} + export const eids = { ...UID1_EIDS, ...UID2_EIDS, diff --git a/modules/liveIntentAnalyticsAdapter.js b/modules/liveIntentAnalyticsAdapter.js index b9624958592..a86b6412f8d 100644 --- a/modules/liveIntentAnalyticsAdapter.js +++ b/modules/liveIntentAnalyticsAdapter.js @@ -1,134 +1,121 @@ -import {ajax} from '../src/ajax.js'; -import { generateUUID, logInfo, logWarn } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { generateUUID, isNumber } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; -import { auctionManager } from '../src/auctionManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { config as prebidConfig } from '../src/config.js'; +import { auctionManager } from '../src/auctionManager.js'; const ANALYTICS_TYPE = 'endpoint'; const URL = 'https://wba.liadm.com/analytic-events'; const GVL_ID = 148; const ADAPTER_CODE = 'liveintent'; -const DEFAULT_BID_WON_TIMEOUT = 2000; -const { AUCTION_END } = EVENTS; -let bidWonTimeout; - -function handleAuctionEnd(args) { - setTimeout(() => { - const auction = auctionManager.index.getAuction({auctionId: args.auctionId}); - const winningBids = (auction) ? auction.getWinningBids() : []; - const data = createAnalyticsEvent(args, winningBids); - sendAnalyticsEvent(data); - }, bidWonTimeout); -} +const { AUCTION_INIT, BID_WON } = EVENTS; +const INTEGRATION_ID = '$$PREBID_GLOBAL$$'; -function getAnalyticsEventBids(bidsReceived) { - return bidsReceived.map(bid => { - return { - adUnitCode: bid.adUnitCode, - timeToRespond: bid.timeToRespond, - cpm: bid.cpm, - currency: bid.currency, - ttl: bid.ttl, - bidder: bid.bidder - }; - }); -} +let partnerIdFromUserIdConfig; +let sendAuctionInitEvents; + +let liAnalytics = Object.assign(adapter({URL, ANALYTICS_TYPE}), { + track({ eventType, args }) { + switch (eventType) { + case AUCTION_INIT: + if (sendAuctionInitEvents) { + handleAuctionInitEvent(args); + } + break; + case BID_WON: + handleBidWonEvent(args); + break; + } + } +}); + +function handleAuctionInitEvent(auctionInitEvent) { + const liveIntentIdsPresent = checkLiveIntentIdsPresent(auctionInitEvent.bidderRequests) + + // This is for old integration that enable or disable the user id module + // dependeing on the result of rolling the dice outside of Prebid. + const partnerIdFromAnalyticsLabels = auctionInitEvent.analyticsLabels?.partnerId; -function getBannerSizes(banner) { - if (banner && banner.sizes) { - return banner.sizes.map(size => { - const [width, height] = size; - return {w: width, h: height}; - }); - } else return []; + const data = { + id: generateUUID(), + aid: auctionInitEvent.auctionId, + u: getRefererInfo().page, + ats: auctionInitEvent.timestamp, + pid: partnerIdFromUserIdConfig || partnerIdFromAnalyticsLabels, + iid: INTEGRATION_ID, + tr: window.liTreatmentRate, + me: encodeBoolean(window.liModuleEnabled), + liip: encodeBoolean(liveIntentIdsPresent), + aun: auctionInitEvent?.adUnits?.length || 0 + }; + const filteredData = ignoreUndefined(data); + sendData('auction-init', filteredData); } -function getUniqueBy(arr, key) { - return [...new Map(arr.map(item => [item[key], item])).values()] +function handleBidWonEvent(bidWonEvent) { + const auction = auctionManager.index.getAuction({auctionId: bidWonEvent.auctionId}); + const liveIntentIdsPresent = checkLiveIntentIdsPresent(auction?.getBidRequests()) + + // This is for old integration that enable or disable the user id module + // depending on the result of rolling the dice outside of Prebid. + const partnerIdFromAnalyticsLabels = bidWonEvent.analyticsLabels?.partnerId; + + const data = { + id: generateUUID(), + aid: bidWonEvent.auctionId, + u: getRefererInfo().page, + ats: auction?.getAuctionStart(), + auc: bidWonEvent.adUnitCode, + auid: bidWonEvent.adUnitId, + cpm: bidWonEvent.cpm, + c: bidWonEvent.currency, + b: bidWonEvent.bidder, + bc: bidWonEvent.bidderCode, + pid: partnerIdFromUserIdConfig || partnerIdFromAnalyticsLabels, + iid: INTEGRATION_ID, + sts: bidWonEvent.requestTimestamp, + rts: bidWonEvent.responseTimestamp, + tr: window.liTreatmentRate, + me: encodeBoolean(window.liModuleEnabled), + liip: encodeBoolean(liveIntentIdsPresent) + }; + + const filteredData = ignoreUndefined(data); + sendData('bid-won', filteredData); } -function createAnalyticsEvent(args, winningBids) { - let payload = { - instanceId: generateUUID(), - url: getRefererInfo().page, - bidsReceived: getAnalyticsEventBids(args.bidsReceived), - auctionStart: args.timestamp, - auctionEnd: args.auctionEnd, - adUnits: [], - userIds: [], - bidders: [] - } - let allUserIds = []; - - if (args.adUnits) { - args.adUnits.forEach(unit => { - if (unit.mediaTypes && unit.mediaTypes.banner) { - payload['adUnits'].push({ - code: unit.code, - mediaType: 'banner', - sizes: getBannerSizes(unit.mediaTypes.banner), - ortb2Imp: unit.ortb2Imp - }); - } - if (unit.bids) { - let userIds = unit.bids.flatMap(getAnalyticsEventUserIds); - allUserIds.push(...userIds); - let bidders = unit.bids.map(({bidder, params}) => { - return { bidder, params } - }); - - payload['bidders'].push(...bidders); - } - }) - let uniqueUserIds = getUniqueBy(allUserIds, 'source'); - payload['userIds'] = uniqueUserIds; - } - payload['winningBids'] = getAnalyticsEventBids(winningBids); - payload['auctionId'] = args.auctionId; - return payload; +function encodeBoolean(value) { + return value === undefined ? undefined : value ? 'y' : 'n' } -function getAnalyticsEventUserIds(bid) { - if (bid && bid.userIdAsEids) { - return bid.userIdAsEids.map(({source, uids, ext}) => { - let analyticsEventUserId = {source, uids, ext}; - return ignoreUndefined(analyticsEventUserId) - }); - } else { return []; } +function checkLiveIntentIdsPresent(bidRequests) { + const eids = bidRequests?.flatMap(r => r?.bids).flatMap(b => b?.userIdAsEids); + return !!eids.find(eid => eid?.source === 'liveintent.com') || !!eids.flatMap(e => e?.uids).find(u => u?.ext?.provider === 'liveintent.com') } -function sendAnalyticsEvent(data) { - ajax(URL, { - success: function () { - logInfo('LiveIntent Prebid Analytics: send data success'); - }, - error: function (e) { - logWarn('LiveIntent Prebid Analytics: send data error' + e); - } - }, JSON.stringify(data), { - contentType: 'application/json', - method: 'POST' - }) +function sendData(path, data) { + const fields = Object.entries(data); + if (fields.length > 0) { + const params = fields.map(([key, value]) => key + '=' + encodeURIComponent(value)).join('&'); + ajax(URL + '/' + path + '?' + params, undefined, null, { method: 'GET' }); + } } function ignoreUndefined(data) { - const filteredData = Object.entries(data).filter(([key, value]) => value) - return Object.fromEntries(filteredData) + const filteredData = Object.entries(data).filter(([key, value]) => isNumber(value) || value); + return Object.fromEntries(filteredData); } -let liAnalytics = Object.assign(adapter({URL, ANALYTICS_TYPE}), { - track({ eventType, args }) { - if (eventType == AUCTION_END && args) { handleAuctionEnd(args); } - } -}); - // save the base class function liAnalytics.originEnableAnalytics = liAnalytics.enableAnalytics; // override enableAnalytics so we can get access to the config passed in from the page liAnalytics.enableAnalytics = function (config) { - bidWonTimeout = config?.options?.bidWonTimeout ?? DEFAULT_BID_WON_TIMEOUT; + const userIdModuleConfig = prebidConfig.getConfig('userSync.userIds').filter(m => m.name == 'liveIntentId')?.at(0)?.params + partnerIdFromUserIdConfig = userIdModuleConfig?.liCollectConfig?.appId || userIdModuleConfig?.distributorId; + sendAuctionInitEvents = config?.options.sendAuctionInitEvents; liAnalytics.originEnableAnalytics(config); // call the base class function }; diff --git a/test/fixtures/liveIntentAuctionEvents.js b/test/fixtures/liveIntentAuctionEvents.js new file mode 100644 index 00000000000..cb0198f7caa --- /dev/null +++ b/test/fixtures/liveIntentAuctionEvents.js @@ -0,0 +1,2347 @@ +export const AUCTION_INIT_EVENT = { + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'timestamp': 1739969798557, + 'auctionStatus': 'inProgress', + 'adUnits': [ + { + 'code': 'test-div', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'liveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ] + } + ], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'adUnitId': 'dfeffb52-99fc-4b81-b29c-2c275cd856f5', + 'transactionId': '2a3815e5-5285-415f-8d16-efdbe7eb9be5', + 'ortb2Imp': { + 'ext': { + 'tid': '2a3815e5-5285-415f-8d16-efdbe7eb9be5' + } + } + }, + { + 'code': 'test-div2', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'liveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ] + } + ], + 'sizes': [ + [ + 728, + 90 + ] + ], + 'adUnitId': '64ed0035-8f8b-49a9-956c-4d21be034175', + 'transactionId': '744fa14d-62a6-45a2-a880-c0a2a5aa6c01', + 'ortb2Imp': { + 'ext': { + 'tid': '744fa14d-62a6-45a2-a880-c0a2a5aa6c01' + } + } + } + ], + 'adUnitCodes': [ + 'test-div', + 'test-div2' + ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'bidderRequestId': '16ed05f7946bed', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placement_id': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'liveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': {} + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': '2a3815e5-5285-415f-8d16-efdbe7eb9be5', + 'adUnitId': 'dfeffb52-99fc-4b81-b29c-2c275cd856f5', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '2635cc051f8d47', + 'bidderRequestId': '16ed05f7946bed', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'src': 'client', + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1, + 'adapter.client.validate': 0.10000002384185791, + 'adapters.client.appnexus.validate': 0.10000002384185791, + 'adapter.client.buildRequests': 1.0999999940395355, + 'adapters.client.appnexus.buildRequests': 1.0999999940395355 + }, + 'auctionsCount': 1, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'deferBilling': false, + 'ortb2': { + 'source': {}, + 'site': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'publisher': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com' + }, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + }, + 'device': { + 'w': 3840, + 'h': 2160, + 'dnt': 1, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'language': 'en', + 'ext': { + 'vpw': 150, + 'vph': 1573 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not(A:Brand', + 'version': [ + '99' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '133' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '133' + ] + } + ], + 'mobile': 0 + } + }, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ] + } + } + } + }, + { + 'bidder': 'appnexus', + 'params': { + 'placement_id': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'liveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'liveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': {} + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ] + ] + } + }, + 'adUnitCode': 'test-div2', + 'transactionId': '744fa14d-62a6-45a2-a880-c0a2a5aa6c01', + 'adUnitId': '64ed0035-8f8b-49a9-956c-4d21be034175', + 'sizes': [ + [ + 728, + 90 + ] + ], + 'bidId': '39ca9c9224aa1a', + 'bidderRequestId': '16ed05f7946bed', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'src': 'client', + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1, + 'adapter.client.validate': 0.10000002384185791, + 'adapters.client.appnexus.validate': 0.10000002384185791, + 'adapter.client.buildRequests': 1.0999999940395355, + 'adapters.client.appnexus.buildRequests': 1.0999999940395355 + }, + 'auctionsCount': 1, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'deferBilling': false, + 'ortb2': { + 'source': {}, + 'site': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'publisher': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com' + }, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + }, + 'device': { + 'w': 3840, + 'h': 2160, + 'dnt': 1, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'language': 'en', + 'ext': { + 'vpw': 150, + 'vph': 1573 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not(A:Brand', + 'version': [ + '99' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '133' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '133' + ] + } + ], + 'mobile': 0 + } + }, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ] + } + } + } + } + ], + 'auctionStart': 1739969798557, + 'timeout': 2000, + 'refererInfo': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + ], + 'topmostLocation': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'location': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'canonicalUrl': null, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'ref': null, + 'legacy': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + ], + 'referer': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'canonicalUrl': null + } + }, + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1, + 'adapter.client.validate': 0.10000002384185791, + 'adapters.client.appnexus.validate': 0.10000002384185791, + 'adapter.client.buildRequests': 1.0999999940395355, + 'adapters.client.appnexus.buildRequests': 1.0999999940395355 + }, + 'ortb2': { + 'source': {}, + 'site': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'publisher': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com' + }, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + }, + 'device': { + 'w': 3840, + 'h': 2160, + 'dnt': 1, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'language': 'en', + 'ext': { + 'vpw': 150, + 'vph': 1573 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not(A:Brand', + 'version': [ + '99' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '133' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '133' + ] + } + ], + 'mobile': 0 + } + }, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'liveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'liveintent.com' + } + } + ] + } + ] + } + } + }, + 'start': 1739969798558 + } + ], + 'noBids': [], + 'bidsReceived': [], + 'bidsRejected': [], + 'winningBids': [], + 'timeout': 2000, + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'adapter.client.validate': [ + 0.10000002384185791 + ], + 'adapters.client.appnexus.validate': [ + 0.10000002384185791 + ], + 'adapter.client.buildRequests': [ + 1.0999999940395355 + ], + 'adapters.client.appnexus.buildRequests': [ + 1.0999999940395355 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1 + }, + 'seatNonBids': [] +} + +export const AUCTION_INIT_EVENT_NOT_LI = { + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'timestamp': 1739969798557, + 'auctionStatus': 'inProgress', + 'adUnits': [ + { + 'code': 'test-div', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'NOTNOTliveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ] + } + ], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'adUnitId': 'dfeffb52-99fc-4b81-b29c-2c275cd856f5', + 'transactionId': '2a3815e5-5285-415f-8d16-efdbe7eb9be5', + 'ortb2Imp': { + 'ext': { + 'tid': '2a3815e5-5285-415f-8d16-efdbe7eb9be5' + } + } + }, + { + 'code': 'test-div2', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ] + } + ], + 'sizes': [ + [ + 728, + 90 + ] + ], + 'adUnitId': '64ed0035-8f8b-49a9-956c-4d21be034175', + 'transactionId': '744fa14d-62a6-45a2-a880-c0a2a5aa6c01', + 'ortb2Imp': { + 'ext': { + 'tid': '744fa14d-62a6-45a2-a880-c0a2a5aa6c01' + } + } + } + ], + 'adUnitCodes': [ + 'test-div', + 'test-div2' + ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'bidderRequestId': '16ed05f7946bed', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placement_id': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': {} + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': '2a3815e5-5285-415f-8d16-efdbe7eb9be5', + 'adUnitId': 'dfeffb52-99fc-4b81-b29c-2c275cd856f5', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '2635cc051f8d47', + 'bidderRequestId': '16ed05f7946bed', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'src': 'client', + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1, + 'adapter.client.validate': 0.10000002384185791, + 'adapters.client.appnexus.validate': 0.10000002384185791, + 'adapter.client.buildRequests': 1.0999999940395355, + 'adapters.client.appnexus.buildRequests': 1.0999999940395355 + }, + 'auctionsCount': 1, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'deferBilling': false, + 'ortb2': { + 'source': {}, + 'site': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'publisher': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com' + }, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + }, + 'device': { + 'w': 3840, + 'h': 2160, + 'dnt': 1, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'language': 'en', + 'ext': { + 'vpw': 150, + 'vph': 1573 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not(A:Brand', + 'version': [ + '99' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '133' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '133' + ] + } + ], + 'mobile': 0 + } + }, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ] + } + } + } + }, + { + 'bidder': 'appnexus', + 'params': { + 'placement_id': 13144370 + }, + 'userId': { + 'lipb': { + 'nonId': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'sovrn': 'testSovrn', + 'medianet': 'testMadianet', + 'bidswitch': 'testBidswitch', + 'magnite': 'testMagnite', + 'lipbid': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==' + }, + 'bidswitch': { + 'id': 'testBidswitch', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'medianet': { + 'id': 'testMadianet', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'magnite': { + 'id': 'testMagnite', + 'ext': { + 'provider': 'NOTliveintent.com' + } + }, + 'sovrn': { + 'id': 'testSovrn', + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + }, + 'userIdAsEids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': {} + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ] + ] + } + }, + 'adUnitCode': 'test-div2', + 'transactionId': '744fa14d-62a6-45a2-a880-c0a2a5aa6c01', + 'adUnitId': '64ed0035-8f8b-49a9-956c-4d21be034175', + 'sizes': [ + [ + 728, + 90 + ] + ], + 'bidId': '39ca9c9224aa1a', + 'bidderRequestId': '16ed05f7946bed', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'src': 'client', + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1, + 'adapter.client.validate': 0.10000002384185791, + 'adapters.client.appnexus.validate': 0.10000002384185791, + 'adapter.client.buildRequests': 1.0999999940395355, + 'adapters.client.appnexus.buildRequests': 1.0999999940395355 + }, + 'auctionsCount': 1, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'deferBilling': false, + 'ortb2': { + 'source': {}, + 'site': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'publisher': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com' + }, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + }, + 'device': { + 'w': 3840, + 'h': 2160, + 'dnt': 1, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'language': 'en', + 'ext': { + 'vpw': 150, + 'vph': 1573 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not(A:Brand', + 'version': [ + '99' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '133' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '133' + ] + } + ], + 'mobile': 0 + } + }, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ] + } + } + } + } + ], + 'auctionStart': 1739969798557, + 'timeout': 2000, + 'refererInfo': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + ], + 'topmostLocation': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'location': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'canonicalUrl': null, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'ref': null, + 'legacy': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + ], + 'referer': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html', + 'canonicalUrl': null + } + }, + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1, + 'adapter.client.validate': 0.10000002384185791, + 'adapters.client.appnexus.validate': 0.10000002384185791, + 'adapter.client.buildRequests': 1.0999999940395355, + 'adapters.client.appnexus.buildRequests': 1.0999999940395355 + }, + 'ortb2': { + 'source': {}, + 'site': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com', + 'publisher': { + 'domain': 'vdreiling-lab-test.s3.us-east-1.amazonaws.com' + }, + 'page': 'https://vdreiling-lab-test.s3.us-east-1.amazonaws.com/prebid-presentation/pageWith2.html' + }, + 'device': { + 'w': 3840, + 'h': 2160, + 'dnt': 1, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'language': 'en', + 'ext': { + 'vpw': 150, + 'vph': 1573 + }, + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Not(A:Brand', + 'version': [ + '99' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '133' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '133' + ] + } + ], + 'mobile': 0 + } + }, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'NOTliveintent.com', + 'uids': [ + { + 'id': '12-hMaZCgZ9Q99h3cGDam21dEfxTawgIafx1J1zxvXTHwnrXUHWudCwcgIjkbTbZpRd2DV9ivSyz2L9t27VTNWXYAvYmk82zmrmciW71dSFq00zHg==', + 'atype': 3 + } + ] + }, + { + 'source': 'bidswitch.net', + 'uids': [ + { + 'id': 'testBidswitch', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'media.net', + 'uids': [ + { + 'id': 'testMadianet', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'rubiconproject.com', + 'uids': [ + { + 'id': 'testMagnite', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + }, + { + 'source': 'liveintent.sovrn.com', + 'uids': [ + { + 'id': 'testSovrn', + 'atype': 3, + 'ext': { + 'provider': 'NOTliveintent.com' + } + } + ] + } + ] + } + } + }, + 'start': 1739969798558 + } + ], + 'noBids': [], + 'bidsReceived': [], + 'bidsRejected': [], + 'winningBids': [], + 'timeout': 2000, + 'metrics': { + 'userId.init.consent': [ + 0 + ], + 'userId.mod.init': [ + 2.0999999940395355 + ], + 'userId.mods.liveIntentId.init': [ + 2.0999999940395355 + ], + 'userId.init.modules': [ + 2.2999999821186066 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 626.0999999940395 + ], + 'userId.mods.liveIntentId.callback': [ + 626.0999999940395 + ], + 'userId.callbacks.total': [ + 626.3000000119209 + ], + 'userId.total': [ + 629.5999999940395 + ], + 'adapter.client.validate': [ + 0.10000002384185791 + ], + 'adapters.client.appnexus.validate': [ + 0.10000002384185791 + ], + 'adapter.client.buildRequests': [ + 1.0999999940395355 + ], + 'adapters.client.appnexus.buildRequests': [ + 1.0999999940395355 + ], + 'requestBids.userId': 623.7999999821186, + 'requestBids.validate': 0.20000001788139343, + 'requestBids.makeRequests': 1 + }, + 'seatNonBids': [] +} + +export const BID_WON_EVENT = { + 'bidderCode': 'appnexus', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '4e02072b881823', + 'requestId': '3fae2718fd70f', + 'transactionId': '6daa1dac-9eea-47ce-82ce-ce9681df1ec5', + 'adUnitId': 'afc6bc6a-3082-4940-b37f-d22e1b026e48', + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 1.5, + 'creativeId': 98493734, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'test-div2', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885, + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'bsid': '9325' + } + ] + }, + 'brandId': 555545 + }, + 'ad': "
", + 'metrics': { + 'userId.init.consent': [ + 0.09999999403953552 + ], + 'userId.mod.init': [ + 1.7999999821186066 + ], + 'userId.mods.liveIntentId.init': [ + 1.7999999821186066 + ], + 'userId.init.modules': [ + 1.9000000059604645 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 149.69999998807907 + ], + 'userId.mods.liveIntentId.callback': [ + 149.69999998807907 + ], + 'userId.callbacks.total': [ + 149.80000001192093 + ], + 'userId.total': [ + 152.90000000596046 + ], + 'requestBids.userId': 147.2999999821186, + 'requestBids.validate': 0.30000001192092896, + 'requestBids.makeRequests': 0.699999988079071, + 'requestBids.total': 215.7999999821186, + 'requestBids.callBids': 65, + 'adapter.client.validate': 0, + 'adapters.client.appnexus.validate': 0, + 'adapter.client.buildRequests': 1.199999988079071, + 'adapters.client.appnexus.buildRequests': 1.199999988079071, + 'adapter.client.total': 62.69999998807907, + 'adapters.client.appnexus.total': 62.69999998807907, + 'adapter.client.net': 59.5, + 'adapters.client.appnexus.net': 59.5, + 'adapter.client.interpretResponse': 0.5, + 'adapters.client.appnexus.interpretResponse': 0.5, + 'addBidResponse.validate': 0.09999999403953552, + 'addBidResponse.total': 0.9000000059604645, + 'render.deferred': null, + 'render.pending': 130.60000002384186, + 'render.e2e': 346.40000000596046, + 'adserver.pending': 130.80000001192093, + 'adserver.e2e': 346.59999999403954 + }, + 'adapterCode': 'appnexus', + 'originalCpm': 1.5, + 'originalCurrency': 'USD', + 'deferBilling': false, + 'deferRendering': false, + 'responseTimestamp': 1739971147806, + 'requestTimestamp': 1739971147744, + 'bidder': 'appnexus', + 'timeToRespond': 62, + 'pbLg': '1.50', + 'pbMg': '1.50', + 'pbHg': '1.50', + 'pbAg': '1.50', + 'pbDg': '1.50', + 'pbCg': '', + 'size': '728x90', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '4e02072b881823', + 'hb_pb': '1.50', + 'hb_size': '728x90', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '', + 'hb_crid': 98493734 + }, + 'latestTargetedAuctionId': 'a8ac8297-9f6f-4e35-bb42-8405ea908e92', + 'status': 'rendered', + 'params': [ + { + 'placementId': 13144370 + } + ] +} + +export const BID_WON_EVENT_UNDEFINED = { + 'bidderCode': undefined, + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '4e02072b881823', + 'requestId': '3fae2718fd70f', + 'transactionId': '6daa1dac-9eea-47ce-82ce-ce9681df1ec5', + 'adUnitId': undefined, + 'auctionId': '87b4a93d-19ae-432a-96f0-8c2d4cc1c539', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': undefined, + 'creativeId': 98493734, + 'currency': undefined, + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': undefined, + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885, + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'bsid': '9325' + } + ] + }, + 'brandId': 555545 + }, + 'ad': "
", + 'metrics': { + 'userId.init.consent': [ + 0.09999999403953552 + ], + 'userId.mod.init': [ + 1.7999999821186066 + ], + 'userId.mods.liveIntentId.init': [ + 1.7999999821186066 + ], + 'userId.init.modules': [ + 1.9000000059604645 + ], + 'userId.callbacks.pending': [ + 0 + ], + 'userId.mod.callback': [ + 149.69999998807907 + ], + 'userId.mods.liveIntentId.callback': [ + 149.69999998807907 + ], + 'userId.callbacks.total': [ + 149.80000001192093 + ], + 'userId.total': [ + 152.90000000596046 + ], + 'requestBids.userId': 147.2999999821186, + 'requestBids.validate': 0.30000001192092896, + 'requestBids.makeRequests': 0.699999988079071, + 'requestBids.total': 215.7999999821186, + 'requestBids.callBids': 65, + 'adapter.client.validate': 0, + 'adapters.client.appnexus.validate': 0, + 'adapter.client.buildRequests': 1.199999988079071, + 'adapters.client.appnexus.buildRequests': 1.199999988079071, + 'adapter.client.total': 62.69999998807907, + 'adapters.client.appnexus.total': 62.69999998807907, + 'adapter.client.net': 59.5, + 'adapters.client.appnexus.net': 59.5, + 'adapter.client.interpretResponse': 0.5, + 'adapters.client.appnexus.interpretResponse': 0.5, + 'addBidResponse.validate': 0.09999999403953552, + 'addBidResponse.total': 0.9000000059604645, + 'render.deferred': null, + 'render.pending': 130.60000002384186, + 'render.e2e': 346.40000000596046, + 'adserver.pending': 130.80000001192093, + 'adserver.e2e': 346.59999999403954 + }, + 'adapterCode': 'appnexus', + 'originalCpm': 1.5, + 'originalCurrency': 'USD', + 'deferBilling': false, + 'deferRendering': false, + 'responseTimestamp': undefined, + 'requestTimestamp': undefined, + 'bidder': undefined, + 'timeToRespond': 62, + 'pbLg': '1.50', + 'pbMg': '1.50', + 'pbHg': '1.50', + 'pbAg': '1.50', + 'pbDg': '1.50', + 'pbCg': '', + 'size': '728x90', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '4e02072b881823', + 'hb_pb': '1.50', + 'hb_size': '728x90', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '', + 'hb_crid': 98493734 + }, + 'latestTargetedAuctionId': 'a8ac8297-9f6f-4e35-bb42-8405ea908e92', + 'status': 'rendered', + 'params': [ + { + 'placementId': 13144370 + } + ] +} diff --git a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js index 73a8d41be72..9f30c6e60f0 100644 --- a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js +++ b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js @@ -2,8 +2,9 @@ import liAnalytics from 'modules/liveIntentAnalyticsAdapter'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; import { auctionManager } from 'src/auctionManager.js'; -import {expectEvents} from '../../helpers/analytics.js'; import { EVENTS } from 'src/constants.js'; +import { config } from 'src/config.js'; +import { BID_WON_EVENT, AUCTION_INIT_EVENT, BID_WON_EVENT_UNDEFINED, AUCTION_INIT_EVENT_NOT_LI } from '../../fixtures/liveIntentAuctionEvents'; let utils = require('src/utils'); let refererDetection = require('src/refererDetection'); @@ -14,13 +15,24 @@ let clock; let now = new Date(); let events = require('src/events'); -let auctionId = '99abbc81-c1f1-41cd-8f25-f7149244c897' + +const USERID_CONFIG = [ + { + 'name': 'liveIntentId', + 'params': { + 'liCollectConfig': { + 'appId': 'a123' + } + } + } +]; const configWithSamplingAll = { provider: 'liveintent', options: { bidWonTimeout: 2000, - sampling: 1 + sampling: 1, + sendAuctionInitEvents: true } } @@ -28,291 +40,123 @@ const configWithSamplingNone = { provider: 'liveintent', options: { bidWonTimeout: 2000, - sampling: 0 + sampling: 0, + sendAuctionInitEvents: true } } -let args = { - auctionId: auctionId, - timestamp: 1660915379703, - auctionEnd: 1660915381635, - adUnits: [ - { - code: 'ID_Bot100AdJ1', - mediaTypes: { - banner: { - sizes: [ - [ - 300, - 250 - ], - [ - 320, - 50 - ] - ] - } - }, - ortb2Imp: { - gpid: '/777/test/home/ID_Bot100AdJ1', - ext: { - data: { - aupName: '/777/test/home/ID_Bot100AdJ1', - adserver: { - name: 'gam', - adslot: '/777/test/home/ID_Bot100AdJ1' - }, - pbadslot: '/777/test/home/ID_Bot100AdJ1' - }, - gpid: '/777/test/home/ID_Bot100AdJ1' - } - }, - bids: [ - { - bidder: 'testBidder', - params: { - siteId: 321218, - zoneId: 1732558, - position: 'bug', - accountId: 10777 - }, - userIdAsEids: [ - { - source: 'source1.com', - uids: [ - { - id: 'ID5*yO-L9xRugTx4mkIJ9z99eYva6CZQhz8B70QOkLLSEEQWowsxvVQqMaZOt4qpBTYAFqR3y6ZtZ8qLJJBAsRqnRRalTfy8iZszQavAAkZcAjkWpxp6DnOSkF3R5LafC10OFqhwcxH699dDc_fI6RVEGBasN6zrJwgqCGelgfQLtQwWrikWRyi0l3ICFj9JUiVGFrCF8SAFaqJD9A0_I07a8xa0-jADtEj1T8w30oX--sMWvTK_I5_3zA5f3z0OMoxbFsCMFdhfGRDuw5GrpI475g', - atype: 1, - ext: { - linkType: 2 - } - } - ] - }, - { - source: 'source2.com', - uids: [ - { - id: 'ID5*yO-L9xRugTx4mkIJ9z99eYva6CZQhz8B70QOkLLSEEQWowsxvVQqMaZOt4qpBTYAFqR3y6ZtZ8qLJJBAsRqnRRalTfy8iZszQavAAkZcAjkWpxp6DnOSkF3R5LafC10OFqhwcxH699dDc_fI6RVEGBasN6zrJwgqCGelgfQLtQwWrikWRyi0l3ICFj9JUiVGFrCF8SAFaqJD9A0_I07a8xa0-jADtEj1T8w30oX--sMWvTK_I5_3zA5f3z0OMoxbFsCMFdhfGRDuw5GrpI475g', - atype: 1, - ext: { - linkType: 2 - } - } - ] - } - ] - }, - { - bidder: 'testBidder2', - params: { - adSlot: '2926251', - publisherId: '159423' - }, - userIdAsEids: [ - { - source: 'source1.com', - uids: [ - { - id: 'ID5*yO-L9xRugTx4mkIJ9z99eYva6CZQhz8B70QOkLLSEEQWowsxvVQqMaZOt4qpBTYAFqR3y6ZtZ8qLJJBAsRqnRRalTfy8iZszQavAAkZcAjkWpxp6DnOSkF3R5LafC10OFqhwcxH699dDc_fI6RVEGBasN6zrJwgqCGelgfQLtQwWrikWRyi0l3ICFj9JUiVGFrCF8SAFaqJD9A0_I07a8xa0-jADtEj1T8w30oX--sMWvTK_I5_3zA5f3z0OMoxbFsCMFdhfGRDuw5GrpI475g', - atype: 1, - ext: { - linkType: 2 - } - } - ] - } - ] - } - ] - } - ], - bidderRequests: [ - { - bidderCode: 'tripl_ss1', - auctionId: '8e5a5eda-a7dc-49a3-bc7f-654fc', - bidderRequestId: '953fe1ee8a1645', - uniquePbsTid: '0da1f980-8351-415d-860d-ebbdb4274179', - auctionStart: 1660915379703 - }, - { - bidderCode: 'tripl_ss2', - auctionId: '8e5a5eda-a7dc-49a3-bc7f-6ca682ae893c', - bidderRequestId: '953fe1ee8a164e', - uniquePbsTid: '0da1f980-8351-415d-860d-ebbdb4274180', - auctionStart: 1660915379703 - } - ], - bidsReceived: [ - { - adUnitCode: 'ID_Bot100AdJ1', - timeToRespond: 824, - cpm: 0.447, - currency: 'USD', - ttl: 300, - bidder: 'testBidder' - } - ], - winningBids: [] -} - -let winningBids = [ - { - adUnitCode: 'ID_Bot100AdJ1', - timeToRespond: 824, - cpm: 0.447, - currency: 'USD', - ttl: 300, - bidder: 'testBidder' +const configWithNoAuctionInit = { + provider: 'liveintent', + options: { + bidWonTimeout: 2000, + sampling: 1, + sendAuctionInitEvents: false } -]; - -let expectedEvent = { - instanceId: instanceId, - url: url, - bidsReceived: [ - { - adUnitCode: 'ID_Bot100AdJ1', - timeToRespond: 824, - cpm: 0.447, - currency: 'USD', - ttl: 300, - bidder: 'testBidder' - } - ], - auctionStart: 1660915379703, - auctionEnd: 1660915381635, - adUnits: [ - { - code: 'ID_Bot100AdJ1', - mediaType: 'banner', - sizes: [ - { - w: 300, - h: 250 - }, - { - w: 320, - h: 50 - } - ], - ortb2Imp: { - gpid: '/777/test/home/ID_Bot100AdJ1', - ext: { - data: { - aupName: '/777/test/home/ID_Bot100AdJ1', - adserver: { - name: 'gam', - adslot: '/777/test/home/ID_Bot100AdJ1' - }, - pbadslot: '/777/test/home/ID_Bot100AdJ1' - }, - gpid: '/777/test/home/ID_Bot100AdJ1' - } - } - } - ], - winningBids: [ - { - adUnitCode: 'ID_Bot100AdJ1', - timeToRespond: 824, - cpm: 0.447, - currency: 'USD', - ttl: 300, - bidder: 'testBidder' - } - ], - auctionId: auctionId, - userIds: [ - { - source: 'source1.com', - uids: [ - { - id: 'ID5*yO-L9xRugTx4mkIJ9z99eYva6CZQhz8B70QOkLLSEEQWowsxvVQqMaZOt4qpBTYAFqR3y6ZtZ8qLJJBAsRqnRRalTfy8iZszQavAAkZcAjkWpxp6DnOSkF3R5LafC10OFqhwcxH699dDc_fI6RVEGBasN6zrJwgqCGelgfQLtQwWrikWRyi0l3ICFj9JUiVGFrCF8SAFaqJD9A0_I07a8xa0-jADtEj1T8w30oX--sMWvTK_I5_3zA5f3z0OMoxbFsCMFdhfGRDuw5GrpI475g', - atype: 1, - ext: { - linkType: 2 - } - } - ] - }, - { - source: 'source2.com', - uids: [ - { - id: 'ID5*yO-L9xRugTx4mkIJ9z99eYva6CZQhz8B70QOkLLSEEQWowsxvVQqMaZOt4qpBTYAFqR3y6ZtZ8qLJJBAsRqnRRalTfy8iZszQavAAkZcAjkWpxp6DnOSkF3R5LafC10OFqhwcxH699dDc_fI6RVEGBasN6zrJwgqCGelgfQLtQwWrikWRyi0l3ICFj9JUiVGFrCF8SAFaqJD9A0_I07a8xa0-jADtEj1T8w30oX--sMWvTK_I5_3zA5f3z0OMoxbFsCMFdhfGRDuw5GrpI475g', - atype: 1, - ext: { - linkType: 2 - } - } - ] - } - ], - bidders: [ - { - bidder: 'testBidder', - params: { - siteId: 321218, - zoneId: 1732558, - position: 'bug', - accountId: 10777 - } - }, - { - bidder: 'testBidder2', - params: { - adSlot: '2926251', - publisherId: '159423' - } - } - ] -}; +} describe('LiveIntent Analytics Adapter ', () => { beforeEach(function () { sandbox = sinon.sandbox.create(); sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG); + sandbox.stub(utils, 'generateUUID').returns(instanceId); + sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); + sandbox.stub(auctionManager.index, 'getAuction').withArgs({auctionId: AUCTION_INIT_EVENT.auctionId}).returns({ + getBidRequests: () => AUCTION_INIT_EVENT.bidderRequests, + getAuctionStart: () => AUCTION_INIT_EVENT.timestamp + }); clock = sandbox.useFakeTimers(now.getTime()); }); afterEach(function () { liAnalytics.disableAnalytics(); sandbox.restore(); clock.restore(); + window.liTreatmentRate = undefined + window.liModuleEnabled = undefined }); it('request is computed and sent correctly when sampling is 1', () => { liAnalytics.enableAnalytics(configWithSamplingAll); - sandbox.stub(utils, 'generateUUID').returns(instanceId); - sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); - sandbox.stub(auctionManager.index, 'getAuction').withArgs({auctionId}).returns({ getWinningBids: () => winningBids }); - events.emit(EVENTS.AUCTION_END, args); - clock.tick(2000); + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=y&aun=2') - let requestBody = JSON.parse(server.requests[0].requestBody); - expect(requestBody).to.deep.equal(expectedEvent); + events.emit(EVENTS.BID_WON, BID_WON_EVENT); + expect(server.requests.length).to.equal(2); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&liip=y'); }); - it('track is called', () => { - sandbox.stub(liAnalytics, 'track'); + it('request is computed and sent correctly when sampling is 1 and liModule is enabled', () => { + window.liModuleEnabled = true liAnalytics.enableAnalytics(configWithSamplingAll); - expectEvents().to.beTrackedBy(liAnalytics.track); - }) - it('no request is computed when sampling is 0', () => { - liAnalytics.enableAnalytics(configWithSamplingNone); - sandbox.stub(utils, 'generateUUID').returns(instanceId); - sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); - sandbox.stub(auctionManager.index, 'getAuction').withArgs({auctionId}).returns({ getWinningBids: () => winningBids }); - events.emit(EVENTS.AUCTION_END, args); - clock.tick(2000); + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&me=y&liip=y&aun=2') + + events.emit(EVENTS.BID_WON, BID_WON_EVENT); + expect(server.requests.length).to.equal(2); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&me=y&liip=y'); + }); + + it('request is computed and sent correctly when sampling is 1 and liModule is disabled', () => { + window.liModuleEnabled = false + liAnalytics.enableAnalytics(configWithSamplingAll); + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&me=n&liip=y&aun=2') + + events.emit(EVENTS.BID_WON, BID_WON_EVENT); + expect(server.requests.length).to.equal(2); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&me=n&liip=y'); + }); + + it('request is computed and sent correctly when sampling is 1 and should forward the correct liTreatmentRate', () => { + window.liTreatmentRate = 0.95 + liAnalytics.enableAnalytics(configWithSamplingAll); + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&tr=0.95&liip=y&aun=2') + + events.emit(EVENTS.BID_WON, BID_WON_EVENT); + expect(server.requests.length).to.equal(2); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&auc=test-div2&auid=afc6bc6a-3082-4940-b37f-d22e1b026e48&cpm=1.5&c=USD&b=appnexus&bc=appnexus&pid=a123&iid=pbjs&sts=1739971147744&rts=1739971147806&tr=0.95&liip=y'); + }); + + it('not send any events on auction init if disabled in settings', () => { + liAnalytics.enableAnalytics(configWithNoAuctionInit); + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); expect(server.requests.length).to.equal(0); }); - it('track is not called', () => { - sandbox.stub(liAnalytics, 'track'); + it('not send fields that are undefined', () => { + liAnalytics.enableAnalytics(configWithSamplingAll); + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); + events.emit(EVENTS.BID_WON, BID_WON_EVENT_UNDEFINED); + + expect(server.requests.length).to.equal(2); + expect(server.requests[1].url).to.equal('https://wba.liadm.com/analytic-events/bid-won?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=y'); + }); + + it('liip should be n if there is no source or provider in userIdAsEids have the value liveintent.com', () => { + liAnalytics.enableAnalytics(configWithSamplingAll); + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT_NOT_LI); + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://wba.liadm.com/analytic-events/auction-init?id=77abbc81-c1f1-41cd-8f25-f7149244c800&aid=87b4a93d-19ae-432a-96f0-8c2d4cc1c539&u=https%3A%2F%2Fwww.test.com&ats=1739969798557&pid=a123&iid=pbjs&liip=n&aun=2'); + }); + + it('no request is computed when sampling is 0', () => { liAnalytics.enableAnalytics(configWithSamplingNone); - sinon.assert.callCount(liAnalytics.track, 0); - }) + + events.emit(EVENTS.AUCTION_INIT, AUCTION_INIT_EVENT); + events.emit(EVENTS.BID_WON, BID_WON_EVENT); + + expect(server.requests.length).to.equal(0); + }); }); diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js index e63eab08ea6..86e39e76f4e 100644 --- a/test/spec/modules/liveIntentExternalIdSystem_spec.js +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -1,4 +1,5 @@ import { liveIntentExternalIdSubmodule, resetSubmodule } from 'libraries/liveIntentId/externalIdSystem.js'; +import { DEFAULT_TREATMENT_RATE } from 'libraries/liveIntentId/shared.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; import * as refererDetection from '../../../src/refererDetection.js'; const DEFAULT_AJAX_TIMEOUT = 5000 @@ -11,6 +12,7 @@ describe('LiveIntentExternalId', function() { let gppConsentDataStub; let coppaConsentDataStub; let refererInfoStub; + let randomStub; beforeEach(function() { uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); @@ -18,6 +20,7 @@ describe('LiveIntentExternalId', function() { gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); coppaConsentDataStub = sinon.stub(coppaDataHandler, 'getCoppa'); refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + randomStub = sinon.stub(Math, 'random').returns(0.6); }); afterEach(function() { @@ -26,7 +29,10 @@ describe('LiveIntentExternalId', function() { gppConsentDataStub.restore(); coppaConsentDataStub.restore(); refererInfoStub.restore(); + randomStub.restore(); window.liQHub = []; // reset + window.liModuleEnabled = undefined; // reset + window.liTreatmentRate = undefined; // reset resetSubmodule(); }); @@ -312,7 +318,7 @@ describe('LiveIntentExternalId', function() { }); it('should decode values with the segments but no nonId', function() { - const result = liveIntentExternalIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + const result = liveIntentExternalIdSubmodule.decode({segments: ['tak']}, defaultConfigParams); expect(result).to.eql({'lipb': {'segments': ['tak']}}); }); @@ -472,7 +478,185 @@ describe('LiveIntentExternalId', function() { }); it('should decode the segments as part of lipb', function() { - const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); }); + + it('getId does not set the global variables when liModuleEnabled, liTreatmentRate and activatePartialTreatment are undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.be.undefined + }); + + it('getId does not set the global variables when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId does not set the global variables when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is false', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: false } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId does not change the global variables when liModuleEnabled is true, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(1.0) // 1.0 < 0.7 = false, but should be ignored by the module + window.liModuleEnabled = true; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId does not change the global variables when liModuleEnabled is false, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(0.5) // 0.5 < 0.7 = true, but should be ignored by the module + window.liModuleEnabled = false; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId sets the global variables correctly when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is true, and experiment returns false', function() { + randomStub.returns(1) // 1 < 0.7 = false + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId sets the global variables correctly when liModuleEnabled is undefined, liTreatmentRate is undefined and activatePartialTreatment is true, and experiment returns true', function() { + randomStub.returns(0.0) // 0.0 < DEFAULT_TREATMENT_RATE (0.97) = true + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentExternalIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(DEFAULT_TREATMENT_RATE) + }); + + it('should decode IDs when liModuleEnabled, liTreatmentRate and activatePartialTreatment are undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.be.undefined + }); + + it('should decode IDs when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should decode IDs when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is false', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: false } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should decode IDs when liModuleEnabled is true, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(1.0) // 1 < 0.7 = false, but should be ignored by the module as liModuleEnabled is already defined + window.liModuleEnabled = true; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is false, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(0.5) // 0.5 < 0.7 = true, but should be ignored by the module as liModuleEnabled is already defined + window.liModuleEnabled = false; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is false, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + window.liModuleEnabled = false; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is true, and experiment returns false', function() { + randomStub.returns(1.0) // 1.0 < 0.7 = false + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is undefined, liTreatmentRate is undefined and activatePartialTreatment is true, and experiment returns false', function() { + randomStub.returns(1.0) // 1.0 < DEFAULT_TREATMENT_RATE (0.97) = false + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(DEFAULT_TREATMENT_RATE) + }); + + it('should decode IDs when liModuleEnabled is undefined, liTreatmentRate is undefined and activatePartialTreatment is true, and experiment returns true', function() { + randomStub.returns(0.0) // 0.0 < DEFAULT_TREATMENT_RATE (0.97) = true + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(DEFAULT_TREATMENT_RATE) + }); }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index b028e3e4890..07b5121b3ae 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -1,5 +1,6 @@ import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'libraries/liveIntentId/idSystem.js'; import * as utils from 'src/utils.js'; +import { DEFAULT_TREATMENT_RATE } from 'libraries/liveIntentId/shared.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; import * as refererDetection from '../../../src/refererDetection.js'; @@ -9,7 +10,7 @@ import {createEidsArray} from '../../../modules/userId/eids.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; -const defaultConfigParams = {publisherId: PUBLISHER_ID, fireEventDelay: 1}; +const defaultConfigParams = { params: { publisherId: PUBLISHER_ID, fireEventDelay: 1 } }; const responseHeader = {'Content-Type': 'application/json'} function requests(...urlRegExps) { @@ -34,6 +35,7 @@ describe('LiveIntentId', function() { let imgStub; let coppaConsentDataStub; let refererInfoStub; + let randomStub; beforeEach(function() { liveIntentIdSubmodule.setModuleMode('standard'); @@ -46,6 +48,7 @@ describe('LiveIntentId', function() { gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); coppaConsentDataStub = sinon.stub(coppaDataHandler, 'getCoppa'); refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + randomStub = sinon.stub(Math, 'random').returns(0.6); }); afterEach(function() { @@ -58,6 +61,9 @@ describe('LiveIntentId', function() { gppConsentDataStub.restore(); coppaConsentDataStub.restore(); refererInfoStub.restore(); + randomStub.restore(); + window.liModuleEnabled = undefined; // reset + window.liTreatmentRate = undefined; // reset resetLiveIntentIdSubmodule(); }); @@ -72,7 +78,7 @@ describe('LiveIntentId', function() { applicableSections: [1, 2] }) let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); setTimeout(() => { let requests = idxRequests().concat(rpRequests()); @@ -92,7 +98,7 @@ describe('LiveIntentId', function() { gppString: 'gppConsentDataString', applicableSections: [1] }) - liveIntentIdSubmodule.getId({ params: defaultConfigParams }); + liveIntentIdSubmodule.getId(defaultConfigParams); setTimeout(() => { let request = rpRequests()[0]; expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=0.*&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString.*&gpp_as=1.*/); @@ -102,7 +108,7 @@ describe('LiveIntentId', function() { it('should fire an event when getId and a hash is provided', function(done) { liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams, + ...defaultConfigParams.params, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { @@ -113,7 +119,7 @@ describe('LiveIntentId', function() { }); it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) { - liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { let request = rpRequests()[0]; expect(request.url).to.contain('tv=$prebid.version$') @@ -123,7 +129,7 @@ describe('LiveIntentId', function() { it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ...{ url: 'https://dummy.liveintent.com', liCollectConfig: { @@ -168,7 +174,7 @@ describe('LiveIntentId', function() { gppString: 'gppConsentDataString', applicableSections: [1] }) - liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { let request = rpRequests()[0]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); @@ -178,7 +184,7 @@ describe('LiveIntentId', function() { it('should fire an event when decode and a hash is provided', function(done) { liveIntentIdSubmodule.decode({}, { params: { - ...defaultConfigParams, + ...defaultConfigParams.params, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { @@ -189,7 +195,7 @@ describe('LiveIntentId', function() { }); it('should fire an event when decode', function(done) { - liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { expect(rpRequests().length).to.be.eq(1); done(); @@ -197,10 +203,10 @@ describe('LiveIntentId', function() { }); it('should initialize LiveConnect and send data only once', function(done) { - liveIntentIdSubmodule.getId({ params: defaultConfigParams }); - liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); - liveIntentIdSubmodule.getId({ params: defaultConfigParams }); - liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); + liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { expect(rpRequests().length).to.be.eq(1); done(); @@ -210,7 +216,7 @@ describe('LiveIntentId', function() { it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; expect(request.url).to.match(/https:\/\/dummy.liveintent.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); @@ -253,7 +259,7 @@ describe('LiveIntentId', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ...{ 'url': 'https://dummy.liveintent.com/idex', 'partner': 'rubicon' @@ -273,7 +279,7 @@ describe('LiveIntentId', function() { it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); @@ -288,7 +294,7 @@ describe('LiveIntentId', function() { it('should log an error and continue to callback if ajax request errors', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); @@ -305,7 +311,7 @@ describe('LiveIntentId', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' getCookieStub.withArgs('_lc2_fpi').returns(oldCookie) let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; const expected = new RegExp('https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*duid=' + oldCookie + '.*&cd=.localhost.*&resolve=nonId.*'); @@ -323,7 +329,7 @@ describe('LiveIntentId', function() { getCookieStub.withArgs('_lc2_fpi').returns(oldCookie); getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); const configParams = { params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ...{ 'identifiersToResolve': ['_thirdPC'] } @@ -346,7 +352,7 @@ describe('LiveIntentId', function() { getCookieStub.returns(null); getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'}); const configParams = { params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ...{ 'identifiersToResolve': ['_thirdPC'] } @@ -366,7 +372,7 @@ describe('LiveIntentId', function() { it('should include ip4,ip6,userAgent if it\'s present', function(done) { liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ipv4: 'foov4', ipv6: 'foov6', userAgent: 'boo' @@ -381,24 +387,24 @@ describe('LiveIntentId', function() { it('should send an error when the cookie jar throws an unexpected error', function() { getCookieStub.throws('CookieError', 'A message'); - liveIntentIdSubmodule.getId({ params: defaultConfigParams }); + liveIntentIdSubmodule.getId(defaultConfigParams); expect(imgStub.getCall(0).args[0]).to.match(/.*ae=.+/); }); it('should decode a unifiedId to lipbId and remove it', function() { - const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); }); it('should decode a nonId to lipbId', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); }); it('should resolve extra attributes', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); @@ -413,71 +419,71 @@ describe('LiveIntentId', function() { }); it('should decode a uid2 to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with the segments but no nonId', function() { - const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, defaultConfigParams); expect(result).to.eql({'lipb': {'segments': ['tak']}}); }); it('should decode values with uid2 but no nonId', function() { - const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a medianet id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a sovrn id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a magnite id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an index id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an openx id to a separate object when present', function () { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an pubmatic id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a thetradedesk id to a separate object when present', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); it('should decode the segments as part of lipb', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); }); it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams, + ...defaultConfigParams.params, ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); @@ -493,13 +499,13 @@ describe('LiveIntentId', function() { it('should decode a idCookie as fpid if it exists and coppa is false', function() { coppaConsentDataStub.returns(false) - const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, { params: defaultConfigParams }) + const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, defaultConfigParams) expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'fpid': 'bar'}, 'fpid': {'id': 'bar'}}) }); it('should not decode a idCookie as fpid if it exists and coppa is true', function() { coppaConsentDataStub.returns(true) - const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, { params: defaultConfigParams }) + const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, defaultConfigParams) expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo'}}) }); @@ -508,7 +514,7 @@ describe('LiveIntentId', function() { const cookieName = 'testcookie' getCookieStub.withArgs(cookieName).returns(expectedValue) const config = { params: { - ...defaultConfigParams, + ...defaultConfigParams.params, fpid: { 'strategy': 'cookie', 'name': cookieName }, requestedAttributesOverrides: { 'fpid': true } } } @@ -532,12 +538,12 @@ describe('LiveIntentId', function() { }); it('should decode a sharethrough id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a sonobi id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); @@ -552,10 +558,188 @@ describe('LiveIntentId', function() { }); it('should decode a vidazoo id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('getId does not set the global variables when liModuleEnabled, liTreatmentRate and activatePartialTreatment are undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.be.undefined + }); + + it('getId does not set the global variables when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId does not set the global variables when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is false', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: false } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId does not change the global variables when liModuleEnabled is true, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(1.0) // 1.0 < 0.7 = false, but should be ignored by the module + window.liModuleEnabled = true; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId does not change the global variables when liModuleEnabled is false, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(0.5) // 0.5 < 0.7 = true, but should be ignored by the module + window.liModuleEnabled = false; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId sets the global variables correctly when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is true, and experiment returns false', function() { + randomStub.returns(1) // 1 < 0.7 = false + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('getId sets the global variables correctly when liModuleEnabled is undefined, liTreatmentRate is undefined and activatePartialTreatment is true, and experiment returns true', function() { + randomStub.returns(0.0) // 0.0 < DEFAULT_TREATMENT_RATE (0.97) = true + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + liveIntentIdSubmodule.getId(configWithPartialTreatment).callback(() => {}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(DEFAULT_TREATMENT_RATE) + }); + + it('should decode IDs when liModuleEnabled, liTreatmentRate and activatePartialTreatment are undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.be.undefined + }); + + it('should decode IDs when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is undefined', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: undefined } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should decode IDs when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is false', function() { + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: false } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.be.undefined + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should decode IDs when liModuleEnabled is true, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(1.0) // 1 < 0.7 = false, but should be ignored by the module as liModuleEnabled is already defined + window.liModuleEnabled = true; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is false, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + randomStub.returns(0.5) // 0.5 < 0.7 = true, but should be ignored by the module as liModuleEnabled is already defined + window.liModuleEnabled = false; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is false, liTreatmentRate is 0.7 and activatePartialTreatment is true', function() { + window.liModuleEnabled = false; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is undefined, liTreatmentRate is 0.7 and activatePartialTreatment is true, and experiment returns false', function() { + randomStub.returns(1.0) // 1.0 < 0.7 = false + window.liModuleEnabled = undefined; + window.liTreatmentRate = 0.7; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(0.7) + }); + + it('should not decode IDs when liModuleEnabled is undefined, liTreatmentRate is undefined and activatePartialTreatment is true, and experiment returns false', function() { + randomStub.returns(1.0) // 1.0 < DEFAULT_TREATMENT_RATE (0.97) = false + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({}); + expect(window.liModuleEnabled).to.eq(false) + expect(window.liTreatmentRate).to.eq(DEFAULT_TREATMENT_RATE) + }); + + it('should decode IDs when liModuleEnabled is undefined, liTreatmentRate is undefined and activatePartialTreatment is true, and experiment returns true', function() { + randomStub.returns(0.0) // 0.0 < DEFAULT_TREATMENT_RATE (0.97) = true + window.liModuleEnabled = undefined; + window.liTreatmentRate = undefined; + const configWithPartialTreatment = { params: { ...defaultConfigParams.params, activatePartialTreatment: true } }; + + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar', segments: ['tak'] }, configWithPartialTreatment); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar', 'segments': ['tak']}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(window.liModuleEnabled).to.eq(true) + expect(window.liTreatmentRate).to.eq(DEFAULT_TREATMENT_RATE) + }); + describe('eid', () => { before(() => { attachIdSystem(liveIntentIdSubmodule); From 9e4a5b7e984d50965bc6791abbbea401097ee0f1 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 27 Mar 2025 10:51:53 -0400 Subject: [PATCH 1043/1097] Update adloader.js (#12929) --- src/adloader.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/adloader.js b/src/adloader.js index adb79284012..0d59164298a 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -10,8 +10,6 @@ const _approvedLoadExternalJSList = [ // Prebid maintained modules: 'debugging', 'outstream', - // Bid Modules - only exception is on rendering edge cases, to clean up in Prebid 10: - 'showheroes-bs', // RTD modules: 'aaxBlockmeter', 'adagio', From 07c83d6141859ee47da69203b96a5417edd3b180 Mon Sep 17 00:00:00 2001 From: Sir-Will Date: Thu, 27 Mar 2025 17:37:33 +0100 Subject: [PATCH 1044/1097] PBS Bid Adapter : add BEFORE_PBS_HTTP event (#12889) * Add pbs before http event * Fix tests --------- Co-authored-by: Patrick McCann --- modules/prebidServerBidAdapter/index.js | 19 ++++++++------ src/constants.js | 1 + .../modules/prebidServerBidAdapter_spec.js | 26 ++++++++++++++----- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index faf280d68b4..5a9c5594ab1 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -503,14 +503,17 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques .filter(uniques); const request = s2sBidRequest.metrics.measureTime('buildRequests', () => buildPBSRequest(s2sBidRequest, bidRequests, adUnits, requestedBidders)); - const requestJson = request && JSON.stringify(request); - logInfo('BidRequest: ' + requestJson); - const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); - const customHeaders = s2sBidRequest?.s2sConfig?.customHeaders ?? {}; - if (request && requestJson && endpointUrl) { + const requestData = { + endpointUrl: getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent), + requestJson: request && JSON.stringify(request), + customHeaders: s2sBidRequest?.s2sConfig?.customHeaders ?? {}, + }; + events.emit(EVENTS.BEFORE_PBS_HTTP, requestData) + logInfo('BidRequest: ' + requestData); + if (request && requestData.requestJson && requestData.endpointUrl) { const networkDone = s2sBidRequest.metrics.startTiming('net'); ajax( - endpointUrl, + requestData.endpointUrl, { success: function (response) { networkDone(); @@ -537,12 +540,12 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques onError.apply(this, arguments); } }, - requestJson, + requestData.requestJson, { contentType: 'text/plain', withCredentials: true, browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)), - customHeaders + customHeaders: requestData.customHeaders } ); } else { diff --git a/src/constants.js b/src/constants.js index c27b69ee2c8..07a806e125f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -48,6 +48,7 @@ export const EVENTS = { PAAPI_BID: 'paapiBid', PAAPI_NO_BID: 'paapiNoBid', PAAPI_ERROR: 'paapiError', + BEFORE_PBS_HTTP: 'beforePBSHttp', BROWSI_INIT: 'browsiInit', BROWSI_DATA: 'browsiData', }; diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index ee6c56fe9a6..33cc676368f 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3270,8 +3270,8 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); - sinon.assert.calledOnce(events.emit); - const event = events.emit.firstCall.args; + sinon.assert.calledTwice(events.emit); + const event = events.emit.secondCall.args; expect(event[0]).to.equal(EVENTS.BIDDER_DONE); expect(event[1].bids[0]).to.have.property('serverResponseTimeMs', 8); @@ -3302,7 +3302,7 @@ describe('S2S Adapter', function () { const responding = deepClone(nonbidResponse); Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) server.requests[0].respond(200, {}, JSON.stringify(responding)); - const event = events.emit.secondCall.args; + const event = events.emit.thirdCall.args; expect(event[0]).to.equal(EVENTS.SEAT_NON_BID); expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); expect(event[1].requestedBidders).to.deep.equal(['appnexus']); @@ -3319,7 +3319,7 @@ describe('S2S Adapter', function () { const responding = deepClone(nonbidResponse); Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) server.requests[0].respond(200, {}, JSON.stringify(responding)); - const event = events.emit.thirdCall.args; + const event = events.emit.getCall(3).args; expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); expect(event[1].requestedBidders).to.deep.equal(['appnexus']); @@ -3336,12 +3336,26 @@ describe('S2S Adapter', function () { const responding = deepClone(atagResponse); Object.assign(responding.ext.prebid.analytics.tags, ['stuff']) server.requests[0].respond(200, {}, JSON.stringify(responding)); - const event = events.emit.secondCall.args; + const event = events.emit.thirdCall.args; expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); expect(event[1].atag[0]).to.deep.equal('stuff'); expect(event[1].response).to.deep.equal(responding); }); + it('emits the BEFORE_PBS_HTTP event and captures responses', function () { + config.setConfig({ CONFIG }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); + + sinon.assert.calledTwice(events.emit); + const event = events.emit.firstCall.args; + expect(event[0]).to.equal(EVENTS.BEFORE_PBS_HTTP); + expect(event[1]).to.have.property('requestJson', server.requests[0].requestBody); + expect(event[1]).to.have.property('endpointUrl', CONFIG.endpoint.p1Consent); + expect(event[1].customHeaders).to.deep.equal({}); + }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30 @@ -3353,7 +3367,7 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); - sinon.assert.calledOnce(events.emit); + sinon.assert.calledTwice(events.emit); const event = events.emit.firstCall.args; sinon.assert.calledOnce(addBidResponse); const response = addBidResponse.firstCall.args[1]; From be3321841c4bb2c6e9d9279f67ef4ea9d459ecbe Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 27 Mar 2025 18:27:30 +0000 Subject: [PATCH 1045/1097] Prebid 9.37.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 903a91bdd43..100242005b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.37.0-pre", + "version": "9.37.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.37.0-pre", + "version": "9.37.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index abb576573a8..69e5e8ea1d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.37.0-pre", + "version": "9.37.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From c6dc75b76d5ff02a42c0251eb4c994cdba03a8f1 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 27 Mar 2025 18:27:30 +0000 Subject: [PATCH 1046/1097] Increment version to 9.38.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 100242005b6..e4d10e72caf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.37.0", + "version": "9.38.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.37.0", + "version": "9.38.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 69e5e8ea1d8..008af413d2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.37.0", + "version": "9.38.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 7ea5fe8670bbea794c4a81d02b66c353de35c2ee Mon Sep 17 00:00:00 2001 From: Rupesh Lakhani <35333377+AskRupert-DM@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:01:43 +0100 Subject: [PATCH 1047/1097] Ozone Bid Adapter : support vastURL & vastXML (#12936) * Update ozoneBidAdapter.js support for vastXML/vastURL * Update ozoneBidAdapter_spec.js updated spec test --- modules/ozoneBidAdapter.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 9ca47c575c5..ead659a7f3b 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -22,7 +22,7 @@ const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.5'; +const OZONEVERSION = '3.0.0'; export const spec = { gvlid: 524, aliases: [{code: 'venatus', gvlid: 524}], @@ -551,11 +551,11 @@ export const spec = { if (videoContext === 'outstream') { logInfo('setting thisBid.mediaType = VIDEO & attach a renderer to OUTSTREAM video'); thisBid.renderer = newRenderer(thisBid.bidId); + thisBid.vastUrl = `https://${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'missing_host')}${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'missing_path')}?uuid=${deepAccess(thisBid, 'ext.prebid.targeting.hb_uuid', 'missing_uuid')}`; + thisBid.vastXml = thisBid.adm; } else { - logInfo('not an outstream video, will set thisBid.mediaType = VIDEO and thisBid.vastUrl and not attach a renderer'); - thisBid.vastUrl = `https://${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'missing_host')}${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'missing_path')}?id=${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_id', 'missing_id')}`; // need to see if this works ok for ozone - adserverTargeting['hb_cache_host'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'no-host'); - adserverTargeting['hb_cache_path'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'no-path'); + logInfo('not an outstream video (presumably instream), will set thisBid.mediaType = VIDEO and thisBid.vastUrl and not attach a renderer'); + thisBid.vastUrl = `https://${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'missing_host')}${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'missing_path')}?uuid=${deepAccess(thisBid, 'ext.prebid.targeting.hb_uuid', 'missing_uuid')}`; // need to see if this works ok for ozone if (!thisBid.hasOwnProperty('videoCacheKey')) { let videoCacheUuid = deepAccess(thisBid, 'ext.prebid.targeting.hb_uuid', 'no_hb_uuid'); logInfo(`Adding videoCacheKey: ${videoCacheUuid}`); @@ -567,6 +567,7 @@ export const spec = { } else { this.setBidMediaTypeIfNotExist(thisBid, BANNER); } + adserverTargeting = Object.assign(adserverTargeting, deepAccess(thisBid, 'ext.prebid.targeting', {})); if (enhancedAdserverTargeting) { let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid, defaultWidth, defaultHeight); logInfo('Going to iterate allBidsForThisBidId', deepClone(allBidsForThisBidid)); From 0f7c725c5232f88cda63d161080fd61ed371cc04 Mon Sep 17 00:00:00 2001 From: Antoine Niek Date: Mon, 31 Mar 2025 09:36:08 -0400 Subject: [PATCH 1048/1097] New RTD submodule: optableRtdProvider (#12850) * Optable RTD submodule: Initial commit * fix typo: user.ext.data -> user.data * Optable RTD submodule: Restrict insecure bundle URLs * optableRtdProvider doc: add a note to erase optable.ext. custom fields * Optable RTD submodule: Change the method of passing extra data --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> Co-authored-by: Eugene Dorfman --- .../gpt/optableRtdProvider_example.html | 274 ++++++++++++++++++ modules/.submodules.json | 1 + modules/optableRtdProvider.js | 196 +++++++++++++ modules/optableRtdProvider.md | 141 +++++++++ src/adloader.js | 1 + test/spec/modules/optableRtdProvider_spec.js | 238 +++++++++++++++ 6 files changed, 851 insertions(+) create mode 100644 integrationExamples/gpt/optableRtdProvider_example.html create mode 100644 modules/optableRtdProvider.js create mode 100644 modules/optableRtdProvider.md create mode 100644 test/spec/modules/optableRtdProvider_spec.js diff --git a/integrationExamples/gpt/optableRtdProvider_example.html b/integrationExamples/gpt/optableRtdProvider_example.html new file mode 100644 index 00000000000..f539fe90554 --- /dev/null +++ b/integrationExamples/gpt/optableRtdProvider_example.html @@ -0,0 +1,274 @@ + + + + Optable RTD submodule example - Prebid.js + + + + + + + + + + + + +
+
+ optable +
+
+
+

Optable RTD module example

+
+ +

web-sdk-demo-gam360/header-ad

+
+

No response

+ +
+ +

web-sdk-demo-gam360/box-ad

+
+

No response

+ +
+ +

web-sdk-demo-gam360/footer-ad

+
+

No response

+ +
+ + +
+ + diff --git a/modules/.submodules.json b/modules/.submodules.json index d1c05428530..0165c9adc64 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -97,6 +97,7 @@ "mobianRtdProvider", "neuwoRtdProvider", "oneKeyRtdProvider", + "optableRtdProvider", "optimeraRtdProvider", "oxxionRtdProvider", "permutiveRtdProvider", diff --git a/modules/optableRtdProvider.js b/modules/optableRtdProvider.js new file mode 100644 index 00000000000..631084efece --- /dev/null +++ b/modules/optableRtdProvider.js @@ -0,0 +1,196 @@ +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {config} from '../src/config.js'; +import {submodule} from '../src/hook.js'; +import {deepAccess, mergeDeep, prefixLog} from '../src/utils.js'; + +const MODULE_NAME = 'optable'; +export const LOG_PREFIX = `[${MODULE_NAME} RTD]:`; +const optableLog = prefixLog(LOG_PREFIX); +const {logMessage, logWarn, logError} = optableLog; + +/** + * Extracts the parameters for Optable RTD module from the config object passed at instantiation + * @param {Object} moduleConfig Configuration object for the module + */ +export const parseConfig = (moduleConfig) => { + let bundleUrl = deepAccess(moduleConfig, 'params.bundleUrl', null); + let adserverTargeting = deepAccess(moduleConfig, 'params.adserverTargeting', true); + let handleRtd = deepAccess(moduleConfig, 'params.handleRtd', null); + + // If present, trim the bundle URL + if (typeof bundleUrl === 'string') { + bundleUrl = bundleUrl.trim(); + } + + // Verify that bundleUrl is a valid URL: only secure (HTTPS) URLs are allowed + if (typeof bundleUrl === 'string' && bundleUrl.length && !bundleUrl.startsWith('https://')) { + throw new Error( + LOG_PREFIX + ' Invalid URL format for bundleUrl in moduleConfig. Only HTTPS URLs are allowed.' + ); + } + + if (handleRtd && typeof handleRtd !== 'function') { + throw new Error(LOG_PREFIX + ' handleRtd must be a function'); + } + + return {bundleUrl, adserverTargeting, handleRtd}; +} + +/** + * Default function to handle/enrich RTD data + * @param reqBidsConfigObj Bid request configuration object + * @param optableExtraData Additional data to be used by the Optable SDK + * @param mergeFn Function to merge data + * @returns {Promise} + */ +export const defaultHandleRtd = async (reqBidsConfigObj, optableExtraData, mergeFn) => { + const optableBundle = /** @type {Object} */ (window.optable); + // Call Optable DCN for targeting data and return the ORTB2 object + const targetingData = await optableBundle?.instance?.targeting(); + logMessage('Original targeting data from targeting(): ', targetingData); + + if (!targetingData || !targetingData.ortb2) { + logWarn('No targeting data found'); + return; + } + + mergeFn( + reqBidsConfigObj.ortb2Fragments.global, + targetingData.ortb2, + ); + logMessage('Prebid\'s global ORTB2 object after merge: ', reqBidsConfigObj.ortb2Fragments.global); +}; + +/** + * Get data from Optable and merge it into the global ORTB2 object + * @param {Function} handleRtdFn Function to handle RTD data + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Object} optableExtraData Additional data to be used by the Optable SDK + * @param {Function} mergeFn Function to merge data + */ +export const mergeOptableData = async (handleRtdFn, reqBidsConfigObj, optableExtraData, mergeFn) => { + if (handleRtdFn.constructor.name === 'AsyncFunction') { + await handleRtdFn(reqBidsConfigObj, optableExtraData, mergeFn); + } else { + handleRtdFn(reqBidsConfigObj, optableExtraData, mergeFn); + } +}; + +/** + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} callback Called on completion + * @param {Object} moduleConfig Configuration for Optable RTD module + * @param {Object} userConsent + */ +export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => { + try { + // Extract the bundle URL from the module configuration + const {bundleUrl, handleRtd} = parseConfig(moduleConfig); + + const handleRtdFn = handleRtd || defaultHandleRtd; + const optableExtraData = config.getConfig('optableRtdConfig') || {}; + + if (bundleUrl) { + // If bundleUrl is present, load the Optable JS bundle + // by using the loadExternalScript function + logMessage('Custom bundle URL found in config: ', bundleUrl); + + // Load Optable JS bundle and merge the data + loadExternalScript(bundleUrl, MODULE_TYPE_RTD, MODULE_NAME, () => { + logMessage('Successfully loaded Optable JS bundle'); + mergeOptableData(handleRtdFn, reqBidsConfigObj, optableExtraData, mergeDeep).then(callback, callback); + }, document); + } else { + // At this point, we assume that the Optable JS bundle is already + // present on the page. If it is, we can directly merge the data + // by passing the callback to the optable.cmd.push function. + logMessage('Custom bundle URL not found in config. ' + + 'Assuming Optable JS bundle is already present on the page'); + window.optable = window.optable || { cmd: [] }; + window.optable.cmd.push(() => { + logMessage('Optable JS bundle found on the page'); + mergeOptableData(handleRtdFn, reqBidsConfigObj, optableExtraData, mergeDeep).then(callback, callback); + }); + } + } catch (error) { + // If an error occurs, log it and call the callback + // to continue with the auction + logError(error); + callback(); + } +} + +/** + * Get Optable targeting data and merge it into the ad units + * @param adUnits Array of ad units + * @param moduleConfig Module configuration + * @param userConsent User consent + * @param auction Auction object + * @returns {Object} Targeting data + */ +export const getTargetingData = (adUnits, moduleConfig, userConsent, auction) => { + // Extract `adserverTargeting` from the module configuration + const {adserverTargeting} = parseConfig(moduleConfig); + logMessage('Ad Server targeting: ', adserverTargeting); + + if (!adserverTargeting) { + logMessage('Ad server targeting is disabled'); + return {}; + } + + const targetingData = {}; + + // Get the Optable targeting data from the cache + const optableTargetingData = window?.optable?.instance?.targetingKeyValuesFromCache() || {}; + + // If no Optable targeting data is found, return an empty object + if (!Object.keys(optableTargetingData).length) { + logWarn('No Optable targeting data found'); + return targetingData; + } + + // Merge the Optable targeting data into the ad units + adUnits.forEach(adUnit => { + targetingData[adUnit] = targetingData[adUnit] || {}; + mergeDeep(targetingData[adUnit], optableTargetingData); + }); + + // If the key contains no data, remove it + Object.keys(targetingData).forEach((adUnit) => { + Object.keys(targetingData[adUnit]).forEach((key) => { + if (!targetingData[adUnit][key] || !targetingData[adUnit][key].length) { + delete targetingData[adUnit][key]; + } + }); + + // If the ad unit contains no data, remove it + if (!Object.keys(targetingData[adUnit]).length) { + delete targetingData[adUnit]; + } + }); + + logMessage('Optable targeting data: ', targetingData); + return targetingData; +}; + +/** + * Dummy init function + * @param {Object} config Module configuration + * @param {boolean} userConsent User consent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +// Optable RTD submodule +export const optableSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData, + getTargetingData, +} + +// Register the Optable RTD submodule +submodule('realTimeData', optableSubmodule); diff --git a/modules/optableRtdProvider.md b/modules/optableRtdProvider.md new file mode 100644 index 00000000000..1250437f6f0 --- /dev/null +++ b/modules/optableRtdProvider.md @@ -0,0 +1,141 @@ +# Optable RTD Submodule + +## Overview + + Module Name: Optable RTD Provider + Module Type: RTD Provider + Maintainer: prebid@optable.co + +## Description + +Optable RTD submodule enriches the OpenRTB request by populating `user.ext.eids` and `user.data` using an identity graph and audience segmentation service hosted by Optable on behalf of the publisher. This RTD submodule primarily relies on the Optable bundle loaded on the page, which leverages the Optable-specific Visitor ID and other PPIDs to interact with the identity graph, enriching the bid request with additional user IDs and audience data. + +## Usage + +### Integration + +Compile the Optable RTD Module with other modules and adapters into your Prebid.js build: + +```bash +gulp build --modules="rtdModule,optableRtdProvider,appnexusBidAdapter,..." +``` + +> Note that Optable RTD module is dependent on the global real-time data module, `rtdModule`. + +### Preloading Optable SDK bundle + +In order to use the module you first need to register with Optable and obtain a bundle URL. The bundle URL may be specified as a `bundleUrl` parameter to the script, or otherwise it can be added directly to the page source as: + +```html + +``` + +In this case bundleUrl parameter is not needed and the script will await bundle loading before delegating to it. + +### Configuration + +This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to 1000 ms and make sure `waitForIt` is set to `true` for the `Optable` RTD provider. + +```javascript +pbjs.setConfig({ + debug: true, // we recommend turning this on for testing as it adds more logging + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: 'optable', + waitForIt: true, // should be true, otherwise the auctionDelay will be ignored + params: { + bundleUrl: '', + adserverTargeting: '', + }, + }, + ], + }, +}); +``` + +### Additional input to the module + +Optable bundle may use PPIDs (publisher provided IDs) from the `user.ext.eids` as input. + +In addition, other arbitrary keys can be used as input, f.e. the following: + +- `optableRtdConfig.email` - a SHA256-hashed user email +- `optableRtdConfig.phone` - a SHA256-hashed [E.164 normalized phone](https://unifiedid.com/docs/getting-started/gs-normalization-encoding#phone-number-normalization) (meaning a phone number consisting of digits and leading plus sign without spaces or any additional characters, f.e. a US number would be: `+12345678999`) +- `optableRtdConfig.postal_code` - a ZIP postal code string + +Each of these identifiers is completely optional and can be provided through `pbjs.mergeConfig(...)` like so: + +```javascript +pbjs.mergeConfig({ + optableRtdConfig: { + email: await sha256("test@example.com"), + phone: await sha256("12345678999"), + postal_code: "61054" + } +}) +``` + +Where `sha256` function can be defined as: + +```javascript +async function sha256(input) { + return [...new Uint8Array( + await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input)) + )].map(b => b.toString(16).padStart(2, "0")).join(""); +} +``` + +To handle PPIDs and the above input - a custom `handleRtd` function may need to be provided. + +### Parameters + +| Name | Type | Description | Default | Notes | +|--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|----------| +| name | String | Real time data module name | Always `optable` | | +| waitForIt | Boolean | Should be set `true` together with `auctionDelay: 1000` | `false` | | +| params | Object | | | | +| params.bundleUrl | String | Optable bundle URL | `null` | Optional | +| params.adserverTargeting | Boolean | If set to `true`, targeting keywords will be passed to the ad server upon auction completion | `true` | Optional | +| params.handleRtd | Function | An optional function that uses Optable data to enrich `reqBidsConfigObj` with the real-time data. If not provided, the module will do a default call to Optable bundle. The function signature is `[async] (reqBidsConfigObj, optableExtraData, mergeFn) => {}` | `null` | Optional | + +## Publisher Customized RTD Handler Function + +When there is more pre-processing or post-processing needed prior/post calling Optable bundle - a custom `handleRtd` +function can be supplied to do that. +This function will also be responsible for the `reqBidsConfigObj` enrichment. +It will also receive the `optableExtraData` object, which can contain the extra data required for the enrichment and +shouldn't be shared with other RTD providers/bidders. +`mergeFn` parameter taken by `handleRtd` is a standard Prebid.js utility function that take an object to be enriched and +an object to enrich with: the second object's fields will be merged into the first one (also see the code of an example +mentioned below): + +```javascript +mergeFn( + reqBidsConfigObj.ortb2Fragments.global, // or other nested object as needed + rtdData, +); +``` + +A `handleRtd` function implementation has access to its surrounding context including capturing a `pbjs` object, calling `pbjs.getConfig()` and f.e. reading off the `consentManagement` config to make the appropriate decision based on it. + +## Example + +If you want to see an example of how the optable RTD module works, run the following command: + +```bash +gulp serve --modules=optableRtdProvider,consentManagementGpp,consentManagementTcf,appnexusBidAdapter +``` + +and then open the following URL in your browser: + +[`http://localhost:9999/integrationExamples/gpt/optableRtdProvider_example.html`](http://localhost:9999/integrationExamples/gpt/optableRtdProvider_example.html) + +Open the browser console to see the logs. + +## Maintainer contacts + +Any suggestions or questions can be directed to [prebid@optable.co](mailto:prebid@optable.co). + +Alternatively please open a new [issue](https://github.com/prebid/prebid-server-java/issues/new) or [pull request](https://github.com/prebid/prebid-server-java/pulls) in this repository. diff --git a/src/adloader.js b/src/adloader.js index 0d59164298a..2ea59d67440 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -36,6 +36,7 @@ const _approvedLoadExternalJSList = [ 'wurfl', 'nodalsAi', 'anonymised', + 'optable', // UserId Submodules 'justtag', 'tncId', diff --git a/test/spec/modules/optableRtdProvider_spec.js b/test/spec/modules/optableRtdProvider_spec.js new file mode 100644 index 00000000000..c725e1024d3 --- /dev/null +++ b/test/spec/modules/optableRtdProvider_spec.js @@ -0,0 +1,238 @@ +import { + parseConfig, + defaultHandleRtd, + mergeOptableData, + getBidRequestData, + getTargetingData, + optableSubmodule, +} from 'modules/optableRtdProvider'; + +describe('Optable RTD Submodule', function () { + describe('parseConfig', function () { + it('parses valid config correctly', function () { + const config = { + params: { + bundleUrl: 'https://cdn.optable.co/bundle.js', + adserverTargeting: true, + handleRtd: () => {} + } + }; + expect(parseConfig(config)).to.deep.equal({ + bundleUrl: 'https://cdn.optable.co/bundle.js', + adserverTargeting: true, + handleRtd: config.params.handleRtd, + }); + }); + + it('trims bundleUrl if it contains extra spaces', function () { + const config = {params: {bundleUrl: ' https://cdn.optable.co/bundle.js '}}; + expect(parseConfig(config).bundleUrl).to.equal('https://cdn.optable.co/bundle.js'); + }); + + it('throws an error for invalid bundleUrl format', function () { + expect(() => parseConfig({params: {bundleUrl: 'invalidURL'}})).to.throw(); + expect(() => parseConfig({params: {bundleUrl: 'www.invalid.com'}})).to.throw(); + }); + + it('throws an error for non-HTTPS bundleUrl', function () { + expect(() => parseConfig({params: {bundleUrl: 'http://cdn.optable.co/bundle.js'}})).to.throw(); + expect(() => parseConfig({params: {bundleUrl: '//cdn.optable.co/bundle.js'}})).to.throw(); + expect(() => parseConfig({params: {bundleUrl: '/bundle.js'}})).to.throw(); + }); + + it('defaults adserverTargeting to true if missing', function () { + expect(parseConfig( + {params: {bundleUrl: 'https://cdn.optable.co/bundle.js'}} + ).adserverTargeting).to.be.true; + }); + + it('throws an error if handleRtd is not a function', function () { + expect(() => parseConfig({params: {handleRtd: 'notAFunction'}})).to.throw(); + }); + }); + + describe('defaultHandleRtd', function () { + let sandbox, reqBidsConfigObj, mergeFn; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + reqBidsConfigObj = {ortb2Fragments: {global: {}}}; + mergeFn = sinon.spy(); + window.optable = {instance: {targeting: sandbox.stub()}}; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('merges valid targeting data into the global ORTB2 object', async function () { + const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}}; + window.optable.instance.targeting.resolves(targetingData); + + await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); + expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true; + }); + + it('does nothing if targeting data is missing the ortb2 property', async function () { + window.optable.instance.targeting.resolves({}); + + await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); + expect(mergeFn.called).to.be.false; + }); + }); + + describe('mergeOptableData', function () { + let sandbox, mergeFn, handleRtdFn, reqBidsConfigObj; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + mergeFn = sinon.spy(); + reqBidsConfigObj = {ortb2Fragments: {global: {}}}; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('calls handleRtdFn synchronously if it is a regular function', async function () { + handleRtdFn = sinon.spy(); + await mergeOptableData(handleRtdFn, reqBidsConfigObj, {}, mergeFn); + expect(handleRtdFn.calledOnceWith(reqBidsConfigObj, {}, mergeFn)).to.be.true; + }); + + it('calls handleRtdFn asynchronously if it is an async function', async function () { + handleRtdFn = sinon.stub().resolves(); + await mergeOptableData(handleRtdFn, reqBidsConfigObj, {}, mergeFn); + expect(handleRtdFn.calledOnceWith(reqBidsConfigObj, {}, mergeFn)).to.be.true; + }); + }); + + describe('getBidRequestData', function () { + let sandbox, reqBidsConfigObj, callback, moduleConfig; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + reqBidsConfigObj = {ortb2Fragments: {global: {}}}; + callback = sinon.spy(); + moduleConfig = {params: {bundleUrl: 'https://cdn.optable.co/bundle.js'}}; + + sandbox.stub(window, 'optable').value({cmd: []}); + sandbox.stub(window.document, 'createElement'); + sandbox.stub(window.document, 'head'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('loads Optable JS bundle if bundleUrl is provided', function () { + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + expect(window.document.createElement.called).to.be.true; + }); + + it('uses existing Optable instance if no bundleUrl is provided', function () { + moduleConfig.params.bundleUrl = null; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + expect(window.optable.cmd.length).to.equal(1); + }); + + it('calls callback when assuming the bundle is present', function (done) { + moduleConfig.params.bundleUrl = null; + + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + + // Check that the function is queued + expect(window.optable.cmd.length).to.equal(1); + // Manually trigger the queued function + window.optable.cmd[0](); + + setTimeout(() => { + expect(callback.calledOnce).to.be.true; + done(); + }, 50); + }); + + it('mergeOptableData catches error and executes callback when something goes wrong', function (done) { + moduleConfig.params.bundleUrl = null; + moduleConfig.params.handleRtd = () => { throw new Error('Test error'); }; + + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + + expect(window.optable.cmd.length).to.equal(1); + window.optable.cmd[0](); + + setTimeout(() => { + expect(callback.calledOnce).to.be.true; + done(); + }, 50); + }); + + it('getBidRequestData catches error and executes callback when something goes wrong', function (done) { + moduleConfig.params.bundleUrl = null; + moduleConfig.params.handleRtd = 'not a function'; + + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + + expect(window.optable.cmd.length).to.equal(0); + + setTimeout(() => { + expect(callback.calledOnce).to.be.true; + done(); + }, 50); + }); + + it("doesn't fail when optable is not available", function (done) { + delete window.optable; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + expect(window?.optable?.cmd?.length).to.be.undefined; + + setTimeout(() => { + expect(callback.calledOnce).to.be.true; + done(); + }, 50); + }); + }); + + describe('getTargetingData', function () { + let sandbox, moduleConfig; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + moduleConfig = {params: {adserverTargeting: true}}; + window.optable = {instance: {targetingKeyValuesFromCache: sandbox.stub().returns({key1: 'value1'})}}; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('returns correct targeting data when Optable data is available', function () { + const result = getTargetingData(['adUnit1'], moduleConfig, {}, {}); + expect(result).to.deep.equal({adUnit1: {key1: 'value1'}}); + }); + + it('returns empty object when no Optable data is found', function () { + window.optable.instance.targetingKeyValuesFromCache.returns({}); + expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({}); + }); + + it('returns empty object when adserverTargeting is disabled', function () { + moduleConfig.params.adserverTargeting = false; + expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({}); + }); + + it('returns empty object when provided keys contain no data', function () { + window.optable.instance.targetingKeyValuesFromCache.returns({key1: []}); + expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({}); + + window.optable.instance.targetingKeyValuesFromCache.returns({key1: [], key2: [], key3: []}); + expect(getTargetingData(['adUnit1'], moduleConfig, {}, {})).to.deep.equal({}); + }); + }); + + describe('init', function () { + it('initializes Optable RTD module', function () { + expect(optableSubmodule.init()).to.be.true; + }); + }); +}); From e2d074d6ddfc2ce06d8faf3099596c9393382205 Mon Sep 17 00:00:00 2001 From: Bernhard Bohne <1151341+el-chuck@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:38:18 +0200 Subject: [PATCH 1049/1097] Smaato: Add iframe UserSyncs (#12924) --- modules/smaatoBidAdapter.js | 27 ++++++-- test/spec/modules/smaatoBidAdapter_spec.js | 77 +++++++++++++++++++--- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index f8a363cb084..fc7ad9d7d94 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -19,11 +19,12 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.2' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.3' const TTL = 300; const CURRENCY = 'USD'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; -const SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' +const IMAGE_SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' +const IFRAME_SYNC_URL = 'https://s.ad.smaato.net/i/?adExInit=p' export const spec = { code: BIDDER_CODE, @@ -197,7 +198,7 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - if (syncOptions && syncOptions.pixelEnabled) { + if (syncOptions) { let gdprParams = ''; if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { @@ -207,10 +208,22 @@ export const spec = { } } - return [{ - type: 'image', - url: SYNC_URL + gdprParams - }]; + if (syncOptions.iframeEnabled) { + let maxUrlsParam = ''; + if (config.getConfig('userSync') && config.getConfig('userSync').syncsPerBidder) { + maxUrlsParam = `&maxUrls=${config.getConfig('userSync').syncsPerBidder}`; + } + + return [{ + type: 'iframe', + url: IFRAME_SYNC_URL + gdprParams + maxUrlsParam + }]; + } else if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: IMAGE_SYNC_URL + gdprParams + }]; + } } return []; diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index f9d300d84e4..6d117f829f9 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -12,7 +12,8 @@ import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; -const SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' +const IMAGE_SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' +const IFRAME_SYNC_URL = 'https://s.ad.smaato.net/i/?adExInit=p' const ADTYPE_IMG = 'Img'; const ADTYPE_VIDEO = 'Video'; @@ -1670,41 +1671,101 @@ describe('smaatoBidAdapterTest', () => { }); describe('getUserSyncs', () => { - it('when pixelEnabled false then returns no pixels', () => { + afterEach(() => { + config.resetConfig(); + }) + + it('when pixelEnabled and iframeEnabled false then returns no syncs', () => { expect(spec.getUserSyncs()).to.be.empty }) - it('when pixelEnabled true then returns pixel', () => { + it('when pixelEnabled true then returns image sync', () => { expect(spec.getUserSyncs({pixelEnabled: true}, null, null, null)).to.deep.equal( [ { type: 'image', - url: SYNC_URL + url: IMAGE_SYNC_URL } ] ) }) - it('when pixelEnabled true and gdprConsent then returns pixel with gdpr params', () => { + it('when iframeEnabled true then returns iframe sync', () => { + expect(spec.getUserSyncs({iframeEnabled: true}, null, null, null)).to.deep.equal( + [ + { + type: 'iframe', + url: IFRAME_SYNC_URL + } + ] + ) + }) + + it('when iframeEnabled true and syncsPerBidder then returns iframe sync', () => { + config.setConfig({userSync: {syncsPerBidder: 5}}); + expect(spec.getUserSyncs({iframeEnabled: true}, null, null, null)).to.deep.equal( + [ + { + type: 'iframe', + url: `${IFRAME_SYNC_URL}&maxUrls=5` + } + ] + ) + }) + + it('when iframeEnabled and pixelEnabled true then returns iframe sync', () => { + expect(spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, null, null, null)).to.deep.equal( + [ + { + type: 'iframe', + url: IFRAME_SYNC_URL + } + ] + ) + }) + + it('when pixelEnabled true and gdprConsent then returns image sync with gdpr params', () => { expect(spec.getUserSyncs({pixelEnabled: true}, null, {gdprApplies: true, consentString: CONSENT_STRING}, null)).to.deep.equal( [ { type: 'image', - url: `${SYNC_URL}&gdpr=1&gdpr_consent=${CONSENT_STRING}` + url: `${IMAGE_SYNC_URL}&gdpr=1&gdpr_consent=${CONSENT_STRING}` } ] ) }) - it('when pixelEnabled true and gdprConsent without gdpr then returns pixel with gdpr_consent', () => { + it('when iframeEnabled true and gdprConsent then returns iframe with gdpr params', () => { + expect(spec.getUserSyncs({iframeEnabled: true}, null, {gdprApplies: true, consentString: CONSENT_STRING}, null)).to.deep.equal( + [ + { + type: 'iframe', + url: `${IFRAME_SYNC_URL}&gdpr=1&gdpr_consent=${CONSENT_STRING}` + } + ] + ) + }) + + it('when pixelEnabled true and gdprConsent without gdpr then returns pixel sync with gdpr_consent', () => { expect(spec.getUserSyncs({pixelEnabled: true}, null, {consentString: CONSENT_STRING}, null), null).to.deep.equal( [ { type: 'image', - url: `${SYNC_URL}&gdpr_consent=${CONSENT_STRING}` + url: `${IMAGE_SYNC_URL}&gdpr_consent=${CONSENT_STRING}` } ] ) }) + + it('when iframeEnabled true and gdprConsent without gdpr then returns iframe sync with gdpr_consent', () => { + expect(spec.getUserSyncs({iframeEnabled: true}, null, {consentString: CONSENT_STRING}, null), null).to.deep.equal( + [ + { + type: 'iframe', + url: `${IFRAME_SYNC_URL}&gdpr_consent=${CONSENT_STRING}` + } + ] + ) + }) }) }); From 9ab9cc9362f402ab16460030c8e1320d3bf1f0ec Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 1 Apr 2025 19:11:29 -0700 Subject: [PATCH 1050/1097] Build system: clear event log between test suites (#12946) --- test/helpers/global_hooks.js | 24 ++++++++++++++++++++++++ test/test_deps.js | 1 + 2 files changed, 25 insertions(+) create mode 100644 test/helpers/global_hooks.js diff --git a/test/helpers/global_hooks.js b/test/helpers/global_hooks.js new file mode 100644 index 00000000000..3df3d27d032 --- /dev/null +++ b/test/helpers/global_hooks.js @@ -0,0 +1,24 @@ +import {clearEvents} from '../../src/events.js'; + +window.describe = window.context = ((orig) => { + let level = 0; + return function (name, fn, ...args) { + try { + if (level++ === 0) { + fn = ((orig) => { + return function (...args) { + const result = orig.apply(this, args); + after(() => { + // run this after each top-level "describe", roughly equivalent to each file + clearEvents(); + }); + return result; + } + })(fn) + } + return orig.call(this, name, fn, ...args); + } finally { + level--; + } + } +})(window.describe); diff --git a/test/test_deps.js b/test/test_deps.js index 3f0f766b457..1afcb435061 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -14,6 +14,7 @@ window.addEventListener('unhandledrejection', function (ev) { console.error('Unhandled rejection:', ev.reason); }) +require('test/helpers/global_hooks.js'); require('test/helpers/consentData.js'); require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); From 0e36c5295bff48a91da915a783cab6ec7b1ce928 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 2 Apr 2025 11:15:32 -0500 Subject: [PATCH 1051/1097] NewsPassID Bid Adapter: refactor (#12923) * newspassidBidAdapter refactor * update to use setBidderConfig * revert to legacy params publisherId, placementId --- modules/newspassid.md | 76 - modules/newspassidBidAdapter.js | 813 ++----- modules/newspassidBidAdapter.md | 47 + .../spec/modules/newspassidBidAdapter_spec.js | 2083 +++-------------- 4 files changed, 514 insertions(+), 2505 deletions(-) delete mode 100644 modules/newspassid.md create mode 100644 modules/newspassidBidAdapter.md diff --git a/modules/newspassid.md b/modules/newspassid.md deleted file mode 100644 index 6fa709e5ba6..00000000000 --- a/modules/newspassid.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -Module Name: NewspassId Bidder Adapter -Module Type: Bidder Adapter -Maintainer: techsupport@newspassid.com -layout: bidder -title: Newspass ID -description: LMC Newspass ID Prebid JS Bidder Adapter -biddercode: newspassid -gdpr_supported: false -gvl_id: none -usp_supported: true -coppa_supported: false -schain_supported: true -dchain_supported: false -userIds: criteo, id5Id, tdid, identityLink, liveIntentId, parrableId, pubCommonId, lotamePanoramaId, sharedId, fabrickId -media_types: banner -safeframes_ok: true -deals_supported: true -floors_supported: false -fpd_supported: false -pbjs: true -pbs: false -prebid_member: false -multiformat_supported: will-bid-on-any ---- - -### Description - -LMC Newspass ID Prebid JS Bidder Adapter that connects to the NewspassId demand source(s). - -The Newspass bid adapter supports Banner mediaTypes ONLY. -This is intended for USA audiences only, and does not support GDPR - - -### Bid Params - -{: .table .table-bordered .table-striped } - -| Name | Scope | Description | Example | Type | -|-----------|----------|---------------------------|------------|----------| -| `siteId` | required | The site ID. | `"NPID0000001"` | `string` | -| `publisherId` | required | The publisher ID. | `"4204204201"` | `string` | -| `placementId` | required | The placement ID. | `"0420420421"` | `string` | -| `customData` | optional | publisher key-values used for targeting | `[{"settings":{},"targeting":{"key1": "value1", "key2": "value2"}}], ` | `array` | - -### Test Parameters - - -A test ad unit that will consistently return test creatives: - -``` - -//Banner adUnit - -adUnits = [{ - code: 'id-of-your-banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'newspassid', - params: { - publisherId: 'NEWSPASS0001', /* an ID to identify the publisher account - required */ - siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ - placementId: '8000000015', /* an ID used to identify the piece of inventory - required - for appnexus test use 13144370. */ - customData: [{"settings": {}, "targeting": {"key": "value", "key2": ["value1", "value2"]}}],/* optional array with 'targeting' placeholder for passing publisher specific key-values for targeting. */ - } - }] - }]; -``` - -### Note: - -Please contact us at techsupport@newspassid.com for any assistance testing your implementation before going live into production. diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index d33b4e64297..fac9841318d 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -1,674 +1,165 @@ -import { - deepClone, - logInfo, - logError, - deepAccess, - logWarn, - deepSetValue, - isArray, - contains, - parseUrl, - generateUUID -} from '../src/utils.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { deepSetValue } from '../src/utils.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {getPriceBucketString} from '../src/cpmBucketManager.js'; -import {getRefererInfo} from '../src/refererDetection.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + */ + const BIDDER_CODE = 'newspassid'; -const ORIGIN = 'https://bidder.newspassid.com' // applies only to auction & cookie -const AUCTIONURI = '/openrtb2/auction'; -const NEWSPASSCOOKIESYNC = '/static/load-cookie.html'; -const NEWSPASSVERSION = '1.1.4'; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_TTL = 300; +const ENDPOINT_URL = 'https://npid.amspbs.com/v0/bid/request'; +const GVL_ID = 1317; +const SYNC_URL = 'https://npid.amspbs.com/v0/user/sync'; + +const converter = ortbConverter({ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.newspassid', { + publisher: resolveNewpassidPublisherId(bidRequest), + placementId: bidRequest.params.placementId, + }) + return imp; + }, + context: { + ttl: DEFAULT_TTL, + netRevenue: DEFAULT_NET_REVENUE + } +}); + +/** + * Helper function to add params to url + * @param {string} url + * @param {object} params + * @returns {string} + */ +const addParamsToUrl = (url, params) => { + const urlObj = new URL(url); + Object.entries(params).forEach(([key, value]) => { + urlObj.searchParams.set(key, value); + }); + return urlObj.toString(); +}; + +/** + * Get the global publisherId for the newspassid bidder + * @returns {string|null} + */ +const getGlobalPublisherIdOrNull = () => { + const globalPublisherId = config.getConfig('newspassid.publisherId'); + if (globalPublisherId) return globalPublisherId; + return null; +}; + +/** + * Resolve the publisherId for the newspassid bidder + * @param {BidRequest|undefined} bidRequest + * @returns {string|null} + */ +export const resolveNewpassidPublisherId = (bidRequest) => { + if (typeof bidRequest !== 'object') return getGlobalPublisherIdOrNull(); + + // get publisherId from bidRequest params + const { params } = bidRequest; + if (params?.publisherId) return params?.publisherId; + + return getGlobalPublisherIdOrNull(); +}; + +/** + * @type {BidderSpec} + */ export const spec = { - version: NEWSPASSVERSION, code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - cookieSyncBag: {publisherId: null, siteId: null, userIdObject: {}}, // variables we want to make available to cookie sync - propertyBag: {config: null, pageId: null, buildRequestsStart: 0, buildRequestsEnd: 0, endpointOverride: null}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ - config_defaults: { - 'logId': 'NEWSPASSID', - 'bidder': 'newspassid', - 'auctionUrl': ORIGIN + AUCTIONURI, - 'cookieSyncUrl': ORIGIN + NEWSPASSCOOKIESYNC - }, - loadConfiguredData(bid) { - if (this.propertyBag.config) { return; } - this.propertyBag.config = deepClone(this.config_defaults); - let bidder = bid.bidder || 'newspassid'; - this.propertyBag.config.logId = bidder.toUpperCase(); - this.propertyBag.config.bidder = bidder; - let bidderConfig = config.getConfig(bidder) || {}; - logInfo('got bidderConfig: ', deepClone(bidderConfig)); - let arrGetParams = this.getGetParametersAsObject(); - if (bidderConfig.endpointOverride) { - if (bidderConfig.endpointOverride.origin) { - this.propertyBag.endpointOverride = bidderConfig.endpointOverride.origin; - this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; - this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.origin + NEWSPASSCOOKIESYNC; - } - if (bidderConfig.endpointOverride.cookieSyncUrl) { - this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.cookieSyncUrl; - } - if (bidderConfig.endpointOverride.auctionUrl) { - this.propertyBag.endpointOverride = bidderConfig.endpointOverride.auctionUrl; - this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.auctionUrl; - } - } - try { - if (arrGetParams.hasOwnProperty('auction')) { - logInfo('GET: setting auction endpoint to: ' + arrGetParams.auction); - this.propertyBag.config.auctionUrl = arrGetParams.auction; - } - if (arrGetParams.hasOwnProperty('cookiesync')) { - logInfo('GET: setting cookiesync to: ' + arrGetParams.cookiesync); - this.propertyBag.config.cookieSyncUrl = arrGetParams.cookiesync; - } - } catch (e) {} - logInfo('set propertyBag.config to', this.propertyBag.config); - }, - getAuctionUrl() { - return this.propertyBag.config.auctionUrl; - }, - getCookieSyncUrl() { - return this.propertyBag.config.cookieSyncUrl; - }, - isBidRequestValid(bid) { - this.loadConfiguredData(bid); - logInfo('isBidRequestValid : ', config.getConfig(), bid); - let adUnitCode = bid.adUnitCode; // adunit[n].code - let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED'; - if (!(bid.params.hasOwnProperty('placementId'))) { - logError(err1.replace('{param}', 'placementId'), adUnitCode); - return false; - } - if (!this.isValidPlacementId(bid.params.placementId)) { - logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); - return false; - } - if (!(bid.params.hasOwnProperty('publisherId'))) { - logError(err1.replace('{param}', 'publisherId'), adUnitCode); - return false; - } - if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumeric characters including hyphens', adUnitCode); - return false; - } - if (!(bid.params.hasOwnProperty('siteId'))) { - logError(err1.replace('{param}', 'siteId'), adUnitCode); - return false; - } - if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); - return false; - } - if (bid.params.hasOwnProperty('customParams')) { - logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); - return false; - } - if (bid.params.hasOwnProperty('customData')) { - if (!Array.isArray(bid.params.customData)) { - logError('VALIDATION FAILED : customData is not an Array', adUnitCode); - return false; - } - if (bid.params.customData.length < 1) { - logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); - return false; - } - if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { - logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); - return false; - } - if (typeof bid.params.customData[0]['targeting'] != 'object') { - logError('VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); - return false; - } - } - return true; - }, - isValidPlacementId(placementId) { - return placementId.toString().match(/^[0-9]{10}$/); - }, - buildRequests(validBidRequests, bidderRequest) { - this.loadConfiguredData(validBidRequests[0]); - this.propertyBag.buildRequestsStart = new Date().getTime(); - logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, deepClone(validBidRequests), 'bidderRequest', deepClone(bidderRequest)); - if (this.blockTheRequest()) { - return []; - } - let htmlParams = {'publisherId': '', 'siteId': ''}; - if (validBidRequests.length > 0) { - this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); - this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); - this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); - htmlParams = validBidRequests[0].params; - } - logInfo('cookie sync bag', this.cookieSyncBag); - let singleRequest = config.getConfig('newspassid.singleRequest'); - singleRequest = singleRequest !== false; // undefined & true will be true - logInfo(`config newspassid.singleRequest : `, singleRequest); - let npRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params - logInfo('going to get ortb2 from bidder request...'); - let fpd = deepAccess(bidderRequest, 'ortb2', null); - logInfo('got fpd: ', fpd); - if (fpd && deepAccess(fpd, 'user')) { - logInfo('added FPD user object'); - npRequest.user = fpd.user; - } - const getParams = this.getGetParametersAsObject(); - const isTestMode = getParams['nptestmode'] || null; // this can be any string, it's used for testing ads - npRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; - let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string - let schain = null; - let tosendtags = validBidRequests.map(npBidRequest => { - var obj = {}; - let placementId = placementIdOverrideFromGetParam || this.getPlacementId(npBidRequest); // prefer to use a valid override param, else the bidRequest placement Id - obj.id = npBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder newspass made bid for unknown request ID: mb7953.859498327448. Ignoring." - obj.tagid = placementId; - let parsed = parseUrl(this.getRefererInfo().page); - obj.secure = parsed.protocol === 'https' ? 1 : 0; - let arrBannerSizes = []; - if (!npBidRequest.hasOwnProperty('mediaTypes')) { - if (npBidRequest.hasOwnProperty('sizes')) { - logInfo('no mediaTypes detected - will use the sizes array in the config root'); - arrBannerSizes = npBidRequest.sizes; - } else { - logInfo('Cannot set sizes for banner type'); - } - } else { - if (npBidRequest.mediaTypes.hasOwnProperty(BANNER)) { - arrBannerSizes = npBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); - } - if (npBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { - obj.native = npBidRequest.mediaTypes[NATIVE]; - logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); - } - } - if (arrBannerSizes.length > 0) { - obj.banner = { - topframe: 1, - w: arrBannerSizes[0][0] || 0, - h: arrBannerSizes[0][1] || 0, - format: arrBannerSizes.map(s => { - return {w: s[0], h: s[1]}; - }) - }; - } - obj.placementId = placementId; - deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); - obj.ext['newspassid'] = {}; - obj.ext['newspassid'].adUnitCode = npBidRequest.adUnitCode; // eg. 'mpu' - if (npBidRequest.params.hasOwnProperty('customData')) { - obj.ext['newspassid'].customData = npBidRequest.params.customData; - } - logInfo(`obj.ext.newspassid is `, obj.ext['newspassid']); - if (isTestMode != null) { - logInfo('setting isTestMode to ', isTestMode); - if (obj.ext['newspassid'].hasOwnProperty('customData')) { - for (let i = 0; i < obj.ext['newspassid'].customData.length; i++) { - obj.ext['newspassid'].customData[i]['targeting']['nptestmode'] = isTestMode; - } - } else { - obj.ext['newspassid'].customData = [{'settings': {}, 'targeting': {}}]; - obj.ext['newspassid'].customData[0].targeting['nptestmode'] = isTestMode; + gvlid: GVL_ID, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + isBidRequestValid: function(bidRequest) { + const publisherId = resolveNewpassidPublisherId(bidRequest); + return !!(bidRequest.params && publisherId && bidRequest.params.placementId); + }, + + buildRequests: function(bidRequests, bidderRequest) { + // convert to ortb using the converter utility + const data = converter.toORTB({ bidRequests, bidderRequest }); + + return [ + { + method: 'POST', + url: ENDPOINT_URL, + data: data, + options: { + withCredentials: true } } - if (fpd && deepAccess(fpd, 'site')) { - logInfo('adding fpd.site'); - if (deepAccess(obj, 'ext.newspassid.customData.0.targeting', false)) { - obj.ext.newspassid.customData[0].targeting = Object.assign(obj.ext.newspassid.customData[0].targeting, fpd.site); - } else { - deepSetValue(obj, 'ext.newspassid.customData.0.targeting', fpd.site); + ]; + }, + + interpretResponse: function(serverResponse, bidRequest) { + const response = serverResponse.body; + const bidResponses = []; + + if (!response || !response.seatbid || !response.seatbid[0].bid) { + return bidResponses; + } + + response.seatbid[0].bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + currency: response.cur || DEFAULT_CURRENCY, + netRevenue: true, + ttl: DEFAULT_TTL, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain || [], } - } - if (!schain && deepAccess(npBidRequest, 'schain')) { - schain = npBidRequest.schain; - } - let gpid = deepAccess(npBidRequest, 'ortb2Imp.ext.gpid'); - if (gpid) { - deepSetValue(obj, 'ext.gpid', gpid); - } - return obj; + }); }); - let extObj = {}; - extObj['newspassid'] = {}; - extObj['newspassid']['np_pb_v'] = NEWSPASSVERSION; - extObj['newspassid']['np_rw'] = placementIdOverrideFromGetParam ? 1 : 0; - if (validBidRequests.length > 0) { - let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - if (userIds.hasOwnProperty('pubcid')) { - extObj['newspassid'].pubcid = userIds.pubcid; - } - } - extObj['newspassid'].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called - let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); - let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; - extObj['newspassid']['np_kvp_rw'] = useWhitelistAdserverKeys ? 1 : 0; - if (getParams.hasOwnProperty('npf')) { extObj['newspassid']['npf'] = getParams.npf === 'true' || getParams.npf === '1' ? 1 : 0; } - if (getParams.hasOwnProperty('nppf')) { extObj['newspassid']['nppf'] = getParams.nppf === 'true' || getParams.nppf === '1' ? 1 : 0; } - if (getParams.hasOwnProperty('nprp') && getParams.nprp.match(/^[0-3]$/)) { extObj['newspassid']['nprp'] = parseInt(getParams.nprp); } - if (getParams.hasOwnProperty('npip') && getParams.npip.match(/^\d+$/)) { extObj['newspassid']['npip'] = parseInt(getParams.npip); } - if (this.propertyBag.endpointOverride != null) { extObj['newspassid']['origin'] = this.propertyBag.endpointOverride; } - let userExtEids = deepAccess(validBidRequests, '0.userIdAsEids', []); // generate the UserIDs in the correct format for UserId module - npRequest.site = { - 'publisher': {'id': htmlParams.publisherId}, - 'page': this.getRefererInfo().page, - 'id': htmlParams.siteId + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + if (!syncOptions.iframeEnabled) return []; // disable if iframe sync is disabled + if (!hasPurpose1Consent(gdprConsent)) return []; // disable if no purpose1 consent + if (config.getConfig('coppa') === true) return []; // disable syncs for coppa + + const params = { + gdpr: gdprConsent?.gdprApplies ? 1 : 0, + gdpr_consent: gdprConsent?.gdprApplies + ? encodeURIComponent(gdprConsent?.consentString || '') + : '', + gpp: encodeURIComponent(gppConsent?.gppString || ''), + gpp_sid: encodeURIComponent(gppConsent?.applicableSections || ''), + us_privacy: encodeURIComponent(uspConsent || ''), }; - npRequest.test = config.getConfig('debug') ? 1 : 0; - if (bidderRequest && bidderRequest.uspConsent) { - logInfo('ADDING USP consent info'); - deepSetValue(npRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } else { - logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); - } - if (schain) { // we set this while iterating over the bids - logInfo('schain found'); - deepSetValue(npRequest, 'source.ext.schain', schain); - } - if (config.getConfig('coppa') === true) { - deepSetValue(npRequest, 'regs.coppa', 1); - } - if (singleRequest) { - logInfo('buildRequests starting to generate response for a single request'); - npRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) - npRequest.imp = tosendtags; - npRequest.ext = extObj; - deepSetValue(npRequest, 'user.ext.eids', userExtEids); - var ret = { - method: 'POST', - url: this.getAuctionUrl(), - data: JSON.stringify(npRequest), - bidderRequest: bidderRequest - }; - logInfo('buildRequests request data for single = ', deepClone(npRequest)); - this.propertyBag.buildRequestsEnd = new Date().getTime(); - logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); - return ret; - } - let arrRet = tosendtags.map(imp => { - logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); - let npRequestSingle = Object.assign({}, npRequest); - npRequestSingle.id = generateUUID(); - npRequestSingle.imp = [imp]; - npRequestSingle.ext = extObj; - deepSetValue(npRequestSingle, 'user.ext.eids', userExtEids); - logInfo('buildRequests RequestSingle (for non-single) = ', npRequestSingle); - return { - method: 'POST', - url: this.getAuctionUrl(), - data: JSON.stringify(npRequestSingle), - bidderRequest: bidderRequest - }; + + const globalPublisherId = resolveNewpassidPublisherId({}); + if (globalPublisherId) { + // "publisher" is a convention on the server side + params.publisher = globalPublisherId; + } + + let syncs = []; + + // iframe sync + syncs.push({ + type: 'iframe', + url: addParamsToUrl(SYNC_URL, params), }); - this.propertyBag.buildRequestsEnd = new Date().getTime(); - logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); - return arrRet; - }, - interpretResponse(serverResponse, request) { - if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadConfiguredData(request.bidderRequest.bids[0]); } - let startTime = new Date().getTime(); - logInfo(`interpretResponse time: ${startTime}. buildRequests done -> interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - logInfo(`serverResponse, request`, deepClone(serverResponse), deepClone(request)); - serverResponse = serverResponse.body || {}; - let aucId = serverResponse.id; // this will be correct for single requests and non-single - if (!serverResponse.hasOwnProperty('seatbid')) { - return []; - } - if (typeof serverResponse.seatbid !== 'object') { - return []; - } - let arrAllBids = []; - let enhancedAdserverTargeting = config.getConfig('newspassid.enhancedAdserverTargeting'); - logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - if (typeof enhancedAdserverTargeting == 'undefined') { - enhancedAdserverTargeting = true; - } - logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. - serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); - let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); - let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; - for (let i = 0; i < serverResponse.seatbid.length; i++) { - let sb = serverResponse.seatbid[i]; - for (let j = 0; j < sb.bid.length; j++) { - let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); - logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); - const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); - let thisBid = this.addStandardProperties(sb.bid[j], defaultWidth, defaultHeight); - thisBid.meta = {advertiserDomains: thisBid.adomain || []}; - let bidType = deepAccess(thisBid, 'ext.prebid.type'); - logInfo(`this bid type is : ${bidType}`, j); - let adserverTargeting = {}; - if (enhancedAdserverTargeting) { - let allBidsForThisBidid = this.getAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); - Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { - logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); - adserverTargeting['np_' + bidderName] = bidderName; - adserverTargeting['np_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); - adserverTargeting['np_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); - adserverTargeting['np_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); - adserverTargeting['np_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); - if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { - adserverTargeting['np_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); - } - }); - } else { - logInfo(`newspassid.enhancedAdserverTargeting is set to false, no per-bid keys will be sent to adserver.`); - } - let {seat: winningSeat, bid: winningBid} = this.getWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); - adserverTargeting['np_auc_id'] = String(aucId); - adserverTargeting['np_winner'] = String(winningSeat); - adserverTargeting['np_bid'] = 'true'; - if (enhancedAdserverTargeting) { - adserverTargeting['np_imp_id'] = String(winningBid.impid); - adserverTargeting['np_pb_r'] = getRoundedBid(winningBid.price, bidType); - adserverTargeting['np_adId'] = String(winningBid.adId); - adserverTargeting['np_size'] = `${winningBid.width}x${winningBid.height}`; - } - if (useWhitelistAdserverKeys) { // delete any un-whitelisted keys - logInfo('Going to filter out adserver targeting keys not in the whitelist: ', whitelistAdserverKeys); - Object.keys(adserverTargeting).forEach(function(key) { if (whitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); - } - thisBid.adserverTargeting = adserverTargeting; - arrAllBids.push(thisBid); - } - } - let endTime = new Date().getTime(); - logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); - return arrAllBids; - }, - removeSingleBidderMultipleBids(seatbid) { - var ret = []; - for (let i = 0; i < seatbid.length; i++) { - let sb = seatbid[i]; - var retSeatbid = {'seat': sb.seat, 'bid': []}; - var bidIds = []; - for (let j = 0; j < sb.bid.length; j++) { - var candidate = sb.bid[j]; - if (contains(bidIds, candidate.impid)) { - continue; // we've already fully assessed this impid, found the highest bid from this seat for it - } - bidIds.push(candidate.impid); - for (let k = j + 1; k < sb.bid.length; k++) { - if (sb.bid[k].impid === candidate.impid && sb.bid[k].price > candidate.price) { - candidate = sb.bid[k]; - } - } - retSeatbid.bid.push(candidate); - } - ret.push(retSeatbid); - } - return ret; - }, - getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { - logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); - if (!serverResponse || serverResponse.length === 0) { - return []; - } - if (optionsType.iframeEnabled) { - var arrQueryString = []; - if (config.getConfig('debug')) { - arrQueryString.push('pbjs_debug=true'); - } - arrQueryString.push('usp_consent=' + (usPrivacy || '')); - for (let keyname in this.cookieSyncBag.userIdObject) { - arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); - } - arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); - arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); - arrQueryString.push('cb=' + Date.now()); - arrQueryString.push('bidder=' + this.propertyBag.config.bidder); - var strQueryString = arrQueryString.join('&'); - if (strQueryString.length > 0) { - strQueryString = '?' + strQueryString; - } - logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); - return [{ - type: 'iframe', - url: this.getCookieSyncUrl() + strQueryString - }]; - } - }, - getBidRequestForBidId(bidId, arrBids) { - for (let i = 0; i < arrBids.length; i++) { - if (arrBids[i].bidId === bidId) { // bidId in the request comes back as impid in the seatbid bids - return arrBids[i]; - } - } - return null; - }, - findAllUserIds(bidRequest) { - var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; - if (bidRequest.hasOwnProperty('userId')) { - for (let arrayId in searchKeysSingle) { - let key = searchKeysSingle[arrayId]; - if (bidRequest.userId.hasOwnProperty(key)) { - if (typeof (bidRequest.userId[key]) == 'string') { - ret[key] = bidRequest.userId[key]; - } else if (typeof (bidRequest.userId[key]) == 'object') { - logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); - ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values - } else { - logError(`failed to get string key value for userId : ${key}`); - } - } - } - let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); - if (lipbid) { - ret['lipb'] = {'lipbid': lipbid}; - } - let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); - if (id5id) { - ret['id5id'] = id5id; - } - let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); - if (sharedid) { - ret['sharedid'] = sharedid; - } - } - if (!ret.hasOwnProperty('pubcid')) { - let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); - if (pubcid) { - ret['pubcid'] = pubcid; // if built with old pubCommonId module - } - } - return ret; - }, - getPlacementId(bidRequest) { - return (bidRequest.params.placementId).toString(); - }, - getPlacementIdOverrideFromGetParam() { - let arr = this.getGetParametersAsObject(); - if (arr.hasOwnProperty('npstoredrequest')) { - if (this.isValidPlacementId(arr['npstoredrequest'])) { - logInfo(`using GET npstoredrequest ` + arr['npstoredrequest'] + ' to replace placementId'); - return arr['npstoredrequest']; - } else { - logError(`GET npstoredrequest FAILED VALIDATION - will not use it`); - } - } - return null; - }, - getGetParametersAsObject() { - let parsed = parseUrl(this.getRefererInfo().location); // was getRefererInfo().page but this is not backwards compatible - logInfo('getGetParametersAsObject found:', parsed.search); - return parsed.search; - }, - getRefererInfo() { - if (getRefererInfo().hasOwnProperty('location')) { - logInfo('FOUND location on getRefererInfo OK (prebid >= 7); will use getRefererInfo for location & page'); - return getRefererInfo(); - } else { - logInfo('DID NOT FIND location on getRefererInfo (prebid < 7); will use legacy code that ALWAYS worked reliably to get location & page ;-)'); - try { - return { - page: top.location.href, - location: top.location.href - }; - } catch (e) { - return { - page: window.location.href, - location: window.location.href - }; - } - } - }, - blockTheRequest() { - let npRequest = config.getConfig('newspassid.np_request'); - if (typeof npRequest == 'boolean' && !npRequest) { - logWarn(`Will not allow auction : np_request is set to false`); - return true; - } - return false; - }, - getPageId: function() { - if (this.propertyBag.pageId == null) { - let randPart = ''; - let allowable = '0123456789abcdefghijklmnopqrstuvwxyz'; - for (let i = 20; i > 0; i--) { - randPart += allowable[Math.floor(Math.random() * 36)]; - } - this.propertyBag.pageId = new Date().getTime() + '_' + randPart; - } - return this.propertyBag.pageId; - }, - addStandardProperties(seatBid, defaultWidth, defaultHeight) { - seatBid.cpm = seatBid.price; - seatBid.bidId = seatBid.impid; - seatBid.requestId = seatBid.impid; - seatBid.width = seatBid.w || defaultWidth; - seatBid.height = seatBid.h || defaultHeight; - seatBid.ad = seatBid.adm; - seatBid.netRevenue = true; - seatBid.creativeId = seatBid.crid; - seatBid.currency = 'USD'; - seatBid.ttl = 300; - return seatBid; - }, - getWinnerForRequestBid(requestBidId, serverResponseSeatBid) { - let thisBidWinner = null; - let winningSeat = null; - for (let j = 0; j < serverResponseSeatBid.length; j++) { - let theseBids = serverResponseSeatBid[j].bid; - let thisSeat = serverResponseSeatBid[j].seat; - for (let k = 0; k < theseBids.length; k++) { - if (theseBids[k].impid === requestBidId) { - if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { - thisBidWinner = theseBids[k]; - winningSeat = thisSeat; - break; - } - } - } - } - return {'seat': winningSeat, 'bid': thisBidWinner}; - }, - getAllBidsForBidId(matchBidId, serverResponseSeatBid) { - let objBids = {}; - for (let j = 0; j < serverResponseSeatBid.length; j++) { - let theseBids = serverResponseSeatBid[j].bid; - let thisSeat = serverResponseSeatBid[j].seat; - for (let k = 0; k < theseBids.length; k++) { - if (theseBids[k].impid === matchBidId) { - if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid - if (objBids[thisSeat]['price'] < theseBids[k].price) { - objBids[thisSeat] = theseBids[k]; - } - } else { - objBids[thisSeat] = theseBids[k]; - } - } - } - } - return objBids; + + return syncs; } }; -export function injectAdIdsIntoAllBidResponses(seatbid) { - logInfo('injectAdIdsIntoAllBidResponses', seatbid); - for (let i = 0; i < seatbid.length; i++) { - let sb = seatbid[i]; - for (let j = 0; j < sb.bid.length; j++) { - sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-np-${j}`; - } - } - return seatbid; -} -export function checkDeepArray(Arr) { - if (Array.isArray(Arr)) { - if (Array.isArray(Arr[0])) { - return Arr[0]; - } else { - return Arr; - } - } else { - return Arr; - } -} -export function defaultSize(thebidObj) { - if (!thebidObj) { - logInfo('defaultSize received empty bid obj! going to return fixed default size'); - return { - 'defaultHeight': 250, - 'defaultWidth': 300 - }; - } - const {sizes} = thebidObj; - const returnObject = {}; - returnObject.defaultWidth = checkDeepArray(sizes)[0]; - returnObject.defaultHeight = checkDeepArray(sizes)[1]; - return returnObject; -} -export function getRoundedBid(price, mediaType) { - const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity' - let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom' - let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' ** - let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); - let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); - logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); - let priceStringsObj = getPriceBucketString( - price, - theConfigObject, - config.getConfig('currency.granularityMultiplier') - ); - logInfo('priceStringsObj', priceStringsObj); - let granularityNamePriceStringsKeyMapping = { - 'medium': 'med', - 'custom': 'custom', - 'high': 'high', - 'low': 'low', - 'dense': 'dense' - }; - if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { - let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; - logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); - return priceStringsObj[priceStringsKey]; - } - return priceStringsObj['auto']; -} -export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets) { - if (typeof mediaTypeGranularity === 'string') { - return mediaTypeGranularity; - } - if (typeof mediaTypeGranularity === 'object') { - return 'custom'; - } - if (typeof strBuckets === 'string') { - return strBuckets; - } - return 'auto'; // fall back to a default key - should literally never be needed. -} -export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets) { - if (typeof mediaTypeGranularity === 'object') { - return mediaTypeGranularity; - } - if (strBuckets === 'custom') { - return objBuckets; - } - return ''; -} + registerBidder(spec); -logInfo(`*BidAdapter ${NEWSPASSVERSION} was loaded`); diff --git a/modules/newspassidBidAdapter.md b/modules/newspassidBidAdapter.md new file mode 100644 index 00000000000..aff1d902c3e --- /dev/null +++ b/modules/newspassidBidAdapter.md @@ -0,0 +1,47 @@ +Overview +======== + +``` +Module Name: NewsPassID Bid Adapter +Module Type: Bidder Adapter +Maintainer: techsupport@newspassid.com +``` + +Description +=========== + +Bid adapter to connect to Local Media Consortium's NewsPassID (NPID) demand source(s). This adapter runs bid requests through ad server technology built and maintained by Aditude. + +# Bid Parameters + +| Key | Required | Example | Description | +| --- | -------- | ------- | ----------- | +| `publisherId` | yes | `"123456"` | For associating the publisher account for the NewsPassID initiative | +| `placementId` | yes | `"leftrail-mobile-1"` | For associating the ad placement inventory with demand. This ID must be predefined by NewsPassID provider | + + +# Test Parameters + +```javascript +pbjs.setConfig({ + newspassid: { + publisherId: '123456', + } +}); + +var adUnits = [ + { + code: 'newspass-test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'newspassid', + params: { + publisherId: '123456', // optional if you set in bidder config + placementId: 'test-group1' + } + } + ] + } +] +``` diff --git a/test/spec/modules/newspassidBidAdapter_spec.js b/test/spec/modules/newspassidBidAdapter_spec.js index 6468d4f530a..dec630f3f6a 100644 --- a/test/spec/modules/newspassidBidAdapter_spec.js +++ b/test/spec/modules/newspassidBidAdapter_spec.js @@ -1,1799 +1,346 @@ -import { expect } from 'chai'; -import { spec, defaultSize } from 'modules/newspassidBidAdapter.js'; +import { spec } from 'modules/newspassidBidAdapter.js'; import { config } from 'src/config.js'; -import {getGranularityKeyName, getGranularityObject} from '../../../modules/newspassidBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -const NEWSPASSURI = 'https://bidder.newspassid.com/openrtb2/auction'; -const BIDDER_CODE = 'newspassid'; -var validBidRequests = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, +import { deepClone } from 'src/utils.js'; +import { resolveNewpassidPublisherId } from '../../../modules/newspassidBidAdapter'; + +describe('newspassidBidAdapter', function () { + const TEST_PUBLISHER_ID = '123456'; + const TEST_PLACEMENT_ID = 'test-group1'; + + const validBidRequest = { bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsNoCustomData = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsMulti = [ - { - testId: 1, - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - }, - { - testId: 2, - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff0', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c0', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsWithUserIdData = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', - userId: { - 'pubcid': '12345678', - 'tdid': '1111tdid', - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'criteoId': '1111criteoId', - 'idl_env': 'liverampId', - 'lipb': {'lipbid': 'lipbidId123'}, - 'parrableId': {'eid': '01.5678.parrableid'}, - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }, - userIdAsEids: [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '12345678', - 'atype': 1 - } - ] - }, - { - 'source': 'adserver.org', - 'uids': [{ - 'id': '1111tdid', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }, - { - 'source': 'id5-sync.com', - 'uids': [{ - 'id': 'ID5-someId', - 'atype': 1, - }] - }, - { - 'source': 'criteoId', - 'uids': [{ - 'id': '1111criteoId', - 'atype': 1, - }] - }, - { - 'source': 'idl_env', - 'uids': [{ - 'id': 'liverampId', - 'atype': 1, - }] - }, - { - 'source': 'lipb', - 'uids': [{ - 'id': {'lipbid': 'lipbidId123'}, - 'atype': 1, - }] - }, - { - 'source': 'parrableId', - 'uids': [{ - 'id': {'eid': '01.5678.parrableid'}, - 'atype': 1, - }] - } - ] - } -]; -var validBidRequestsMinimal = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsNoSizes = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsWithBannerMediaType = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsIsThisCamelCaseEnough = [ - { - 'bidder': 'newspassid', - 'testname': 'validBidRequestsIsThisCamelCaseEnough', - 'params': { - 'publisherId': 'newspassRUP0001', - 'placementId': '8000000009', - 'siteId': '4204204201', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ], - 'userId': { - 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' - }, - 'userIdAsEids': [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', - 'atype': 1 - } - ] - } - ] - }, - mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, - 'adUnitCode': 'some-ad', - 'transactionId': '02c1ea7d-0bf2-451b-a122-1420040d1cf8', - 'bidId': '2899ec066a91ff8', - 'bidderRequestId': '1c1586b27a1b5c8', - 'auctionId': '0456c9b7-5ab2-4fec-9e10-f418d3d1f04c', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } -]; -var validBidderRequest = { - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - auctionStart: 1536838908986, - bidderCode: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - bids: [{ - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - }], - doneCbCallCount: 1, - start: 1536838908987, - timeout: 3000 -}; -var emptyObject = {}; -var validResponse = { - 'body': { - 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', - 'seatbid': [ - { - 'bid': [ - { - 'id': '677903815252395017', - 'impid': '2899ec066a91ff8', - 'price': 0.5, - 'adm': '', - 'adid': '98493581', - 'adomain': [ - 'http://prebid.org' - ], - 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', - 'cid': '9325', - 'crid': '98493581', - 'cat': [ - 'IAB3-1' - ], - 'w': 300, - 'h': 600, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 555545, - 'auction_id': 6500448734132353000, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } - ], - 'seat': 'appnexus' - } - ], - 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ - 'ext': { - 'responsetimemillis': { - 'appnexus': 47, - 'openx': 30 - } - }, - 'timing': { - 'start': 1536848078.089177, - 'end': 1536848078.142203, - 'TimeTaken': 0.05302619934082031 - } - }, - 'headers': {} -}; -var validResponse2BidsSameAdunit = { - 'body': { - 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', - 'seatbid': [ - { - 'bid': [ - { - 'id': '677903815252395017', - 'impid': '2899ec066a91ff8', - 'price': 0.5, - 'adm': '', - 'adid': '98493581', - 'adomain': [ - 'http://prebid.org' - ], - 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', - 'cid': '9325', - 'crid': '98493581', - 'cat': [ - 'IAB3-1' - ], - 'w': 300, - 'h': 600, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 555545, - 'auction_id': 6500448734132353000, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - }, - { - 'id': '677903815252395010', - 'impid': '2899ec066a91ff8', - 'price': 0.9, - 'adm': '', - 'adid': '98493580', - 'adomain': [ - 'http://prebid.org' - ], - 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', - 'cid': '9320', - 'crid': '98493580', - 'cat': [ - 'IAB3-1' - ], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 555540, - 'auction_id': 6500448734132353000, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } ], - 'seat': 'npappnexus' - } - ], - 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ - 'ext': { - 'responsetimemillis': { - 'appnexus': 47, - 'openx': 30 - } - }, - 'timing': { - 'start': 1536848078.089177, - 'end': 1536848078.142203, - 'TimeTaken': 0.05302619934082031 - } - }, - 'headers': {} -}; -var validBidResponse1adWith2Bidders = { - 'body': { - 'id': '91221f96-b931-4acc-8f05-c2a1186fa5ac', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', - 'impid': '2899ec066a91ff8', - 'price': 0.36754, - 'adm': '', - 'adid': '134928661', - 'adomain': [ - 'somecompany.com' - ], - 'iurl': 'https:\/\/ams1-ib.adnxs.com\/cr?id=134928661', - 'cid': '8825', - 'crid': '134928661', - 'cat': [ - 'IAB8-15', - 'IAB8-16', - 'IAB8-4', - 'IAB8-1', - 'IAB8-14', - 'IAB8-6', - 'IAB8-13', - 'IAB8-3', - 'IAB8-17', - 'IAB8-12', - 'IAB8-8', - 'IAB8-7', - 'IAB8-2', - 'IAB8-9', - 'IAB8', - 'IAB8-11' - ], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 14640, - 'auction_id': 1.8369641905139e+18, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } - ], - 'seat': 'appnexus' - }, - { - 'bid': [ - { - 'id': '75665207-a1ca-49db-ba0e-a5e9c7d26f32', - 'impid': '37fff511779365a', - 'price': 1.046, - 'adm': '
removed
', - 'adomain': [ - 'kx.com' - ], - 'crid': '13005', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - } - } - } - ], - 'seat': 'openx' - } - ], - 'ext': { - 'responsetimemillis': { - 'appnexus': 91, - 'openx': 109, - 'npappnexus': 46, - 'npbeeswax': 2, - 'pangaea': 91 - } - } - }, - 'headers': {} -}; -var multiRequest1 = [ - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 'uayf5jmv3', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] + params: { + publisherId: TEST_PUBLISHER_ID, + placementId: TEST_PLACEMENT_ID }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] + mediaTypes: { + banner: { + sizes: [[300, 250]] } }, - 'adUnitCode': 'mpu', - 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2d30e86db743a8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 't8nxz6qzd', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ] - } - }, - 'adUnitCode': 'leaderboard', - 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ], - 'bidId': '3025f169863b7f8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } -]; -var multiBidderRequest1 = { - bidderRequest: { - 'bidderCode': 'newspassid', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'bidderRequestId': '1d03a1dfc563fc', - 'bids': [ - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'txeh7uyo0', - 't8nxz6qzd', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'mpu', - 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2d30e86db743a8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 't8nxz6qzd', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ] - } - }, - 'adUnitCode': 'leaderboard', - 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ], - 'bidId': '3025f169863b7f8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1592918645574, - 'timeout': 3000, - 'refererInfo': { - 'referer': 'http://some.referrer.com', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://some.referrer.com' - ] + adUnitCode: 'test-div', + transactionId: '123456', + bidId: '789', + bidderRequestId: 'abc', + auctionId: 'xyz' + }; + + const validBidderRequest = { + bidderCode: 'newspassid', + auctionId: 'xyz', + bidderRequestId: 'abc', + bids: [validBidRequest], + gdprConsent: { + gdprApplies: true, + consentString: 'consent123' }, - 'start': 1592918645578 - } -}; -var multiResponse1 = { - 'body': { - 'id': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'seatbid': [ - { - 'bid': [ - { - 'id': '4419718600113204943', - 'impid': '2d30e86db743a8', - 'price': 0.2484, - 'adm': '', - 'adid': '119683582', - 'adomain': [ - 'https://someurl.com' - ], - 'iurl': 'https://ams1-ib.adnxs.com/cr?id=119683582', - 'cid': '9979', - 'crid': '119683582', - 'cat': [ - 'IAB3' - ], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {}, - 'appnexus': { - 'brand_id': 734921, - 'auction_id': 2995348111857539600, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - }, - 'cpm': 0.2484, - 'bidId': '2d30e86db743a8', - 'requestId': '2d30e86db743a8', - 'width': 300, - 'height': 250, - 'ad': '', - 'netRevenue': true, - 'creativeId': '119683582', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.2484, - 'originalCurrency': 'USD' - }, - { - 'id': '18552976939844681', - 'impid': '3025f169863b7f8', - 'price': 0.0621, - 'adm': '', - 'adid': '120179216', - 'adomain': [ - 'appnexus.com' - ], - 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', - 'cid': '9979', - 'crid': '120179216', - 'w': 970, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {}, - 'appnexus': { - 'brand_id': 1, - 'auction_id': 3449036134472542700, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - }, - 'cpm': 0.0621, - 'bidId': '3025f169863b7f8', - 'requestId': '3025f169863b7f8', - 'width': 970, - 'height': 250, - 'ad': '', - 'netRevenue': true, - 'creativeId': '120179216', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.0621, - 'originalCurrency': 'USD' - }, - { - 'id': '18552976939844999', - 'impid': '3025f169863b7f8', - 'price': 0.521, - 'adm': '', - 'adid': '120179216', - 'adomain': [ - 'appnexus.com' - ], - 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', - 'cid': '9999', - 'crid': '120179299', - 'w': 728, - 'h': 90, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {}, - 'appnexus': { - 'brand_id': 1, - 'auction_id': 3449036134472542700, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - }, - 'cpm': 0.521, - 'bidId': '3025f169863b7f8', - 'requestId': '3025f169863b7f8', - 'width': 728, - 'height': 90, - 'ad': '', - 'netRevenue': true, - 'creativeId': '120179299', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.0621, - 'originalCurrency': 'USD' - } - ], - 'seat': 'npappnexus' - }, - { - 'bid': [ - { - 'id': '1c605e8a-4992-4ec6-8a5c-f82e2938c2db', - 'impid': '2d30e86db743a8', - 'price': 0.01, - 'adm': '
', - 'crid': '540463358', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {} - } - }, - 'cpm': 0.01, - 'bidId': '2d30e86db743a8', - 'requestId': '2d30e86db743a8', - 'width': 300, - 'height': 250, - 'ad': '
', - 'netRevenue': true, - 'creativeId': '540463358', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.01, - 'originalCurrency': 'USD' - }, - { - 'id': '3edeb4f7-d91d-44e2-8aeb-4a2f6d295ce5', - 'impid': '3025f169863b7f8', - 'price': 0.01, - 'adm': '
', - 'crid': '540221061', - 'w': 970, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {} - } - }, - 'cpm': 0.01, - 'bidId': '3025f169863b7f8', - 'requestId': '3025f169863b7f8', - 'width': 970, - 'height': 250, - 'ad': '
', - 'netRevenue': true, - 'creativeId': '540221061', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.01, - 'originalCurrency': 'USD' - } - ], - 'seat': 'openx' - } - ], - 'ext': { - 'debug': {}, - 'responsetimemillis': { - 'beeswax': 6, - 'openx': 91, - 'npappnexus': 40, - 'npbeeswax': 6 - } + refererInfo: { + page: 'http://example.com' } - }, - 'headers': {} -}; -describe('newspassid Adapter', function () { - describe('isBidRequestValid', function () { - let validBidReq = { - bidder: BIDDER_CODE, - params: { - placementId: '1310000099', - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); - var validBidReq2 = { - bidder: BIDDER_CODE, - params: { - placementId: '1310000099', - publisherId: '9876abcd12-3', - siteId: '1234567890', - customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}] - }, - siteId: 1234567890 + }; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: '789', + price: 2.5, + w: 300, + h: 250, + crid: 'creative123', + adm: '
ad
', + adomain: ['advertiser.com'] + }] + }], + cur: 'USD' } - it('should return true when required params found and all optional params are valid', function () { - expect(spec.isBidRequestValid(validBidReq2)).to.equal(true); - }); - var xEmptyPlacement = { - bidder: BIDDER_CODE, - params: { - placementId: '', - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate empty placementId', function () { - expect(spec.isBidRequestValid(xEmptyPlacement)).to.equal(false); - }); - var xMissingPlacement = { - bidder: BIDDER_CODE, - params: { - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate missing placementId', function () { - expect(spec.isBidRequestValid(xMissingPlacement)).to.equal(false); - }); - var xBadPlacement = { - bidder: BIDDER_CODE, - params: { - placementId: '123X45', - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate placementId with a non-numeric value', function () { - expect(spec.isBidRequestValid(xBadPlacement)).to.equal(false); - }); - var xBadPlacementTooShort = { - bidder: BIDDER_CODE, - params: { - placementId: 123456789, /* should be exactly 10 chars */ - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate placementId with a numeric value of wrong length', function () { - expect(spec.isBidRequestValid(xBadPlacementTooShort)).to.equal(false); - }); - var xBadPlacementTooLong = { - bidder: BIDDER_CODE, - params: { - placementId: 12345678901, /* should be exactly 10 chars */ - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate placementId with a numeric value of wrong length', function () { - expect(spec.isBidRequestValid(xBadPlacementTooLong)).to.equal(false); - }); - var xMissingPublisher = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - siteId: '1234567890' - } - }; - it('should not validate missing publisherId', function () { - expect(spec.isBidRequestValid(xMissingPublisher)).to.equal(false); - }); - var xMissingSiteId = { - bidder: BIDDER_CODE, - params: { - publisherId: '9876abcd12-3', - placementId: '1234567890', - } - }; - it('should not validate missing sitetId', function () { - expect(spec.isBidRequestValid(xMissingSiteId)).to.equal(false); - }); - var xBadPublisherTooShort = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '9876abcd12a', - siteId: '1234567890' - } - }; - it('should not validate publisherId being too short', function () { - expect(spec.isBidRequestValid(xBadPublisherTooShort)).to.equal(false); - }); - var xBadPublisherTooLong = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '9876abcd12abc', - siteId: '1234567890' - } - }; - it('should not validate publisherId being too long', function () { - expect(spec.isBidRequestValid(xBadPublisherTooLong)).to.equal(false); - }); - var publisherNumericOk = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: 123456789012, - siteId: '1234567890' - } - }; - it('should validate publisherId being 12 digits', function () { - expect(spec.isBidRequestValid(publisherNumericOk)).to.equal(true); - }); - var xEmptyPublisher = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '', - siteId: '1234567890' - } - }; - it('should not validate empty publisherId', function () { - expect(spec.isBidRequestValid(xEmptyPublisher)).to.equal(false); - }); - var xBadSite = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '9876abcd12-3', - siteId: '12345Z' - } - }; - it('should not validate bad siteId', function () { - expect(spec.isBidRequestValid(xBadSite)).to.equal(false); - }); - it('should not validate siteId too long', function () { - expect(spec.isBidRequestValid(xBadSite)).to.equal(false); - }); - it('should not validate siteId too short', function () { - expect(spec.isBidRequestValid(xBadSite)).to.equal(false); - }); - var allNonStrings = { - bidder: BIDDER_CODE, - params: { - placementId: 1234567890, - publisherId: '9876abcd12-3', - siteId: 1234567890 - } - }; - it('should validate all numeric values being sent as non-string numbers', function () { - expect(spec.isBidRequestValid(allNonStrings)).to.equal(true); - }); - var emptySiteId = { - bidder: BIDDER_CODE, - params: { - placementId: 1234567890, - publisherId: '9876abcd12-3', - siteId: '' - } - }; - it('should not validate siteId being empty string (it is required now)', function () { - expect(spec.isBidRequestValid(emptySiteId)).to.equal(false); - }); - var xBadCustomData = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customData': 'this aint gonna work' - } - }; - it('should not validate customData not being an array', function () { - expect(spec.isBidRequestValid(xBadCustomData)).to.equal(false); - }); - var xBadCustomDataOldCustomdataValue = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customData': {'gender': 'bart', 'age': 'low'} - } - }; - it('should not validate customData being an object, not an array', function () { - expect(spec.isBidRequestValid(xBadCustomDataOldCustomdataValue)).to.equal(false); - }); - var xBadCustomDataZerocd = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1111111110', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customData': [] - } - }; - it('should not validate customData array having no elements', function () { - expect(spec.isBidRequestValid(xBadCustomDataZerocd)).to.equal(false); - }); - var xBadCustomDataNotargeting = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'customData': [{'settings': {}, 'xx': {'gender': 'bart', 'age': 'low'}}], - siteId: '1234567890' - } - }; - it('should not validate customData[] having no "targeting"', function () { - expect(spec.isBidRequestValid(xBadCustomDataNotargeting)).to.equal(false); - }); - var xBadCustomDataTgtNotObj = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'customData': [{'settings': {}, 'targeting': 'this should be an object'}], - siteId: '1234567890' - } - }; - it('should not validate customData[0].targeting not being an object', function () { - expect(spec.isBidRequestValid(xBadCustomDataTgtNotObj)).to.equal(false); - }); - var xBadCustomParams = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customParams': 'this key is no longer valid' - } - }; - it('should not validate customParams - this is a renamed key', function () { - expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); + }; + + describe('gvlid', function() { + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(1317); }); }); - describe('buildRequests', function () { - it('sends bid request to NEWSPASSURI via POST', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.url).to.equal(NEWSPASSURI); - expect(request.method).to.equal('POST'); - }); - it('sends data as a string', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.data).to.be.a('string'); - }); - it('sends all bid parameters', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('adds all parameters inside the ext object only', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.data).to.be.a('string'); - var data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(request).not.to.have.key('lotameData'); - expect(request).not.to.have.key('customData'); - }); - it('adds all parameters inside the ext object only - lightning', function () { - let localBidReq = JSON.parse(JSON.stringify(validBidRequests)); - const request = spec.buildRequests(localBidReq, validBidderRequest); - expect(request.data).to.be.a('string'); - var data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(request).not.to.have.key('lotameData'); - expect(request).not.to.have.key('customData'); - }); - it('has correct bidder', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); - }); - it('handles mediaTypes element correctly', function () { - const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('handles no newspassid or custom data', function () { - const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('should not crash when there is no sizes element at all', function () { - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('should be able to handle non-single requests', function () { - config.setConfig({'newspassid': {'singleRequest': false}}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - expect(request).to.be.a('array'); - expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - config.setConfig({'newspassid': {'singleRequest': true}}); - }); - it('should not have imp[N].ext.newspassid.userId', function () { - let bidderRequest = validBidderRequest; - let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'pubcid': '5555', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - let firstBid = payload.imp[0].ext.newspassid; - expect(firstBid).to.not.have.property('userId'); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests - }); - it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { - let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; - const request = spec.buildRequests(bidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests - }); - it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { - const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.user).to.exist; - expect(payload.user.ext).to.exist; - expect(payload.user.ext.eids).to.exist; - expect(payload.user.ext.eids[0]['source']).to.equal('pubcid.org'); - expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); - expect(payload.user.ext.eids[1]['source']).to.equal('adserver.org'); - expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('1111tdid'); - expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); - expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); - expect(payload.user.ext.eids[3]['source']).to.equal('criteoId'); - expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('1111criteoId'); - expect(payload.user.ext.eids[4]['source']).to.equal('idl_env'); - expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); - expect(payload.user.ext.eids[5]['source']).to.equal('lipb'); - expect(payload.user.ext.eids[5]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); - expect(payload.user.ext.eids[6]['source']).to.equal('parrableId'); - expect(payload.user.ext.eids[6]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); - }); - it('replaces the auction url for a config override', function () { - spec.propertyBag.config = null; - let fakeOrigin = 'http://sometestendpoint'; - config.setConfig({'newspassid': {'endpointOverride': {'origin': fakeOrigin}}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.url).to.equal(fakeOrigin + '/openrtb2/auction'); - expect(request.method).to.equal('POST'); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.origin).to.equal(fakeOrigin); - config.setConfig({'newspassid': {'kvpPrefix': null, 'endpointOverride': null}}); - }); - it('replaces the FULL auction url for a config override', function () { - spec.propertyBag.config = null; - let fakeurl = 'http://sometestendpoint/myfullurl'; - config.setConfig({'newspassid': {'endpointOverride': {'auctionUrl': fakeurl}}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.url).to.equal(fakeurl); - expect(request.method).to.equal('POST'); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.origin).to.equal(fakeurl); - config.setConfig({'newspassid': {'kvpPrefix': null, 'endpointOverride': null}}); - }); - it('should ignore kvpPrefix', function () { - spec.propertyBag.config = null; - config.setConfig({'newspassid': {'kvpPrefix': 'np'}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result[0].adserverTargeting).to.have.own.property('np_appnexus_crid'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_crid')).to.equal('98493581'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_adId')).to.equal('2899ec066a91ff8-0-np-0'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_size')).to.equal('300x600'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_pb_r')).to.equal('0.50'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_bid')).to.equal('true'); + + describe('resolveNewpassidPublisherId', function() { + afterEach(() => { config.resetConfig(); }); - it('should create a meta object on each bid returned', function () { - spec.propertyBag.config = null; - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result[0]).to.have.own.property('meta'); - expect(result[0].meta.advertiserDomains[0]).to.equal('http://prebid.org'); - config.resetConfig(); - }); - it('should use nptestmode GET value if set', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'nptestmode': 'mytestvalue_123'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(data.imp[0].ext.newspassid.customData[0].targeting.nptestmode).to.equal('mytestvalue_123'); - }); - it('should pass through GET params if present: npf, nppf, nprp, npip', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {npf: '1', nppf: '0', nprp: '2', npip: '123'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.npf).to.equal(1); - expect(data.ext.newspassid.nppf).to.equal(0); - expect(data.ext.newspassid.nprp).to.equal(2); - expect(data.ext.newspassid.npip).to.equal(123); - }); - it('should pass through GET params if present: npf, nppf, nprp, npip with alternative values', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {npf: 'false', nppf: 'true', nprp: 'xyz', npip: 'hello'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.npf).to.equal(0); - expect(data.ext.newspassid.nppf).to.equal(1); - expect(data.ext.newspassid).to.not.haveOwnProperty('nprp'); - expect(data.ext.newspassid).to.not.haveOwnProperty('npip'); + + it('should return null if no bidrequest object or no global publisherId set', function() { + expect(resolveNewpassidPublisherId()).to.equal(null); }); - it('should use nptestmode GET value if set, even if there is no customdata in config', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'nptestmode': 'mytestvalue_123'}; - }; - const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(data.imp[0].ext.newspassid.customData[0].targeting.nptestmode).to.equal('mytestvalue_123'); + + it('should return global publisherId if no bidrequest object and global publisherId set', function() { + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + expect(resolveNewpassidPublisherId()).to.equal(TEST_PUBLISHER_ID); }); - it('should use GET values auction=[encoded URL] & cookiesync=[encoded url] if set', function() { - spec.propertyBag.config = null; - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {}; - }; - let request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - let url = request.url; - expect(url).to.equal('https://bidder.newspassid.com/openrtb2/auction'); - let cookieUrl = specMock.getCookieSyncUrl(); - expect(cookieUrl).to.equal('https://bidder.newspassid.com/static/load-cookie.html'); - specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'auction': 'https://www.someurl.com/auction', 'cookiesync': 'https://www.someurl.com/sync'}; - }; - request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - url = request.url; - expect(url).to.equal('https://www.someurl.com/auction'); - cookieUrl = specMock.getCookieSyncUrl(); - expect(cookieUrl).to.equal('https://www.someurl.com/sync'); + }); + + describe('isBidRequestValid', function() { + it('should return true when required params are present', function() { + expect(spec.isBidRequestValid(validBidRequest)).to.be.true; + }); + + it('should return false when publisherId is missing', function() { + const bid = deepClone(validBidRequest); + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when placementId is missing', function() { + const bid = deepClone(validBidRequest); + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; }); - it('should use a valid npstoredrequest GET value if set to override the placementId values, and set np_rw if we find it', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'npstoredrequest': '1122334455'}; // 10 digits are valid + }); + + describe('buildRequests', function() { + it('should create request data', function() { + const requests = spec.buildRequests([validBidRequest], validBidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('https://npid.amspbs.com/v0/bid/request'); + expect(requests[0].options.withCredentials).to.be.true; + }); + + it('should include bidder params in ortb2 request', function() { + const requests = spec.buildRequests([validBidRequest], validBidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.newspassid.publisher).to.equal(TEST_PUBLISHER_ID); + expect(data.imp[0].ext.newspassid.placementId).to.equal(TEST_PLACEMENT_ID); + }); + + it('should use global publisherId when not set in bid params', function() { + const validBidRequestWithoutPublisherId = { + ...validBidRequest, + params: { + placementId: TEST_PLACEMENT_ID + }, }; - const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.np_rw).to.equal(1); - expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); - }); - it('should NOT use an invalid npstoredrequest GET value if set to override the placementId values, and set np_rw to 0', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'npstoredrequest': 'BADVAL'}; // 10 digits are valid + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + const requests = spec.buildRequests([validBidRequestWithoutPublisherId], validBidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.newspassid.publisher).to.equal(TEST_PUBLISHER_ID); + expect(data.imp[0].ext.newspassid.placementId).to.equal(TEST_PLACEMENT_ID); + }); + + it('should use publisherId from bidRequest first over global publisherId', function() { + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + const validBidRequestWithDifferentPublisherId = { + ...validBidRequest, + params: { + publisherId: 'publisherId123' + } }; - const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.np_rw).to.equal(0); - expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); - }); - it('should pick up the config value of coppa & set it in the request', function () { - config.setConfig({'coppa': true}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.regs).to.include.keys('coppa'); - expect(payload.regs.coppa).to.equal(1); - config.resetConfig(); - }); - it('should pick up the config value of coppa & only set it in the request if its true', function () { - config.setConfig({'coppa': false}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - const payload = JSON.parse(request.data); - expect(utils.deepAccess(payload, 'regs.coppa')).to.be.undefined; - config.resetConfig(); - }); - it('should should contain a unique page view id in the auction request which persists across calls', function () { - let request = spec.buildRequests(validBidRequests, validBidderRequest); - let payload = JSON.parse(request.data); - expect(utils.deepAccess(payload, 'ext.newspassid.pv')).to.be.a('string'); - request = spec.buildRequests(validBidRequestsIsThisCamelCaseEnough, validBidderRequest); - let payload2 = JSON.parse(request.data); - expect(utils.deepAccess(payload2, 'ext.newspassid.pv')).to.be.a('string'); - expect(utils.deepAccess(payload2, 'ext.newspassid.pv')).to.equal(utils.deepAccess(payload, 'ext.newspassid.pv')); + const requests = spec.buildRequests([validBidRequestWithDifferentPublisherId], validBidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.newspassid.publisher).to.equal('publisherId123'); + }); + + it('should handle multiple bid requests', function() { + const secondBidRequest = deepClone(validBidRequest); + secondBidRequest.bidId = '790'; + const requests = spec.buildRequests([validBidRequest, secondBidRequest], validBidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data.imp).to.have.lengthOf(2); }); - it('should indicate that the whitelist was used when it contains valid data', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_pb', 'np_appnexus_imp_id']}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.np_kvp_rw).to.equal(1); - config.resetConfig(); + }); + + describe('interpretResponse', function() { + it('should return empty array if no valid bids', function() { + const invalidResponse = {body: {}}; + const bids = spec.interpretResponse(invalidResponse); + expect(bids).to.be.empty; + }); + + it('should return empty array if no seatbid', function() { + const noSeatbidResponse = {body: {cur: 'USD'}}; + const bids = spec.interpretResponse(noSeatbidResponse); + expect(bids).to.be.empty; + }); + + it('should interpret valid server response', function() { + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + expect(bids[0]).to.deep.equal({ + requestId: '789', + cpm: 2.5, + width: 300, + height: 250, + creativeId: 'creative123', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '
ad
', + meta: { + advertiserDomains: ['advertiser.com'] + } + }); }); - it('should indicate that the whitelist was not used when it contains no data', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': []}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.np_kvp_rw).to.equal(0); + }); + + describe('getUserSyncs', function() { + afterEach(() => { config.resetConfig(); }); - it('should indicate that the whitelist was not used when it is not set in the config', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.np_kvp_rw).to.equal(0); - }); - it('should handle ortb2 site data', function () { - let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - bidderRequest.ortb2 = { - 'site': { - 'name': 'example_ortb2_name', - 'domain': 'page.example.com', - 'cat': ['IAB2'], - 'sectioncat': ['IAB2-2'], - 'pagecat': ['IAB2-2'], - 'page': 'https://page.example.com/here.html', - 'ref': 'https://ref.example.com', - 'keywords': 'power tools, drills', - 'search': 'drill' + + it('should expect correct host', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, '', {}); + const url = new URL(syncs[0].url); + expect(url.host).to.equal('npid.amspbs.com'); + }); + + it('should expect correct pathname', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, '', {}); + const url = new URL(syncs[0].url); + expect(url.pathname).to.equal('/v0/user/sync'); + }); + + it('should return empty array when iframe sync option is disabled', function() { + const syncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(syncs).to.be.empty; + }); + + it('should use iframe sync when iframe enabled', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://npid.amspbs.com/v0/user/sync?gdpr=0&gdpr_consent=&gpp=&gpp_sid=&us_privacy='); + }); + + it('should include GDPR params if purpose 1 is true', function() { + const consentString = 'CQO03QAQO03QAPoABABGBiEIAIAAAIAAAACQKSwAQKSgpLABApKAAAAA.QKSwAQKSgAAA.IAAA'; // purpose 1 true + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + purpose: { + consents: { + 1: true + } + } } }; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.imp[0].ext.newspassid.customData[0].targeting.name).to.equal('example_ortb2_name'); - expect(payload.user.ext).to.not.have.property('gender'); - }); - it('should add ortb2 site data when there is no customData already created', function () { - let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - bidderRequest.ortb2 = { - 'site': { - 'name': 'example_ortb2_name', - 'domain': 'page.example.com', - 'cat': ['IAB2'], - 'sectioncat': ['IAB2-2'], - 'pagecat': ['IAB2-2'], - 'page': 'https://page.example.com/here.html', - 'ref': 'https://ref.example.com', - 'keywords': 'power tools, drills', - 'search': 'drill' + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('1'); + expect(url.searchParams.get('gdpr_consent')).to.equal(encodeURIComponent(consentString)); + }); + + it('should disable user sync when purpose 1 is false', function() { + const consentString = 'CQO03QAQO03QAPoABABGBiEIAHAAAHAAAACQKSwAQKSgpLABApKAAAAA.QKSwAQKSgAAA.IAAA'; // purpose 1 false + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + purpose: { + consents: { + 1: false + } + } } }; - const request = spec.buildRequests(validBidRequestsNoCustomData, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.imp[0].ext.newspassid.customData[0].targeting.name).to.equal('example_ortb2_name'); - expect(payload.imp[0].ext.newspassid.customData[0].targeting).to.not.have.property('gender') - }); - it('should add ortb2 user data to the user object', function () { - let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - bidderRequest.ortb2 = { - 'user': { - 'gender': 'I identify as a box of rocks' - } + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent); + expect(syncs).to.be.empty; + }); + + it('should include correct us_privacy param', function() { + const uspConsent = '1YNN'; + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, uspConsent, {}); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('0'); + expect(url.searchParams.get('gdpr_consent')).to.equal(''); + expect(url.searchParams.get('gpp')).to.equal(''); + expect(url.searchParams.get('gpp_sid')).to.equal(''); + expect(url.searchParams.get('us_privacy')).to.equal(uspConsent); + }); + + it('should include correct GPP params', function() { + const gppConsentString = 'DBABMA~1YNN'; + const gppSections = '2,6'; + const gppConsent = { + gppApplies: true, + gppString: gppConsentString, + applicableSections: gppSections }; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.user.gender).to.equal('I identify as a box of rocks'); - }); - it('handles schain object in each bidrequest (will be the same in each br)', function () { - let br = JSON.parse(JSON.stringify(validBidRequests)); - let schainConfigObject = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'bidderA.com', - 'sid': '00001', - 'hp': 1 + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, '', gppConsent); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('0'); + expect(url.searchParams.get('gdpr_consent')).to.equal(''); + expect(url.searchParams.get('gpp')).to.equal(encodeURIComponent(gppConsentString)); + expect(url.searchParams.get('gpp_sid')).to.equal(encodeURIComponent(gppSections)); + expect(url.searchParams.get('us_privacy')).to.equal(''); + }); + + it('should include publisher param when publisherId is set in config', function() { + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + const syncs = spec.getUserSyncs({iframeEnabled: true}); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('0'); + expect(url.searchParams.get('gdpr_consent')).to.equal(''); + expect(url.searchParams.get('gpp')).to.equal(''); + expect(url.searchParams.get('gpp_sid')).to.equal(''); + expect(url.searchParams.get('us_privacy')).to.equal(''); + expect(url.searchParams.get('publisher')).to.equal(encodeURIComponent(TEST_PUBLISHER_ID)); + }); + + it('should have zero user syncs if coppa is true', function() { + config.setConfig({coppa: true}); + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.be.empty; + }); + + it('should include all params when all are present', function() { + const consentString = 'CQO03QAQO03QAPoABABGBiEIAIAAAIAAAACQKSwAQKSgpLABApKAAAAA.QKSwAQKSgAAA.IAAA'; // purpose 1 true + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + purpose: { + consents: { + 1: true + } } - ] + } }; - br[0]['schain'] = schainConfigObject; - const request = spec.buildRequests(br, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.source.ext).to.haveOwnProperty('schain'); - expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` - }); - }); - describe('interpretResponse', function () { - it('should build bid array', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result.length).to.equal(1); - }); - it('should have all relevant fields', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - const bid = result[0]; - expect(bid.cpm).to.equal(validResponse.body.seatbid[0].bid[0].cpm); - expect(bid.width).to.equal(validResponse.body.seatbid[0].bid[0].width); - expect(bid.height).to.equal(validResponse.body.seatbid[0].bid[0].height); - }); - it('should build bid array with usp/CCPA', function () { - let validBR = JSON.parse(JSON.stringify(validBidderRequest)); - validBR.uspConsent = '1YNY'; - const request = spec.buildRequests(validBidRequests, validBR); - const payload = JSON.parse(request.data); - expect(payload.user.ext.uspConsent).not.to.exist; - expect(payload.regs.ext.us_privacy).to.equal('1YNY'); - }); - it('should fail ok if no seatbid in server response', function () { - const result = spec.interpretResponse({}, {}); - expect(result).to.be.an('array'); - expect(result).to.be.empty; - }); - it('should fail ok if seatbid is not an array', function () { - const result = spec.interpretResponse({'body': {'seatbid': 'nothing_here'}}, {}); - expect(result).to.be.an('array'); - expect(result).to.be.empty; - }); - it('should correctly parse response where there are more bidders than ad slots', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validBidResponse1adWith2Bidders, request); - expect(result.length).to.equal(2); - }); - it('should have a ttl of 600', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result[0].ttl).to.equal(300); - }); - it('should handle a valid whitelist, removing items not on the list & leaving others', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_crid', 'np_appnexus_adId']}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adId')).to.equal('2899ec066a91ff8-0-np-0'); - config.resetConfig(); - }); - it('should ignore a whitelist if enhancedAdserverTargeting is false', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_crid', 'np_appnexus_imp_id'], 'enhancedAdserverTargeting': false}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_imp_id')).to.be.undefined; - config.resetConfig(); - }); - it('should correctly handle enhancedAdserverTargeting being false', function () { - config.setConfig({'newspassid': {'enhancedAdserverTargeting': false}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_imp_id')).to.be.undefined; - config.resetConfig(); - }); - it('should add unique adId values to each bid', function() { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - let validres = JSON.parse(JSON.stringify(validResponse2BidsSameAdunit)); - const result = spec.interpretResponse(validres, request); - expect(result.length).to.equal(1); - expect(result[0]['price']).to.equal(0.9); - expect(result[0]['adserverTargeting']['np_npappnexus_adId']).to.equal('2899ec066a91ff8-0-np-1'); - }); - it('should add np_auc_id (response id value)', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - let validres = JSON.parse(JSON.stringify(validBidResponse1adWith2Bidders)); - const result = spec.interpretResponse(validres, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_auc_id')).to.equal(validBidResponse1adWith2Bidders.body.id); - }); - it('should correctly process an auction with 2 adunits & multiple bidders one of which bids for both adslots', function() { - let validres = JSON.parse(JSON.stringify(multiResponse1)); - let request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); - let result = spec.interpretResponse(validres, request); - expect(result.length).to.equal(4); // one of the 5 bids will have been removed - expect(result[1]['price']).to.equal(0.521); - expect(result[1]['impid']).to.equal('3025f169863b7f8'); - expect(result[1]['id']).to.equal('18552976939844999'); - expect(result[1]['adserverTargeting']['np_npappnexus_adId']).to.equal('3025f169863b7f8-0-np-2'); - validres = JSON.parse(JSON.stringify(multiResponse1)); - validres.body.seatbid[0].bid[1].price = 1.1; - validres.body.seatbid[0].bid[1].cpm = 1.1; - request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); - result = spec.interpretResponse(validres, request); - expect(result[1]['price']).to.equal(1.1); - expect(result[1]['impid']).to.equal('3025f169863b7f8'); - expect(result[1]['id']).to.equal('18552976939844681'); - expect(result[1]['adserverTargeting']['np_npappnexus_adId']).to.equal('3025f169863b7f8-0-np-1'); - }); - }); - describe('userSyncs', function () { - it('should fail gracefully if no server response', function () { - const result = spec.getUserSyncs('bad', false, emptyObject); - expect(result).to.be.empty; - }); - it('should fail gracefully if server response is empty', function () { - const result = spec.getUserSyncs('bad', [], emptyObject); - expect(result).to.be.empty; - }); - it('should append the various values if they exist', function() { - spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject); - expect(result).to.be.an('array'); - expect(result[0].url).to.include('publisherId=9876abcd12-3'); - expect(result[0].url).to.include('siteId=1234567890'); - }); - it('should append ccpa (usp data)', function() { - spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject, '1YYN'); - expect(result).to.be.an('array'); - expect(result[0].url).to.include('usp_consent=1YYN'); - }); - it('should use "" if no usp is sent to cookieSync', function() { - spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject); - expect(result).to.be.an('array'); - expect(result[0].url).to.include('usp_consent=&'); - }); - }); - describe('default size', function () { - it('should should return default sizes if no obj is sent', function () { - let obj = ''; - const result = defaultSize(obj); - expect(result.defaultHeight).to.equal(250); - expect(result.defaultWidth).to.equal(300); - }); - }); - describe('getGranularityKeyName', function() { - it('should return a string granularity as-is', function() { - const result = getGranularityKeyName('', 'this is it', ''); - expect(result).to.equal('this is it'); - }); - it('should return "custom" for a mediaTypeGranularity object', function() { - const result = getGranularityKeyName('', {}, ''); - expect(result).to.equal('custom'); - }); - it('should return "custom" for a mediaTypeGranularity object', function() { - const result = getGranularityKeyName('', false, 'string buckets'); - expect(result).to.equal('string buckets'); - }); - }); - describe('getGranularityObject', function() { - it('should return an object as-is', function() { - const result = getGranularityObject('', {'name': 'mark'}, '', ''); - expect(result.name).to.equal('mark'); - }); - it('should return an object as-is', function() { - const result = getGranularityObject('', false, 'custom', {'name': 'rupert'}); - expect(result.name).to.equal('rupert'); - }); - }); - describe('blockTheRequest', function() { - it('should return true if np_request is false', function() { - config.setConfig({'newspassid': {'np_request': false}}); - let result = spec.blockTheRequest(); - expect(result).to.be.true; - config.resetConfig(); - }); - it('should return false if np_request is true', function() { - config.setConfig({'newspassid': {'np_request': true}}); - let result = spec.blockTheRequest(); - expect(result).to.be.false; - config.resetConfig(); - }); - }); - describe('getPageId', function() { - it('should return the same Page ID for multiple calls', function () { - let result = spec.getPageId(); - expect(result).to.be.a('string'); - let result2 = spec.getPageId(); - expect(result2).to.equal(result); - }); - }); - describe('getBidRequestForBidId', function() { - it('should locate a bid inside a bid array', function () { - let result = spec.getBidRequestForBidId('2899ec066a91ff8', validBidRequestsMulti); - expect(result.testId).to.equal(1); - result = spec.getBidRequestForBidId('2899ec066a91ff0', validBidRequestsMulti); - expect(result.testId).to.equal(2); - }); - }); - describe('removeSingleBidderMultipleBids', function() { - it('should remove the multi bid by npappnexus for adslot 2d30e86db743a8', function() { - let validres = JSON.parse(JSON.stringify(multiResponse1)); - expect(validres.body.seatbid[0].bid.length).to.equal(3); - expect(validres.body.seatbid[0].seat).to.equal('npappnexus'); - let response = spec.removeSingleBidderMultipleBids(validres.body.seatbid); - expect(response.length).to.equal(2); - expect(response[0].bid.length).to.equal(2); - expect(response[0].seat).to.equal('npappnexus'); - expect(response[1].bid.length).to.equal(2); + const uspConsent = '1YNN'; + const gppConsentString = 'DBABMA~1YNN'; + const gppSections = '2,6'; + const gppConsent = { + gppApplies: true, + gppString: gppConsentString, + applicableSections: gppSections + }; + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent, uspConsent, gppConsent); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('1'); + expect(url.searchParams.get('gdpr_consent')).to.equal(encodeURIComponent(consentString)); + expect(url.searchParams.get('gpp')).to.equal(encodeURIComponent(gppConsentString)); + expect(url.searchParams.get('gpp_sid')).to.equal(encodeURIComponent(gppSections)); + expect(url.searchParams.get('us_privacy')).to.equal(encodeURIComponent(uspConsent)); + expect(url.searchParams.get('publisher')).to.equal(encodeURIComponent(TEST_PUBLISHER_ID)); }); }); }); From 03585a8fba989847695dbc45eea56f74746e7a26 Mon Sep 17 00:00:00 2001 From: Ben Boonsiri Date: Wed, 2 Apr 2025 12:17:02 -0400 Subject: [PATCH 1052/1097] StackAdapt Bid Adapter: initial release (#12896) * inital stackadapt bidder adapter * review - text/plain, floors module, endpoint * review - redundant checks --- modules/stackadaptBidAdapter.js | 200 +++ modules/stackadaptBidAdapter.md | 69 + .../spec/modules/stackadaptBidAdapter_spec.js | 1380 +++++++++++++++++ 3 files changed, 1649 insertions(+) create mode 100644 modules/stackadaptBidAdapter.js create mode 100644 modules/stackadaptBidAdapter.md create mode 100644 test/spec/modules/stackadaptBidAdapter_spec.js diff --git a/modules/stackadaptBidAdapter.js b/modules/stackadaptBidAdapter.js new file mode 100644 index 00000000000..f6989b24fb3 --- /dev/null +++ b/modules/stackadaptBidAdapter.js @@ -0,0 +1,200 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { deepSetValue, logWarn, parseSizesInput, isNumber, isInteger, replaceAuctionPrice, formatQS, isFn, isPlainObject } from '../src/utils.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; + +const BIDDER_CODE = 'stackadapt'; +const ENDPOINT_URL = 'https://pjs.srv.stackadapt.com/br'; +const USER_SYNC_ENDPOINT = 'https://sync.srv.stackadapt.com/sync?nid=pjs'; +const CURRENCY = 'USD'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: CURRENCY, + }, + + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + request.id = bidderRequest.bidderRequestId + + deepSetValue(request, 'site.publisher.id', bid.params.publisherId); + deepSetValue(request, 'test', bid.params.testMode); + + return request; + }, + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + + if (bidRequest.params.placementId) { + deepSetValue(imp, 'tagid', bidRequest.params.placementId); + } + if (bidRequest.params.banner?.expdir) { + deepSetValue(imp, 'banner.expdir', bidRequest.params.banner.expdir); + } + + const bidfloor = getBidFloor(bidRequest); + if (bidfloor) { + imp.bidfloor = parseFloat(bidfloor); + imp.bidfloorcur = CURRENCY; + } + + if (!isNumber(imp.secure)) { + imp.secure = 1 + } + + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + const requestMediaTypes = Object.keys(bidRequest.mediaTypes); + + if (requestMediaTypes.length === 1) { + context.mediaType = requestMediaTypes[0]; + } else { + if (bid.adm?.search(/^(<\?xml| { + const defaultBidRequest = { + 'bidderRequestId': '2856b3d7c2c8e93e', + 'bidder': 'stackadapt', + 'params': { + 'publisherId': 473298, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [336, 280], + [320, 100] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'aa837ec1-ba90-3821-jduq-1cc083921a9a', + 'sizes': [ + [336, 280], + [320, 100] + ], + 'bidId': '001', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'ortb2': {} + }; + + const ortbResponse = { + 'body': { + 'id': '2856b3d7c2c8e93e', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'impid': '001', + 'price': 6.97, + 'adid': '5739901', + 'adm': '', + 'adomain': ['mobility.com'], + 'crid': '5739901', + 'w': 336, + 'h': 280, + } + ], + 'seat': 'StackAdapt' + } + ], + 'cur': 'USD' + }, + 'headers': {} + } + + it('should return empty', () => { + const req = spec.buildRequests([defaultBidRequest], { + bids: [defaultBidRequest] + }) + const result = spec.interpretResponse(null, { + data: req.data + }) + + expect(result.length).to.eq(0); + }); + + it('should set mediaType from bid request mediaTypes', () => { + const req = spec.buildRequests([defaultBidRequest], { + id: '832j6c82-893j-21j9-8392-4wd9d82pl739', + bidderRequestId: '2856b3d7c2c8e93e', + bids: [defaultBidRequest] + }) + const result = spec.interpretResponse(ortbResponse, { + data: req.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + + it('should set mediaType from present video adm', () => { + const bidRequest = mergeDeep(defaultBidRequest, { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(ortbResponse); + const ortbReq = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + deepSetValue(bannerResponse, 'body.seatbid.0.bid.0.adm', ''); + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('video') + }); + + it('should set mediaType from missing adm', () => { + const bidRequest = mergeDeep(defaultBidRequest, { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const ortbReq = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + const result = spec.interpretResponse(ortbResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + }) + + describe('interpretResponse() empty', function () { + it('should handle empty response', function () { + let result = spec.interpretResponse({}); + expect(result.length).to.equal(0); + }); + + it('should handle empty seatbid response', function () { + let response = { + body: { + 'id': '9p1a65c0oc85a62', + 'seatbid': [] + } + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('interpretResponse() single-display - complete', function () { + const ortbResponse = { + body: { + 'id': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'bidid': '173283728930905039521896', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'impid': '5', + 'crid': '1609382', + 'price': 6.97, + 'adm': '', + 'cat': [ + 'IAB1', + 'IAB2' + ], + 'h': 50, + 'w': 320, + 'dealid': '189321890321', + 'adomain': ['mobility.com'], + 'ext': { + 'creative_id': '8493266', + 'bid_type': 'cpm', + 'crtype': 'display' + } + } + ], + 'seat': 'StackAdapt' + } + ], + 'cur': 'USD', + } + }; + + const bidderRequest = { + 'id': '832j6c82-893j-21j9-8392-4wd9d82pl739', + 'bidder': 'stackadapt', + 'params': { + 'publisherId': 473298, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 320, + 50 + ] + ] + } + }, + 'sizes': [ + [ + 320, + 50 + ] + ], + 'bidId': '5', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'ortb2': {} + }; + + const expectedBid = { + 'requestId': '5', + 'seatBidId': '1', + 'cpm': 6.97, + 'width': 320, + 'height': 50, + 'creativeId': '1609382', + 'creative_id': '1609382', + 'dealId': '189321890321', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['mobility.com'], + 'primaryCatId': 'IAB1', + 'secondaryCatIds': [ + 'IAB2' + ] + } + }; + + it('should match bid response', function () { + const ortbRequest = spec.buildRequests([bidderRequest], { + bids: [bidderRequest] + }) + + let result = spec.interpretResponse(ortbResponse, {data: ortbRequest.data}); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + }); + + describe('interpretResponse() multi-display - complete', function () { + const ortbResponse = { + 'body': { + 'id': 'r4r90kj7-2816-392j-1d41-31y998t21d2d', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'impid': '001', + 'price': 3.50, + 'adm': '', + 'cid': '4521903', + 'crid': '6254972', + 'adomain': [ + 'test.com' + ], + 'dealid': '122781928112', + 'w': 320, + 'h': 50, + 'cat': [], + }, + { + 'id': '2', + 'impid': '002', + 'price': 4.75, + 'adm': '', + 'cid': '8472189', + 'crid': '8593271', + 'adomain': [ + 'test.com' + ], + 'dealid': '849328172299', + 'w': 300, + 'h': 250, + 'cat': [], + } + ], + 'seat': 'StackAdapt' + } + ], + 'cur': 'USD' + } + }; + + const bidderRequest1 = { + 'id': '11dd91ds-197k-23e1-9950-q79s37aq0a42', + 'bidder': 'stackadapt', + 'params': { + 'publisherId': 473298, + 'placementId': 'placement1' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 320, + 50 + ] + ] + } + }, + 'sizes': [ + [ + 320, + 50 + ] + ], + 'bidId': '001', + 'bidderRequestId': 'r4r90kj7-2816-392j-1d41-31y998t21d2d', + 'auctionId': '7483329d-22il-2hyu-1d78-1098qw89457l', + 'ortb2': {} + }; + + const bidderRequest2 = { + 'id': '11dd91ds-197k-23e1-9950-q79s37aq0a43', + 'bidder': 'stackadapt', + 'params': { + 'publisherId': 473298, + 'placementId': 'placement2' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 728, + 90 + ] + ] + } + }, + 'sizes': [ + [ + 728, + 90 + ] + ], + 'bidId': '002', + 'bidderRequestId': 'r4r90kj7-2816-392j-1d41-31y998t21d2d', + 'auctionId': '7483329d-22il-2hyu-1d78-1098qw89457l', + 'ortb2': {} + }; + + const expectedBids = [ + { + 'requestId': '001', + 'seatBidId': '1', + 'cpm': 3.5, + 'width': 320, + 'height': 50, + 'creativeId': '6254972', + 'creative_id': '6254972', + 'currency': 'USD', + 'dealId': '122781928112', + 'netRevenue': true, + 'ttl': 300, + 'ad': '', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['test.com'], + 'primaryCatId': undefined, + 'secondaryCatIds': [] + } + }, + { + 'requestId': '002', + 'seatBidId': '2', + 'cpm': 4.75, + 'width': 300, + 'height': 250, + 'creativeId': '8593271', + 'creative_id': '8593271', + 'currency': 'USD', + 'dealId': '849328172299', + 'netRevenue': true, + 'ttl': 300, + 'ad': '', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': ['test.com'], + 'primaryCatId': undefined, + 'secondaryCatIds': [] + } + } + ]; + + it('should match bid response', function () { + const ortbRequest = spec.buildRequests([bidderRequest1, bidderRequest2], { + bids: [bidderRequest1, bidderRequest2] + }) + let result = spec.interpretResponse(ortbResponse, {data: ortbRequest.data}); + expect(result.length).to.equal(2); + expect(result).to.deep.equal(expectedBids); + }); + }); + + if (FEATURES.VIDEO) { + describe('interpretResponse() single-video - complete', function () { + const ortbResponse = { + 'body': { + 'id': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'bidid': '173283728930905039521879', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'crid': '6254972', + 'ext': { + 'creative_id': '1762289', + 'bid_type': 'cpm', + 'duration': 30, + }, + 'adm': '', + 'h': 480, + 'impid': '001', + 'id': '1', + 'price': 11.5, + 'w': 600 + } + ], + 'seat': 'StackAdapt' + } + ] + }, + 'headers': {} + }; + + const bidderRequest = { + 'id': '748a3c21-908a-25j9-4301-2ca9d11al199', + 'bidder': 'stackadapt', + 'params': { + 'publisherId': 473298, + }, + 'mediaTypes': { + 'video': {} + }, + 'bidId': '001', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'ortb2': {} + }; + + const expectedBid = { + 'requestId': '001', + 'seatBidId': '1', + 'cpm': 11.5, + 'creativeId': '6254972', + 'creative_id': '6254972', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'width': 600, + 'height': 480, + 'mediaType': 'video', + 'vastXml': '', + 'meta': {} + }; + + it('should match bid response with adm', function () { + const ortbRequest = spec.buildRequests([bidderRequest], { + bids: [bidderRequest] + }) + + let result = spec.interpretResponse(ortbResponse, {data: ortbRequest.data}); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(expectedBid); + }); + }); + } + + describe('isBidRequestValid()', function() { + const bannerBidderRequest = { + 'bidder': 'stackadapt', + 'params': { + 'publisherId': '11111', + 'placementId': '1' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [200, 50] + ] + } + }, + 'bidId': '001', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + }; + + describe('basic tests', function () { + it('should be valid with required bid.params', function () { + expect(spec.isBidRequestValid(bannerBidderRequest)).to.equal(true); + }); + + it('should be invalid when missing publisherId param', function () { + const bidderRequest = deepClone(bannerBidderRequest); + delete bidderRequest.params.publisherId; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid if bid request is not mediaTypes.banner or mediaTypes.video', function () { + const bidderRequest = deepClone(bannerBidderRequest); + delete bidderRequest.mediaTypes + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid if bidfloor is incorrect type', function () { + const bidderRequest = deepClone(bannerBidderRequest); + bidderRequest.params.bidfloor = 'invalid bidfloor'; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be valid if bidfloor param is a float', function () { + const bidderRequest = deepClone(bannerBidderRequest); + bidderRequest.params.bidfloor = 3.01; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(true); + }); + }); + + describe('banner tests', function () { + it('should be invalid if banner sizes is wrong format', function () { + const bidderRequest = deepClone(bannerBidderRequest); + bidderRequest.mediaTypes.banner.sizes = 'invalid'; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid if missing banner sizes', function () { + const bidderRequest = deepClone(bannerBidderRequest); + delete bidderRequest.mediaTypes.banner.sizes; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid when passed valid banner.pos', function () { + const bidderRequest = deepClone(bannerBidderRequest); + bidderRequest.mediaTypes.banner.pos = 1; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(true); + }); + }); + + if (FEATURES.VIDEO) { + describe('video tests', function () { + const videoBidderRequest = { + 'bidder': 'stackadapt', + 'params': { + 'publisherId': '11111', + 'placementId': '1' + }, + 'mediaTypes': { + 'video': { + 'maxduration': 120, + 'api': [2, 7], + 'mimes': [ + 'video/mp4', + 'application/javascript', + 'video/webm' + ], + 'protocols': [2, 3, 5, 6, 7, 8], + 'plcmt': 1, + } + }, + 'sizes': [ + [200, 50] + ], + 'bidId': '001', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + }; + + it('should be valid with required bid.params', function () { + const bidderRequest = deepClone(videoBidderRequest); + expect(spec.isBidRequestValid(bidderRequest)).to.equal(true); + }); + + it('should be invalid if missing bid.mediaTypes.video.maxduration', function () { + const bidderRequest = deepClone(videoBidderRequest); + delete bidderRequest.mediaTypes.video.maxduration; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid if missing bid.mediaTypes.video.api', function () { + const bidderRequest = deepClone(videoBidderRequest); + delete bidderRequest.mediaTypes.video.api; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid if missing bid.mediaTypes.video.mimes', function () { + const bidderRequest = deepClone(videoBidderRequest); + delete bidderRequest.mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + + it('should be invalid if missing bid.mediaTypes.video.protocols', function () { + const bidderRequest = deepClone(videoBidderRequest); + delete bidderRequest.mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bidderRequest)).to.equal(false); + }); + }); + } + }); + + describe('buildRequests() banner', function () { + const bidRequests = [{ + 'bidder': 'stackadapt', + 'params': { + 'publisherId': '11111', + 'placementId': '1', + 'bidfloor': 1.01 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[336, 280], [320, 100]] + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '2121283921', + } + }, + 'sizes': [[336, 280], [320, 100]], + 'bidId': '001', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'aa837ec1-ba90-3821-jduq-1cc083921a9a', + 'src': 'client', + 'bidRequestsCount': 10 + }]; + + const bidderRequest = { + 'bidderCode': 'stackadapt', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + ortb2: { + source: { + tid: '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + }, + site: { + domain: 'tech.stacktest.com', + publisher: { + domain: 'stacktest.com' + }, + page: 'https://tech.stacktest.com/', + ref: 'https://www.google.com/' + } + }, + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionStart': 1731042158610, + 'timeout': 1750, + 'refererInfo': { + 'reachedTop': true, + 'numIframes': 0, + 'isAmp': false, + 'page': 'https://www.mobile.com/test', + 'domain': 'www.mobile.com', + 'ref': 'https://testsite.com/', + }, + 'start': 1731042158587 + }; + + bidderRequest.bids = bidRequests; + + it('should have correct request components', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(ortbRequest.method).to.equal('POST'); + expect(ortbRequest.url).to.be.not.empty; + expect(ortbRequest.data).to.be.not.null; + }); + + it('should set ortb request.id to bidderRequestId', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.id).to.equal('5ce18294-9682-4ad0-1c92-0ab12bg8dc5e'); + }); + + it('should set impression id from bidId', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].id).to.equal('001'); + }); + + it('should set correct endpoint', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(ortbRequest.url).to.equal('https://pjs.srv.stackadapt.com/br'); + }); + + it('should set correct publisherId', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.site?.publisher?.id).to.equal(bidRequests[0].params.publisherId); + }); + + it('should set placementId in tagid', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].tagid).to.equal(bidRequests[0].params.placementId); + }); + + it('should set bidfloor if param set', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].bidfloor).to.equal(bidRequests[0].params.bidfloor); + }); + + it('should set gpid in ortb ext.gpid if present', function () { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + const gpid = 'site-desktop-homepage-banner-top'; + clonedBidRequests[0].ortb2Imp = { + ext: { + gpid: gpid + } + }; + clonedBidderRequest.bids = clonedBidRequests; + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + + expect(ortbRequest.imp[0].ext).to.be.not.null; + expect(ortbRequest.imp[0].ext.gpid).to.equal(gpid); + }); + + it('should set rwdd in imp.rwdd if present', function () { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + const rwdd = 1; + clonedBidRequests[0].ortb2Imp = { + rwdd: rwdd, + }; + clonedBidderRequest.bids = clonedBidRequests; + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + + expect(ortbRequest.imp[0].rwdd).to.be.not.null; + expect(ortbRequest.imp[0].rwdd).to.equal(1); + }); + + it('should set source.tid', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.source?.tid).to.equal(bidderRequest.ortb2.source.tid); + }); + + it('should set ad sizes in the ortb request', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(336); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(280); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(320); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(100); + }); + + it('should set referer in the bid request. ortb object takes precedence', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.site.page).to.equal('https://tech.stacktest.com/'); + }); + + it('should set the banner pos if sent', function () { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + clonedBidRequests[0].mediaTypes.banner.pos = 1; + clonedBidderRequest.bids = clonedBidRequests; + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + + expect(ortbRequest.imp[0].banner.pos).to.equal(1); + }); + + it('should set the banner expansion direction if param set', function () { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + const expdir = [1, 3] + clonedBidRequests[0].params.banner = { + expdir: expdir + }; + + clonedBidderRequest.bids = clonedBidRequests; + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + + expect(ortbRequest.imp[0].banner.expdir).to.equal(expdir); + }); + + it('should set first party site data after merge', function () { + const ortb2 = { + site: { + publisher: { + domain: 'https://publisher.com', + } + } + }; + const bidderRequestWithoutRefererDomain = { + ...bidderRequest, + refererInfo: { + ...bidRequests.referer, + domain: null + } + } + + const ortbRequest = spec.buildRequests(bidRequests, {...bidderRequestWithoutRefererDomain, ortb2}).data; + expect(ortbRequest.site.publisher).to.deep.equal({domain: 'https://publisher.com', id: '11111'}); + }); + + it('should set first party side data publisher domain taking precedence over referer domain', function () { + const ortb2 = { + site: { + domain: 'https://publisher.com', + } + }; + const ortbRequest = spec.buildRequests(bidRequests, {...bidderRequest, ortb2}).data; + expect(ortbRequest.site.domain).to.equal('https://publisher.com'); + }); + + it('should set bcat if present', function () { + const ortb2 = { + bcat: ['IAB1', 'IAB2'] + }; + const ortbRequest = spec.buildRequests(bidRequests, {...bidderRequest, ortb2}).data; + expect(ortbRequest.bcat).to.deep.equal(['IAB1', 'IAB2']); + }); + + it('should set badv if present', function () { + const ortb2 = { + badv: ['chargers.com', 'house.com'] + }; + const ortbRequest = spec.buildRequests(bidRequests, {...bidderRequest, ortb2}).data; + expect(ortbRequest.badv).to.deep.equal(['chargers.com', 'house.com']); + }); + + it('should set battr if present', function () { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + const battr = [1, 2, 3]; + clonedBidRequests[0].ortb2Imp = { + banner: { + battr: battr + } + }; + clonedBidderRequest.bids = clonedBidRequests; + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + + expect(ortbRequest.imp[0].banner.battr).to.deep.equal(battr); + }); + + it('should set ortb2 gdpr consent info', function () { + const consentString = 'CQGRvoAQGRvoAAHABAENBKFsAP_gAEPgAAAAKhNV'; + const ortb2 = { + user: { + ext: { + consent: consentString + } + }, + regs: { + ext: { + gdpr: 1 + } + } + }; + let clonedBidderRequest = {...deepClone(bidderRequest), ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, clonedBidderRequest).data; + expect(ortbRequest.user.ext.consent).to.equal(consentString); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + }); + + it('should set ortb2 usp consent info', function () { + const consentString = 'CQGRvoAQGRvoAAHABAENBKFsAP_gAEPgAAAAKhNV'; + const ortb2 = { + regs: { + ext: { + us_privacy: consentString + } + } + }; + let clonedBidderRequest = {...deepClone(bidderRequest), ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, clonedBidderRequest).data; + expect(ortbRequest.regs.ext.us_privacy).to.equal(consentString); + }); + + it('should set ortb2 coppa consent info', function () { + const ortb2 = { + regs: { + coppa: 1 + } + }; + let clonedBidderRequest = {...deepClone(bidderRequest), ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, clonedBidderRequest).data; + expect(ortbRequest.regs.coppa).to.equal(1); + }); + + it('should set ortb2 gpp consent info', function () { + const ortb2 = { + regs: { + gpp: 'DCACTA~1YAA', + gpp_sid: [9] + } + }; + let clonedBidderRequest = {...deepClone(bidderRequest), ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, clonedBidderRequest).data; + expect(ortbRequest.regs.gpp).to.equal('DCACTA~1YAA'); + expect(ortbRequest.regs.gpp_sid).to.eql([9]); + }); + + it('should set schain info', function () { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + const schain = { + 'nodes': [{ + 'asi': 'adtech.com', + 'sid': '1078492', + 'hp': 1 + }, { + 'asi': 'google.com', + 'sid': 'pub-315292981', + 'hp': 1 + }], + 'complete': 1, + 'ver': '1.0' + }; + + clonedBidRequests[0].schain = schain; + clonedBidderRequest.bids = clonedBidRequests; + + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + expect(ortbRequest.source.ext.schain).to.deep.equal(schain); + }); + + it('should set first party site data', function () { + const ortb2 = { + site: { + id: '144da00b-8309-4b2e-9482-4b3829c0b54a', + name: 'game', + domain: 'game.wiki.com', + cat: ['IAB1'], + sectioncat: ['IAB1-1'], + pagecat: ['IAB1-1'], + page: 'https://game.wiki.com/craft', + ref: 'https://www.google.com/', + keywords: 'device={}' + } + }; + const mergedBidderRequest = {...bidderRequest, ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, mergedBidderRequest).data; + expect(ortbRequest.site.id).to.equal('144da00b-8309-4b2e-9482-4b3829c0b54a'); + expect(ortbRequest.site.name).to.equal('game'); + expect(ortbRequest.site.domain).to.equal('game.wiki.com'); + expect(ortbRequest.site.cat[0]).to.equal('IAB1'); + expect(ortbRequest.site.sectioncat[0]).to.equal('IAB1-1'); + expect(ortbRequest.site.pagecat[0]).to.equal('IAB1-1'); + expect(ortbRequest.site.page).to.equal('https://game.wiki.com/craft'); + expect(ortbRequest.site.ref).to.equal('https://www.google.com/'); + expect(ortbRequest.site.keywords).to.equal('device={}'); + }); + + it('should set from floor module if no bidfloor is sent', function () { + const clonedBidderRequests = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + delete clonedBidRequests[0].params.bidfloor; + const bidfloor = 1.00 + clonedBidRequests[0].getFloor = () => { + return { currency: 'USD', floor: 1.00 }; + }; + clonedBidderRequests.bids = clonedBidRequests; + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequests).data; + expect(ortbRequest.imp[0].bidfloor).to.equal(bidfloor); + }); + + it('should set default secure value if not present', function () { + const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].secure).to.equal(1); + }); + + it('should set secure to request when present', function () { + const clonedBidderReqest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + clonedBidRequests[0].ortb2Imp.secure = 0; + clonedBidderReqest.bids = clonedBidRequests; + + let ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderReqest).data; + expect(0).to.equal(ortbRequest.imp[0].secure); + + clonedBidRequests[0].ortb2Imp.secure = 1; + ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderReqest).data; + expect(1).to.equal(ortbRequest.imp[0].secure); + }); + + const extFirstPartyData = { + data: { + firstPartyKey: 'firstPartyValue', + firstPartyKey2: ['value', 'value2'] + }, + custom: 'custom_data', + custom_kvp: { + customKey: 'customValue' + } + } + + function validateExtFirstPartyData(ext) { + expect(ext.data.firstPartyKey).to.equal('firstPartyValue'); + expect(ext.data.firstPartyKey2).to.eql(['value', 'value2']); + expect(ext.custom).to.equal('custom_data'); + expect(ext.custom_kvp.customKey).to.equal('customValue'); + } + + it('should set site first party data', function() { + const ortb2 = { + site: { + ext: extFirstPartyData, + search: 'test search' + } + }; + + const bidderRequestMerged = {...bidderRequest, ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, bidderRequestMerged).data; + + validateExtFirstPartyData(ortbRequest.site.ext) + expect(ortbRequest.site.search).to.equal('test search') + }); + + it('should set user first party data', function() { + const ortb2 = { + user: { + ext: extFirstPartyData, + yob: 1998 + } + }; + + const bidderRequestMerged = {...bidderRequest, ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, bidderRequestMerged).data; + + validateExtFirstPartyData(ortbRequest.user.ext) + expect(ortbRequest.user.yob).to.equal(1998) + }); + + it('should set imp first party data', function() { + const clonedBidderRequest = deepClone(bidderRequest); + const clonedBidRequests = deepClone(bidRequests); + const metric = { type: 'viewability', value: 0.8 }; + clonedBidRequests[0].ortb2Imp = { + ext: extFirstPartyData, + metric: [metric], + clickbrowser: 1 + }; + clonedBidderRequest.bids = clonedBidRequests; + + const ortbRequest = spec.buildRequests(clonedBidRequests, clonedBidderRequest).data; + + validateExtFirstPartyData(ortbRequest.imp[0].ext) + expect(ortbRequest.imp[0].tagid).to.equal('1'); + expect(ortbRequest.imp[0].metric[0]).to.deep.equal(metric); + expect(ortbRequest.imp[0].clickbrowser).to.equal(1) + }); + + it('should set app first party data', function() { + const ortb2 = { + app: { + ext: extFirstPartyData, + ver: 'v1.0' + } + }; + + const bidderRequestMerged = {...bidderRequest, ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, bidderRequestMerged).data; + + validateExtFirstPartyData(ortbRequest.app.ext) + expect(ortbRequest.app.ver).to.equal('v1.0') + }); + + it('should set device first party data', function() { + const ortb2 = { + device: { + ext: extFirstPartyData, + os: 'ios' + } + }; + + const bidderRequestMerged = {...bidderRequest, ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, bidderRequestMerged).data; + + validateExtFirstPartyData(ortbRequest.device.ext) + expect(ortbRequest.device.os).to.equal('ios') + }); + + it('should set pmp first party data', function() { + const ortb2 = { + pmp: { + ext: extFirstPartyData, + private_auction: 1 + } + }; + + let bidderRequestMerged = {...bidderRequest, ortb2}; + const ortbRequest = spec.buildRequests(bidRequests, bidderRequestMerged).data; + + validateExtFirstPartyData(ortbRequest.pmp.ext) + expect(ortbRequest.pmp.private_auction).to.equal(1) + }); + }); + + describe('buildRequests() banner-multiple', function () { + const multiBidRequests = [{ + 'bidder': 'stackadapt', + 'params': { + 'publisherId': '11111', + 'placementId': '1' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '2121283921', + } + }, + 'sizes': [[300, 250]], + 'bidId': '001', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'aa837ec1-ba90-3821-jduq-1cc083921a9a', + 'src': 'client', + 'bidRequestsCount': 5 + }, { + 'bidder': 'stackadapt', + 'params': { + 'publisherId': '11111', + 'placementId': '2' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[336, 280], [320, 100]] + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '3728192832', + } + }, + 'sizes': [[336, 280], [320, 100]], + 'bidId': '002', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'au289bg3-bc89-3894-dfak-3dp281927l1b', + 'src': 'client', + 'bidRequestsCount': 10 + }]; + + const bidderRequest = { + 'bidderCode': 'stackadapt', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + ortb2: { + source: { + tid: '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + } + }, + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionStart': 1731042158610, + 'timeout': 1750, + 'refererInfo': { + 'reachedTop': true, + 'numIframes': 0, + 'isAmp': false, + 'page': 'https://www.mobile.com/test', + 'domain': 'www.mobile.com', + 'ref': 'https://testsite.com/', + }, + 'start': 1731042158587 + }; + + it('should correctly set multiple impressions', function () { + const ortbRequest = spec.buildRequests(multiBidRequests, bidderRequest).data; + expect(ortbRequest.imp.length).to.equal(2); + expect(ortbRequest.source?.tid).to.equal(bidderRequest.ortb2.source.tid); + expect(ortbRequest.imp[0].ext?.tid).to.equal('2121283921'); + expect(ortbRequest.imp[1].ext?.tid).to.equal('3728192832'); + }); + + it('should correctly set the tagids for each impression', function () { + const ortbRequest = spec.buildRequests(multiBidRequests, bidderRequest).data; + + expect(ortbRequest.imp[0].id).to.equal('001'); + expect(ortbRequest.imp[0].tagid).to.equal('1'); + + expect(ortbRequest.imp[1].id).to.equal('002'); + expect(ortbRequest.imp[1].tagid).to.equal('2'); + }); + + it('should set the sizes for each impression', function () { + const ortbRequest = spec.buildRequests(multiBidRequests, bidderRequest).data; + + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(250); + + expect(ortbRequest.imp[1].banner.format[0].w).to.equal(336); + expect(ortbRequest.imp[1].banner.format[0].h).to.equal(280); + expect(ortbRequest.imp[1].banner.format[1].w).to.equal(320); + expect(ortbRequest.imp[1].banner.format[1].h).to.equal(100); + }); + }); + + if (FEATURES.VIDEO) { + describe('buildRequests() video', function () { + const videoBidRequests = [{ + 'bidder': 'stackadapt', + 'params': { + 'publisherId': '11111', + 'placementId': '1' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [187, 105], + 'api': [1, 2], + 'mimes': [ + 'video/mp4', + 'video/x-ms-wmv', + 'application/javascript' + ], + 'protocols': [2, 3, 4, 5, 6], + 'minduration': 1, + 'maxduration': 60 + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '2121283921', + } + }, + 'transactionId': 'aa837ec1-ba90-3821-jduq-1cc083921a9a', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'bidId': '001', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'src': 'client', + 'bidRequestsCount': 10 + }]; + + const bidderRequest = { + 'bidderCode': 'stackadapt', + 'auctionId': '8d6e5b89-9c9f-4f25-9d4f-e4c08b0b9d7f', + 'bidderRequestId': '5ce18294-9682-4ad0-1c92-0ab12bg8dc5e', + 'auctionStart': 1731042158610, + 'timeout': 1750, + 'refererInfo': { + 'reachedTop': true, + 'numIframes': 0, + 'isAmp': false, + 'page': 'https://www.mobile.com/test', + 'domain': 'www.mobile.com', + 'ref': 'https://testsite.com/', + }, + 'start': 1731042158587, + }; + + it('should set the ad size', function () { + const ortbRequest = spec.buildRequests(videoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.w).to.equal(187); + expect(ortbRequest.imp[0].video.h).to.equal(105); + }); + + it('should set mimes', function () { + const ortbRequest = spec.buildRequests(videoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.mimes[0]).to.equal('video/mp4'); + expect(ortbRequest.imp[0].video.mimes[1]).to.equal('video/x-ms-wmv'); + expect(ortbRequest.imp[0].video.mimes[2]).to.equal('application/javascript'); + }); + + it('should set min and max duration', function () { + const ortbRequest = spec.buildRequests(videoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.minduration).to.equal(1); + expect(ortbRequest.imp[0].video.maxduration).to.equal(60); + }); + + it('should set api frameworks array', function () { + const ortbRequest = spec.buildRequests(videoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.api[0]).to.equal(1); + expect(ortbRequest.imp[0].video.api[1]).to.equal(2); + }); + + it('should set the protocols array', function () { + const ortbRequest = spec.buildRequests(videoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.protocols[0]).to.equal(2); + expect(ortbRequest.imp[0].video.protocols[1]).to.equal(3); + expect(ortbRequest.imp[0].video.protocols[2]).to.equal(4); + expect(ortbRequest.imp[0].video.protocols[3]).to.equal(5); + expect(ortbRequest.imp[0].video.protocols[4]).to.equal(6); + }); + + it('should set skip if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.skip = 1; + clonnedVideoBidRequests[0].mediaTypes.video.skipmin = 5; + clonnedVideoBidRequests[0].mediaTypes.video.skipafter = 10; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.skipmin).to.equal(5); + expect(ortbRequest.imp[0].video.skipafter).to.equal(10); + }); + + it('should set bitrate if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.minbitrate = 100; + clonnedVideoBidRequests[0].mediaTypes.video.maxbitrate = 500; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.minbitrate).to.equal(100); + expect(ortbRequest.imp[0].video.maxbitrate).to.equal(500); + }); + + it('should set pos if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.pos = 1; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.pos).to.equal(1); + }); + + it('should set playbackmethod if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.playbackmethod = [1]; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.playbackmethod[0]).to.equal(1); + }); + + it('should set startdelay if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.startdelay = -1; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.startdelay).to.equal(-1); + }); + + it('should set placement if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.plcmt = 3; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.plcmt).to.equal(3); + }); + + it('should set plcmt if present', function () { + const clonnedVideoBidRequests = deepClone(videoBidRequests); + clonnedVideoBidRequests[0].mediaTypes.video.plcmt = 3; + + const ortbRequest = spec.buildRequests(clonnedVideoBidRequests, bidderRequest).data; + expect(ortbRequest.imp[0].video.plcmt).to.equal(3); + }); + }); + } + + describe('getUserSyncs', function () { + it('should get usersync', function () { + const syncOptions = { + pixelEnabled: true + }; + const gdprConsentString = 'CQGRvoAQGRvoAAHABAENBKFsAP_gAEPgAAAAKhNV'; + const gdprConsent = { + consentString: gdprConsentString, + gdprApplies: true + }; + const uspConsent = '1YNY'; + const gppConsent = { + gppString: 'DCACTA~1YAB', + applicableSections: [7, 8] + }; + + let syncs = spec.getUserSyncs(syncOptions, [], gdprConsent, uspConsent, gppConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://sync.srv.stackadapt.com/sync?nid=pjs&gdpr=1&gdpr_consent=CQGRvoAQGRvoAAHABAENBKFsAP_gAEPgAAAAKhNV&us_privacy=1YNY&gpp=DCACTA~1YAB&gpp_sid=7,8'); + + let params = new URLSearchParams(new URL(syncs[0].url).search); + expect(params.get('us_privacy')).to.equal(uspConsent); + expect(params.get('gdpr')).to.equal('1'); + expect(params.get('gdpr_consent')).to.equal(gdprConsentString); + expect(params.get('gpp')).to.equal(gppConsent.gppString); + expect(params.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()); + }); + }); +}); From f68ce7861a5dc1b011a8217b7298643be0b8dba0 Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Wed, 2 Apr 2025 17:23:56 +0100 Subject: [PATCH 1053/1097] NodalAiRtdModule: stricter consent checks (#12931) * fix: stricter consent checks * Updated consent checks --------- Co-authored-by: slimkrazy --- modules/nodalsAiRtdProvider.js | 8 +- test/spec/modules/nodalsAiRtdProvider_spec.js | 186 ++++++++++-------- 2 files changed, 109 insertions(+), 85 deletions(-) diff --git a/modules/nodalsAiRtdProvider.js b/modules/nodalsAiRtdProvider.js index f8db70e9218..9359d5187b9 100644 --- a/modules/nodalsAiRtdProvider.js +++ b/modules/nodalsAiRtdProvider.js @@ -246,14 +246,16 @@ class NodalsAiRtdProvider { */ #hasRequiredUserConsent(userConsent) { - if (userConsent?.gdpr?.gdprApplies !== true) { + if (userConsent.gdpr === undefined || userConsent.gdpr?.gdprApplies === false) { return true; } if ( - userConsent?.gdpr?.vendorData?.vendor?.consents?.[this.gvlid] === false + [false, undefined].includes(userConsent.gdpr.vendorData?.vendor?.consents?.[this.gvlid]) ) { return false; - } else if (userConsent?.gdpr?.vendorData?.purpose?.consents[1] === false) { + } else if (userConsent.gdpr.vendorData?.purpose?.consents[1] === false || + userConsent.gdpr.vendorData?.purpose?.consents[7] === false + ) { return false; } return true; diff --git a/test/spec/modules/nodalsAiRtdProvider_spec.js b/test/spec/modules/nodalsAiRtdProvider_spec.js index 69db7b5660c..551a08064a3 100644 --- a/test/spec/modules/nodalsAiRtdProvider_spec.js +++ b/test/spec/modules/nodalsAiRtdProvider_spec.js @@ -71,10 +71,11 @@ const generateGdprConsent = (consent = {}) => { const defaults = { gdprApplies: true, purpose1Consent: true, + purpose7Consent: true, nodalsConsent: true, }; - const mergedConsent = Object.assign({}, defaults, consent); - return { + const mergedConsent = { ...defaults, ...consent }; + return JSON.parse(JSON.stringify({ gdpr: { gdprApplies: mergedConsent.gdprApplies, consentString: mergedConsent.consentString, @@ -82,11 +83,15 @@ const generateGdprConsent = (consent = {}) => { purpose: { consents: { 1: mergedConsent.purpose1Consent, + 2: true, 3: true, 4: true, 5: true, 6: true, + 7: mergedConsent.purpose7Consent, + 8: true, 9: true, + 10: true, }, }, specialFeatureOptins: { @@ -99,7 +104,7 @@ const generateGdprConsent = (consent = {}) => { }, }, }, - }; + })); }; const setDataInLocalStorage = (data) => { @@ -138,6 +143,11 @@ const createTargetingEngineStub = (getTargetingDataReturnValue = {}, raiseError describe('NodalsAI RTD Provider', () => { let sandbox; let validConfig; + const permissiveUserConsent = generateGdprConsent(); + const vendorRestrictiveUserConsent = generateGdprConsent({ nodalsConsent: false }); + const noPurpose1UserConsent = generateGdprConsent({ purpose1Consent: false }); + const noPurpose7UserConsent = generateGdprConsent({ purpose7Consent: false }); + const outsideGdprUserConsent = generateGdprConsent({ gdprApplies: false }); beforeEach(() => { sandbox = sinon.sandbox.create(); @@ -178,19 +188,18 @@ describe('NodalsAI RTD Provider', () => { describe('init()', () => { describe('when initialised with empty consent data', () => { - const userConsent = {}; - - it('should return true when initialized with valid config and empty user consent', function () { - const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + it('should return true when initialised with valid config and empty user consent', function () { + const result = nodalsAiRtdSubmodule.init(validConfig, {}); server.respond(); expect(result).to.be.true; expect(server.requests.length).to.equal(1); }); - it('should return false when initialized with invalid config', () => { + it('should return false when initialised with invalid config', () => { const config = { params: { invalid: true } }; - const result = nodalsAiRtdSubmodule.init(config, userConsent); + const result = nodalsAiRtdSubmodule.init(config, {}); + server.respond(); expect(result).to.be.false; expect(server.requests.length).to.equal(0); @@ -199,8 +208,15 @@ describe('NodalsAI RTD Provider', () => { describe('when initialised with valid config data', () => { it('should return false when user is under GDPR jurisdiction and purpose1 has not been granted', () => { - const userConsent = generateGdprConsent({ purpose1Consent: false }); - const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + const result = nodalsAiRtdSubmodule.init(validConfig, noPurpose1UserConsent); + server.respond(); + + expect(result).to.be.false; + expect(server.requests.length).to.equal(0); + }); + + it('should return false when user is under GDPR jurisdiction and purpose7 has not been granted', () => { + const result = nodalsAiRtdSubmodule.init(validConfig, noPurpose7UserConsent); server.respond(); expect(result).to.be.false; @@ -208,16 +224,25 @@ describe('NodalsAI RTD Provider', () => { }); it('should return false when user is under GDPR jurisdiction and Nodals AI as a vendor has no consent', () => { - const userConsent = generateGdprConsent({ nodalsConsent: false }); + const result = nodalsAiRtdSubmodule.init(validConfig, vendorRestrictiveUserConsent); + server.respond(); + + expect(result).to.be.false; + expect(server.requests.length).to.equal(0); + }); + + it('should return false when user is under GDPR jurisdiction and Nodals AI is not present in the decoded consent string', () => { + const userConsent = JSON.parse(JSON.stringify(permissiveUserConsent)); + userConsent.gdpr.vendorData.vendor.consents = {}; const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + server.respond(); expect(result).to.be.false; expect(server.requests.length).to.equal(0); }); it('should return true when user is under GDPR jurisdiction and all consent provided', function () { - const userConsent = generateGdprConsent(); - const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + const result = nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); server.respond(); expect(result).to.be.true; @@ -225,8 +250,7 @@ describe('NodalsAI RTD Provider', () => { }); it('should return true when user is not under GDPR jurisdiction', () => { - const userConsent = generateGdprConsent({ gdprApplies: false }); - const result = nodalsAiRtdSubmodule.init(validConfig, userConsent); + const result = nodalsAiRtdSubmodule.init(validConfig, outsideGdprUserConsent); server.respond(); expect(result).to.be.true; @@ -237,7 +261,8 @@ describe('NodalsAI RTD Provider', () => { describe('when initialised with valid config and data already in storage', () => { it('should return true and not make a remote request when stored data is valid', function () { setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: Date.now() }); - const result = nodalsAiRtdSubmodule.init(validConfig, {}); + const result = nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); + server.respond(); expect(result).to.be.true; expect(server.requests.length).to.equal(0); @@ -245,7 +270,7 @@ describe('NodalsAI RTD Provider', () => { it('should return true and make a remote request when stored data has no TTL defined', function () { setDataInLocalStorage({ data: { foo: 'bar' } }); - const result = nodalsAiRtdSubmodule.init(validConfig, {}); + const result = nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); server.respond(); expect(result).to.be.true; @@ -254,7 +279,7 @@ describe('NodalsAI RTD Provider', () => { it('should return true and make a remote request when stored data has expired', function () { setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: 100 }); - const result = nodalsAiRtdSubmodule.init(validConfig, {}); + const result = nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); server.respond(); expect(result).to.be.true; @@ -269,7 +294,7 @@ describe('NodalsAI RTD Provider', () => { }); const config = Object.assign({}, validConfig); config.params.storage = { ttl: 4 * 60 }; - const result = nodalsAiRtdSubmodule.init(config, {}); + const result = nodalsAiRtdSubmodule.init(config, permissiveUserConsent); server.respond(); expect(result).to.be.true; @@ -282,7 +307,7 @@ describe('NodalsAI RTD Provider', () => { data: { foo: 'bar', meta: { ttl: 4 * 60 } }, createdAt: fiveMinutesAgoMs, }); - const result = nodalsAiRtdSubmodule.init(validConfig, {}); + const result = nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); server.respond(); expect(result).to.be.true; @@ -297,7 +322,8 @@ describe('NodalsAI RTD Provider', () => { }); const config = Object.assign({}, validConfig); config.params.storage = { ttl: 6 * 60 }; - const result = nodalsAiRtdSubmodule.init(config, {}); + const result = nodalsAiRtdSubmodule.init(config, permissiveUserConsent); + server.respond(); expect(result).to.be.true; expect(server.requests.length).to.equal(0); @@ -311,7 +337,9 @@ describe('NodalsAI RTD Provider', () => { }); const config = Object.assign({}, validConfig); config.params.storage = { ttl: 6 * 60 }; - const result = nodalsAiRtdSubmodule.init(config, {}); + const result = nodalsAiRtdSubmodule.init(config, permissiveUserConsent); + server.respond(); + expect(result).to.be.true; expect(server.requests.length).to.equal(0); }); @@ -320,7 +348,7 @@ describe('NodalsAI RTD Provider', () => { setDataInLocalStorage({ data: { foo: 'bar' }, createdAt: Date.now() }); const config = Object.assign({}, validConfig); config.params.storage = { key: overrideLocalStorageKey }; - const result = nodalsAiRtdSubmodule.init(config, {}); + const result = nodalsAiRtdSubmodule.init(config, permissiveUserConsent); server.respond(); expect(result).to.be.true; @@ -330,8 +358,7 @@ describe('NodalsAI RTD Provider', () => { describe('when performing requests to the publisher endpoint', () => { it('should construct the correct URL to the default origin', () => { - const userConsent = generateGdprConsent(); - nodalsAiRtdSubmodule.init(validConfig, userConsent); + nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); let request = server.requests[0]; server.respond(); @@ -344,8 +371,7 @@ describe('NodalsAI RTD Provider', () => { it('should construct the URL to the overridden origin when specified in the config', () => { const config = Object.assign({}, validConfig); config.params.endpoint = { origin: 'http://localhost:8000' }; - const userConsent = generateGdprConsent(); - nodalsAiRtdSubmodule.init(config, userConsent); + nodalsAiRtdSubmodule.init(config, permissiveUserConsent); let request = server.requests[0]; server.respond(); @@ -356,8 +382,7 @@ describe('NodalsAI RTD Provider', () => { }); it('should construct the correct URL with the correct path', () => { - const userConsent = generateGdprConsent(); - nodalsAiRtdSubmodule.init(validConfig, userConsent); + nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); let request = server.requests[0]; server.respond(); @@ -369,8 +394,7 @@ describe('NodalsAI RTD Provider', () => { const consentData = { consentString: 'foobarbaz', }; - const userConsent = generateGdprConsent(consentData); - nodalsAiRtdSubmodule.init(validConfig, userConsent); + nodalsAiRtdSubmodule.init(validConfig, generateGdprConsent(consentData)); let request = server.requests[0]; server.respond(); @@ -387,8 +411,7 @@ describe('NodalsAI RTD Provider', () => { describe('when handling responses from the publisher endpoint', () => { it('should store successful response data in local storage', () => { - const userConsent = generateGdprConsent(); - nodalsAiRtdSubmodule.init(validConfig, userConsent); + nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); let request = server.requests[0]; server.respond(); @@ -404,10 +427,9 @@ describe('NodalsAI RTD Provider', () => { }); it('should store successful response data in local storage under the override key', () => { - const userConsent = generateGdprConsent(); const config = Object.assign({}, validConfig); config.params.storage = { key: overrideLocalStorageKey }; - nodalsAiRtdSubmodule.init(config, userConsent); + nodalsAiRtdSubmodule.init(config, permissiveUserConsent); server.respond(); let request = server.requests[0]; const storedData = JSON.parse( @@ -420,8 +442,7 @@ describe('NodalsAI RTD Provider', () => { }); it('should attempt to load the referenced script libraries contained in the response payload', () => { - const userConsent = generateGdprConsent(); - nodalsAiRtdSubmodule.init(validConfig, userConsent); + nodalsAiRtdSubmodule.init(validConfig, permissiveUserConsent); server.respond(); expect(loadExternalScriptStub.calledTwice).to.be.true; @@ -448,7 +469,7 @@ describe('NodalsAI RTD Provider', () => { const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - {} + permissiveUserConsent ); server.respond(); expect(result).to.deep.equal({}); @@ -457,7 +478,6 @@ describe('NodalsAI RTD Provider', () => { it('should return an empty object when getTargetingData throws error', () => { createTargetingEngineStub({adUnit1: {someKey: 'someValue'}}, true); - const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), @@ -466,7 +486,7 @@ describe('NodalsAI RTD Provider', () => { const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - userConsent + permissiveUserConsent ); expect(result).to.deep.equal({}); expect(server.requests.length).to.equal(0); @@ -475,7 +495,6 @@ describe('NodalsAI RTD Provider', () => { it('should initialise the versioned targeting engine if fresh data is in storage and not make a HTTP request', () => { const returnData = {}; const engine = createTargetingEngineStub(returnData); - const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), @@ -484,8 +503,9 @@ describe('NodalsAI RTD Provider', () => { nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - userConsent + permissiveUserConsent ); + server.respond(); expect(engine.init.called).to.be.true; const args = engine.init.getCall(0).args; @@ -496,7 +516,6 @@ describe('NodalsAI RTD Provider', () => { it('should initialise the versioned targeting engine with stale data if data expired and fetch fresh data', function () { const returnData = {}; const engine = createTargetingEngineStub(returnData); - const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: 100, @@ -505,7 +524,7 @@ describe('NodalsAI RTD Provider', () => { nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - userConsent + permissiveUserConsent ); server.respond(); @@ -519,7 +538,6 @@ describe('NodalsAI RTD Provider', () => { const engine = createTargetingEngineStub( engineGetTargetingDataReturnValue ); - const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), @@ -528,13 +546,14 @@ describe('NodalsAI RTD Provider', () => { nodalsAiRtdSubmodule.getTargetingData( ['adUnit1', 'adUnit2'], validConfig, - userConsent + permissiveUserConsent ); + server.respond(); expect(engine.getTargetingData.called).to.be.true; const args = engine.getTargetingData.getCall(0).args; expect(args[0]).to.deep.equal(['adUnit1', 'adUnit2']); - expect(args[1]).to.deep.equal(userConsent); + expect(args[1]).to.deep.equal(permissiveUserConsent); expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); @@ -543,7 +562,6 @@ describe('NodalsAI RTD Provider', () => { it('should return the data from engine.getTargetingData when storage data is available and we have consent under GDPR jurisdiction', () => { createTargetingEngineStub(engineGetTargetingDataReturnValue); - const userConsent = generateGdprConsent(); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), @@ -552,15 +570,15 @@ describe('NodalsAI RTD Provider', () => { const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - userConsent + permissiveUserConsent ); + server.respond(); expect(result).to.deep.equal(engineGetTargetingDataReturnValue); }); it('should return the data from engine.getTargetingData when storage is available and we are NOT under GDPR jurisdiction', () => { createTargetingEngineStub(engineGetTargetingDataReturnValue); - const userConsent = generateGdprConsent({ gdprApplies: false }); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), @@ -569,15 +587,15 @@ describe('NodalsAI RTD Provider', () => { const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - userConsent + outsideGdprUserConsent ); + server.respond(); expect(result).to.deep.equal(engineGetTargetingDataReturnValue); }); it('should return an empty object when data is available, but user has not provided consent to Nodals AI as a vendor', () => { createTargetingEngineStub(engineGetTargetingDataReturnValue); - const userConsent = generateGdprConsent({ nodalsConsent: false }); setDataInLocalStorage({ data: successPubEndpointResponse, createdAt: Date.now(), @@ -586,8 +604,9 @@ describe('NodalsAI RTD Provider', () => { const result = nodalsAiRtdSubmodule.getTargetingData( ['adUnit1'], validConfig, - userConsent + vendorRestrictiveUserConsent ); + server.respond(); expect(result).to.deep.equal({}); }); @@ -600,11 +619,12 @@ describe('NodalsAI RTD Provider', () => { createdAt: Date.now(), }); const engine = createTargetingEngineStub(); - const userConsent = generateGdprConsent({ nodalsConsent: false }); + const customUserConsent = generateGdprConsent({ nodalsConsent: false }); const callback = sinon.spy(); nodalsAiRtdSubmodule.getBidRequestData( - {}, callback, validConfig, userConsent + {}, callback, validConfig, vendorRestrictiveUserConsent ); + server.respond(); expect(callback.called).to.be.true; expect(engine.init.called).to.be.false; @@ -613,11 +633,10 @@ describe('NodalsAI RTD Provider', () => { }); it('should not store function arguments in a queue when no data is in localstorage and make a HTTP request for data', () => { - const userConsent = generateGdprConsent(); const callback = sinon.spy(); const requestObj = {dummy: 'obj'} nodalsAiRtdSubmodule.getBidRequestData( - requestObj, callback, validConfig, userConsent + requestObj, callback, validConfig, permissiveUserConsent ); server.respond(); @@ -631,18 +650,18 @@ describe('NodalsAI RTD Provider', () => { data: successPubEndpointResponse, createdAt: Date.now(), }); - const userConsent = generateGdprConsent(); const callback = sinon.spy(); const reqBidsConfigObj = {dummy: 'obj'} nodalsAiRtdSubmodule.getBidRequestData( - reqBidsConfigObj, callback, validConfig, userConsent + reqBidsConfigObj, callback, validConfig, permissiveUserConsent ); + server.respond(); expect(callback.called).to.be.false; expect(window.$nodals.cmdQueue).to.be.an('array').with.length(1); expect(window.$nodals.cmdQueue[0].cmd).to.equal('getBidRequestData'); expect(window.$nodals.cmdQueue[0].runtimeFacts).to.have.keys(['prebid.version', 'page.url']); - expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, reqBidsConfigObj, callback, userConsent}); + expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, reqBidsConfigObj, callback, userConsent: permissiveUserConsent}); expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('deps').that.deep.equals( successPubEndpointResponse.deps); expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('facts').that.deep.includes( @@ -658,12 +677,12 @@ describe('NodalsAI RTD Provider', () => { createdAt: Date.now(), }); const engine = createTargetingEngineStub(); - const userConsent = generateGdprConsent(); const callback = sinon.spy(); const reqBidsConfigObj = {dummy: 'obj'} nodalsAiRtdSubmodule.getBidRequestData( - reqBidsConfigObj, callback, validConfig, userConsent + reqBidsConfigObj, callback, validConfig, permissiveUserConsent ); + server.respond(); expect(callback.called).to.be.false; expect(engine.init.called).to.be.true; @@ -671,7 +690,7 @@ describe('NodalsAI RTD Provider', () => { const args = engine.getBidRequestData.getCall(0).args; expect(args[0]).to.deep.equal(reqBidsConfigObj); expect(args[1]).to.deep.equal(callback); - expect(args[2]).to.deep.equal(userConsent); + expect(args[2]).to.deep.equal(permissiveUserConsent); expect(args[3].deps).to.deep.equal(successPubEndpointResponse.deps); expect(args[3].facts).to.deep.include(successPubEndpointResponse.facts); expect(args[3].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); @@ -686,11 +705,11 @@ describe('NodalsAI RTD Provider', () => { createdAt: Date.now(), }); const engine = createTargetingEngineStub(); - const userConsent = generateGdprConsent({ nodalsConsent: false }); const bidResponse = {dummy: 'obj', 'bid': 'foo'}; nodalsAiRtdSubmodule.onBidResponseEvent( - bidResponse, validConfig, userConsent + bidResponse, validConfig, vendorRestrictiveUserConsent ); + server.respond(); expect(engine.init.called).to.be.false; expect(engine.onBidResponseEvent.called).to.be.false; @@ -699,12 +718,12 @@ describe('NodalsAI RTD Provider', () => { }); it('should not store function arguments in a queue when no data is in localstorage and make a HTTP request for data', () => { - const userConsent = generateGdprConsent(); const bidResponse = {dummy: 'obj', 'bid': 'foo'}; nodalsAiRtdSubmodule.onBidResponseEvent( - bidResponse, validConfig, userConsent + bidResponse, validConfig, permissiveUserConsent ); server.respond(); + expect(window.$nodals).to.be.undefined; expect(server.requests.length).to.equal(1); }); @@ -717,12 +736,14 @@ describe('NodalsAI RTD Provider', () => { const userConsent = generateGdprConsent(); const bidResponse = {dummy: 'obj', 'bid': 'foo'}; nodalsAiRtdSubmodule.onBidResponseEvent( - bidResponse, validConfig, userConsent + bidResponse, validConfig, permissiveUserConsent ); + server.respond(); + expect(window.$nodals.cmdQueue).to.be.an('array').with.length(1); expect(window.$nodals.cmdQueue[0].cmd).to.equal('onBidResponseEvent'); expect(window.$nodals.cmdQueue[0].runtimeFacts).to.have.keys(['prebid.version', 'page.url']); - expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, bidResponse, userConsent }); + expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, bidResponse, userConsent: permissiveUserConsent }); expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('deps').that.deep.equals( successPubEndpointResponse.deps); expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('facts').that.deep.includes( @@ -738,17 +759,17 @@ describe('NodalsAI RTD Provider', () => { createdAt: Date.now(), }); const engine = createTargetingEngineStub(); - const userConsent = generateGdprConsent(); const bidResponse = {dummy: 'obj', 'bid': 'foo'}; nodalsAiRtdSubmodule.onBidResponseEvent( - bidResponse, validConfig, userConsent + bidResponse, validConfig, permissiveUserConsent ); + server.respond(); expect(engine.init.called).to.be.true; expect(engine.onBidResponseEvent.called).to.be.true; const args = engine.onBidResponseEvent.getCall(0).args; expect(args[0]).to.deep.equal(bidResponse); - expect(args[1]).to.deep.equal(userConsent); + expect(args[1]).to.deep.equal(permissiveUserConsent); expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); @@ -763,11 +784,11 @@ describe('NodalsAI RTD Provider', () => { createdAt: Date.now(), }); const engine = createTargetingEngineStub(); - const userConsent = generateGdprConsent({ nodalsConsent: false }); const auctionDetails = {dummy: 'obj', auction: 'foo'}; nodalsAiRtdSubmodule.onAuctionEndEvent( - auctionDetails, validConfig, userConsent + auctionDetails, validConfig, vendorRestrictiveUserConsent ); + server.respond(); expect(engine.init.called).to.be.false; expect(engine.onAuctionEndEvent.called).to.be.false; @@ -776,12 +797,12 @@ describe('NodalsAI RTD Provider', () => { }); it('should not store function arguments in a queue when no data is in localstorage and make a HTTP request for data', () => { - const userConsent = generateGdprConsent(); const auctionDetails = {dummy: 'obj', auction: 'foo'}; nodalsAiRtdSubmodule.onAuctionEndEvent( - auctionDetails, validConfig, userConsent + auctionDetails, validConfig, permissiveUserConsent ); server.respond(); + expect(window.$nodals).to.be.undefined; expect(server.requests.length).to.equal(1); }); @@ -791,16 +812,16 @@ describe('NodalsAI RTD Provider', () => { data: successPubEndpointResponse, createdAt: Date.now(), }); - const userConsent = generateGdprConsent(); const auctionDetails = {dummy: 'obj', auction: 'foo'}; nodalsAiRtdSubmodule.onAuctionEndEvent( - auctionDetails, validConfig, userConsent + auctionDetails, validConfig, permissiveUserConsent ); + server.respond(); expect(window.$nodals.cmdQueue).to.be.an('array').with.length(1); expect(window.$nodals.cmdQueue[0].cmd).to.equal('onAuctionEndEvent'); expect(window.$nodals.cmdQueue[0].runtimeFacts).to.have.keys(['prebid.version', 'page.url']); - expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, auctionDetails, userConsent }); + expect(window.$nodals.cmdQueue[0].data).to.deep.include({config: validConfig, auctionDetails, userConsent: permissiveUserConsent }); expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('deps').that.deep.equals( successPubEndpointResponse.deps); expect(window.$nodals.cmdQueue[0].data.storedData).to.have.property('facts').that.deep.includes( @@ -819,14 +840,15 @@ describe('NodalsAI RTD Provider', () => { const userConsent = generateGdprConsent(); const auctionDetails = {dummy: 'obj', auction: 'foo'}; nodalsAiRtdSubmodule.onAuctionEndEvent( - auctionDetails, validConfig, userConsent + auctionDetails, validConfig, permissiveUserConsent ); + server.respond(); expect(engine.init.called).to.be.true; expect(engine.onAuctionEndEvent.called).to.be.true; const args = engine.onAuctionEndEvent.getCall(0).args; expect(args[0]).to.deep.equal(auctionDetails); - expect(args[1]).to.deep.equal(userConsent); + expect(args[1]).to.deep.equal(permissiveUserConsent); expect(args[2].deps).to.deep.equal(successPubEndpointResponse.deps); expect(args[2].facts).to.deep.include(successPubEndpointResponse.facts); expect(args[2].campaigns).to.deep.equal(successPubEndpointResponse.campaigns); From 08a4c3fb095b57d41020f028bc5d24c292d1a7ee Mon Sep 17 00:00:00 2001 From: Eugene Dorfman Date: Wed, 2 Apr 2025 18:56:23 +0200 Subject: [PATCH 1054/1097] Optable RTD submodule: check for cached data before firing a request (#12954) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> --- modules/optableRtdProvider.js | 9 +++++-- test/spec/modules/optableRtdProvider_spec.js | 26 +++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/modules/optableRtdProvider.js b/modules/optableRtdProvider.js index 631084efece..a8a69ce2345 100644 --- a/modules/optableRtdProvider.js +++ b/modules/optableRtdProvider.js @@ -46,8 +46,13 @@ export const parseConfig = (moduleConfig) => { */ export const defaultHandleRtd = async (reqBidsConfigObj, optableExtraData, mergeFn) => { const optableBundle = /** @type {Object} */ (window.optable); - // Call Optable DCN for targeting data and return the ORTB2 object - const targetingData = await optableBundle?.instance?.targeting(); + // Get targeting data from cache, if available + let targetingData = optableBundle?.instance?.targetingFromCache(); + // If no targeting data is found in the cache, call the targeting function + if (!targetingData) { + // Call Optable DCN for targeting data and return the ORTB2 object + targetingData = await optableBundle?.instance?.targeting(); + } logMessage('Original targeting data from targeting(): ', targetingData); if (!targetingData || !targetingData.ortb2) { diff --git a/test/spec/modules/optableRtdProvider_spec.js b/test/spec/modules/optableRtdProvider_spec.js index c725e1024d3..7aa4be3c8b2 100644 --- a/test/spec/modules/optableRtdProvider_spec.js +++ b/test/spec/modules/optableRtdProvider_spec.js @@ -58,7 +58,12 @@ describe('Optable RTD Submodule', function () { sandbox = sinon.createSandbox(); reqBidsConfigObj = {ortb2Fragments: {global: {}}}; mergeFn = sinon.spy(); - window.optable = {instance: {targeting: sandbox.stub()}}; + window.optable = { + instance: { + targeting: sandbox.stub(), + targetingFromCache: sandbox.stub(), + }, + }; }); afterEach(() => { @@ -67,6 +72,7 @@ describe('Optable RTD Submodule', function () { it('merges valid targeting data into the global ORTB2 object', async function () { const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}}; + window.optable.instance.targetingFromCache.returns(targetingData); window.optable.instance.targeting.resolves(targetingData); await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); @@ -74,11 +80,29 @@ describe('Optable RTD Submodule', function () { }); it('does nothing if targeting data is missing the ortb2 property', async function () { + window.optable.instance.targetingFromCache.returns({}); window.optable.instance.targeting.resolves({}); await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); expect(mergeFn.called).to.be.false; }); + + it('uses targeting data from cache if available', async function () { + const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}}; + window.optable.instance.targetingFromCache.returns(targetingData); + + await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); + expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true; + }); + + it('calls targeting function if no data is found in cache', async function () { + const targetingData = {ortb2: {user: {ext: {optable: 'testData'}}}}; + window.optable.instance.targetingFromCache.returns(null); + window.optable.instance.targeting.resolves(targetingData); + + await defaultHandleRtd(reqBidsConfigObj, {}, mergeFn); + expect(mergeFn.calledWith(reqBidsConfigObj.ortb2Fragments.global, targetingData.ortb2)).to.be.true; + }); }); describe('mergeOptableData', function () { From 316a85982b43e461c7c4c942c242c47ec4ed059c Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 2 Apr 2025 13:54:52 -0400 Subject: [PATCH 1055/1097] Core tests: eliminate some sinon.reset (#12942) * Update cmpClient_spec.js: eliminate sinon.reset * Update cmpClient_spec.js * Update redactor_spec.js * Update cmpClient_spec.js * Update translator_spec.js * Update adapterManager_spec.js * Update adapterManager_spec.js * Update adapterManager_spec.js * Update cmpClient_spec.js * Update adapterManager_spec.js * Update adapterManager_spec.js * Update adapterManager_spec.js * Update adapterManager_spec.js * Update translator_spec.js * Update cmpClient_spec.js * Update redactor_spec.js --- test/spec/activities/redactor_spec.js | 3 +- test/spec/libraries/cmp/cmpClient_spec.js | 6 ++- .../translator_spec.js | 3 +- test/spec/unit/core/adapterManager_spec.js | 42 ++++++++++++------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/test/spec/activities/redactor_spec.js b/test/spec/activities/redactor_spec.js index 84d98a958a0..c5c194da73d 100644 --- a/test/spec/activities/redactor_spec.js +++ b/test/spec/activities/redactor_spec.js @@ -37,7 +37,8 @@ describe('objectTransformer', () => { }); it('does not run rule once it is known that it does not apply', () => { - applies.reset(); + applies.resetHistory(); + applies.resetBehavior(); applies.callsFake(() => false); run.callsFake((_1, _2, _3, _4, applies) => applies()); objectTransformer([rule])({}, {}); diff --git a/test/spec/libraries/cmp/cmpClient_spec.js b/test/spec/libraries/cmp/cmpClient_spec.js index adbbbf5cb1d..e779ad9eb23 100644 --- a/test/spec/libraries/cmp/cmpClient_spec.js +++ b/test/spec/libraries/cmp/cmpClient_spec.js @@ -112,7 +112,8 @@ describe('cmpClient', () => { }) it('rejects when CMP api throws', (done) => { - mockApiFn.reset(); + mockApiFn.resetBehavior(); + mockApiFn.resetHistory(); const e = new Error(); mockApiFn.throws(e); mkClient()({}).catch(val => { @@ -255,7 +256,8 @@ describe('cmpClient', () => { beforeEach(() => { callId = null; - messenger.reset(); + messenger.resetHistory(); + messenger.resetBehavior(); messenger.callsFake((msg) => { if (msg.mockApiCall) callId = msg.mockApiCall.callId; }); diff --git a/test/spec/ortb2.5StrictTranslator/translator_spec.js b/test/spec/ortb2.5StrictTranslator/translator_spec.js index 4bda3d96235..af2755120c7 100644 --- a/test/spec/ortb2.5StrictTranslator/translator_spec.js +++ b/test/spec/ortb2.5StrictTranslator/translator_spec.js @@ -6,7 +6,8 @@ describe('toOrtb25Strict', () => { translator = sinon.stub().callsFake((o) => o); }) it('uses provided translator', () => { - translator.reset(); + translator.resetBehavior(); + translator.resetHistory(); translator.callsFake(() => ({id: 'test'})); expect(toOrtb25Strict(null, translator)).to.eql({id: 'test'}); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 6e7280abfa3..90f25d87ba9 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -123,7 +123,8 @@ describe('adapterManager tests', function () { beforeEach(function () { sinon.stub(utils, 'logError'); - appnexusAdapterMock.callBids.reset(); + appnexusAdapterMock.callBids.resetHistory(); + appnexusAdapterMock.callBids.resetBehavior() adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; adapterManager.bidderRegistry['badBidder'] = badAdapterMock; @@ -531,7 +532,8 @@ describe('adapterManager tests', function () { beforeEach(function () { config.setConfig({s2sConfig: CONFIG}); adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; - prebidServerAdapterMock.callBids.reset(); + prebidServerAdapterMock.callBids.resetHistory(); + prebidServerAdapterMock.callBids.resetBehavior(); }); const bidRequests = [{ @@ -715,7 +717,8 @@ describe('adapterManager tests', function () { let cnt, count = () => cnt++; beforeEach(function () { - prebidServerAdapterMock.callBids.reset(); + prebidServerAdapterMock.callBids.resetHistory(); + prebidServerAdapterMock.callBids.resetBehavior(); cnt = 0; events.on(EVENTS.BID_REQUESTED, count); }); @@ -756,7 +759,8 @@ describe('adapterManager tests', function () { beforeEach(function () { config.setConfig({s2sConfig: [CONFIG, CONFIG2]}); adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; - prebidServerAdapterMock.callBids.reset(); + prebidServerAdapterMock.callBids.resetHistory(); + prebidServerAdapterMock.callBids.resetBehavior(); }); const bidRequests = [{ @@ -1103,7 +1107,8 @@ describe('adapterManager tests', function () { let cnt, count = () => cnt++; beforeEach(function () { - prebidServerAdapterMock.callBids.reset(); + prebidServerAdapterMock.callBids.resetHistory(); + prebidServerAdapterMock.callBids.resetBehavior(); cnt = 0; events.on(EVENTS.BID_REQUESTED, count); }); @@ -1205,10 +1210,14 @@ describe('adapterManager tests', function () { stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); - prebidServerAdapterMock.callBids.reset(); - adequantAdapterMock.callBids.reset(); - appnexusAdapterMock.callBids.reset(); - rubiconAdapterMock.callBids.reset(); + prebidServerAdapterMock.callBids.resetHistory(); + prebidServerAdapterMock.callBids.resetBehavior(); + adequantAdapterMock.callBids.resetHistory(); + adequantAdapterMock.callBids.resetBehavior() + appnexusAdapterMock.callBids.resetHistory(); + appnexusAdapterMock.callBids.resetBehavior() + rubiconAdapterMock.callBids.resetHistory(); + rubiconAdapterMock.callBids.resetBehavior(); }); afterEach(function () { @@ -1392,11 +1401,16 @@ describe('adapterManager tests', function () { adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; adapterManager.bidderRegistry['pubmatic'] = pubmaticAdapterMock; - prebidServerAdapterMock.callBids.reset(); - adequantAdapterMock.callBids.reset(); - appnexusAdapterMock.callBids.reset(); - rubiconAdapterMock.callBids.reset(); - pubmaticAdapterMock.callBids.reset(); + prebidServerAdapterMock.callBids.resetHistory(); + adequantAdapterMock.callBids.resetHistory(); + appnexusAdapterMock.callBids.resetHistory(); + rubiconAdapterMock.callBids.resetHistory(); + pubmaticAdapterMock.callBids.resetHistory(); + prebidServerAdapterMock.callBids.resetBehavior(); + adequantAdapterMock.callBids.resetBehavior(); + appnexusAdapterMock.callBids.resetBehavior(); + rubiconAdapterMock.callBids.resetBehavior(); + pubmaticAdapterMock.callBids.resetBehavior(); }); it('calls server adapter if no sources defined for config where testing is true, ' + From c030f5329d669879f0a0675b0115f4bd37b2cfa3 Mon Sep 17 00:00:00 2001 From: hamper Date: Thu, 3 Apr 2025 16:37:30 +0300 Subject: [PATCH 1056/1097] Vistars bid adapter: initial release (#12813) * Vistars bid adapter * fix: video bid testing * fix: consent parameters names * fix: remove unneeded code --- modules/vistarsBidAdapter.js | 111 +++++++++ modules/vistarsBidAdapter.md | 44 ++++ test/spec/modules/vistarsBidAdapter_spec.js | 248 ++++++++++++++++++++ 3 files changed, 403 insertions(+) create mode 100644 modules/vistarsBidAdapter.js create mode 100644 modules/vistarsBidAdapter.md create mode 100644 test/spec/modules/vistarsBidAdapter_spec.js diff --git a/modules/vistarsBidAdapter.js b/modules/vistarsBidAdapter.js new file mode 100644 index 00000000000..f77586ff8bf --- /dev/null +++ b/modules/vistarsBidAdapter.js @@ -0,0 +1,111 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { deepSetValue, replaceAuctionPrice, deepClone, deepAccess } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'vistars'; +const DEFAULT_ENDPOINT = 'ex-asr.vistarsagency.com'; +const SYNC_ENDPOINT = 'sync.vistarsagency.com'; +const ADOMAIN = 'vistarsagency.com'; +const TIME_TO_LIVE = 360; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'ext.prebid', true); + + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.adm = replaceAuctionPrice(bidResponse.adm, bidResponse.price); + bidResponse.burl = replaceAuctionPrice(bidResponse.burl, bidResponse.price); + bidResponse.nurl = replaceAuctionPrice(bidResponse.nurl, bidResponse.price); + + return bidResponse; + } +}); + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + let valid = bid.params.source; + + return !!valid; + }, + + buildRequests: function(bids, bidderRequest) { + return bids.map((bid) => { + let endpoint = bid.params.endpoint || DEFAULT_ENDPOINT; + return { + method: 'POST', + url: `https://${endpoint}/bid?source=${bid.params.source}`, + data: converter.toORTB({ + bidRequests: [bid], + bidderRequest: deepClone(bidderRequest), + context: { + mediaType: deepAccess(bid, 'mediaTypes.video') ? VIDEO : BANNER + }, + }), + }; + }); + }, + + interpretResponse: function(response, request) { + if (!response?.body) { + return []; + } + + const bids = converter.fromORTB({response: response.body, request: request.data}).bids; + bids.forEach((bid) => { + bid.meta = bid.meta || {}; + bid.meta.advertiserDomains = bid.meta.advertiserDomains || []; + if (bid.meta.advertiserDomains.length == 0) { + bid.meta.advertiserDomains.push(ADOMAIN); + } + + bid.ttl = bid.ttl || TIME_TO_LIVE; + }); + + return bids; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + + if (!hasPurpose1Consent(gdprConsent)) { + return syncs; + } + + let params = `us_privacy=${uspConsent || ''}&gdpr_consent=${gdprConsent?.consentString ? gdprConsent.consentString : ''}`; + if (typeof gdprConsent?.gdprApplies === 'boolean') { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `//${SYNC_ENDPOINT}/match/sp.ifr?${params}` + }); + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: `//${SYNC_ENDPOINT}/match/sp?${params}` + }); + } + + return syncs; + }, + + supportedMediaTypes: [ BANNER, VIDEO ] +} + +registerBidder(spec); diff --git a/modules/vistarsBidAdapter.md b/modules/vistarsBidAdapter.md new file mode 100644 index 00000000000..6252aaaa180 --- /dev/null +++ b/modules/vistarsBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +``` +Module Name: Vistars Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@vistarsagency.com +``` + +# Description +Connects to Vistars server for bids. +Module supports banner and video mediaType. + +# Test Parameters + +``` + var adUnits = [{ + code: '/test/div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'vistars', + params: { + source: 'ssp1', + } + }] + }, + { + code: '/test/div', + mediaTypes: { + video: { + playerSize: [[640, 360]] + } + }, + bids: [{ + bidder: 'vistars', + params: { + source: 'ssp1', + } + }] + },]; +``` diff --git a/test/spec/modules/vistarsBidAdapter_spec.js b/test/spec/modules/vistarsBidAdapter_spec.js new file mode 100644 index 00000000000..26be79e5e1a --- /dev/null +++ b/test/spec/modules/vistarsBidAdapter_spec.js @@ -0,0 +1,248 @@ +import { expect } from 'chai'; +import { spec } from 'modules/vistarsBidAdapter.js'; +import { deepClone } from 'src/utils.js'; + +describe('vistarsBidAdapterTests', function () { + let bidRequestData = { + bids: [ + { + adUnitCode: 'div-banner-id', + bidId: 'bid-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bidder: 'vistars', + params: { + source: 'ssp1', + }, + requestId: 'request-123', + } + ] + }; + + it('validate_pub_params', function () { + expect( + spec.isBidRequestValid({ + bidder: 'vistars', + params: { + source: 'ssp1', + } + }) + ).to.equal(true); + }); + + it('validate_generated_url', function () { + const request = spec.buildRequests(deepClone(bidRequestData.bids), { timeout: 1234 }); + let req_url = request[0].url; + + expect(req_url).to.equal('https://ex-asr.vistarsagency.com/bid?source=ssp1'); + }); + + it('validate_response_params', function () { + let serverResponse = { + body: { + id: 'bid123', + seatbid: [ + { + bid: [ + { + id: '1bh7jku7-ko2g-8654-ab72-h268abcde271', + impid: 'bid-123', + price: 0.6565, + adm: '

AD

', + adomain: ['abc.com'], + cid: '1242512', + crid: '535231', + w: 300, + h: 600, + mtype: 1, + ext: { + prebid: { + type: 'banner', + } + } + }, + ], + seat: '4212', + }, + ], + cur: 'EUR', + } + }; + + const bidRequest = deepClone(bidRequestData.bids) + bidRequest[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + } + } + + const request = spec.buildRequests(bidRequest); + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.ad).to.equal('

AD

'); + expect(bid.cpm).to.equal(0.6565); + expect(bid.currency).to.equal('EUR'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(600); + expect(bid.creativeId).to.equal('535231'); + expect(bid.meta.advertiserDomains).to.deep.equal(['abc.com']); + }); + + it('validate_invalid_response', function () { + let serverResponse = { + body: {} + }; + + const bidRequest = deepClone(bidRequestData.bids) + bidRequest[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + } + } + + const request = spec.buildRequests(bidRequest); + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(0); + }) + + if (FEATURES.VIDEO) { + it('video_bid', function () { + const bidRequest = deepClone(bidRequestData.bids); + bidRequest[0].mediaTypes = { + video: { + playerSize: [234, 765] + } + }; + + const request = spec.buildRequests(bidRequest, { timeout: 1234 }); + const vastXml = ''; + let serverResponse = { + body: { + id: 'bid123', + seatbid: [ + { + bid: [ + { + id: '1bh7jku7-ko2g-8654-ab72-h268abcde271', + impid: 'bid-123', + price: 0.6565, + adm: vastXml, + adomain: ['abc.com'], + cid: '1242512', + crid: '535231', + w: 300, + h: 600, + mtype: 1, + ext: { + prebid: { + type: 'banner', + } + } + }, + ], + seat: '4212', + }, + ], + cur: 'EUR', + } + }; + + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(vastXml); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(600); + }); + } +}); + +describe('getUserSyncs', function() { + it('returns empty sync array', function() { + const syncOptions = {}; + + expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); + }); + + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({ + pixelEnabled: true, + }, {}, {}, '1---'); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('//sync.vistarsagency.com/match/sp?us_privacy=1---&gdpr_consent=') + }); + + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({ + iframeEnabled: true, + }, {}, { + gdprApplies: true, + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: { + purpose: { + consents: { + 1: true + }, + }, + } + }, ''); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('//sync.vistarsagency.com/match/sp.ifr?us_privacy=&gdpr_consent=COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&gdpr=1') + }); + + it('Should return array of objects with proper sync config , include GDPR, no purpose', function() { + const syncData = spec.getUserSyncs({ + iframeEnabled: true, + }, {}, { + gdprApplies: true, + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: { + purpose: { + consents: { + 1: false + }, + }, + } + }, ''); + expect(syncData).is.empty; + }); + + it('Should return array of objects with proper sync config , GDPR not applies', function() { + const syncData = spec.getUserSyncs({ + iframeEnabled: true, + }, {}, { + gdprApplies: false, + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + }, ''); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('//sync.vistarsagency.com/match/sp.ifr?us_privacy=&gdpr_consent=COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&gdpr=0') + }); +}) From 6fff6a990399ac7edeef96cc4630bdff59c024b6 Mon Sep 17 00:00:00 2001 From: Graham Higgins Date: Thu, 3 Apr 2025 10:56:30 -0400 Subject: [PATCH 1057/1097] Criteo Bid Adapter: Use optional chaining for callbacks (#12950) Avoids errors when callbacks are not iterable. Closes #12949 --- modules/criteoBidAdapter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index b01e7361e3f..a38660c4f25 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -284,9 +284,7 @@ export const spec = { saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR, refererInfo.domain); } - if (response.callbacks) { - response.callbacks.forEach(triggerPixel); - } + response?.callbacks?.forEach?.(triggerPixel); } }, true); From 9b4aae92eef0254405776baee4fbfceabbfc8c0d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 3 Apr 2025 10:05:38 -0700 Subject: [PATCH 1058/1097] Build system: setup dist directory for NPM release (#12959) * Build system: setup dist directory for NPM release * Fix .npmignore --- .gitignore | 3 +++ gulpfile.js | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e5f000dd4d5..080b95e0753 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Built Files node_modules/ build +# dist and npmignore are generated by "gulp build" +/dist/ +.npmignore # Test Files test/app diff --git a/gulpfile.js b/gulpfile.js index 8481283b652..608c9f67819 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -48,7 +48,7 @@ function bundleToStdout() { bundleToStdout.displayName = 'bundle-to-stdout'; function clean() { - return gulp.src(['build'], { + return gulp.src(['build', 'dist'], { read: false, allowEmpty: true }) @@ -329,6 +329,16 @@ function bundle(dev, moduleArr) { .pipe(gulpif(sm, sourcemaps.write('.'))); } +function setupDist() { + return gulp.src(['build/dist/**/*']) + .pipe(rename(function (path) { + if (path.dirname === '.' && path.basename === 'prebid') { + path.dirname = 'not-for-prod'; + } + })) + .pipe(gulp.dest('dist')) +} + // Run the unit tests. // // By default, this runs in headless chrome. @@ -535,7 +545,9 @@ gulp.task(viewCoverage); gulp.task('coveralls', gulp.series('test-coverage', coveralls)); -gulp.task('build', gulp.series(clean, 'build-bundle-prod', updateCreativeExample)); +// npm will by default use .gitignore, so create an .npmignore that is a copy of it except it includes "dist" +gulp.task('setup-npmignore', run("sed 's/^\\/\\?dist\\/\\?$//g;w .npmignore' .gitignore", {quiet: true})); +gulp.task('build', gulp.series(clean, 'build-bundle-prod', updateCreativeExample, setupDist, 'setup-npmignore')); gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); From cf488e332ba8ef2b6fc3a41d9c41a443dd812a5c Mon Sep 17 00:00:00 2001 From: tarasmatokhniuk Date: Thu, 3 Apr 2025 19:24:19 +0200 Subject: [PATCH 1059/1097] Adtrgtme Bid Adapter: function renaming (#12958) * Adtrgtme Bid Adapter: function renaming * adtrgtme version changes * fix version * skip pb client version check --- modules/adtrgtmeBidAdapter.js | 4 ++-- test/spec/modules/adtrgtmeBidAdapter_spec.js | 15 +++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js index ba30b17e3d1..9432031e77e 100644 --- a/modules/adtrgtmeBidAdapter.js +++ b/modules/adtrgtmeBidAdapter.js @@ -13,7 +13,7 @@ import { config } from '../src/config.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; const BIDDER_CODE = 'adtrgtme'; -const BIDDER_VERSION = '1.0.5'; +const BIDDER_VERSION = '1.0.6'; const BIDDER_URL = 'https://z.cdn.adtarget.market/ssp?prebid&s='; const PREBIDJS_VERSION = '$prebid.version$'; const DEFAULT_TTL = 300; @@ -161,7 +161,7 @@ export const spec = { aliases: [], supportedMediaTypes: [BANNER], - isOK: function (bid) { + isBidRequestValid: function (bid) { const params = bid.params; if ( isPlainObject(params) && diff --git a/test/spec/modules/adtrgtmeBidAdapter_spec.js b/test/spec/modules/adtrgtmeBidAdapter_spec.js index a74844857ce..925f5763c5d 100644 --- a/test/spec/modules/adtrgtmeBidAdapter_spec.js +++ b/test/spec/modules/adtrgtmeBidAdapter_spec.js @@ -6,7 +6,7 @@ const DEFAULT_SID = '1220291391'; const DEFAULT_ZID = '1836455615'; const DEFAULT_PIXEL_URL = 'https://cdn.adtarget.me/libs/1x1.gif'; const DEFAULT_BANNER_URL = 'https://cdn.adtarget.me/libs/banner/300x250.jpg'; -const BIDDER_VERSION = '1.0.5'; +const BIDDER_VERSION = '1.0.6'; const PREBIDJS_VERSION = '$prebid.version$'; const createBidRequest = ({bidId, adUnitCode, bidOverride, zid, ortb2}) => { @@ -211,7 +211,7 @@ describe('Adtrgtme Bid Adapter:', () => { BAD_VALUE.forEach(value => { it(`should determine bad bid for ${JSON.stringify(value)}`, () => { - expect(spec.isOK(value)).to.be.false; + expect(spec.isBidRequestValid(value)).to.be.false; }); }); @@ -224,7 +224,7 @@ describe('Adtrgtme Bid Adapter:', () => { OK_VALUE.forEach(value => { it(`should determine OK bid for ${JSON.stringify(value)}`, () => { - expect(spec.isOK(value)).to.be.true; + expect(spec.isBidRequestValid(value)).to.be.true; }); }); }); @@ -572,15 +572,6 @@ describe('Adtrgtme Bid Adapter:', () => { } }); - expect(data.source).to.deep.equal({ - ext: { - hb: 1, - bidderver: BIDDER_VERSION, - prebidjsver: PREBIDJS_VERSION - }, - fd: 1 - }); - expect(data.cur).to.deep.equal(['USD']); }); From 24b0af046fb31880a06d9746a6910152f9c16ef0 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 3 Apr 2025 18:15:31 +0000 Subject: [PATCH 1060/1097] Prebid 9.38.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4d10e72caf..74dc479bf0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.38.0-pre", + "version": "9.38.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.38.0-pre", + "version": "9.38.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 008af413d2a..81a42e17367 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.38.0-pre", + "version": "9.38.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From cb909c5b8cbed52b3b26d1bb65d510788823f5a0 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 3 Apr 2025 18:15:31 +0000 Subject: [PATCH 1061/1097] Increment version to 9.39.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74dc479bf0f..8d999fcef68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.38.0", + "version": "9.39.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.38.0", + "version": "9.39.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 81a42e17367..25b9acb3cc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.38.0", + "version": "9.39.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 5cbb466532e8bdcd4b01692c9264a9d4c01453bd Mon Sep 17 00:00:00 2001 From: Komal Kumari <169047654+pm-komal-kumari@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:30:57 +0530 Subject: [PATCH 1062/1097] PubMatic RTD : fixed unit test cases (#12962) * Fixed test cases for pubmatic RTD (cherry picked from commit 163fc56bc3ab69d92660e82c36a6cf8626367e29) * Add space --------- Co-authored-by: Komal Kumari --- test/spec/modules/pubmaticRtdProvider_spec.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/spec/modules/pubmaticRtdProvider_spec.js b/test/spec/modules/pubmaticRtdProvider_spec.js index 11ec378c700..8e9fc0681c6 100644 --- a/test/spec/modules/pubmaticRtdProvider_spec.js +++ b/test/spec/modules/pubmaticRtdProvider_spec.js @@ -296,7 +296,7 @@ describe('Pubmatic RTD Provider', () => { setFloorsConfig(validData); - expect(confStub.calledOnce).to.be.true; + expect(confStub.called).to.be.true; const calledWith = confStub.getCall(0).args[0]; expect(calledWith).to.have.nested.property('floors.data.currency', 'USD'); expect(calledWith).to.have.nested.property('floors.data.schema.fields[0]', 'mediaType'); @@ -306,7 +306,7 @@ describe('Pubmatic RTD Provider', () => { setFloorsConfig(null); expect(confStub.called).to.be.false; - expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.called).to.be.true; expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); }); @@ -314,15 +314,15 @@ describe('Pubmatic RTD Provider', () => { setFloorsConfig(undefined); expect(confStub.called).to.be.false; - expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.called).to.be.true; expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); }); - it('should log message when data is an empty object', () => { + it('should log message when data is an empty object ', () => { setFloorsConfig({}); expect(confStub.called).to.be.false; - expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.called).to.be.true; expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); }); @@ -330,7 +330,7 @@ describe('Pubmatic RTD Provider', () => { setFloorsConfig([]); expect(confStub.called).to.be.false; - expect(logMessageStub.calledOnce).to.be.true; + expect(logMessageStub.called).to.be.true; expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); }); @@ -349,7 +349,7 @@ describe('Pubmatic RTD Provider', () => { setFloorsConfig(floorData); - expect(confStub.calledOnce).to.be.true; + expect(confStub.called).to.be.true; const calledWith = confStub.getCall(0).args[0]; expect(calledWith.floors.data).to.deep.equal(floorData); }); @@ -430,7 +430,7 @@ describe('Pubmatic RTD Provider', () => { fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); await fetchFloorRules('publisherId', 'profileId'); - expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.called).to.be.true; expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching floors'); }); @@ -445,7 +445,7 @@ describe('Pubmatic RTD Provider', () => { fetchStub.rejects(new Error('Network Error')); await fetchFloorRules('publisherId', 'profileId'); - expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.called).to.be.true; expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching floors'); }); }); @@ -470,8 +470,8 @@ describe('Pubmatic RTD Provider', () => { fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200 })); await setPriceFloors('publisherId', 'profileId'); - expect(fetchStub.calledOnce).to.be.true; - expect(confStub.calledOnce).to.be.true; + expect(fetchStub.called).to.be.true; + expect(confStub.called).to.be.true; }); }); }); @@ -519,7 +519,7 @@ describe('Pubmatic RTD Provider', () => { _pubmaticFloorRulesPromiseMock = Promise.resolve(); pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); await _pubmaticFloorRulesPromiseMock; - expect(continueAuctionStub.calledOnce); + expect(continueAuctionStub.called); expect( continueAuctionStub.alwaysCalledWith( hookConfig From 76c34d983362448ad26ea5b9cf05b0b479b238f8 Mon Sep 17 00:00:00 2001 From: Maxim Yermolayev <47022046+PixelQuasar@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:10:52 +0300 Subject: [PATCH 1063/1097] Yandex Bid Adapter : pass document language as site.content.language ortb parameter (#12918) * Yandex bid adapter: Pass document language as 'banner-lang' query parameter Add an additional query parameter 'banner-lang' to handle the language of the page being viewed by the user * add documentLang enrichment parameter * set document lang to ortb.site.content.language in yandex bid adapter * minor fix * pass content language as language fallback in yandex bid adapter * pass document language to request params instead of site.content.language * minor fixes * minor fix * use deepSetValue to store documentlang into site content lang * minor fixes * unit test fixes * minor fix * change ext.prebid.bidRequest.params.documentLang to site.ext.data.documentLang --- modules/yandexBidAdapter.js | 7 ++++ src/fpd/enrichment.js | 8 +++- src/utils.js | 4 ++ test/spec/fpd/enrichment_spec.js | 11 ++++++ test/spec/modules/yandexBidAdapter_spec.js | 43 ++++++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index eed0902e9dc..ffddfa0c2dd 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -175,6 +175,13 @@ export const spec = { device: ortb2?.device, }; + if (!data?.site?.content?.language) { + const documentLang = deepAccess(ortb2, 'site.ext.data.documentLang'); + if (documentLang) { + deepSetValue(data, 'site.content.language', documentLang); + } + } + const eids = deepAccess(bidRequest, 'userIdAsEids'); if (eids && eids.length) { deepSetValue(data, 'user.ext.eids', eids); diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 97f9e434128..51c57a88f44 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -1,7 +1,7 @@ import {hook} from '../hook.js'; import {getRefererInfo, parseDomain} from '../refererDetection.js'; import {findRootDomain} from './rootDomain.js'; -import {deepSetValue, getDefinedParams, getDNT, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; +import {deepSetValue, getDefinedParams, getDNT, getDocument, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; import {config} from '../config.js'; import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; import {PbPromise} from '../utils/promise.js'; @@ -18,6 +18,7 @@ export const dep = { getWindowSelf, getHighEntropySUA, getLowEntropySUA, + getDocument }; const oneClient = clientSectionChecker('FPD') @@ -51,6 +52,11 @@ export const enrichFPD = hook('sync', (fpd) => { deepSetValue(ortb2, 'device.ext', Object.assign({}, ext, ortb2.device.ext)); } + const documentLang = dep.getDocument().documentElement.lang; + if (documentLang) { + deepSetValue(ortb2, 'site.ext.data.documentLang', documentLang); + } + ortb2 = oneClient(ortb2); for (let section of CLIENT_SECTIONS) { if (hasSection(ortb2, section)) { diff --git a/src/utils.js b/src/utils.js index 619b9cdc144..b071dff4fe2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -200,6 +200,10 @@ export function getWindowLocation() { return window.location; } +export function getDocument() { + return document; +} + export function canAccessWindowTop() { try { if (internal.getWindowTop().location.href) { diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 5bf1dbc22a4..f9312b308ff 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -151,6 +151,17 @@ describe('FPD enrichment', () => { expect(ortb2.site.publisher.domain).to.eql('pub.com'); }); }); + + it('should pass documentElement.lang into bid request params', function () { + sandbox.stub(dep, 'getDocument').returns({ + documentElement: { + lang: 'fr-FR' + } + }); + return fpd().then(ortb2 => { + expect(ortb2.site.ext.data.documentLang).to.equal('fr-FR'); + }); + }); }); describe('device', () => { diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index e6b2f5cc1f0..15b000f562f 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -42,6 +42,49 @@ describe('Yandex adapter', function () { }); describe('buildRequests', function () { + let mockBidRequests; + let mockBidderRequest; + + beforeEach(function () { + mockBidRequests = [{ + bidId: 'bid123', + params: { + placementId: 'R-I-123456-2', + } + }]; + mockBidderRequest = { + ortb2: { + device: { + language: 'fr' + }, + site: { + ext: { + data: { + documentLang: 'en' + } + } + } + } + }; + }); + + it('should set site.content.language from document language if it is not set', function () { + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + expect(requests[0].data.site.content.language).to.equal('en'); + }); + + it('should preserve existing site.content.language if it is set', function () { + mockBidderRequest.ortb2.site.content = {language: 'es'}; + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + expect(requests[0].data.site.content.language).to.equal('es'); + }); + + it('should do nothing when document language does not exist', function () { + delete mockBidderRequest.ortb2.site.ext.data.documentLang; + const requests = spec.buildRequests(mockBidRequests, mockBidderRequest); + expect(requests[0].data.site?.content?.language).to.be.undefined; + }); + /** @type {import('../../../src/auction').BidderRequest} */ const bidderRequest = { ortb2: { From 46a349a405dd0b40e4969eecd9fa848fe6272c8a Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Tue, 8 Apr 2025 15:19:34 +0200 Subject: [PATCH 1064/1097] placement support added (#12953) Co-authored-by: Gabriel Chicoye --- modules/nexx360BidAdapter.js | 58 +- modules/nexx360BidAdapter.md | 2 +- test/spec/modules/nexx360BidAdapter_spec.js | 806 +++++++++++--------- 3 files changed, 467 insertions(+), 399 deletions(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 4d63e739cba..a2c1d57ffae 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -22,7 +22,7 @@ const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstre const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '4.3'; +const BIDDER_VERSION = '5.0'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; @@ -35,7 +35,7 @@ const ALIASES = [ { code: 'pubtech' }, { code: '1accord', gvlid: 965 }, { code: 'easybid', gvlid: 1068 }, - { code: 'prismassp', gvlid: 965 }, + { code: 'prismassp', gvlid: 965 } ]; export const storage = getStorageManager({ @@ -66,15 +66,9 @@ export function getNexx360LocalStorage() { } } -function getAdContainer(container) { - if (document.getElementById(container)) { - return document.getElementById(container); - } -} - /** * Get the AMX ID - * @return {string | false } false if localstorageNotEnabled + * @return { string | false } false if localstorageNotEnabled */ export function getAmxId() { if (!storage.localStorageIsEnabled()) { @@ -91,40 +85,36 @@ const converter = ortbConverter({ ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) }, imp(buildImp, bidRequest, context) { - // console.log(bidRequest, context); const imp = buildImp(bidRequest, context); - const tagid = bidRequest.params.adUnitName ? bidRequest.params.adUnitName : bidRequest.adUnitCode; - deepSetValue(imp, 'tagid', tagid); + deepSetValue(imp, 'tagid', bidRequest.adUnitCode); deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); - const divId = bidRequest.params.divId ? bidRequest.params.divId : bidRequest.adUnitCode; + if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); + if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); + const divId = bidRequest.params.divId || bidRequest.adUnitCode; deepSetValue(imp, 'ext.divId', divId); - const slotEl = getAdContainer(divId); + const slotEl = document.getElementById(divId); if (slotEl) { deepSetValue(imp, 'ext.dimensions.slotW', slotEl.offsetWidth); deepSetValue(imp, 'ext.dimensions.slotH', slotEl.offsetHeight); deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); } - deepSetValue(imp, 'ext.nexx360', bidRequest.params.tagId); - deepSetValue(imp, 'ext.nexx360.tagId', bidRequest.params.tagId); - deepSetValue(imp, 'ext.nexx360.videoTagId', bidRequest.params.videoTagId); - deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); + if (bidRequest.params.tagId) deepSetValue(imp, 'ext.nexx360.tagId', bidRequest.params.tagId); + if (bidRequest.params.placement) deepSetValue(imp, 'ext.nexx360.placement', bidRequest.params.placement); + if (bidRequest.params.videoTagId) deepSetValue(imp, 'ext.nexx360.videoTagId', bidRequest.params.videoTagId); + if (bidRequest.params.allBids) deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); if (imp.video) { const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); deepSetValue(imp, 'video.ext.playerSize', playerSize); deepSetValue(imp, 'video.ext.context', videoContext); } - - if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); - if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); return imp; }, request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); const nexx360LocalStorage = getNexx360LocalStorage(); if (nexx360LocalStorage) { - deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); deepSetValue(request, 'ext.localStorage.nexx360Id', nexx360LocalStorage.nexx360Id); } const amxId = getAmxId(); @@ -174,8 +164,8 @@ function isBidRequestValid(bid) { logError('bid.params.allBids needs to be a boolean'); return false; } - if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId) { - logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId must be defined'); + if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId && !bid.params.placement) { + logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId or bid.params.placement must be defined'); return false; } return true; @@ -212,16 +202,17 @@ function interpretResponse(serverResponse) { const { bidderSettings } = getGlobal(); const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; - let bids = []; - respBody.seatbid.forEach(seatbid => { - bids = [...bids, ...seatbid.bid.map(bid => { + let responses = []; + for (let i = 0; i < respBody.seatbid.length; i++) { + const seatbid = respBody.seatbid[i]; + for (let j = 0; j < seatbid.bid.length; j++) { + const bid = seatbid.bid[j]; const response = { requestId: bid.impid, cpm: bid.price, width: bid.w, height: bid.h, creativeId: bid.crid, - dealId: bid.dealid, currency: respBody.cur, netRevenue: true, ttl: 120, @@ -231,6 +222,7 @@ function interpretResponse(serverResponse) { demandSource: bid.ext.ssp, }, }; + if (bid.dealid) response.dealid = bid.dealid; if (allowAlternateBidderCodes) response.bidderCode = `n360_${bid.ext.ssp}`; if (bid.ext.mediaType === BANNER) { @@ -244,7 +236,7 @@ function interpretResponse(serverResponse) { if (bid.ext.mediaType === OUTSTREAM) { response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); - response.divId = bid.ext.divId + if (bid.ext.divId) response.divId = bid.ext.divId }; if (bid.ext.mediaType === NATIVE) { try { @@ -253,10 +245,10 @@ function interpretResponse(serverResponse) { } } catch (e) {} } - return response; - })]; - }); - return bids; + responses.push(response); + } + } + return responses; } /** diff --git a/modules/nexx360BidAdapter.md b/modules/nexx360BidAdapter.md index 5935b568a13..d9038bfc0b1 100644 --- a/modules/nexx360BidAdapter.md +++ b/modules/nexx360BidAdapter.md @@ -10,7 +10,7 @@ Maintainer: gabriel@nexx360.io Connects to Nexx360 network for bids. -To use us as a bidder you must have an account and an active "tagId" on our Nexx360 platform. +To use us as a bidder you must have an account and an active "tagId" or "placement" on our Nexx360 platform. # Test Parameters diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 07d5313a788..b8bed5f01de 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -5,271 +5,180 @@ import { import { sandbox } from 'sinon'; import { getAmxId } from '../../../modules/nexx360BidAdapter'; -const instreamResponse = { - 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', - 'cur': 'USD', - 'seatbid': [ - { - 'bid': [ - { - 'id': '8275140264321181514', - 'impid': '263cba3b8bfb72', - 'price': 5, - 'adomain': [ - 'appnexus.com' - ], - 'crid': '97517771', - 'h': 1, - 'w': 1, - 'adm': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ', - 'ext': { - 'mediaType': 'instream', - 'ssp': 'appnexus', - 'divId': 'video1', - 'adUnitCode': 'video1', - } - } - ], - 'seat': 'appnexus' - } - ], - 'ext': { - 'cookies': [] - } -}; - -describe('Nexx360 bid adapter tests', function () { - const DISPLAY_BID_REQUEST = { - 'id': '77b3f21a-e0df-4495-8bce-4e8a1d2309c1', - 'imp': [ - {'id': '2b4d8fc1c1c7ea', - 'tagid': 'div-1', - 'ext': {'divId': 'div-1', 'nexx360': {'account': '1067', 'tag_id': 'luvxjvgn'}}, - 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}], 'topframe': 1}}, {'id': '38fc428ab96638', 'tagid': 'div-2', 'ext': {'divId': 'div-2', 'nexx360': {'account': '1067', 'tag_id': 'luvxjvgn'}}, 'banner': {'format': [{'w': 728, 'h': 90}, {'w': 970, 'h': 250}], 'topframe': 1}}], - 'cur': ['USD'], - 'at': 1, - 'tmax': 3000, - 'site': {'page': 'https://test.nexx360.io/adapter/index.html?nexx360_test=1', 'domain': 'test.nexx360.io'}, - 'regs': {'coppa': 0, 'ext': {'gdpr': 1}}, - 'device': { - 'dnt': 0, - 'h': 844, - 'w': 390, - 'ua': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', - 'language': 'fr' - }, - 'user': { - 'ext': { - 'consent': 'CPgocUAPgocUAAKAsAENCkCsAP_AAH_AAAqIJDtd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7994JEAEmGrcQBdmWODNoGEUCIEYVhIVQKACCgGFogMAHBwU7KwCfWECABAKAIwIgQ4AowIBAAAJAEhEAEgRYIAAARAIAAQAIhEIAGBgEFgBYGAQAAgGgYohQACBIQZEBEUpgQFQJBAa2VCCUF0hphAHWWAFBIjYqABEEgIrAAEBYOAYIkBKxYIEmKN8gBGCFAKJUK1EAAAA.YAAAAAAAAAAA', - 'ConsentedProvidersSettings': {'consented_providers': '1~39.43.46.55.61.70.83.89.93.108.117.122.124.131.135.136.143.144.147.149.159.162.167.171.192.196.202.211.218.228.230.239.241.259.266.272.286.291.311.317.322.323.326.327.338.367.371.385.389.394.397.407.413.415.424.430.436.445.449.453.482.486.491.494.495.501.503.505.522.523.540.550.559.560.568.574.576.584.587.591.733.737.745.787.802.803.817.820.821.829.839.864.867.874.899.904.922.931.938.979.981.985.1003.1024.1027.1031.1033.1040.1046.1051.1053.1067.1085.1092.1095.1097.1099.1107.1127.1135.1143.1149.1152.1162.1166.1186.1188.1201.1205.1211.1215.1226.1227.1230.1252.1268.1270.1276.1284.1286.1290.1301.1307.1312.1345.1356.1364.1365.1375.1403.1415.1416.1419.1440.1442.1449.1455.1456.1465.1495.1512.1516.1525.1540.1548.1555.1558.1564.1570.1577.1579.1583.1584.1591.1603.1616.1638.1651.1653.1665.1667.1677.1678.1682.1697.1699.1703.1712.1716.1721.1725.1732.1745.1750.1765.1769.1782.1786.1800.1808.1810.1825.1827.1832.1838.1840.1842.1843.1845.1859.1866.1870.1878.1880.1889.1899.1917.1929.1942.1944.1962.1963.1964.1967.1968.1969.1978.2003.2007.2008.2027.2035.2039.2044.2047.2052.2056.2064.2068.2070.2072.2074.2088.2090.2103.2107.2109.2115.2124.2130.2133.2137.2140.2145.2147.2150.2156.2166.2177.2183.2186.2202.2205.2216.2219.2220.2222.2225.2234.2253.2264.2279.2282.2292.2299.2305.2309.2312.2316.2322.2325.2328.2331.2334.2335.2336.2337.2343.2354.2357.2358.2359.2370.2376.2377.2387.2392.2394.2400.2403.2405.2407.2411.2414.2416.2418.2425.2440.2447.2459.2461.2462.2465.2468.2472.2477.2481.2484.2486.2488.2493.2496.2497.2498.2499.2501.2510.2511.2517.2526.2527.2532.2534.2535.2542.2552.2563.2564.2567.2568.2569.2571.2572.2575.2577.2583.2584.2596.2601.2604.2605.2608.2609.2610.2612.2614.2621.2628.2629.2633.2634.2636.2642.2643.2645.2646.2647.2650.2651.2652.2656.2657.2658.2660.2661.2669.2670.2677.2681.2684.2686.2687.2690.2695.2698.2707.2713.2714.2729.2739.2767.2768.2770.2772.2784.2787.2791.2792.2798.2801.2805.2812.2813.2816.2817.2818.2821.2822.2827.2830.2831.2834.2838.2839.2840.2844.2846.2847.2849.2850.2852.2854.2856.2860.2862.2863.2865.2867.2869.2873.2874.2875.2876.2878.2880.2881.2882.2883.2884.2886.2887.2888.2889.2891.2893.2894.2895.2897.2898.2900.2901.2908.2909.2911.2912.2913.2914.2916.2917.2918.2919.2920.2922.2923.2924.2927.2929.2930.2931.2939.2940.2941.2947.2949.2950.2956.2961.2962.2963.2964.2965.2966.2968.2970.2973.2974.2975.2979.2980.2981.2983.2985.2986.2987.2991.2994.2995.2997.2999.3000.3002.3003.3005.3008.3009.3010.3012.3016.3017.3018.3019.3024.3025.3028.3034.3037.3038.3043.3045.3048.3052.3053.3055.3058.3059.3063.3065.3066.3068.3070.3072.3073.3074.3075.3076.3077.3078.3089.3090.3093.3094.3095.3097.3099.3104.3106.3109.3112.3117.3118.3119.3120.3124.3126.3127.3128.3130.3135.3136.3145.3149.3150.3151.3154.3155.3162.3163.3167.3172.3173.3180.3182.3183.3184.3185.3187.3188.3189.3190.3194.3196.3197.3209.3210.3211.3214.3215.3217.3219.3222.3223.3225.3226.3227.3228.3230.3231.3232.3234.3235.3236.3237.3238.3240.3241.3244.3245.3250.3251.3253.3257.3260.3268.3270.3272.3281.3288.3290.3292.3293.3295.3296.3300.3306.3307.3308.3314.3315.3316.3318.3324.3327.3328.3330'}, - 'eids': [{'source': 'id5-sync.com', - 'uids': [{'id': 'ID5*tdrSpYbccONIbxmulXFRLEil1aozZGGVMo9eEZgydgYoYFZQRYoae3wJyY0YtmXGKGJ7uXIQByQ6f7uzcpy9Oyhj1jGRzCf0BCoI4VkkKZIoZBubolUKUXXxOIdQOz7ZKGV0E3sqi9Zut0BbOuoJAihpLbgfNgDJ0xRmQw04rDooaxn7_TIPzEX5_L5ohNkUKG01Gnh2djvcrcPigKlk7ChwnauCwHIetHYI32yYAnAocYyqoM9XkoVOHtyOTC_UKHIR0qVBVIzJ1Nn_g7kLqyhzfosadKVvf7RQCsE6QrYodtpOJKg7i72-tnMXkzgmKHjh98aEDfTQrZOkKebmAyh6GlOHtYn_sZBFjJwtWp4oe9j2QTNbzK3G0jp1PlJqKHxiu4LawFEKJ3yi5-NFUyh-YkEalJUWyl1cDlWo5NQogAy2HM8N_w0qrVQgNbrTKIHK3KzTXztH7WzBgYrk8g', - 'atype': 1, - 'ext': {'linkType': 2}}]}, - {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}]}}, - 'ext': { - 'source': 'prebid.js', - 'version': '7.20.0-pre', - 'pageViewId': '5b970aba-51e9-4e0a-8299-f3f5618c695e' - }} - - const VIDEO_BID_REQUEST = [ - { - 'bidder': 'nexx360', - 'params': { - 'account': '1067', - 'tagId': 'yqsc1tfj' - }, - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'playerSize': [[640, 480]], - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5, 6], - 'playbackmethod': [2], - 'skip': 1 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '5434c81c-7210-44ae-9014-67c75dee48d0', - 'sizes': [[640, 480]], - 'bidId': '22f90541e576a3', - 'bidderRequestId': '1d4549243f3bfd', - 'auctionId': 'ed21b528-bcab-47e2-8605-ec9b71000c89', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ] - +describe('Nexx360 bid adapter tests', () => { const DEFAULT_OPTIONS = { gdprConsent: { gdprApplies: true, consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', - vendorData: {} + vendorData: {}, }, refererInfo: { referer: 'https://www.prebid.org', - canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page', }, uspConsent: '111222333', - userId: { 'id5id': { uid: '1111' } }, + userId: { id5id: { uid: '1111' } }, schain: { - 'ver': '1.0', - 'complete': 1, - 'nodes': [{ - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - }] + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com', + }], }, }; + - describe('isBidRequestValid()', function() { + describe('isBidRequestValid()', () => { let bannerBid; - beforeEach(function () { + beforeEach(() => { bannerBid = { - 'bidder': 'nexx360', - 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 600]]}}, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', + bidder: 'nexx360', + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + adUnitCode: 'div-1', + transactionId: '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + sizes: [[300, 250], [300, 600]], + bidId: '4906582fc87d0c', + bidderRequestId: '332fda16002dbe', + auctionId: '98932591-c822-42e3-850e-4b3cf748d063', } }); - it('We verify isBidRequestValid with unvalid adUnitName', function() { + it('We verify isBidRequestValid with unvalid adUnitName', () => { bannerBid.params = { adUnitName: 1 }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); }); - it('We verify isBidRequestValid with empty adUnitName', function() { + it('We verify isBidRequestValid with empty adUnitName', () => { bannerBid.params = { adUnitName: '' }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); }); - it('We verify isBidRequestValid with unvalid adUnitPath', function() { + it('We verify isBidRequestValid with unvalid adUnitPath', () => { bannerBid.params = { adUnitPath: 1 }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); }); - it('We verify isBidRequestValid with unvalid divId', function() { + it('We verify isBidRequestValid with unvalid divId', () => { bannerBid.params = { divId: 1 }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); }); - it('We verify isBidRequestValid unvalid allBids', function() { + it('We verify isBidRequestValid unvalid allBids', () => { bannerBid.params = { allBids: 1 }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); }); - it('We verify isBidRequestValid with uncorrect tagid', function() { + it('We verify isBidRequestValid with uncorrect tagid', () => { bannerBid.params = { 'tagid': 'luvxjvgn' }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); }); - it('We verify isBidRequestValid with correct tagId', function() { + it('We verify isBidRequestValid with correct tagId', () => { bannerBid.params = { 'tagId': 'luvxjvgn' }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); }); + + it('We verify isBidRequestValid with correct placement', () => { + bannerBid.params = { 'placement': 'testad' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); }); - describe('getNexx360LocalStorage disabled', function () { - before(function () { + describe('getNexx360LocalStorage disabled', () => { + before(() => { sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => false); }); - it('We test if we get the nexx360Id', function() { + it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); expect(output).to.be.eql(false); }); - after(function () { + after(() => { sandbox.restore() }); }) - describe('getNexx360LocalStorage enabled but nothing', function () { - before(function () { + describe('getNexx360LocalStorage enabled but nothing', () => { + before(() => { sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); sandbox.stub(storage, 'setDataInLocalStorage'); sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); }); - it('We test if we get the nexx360Id', function() { + it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); expect(typeof output.nexx360Id).to.be.eql('string'); }); - after(function () { + after(() => { sandbox.restore() }); }) - describe('getNexx360LocalStorage enabled but wrong payload', function () { - before(function () { + describe('getNexx360LocalStorage enabled but wrong payload', () => { + before(() => { sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); sandbox.stub(storage, 'setDataInLocalStorage'); sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); }); - it('We test if we get the nexx360Id', function() { + it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); expect(output).to.be.eql(false); }); - after(function () { + after(() => { sandbox.restore() }); }); - describe('getNexx360LocalStorage enabled', function () { - before(function () { + describe('getNexx360LocalStorage enabled', () => { + before(() => { sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); sandbox.stub(storage, 'setDataInLocalStorage'); sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); }); - it('We test if we get the nexx360Id', function() { + it('We test if we get the nexx360Id', () => { const output = getNexx360LocalStorage(); expect(output.nexx360Id).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); }); - after(function () { + after(() => { sandbox.restore() }); }); - describe('getAmxId() with localStorage enabled and data not set', function() { - before(function () { + describe('getAmxId() with localStorage enabled and data not set', () => { + before(() => { sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); sandbox.stub(storage, 'setDataInLocalStorage'); sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); }); - it('We test if we get the amxId', function() { + it('We test if we get the amxId', () => { const output = getAmxId(); expect(output).to.be.eql(false); }); - after(function () { + after(() => { sandbox.restore() }); }); - describe('getAmxId() with localStorage enabled and data set', function() { - before(function () { + describe('getAmxId() with localStorage enabled and data set', () => { + before(() => { sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); sandbox.stub(storage, 'setDataInLocalStorage'); sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'abcdef'); }); - it('We test if we get the amxId', function() { + it('We test if we get the amxId', () => { const output = getAmxId(); expect(output).to.be.eql('abcdef'); }); - after(function () { + after(() => { sandbox.restore() }); }); - describe('buildRequests()', function() { - before(function () { + describe('buildRequests()', () => { + before(() => { const documentStub = sandbox.stub(document, 'getElementById'); documentStub.withArgs('div-1').returns({ offsetWidth: 200, @@ -280,7 +189,7 @@ describe('Nexx360 bid adapter tests', function () { } }); }); - describe('We test with a multiple display bids', function() { + describe('We test with a multiple display bids', () => { const sampleBids = [ { bidder: 'nexx360', @@ -290,6 +199,11 @@ describe('Nexx360 bid adapter tests', function () { adUnitName: 'header-ad', adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', }, + ortb2Imp: { + ext: { + gpid: '/12345/nexx360/Homepage/HP/Header-Ad', + } + }, adUnitCode: 'header-ad-1234', transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', sizes: [[300, 250], [300, 600]], @@ -326,7 +240,7 @@ describe('Nexx360 bid adapter tests', function () { { bidder: 'nexx360', params: { - tagId: 'luvxjvgn', + placement: 'testPlacement', allBids: true, }, mediaTypes: { @@ -334,6 +248,7 @@ describe('Nexx360 bid adapter tests', function () { sizes: [[728, 90], [970, 250]] } }, + adUnitCode: 'div-2-abcd', transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', sizes: [[728, 90], [970, 250]], @@ -375,8 +290,8 @@ describe('Nexx360 bid adapter tests', function () { consentString: 'CPhdLUAPhdLUAAKAsAENCmCsAP_AAE7AAAqIJFNd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7997BIiAaADgAJYBnwEeAJXAXmAwQBj4DtgHcgPBAeKBIgAA.YAAAAAAAAAAA', } }; - it('We perform a test with 2 display adunits', function() { - const displayBids = [...sampleBids]; + it('We perform a test with 2 display adunits', () => { + const displayBids = structuredClone(sampleBids); displayBids[0].mediaTypes = { banner: { sizes: [[300, 250], [300, 600]] @@ -385,33 +300,76 @@ describe('Nexx360 bid adapter tests', function () { const request = spec.buildRequests(displayBids, bidderRequest); const requestContent = request.data; expect(request).to.have.property('method').and.to.equal('POST'); - expect(requestContent.cur[0]).to.be.eql('USD'); - expect(requestContent.imp.length).to.be.eql(2); - expect(requestContent.imp[0].id).to.be.eql('44a2706ac3574'); - expect(requestContent.imp[0].tagid).to.be.eql('header-ad'); - expect(requestContent.imp[0].ext.divId).to.be.eql('div-1'); - expect(requestContent.imp[0].ext.adUnitCode).to.be.eql('header-ad-1234'); - expect(requestContent.imp[0].ext.adUnitName).to.be.eql('header-ad'); - expect(requestContent.imp[0].ext.adUnitPath).to.be.eql('/12345/nexx360/Homepage/HP/Header-Ad'); - expect(requestContent.imp[0].ext.dimensions.slotW).to.be.eql(200); - expect(requestContent.imp[0].ext.dimensions.slotH).to.be.eql(250); - expect(requestContent.imp[0].ext.dimensions.cssMaxW).to.be.eql('400px'); - expect(requestContent.imp[0].ext.dimensions.cssMaxH).to.be.eql('350px'); - expect(requestContent.imp[0].ext.nexx360.tagId).to.be.eql('luvxjvgn'); - expect(requestContent.imp[0].banner.format.length).to.be.eql(2); - expect(requestContent.imp[0].banner.format[0].w).to.be.eql(300); - expect(requestContent.imp[0].banner.format[0].h).to.be.eql(250); - expect(requestContent.imp[1].ext.nexx360.allBids).to.be.eql(true); - expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); - expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); - expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); - expect(requestContent.ext.bidderVersion).to.be.eql('4.3'); - expect(requestContent.ext.source).to.be.eql('prebid.js'); + const expectedRequest = { + imp: [ + { + id: '44a2706ac3574', + banner: { + topframe: 0, + format: [ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + ], + }, + secure: 1, + tagid: 'header-ad-1234', + ext: { + adUnitCode: 'header-ad-1234', + gpid: '/12345/nexx360/Homepage/HP/Header-Ad', + divId: 'div-1', + dimensions: { + slotW: 200, + slotH: 250, + cssMaxW: '400px', + cssMaxH: '350px', + }, + nexx360: { + tagId: 'luvxjvgn', + }, + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', + }, + }, + { + id: '5ba94555219a03', + banner: { + topframe: 0, + format: [ + { w: 728, h: 90 }, + { w: 970, h: 250 }, + ], + }, + secure: 1, + tagid: 'div-2-abcd', + ext: { + adUnitCode: 'div-2-abcd', + divId: 'div-2-abcd', + nexx360: { + placement: 'testPlacement', + allBids: true, + }, + }, + }, + ], + id: requestContent.id, + test: 0, + ext: { + version: requestContent.ext.version, + source: 'prebid.js', + pageViewId: requestContent.ext.pageViewId, + bidderVersion: '5.0', + }, + cur: [ + 'USD', + ], + user: {}, + }; + expect(requestContent).to.be.eql(expectedRequest); }); if (FEATURES.VIDEO) { - it('We perform a test with a multiformat adunit', function() { - const multiformatBids = [...sampleBids]; + it('We perform a test with a multiformat adunit', () => { + const multiformatBids = structuredClone(sampleBids); multiformatBids[0].mediaTypes = { banner: { sizes: [[300, 250], [300, 600]] @@ -427,13 +385,24 @@ describe('Nexx360 bid adapter tests', function () { } }; const request = spec.buildRequests(multiformatBids, bidderRequest); - const requestContent = request.data; - expect(requestContent.imp[0].video.ext.context).to.be.eql('outstream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + const video = request.data.imp[0].video; + const expectedVideo = { + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + w: 640, + h: 480, + ext: { + playerSize: [640, 480], + context: 'outstream', + }, + }; + expect(video).to.eql(expectedVideo); }); - it('We perform a test with a instream adunit', function() { - const videoBids = [sampleBids[0]]; + it('We perform a test with a instream adunit', () => { + const videoBids = structuredClone(sampleBids); videoBids[0].mediaTypes = { video: { context: 'instream', @@ -449,265 +418,372 @@ describe('Nexx360 bid adapter tests', function () { expect(request).to.have.property('method').and.to.equal('POST'); expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }) + }); } }); - after(function () { + after(() => { sandbox.restore() }); }); - describe('interpretResponse()', function() { - it('empty response', function() { + describe('We test intepretResponse', () => { + it('empty response', () => { const response = { body: '' }; const output = spec.interpretResponse(response); expect(output.length).to.be.eql(0); }); - it('banner responses with adUrl only', function() { + it('banner responses with adUrl only', () => { const response = { body: { - 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', - 'cur': 'USD', - 'seatbid': [ + id: 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + cur: 'USD', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '4427551302944024629', - 'impid': '226175918ebeda', - 'price': 1.5, - 'adomain': [ - 'http://prebid.org' + id: '4427551302944024629', + impid: '226175918ebeda', + price: 1.5, + adomain: [ + 'http://prebid.org', ], - 'crid': '98493581', - 'ssp': 'appnexus', - 'h': 600, - 'w': 300, - 'cat': [ - 'IAB3-1' + crid: '98493581', + ssp: 'appnexus', + h: 600, + w: 300, + dealid: 'testdeal', + cat: [ + 'IAB3-1', ], - 'ext': { - 'adUnitCode': 'div-1', - 'mediaType': 'banner', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', - 'ssp': 'appnexus', - } - } + ext: { + adUnitCode: 'div-1', + mediaType: 'banner', + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + ssp: 'appnexus', + }, + }, ], - 'seat': 'appnexus' - } + seat: 'appnexus', + }, ], - 'ext': { - 'id': 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', - 'cookies': [] + ext: { + id: 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + cookies: [], }, - } + }, }; + const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].ext.adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].ext.mediaType); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); - }); - it('banner responses with adm', function() { + const expectedOutput = [{ + requestId: '226175918ebeda', + cpm: 1.5, + width: 300, + height: 600, + creativeId: '98493581', + currency: 'USD', + netRevenue: true, + dealid: 'testdeal', + ttl: 120, + mediaType: 'banner', + meta: { advertiserDomains: ['http://prebid.org'], demandSource: 'appnexus' }, + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + }]; + expect(output).to.eql(expectedOutput); + }); + it('banner responses with adm', () => { const response = { body: { - 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', - 'cur': 'USD', - 'seatbid': [ + id: 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + cur: 'USD', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '4427551302944024629', - 'impid': '226175918ebeda', - 'price': 1.5, - 'adomain': [ - 'http://prebid.org' + id: '4427551302944024629', + impid: '226175918ebeda', + price: 1.5, + adomain: [ + 'http://prebid.org', ], - 'crid': '98493581', - 'ssp': 'appnexus', - 'h': 600, - 'w': 300, - 'adm': '
TestAd
', - 'cat': [ - 'IAB3-1' + crid: '98493581', + ssp: 'appnexus', + h: 600, + w: 300, + adm: '
TestAd
', + cat: [ + 'IAB3-1', ], - 'ext': { - 'adUnitCode': 'div-1', - 'mediaType': 'banner', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', - 'ssp': 'appnexus', - } - } + ext: { + adUnitCode: 'div-1', + mediaType: 'banner', + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + ssp: 'appnexus', + }, + }, ], - 'seat': 'appnexus' - } + seat: 'appnexus', + }, ], - 'ext': { - 'id': 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', - 'cookies': [] + ext: { + id: 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + cookies: [], }, - } + }, }; const output = spec.interpretResponse(response); - expect(output[0].ad).to.be.eql(response.body.seatbid[0].bid[0].adm); - expect(output[0].adUrl).to.be.eql(undefined); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].ext.mediaType); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + const expectedOutput = [{ + requestId: '226175918ebeda', + cpm: 1.5, + width: 300, + height: 600, + creativeId: '98493581', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'http://prebid.org', + ], + demandSource: 'appnexus', + }, + ad: '
TestAd
', + }]; + expect(output).to.eql(expectedOutput); }); - it('instream responses', function() { + + it('instream responses', () => { const response = { body: { - 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', - 'cur': 'USD', - 'seatbid': [ + id: '2be64380-ba0c-405a-ab53-51f51c7bde51', + cur: 'USD', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '8275140264321181514', - 'impid': '263cba3b8bfb72', - 'price': 5, - 'adomain': [ - 'appnexus.com' + id: '8275140264321181514', + impid: '263cba3b8bfb72', + price: 5, + adomain: [ + 'appnexus.com', ], - 'crid': '97517771', - 'h': 1, - 'w': 1, - 'ext': { - 'mediaType': 'instream', - 'ssp': 'appnexus', - 'adUnitCode': 'video1', - 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' - } - } + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'instream', + ssp: 'appnexus', + adUnitCode: 'video1', + }, + }, ], - 'seat': 'appnexus' - } + seat: 'appnexus', + }, ], - 'ext': { - 'cookies': [] - } - } + ext: { + cookies: [], + }, + }, }; + const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].adm); - expect(output[0].mediaType).to.be.eql('video'); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + const expectedOutput = [{ + requestId: '263cba3b8bfb72', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + }]; + expect(output).to.eql(expectedOutput); }); - it('outstream responses', function() { + it('outstream responses', () => { const response = { body: { - 'id': '40c23932-135e-4602-9701-ca36f8d80c07', - 'cur': 'USD', - 'seatbid': [ + id: '40c23932-135e-4602-9701-ca36f8d80c07', + cur: 'USD', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '1186971142548769361', - 'impid': '4ce809b61a3928', - 'price': 5, - 'adomain': [ - 'appnexus.com' + id: '1186971142548769361', + impid: '4ce809b61a3928', + price: 5, + adomain: [ + 'appnexus.com', ], - 'crid': '97517771', - 'h': 1, - 'w': 1, - 'adm': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ', - 'ext': { - 'mediaType': 'outstream', - 'ssp': 'appnexus', - 'adUnitCode': 'div-1', - } - } + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'outstream', + ssp: 'appnexus', + adUnitCode: 'div-1', + }, + }, ], - 'seat': 'appnexus' - } + seat: 'appnexus', + }, ], - 'ext': { - 'cookies': [] - } - } + ext: { + cookies: [], + }, + }, }; + const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].adm); - expect(output[0].mediaType).to.be.eql('video'); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(typeof output[0].renderer).to.be.eql('object'); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + const expectedOutut = [{ + requestId: '4ce809b61a3928', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + renderer: output[0].renderer, + }]; + expect(output).to.eql(expectedOutut); }); - it('native responses', function() { + it('native responses', () => { const response = { body: { - 'id': '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', - 'cur': 'USD', - 'seatbid': [ + id: '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', + cur: 'USD', + seatbid: [ { - 'bid': [ + bid: [ { - 'id': '6624930625245272225', - 'impid': '23e11d845514bb', - 'price': 10, - 'adomain': [ - 'prebid.org' + id: '6624930625245272225', + impid: '23e11d845514bb', + price: 10, + adomain: [ + 'prebid.org', ], - 'crid': '97494204', - 'h': 1, - 'w': 1, - 'cat': [ - 'IAB3-1' + crid: '97494204', + h: 1, + w: 1, + cat: [ + 'IAB3-1', ], - 'ext': { - 'mediaType': 'native', - 'ssp': 'appnexus', - 'adUnitCode': '/19968336/prebid_native_example_1' + ext: { + mediaType: 'native', + ssp: 'appnexus', + adUnitCode: '/19968336/prebid_native_example_1', }, - 'adm': '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}' - } + adm: '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}', + }, ], - 'seat': 'appnexus' - } + seat: 'appnexus', + }, ], - 'ext': { - 'cookies': [], - } - } + ext: { + cookies: [], + }, + }, }; + const output = spec.interpretResponse(response); - expect(output[0].native.ortb.ver).to.be.eql('1.2'); - expect(output[0].native.ortb.assets[0].id).to.be.eql(1); - expect(output[0].mediaType).to.be.eql('native'); + const expectOutput = [{ + requestId: '23e11d845514bb', + cpm: 10, + width: 1, + height: 1, + creativeId: '97494204', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'native', + meta: { + advertiserDomains: [ + 'prebid.org', + ], + demandSource: 'appnexus', + }, + native: { + ortb: { + ver: '1.2', + assets: [ + { + id: 1, + img: { + url: 'https://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg', + w: 989, + h: 742, + ext: { + appnexus: { + prevent_crop: 0, + }, + }, + }, + }, + { + id: 0, + title: { + text: 'This is a Prebid Native Creative', + }, + }, + { + id: 2, + data: { + value: 'Prebid.org', + }, + }, + ], + link: { + url: 'https://ams3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA./bcr=AAAAAAAA8D8=/pp=${AUCTION_PRICE}/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA/cca=OTMyNSNBTVMzOjYxMzU=/bn=97062/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html', + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://ams3-ib.adnxs.com/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}', + }, + ], + }, + }, + }]; + expect(output).to.eql(expectOutput); }); }); - describe('getUserSyncs()', function() { + describe('getUserSyncs()', () => { const response = { body: { cookies: [] } }; - it('Verifies user sync without cookie in bid response', function () { - var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); + it('Verifies user sync without cookie in bid response', () => { + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); }); - it('Verifies user sync with cookies in bid response', function () { + it('Verifies user sync with cookies in bid response', () => { response.body.ext = { cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] }; - var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0]).to.have.property('type').and.to.equal('image'); - expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); + const expectedSyncs = [{ type: 'image', url: 'http://www.cookie.sync.org/' }]; + expect(syncs).to.eql(expectedSyncs); }); - it('Verifies user sync with no bid response', function() { + it('Verifies user sync with no bid response', () => { var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); + expect(syncs).to.eql([]); }); - it('Verifies user sync with no bid body response', function() { + it('Verifies user sync with no bid body response', () => { var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); + expect(syncs).to.eql([]); var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); + expect(syncs).to.eql([]); }); }); }); From e7a76494d4a5d81e84caa9fbeb4b345d1939f04e Mon Sep 17 00:00:00 2001 From: CondorXIO Date: Tue, 8 Apr 2025 20:32:51 +0700 Subject: [PATCH 1065/1097] CondorX Bid Adapter: add subid (#12972) * Add subid * Spacing --- modules/condorxBidAdapter.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/modules/condorxBidAdapter.js b/modules/condorxBidAdapter.js index 1ed87407852..45aff3ffb72 100644 --- a/modules/condorxBidAdapter.js +++ b/modules/condorxBidAdapter.js @@ -8,13 +8,17 @@ const API_URL = 'https://api.condorx.io/cxb/get.json'; const REQUEST_METHOD = 'GET'; const MAX_SIZE_DEVIATION = 0.05; const SUPPORTED_AD_SIZES = [ - [100, 100], [200, 200], [300, 250], [400, 200], [300, 200], [600, 600], [650, 1168], [236, 202], [1080, 1920], [300, 374] + [100, 100], [200, 200], [300, 250], [400, 200], [300, 200], [600, 600], [236, 202], [1080, 1920], [300, 374] ]; function getBidRequestUrl(bidRequest, bidderRequest) { if (bidRequest.params.url && bidRequest.params.url !== 'current url') { return bidRequest.params.url; } + return getBidderRequestUrl(bidderRequest); +} + +function getBidderRequestUrl(bidderRequest) { if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { return bidderRequest.refererInfo.page; } @@ -122,8 +126,20 @@ export const bidderSpec = { const widgetId = bidRequest.params.widget; const websiteId = bidRequest.params.website; const pageUrl = getBidRequestUrl(bidRequest, bidderRequest); + let subid; + try { + let url + try { + url = new URL(pageUrl); + } catch (e) { + url = new URL(getBidderRequestUrl(bidderRequest)) + } + subid = url.hostname; + } catch (e) { + subid = widgetId; + } const bidId = bidRequest.bidId; - let apiUrl = `${API_URL}?w=${websiteId}&wg=${widgetId}&u=${pageUrl}&p=0&ireqid=${bidId}&prebid=${mediaType}&imgw=${imageWidth}&imgh=${imageHeight}`; + let apiUrl = `${API_URL}?w=${websiteId}&wg=${widgetId}&u=${pageUrl}&s=${subid}&p=0&ireqid=${bidId}&prebid=${mediaType}&imgw=${imageWidth}&imgh=${imageHeight}`; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) { apiUrl += `&g=1&gc=${bidderRequest.consentString}`; } From 0f59e37f5c32da4bf4ecba82c32360bf6168eaf6 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 8 Apr 2025 15:55:55 +0200 Subject: [PATCH 1066/1097] AdagioBidAdapter: add support for instl, rwdd ortb2 signals (#12961) --- modules/adagioBidAdapter.js | 11 ++++ test/spec/modules/adagioBidAdapter_spec.js | 58 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index a837c54d1e1..6b1c182e7e6 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -643,6 +643,15 @@ export const spec = { bidRequest.gpid = gpid; } + let instl = deepAccess(bidRequest, 'ortb2Imp.instl'); + if (instl !== undefined) { + bidRequest.instl = instl === 1 || instl === '1' ? 1 : undefined; + } + let rwdd = deepAccess(bidRequest, 'ortb2Imp.rwdd'); + if (rwdd !== undefined) { + bidRequest.rwdd = rwdd === 1 || rwdd === '1' ? 1 : undefined; + } + // features are added by the adagioRtdProvider. const rawFeatures = { ...deepAccess(bidRequest, 'ortb2.site.ext.data.adg_rtd.features', {}), @@ -668,6 +677,8 @@ export const spec = { nativeParams: bidRequest.nativeParams, score: bidRequest.score, transactionId: bidRequest.transactionId, + instl: bidRequest.instl, + rwdd: bidRequest.rwdd, } return adUnit; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index e83437be397..e9cf58f3fed 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1076,6 +1076,64 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.device).to.deep.equal(expectedData); }); }); + + describe('with `rwdd` and `instl` signals', function() { + const tests = [ + { + n: 'Should set signals in bidRequest if value is 1', + ortb2Imp: { + rwdd: 1, + instl: '1' + }, + expected: { + rwdd: 1, + instl: 1 + } + }, + { + n: 'Should not set signals in bidRequest if value is 0', + ortb2Imp: { + rwdd: 0, + instl: '0' + }, + expected: { + rwdd: undefined, + instl: undefined + } + }, + { + n: 'Should not set if rwdd and instl are missformated', + ortb2Imp: { + rwdd: 'a', + ext: { instl: 1 } + }, + expected: { + rwdd: undefined, + instl: undefined + } + }, + { + n: 'Should not set rwdd and instl in bidRequest if undefined', + ortb2Imp: {}, + expected: { + rwdd: undefined, + instl: undefined + } + } + ] + + tests.forEach((t) => { + it(t.n, function() { + const bid01 = new BidRequestBuilder().withParams().build(); + bid01.ortb2Imp = t.ortb2Imp; + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); + const expected = t.expected; + expect(requests[0].data.adUnits[0].rwdd).to.equal(expected.rwdd); + expect(requests[0].data.adUnits[0].instl).to.equal(expected.instl); + }); + }) + }) }); describe('interpretResponse()', function() { From 063d88e2abcaf27cf25ece9c064994dc7d513eed Mon Sep 17 00:00:00 2001 From: Tommy Pettersen <42890605+TommyHPettersen@users.noreply.github.com> Date: Tue, 8 Apr 2025 16:07:56 +0200 Subject: [PATCH 1067/1097] added user agent and structured user agent to request (#12969) --- modules/koblerBidAdapter.js | 4 +++- test/spec/modules/koblerBidAdapter_spec.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index a5c14e7bce1..95ddb7f15d6 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -163,7 +163,9 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { cur: [SUPPORTED_CURRENCY], imp: imps, device: { - devicetype: getDevice() + devicetype: getDevice(), + ua: navigator.userAgent, + sua: validBidRequests[0]?.ortb2?.device?.sua }, site: { page: pageUrl, diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 8bee7c0e2cb..49ea4fc092a 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -544,7 +544,8 @@ describe('KoblerAdapter', function () { } ], device: { - devicetype: 2 + devicetype: 2, + ua: navigator.userAgent }, site: { page: 'bid.kobler.no' From 42aba01c1a8f616c07a7e137f5a4e534550ad0ae Mon Sep 17 00:00:00 2001 From: f-cali Date: Tue, 8 Apr 2025 16:37:21 +0200 Subject: [PATCH 1068/1097] MAINTAG-321 (#12976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue Switching Prebid Version – Citynews (76d8a3332520158) --- modules/onetagBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 9f5a49c8eba..4fe3bad5a5d 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -50,8 +50,8 @@ export function isValid(type, bid) { } else if (type === NATIVE) { if (typeof bid.mediaTypes.native !== 'object' || bid.mediaTypes.native === null) return false; - const assets = bid.mediaTypes.native?.ortb.assets; - const eventTrackers = bid.mediaTypes.native?.ortb.eventtrackers; + const assets = bid.mediaTypes.native?.ortb?.assets; + const eventTrackers = bid.mediaTypes.native?.ortb?.eventtrackers; let isValidAssets = false; let isValidEventTrackers = false; From 86acd347e1cf62ebc7921e8d3960d9cc187c94ef Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Tue, 8 Apr 2025 17:56:24 +0300 Subject: [PATCH 1069/1097] Missena Bid Adapter : refactor payload to use ORTB2 (#12977) --- modules/missenaBidAdapter.js | 21 +----------- test/spec/modules/missenaBidAdapter_spec.js | 36 ++++++++++++--------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 2a160d38d28..26a4f9dde0f 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -57,27 +57,9 @@ function toPayload(bidRequest, bidderRequest) { timeout: bidderRequest.timeout, }; - if (bidderRequest && bidderRequest.refererInfo) { - // TODO: is 'topmostLocation' the right value here? - payload.referer = bidderRequest.refererInfo.topmostLocation; - payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - payload.consent_string = bidderRequest.gdprConsent.consentString; - payload.consent_required = bidderRequest.gdprConsent.gdprApplies; - } - - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; payload.params = bidRequest.params; - if (bidRequest.ortb2?.device?.ext?.cdep) { - payload.cdep = bidRequest.ortb2?.device?.ext?.cdep; - } payload.userEids = bidRequest.userIdAsEids || []; payload.version = '$prebid.version$'; @@ -86,11 +68,10 @@ function toPayload(bidRequest, bidderRequest) { payload.floor_currency = bidFloor?.currency; payload.currency = getCurrencyFromBidderRequest(bidderRequest); payload.schain = bidRequest.schain; - payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0; payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; - payload.screen = { height: screen.height, width: screen.width }; payload.viewport = getViewportSize(); payload.sizes = normalizeBannerSizes(bidRequest.mediaTypes.banner.sizes); + payload.ortb2 = bidderRequest.ortb2; return { method: 'POST', diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index b2267c9110e..e85c4d2429e 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -7,6 +7,7 @@ import * as autoplay from 'libraries/autoplayDetection/autoplay.js'; const REFERRER = 'https://referer'; const REFERRER2 = 'https://referer2'; const COOKIE_DEPRECATION_LABEL = 'test'; +const CONSENT_STRING = 'AAAAAAAAA=='; const API_KEY = 'PA-XXXXXX'; describe('Missena Adapter', function () { @@ -63,11 +64,9 @@ describe('Missena Adapter', function () { }, }; - const consentString = 'AAAAAAAAA=='; - const bidderRequest = { gdprConsent: { - consentString: consentString, + consentString: CONSENT_STRING, gdprApplies: true, }, uspConsent: 'IDO', @@ -75,7 +74,17 @@ describe('Missena Adapter', function () { topmostLocation: REFERRER, canonicalUrl: 'https://canonical', }, - ortb2: { regs: { coppa: 1 } }, + ortb2: { + regs: { coppa: 1, ext: { gdpr: 1 }, us_privacy: 'IDO' }, + user: { + ext: { consent: CONSENT_STRING }, + }, + device: { + w: screen.width, + h: screen.height, + ext: { cdep: COOKIE_DEPRECATION_LABEL }, + }, + }, }; const bids = [bid, bidWithoutFloor]; @@ -119,12 +128,12 @@ describe('Missena Adapter', function () { }); it('should contain coppa', function () { - expect(payload.coppa).to.equal(1); + expect(payload.ortb2.regs.coppa).to.equal(1); }); sandbox.restore(); it('should contain uspConsent', function () { - expect(payload.us_privacy).to.equal('IDO'); + expect(payload.ortb2.regs.us_privacy).to.equal('IDO'); }); it('should contain schain', function () { @@ -151,19 +160,14 @@ describe('Missena Adapter', function () { expect(payload.params.formats).to.eql(['sticky-banner']); }); - it('should send referer information to the request', function () { - expect(payload.referer).to.equal(REFERRER); - expect(payload.referer_canonical).to.equal('https://canonical'); - }); - it('should send viewport', function () { expect(payload.viewport.width).to.equal(viewport.width); expect(payload.viewport.height).to.equal(viewport.height); }); it('should send gdpr consent information to the request', function () { - expect(payload.consent_string).to.equal(consentString); - expect(payload.consent_required).to.equal(true); + expect(payload.ortb2.user.ext.consent).to.equal(CONSENT_STRING); + expect(payload.ortb2.regs.ext.gdpr).to.equal(1); }); it('should send floor data', function () { expect(payload.floor).to.equal(3.5); @@ -179,8 +183,8 @@ describe('Missena Adapter', function () { }); it('should send screen', function () { - expect(payload.screen.width).to.equal(screen.width); - expect(payload.screen.height).to.equal(screen.height); + expect(payload.ortb2.device.w).to.equal(screen.width); + expect(payload.ortb2.device.h).to.equal(screen.height); }); it('should send size', function () { @@ -250,7 +254,7 @@ describe('Missena Adapter', function () { }); it('should send cookie deprecation', function () { - expect(payload.cdep).to.equal(COOKIE_DEPRECATION_LABEL); + expect(payload.ortb2.device.ext.cdep).to.equal(COOKIE_DEPRECATION_LABEL); }); }); From 39885b0f8dcc77ebbec678db8bdc4d7bef6a7fb3 Mon Sep 17 00:00:00 2001 From: hasanideepak <80700939+hasanideepak@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:22:17 +0530 Subject: [PATCH 1070/1097] Dochase Bid Adapter : Initial Release (#12803) * Audiencelogy Bid Adapter : Initial Release * removed duplicates * removed duplicates * added common code in library libraries/audiencelogyUtils * solved linter issues * imported getRequest from library and solved linting issue * solved trailing space issue * new adapter : lane4 * new adapter : lane4 * removed linter errors * removed trailing space * Dochase Bid Adapter : initial release * changed the comment --- modules/dochaseBidAdapter.js | 41 +++ modules/dochaseBidAdapter.md | 61 ++++ test/spec/modules/dochaseBidAdapter_spec.js | 322 ++++++++++++++++++++ 3 files changed, 424 insertions(+) create mode 100644 modules/dochaseBidAdapter.js create mode 100644 modules/dochaseBidAdapter.md create mode 100644 test/spec/modules/dochaseBidAdapter_spec.js diff --git a/modules/dochaseBidAdapter.js b/modules/dochaseBidAdapter.js new file mode 100644 index 00000000000..46b5b720f47 --- /dev/null +++ b/modules/dochaseBidAdapter.js @@ -0,0 +1,41 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, + getNativeResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const URL = 'https://rtb.dochaseadx.com/hb'; +// Export const spec +export const spec = { + code: 'dochase', + supportedMediaTypes: [BANNER, NATIVE], + // Determines whether given bid request is valid or not + isBidRequestValid: (bidRParam) => { + return !!(bidRParam.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: (bidRq, serverRq) => { + // Get Requests based on media types + return getBannerRequest(bidRq, serverRq, URL); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: (bidRes, bidReq) => { + let Response = {}; + const media = JSON.parse(bidReq.data)[0].MediaType; + if (media == BANNER) { + Response = getBannerResponse(bidRes, BANNER); + } else if (media == NATIVE) { + Response = getNativeResponse(bidRes, bidReq, NATIVE); + } + return Response; + } +} + +registerBidder(spec); diff --git a/modules/dochaseBidAdapter.md b/modules/dochaseBidAdapter.md new file mode 100644 index 00000000000..77355b4a7dd --- /dev/null +++ b/modules/dochaseBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Dochase Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dochaseops@dochase.com +``` + +# Description + +Dochase currently supports the BANNER and NATIVE type ads through prebid js + +Module that connects to Dochase's demand sources. + +# Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'dochase', + params: { + placement_id: 5550, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + }, + { + code: 'native-ad-container', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + bids: [ + { + bidder: 'dochase', + params: { + placement_id: 5551, // Required parameter + bid_floor: 1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/dochaseBidAdapter_spec.js b/test/spec/modules/dochaseBidAdapter_spec.js new file mode 100644 index 00000000000..2026f3b5811 --- /dev/null +++ b/test/spec/modules/dochaseBidAdapter_spec.js @@ -0,0 +1,322 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/dochaseBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('dochase adapter', function () { + let bannerRequest, nativeRequest; + let bannerResponse, nativeResponse, invalidBannerResponse, invalidNativeResponse; + + beforeEach(function () { + bannerRequest = [ + { + bidder: 'dochase', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placement_id: 5550, + bid_floor: 0.5 + } + } + ]; + nativeRequest = [ + { + bidder: 'dochase', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + params: { + placement_id: 5551, + bid_floor: 1 + } + } + ]; + bannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': "", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.dochase.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'dochase', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + nativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': '{\"native\":{\"ver\":\"1.1\",\"assets\": [{\"id\":1,\"required\":1,\"title\":{\"text\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":2,\"required\":1,\"img\":{\"url\":\"https://cdn.dochase.com/1_368852_0.png\",\"w\":500,\"h\":300,\"type\":\"3\"}},{\"id\":3,\"required\":0,\"data\":{\"value\":\"Diabetes In Control. A free weekly diabetes newsletter for Medical Professionals.\"}},{\"id\":4,\"required\":1,\"data\":{\"value\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":6,\"required\":1,\"data\":{\"value\":\"URL\"}}],\"link\":{\"url\":\"https://r.dochase.com/adx-rtb-d/servlet/WebF_AdManager.AdLinkManager?qs=H4sIAAAAAAAAAx2U2QHDIAxDVwKDr3F84P1HqNLPtMSRpSeG9RiPXH+5a474KzO/47YX7UoP50m61fLujlNjb76/8ZiblkimHq5nL/ZRedp3031x1tnk55LjSNN6h9/Zq+qmaLLuWTl74m1ZJKnb+m2OtQm/3L4sb933pM92qMOgjJ41MYmPXKnndRVKs+9bfSEumoZIFpTXuXbCP+WXuzl725E3O+9odi5OJrnBzhwjx9+UnFN3nTNt1/HY5aeljKtvZYpoJHNXr8BWa8ysKQY7ZmNA3DHK2qRwY7+zLu+xm9z5eheJ4Pv2usSptvO3p7JHrnXn0T5yVWdccp9Yz7hhoz2iu2zqsXsGFZ9hh14J6yU4TkJ0BgnOY8tY3tS+n2qsw7xZfKuanSNbAo+9nkJ83i20+FwhfbJeDVOllXsdxmDWauYcSRgS9+yG5qHwUDjAxxA0iZnOjlsnI+y09+ATeTEwbAVGgp0Qu/ceP0kjUvpu1Ty7O9MoegfrmLPxdjUh3mJL+XhARby+Ax8iBckf6BQdn9W+DMlvmlzYLuLlIy7YociFOIvXvEiYYCMboVk8BLHbnw3Zmr5us3xbjtXL67L96F15acJXkM5BOmTaUbBkYGdCI+Et8XmlpbuE3xVQwmxryc2y4wP3ByuuP8GogPZz8OpPaBv8diWWUTrC2nnLhdNUrJRTKc9FepDvwHTDwfbbMCTSb4LhUIFkyFrw/i7GtkPi6NCCai6N47TgNsTnzZWRoVtOSLq7FsLiF29y0Gj0GHVPVYG3QOPS7Swc3UuiFAQZJx3YvpHA2geUgVBASMEL4vcDi2Dw3NPtBSC4EQEvH/uMILu6WyUwraywTeVpoqoHTqOoD84FzReKoWemJy6jyuiBieGlQIe6wY2elTaMOwEUFF5NagzPj6nauc0+aXzQN3Q72hxFAgtfORK60RRAHYZLYymIzSJcXLgRFsqrb1UoD+5Atq7TWojaLTfOyUvH9EeJvZEOilQAXrf/ALoI8ZhABQAA\"},\"imptrackers\":[\"https://i.dochase.com/adx-rtb-d/servlet/WebF_AdManager.ImpCounter?price=${AUCTION_PRICE}&ids=5551,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=https%3A%2F%2Fqa-jboss.audiencelogy.com%2Ftn_native_prod.html\",\"https://i.dochase.com/adx-rtb-d/servlet/WebF_AdManager.ImpTracker?price=${AUCTION_PRICE}&ids=5551,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=\",\"https://rtb-east.dochase.com:9001/beacon?uid=44636f6605b06ec6d4389d6efb7e5054&cc=468132&fccap=5&nid=1\"]}}', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.dochase.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'dochase', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidBannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.dochase.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'dochase', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidNativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': 'invalid response', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.dochase.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'dochase', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'dochase', + params: { + placement_id: 5550 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'dochase', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Banner Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(bannerRequest), + bidRequest = spec.buildRequests(bannerRequest); + expect(bannerRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(bannerRequest); + expect(_Request.url).to.equal('https://rtb.dochaseadx.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(bannerRequest[0].bidId); + expect(data[0].placementId).to.equal(5550); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(bannerRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate banner response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(bannerRequest); + let bResponse = spec.interpretResponse(bannerResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(bannerRequest); + let response = spec.interpretResponse(invalidBannerResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('Validate Native Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(nativeRequest), + bidRequest = spec.buildRequests(nativeRequest); + expect(nativeRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(nativeRequest); + expect(_Request.url).to.equal('https://rtb.dochaseadx.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(nativeRequest[0].bidId); + expect(data[0].placementId).to.equal(5551); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(nativeRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate native response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(nativeRequest); + let bResponse = spec.interpretResponse(nativeResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(nativeResponse.body.seatbid[0].bid[0].impid); + // expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + // expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('native'); + expect(bResponse[0].native.clickUrl).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.impressionTrackers).to.be.an('array').with.length.above(0); + expect(bResponse[0].native.title).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.image.url).to.be.a('string').and.not.be.empty; + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['www.diabetesincontrol.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(nativeResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(nativeResponse.body.seatbid[0].bid[0].dealId); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); From 8d426403f02a9b2c4843853af49ddff8c111f92b Mon Sep 17 00:00:00 2001 From: Muhammad Ubaid <79584441+hi-ubaid@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:41:03 +0500 Subject: [PATCH 1071/1097] Bugfix: Update adRendering.js styling for iframe in case of insterstitial ads (#12975) * update styling for frame in case of instl ads * Revert "update styling for frame in case of instl ads" This reverts commit 0470efc2c45d4c4d5d0ce03ed4f95809ec4bfa2c. * AdRendering file update for interstitial iframe handling * Revert "AdRendering file update for interstitial iframe handling" This reverts commit 64739292537f292976f4f40271cd2d613ce23a87. * adding style without relying on frame id and display:block * Update style.width/height in direct rendering * Core: set instl flag on bid responses * Core: do not resize remote creative frames for interstitials * Core: include instl flag in cross-frame messages * Revert "adding style without relying on frame id and display:block" This reverts commit d957586c5e13d0a42827242f041af72d076c825f. * iframe handling for missing safeframes * core: resize interstitials' inner iframe --------- Co-authored-by: ubaid Co-authored-by: Demetrio Girardi --- creative/renderers/display/renderer.js | 8 +++++- .../creative-renderer-display/renderer.js | 2 +- src/adRendering.js | 18 +++++++++---- src/auction.js | 5 +++- src/secureCreatives.js | 5 +++- test/spec/auctionmanager_spec.js | 25 +++++++++++++++++++ test/spec/creative/displayRenderer_spec.js | 23 +++++++++++++++++ test/spec/unit/adRendering_spec.js | 6 +++++ test/spec/unit/secureCreatives_spec.js | 10 ++++++++ 9 files changed, 93 insertions(+), 9 deletions(-) diff --git a/creative/renderers/display/renderer.js b/creative/renderers/display/renderer.js index 2885148abf5..5a493bf1249 100644 --- a/creative/renderers/display/renderer.js +++ b/creative/renderers/display/renderer.js @@ -1,6 +1,6 @@ import {ERROR_NO_AD} from './constants.js'; -export function render({ad, adUrl, width, height}, {mkFrame}, win) { +export function render({ad, adUrl, width, height, instl}, {mkFrame}, win) { if (!ad && !adUrl) { throw { reason: ERROR_NO_AD, @@ -19,6 +19,12 @@ export function render({ad, adUrl, width, height}, {mkFrame}, win) { attrs.srcdoc = ad; } doc.body.appendChild(mkFrame(doc, attrs)); + if (instl && win.frameElement) { + // interstitials are rendered in a nested iframe that needs to be sized + const style = win.frameElement.style; + style.width = width ? `${width}px` : '100vw'; + style.height = height ? `${height}px` : '100vh'; + } } } diff --git a/libraries/creative-renderer-display/renderer.js b/libraries/creative-renderer-display/renderer.js index 442c751590e..9d9fbc7dfda 100644 --- a/libraries/creative-renderer-display/renderer.js +++ b/libraries/creative-renderer-display/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "(()=>{\"use strict\";window.render=function({ad:e,adUrl:t,width:n,height:d},{mkFrame:i},o){if(!e&&!t)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{if(null==d){const e=o.document?.body;[e,e?.parentElement].filter((e=>null!=e?.style)).forEach((e=>e.style.height=\"100%\"))}const r=o.document,s={width:n??\"100%\",height:d??\"100%\"};t&&!e?s.src=t:s.srcdoc=e,r.body.appendChild(i(r,s))}}})();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";window.render=function({ad:e,adUrl:t,width:n,height:i,instl:d},{mkFrame:r},s){if(!e&&!t)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{if(null==i){const e=s.document?.body;[e,e?.parentElement].filter((e=>null!=e?.style)).forEach((e=>e.style.height=\"100%\"))}const h=s.document,o={width:n??\"100%\",height:i??\"100%\"};if(t&&!e?o.src=t:o.srcdoc=e,h.body.appendChild(r(h,o)),d&&s.frameElement){const e=s.frameElement.style;e.width=n?`${n}px`:\"100vw\",e.height=i?`${i}px`:\"100vh\"}}}})();" \ No newline at end of file diff --git a/src/adRendering.js b/src/adRendering.js index ca25e920d2a..45a6d64b664 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -127,7 +127,7 @@ function creativeMessageHandler(deps) { } export const getRenderingData = hook('sync', function (bidResponse, options) { - const {ad, adUrl, cpm, originalCpm, width, height} = bidResponse + const {ad, adUrl, cpm, originalCpm, width, height, instl} = bidResponse const repl = { AUCTION_PRICE: originalCpm || cpm, CLICKTHROUGH: options?.clickUrl || '' @@ -136,7 +136,8 @@ export const getRenderingData = hook('sync', function (bidResponse, options) { ad: replaceMacros(ad, repl), adUrl: replaceMacros(adUrl, repl), width, - height + height, + instl }; }) @@ -256,9 +257,16 @@ export function renderAdDirect(doc, adId, options) { emitAdRenderFail(Object.assign({id: adId, bid}, {reason, message})); } function resizeFn(width, height) { - if (doc.defaultView && doc.defaultView.frameElement) { - width && (doc.defaultView.frameElement.width = width); - height && (doc.defaultView.frameElement.height = height); + const frame = doc.defaultView?.frameElement; + if (frame) { + if (width) { + frame.width = width; + frame.style.width && (frame.style.width = `${width}px`); + } + if (height) { + frame.height = height; + frame.style.height && (frame.style.height = `${height}px`); + } } } const messageHandler = creativeMessageHandler({resizeFn}); diff --git a/src/auction.js b/src/auction.js index af29997230c..c6c47769d54 100644 --- a/src/auction.js +++ b/src/auction.js @@ -636,8 +636,11 @@ function getPreparedBidForAuction(bid, {index = auctionManager.index} = {}) { // but others to not be set yet (like priceStrings). See #1372 and #1389. events.emit(EVENTS.BID_ADJUSTMENT, bid); + const adUnit = index.getAdUnit(bid); + bid.instl = adUnit?.ortb2Imp?.instl === 1; + // a publisher-defined renderer can be used to render bids - const bidRenderer = index.getBidRequest(bid)?.renderer || index.getAdUnit(bid).renderer; + const bidRenderer = index.getBidRequest(bid)?.renderer || adUnit.renderer; // a publisher can also define a renderer for a mediaType const bidObjectMediaType = bid.mediaType; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 9715279ff3d..aeb0ecc6a19 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -135,7 +135,10 @@ function handleEventRequest(reply, data, adObject) { return handleCreativeEvent(data, adObject); } -export function resizeRemoteCreative({adId, adUnitCode, width, height}) { +export function resizeRemoteCreative({instl, adId, adUnitCode, width, height}) { + // do not resize interstitials - the creative frame takes the full screen and sizing of the ad should + // be handled within it. + if (instl) return; function getDimension(value) { return value ? value + 'px' : '100%'; } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index d83c6c1d057..e2da420c939 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -1266,6 +1266,31 @@ describe('auctionmanager.js', function () { auction.callBids(); expect(auction.getBidsReceived()[0].ttlBuffer).to.eql(0); }); + + [ + { + request: 1, + response: true + }, + { + request: 0, + response: false + }, + { + request: 2, + response: false + }, + { + request: undefined, + response: false + } + ].forEach(({request, response}) => { + it(`sets bidResponse.instl to ${response} if adUnit.ortb2Imp.instl is ${request}`, () => { + adUnits[0].ortb2Imp = {instl: request}; + auction.callBids(); + expect(auction.getBidsReceived()[0].instl).to.equal(response); + }) + }) }); describe('when auction timeout is 20', function () { diff --git a/test/spec/creative/displayRenderer_spec.js b/test/spec/creative/displayRenderer_spec.js index 9e6a4bbad8c..a269d5f4e33 100644 --- a/test/spec/creative/displayRenderer_spec.js +++ b/test/spec/creative/displayRenderer_spec.js @@ -75,5 +75,28 @@ describe('Creative renderer - display', () => { runRenderer({ad: 'mock'}); expect(doc.body.style.height).to.eql('100%'); expect(doc.body.parentElement.style.height).to.eql('100%'); + }); + + it('sizes frame element if instl = true', () => { + win.frameElement = { style: {}}; + runRenderer({ + ad: 'mock', + width: 123, + height: 321, + instl: true + }); + expect(win.frameElement.style).to.eql({ + width: '123px', + height: '321px' + }) + }); + + it('does not choke if no frame element can be found', () => { + runRenderer({ + ad: 'mock', + width: 123, + height: 321, + instl: true + }); }) }) diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index 334475ab9f5..f447dd2d86a 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -63,6 +63,12 @@ describe('adRendering', () => { bidResponse = {}; }); + it('should carry over instl', () => { + bidResponse.instl = true; + const result = getRenderingData(bidResponse); + expect(result.instl).to.be.true; + }); + ['ad', 'adUrl'].forEach((prop) => { describe(`on ${prop}`, () => { it('replaces AUCTION_PRICE macro', () => { diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 7193278ab3d..1ec2c752fc6 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -579,5 +579,15 @@ describe('secureCreatives', () => { sinon.assert.called(slots[1].getSlotElementId); sinon.assert.calledWith(document.getElementById, 'div2'); }); + + it('should not resize interstitials', () => { + resizeRemoteCreative({ + instl: true, + adId: 'adId', + width: 300, + height: 250, + }); + sinon.assert.notCalled(document.getElementById); + }) }) }); From cfffce61dcfd040da43dd7d19e145ef9f9618ffb Mon Sep 17 00:00:00 2001 From: Sacha <35510349+thebraveio@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:23:44 +0300 Subject: [PATCH 1072/1097] Brave Bid Adapter : add support for bidfloor and eids (#12971) * eids and bidfloor support update BraveAdapter * eids and bidfloor support update BraveAdapter * fix points test on spec test file --------- Co-authored-by: root --- libraries/braveUtils/buildAndInterpret.js | 31 ++--- libraries/braveUtils/index.js | 121 +++++++++++++++--- modules/braveBidAdapter.js | 2 +- test/spec/modules/braveBidAdapter_spec.js | 12 +- .../modules/videoheroesBidAdapter_spec.js | 6 +- 5 files changed, 130 insertions(+), 42 deletions(-) diff --git a/libraries/braveUtils/buildAndInterpret.js b/libraries/braveUtils/buildAndInterpret.js index 66cd63896f7..8dda7f6060c 100644 --- a/libraries/braveUtils/buildAndInterpret.js +++ b/libraries/braveUtils/buildAndInterpret.js @@ -1,38 +1,35 @@ -import { isEmpty, parseUrl } from '../../src/utils.js'; +import { isEmpty } from '../../src/utils.js'; import {config} from '../../src/config.js'; -import { createNativeRequest, createBannerRequest, createVideoRequest } from './index.js'; +import { createNativeRequest, createBannerRequest, createVideoRequest, getFloor, prepareSite, prepareConsents, prepareEids } from './index.js'; import { convertOrtbRequestToProprietaryNative } from '../../src/native.js'; export const buildRequests = (validBidRequests, bidderRequest, endpointURL, defaultCur) => { - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); if (!validBidRequests.length || !bidderRequest) return []; + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const endpoint = endpointURL.replace('hash', validBidRequests[0].params.placementId); const imp = validBidRequests.map((br) => { - const impObject = { id: br.bidId, secure: 1 }; + const impObject = { id: br.bidId, secure: 1, bidfloor: getFloor(br, Object.keys(br.mediaTypes)[0]), defaultCur }; if (br.mediaTypes.banner) impObject.banner = createBannerRequest(br); else if (br.mediaTypes.video) impObject.video = createVideoRequest(br); else if (br.mediaTypes.native) impObject.native = { id: br.transactionId, ver: '1.2', request: createNativeRequest(br) }; return impObject; }); - const page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const data = { id: bidderRequest.bidderRequestId, cur: [defaultCur], - device: { w: screen.width, h: screen.height, language: navigator.language?.split('-')[0], ua: navigator.userAgent }, - site: { domain: parseUrl(page).hostname, page: page }, + device: bidderRequest.ortb2?.device || { w: screen.width, h: screen.height, language: navigator.language?.split('-')[0], ua: navigator.userAgent }, + site: prepareSite(validBidRequests[0], bidderRequest), tmax: bidderRequest.timeout, - imp, + regs: { ext: {}, coppa: config.getConfig('coppa') == true ? 1 : 0 }, + user: { ext: {} }, + imp }; - if (bidderRequest.refererInfo.ref) data.site.ref = bidderRequest.refererInfo.ref; - if (bidderRequest.gdprConsent) { - data.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; - data.user = { ext: { consent: bidderRequest.gdprConsent.consentString || '' } }; - } - if (bidderRequest.uspConsent) data.regs.ext.us_privacy = bidderRequest.uspConsent; - if (config.getConfig('coppa')) data.regs.coppa = 1; + prepareConsents(data, bidderRequest); + prepareEids(data, validBidRequests[0]); + if (validBidRequests[0].schain) data.source = { ext: { schain: validBidRequests[0].schain } }; return { method: 'POST', url: endpoint, data }; @@ -56,12 +53,12 @@ export const interpretResponse = (serverResponse, defaultCur, parseNative) => { netRevenue: true, creativeId: bid.crid, dealId: bid.dealid || null, - mediaType, + mediaType }; switch (mediaType) { case 'video': - bidObj.vastUrl = bid.adm; + bidObj.vastXml = bid.adm; break; case 'native': bidObj.native = parseNative(bid.adm); diff --git a/libraries/braveUtils/index.js b/libraries/braveUtils/index.js index 5756e09ae5c..26a045e9815 100644 --- a/libraries/braveUtils/index.js +++ b/libraries/braveUtils/index.js @@ -1,4 +1,5 @@ import { NATIVE_ASSETS, NATIVE_ASSETS_IDS } from './nativeAssets.js'; +import { isPlainObject, isArray, isArrayOfNums, parseUrl, isFn } from '../../src/utils.js'; /** * Builds a native request object based on the bid request @@ -40,8 +41,23 @@ export function createNativeRequest(br) { * @returns {object} The banner request object */ export function createBannerRequest(br) { - let size = br.mediaTypes.banner.sizes?.[0] || [300, 250]; - return { id: br.transactionId, w: size[0], h: size[1] }; + let [w, h] = [300, 250]; + let format = []; + + if (isArrayOfNums(br.mediaTypes.banner.sizes)) { + [w, h] = br.mediaTypes.banner.sizes; + format.push({ w, h }); + } else if (isArray(br.mediaTypes.banner.sizes)) { + [w, h] = br.mediaTypes.banner.sizes[0]; + if (br.mediaTypes.banner.sizes.length > 1) { format = br.mediaTypes.banner.sizes.map((size) => ({ w: size[0], h: size[1] })); } + } + + return { + w, + h, + format, + id: br.transactionId + } } /** @@ -50,19 +66,12 @@ export function createBannerRequest(br) { * @returns {object} The video request object */ export function createVideoRequest(br) { - let videoObj = { id: br.transactionId }; - const supportedParams = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity']; - - supportedParams.forEach((param) => { - if (br.mediaTypes.video[param] !== undefined) { - videoObj[param] = br.mediaTypes.video[param]; - } - }); + let videoObj = {...br.mediaTypes.video, id: br.transactionId}; - const playerSize = br.mediaTypes.video.playerSize; - if (playerSize) { - videoObj.w = Array.isArray(playerSize[0]) ? playerSize[0][0] : playerSize[0]; - videoObj.h = Array.isArray(playerSize[0]) ? playerSize[0][1] : playerSize[1]; + if (videoObj.playerSize) { + const size = Array.isArray(videoObj.playerSize[0]) ? videoObj.playerSize[0] : videoObj.playerSize; + videoObj.w = size[0]; + videoObj.h = size[1]; } else { videoObj.w = 640; videoObj.h = 480; @@ -70,7 +79,6 @@ export function createVideoRequest(br) { return videoObj; } - /** * Parses the native ad response * @param {object} adm - The native ad response @@ -93,3 +101,86 @@ export function parseNative(adm) { return bid; } + +/** + * Prepare Bid Floor for request + * @param {object} br - The bid request + * @param {string} mediaType - tyoe of media in request + * @param {string} defaultCur - currency which support bidder + * @returns {number} Parsed float bid floor price + */ +export function getFloor(br, mediaType, defaultCur) { + let floor = 0.05; + + if (!isFn(br.getFloor)) { + return floor; + } + + let floorObj = br.getFloor({ + currency: defaultCur, + mediaType, + size: '*' + }); + + if (isPlainObject(floorObj) && !isNaN(parseFloat(floorObj.floor))) { + floor = parseFloat(floorObj.floor) || floor; + } + + return floor; +} + +/** + * Builds site object + * @param {object} br - The bid request, request - bidderRequest data + * @param {object} request - bidderRequest data + * @returns {object} The site request object + */ +export function prepareSite(br, request) { + let siteObj = {}; + + siteObj.publisher = { + id: br.params.placementId.toString() + }; + + siteObj.domain = parseUrl(request.refererInfo.page || request.refererInfo.topmostLocation).hostname; + siteObj.page = request.refererInfo.page || request.refererInfo.topmostLocation; + + if (request.refererInfo.ref) { + siteObj.site.ref = request.refererInfo.ref; + } + + return siteObj; +} + +/** + * Adds privacy data to request object + * @param {object} data - The request object to bidder + * @param {object} request - bidderRequest data + * @returns {boolean} Response with true once finish + */ +export function prepareConsents(data, request) { + if (request.gdprConsent !== undefined) { + data.regs.ext.gdpr = request.gdprConsent.gdprApplies ? 1 : 0; + data.user.ext.consent = request.gdprConsent.consentString ? request.gdprConsent.consentString : ''; + } + + if (request.uspConsent !== undefined) { + data.regs.ext.us_privacy = request.uspConsent; + } + + return true; +} + +/** + * Adds Eids object to request object + * @param {object} data - The request object to bidder + * @param {object} br - The bid request + * @returns {boolean} Response with true once finish + */ +export function prepareEids(data, br) { + if (br.userIdAsEids !== undefined) { + data.user.ext.eids = br.userIdAsEids; + } + + return true; +} diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index 7689aade114..23fdfd43d7f 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -11,7 +11,7 @@ import { buildRequests, interpretResponse } from '../libraries/braveUtils/buildA const BIDDER_CODE = 'brave'; const DEFAULT_CUR = 'USD'; -const ENDPOINT_URL = `https://point.bravegroup.tv/?t=2&partner=hash`; +const ENDPOINT_URL = `https://point.braveglobal.tv/?t=2&partner=hash`; export const spec = { code: BIDDER_CODE, diff --git a/test/spec/modules/braveBidAdapter_spec.js b/test/spec/modules/braveBidAdapter_spec.js index 392f3b9f263..29c15bc0c40 100644 --- a/test/spec/modules/braveBidAdapter_spec.js +++ b/test/spec/modules/braveBidAdapter_spec.js @@ -197,7 +197,7 @@ describe('BraveBidAdapter', function() { }); it('Returns valid URL', function () { - expect(request.url).to.equal('https://point.bravegroup.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); + expect(request.url).to.equal('https://point.braveglobal.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); }); it('Returns empty data if no valid requests are passed', function () { @@ -221,7 +221,7 @@ describe('BraveBidAdapter', function() { }); it('Returns valid URL', function () { - expect(request.url).to.equal('https://point.bravegroup.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); + expect(request.url).to.equal('https://point.braveglobal.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); }); }); @@ -240,7 +240,7 @@ describe('BraveBidAdapter', function() { }); it('Returns valid URL', function () { - expect(request.url).to.equal('https://point.bravegroup.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); + expect(request.url).to.equal('https://point.braveglobal.tv/?t=2&partner=to0QI2aPgkbBZq6vgf0oHitouZduz0qw'); }); }); @@ -304,18 +304,18 @@ describe('BraveBidAdapter', function() { creativeId: response_video.seatbid[0].bid[0].crid, dealId: response_video.seatbid[0].bid[0].dealid, mediaType: 'video', - vastUrl: response_video.seatbid[0].bid[0].adm + vastXml: response_video.seatbid[0].bid[0].adm } let videoResponses = spec.interpretResponse(videoResponse); expect(videoResponses).to.be.an('array').that.is.not.empty; let dataItem = videoResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.vastUrl).to.equal(expectedBidResponse.vastUrl) + expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); expect(dataItem.netRevenue).to.be.true; diff --git a/test/spec/modules/videoheroesBidAdapter_spec.js b/test/spec/modules/videoheroesBidAdapter_spec.js index 8f99ca4d17d..1bdbebf36ab 100644 --- a/test/spec/modules/videoheroesBidAdapter_spec.js +++ b/test/spec/modules/videoheroesBidAdapter_spec.js @@ -304,18 +304,18 @@ describe('VideoheroesBidAdapter', function() { creativeId: response_video.seatbid[0].bid[0].crid, dealId: response_video.seatbid[0].bid[0].dealid, mediaType: 'video', - vastUrl: response_video.seatbid[0].bid[0].adm + vastXml: response_video.seatbid[0].bid[0].adm } let videoResponses = spec.interpretResponse(videoResponse); expect(videoResponses).to.be.an('array').that.is.not.empty; let dataItem = videoResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId', 'netRevenue', 'currency', 'dealId', 'mediaType'); expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.vastUrl).to.equal(expectedBidResponse.vastUrl) + expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); expect(dataItem.netRevenue).to.be.true; From 66aab4d3a875b65ad25f0429ab201b8d2051a3f6 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Wed, 9 Apr 2025 16:59:30 +0200 Subject: [PATCH 1073/1097] Core: Getting window dimensions unification (#12925) * Core: Getting window dimensions unification * event handler * method signature change * test fix * test fix * removing listeners, adding time check --------- Co-authored-by: Patrick McCann --- eslint.config.js | 7 ++- libraries/percentInView/percentInView.js | 10 +++- libraries/viewport/viewport.js | 14 +++-- modules/33acrossBidAdapter.js | 9 ++-- modules/AsteriobidPbmAnalyticsAdapter.js | 8 +-- modules/ablidaBidAdapter.js | 4 +- modules/adagioRtdProvider.js | 14 ++--- modules/adfBidAdapter.js | 7 +-- modules/adnuntiusBidAdapter.js | 9 ++-- modules/adspiritBidAdapter.js | 5 +- modules/agmaAnalyticsAdapter.js | 10 +--- modules/aidemBidAdapter.js | 6 +-- modules/byDataAnalyticsAdapter.js | 7 ++- modules/carodaBidAdapter.js | 7 ++- modules/chtnwBidAdapter.js | 6 ++- modules/connatixBidAdapter.js | 2 +- modules/contxtfulRtdProvider.js | 14 +++-- modules/datablocksBidAdapter.js | 15 +++--- modules/dianomiBidAdapter.js | 8 +-- modules/digitalMatterBidAdapter.js | 8 +-- modules/eplanningBidAdapter.js | 4 +- modules/goldbachBidAdapter.js | 5 +- modules/greenbidsBidAdapter.js | 6 +-- modules/gumgumBidAdapter.js | 6 +-- modules/h12mediaBidAdapter.js | 4 +- modules/hadronAnalyticsAdapter.js | 8 +-- modules/iasRtdProvider.js | 3 +- modules/impactifyBidAdapter.js | 6 +-- modules/insticatorBidAdapter.js | 6 +-- modules/invibesBidAdapter.js | 6 +-- modules/invisiblyAnalyticsAdapter.js | 8 +-- modules/jixieBidAdapter.js | 6 +-- modules/justpremiumBidAdapter.js | 30 +++-------- modules/lassoBidAdapter.js | 6 ++- modules/lemmaDigitalBidAdapter.js | 4 +- modules/livewrappedBidAdapter.js | 6 +-- modules/mantisBidAdapter.js | 6 ++- modules/marsmediaBidAdapter.js | 2 +- modules/medianetAnalyticsAdapter.js | 8 +-- modules/mgidBidAdapter.js | 7 +-- modules/missenaBidAdapter.js | 2 + modules/nextMillenniumBidAdapter.js | 9 ++-- modules/nobidBidAdapter.js | 7 +-- modules/omsBidAdapter.js | 2 +- modules/onetagBidAdapter.js | 19 +++---- modules/onomagicBidAdapter.js | 2 +- modules/seedtagBidAdapter.js | 6 +-- modules/sizeMappingV2.js | 13 ++--- modules/snigelBidAdapter.js | 6 ++- modules/sonobiBidAdapter.js | 4 +- modules/sspBCBidAdapter.js | 4 +- modules/stroeerCoreBidAdapter.js | 4 +- modules/teadsBidAdapter.js | 6 +-- modules/underdogmediaBidAdapter.js | 2 +- modules/undertoneBidAdapter.js | 7 +-- modules/vibrantmediaBidAdapter.js | 6 +-- modules/videojsVideoProvider.js | 5 +- modules/yieldloveBidAdapter.js | 4 +- modules/yieldmoBidAdapter.js | 5 +- src/fpd/enrichment.js | 10 ++-- src/utils.js | 52 ++++++++++++++++++- test/spec/fpd/enrichment_spec.js | 11 ++-- test/spec/modules/33acrossBidAdapter_spec.js | 8 +++ test/spec/modules/adnuntiusBidAdapter_spec.js | 7 +-- test/spec/modules/connatixBidAdapter_spec.js | 7 +++ .../spec/modules/contxtfulRtdProvider_spec.js | 5 +- test/spec/modules/eplanningBidAdapter_spec.js | 14 +++-- .../spec/modules/insticatorBidAdapter_spec.js | 5 +- .../modules/livewrappedBidAdapter_spec.js | 5 +- test/spec/modules/marsmediaBidAdapter_spec.js | 8 ++- test/spec/modules/medianetBidAdapter_spec.js | 2 + test/spec/modules/missenaBidAdapter_spec.js | 3 +- test/spec/modules/omsBidAdapter_spec.js | 9 +++- test/spec/modules/onomagicBidAdapter_spec.js | 4 ++ test/spec/modules/sizeMappingV2_spec.js | 29 ++++------- test/spec/modules/undertoneBidAdapter_spec.js | 6 +-- .../modules/vibrantmediaBidAdapter_spec.js | 7 +-- .../submodules/videojsVideoProvider_spec.js | 26 ++++++---- test/spec/utils_spec.js | 28 ++++++++++ 79 files changed, 400 insertions(+), 266 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index c17ba228561..e683c044e71 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -192,7 +192,12 @@ module.exports = [ { property: 'getBoundingClientRect', message: 'use libraries/boundingClientRect instead' - } + }, + ...['scrollTop', 'scrollLeft', 'innerHeight', 'innerWidth', 'visualViewport'].map((property) => ({ + object: 'window', + property, + message: 'use utils/getWinDimensions instead' + })) ] } }) diff --git a/libraries/percentInView/percentInView.js b/libraries/percentInView/percentInView.js index f1d17f933df..31e81e27745 100644 --- a/libraries/percentInView/percentInView.js +++ b/libraries/percentInView/percentInView.js @@ -1,3 +1,4 @@ +import { getWinDimensions } from '../../src/utils.js'; import { getBoundingClientRect } from '../boundingClientRect/boundingClientRect.js'; export function getBoundingBox(element, {w, h} = {}) { @@ -40,12 +41,17 @@ function getIntersectionOfRects(rects) { return bbox; } -export const percentInView = (element, topWin, {w, h} = {}) => { +export const percentInView = (element, {w, h} = {}) => { const elementBoundingBox = getBoundingBox(element, {w, h}); + const { innerHeight, innerWidth } = getWinDimensions(); + // Obtain the intersection of the element and the viewport const elementInViewBoundingBox = getIntersectionOfRects([{ - left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight + left: 0, + top: 0, + right: innerWidth, + bottom: innerHeight }, elementBoundingBox]); let elementInViewArea, elementTotalArea; diff --git a/libraries/viewport/viewport.js b/libraries/viewport/viewport.js index 0a2e1688405..6ac1f839a7b 100644 --- a/libraries/viewport/viewport.js +++ b/libraries/viewport/viewport.js @@ -1,11 +1,10 @@ -import {getWindowTop} from '../../src/utils.js'; +import {getWinDimensions, getWindowTop} from '../../src/utils.js'; export function getViewportCoordinates() { try { const win = getWindowTop(); - let { scrollY: top, scrollX: left, innerHeight, innerWidth } = win; - innerHeight = innerHeight || win.document.documentElement.clientWidth || win.document.body.clientWidth; - innerWidth = innerWidth || win.document.documentElement.clientHeight || win.document.body.clientHeight + let { scrollY: top, scrollX: left } = win; + const { height: innerHeight, width: innerWidth } = getViewportSize(); return { top, right: left + innerWidth, bottom: top + innerHeight, left }; } catch (e) { return {}; @@ -13,11 +12,10 @@ export function getViewportCoordinates() { } export function getViewportSize() { + const windowDimensions = getWinDimensions(); try { - const win = getWindowTop(); - let { innerHeight, innerWidth } = win; - innerHeight = innerHeight || win.document.documentElement.clientWidth || win.document.body.clientWidth; - innerWidth = innerWidth || win.document.documentElement.clientHeight || win.document.body.clientHeight + const innerHeight = windowDimensions.innerHeight || windowDimensions.document.documentElement.clientHeight || windowDimensions.document.body.clientHeight || 0; + const innerWidth = windowDimensions.innerWidth || windowDimensions.document.documentElement.clientWidth || windowDimensions.document.body.clientWidth || 0; return { width: innerWidth, height: innerHeight }; } catch (e) { return {}; diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 8f9d45c2969..9feca97d425 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import { deepAccess, + getWinDimensions, getWindowSelf, getWindowTop, isArray, @@ -548,7 +549,7 @@ function _isViewabilityMeasurable(element) { function _getViewability(element, topWin, { w, h } = {}) { return topWin.document.visibilityState === 'visible' - ? percentInView(element, topWin, { w, h }) + ? percentInView(element, { w, h }) : 0; } @@ -749,11 +750,7 @@ function getViewportDimensions() { } function getScreenDimensions() { - const { - innerWidth: windowWidth, - innerHeight: windowHeight, - screen - } = getWindowSelf(); + const { innerWidth: windowWidth, innerHeight: windowHeight, screen } = getWinDimensions(); const [biggerDimension, smallerDimension] = [ Math.max(screen.width, screen.height), diff --git a/modules/AsteriobidPbmAnalyticsAdapter.js b/modules/AsteriobidPbmAnalyticsAdapter.js index 7f56f5064b7..7f76a96101f 100644 --- a/modules/AsteriobidPbmAnalyticsAdapter.js +++ b/modules/AsteriobidPbmAnalyticsAdapter.js @@ -5,6 +5,7 @@ import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import { EVENTS } from '../src/constants.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager @@ -25,12 +26,7 @@ let flushInterval; var pmAnalyticsEnabled = false; const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; -var w = window; -var d = document; -var e = d.documentElement; -var g = d.getElementsByTagName('body')[0]; -var x = (w && w.innerWidth) || (e && e.clientWidth) || (g && g.clientWidth); -var y = (w && w.innerHeight) || (e && e.clientHeight) || (g && g.clientHeight); +const {width: x, height: y} = getViewportSize(); var _pageView = { eventType: 'pageView', diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index 175d5ff7c72..3881d06f81a 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -2,6 +2,7 @@ import {triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -95,7 +96,6 @@ export const spec = { function getDevice() { const ua = navigator.userAgent; - const topWindow = window.top; if ((/(ipad|xoom|sch-i800|playbook|silk|tablet|kindle)|(android(?!.*mobi))/i).test(ua)) { return 'tablet'; } @@ -105,7 +105,7 @@ function getDevice() { if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Windows\sCE|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/i).test(ua)) { return 'smartphone'; } - const width = topWindow.innerWidth || topWindow.document.documentElement.clientWidth || topWindow.document.body.clientWidth; + const { width } = getViewportSize(); if (width > 320) { return 'desktop'; } diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index ab048ec1e05..9c0e8aabed9 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -18,6 +18,7 @@ import { getDomLoadingDuration, getSafeframeGeometry, getUniqueIdentifierStr, + getWinDimensions, getWindowSelf, getWindowTop, inIframe, @@ -523,12 +524,13 @@ function getSlotPosition(divId) { let box = getBoundingClientRect(domElement); - const docEl = d.documentElement; + const windowDimensions = getWinDimensions(); + const body = d.body; const clientTop = d.clientTop || body.clientTop || 0; const clientLeft = d.clientLeft || body.clientLeft || 0; - const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; + const scrollTop = wt.pageYOffset || windowDimensions.document.documentElement.scrollTop || windowDimensions.document.body.scrollTop; + const scrollLeft = wt.pageXOffset || windowDimensions.document.documentElement.scrollLeft || windowDimensions.document.body.scrollLeft; const elComputedStyle = wt.getComputedStyle(domElement, null); const mustDisplayElement = elComputedStyle.display === 'none'; @@ -585,9 +587,9 @@ function getViewPortDimensions() { viewportDims.h = Math.round(win.h); } else { // window.top based computing - const wt = getWindowTop(); - viewportDims.w = wt.innerWidth; - viewportDims.h = wt.innerHeight; + const { innerWidth, innerHeight } = getWinDimensions(); + viewportDims.w = innerWidth; + viewportDims.h = innerHeight; } return `${viewportDims.w}x${viewportDims.h}`; diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index 70cd526065c..d3e8e05848b 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {deepAccess, deepClone, deepSetValue, mergeDeep, parseSizesInput, setOnAny} from '../src/utils.js'; +import {deepAccess, deepClone, deepSetValue, getWinDimensions, mergeDeep, parseSizesInput, setOnAny} from '../src/utils.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; @@ -55,8 +55,9 @@ export const spec = { if (commonFpd.device) { mergeDeep(device, commonFpd.device); } - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; + const { innerWidth, innerHeight } = getWinDimensions(); + device.w = device.w || innerWidth; + device.h = device.h || innerHeight; device.ua = device.ua || navigator.userAgent; let source = commonFpd.source || {}; diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index cf56a16cf5f..ceeb3ae1d39 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop, deepClone} from '../src/utils.js'; +import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop, deepClone, getWinDimensions} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import {toLegacyResponse, toOrtbNativeRequest} from '../src/native.js'; @@ -289,8 +289,11 @@ export const spec = { if (win.screen && win.screen.availHeight) { queryParamsAndValues.push('screen=' + win.screen.availWidth + 'x' + win.screen.availHeight); } - if (win.innerWidth) { - queryParamsAndValues.push('viewport=' + win.innerWidth + 'x' + win.innerHeight); + + const { innerWidth, innerHeight } = getWinDimensions(); + + if (innerWidth) { + queryParamsAndValues.push('viewport=' + innerWidth + 'x' + innerHeight); } const searchParams = new URLSearchParams(window.location.search); diff --git a/modules/adspiritBidAdapter.js b/modules/adspiritBidAdapter.js index c39ceca8600..18325186eb8 100644 --- a/modules/adspiritBidAdapter.js +++ b/modules/adspiritBidAdapter.js @@ -21,6 +21,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { let requests = []; + const windowDimensions = utils.getWinDimensions(); for (let i = 0; i < validBidRequests.length; i++) { let bidRequest = validBidRequests[i]; bidRequest.adspiritConId = spec.genAdConId(bidRequest); @@ -30,8 +31,8 @@ export const spec = { '&ref=' + encodeURIComponent(bidderRequest.refererInfo.topmostLocation) + '&scx=' + (screen.width) + '&scy=' + (screen.height) + - '&wcx=' + (window.innerWidth || document.documentElement.clientWidth) + - '&wcy=' + (window.innerHeight || document.documentElement.clientHeight) + + '&wcx=' + (windowDimensions.innerWidth || windowDimensions.document.documentElement.clientWidth) + + '&wcy=' + (windowDimensions.innerHeight || windowDimensions.document.documentElement.clientHeight) + '&async=' + bidRequest.adspiritConId + '&t=' + Math.round(Math.random() * 100000); diff --git a/modules/agmaAnalyticsAdapter.js b/modules/agmaAnalyticsAdapter.js index 40bafa483f8..cacdc5db976 100644 --- a/modules/agmaAnalyticsAdapter.js +++ b/modules/agmaAnalyticsAdapter.js @@ -4,8 +4,6 @@ import { logInfo, logError, getWindowSelf, - getWindowTop, - canAccessWindowTop, getPerformanceNow, isEmpty, isEmptyStr, @@ -16,6 +14,7 @@ import { EVENTS } from '../src/constants.js'; import adapterManager, { gdprDataHandler } from '../src/adapterManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { config } from '../src/config.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const GVLID = 1122; const ModuleCode = 'agma'; @@ -28,12 +27,7 @@ const pageViewId = generateUUID(); // Helper functions const getScreen = () => { try { - const win = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); - const d = document; - const e = d.documentElement; - const g = d.getElementsByTagName('body')[0]; - const x = win.innerWidth || e.clientWidth || g.clientWidth; - const y = win.innerHeight || e.clientHeight || g.clientHeight; + const {width: x, height: y} = getViewportSize(); return { x, y }; } catch (e) { return {x: 0, y: 0}; diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 07aee262baa..2f2c46942de 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, deepClone, deepSetValue, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; +import {deepAccess, deepClone, deepSetValue, getWinDimensions, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -144,8 +144,8 @@ function setPrebidRequestEnvironment(payload) { deepSetValue(payload, 'environment.inp.jp', window.JSON.parse.name === 'parse' && typeof window.JSON.parse.prototype === 'undefined'); deepSetValue(payload, 'environment.inp.ofe', window.Object.fromEntries.name === 'fromEntries' && typeof window.Object.fromEntries.prototype === 'undefined'); deepSetValue(payload, 'environment.inp.oa', window.Object.assign.name === 'assign' && typeof window.Object.assign.prototype === 'undefined'); - deepSetValue(payload, 'environment.wpar.innerWidth', window.innerWidth); - deepSetValue(payload, 'environment.wpar.innerHeight', window.innerHeight); + deepSetValue(payload, 'environment.wpar.innerWidth', getWinDimensions().innerWidth); + deepSetValue(payload, 'environment.wpar.innerHeight', getWinDimensions().innerHeight); } function hasValidMediaType(bidRequest) { diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index 508a5593f58..6e3135f5969 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { deepClone, logInfo, logError } from '../src/utils.js'; +import { deepClone, logInfo, logError, getWinDimensions } from '../src/utils.js'; import Base64 from 'crypto-js/enc-base64'; import hmacSHA512 from 'crypto-js/hmac-sha512'; import enc from 'crypto-js/enc-utf8'; @@ -9,6 +9,7 @@ import {getStorageManager} from '../src/storageManager.js'; import { auctionManager } from '../src/auctionManager.js'; import { ajax } from '../src/ajax.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const versionCode = '4.4.1' const secretKey = 'bydata@123456' @@ -261,7 +262,9 @@ ascAdapter.getVisitorData = function (data = {}) { return signedToken; } function detectWidth() { - return window.screen.width || (window.innerWidth && document.documentElement.clientWidth) ? Math.min(window.innerWidth, document.documentElement.clientWidth) : window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName('body')[0].clientWidth; + const {width: viewportWidth} = getViewportSize(); + const windowDimensions = getWinDimensions(); + return windowDimensions.screen.width || (windowDimensions.innerWidth && windowDimensions.document.documentElement.clientWidth) ? Math.min(windowDimensions.innerWidth, windowDimensions.document.documentElement.clientWidth) : viewportWidth; } function giveDeviceTypeOnScreenSize() { var _dWidth = detectWidth(); diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js index ebe4686630f..3060501ba8d 100644 --- a/modules/carodaBidAdapter.js +++ b/modules/carodaBidAdapter.js @@ -8,6 +8,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { deepAccess, deepSetValue, + getWinDimensions, logError, mergeDeep, sizeTupleToRtbSize, @@ -169,8 +170,10 @@ function getORTBCommon (bidderRequest) { } } const device = getConfig('device') || {}; - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; + const { innerWidth, innerHeight } = getWinDimensions(); + + device.w = device.w || innerWidth; + device.h = device.h || innerHeight; device.ua = device.ua || navigator.userAgent; return { app, diff --git a/modules/chtnwBidAdapter.js b/modules/chtnwBidAdapter.js index 5f77cec018a..97843a7074c 100644 --- a/modules/chtnwBidAdapter.js +++ b/modules/chtnwBidAdapter.js @@ -2,6 +2,7 @@ import { generateUUID, getDNT, _each, + getWinDimensions, } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -33,8 +34,9 @@ export const spec = { storage.setCookie(COOKIE_NAME, chtnwId); } const device = getConfig('device') || {}; - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; + const { innerWidth, innerHeight } = getWinDimensions(); + device.w = device.w || innerWidth; + device.h = device.h || innerHeight; device.ua = device.ua || navigator.userAgent; device.dnt = getDNT() ? 1 : 0; device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 649b4094dfa..15b74e1f814 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -107,7 +107,7 @@ export function _isViewabilityMeasurable(element) { export function _getViewability(element, topWin, { w, h } = {}) { return topWin.document.visibilityState === 'visible' - ? percentInView(element, topWin, { w, h }) + ? percentInView(element, { w, h }) : 0; } diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index a3b92c75a79..b97e2759df7 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -16,6 +16,7 @@ import { buildUrl, isArray, generateUUID, + getWinDimensions, canAccessWindowTop, deepAccess, getSafeframeGeometry, @@ -464,8 +465,10 @@ function getUiEvents() { function getScreen() { function getInnerSize() { - let w = window?.innerWidth; - let h = window?.innerHeight; + const { innerWidth, innerHeight } = getWinDimensions(); + + let w = innerWidth; + let h = innerHeight; if (w && h) { return [w, h]; @@ -473,9 +476,10 @@ function getScreen() { } function getDocumentSize() { - let body = window?.document?.body; - let w = body.clientWidth; - let h = body.clientHeight; + const windowDimensions = getWinDimensions(); + + let w = windowDimensions.document.body.clientWidth; + let h = windowDimensions.document.body.clientHeight; if (w && h) { return [w, h]; diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 088dfd32979..3cd974b2b13 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getWindowTop, isEmpty, isGptPubadsDefined} from '../src/utils.js'; +import {deepAccess, getWinDimensions, getWindowTop, isEmpty, isGptPubadsDefined} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; @@ -203,14 +203,15 @@ export const spec = { get_client_info: function () { let botTest = new BotClientTests(); let win = getWindowTop(); + const windowDimensions = getWinDimensions(); return { - 'wiw': win.innerWidth, - 'wih': win.innerHeight, - 'saw': screen ? screen.availWidth : null, - 'sah': screen ? screen.availHeight : null, + 'wiw': windowDimensions.innerWidth, + 'wih': windowDimensions.innerHeight, + 'saw': windowDimensions.screen.availWidth, + 'sah': windowDimensions.screen.availHeight, 'scd': screen ? screen.colorDepth : null, - 'sw': screen ? screen.width : null, - 'sh': screen ? screen.height : null, + 'sw': windowDimensions.screen.width, + 'sh': windowDimensions.screen.height, 'whl': win.history.length, 'wxo': win.pageXOffset, 'wyo': win.pageYOffset, diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js index 3e90a76cf9e..5e43bf955ef 100644 --- a/modules/dianomiBidAdapter.js +++ b/modules/dianomiBidAdapter.js @@ -10,7 +10,8 @@ import { parseSizesInput, deepSetValue, formatQS, - setOnAny + setOnAny, + getWinDimensions } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; @@ -101,8 +102,9 @@ export const spec = { } const device = getConfig('device') || {}; - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; + const { innerWidth, innerHeight } = getWinDimensions(); + device.w = device.w || innerWidth; + device.h = device.h || innerHeight; device.ua = device.ua || navigator.userAgent; const paramsEndpoint = setOnAny(validBidRequests, 'params.endpoint'); diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index 77df1af3886..34cf84eb9d3 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, deepSetValue, getDNT, inIframe, logWarn, parseSizesInput} from '../src/utils.js'; +import {deepAccess, deepSetValue, getDNT, getWinDimensions, inIframe, logWarn, parseSizesInput} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -189,9 +189,11 @@ function getDevice(data) { if (!dnt) { dnt = getDNT() ? 1 : 0; } + const { innerWidth, innerHeight } = getWinDimensions(); + return { - w: data.w || window.innerWidth, - h: data.h || window.innerHeight, + w: data.w || innerWidth, + h: data.h || innerHeight, ua: data.ua || navigator.userAgent, dnt: dnt, language: data.language || navigator.language, diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 135e3c8a86d..5b3f55b9da6 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -1,4 +1,4 @@ -import {getWindowSelf, isEmpty, parseSizesInput, isGptPubadsDefined} from '../src/utils.js'; +import {isEmpty, parseSizesInput, isGptPubadsDefined, getWinDimensions} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -179,7 +179,7 @@ function getUserAgent() { return window.navigator.userAgent; } function getInnerWidth() { - return getWindowSelf().innerWidth; + return getWinDimensions().innerWidth; } function isMobileUserAgent() { return getUserAgent().match(/(mobile)|(ip(hone|ad))|(android)|(blackberry)|(nokia)|(phone)|(opera\smini)/i); diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index e9f9604e594..1b59bcb63ec 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -2,6 +2,7 @@ import { ajax } from '../src/ajax.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; +import { deepAccess } from '../src/utils.js'; /* General config */ const IS_LOCAL_MODE = false; @@ -243,8 +244,8 @@ const getRendererForBid = (bidRequest, creative) => { muted: false, controls: true, styling: { progressbarColor: '#000' }, - videoHeight: Math.min(doc.defaultView?.innerWidth / 16 * 9, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_HEIGHT), - videoVerticalHeight: Math.min(doc.defaultView?.innerWidth / 9 * 16, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_WIDTH), + videoHeight: Math.min(deepAccess(doc, 'defaultView.innerWidth') / 16 * 9, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_HEIGHT), + videoVerticalHeight: Math.min(deepAccess(doc, 'defaultView.innerWidth') / 9 * 16, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_WIDTH), }; const GP = doc.defaultView.GoldPlayer; const player = new GP(options); diff --git a/modules/greenbidsBidAdapter.js b/modules/greenbidsBidAdapter.js index 418cb850527..0ece04fe11f 100644 --- a/modules/greenbidsBidAdapter.js +++ b/modules/greenbidsBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, logError, deepAccess, parseSizesInput, getBidIdParameter, logInfo } from '../src/utils.js'; +import { getValue, logError, deepAccess, parseSizesInput, getBidIdParameter, logInfo, getWinDimensions } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { getHLen } from '../libraries/navigatorData/navigatorData.js'; @@ -69,8 +69,8 @@ export const spec = { devicePixelRatio: topWindow.devicePixelRatio, screenOrientation: screen.orientation?.type, historyLength: getHLen(), - viewportHeight: topWindow.visualViewport?.height, - viewportWidth: topWindow.visualViewport?.width, + viewportHeight: getWinDimensions().visualViewport.height, + viewportWidth: getWinDimensions().visualViewport.width, prebid_version: '$prebid.version$', }; diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index fa117137e86..35fc95d5d6d 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -1,5 +1,5 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {_each, deepAccess, logError, logWarn, parseSizesInput} from '../src/utils.js'; +import {_each, deepAccess, getWinDimensions, logError, logWarn, parseSizesInput} from '../src/utils.js'; import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -82,8 +82,8 @@ function _getBrowserParams(topWindowUrl, mosttopLocation) { } browserParams = { - vw: topWindow.innerWidth, - vh: topWindow.innerHeight, + vw: getWinDimensions().innerWidth, + vh: getWinDimensions().innerHeight, sw: topScreen.width, sh: topScreen.height, pu: stripGGParams(topUrl), diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index 845175775d9..9b9414a9ec6 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -1,6 +1,7 @@ import { inIframe, logError, logMessage, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const BIDDER_CODE = 'h12media'; const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/'; const DEFAULT_CURRENCY = 'USD'; @@ -209,8 +210,7 @@ function isVisible(element) { function getClientDimensions() { try { - const t = window.top.innerWidth || window.top.document.documentElement.clientWidth || window.top.document.body.clientWidth; - const e = window.top.innerHeight || window.top.document.documentElement.clientHeight || window.top.document.body.clientHeight; + const { width: t, height: e } = getViewportSize(); return [Math.round(t), Math.round(e)]; } catch (i) { return [0, 0]; diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 95a1dfaa5e2..b5f8a7baa33 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -6,6 +6,7 @@ import { EVENTS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -24,12 +25,7 @@ var viewId = utils.generateUUID(); var partnerId = DEFAULT_PARTNER_ID; var eventsToTrack = []; -var w = window; -var d = document; -var e = d.documentElement; -var g = d.getElementsByTagName('body')[0]; -var x = w.innerWidth || e.clientWidth || g.clientWidth; -var y = w.innerHeight || e.clientHeight || g.clientHeight; +const { width: x, height: y } = getViewportSize(); var pageView = { eventType: 'pageView', diff --git a/modules/iasRtdProvider.js b/modules/iasRtdProvider.js index b9de7ef4e46..5e72c416cdb 100644 --- a/modules/iasRtdProvider.js +++ b/modules/iasRtdProvider.js @@ -101,7 +101,8 @@ function stringifySlot(bidRequest, adUnitPath) { } function stringifyWindowSize() { - return [window.innerWidth || -1, window.innerHeight || -1].join('.'); + const { innerWidth, innerHeight } = utils.getWinDimensions(); + return [innerWidth || -1, innerHeight || -1].join('.'); } function stringifyScreenSize() { diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index fcd9360d973..2853bd95fd3 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { deepAccess, deepSetValue, generateUUID, isPlainObject } from '../src/utils.js'; +import { deepAccess, deepSetValue, generateUUID, getWinDimensions, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; @@ -150,8 +150,8 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) { if (!request.device) request.device = {}; if (!request.site) request.site = {}; request.device = { - w: window.innerWidth, - h: window.innerHeight, + w: getWinDimensions().innerWidth, + h: getWinDimensions().innerHeight, devicetype: helpers.getDeviceType(), ua: navigator.userAgent, js: 1, diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 4ab7f5927f9..ba4e099dbc5 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue, isFn, logWarn} from '../src/utils.js'; +import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue, isFn, logWarn, getWinDimensions} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {find} from '../src/polyfill.js'; @@ -242,8 +242,8 @@ function buildDevice(bidRequest) { const deviceConfig = ortb2Data?.device || {} const device = { - w: window.innerWidth, - h: window.innerHeight, + w: getWinDimensions().innerWidth, + h: getWinDimensions().innerHeight, js: 1, ext: { localStorage: storage.localStorageIsEnabled(), diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 35c1a12ff5e..8bb5f5aaac6 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -1,4 +1,4 @@ -import {logInfo} from '../src/utils.js'; +import {getWinDimensions, logInfo} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -166,8 +166,8 @@ function buildRequest(bidRequests, bidderRequest) { pcids: Object.keys(invibes.pushedCids).join(','), vId: invibes.visitId, - width: topWin.innerWidth, - height: topWin.innerHeight, + width: getWinDimensions().innerWidth, + height: getWinDimensions().innerHeight, oi: invibes.optIn, diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index af21b1470f6..a2305cc5154 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -7,6 +7,7 @@ import adapterManager from '../src/adapterManager.js'; import { deepClone, hasNonSerializableProperty, generateUUID, logInfo } from '../src/utils.js'; import { EVENTS } from '../src/constants.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events'; const analyticsType = 'endpoint'; @@ -38,12 +39,7 @@ let _bidRequestTimeout = 0; let flushInterval; let invisiblyAnalyticsEnabled = false; -const w = window; -const d = document; -let e = d.documentElement; -let g = d.getElementsByTagName('body')[0]; -let x = w.innerWidth || e.clientWidth || g.clientWidth; -let y = w.innerHeight || e.clientHeight || g.clientHeight; +const { width: x, height: y } = getViewportSize(); let _pageView = { eventType: 'pageView', diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 1e07ce6b5d8..420f3434da7 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getDNT, isArray, logWarn, isFn, isPlainObject, logError, logInfo} from '../src/utils.js'; +import {deepAccess, getDNT, isArray, logWarn, isFn, isPlainObject, logError, logInfo, getWinDimensions} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -104,8 +104,8 @@ function fetchIds_(cfg) { // Now changed to an object. yes the backend is able to handle it. function getDevice_() { const device = config.getConfig('device') || {}; - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; + device.w = device.w || getWinDimensions().innerWidth; + device.h = device.h || getWinDimensions().innerHeight; device.ua = device.ua || navigator.userAgent; device.dnt = getDNT() ? 1 : 0; device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 7f154614e4d..2ed2d544a34 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' -import { deepAccess } from '../src/utils.js'; +import { deepAccess, getWinDimensions } from '../src/utils.js'; const BIDDER_CODE = 'justpremium' const GVLID = 62 @@ -17,7 +17,9 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const c = preparePubCond(validBidRequests) - const dim = getWebsiteDim() + const { + screen + } = getWinDimensions(); const ggExt = getGumGumParams() const payload = { zone: validBidRequests.map(b => { @@ -27,10 +29,10 @@ export const spec = { }), // TODO: is 'page' the right value here? referer: bidderRequest.refererInfo.page, - sw: dim.screenWidth, - sh: dim.screenHeight, - ww: dim.innerWidth, - wh: dim.innerHeight, + sw: screen.width, + sh: screen.height, + ww: getWinDimensions().innerWidth, + wh: getWinDimensions().innerHeight, c: c, id: validBidRequests[0].params.zone, sizes: {}, @@ -245,22 +247,6 @@ function arrayUnique (array) { return a } -function getWebsiteDim () { - let top - try { - top = window.top - } catch (e) { - top = window - } - - return { - screenWidth: top.screen.width, - screenHeight: top.screen.height, - innerWidth: top.innerWidth, - innerHeight: top.innerHeight - } -} - function getGumGumParams () { if (!window.top) return null diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index ccee9616044..19dde7c36e2 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -3,6 +3,7 @@ import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; +import { getWinDimensions } from '../src/utils.js'; const BIDDER_CODE = 'lasso'; const ENDPOINT_URL = 'https://trc.lhmos.com/prebid'; @@ -156,10 +157,11 @@ function getBidRequestUrl(aimXR, params) { function getDeviceData() { const win = window.top; + const winDimensions = getWinDimensions(); return { ua: navigator.userAgent, - width: win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth, - height: win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight, + width: winDimensions.innerWidth || winDimensions.document.documentElement.clientWidth || win.document.body.clientWidth, + height: winDimensions.innerHeight || winDimensions.document.documentElement.clientHeight || win.document.body.clientHeight, browserLanguage: navigator.language, } } diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js index f2d677f14db..2daa768eb9c 100644 --- a/modules/lemmaDigitalBidAdapter.js +++ b/modules/lemmaDigitalBidAdapter.js @@ -450,8 +450,8 @@ export var spec = { dnt: utils.getDNT() ? 1 : 0, ua: navigator.userAgent, language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - w: (window.screen.width || window.innerWidth), - h: (window.screen.height || window.innerHeigh), + w: (utils.getWinDimensions().screen.width || utils.getWinDimensions().innerWidth), + h: (utils.getWinDimensions().screen.height || utils.getWinDimensions().innerHeight), geo: { country: params.country, lat: params.latitude, diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 203996c9fb5..aa520afcda6 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep, isFn, isPlainObject} from '../src/utils.js'; +import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep, isFn, isPlainObject, getWinDimensions} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; @@ -325,12 +325,12 @@ function getDeviceIfa() { function getDeviceWidth() { const device = config.getConfig('device') || {}; - return device.w || window.innerWidth; + return device.w || getWinDimensions().innerWidth; } function getDeviceHeight() { const device = config.getConfig('device') || {}; - return device.h || window.innerHeight; + return device.h || getWinDimensions().innerHeight; } function getCoppa() { diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js index ba7963e6376..ee18dc73ae5 100644 --- a/modules/mantisBidAdapter.js +++ b/modules/mantisBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { getWinDimensions } from '../src/utils.js'; export const storage = getStorageManager({bidderCode: 'mantis'}); @@ -74,8 +75,9 @@ export function onVisible(win, element, doOnVisible, time, pct) { }); } interval = setInterval(function () { - var winHeight = (win.innerHeight || document.documentElement.clientHeight); - var winWidth = (win.innerWidth || document.documentElement.clientWidth); + const windowDimensions = getWinDimensions(); + var winHeight = (windowDimensions.innerHeight || windowDimensions.document.documentElement.clientHeight); + var winWidth = (windowDimensions.innerWidth || windowDimensions.document.documentElement.clientWidth); doCheck(winWidth, winHeight, getBoundingClientRect(element)); }, 100); } diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 8b3366d02c2..81e78ba87b1 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -370,7 +370,7 @@ function MarsmediaAdapter() { function _getViewability(element, topWin, { w, h } = {}) { return topWin.document.visibilityState === 'visible' - ? percentInView(element, topWin, { w, h }) + ? percentInView(element, { w, h }) : 0; } } diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 3f77d60ce0a..c0448f4f056 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -9,7 +9,7 @@ import { logInfo, triggerPixel, uniques, - deepSetValue + deepSetValue, } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -22,6 +22,7 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {ADPOD} from '../src/mediaTypes.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -206,8 +207,9 @@ class PageDetail { } _getWindowSize() { - let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || -1; - let h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || -1; + const { width, height } = getViewportSize(); + let w = width || -1; + let h = height || -1; return `${w}x${h}`; } diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 9e2ea06df69..86cd3fb8250 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -14,7 +14,8 @@ import { isNumber, isBoolean, extractDomainFromHost, - isInteger, deepSetValue, getBidIdParameter, setOnAny + isInteger, deepSetValue, getBidIdParameter, setOnAny, + getWinDimensions } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; @@ -653,8 +654,8 @@ function pageInfo() { location: l, referrer: r || '', masked: m, - wWidth: w.innerWidth, - wHeight: w.innerHeight, + wWidth: getWinDimensions().innerWidth, + wHeight: getWinDimensions().innerHeight, date: t.toUTCString(), timeOffset: t.getTimezoneOffset() }; diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 26a4f9dde0f..0d2054ca09f 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -2,6 +2,7 @@ import { buildUrl, formatQS, generateUUID, + getWinDimensions, isFn, logInfo, safeJSONParse, @@ -69,6 +70,7 @@ function toPayload(bidRequest, bidderRequest) { payload.currency = getCurrencyFromBidderRequest(bidderRequest); payload.schain = bidRequest.schain; payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; + payload.screen = { height: getWinDimensions().screen.height, width: getWinDimensions().screen.width }; payload.viewport = getViewportSize(); payload.sizes = normalizeBannerSizes(bidRequest.mediaTypes.banner.sizes); payload.ortb2 = bidderRequest.ortb2; diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 6e1443b85e5..bbc51e0914b 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -4,6 +4,7 @@ import { deepSetValue, getBidIdParameter, getDefinedParams, + getWinDimensions, getWindowTop, isArray, isStr, @@ -20,6 +21,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const NM_VERSION = '4.3.0'; const PBJS_VERSION = 'v$prebid.version$'; @@ -271,7 +273,7 @@ function getExtNextMilImp(bid) { nm_version: NM_VERSION, pbjs_version: PBJS_VERSION, refresh_count: window?.nmmRefreshCounts[bid.adUnitCode] || 0, - scrollTop: window.pageYOffset || document.documentElement.scrollTop, + scrollTop: window.pageYOffset || getWinDimensions().document.documentElement.scrollTop, }, }; @@ -498,9 +500,10 @@ function getSiteObj() { } function getDeviceObj() { + const { width, height } = getViewportSize(); return { - w: window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth || 0, - h: window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight || 0, + w: width, + h: height, ua: window.navigator.userAgent || undefined, sua: getSua(), }; diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 7cacac819c1..f9d4058b646 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -1,4 +1,4 @@ -import { logInfo, deepAccess, logWarn, isArray, getParameterByName } from '../src/utils.js'; +import { logInfo, deepAccess, logWarn, isArray, getParameterByName, getWinDimensions } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -123,8 +123,9 @@ function nobidBuildRequests(bids, bidderRequest) { }; var clientDim = function() { try { - var width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - var height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + const winDimensions = getWinDimensions(); + var width = Math.max(winDimensions.document.documentElement.clientWidth, winDimensions.innerWidth || 0); + var height = Math.max(winDimensions.document.documentElement.clientHeight, winDimensions.innerHeight || 0); return `${width}x${height}`; } catch (e) { logWarn('Could not parse screen dimensions, error details:', e); diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index de03f65781e..f5b55a68715 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -270,7 +270,7 @@ function _isViewabilityMeasurable(element) { } function _getViewability(element, topWin, {w, h} = {}) { - return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, topWin, {w, h}) : 0; + return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, {w, h}) : 0; } function _extractGpidData(bid) { diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 4fe3bad5a5d..eec7dca9c23 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -6,7 +6,7 @@ import { Renderer } from '../src/Renderer.js'; import { find } from '../src/polyfill.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { deepClone, logError, deepAccess } from '../src/utils.js'; +import { deepClone, logError, deepAccess, getWinDimensions } from '../src/utils.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; /** @@ -269,20 +269,21 @@ function getDocumentVisibility(window) { * @returns {{location: *, referrer: (*|string), stack: (*|Array.), numIframes: (*|Number), wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} */ function getPageInfo(bidderRequest) { + const winDimensions = getWinDimensions(); const topmostFrame = getFrameNesting(); return { location: deepAccess(bidderRequest, 'refererInfo.page', null), referrer: deepAccess(bidderRequest, 'refererInfo.ref', null), stack: deepAccess(bidderRequest, 'refererInfo.stack', []), numIframes: deepAccess(bidderRequest, 'refererInfo.numIframes', 0), - wWidth: topmostFrame.innerWidth, - wHeight: topmostFrame.innerHeight, - oWidth: topmostFrame.outerWidth, - oHeight: topmostFrame.outerHeight, - sWidth: topmostFrame.screen.width, - sHeight: topmostFrame.screen.height, - aWidth: topmostFrame.screen.availWidth, - aHeight: topmostFrame.screen.availHeight, + wWidth: getWinDimensions().innerWidth, + wHeight: getWinDimensions().innerHeight, + oWidth: winDimensions.outerWidth, + oHeight: winDimensions.outerHeight, + sWidth: winDimensions.screen.width, + sHeight: winDimensions.screen.height, + aWidth: winDimensions.screen.availWidth, + aHeight: winDimensions.screen.availHeight, sLeft: 'screenLeft' in topmostFrame ? topmostFrame.screenLeft : topmostFrame.screenX, sTop: 'screenTop' in topmostFrame ? topmostFrame.screenTop : topmostFrame.screenY, xOffset: topmostFrame.pageXOffset, diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index 58b97a7f93e..642cae3996e 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -173,7 +173,7 @@ function _isViewabilityMeasurable(element) { function _getViewability(element, topWin, { w, h } = {}) { return getWindowTop().document.visibilityState === 'visible' - ? percentInView(element, topWin, { w, h }) + ? percentInView(element, { w, h }) : 0; } diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index a8b8fd8fec1..e000c6e4ac8 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -2,7 +2,7 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { _map, isArray, triggerPixel } from '../src/utils.js'; +import { _map, getWinDimensions, isArray, triggerPixel } from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -223,8 +223,8 @@ function geom(adunitCode) { const scrollY = window.scrollY; const { top, left, width, height } = getBoundingClientRect(slot); const viewport = { - width: window.innerWidth, - height: window.innerHeight, + width: getWinDimensions().innerWidth, + height: getWinDimensions().innerHeight, }; return { diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 5ddb2e410cb..ec97cc6a57d 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -6,7 +6,7 @@ import { deepClone, - getWindowTop, + getWinDimensions, isArray, isArrayOfNums, isValidMediaTypes, @@ -331,14 +331,9 @@ export function getFilteredMediaTypes(mediaTypes) { native: undefined } - try { - activeViewportWidth = getWindowTop().innerWidth; - activeViewportHeight = getWindowTop().innerHeight; - } catch (e) { - logWarn(`SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`); - activeViewportWidth = window.innerWidth; - activeViewportHeight = window.innerHeight; - } + activeViewportWidth = getWinDimensions().innerWidth; + activeViewportHeight = getWinDimensions().innerHeight; + const activeViewport = [activeViewportWidth, activeViewportHeight]; Object.keys(mediaTypes).map(mediaType => { const sizeConfig = mediaTypes[mediaType].sizeConfig; diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 13731e2b7e1..cccd809ad8b 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -4,6 +4,7 @@ import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject, inIframe, getDNT, generateUUID} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {getStorageManager} from '../src/storageManager.js'; +import { getViewportSize } from '../libraries/viewport/viewport.js'; const BIDDER_CODE = 'snigel'; const GVLID = 1076; @@ -31,6 +32,7 @@ export const spec = { }, buildRequests: function (bidRequests, bidderRequest) { + const { width: w, height: h } = getViewportSize(); const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); return { method: 'POST', @@ -61,8 +63,8 @@ export const spec = { page: getPage(bidderRequest), topframe: inIframe() === true ? 0 : 1, device: { - w: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, - h: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, + w, + h, dnt: getDNT() ? 1 : 0, language: getLanguage(), }, diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 5efc849eab8..c21d0ae4e95 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, isFn, isPlainObject, parseQueryStringParameters } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, isFn, isPlainObject, parseQueryStringParameters, getWinDimensions } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; @@ -433,7 +433,7 @@ function _getBidIdFromTrinityKey(key) { /** * @param context - the window to determine the innerWidth from. This is purely for test purposes as it should always be the current window */ -export const _isInbounds = (context = window) => (lowerBound = 0, upperBound = Number.MAX_SAFE_INTEGER) => context.innerWidth >= lowerBound && context.innerWidth < upperBound; +export const _isInbounds = (context = getWinDimensions()) => (lowerBound = 0, upperBound = Number.MAX_SAFE_INTEGER) => deepAccess(context, 'innerWidth') >= lowerBound && deepAccess(context, 'innerWidth') < upperBound; /** * @param context - the window to determine the innerWidth from. This is purely for test purposes as it should always be the current window diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 03a9f5a1da9..2dd9ee68733 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getWindowTop, isArray, logInfo, logWarn } from '../src/utils.js'; +import { deepAccess, getWinDimensions, getWindowTop, isArray, logInfo, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -161,7 +161,7 @@ const getNotificationPayload = bidData => { const applyClientHints = ortbRequest => { const { location } = document; const { connection = {}, deviceMemory, userAgentData = {} } = navigator; - const viewport = W.visualViewport || false; + const viewport = getWinDimensions().visualViewport || false; const segments = []; const hints = { 'CH-Ect': connection.effectiveType, diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index 2acdaf5f66f..bf818250c63 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -1,4 +1,4 @@ -import { buildUrl, deepAccess, deepSetValue, generateUUID, getWindowSelf, getWindowTop, isEmpty, isStr, logWarn } from '../src/utils.js'; +import { buildUrl, deepAccess, deepSetValue, generateUUID, getWinDimensions, getWindowSelf, getWindowTop, isEmpty, isStr, logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {find} from '../src/polyfill.js'; @@ -165,7 +165,7 @@ const elementInView = (elementId) => { const visibleInWindow = (el, win) => { const rect = getBoundingClientRect(el); - const inView = (rect.top + rect.height >= 0) && (rect.top <= win.innerHeight); + const inView = (rect.top + rect.height >= 0) && (rect.top <= getWinDimensions().innerHeight); if (win !== win.parent) { return inView && visibleInWindow(win.frameElement, win.parent); diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 1e6d5a696f6..97cfa273b1e 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,4 +1,4 @@ -import {logError, deepAccess, parseSizesInput, isArray, getBidIdParameter} from '../src/utils.js'; +import {logError, deepAccess, parseSizesInput, isArray, getBidIdParameter, getWinDimensions} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js'; @@ -74,8 +74,8 @@ export const spec = { devicePixelRatio: topWindow.devicePixelRatio, screenOrientation: screen.orientation?.type, historyLength: getHLen(), - viewportHeight: topWindow.visualViewport?.height, - viewportWidth: topWindow.visualViewport?.width, + viewportHeight: getWinDimensions().visualViewport.height, + viewportWidth: getWinDimensions().visualViewport.width, hardwareConcurrency: getHC(), deviceMemory: getDM(), hb_version: '$prebid.version$', diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index ca98e8e14fc..612df51a3d3 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -263,7 +263,7 @@ function _getViewability(element, topWin, { h } = {}) { return topWin.document.visibilityState === 'visible' - ? percentInView(element, topWin, { + ? percentInView(element, { w, h }) diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index 1ea2f2c1ce6..a3e5fbbf728 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,7 +2,7 @@ * Adapter to send bids to Undertone */ -import {deepAccess, parseUrl, extractDomainFromHost} from '../src/utils.js'; +import {deepAccess, parseUrl, extractDomainFromHost, getWinDimensions} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -67,8 +67,9 @@ export const spec = { } }, buildRequests: function(validBidRequests, bidderRequest) { - const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); - const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + const windowDimensions = getWinDimensions(); + const vw = Math.max(windowDimensions.document.documentElement.clientWidth, windowDimensions.innerWidth || 0); + const vh = Math.max(windowDimensions.document.documentElement.clientHeight, windowDimensions.innerHeight || 0); const pageSizeArray = vw == 0 || vh == 0 ? null : [vw, vh]; const commons = { 'adapterVersion': '$prebid.version$', diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js index 348d17d2395..1e5f0b0136b 100644 --- a/modules/vibrantmediaBidAdapter.js +++ b/modules/vibrantmediaBidAdapter.js @@ -7,7 +7,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; * Note: Only BANNER and VIDEO are currently supported by the prebid server. */ -import {logError, triggerPixel} from '../src/utils.js'; +import {getWinDimensions, logError, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.js'; @@ -138,8 +138,8 @@ export const spec = { gdpr: bidderRequest.gdprConsent, usp: bidderRequest.uspConsent, window: { - width: window.innerWidth, - height: window.innerHeight, + width: getWinDimensions().innerWidth, + height: getWinDimensions().innerHeight, }, biddata: transformedBidRequests, }; diff --git a/modules/videojsVideoProvider.js b/modules/videojsVideoProvider.js index 0eefe7da364..066a026d597 100644 --- a/modules/videojsVideoProvider.js +++ b/modules/videojsVideoProvider.js @@ -13,6 +13,7 @@ import { submodule } from '../src/hook.js'; import stateFactory from '../libraries/video/shared/state.js'; import { PLAYBACK_MODE } from '../libraries/video/constants/constants.js'; import { getEventHandler } from '../libraries/video/shared/eventHandler.js'; +import { getWinDimensions } from '../src/utils.js'; /** * @typedef {import('../libraries/video/shared/state.js').State} State */ @@ -614,8 +615,8 @@ export const utils = { }, getPositionCode: function({left, top, width, height}) { - const bottom = window.innerHeight - top - height; - const right = window.innerWidth - left - width; + const bottom = getWinDimensions().innerHeight - top - height; + const right = getWinDimensions().innerWidth - left - width; if (left < 0 || right < 0 || top < 0) { return AD_POSITION.UNKNOWN; diff --git a/modules/yieldloveBidAdapter.js b/modules/yieldloveBidAdapter.js index 4568206b20a..fadcf51cc85 100644 --- a/modules/yieldloveBidAdapter.js +++ b/modules/yieldloveBidAdapter.js @@ -45,8 +45,8 @@ export const spec = { const s2sRequest = { device: { ua: window.navigator.userAgent, - w: window.innerWidth, - h: window.innerHeight, + w: utils.getWinDimensions().innerWidth, + h: utils.getWinDimensions().innerHeight, }, site: { ver: '1.9.0', diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 384943ed20c..ae91d39e2cc 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess, deepSetValue, + getWinDimensions, getWindowTop, isArray, isArrayOfNums, @@ -114,8 +115,8 @@ export const spec = { serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; serverRequest.scrd = LOCAL_WINDOW.devicePixelRatio || 0; serverRequest.title = LOCAL_WINDOW.document.title || ''; - serverRequest.w = LOCAL_WINDOW.innerWidth; - serverRequest.h = LOCAL_WINDOW.innerHeight; + serverRequest.w = getWinDimensions().innerWidth; + serverRequest.h = getWinDimensions().innerHeight; } const mtp = window.navigator.maxTouchPoints; diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 51c57a88f44..0b361bb3733 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -1,7 +1,7 @@ import {hook} from '../hook.js'; import {getRefererInfo, parseDomain} from '../refererDetection.js'; import {findRootDomain} from './rootDomain.js'; -import {deepSetValue, getDefinedParams, getDNT, getDocument, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; +import {deepSetValue, getDefinedParams, getDNT, getWinDimensions, getDocument, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; import {config} from '../config.js'; import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; import {PbPromise} from '../utils/promise.js'; @@ -10,6 +10,7 @@ import {isActivityAllowed} from '../activities/rules.js'; import {activityParams} from '../activities/activityParams.js'; import {ACTIVITY_ACCESS_DEVICE} from '../activities/activities.js'; import {MODULE_TYPE_PREBID} from '../activities/modules.js'; +import { getViewportSize } from '../../libraries/viewport/viewport.js'; export const dep = { getRefererInfo, @@ -106,12 +107,11 @@ const ENRICHMENTS = { device() { return winFallback((win) => { // screen.width and screen.height are the physical dimensions of the screen - const w = win.screen.width; - const h = win.screen.height; + const w = getWinDimensions().screen.width; + const h = getWinDimensions().screen.height; // vpw and vph are the viewport dimensions of the browser window - const vpw = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; - const vph = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + const {width: vpw, height: vph} = getViewportSize(); const device = { w, diff --git a/src/utils.js b/src/utils.js index b071dff4fe2..263be7a8bbe 100644 --- a/src/utils.js +++ b/src/utils.js @@ -22,6 +22,7 @@ let consoleWarnExists = Boolean(consoleExists && window.console.warn); let consoleErrorExists = Boolean(consoleExists && window.console.error); let eventEmitter; +let windowDimensions; const pbjsInstance = getGlobal(); @@ -36,6 +37,54 @@ function emitEvent(...args) { } } +export const getWinDimensions = (function() { + let lastCheckTimestamp; + const CHECK_INTERVAL_MS = 20; + return () => { + if (!windowDimensions || !lastCheckTimestamp || (Date.now() - lastCheckTimestamp > CHECK_INTERVAL_MS)) { + internal.resetWinDimensions(); + lastCheckTimestamp = Date.now(); + } + return windowDimensions; + } +})(); + +export function resetWinDimensions() { + const top = canAccessWindowTop() ? internal.getWindowTop() : internal.getWindowSelf(); + + windowDimensions = { + screen: { + width: top.screen?.width, + height: top.screen?.height, + availWidth: top.screen?.availWidth, + availHeight: top.screen?.availHeight, + colorDepth: top.screen?.colorDepth, + }, + innerHeight: top.innerHeight, + innerWidth: top.innerWidth, + outerWidth: top.outerWidth, + outerHeight: top.outerHeight, + visualViewport: { + height: top.visualViewport?.height, + width: top.visualViewport?.width, + }, + document: { + documentElement: { + clientWidth: top.document?.documentElement?.clientWidth, + clientHeight: top.document?.documentElement?.clientHeight, + scrollTop: top.document?.documentElement?.scrollTop, + scrollLeft: top.document?.documentElement?.scrollLeft, + }, + body: { + scrollTop: document.body?.scrollTop, + scrollLeft: document.body?.scrollLeft, + clientWidth: document.body?.clientWidth, + clientHeight: document.body?.clientHeight, + }, + } + }; +} + // this allows stubbing of utility functions that are used internally by other utility functions export const internal = { checkCookieSupport, @@ -54,7 +103,8 @@ export const internal = { logInfo, parseQS, formatQS, - deepEqual + deepEqual, + resetWinDimensions }; let prebidInternal = {}; diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index f9312b308ff..a844a05469a 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -171,24 +171,27 @@ describe('FPD enrichment', () => { }); testWindows(() => win, () => { it('sets w/h', () => { - win.screen.width = 321; - win.screen.height = 123; + const getWinDimensionsStub = sandbox.stub(utils, 'getWinDimensions'); + + getWinDimensionsStub.returns({screen: {width: 321, height: 123}}); return fpd().then(ortb2 => { sinon.assert.match(ortb2.device, { w: 321, h: 123, }); + getWinDimensionsStub.restore(); }); }); it('sets ext.vpw/vph', () => { - win.innerWidth = 12; - win.innerHeight = 21; + const getWinDimensionsStub = sandbox.stub(utils, 'getWinDimensions'); + getWinDimensionsStub.returns({innerWidth: 12, innerHeight: 21, screen: {}}); return fpd().then(ortb2 => { sinon.assert.match(ortb2.device.ext, { vpw: 12, vph: 21, }); + getWinDimensionsStub.restore(); }); }); diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index ff05f412d58..d4ad0184a17 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -1,9 +1,11 @@ import { expect } from 'chai'; import * as utils from 'src/utils.js'; +import { internal } from 'src/utils.js'; import { config } from 'src/config.js'; import { spec } from 'modules/33acrossBidAdapter.js'; +import { resetWinDimensions } from '../../../src/utils'; function validateBuiltServerRequest(builtReq, expectedReq) { expect(builtReq.url).to.equal(expectedReq.url); @@ -499,12 +501,15 @@ describe('33acrossBidAdapter:', function () { sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); sandbox.stub(document, 'getElementById').returns(element); + sandbox.stub(internal, 'getWindowTop').returns(win); + sandbox.stub(internal, 'getWindowSelf').returns(win); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); bidderRequest = {bidderRequestId: 'r1'}; }); afterEach(function() { + resetWinDimensions(); sandbox.restore(); }); describe('isBidRequestValid:', function() { @@ -1002,6 +1007,8 @@ describe('33acrossBidAdapter:', function () { win.innerHeight = 728; win.innerWidth = 727; + resetWinDimensions(); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); @@ -1024,6 +1031,7 @@ describe('33acrossBidAdapter:', function () { utils.getWindowTop.restore(); win.document.visibilityState = 'hidden'; sandbox.stub(utils, 'getWindowTop').returns(win); + resetWinDimensions(); const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 23c4b82de08..436fe170aa3 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -7,6 +7,7 @@ import * as utils from 'src/utils.js'; import { getStorageManager } from 'src/storageManager.js'; import { getGlobal } from '../../../src/prebidGlobal'; import {getUnixTimestampFromNow, getWindowTop} from 'src/utils.js'; +import { getWinDimensions } from '../../../src/utils'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; @@ -48,9 +49,9 @@ describe('adnuntiusBidAdapter', function () { }); const tzo = new Date().getTimezoneOffset(); - const win = getWindowTop() || window; - const screen = win.screen.availWidth + 'x' + win.screen.availHeight; - const viewport = win.innerWidth + 'x' + win.innerHeight; + const winDimensions = getWinDimensions(); + const screen = winDimensions.screen.availWidth + 'x' + winDimensions.screen.availHeight; + const viewport = winDimensions.innerWidth + 'x' + winDimensions.innerHeight; const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid&screen=${screen}&viewport=${viewport}`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; const LOCALHOST_URL = `http://localhost:8078/i?tzo=${tzo}&format=prebid&screen=${screen}&viewport=${viewport}&userId=${usi}`; diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 3d9ef742fa0..62c730c7cc4 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -17,6 +17,7 @@ import { import adapterManager from '../../../src/adapterManager.js'; import * as ajax from '../../../src/ajax.js'; import { ADPOD, BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from '../../../src/utils.js'; const BIDDER_CODE = 'connatix'; @@ -178,19 +179,25 @@ describe('connatixBidAdapter', function () { it('should return the correct percentage if the element is partially in view', () => { const boundingBox = { left: 700, top: 500, right: 900, bottom: 700, width: 200, height: 200 }; getBoundingClientRectStub.returns(boundingBox); + const getWinDimensionsStub = sinon.stub(utils, 'getWinDimensions'); + getWinDimensionsStub.returns({ innerWidth: topWinMock.innerWidth, innerHeight: topWinMock.innerHeight}); const viewability = connatixGetViewability(element, topWinMock); expect(viewability).to.equal(25); // 100x100 / 200x200 = 0.25 -> 25% + getWinDimensionsStub.restore(); }); it('should return 0% if the element is not in view', () => { + const getWinDimensionsStub = sinon.stub(utils, 'getWinDimensions'); + getWinDimensionsStub.returns({ innerWidth: topWinMock.innerWidth, innerHeight: topWinMock.innerHeight}); const boundingBox = { left: 900, top: 700, right: 1100, bottom: 900, width: 200, height: 200 }; getBoundingClientRectStub.returns(boundingBox); const viewability = connatixGetViewability(element, topWinMock); expect(viewability).to.equal(0); + getWinDimensionsStub.restore(); }); it('should use provided width and height if element dimensions are zero', () => { diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index be8e13a5a58..ed313cf4501 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -7,7 +7,7 @@ import * as events from '../../../src/events'; import * as utils from 'src/utils.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js' import Sinon from 'sinon'; -import { deepClone } from '../../../src/utils.js'; +import { deepClone, getWinDimensions } from '../../../src/utils.js'; const MODULE_NAME = 'contxtful'; @@ -668,8 +668,7 @@ describe('contxtfulRtdProvider', function () { // Cannot change the window size from JS // So we take the current size as expectation - const width = window.innerWidth; - const height = window.innerHeight; + const { innerHeight: height, innerWidth: width } = getWinDimensions() let reqBidsConfigObj = { ortb2Fragments: { diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index 9f46c57e422..efb2f4a4cbb 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -8,6 +8,7 @@ import {hook} from '../../../src/hook.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import { makeSlot } from '../integration/faker/googletag.js'; import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; +import { internal, resetWinDimensions } from '../../../src/utils.js'; describe('E-Planning Adapter', function () { const adapter = newBidder('spec'); @@ -625,7 +626,7 @@ describe('E-Planning Adapter', function () { describe('buildRequests', function () { let bidRequests = [validBid]; let sandbox; - let getWindowSelfStub; + let getWindowTopStub; let innerWidth; beforeEach(() => { $$PREBID_GLOBAL$$.bidderSettings = { @@ -634,8 +635,9 @@ describe('E-Planning Adapter', function () { } }; sandbox = sinon.sandbox.create(); - getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf'); - getWindowSelfStub.returns(createWindow(800)); + getWindowTopStub = sandbox.stub(internal, 'getWindowTop'); + getWindowTopStub.returns(createWindow(800)); + resetWinDimensions(); }); afterEach(() => { @@ -647,6 +649,9 @@ describe('E-Planning Adapter', function () { const win = {}; win.self = win; win.innerWidth = innerWidth; + win.location = { + href: 'location' + }; return win; }; @@ -956,7 +961,8 @@ describe('E-Planning Adapter', function () { it('should return the e parameter with a value according to the sizes in order corresponding to the desktop priority list of the ad units', function () { let bidRequestsPrioritySizes = [validBidExistingSizesInPriorityListForDesktop]; // overwrite default innerWdith for tests with a larger one we consider "Desktop" or NOT Mobile - getWindowSelfStub.returns(createWindow(1025)); + getWindowTopStub.returns(createWindow(1025)); + resetWinDimensions(); const e = spec.buildRequests(bidRequestsPrioritySizes, bidderRequest).data.e; expect(e).to.equal('300x250_0:300x250,300x600,970x250'); }); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 158fdebeb76..d6daceddd6e 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, storage } from '../../../modules/insticatorBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js' +import { getWinDimensions } from '../../../src/utils.js'; const USER_ID_KEY = 'hb_insticator_uid'; const USER_ID_DUMMY_VALUE = '74f78609-a92d-4cf1-869f-1b244bbfb5d2'; @@ -387,8 +388,8 @@ describe('InsticatorBidAdapter', function () { expect(data.site.page).not.to.be.empty; expect(data.site.ref).to.equal(bidderRequest.refererInfo.ref); expect(data.device).to.be.an('object'); - expect(data.device.w).to.equal(window.innerWidth); - expect(data.device.h).to.equal(window.innerHeight); + expect(data.device.w).to.equal(getWinDimensions().innerWidth); + expect(data.device.h).to.equal(getWinDimensions().innerHeight); expect(data.device.js).to.equal(1); expect(data.device.ext).to.be.an('object'); expect(data.device.ext.localStorage).to.equal(true); diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 7a3cd26dbe7..f3125fec529 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -5,6 +5,7 @@ import * as utils from 'src/utils.js'; import { NATIVE, VIDEO } from 'src/mediaTypes.js'; import { setConfig as setCurrencyConfig } from '../../../modules/currency'; import { addFPDToBidderRequest } from '../../helpers/fpd'; +import { getWinDimensions } from '../../../src/utils'; describe('Livewrapped adapter tests', function () { let sandbox, @@ -999,8 +1000,8 @@ describe('Livewrapped adapter tests', function () { url: 'https://www.domain.com', seats: {'dsp': ['seat 1']}, version: '1.4', - width: window.innerWidth, - height: window.innerHeight, + width: getWinDimensions().innerWidth, + height: getWinDimensions().innerHeight, cookieSupport: true, adRequests: [{ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index 055b05700b2..ca3876703c8 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,6 +1,7 @@ import { spec } from 'modules/marsmediaBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; +import { internal, resetWinDimensions } from '../../../src/utils'; var marsAdapter = spec; @@ -32,7 +33,9 @@ describe('marsmedia adapter tests', function () { document: { visibilityState: 'visible' }, - + location: { + href: 'http://location' + }, innerWidth: 800, innerHeight: 600 }; @@ -520,10 +523,13 @@ describe('marsmedia adapter tests', function () { context('when element is partially in view', function() { it('returns percentage', function() { + sandbox.stub(internal, 'getWindowTop').returns(win); + resetWinDimensions(); Object.assign(element, { width: 800, height: 800 }); const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(request.data); expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); + internal.getWindowTop.restore(); }); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 009af5dbd66..b899ca2027c 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -3,6 +3,7 @@ import {spec, EVENT_PIXEL_URL, EVENTS} from 'modules/medianetBidAdapter.js'; import { makeSlot } from '../integration/faker/googletag.js'; import { config } from 'src/config.js'; import {server} from '../../mocks/xhr.js'; +import {resetWinDimensions} from '../../../src/utils.js'; $$PREBID_GLOBAL$$.version = $$PREBID_GLOBAL$$.version || 'version'; let VALID_BID_REQUEST = [{ @@ -1926,6 +1927,7 @@ describe('Media.net bid adapter', function () { }); afterEach(function () { + resetWinDimensions(); sandbox.restore(); }); diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index e85c4d2429e..e95903d4791 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec, storage } from 'modules/missenaBidAdapter.js'; import { BANNER } from '../../../src/mediaTypes.js'; import { config } from 'src/config.js'; import * as autoplay from 'libraries/autoplayDetection/autoplay.js'; +import { getWinDimensions } from '../../../src/utils.js'; const REFERRER = 'https://referer'; const REFERRER2 = 'https://referer2'; @@ -19,7 +20,7 @@ describe('Missena Adapter', function () { let sandbox = sinon.sandbox.create(); sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); sandbox.stub(autoplay, 'isAutoplayEnabled').returns(false); - const viewport = { width: window.top.innerWidth, height: window.top.innerHeight }; + const viewport = { width: getWinDimensions().innerWidth, height: getWinDimensions().innerHeight }; const bidId = 'abc'; const bid = { diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index 54d744131cd..e8426930257 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -3,6 +3,7 @@ import * as utils from 'src/utils.js'; import {spec} from 'modules/omsBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config'; +import { internal, resetWinDimensions } from '../../../src/utils'; const URL = 'https://rt.marphezis.com/hb'; @@ -36,7 +37,9 @@ describe('omsBidAdapter', function () { document: { visibilityState: 'visible' }, - + location: { + href: "http:/location" + }, innerWidth: 800, innerHeight: 600 }; @@ -328,6 +331,8 @@ describe('omsBidAdapter', function () { context('when element is partially in view', function () { it('returns percentage', function () { + const getWinDimensionsStub = sandbox.stub(utils, 'getWinDimensions') + getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, {width: 800, height: 800}); const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -337,6 +342,8 @@ describe('omsBidAdapter', function () { context('when width or height of the element is zero', function () { it('try to use alternative values', function () { + const getWinDimensionsStub = sandbox.stub(utils, 'getWinDimensions') + getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, {width: 0, height: 0}); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index c636542c9c9..dcef5b65419 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -161,6 +161,8 @@ describe('onomagicBidAdapter', function() { context('when element is partially in view', function() { it('returns percentage', function() { + const getWinDimensionsStub = sandbox.stub(utils, 'getWinDimensions') + getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, { width: 800, height: 800 }); const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -170,6 +172,8 @@ describe('onomagicBidAdapter', function() { context('when width or height of the element is zero', function() { it('try to use alternative values', function() { + const getWinDimensionsStub = sandbox.stub(utils, 'getWinDimensions') + getWinDimensionsStub.returns({ innerHeight: win.innerHeight, innerWidth: win.innerWidth }); Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 16c1527a3ad..078ce8d4f12 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import * as utils from '../../../src/utils.js'; - +import { internal as utilInternal } from '../../../src/utils.js'; import { isUsingNewSizeMapping, checkAdUnitSetupHook, @@ -1174,20 +1174,22 @@ describe('sizeMappingV2', function () { }); }); - describe('getFilteredMediaTypes(mediaTypes)', function () { + describe('getFilteredMediaTypes(mediaTypes)', function () { beforeEach(function () { + utils.resetWinDimensions(); sinon - .stub(utils, 'getWindowTop') + .stub(utilInternal, 'getWindowTop') .returns({ innerWidth: 1680, - innerHeight: 269 + innerHeight: 269, + location: { + href: 'https://url' + } }); - - sinon.spy(utils, 'logWarn'); }); afterEach(function () { - utils.getWindowTop.restore(); - utils.logWarn.restore(); + utils.resetWinDimensions(); + utilInternal.getWindowTop.restore(); }); it('should return filteredMediaTypes object with all properties (transformedMediaTypes, activeViewport, sizeBucketToSizeMap) evaluated correctly', function () { const [adUnit] = utils.deepClone(AD_UNITS); @@ -1228,17 +1230,6 @@ describe('sizeMappingV2', function () { expect(sizeBucketToSizeMap).to.deep.equal(expectedSizeBucketToSizeMap); expect(transformedMediaTypes).to.deep.equal(expectedTransformedMediaTypes); }); - - it('should throw a warning message if Iframe blocks viewport size to be evaluated correctly', function () { - const [adUnit] = utils.deepClone(AD_UNITS); - utils.getWindowTop.restore(); - sinon - .stub(utils, 'getWindowTop') - .throws(); - getFilteredMediaTypes(adUnit.mediaTypes); - sinon.assert.callCount(utils.logWarn, 1); - sinon.assert.calledWith(utils.logWarn, `SizeMappingv2:: Unfriendly iframe blocks viewport size to be evaluated correctly`); - }); }); describe('setupAdUnitsForLabels', function () { diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 5cf53c661a9..de09e024973 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/undertoneBidAdapter.js'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {deepClone} from '../../../src/utils'; +import {deepClone, getWinDimensions} from '../../../src/utils'; const URL = 'https://hb.undertone.com/hb'; const BIDDER_CODE = 'undertone'; @@ -503,8 +503,8 @@ describe('Undertone Adapter', () => { const bidCommons = JSON.parse(request.data)['commons']; expect(bidCommons).to.be.an('object'); expect(bidCommons.pageSize).to.be.an('array'); - expect(bidCommons.pageSize[0]).to.equal(window.innerWidth); - expect(bidCommons.pageSize[1]).to.equal(window.innerHeight); + expect(bidCommons.pageSize[0]).to.equal(getWinDimensions().innerWidth); + expect(bidCommons.pageSize[1]).to.equal(getWinDimensions().innerHeight); }); it('should send banner coordinates', function() { const request = spec.buildRequests(bidReq, bidderReq); diff --git a/test/spec/modules/vibrantmediaBidAdapter_spec.js b/test/spec/modules/vibrantmediaBidAdapter_spec.js index cf9487ebf25..3fdc27a7a3b 100644 --- a/test/spec/modules/vibrantmediaBidAdapter_spec.js +++ b/test/spec/modules/vibrantmediaBidAdapter_spec.js @@ -3,6 +3,7 @@ import {spec} from 'modules/vibrantmediaBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; import {INSTREAM, OUTSTREAM} from 'src/video.js'; +import { getWinDimensions } from '../../../src/utils'; const EXPECTED_PREBID_SERVER_URL = 'https://prebid.intellitxt.com/prebid'; @@ -545,9 +546,9 @@ describe('VibrantMediaBidAdapter', function () { const request = spec.buildRequests(bidRequests, {}); const payload = JSON.parse(request.data); - expect(payload.window).to.exist; - expect(payload.window.width).to.equal(window.innerWidth); - expect(payload.window.height).to.equal(window.innerHeight); + expect(payload.window).to.exist; + expect(payload.window.width).to.equal(getWinDimensions().innerWidth); + expect(payload.window.height).to.equal(getWinDimensions().innerHeight); }); it('should add the top-level sizes to the bid request, if present', function () { diff --git a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js index 125f608f803..80ddf50fe18 100644 --- a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js @@ -2,6 +2,7 @@ import { SETUP_COMPLETE, SETUP_FAILED } from 'libraries/video/constants/events.js'; +import { getWinDimensions } from '../../../../../src/utils'; const {VideojsProvider, utils} = require('modules/videojsVideoProvider'); @@ -293,31 +294,34 @@ describe('utils', function() { describe('getPositionCode', function() { it('should return the correct position when video is above the fold', function () { + const {innerWidth, innerHeight} = getWinDimensions(); const code = utils.getPositionCode({ - left: window.innerWidth / 10, + left: innerWidth / 10, top: 0, - width: window.innerWidth - window.innerWidth / 10, - height: window.innerHeight, + width: innerWidth - innerWidth / 10, + height: innerHeight, }) expect(code).to.equal(AD_POSITION.ABOVE_THE_FOLD) }); it('should return the correct position when video is below the fold', function () { + const {innerWidth, innerHeight} = getWinDimensions(); const code = utils.getPositionCode({ - left: window.innerWidth / 10, - top: window.innerHeight, - width: window.innerWidth - window.innerWidth / 10, - height: window.innerHeight / 2, + left: innerWidth / 10, + top: innerHeight, + width: innerWidth - innerWidth / 10, + height: innerHeight / 2, }) expect(code).to.equal(AD_POSITION.BELOW_THE_FOLD) }); it('should return the unkown position when the video is out of bounds', function () { + const {innerWidth, innerHeight} = getWinDimensions(); const code = utils.getPositionCode({ - left: window.innerWidth / 10, - top: window.innerHeight, - width: window.innerWidth, - height: window.innerHeight, + left: innerWidth / 10, + top: innerHeight, + width: innerWidth, + height: innerHeight, }) expect(code).to.equal(AD_POSITION.UNKNOWN) }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index ae6b9711ec4..32a9d9e0390 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -4,6 +4,7 @@ import {TARGETING_KEYS} from 'src/constants.js'; import * as utils from 'src/utils.js'; import {binarySearch, deepEqual, encodeMacroURI, memoize, sizesToSizeTuples, waitForElementToLoad} from 'src/utils.js'; import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js'; +import { getWinDimensions, internal } from '../../src/utils.js'; var assert = require('assert'); @@ -1325,3 +1326,30 @@ describe('memoize', () => { }) }); }) + +describe('getWinDimensions', () => { + let clock; + + beforeEach(() => { + clock = sinon.useFakeTimers({ now: new Date() }); + }); + + afterEach(() => { + clock.restore(); + }); + + it('should invoke resetWinDimensions once per 20ms', () => { + const resetWinDimensionsSpy = sinon.spy(internal, 'resetWinDimensions'); + getWinDimensions(); + clock.tick(1); + getWinDimensions(); + clock.tick(1); + getWinDimensions(); + clock.tick(1); + getWinDimensions(); + sinon.assert.calledOnce(resetWinDimensionsSpy); + clock.tick(18); + getWinDimensions(); + sinon.assert.calledTwice(resetWinDimensionsSpy); + }); +}); From 02c35bef249cb8fa04cf64de8a3954cefdfc4eaf Mon Sep 17 00:00:00 2001 From: giathinhly <96400885+giathinhly@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:01:09 +0700 Subject: [PATCH 1074/1097] MediaEyes Bid Adapter : initial release (#12899) * init mediaEyesBidAdapter * fix camel case * renamefile * fix and update bid adpater * fix getBidFloor * add more test --- modules/mediaeyesBidAdapter.js | 129 ++++++++++++ modules/mediaeyesBidAdapter.md | 33 ++++ test/spec/modules/mediaeyesBidAdapter_spec.js | 183 ++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 modules/mediaeyesBidAdapter.js create mode 100644 modules/mediaeyesBidAdapter.md create mode 100644 test/spec/modules/mediaeyesBidAdapter_spec.js diff --git a/modules/mediaeyesBidAdapter.js b/modules/mediaeyesBidAdapter.js new file mode 100644 index 00000000000..5c896d87a48 --- /dev/null +++ b/modules/mediaeyesBidAdapter.js @@ -0,0 +1,129 @@ +import { + BANNER +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { deepAccess, deepSetValue, generateUUID, isArray, isFn, isNumber, isPlainObject, isStr } from '../src/utils.js'; + +const ENDPOINT_URL = 'https://delivery.upremium.asia/ortb/open/auction'; + +export const spec = { + code: 'mediaeyes', + supportedMediaTypes: BANNER, + + isBidRequestValid: (bid) => { + return !!(bid.params.itemId); + }, + + buildRequests: (bidRequests, bidderRequest) => { + let requests = []; + + bidRequests.map(bidRequest => { + let {itemId} = bidRequest.params; + let requestData = { + id: generateUUID(), + imp: [cookingImp(bidRequest)], + device: bidRequest.ortb2?.device, + site: bidRequest.ortb2?.site, + } + requests.push({ + method: 'POST', + url: ENDPOINT_URL + "?item_id=" + itemId, + data: JSON.stringify(requestData), + }); + }) + + return requests + }, + + interpretResponse: (serverResponse, serverRequest) => { + let response = serverResponse.body; + if (!response.seatbid) { + return []; + } + + let rtbBids = response.seatbid + .map(seatbid => seatbid.bid) + .reduce((a, b) => a.concat(b), []); + + let data = rtbBids.map(rtbBid => { + let prBid = { + requestId: rtbBid.impid, + cpm: rtbBid.price, + creativeId: rtbBid.crid, + currency: response.cur || 'USD', + ttl: 360, + netRevenue: true + }; + + prBid.mediaType = BANNER; + prBid.width = rtbBid.w; + prBid.height = rtbBid.h; + prBid.ad = rtbBid.adm; + if (isArray(rtbBid.adomain)) { + deepSetValue(prBid, 'meta.advertiserDomains', rtbBid.adomain); + } + if (isPlainObject(rtbBid.ext)) { + if (isNumber(rtbBid.ext.advertiser_id)) { + deepSetValue(prBid, 'meta.advertiserId', rtbBid.ext.advertiser_id); + } + if (isStr(rtbBid.ext.advertiser_name)) { + deepSetValue(prBid, 'meta.advertiserName', rtbBid.ext.advertiser_name); + } + if (isStr(rtbBid.ext.agency_name)) { + deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name); + } + } + + return prBid + }); + + return data + } +} + +registerBidder(spec); + +function cookingImp(bidReq) { + let imp = {}; + if (bidReq) { + const bidfloor = getBidFloor(bidReq); + if (bidfloor) { + imp.bidfloor = parseFloat(bidfloor); + imp.bidfloorcur = 'USD'; + } + + imp.id = bidReq.bidId; + imp.bidfloor = bidfloor; + imp.banner = cookImpBanner(bidReq); + } + return imp; +} + +const cookImpBanner = ({ mediaTypes, params }) => { + if (!mediaTypes?.banner) return {}; + + const { sizes } = mediaTypes.banner; + return { + w: sizes[0][0], + h: sizes[0][1] + } +}; + +function getBidFloor(bidRequest) { + let bidfloor = deepAccess(bidRequest, 'params.bidFloor', 0) + + if (!bidfloor && isFn(bidRequest.getFloor)) { + let floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor)) { + bidfloor = floor.floor; + } + } + + return bidfloor; +} diff --git a/modules/mediaeyesBidAdapter.md b/modules/mediaeyesBidAdapter.md new file mode 100644 index 00000000000..40e1eb77d67 --- /dev/null +++ b/modules/mediaeyesBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: MediaEyes Bidder Adapter +Module Type: MediaEyes Bidder Adapter +Maintainer: giathinh.ly@urekamedia.vn +``` + +# Description + +Module that connects to MediaEyes Bidder System + +# Test Parameters +``` + var adUnits = [ + { + code: 'div-prebid', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'mediaeyes', + params: { + itemId: '4d27f3cc8bbd5bd153045e' // Item for test + } + } + ] + }, + ]; +``` diff --git a/test/spec/modules/mediaeyesBidAdapter_spec.js b/test/spec/modules/mediaeyesBidAdapter_spec.js new file mode 100644 index 00000000000..b79ece092a2 --- /dev/null +++ b/test/spec/modules/mediaeyesBidAdapter_spec.js @@ -0,0 +1,183 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/mediaeyesBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('mediaeyes adapter', function () { + let request; + let bannerResponse, invalidResponse; + + beforeEach(function () { + request = [ + { + bidder: 'mediaeyes', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + itemId: 'ec1d7389a4a5afa28a23c4', + bidFloor: 0.1 + } + } + ]; + bannerResponse = { + 'body': { + "id": "3c51f851-56d8-4513-b4bb-e5a1612cede3", + "seatbid": [ + { + "bid": [ + { + "impid": "3db1c7f2867eb3", + "adm": " ", + "iurl": "https://static.upremium.asia/n1191/ad/300x250_OWMrIjJQ.jpg", + "h": 250, + "w": 300, + "price": 0.25, + "crid": "6808551", + "adomain": [ + "google.com" + ], + "ext": { + "advertiser_name": "urekamedia", + "agency_name": "urekamedia" + } + } + ] + } + ] + } + }; + invalidResponse = { + 'body': { + + } + }; + }); + + describe('validations', function () { + it('isBidValid : itemId is passed', function () { + let bid = { + bidder: 'mediaeyes', + params: { + itemId: 'ec1d7389a4a5afa28a23c4', + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : itemId is not passed', function () { + let bid = { + bidder: 'mediaeyes', + params: { + + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(_Request); + }); + }); + + describe('responses processing', function () { + it('should return fully-initialized banner bid-response', function () { + let bidRequest = spec.buildRequests(request); + + let resp = spec.interpretResponse(bannerResponse, bidRequest[0])[0]; + expect(resp).to.have.property('requestId'); + expect(resp).to.have.property('cpm'); + expect(resp).to.have.property('width'); + expect(resp).to.have.property('height'); + expect(resp).to.have.property('creativeId'); + expect(resp).to.have.property('currency'); + expect(resp).to.have.property('ttl'); + expect(resp).to.have.property('ad'); + expect(resp).to.have.property('meta'); + }); + + it('no ads returned', function () { + let response = { + "body": { + "id": "0309d787-75cd-4e9d-a430-666fc76c1fbe", + "seatbid": [ + { + "bid": [] + } + ] + } + } + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result.length).to.equal(0); + }); + }) + + describe('setting imp.floor using floorModule', function () { + let newRequest; + let floorModuleTestData; + let getFloor = function (req) { + return floorModuleTestData['banner']; + }; + + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'USD', + 'floor': 1, + }, + }; + newRequest = utils.deepClone(request); + newRequest[0].getFloor = getFloor; + }); + + it('params bidfloor undefined', function () { + floorModuleTestData.banner.floor = 0; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request[0].data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(0); + }); + + it('floormodule if floor is not number', function () { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request[0].data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(0); + }); + + it('floormodule if currency is not matched', function () { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request[0].data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1); + }); + + it('bidFloor is not passed, use minimum from floorModule', function () { + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request[0].data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1); + }); + + it('if params bidFloor is passed, priority use it', function () { + newRequest[0].params.bidFloor = 1; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request[0].data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1); + }); + }); +}); From 33b2905a99249221b37cec2110614fd8e12475fc Mon Sep 17 00:00:00 2001 From: "pratik.ta" <143182729+Pratik3307@users.noreply.github.com> Date: Thu, 10 Apr 2025 19:24:50 +0530 Subject: [PATCH 1075/1097] Medianet Analytics & Adapter : refactor to shared utils (#12968) * refactor: clean up Medianet analytics and adapter * fix: resolve merge conflicts after merging with master --- libraries/medianetUtils/constants.js | 78 + libraries/medianetUtils/logKeys.js | 185 ++ libraries/medianetUtils/logger.js | 111 ++ libraries/medianetUtils/utils.js | 139 ++ modules/medianetAnalyticsAdapter.js | 1539 ++++++++--------- modules/medianetBidAdapter.js | 98 +- .../modules/medianetAnalyticsAdapter_spec.js | 961 +++++++--- test/spec/modules/medianetBidAdapter_spec.js | 15 +- 8 files changed, 1987 insertions(+), 1139 deletions(-) create mode 100644 libraries/medianetUtils/constants.js create mode 100644 libraries/medianetUtils/logKeys.js create mode 100644 libraries/medianetUtils/logger.js create mode 100644 libraries/medianetUtils/utils.js diff --git a/libraries/medianetUtils/constants.js b/libraries/medianetUtils/constants.js new file mode 100644 index 00000000000..b36d1aeeafb --- /dev/null +++ b/libraries/medianetUtils/constants.js @@ -0,0 +1,78 @@ +export const mnetGlobals = { + auctions: {}, // Stores details of ongoing or completed auctions + infoByAdIdMap: {}, // Maps ad IDs to their respective information + bdpMap: {}, + configuration: {}, + logsQueue: [], // Queue for storing logs + errorQueue: [], // Queue for storing errors, + eventQueue: null, + refererInfo: null, +}; + +export const LOGGING_DELAY = 2000; + +export const LOG_TYPE_ID = 'kfk'; +export const LOG_EVT_ID = 'projectevents'; +export const EVENT_PIXEL_URL = 'https://qsearch-a.akamaihd.net/log'; +export const POST_ENDPOINT = 'https://navvy.media.net/log'; +export const GET_ENDPOINT = 'https://pb-logs.media.net/log'; +export const ANALYTICS_VERSION = '2.0.0'; +export const PREBID_VERSION = '$prebid.version$'; +export const MEDIANET = 'medianet'; +export const GLOBAL_VENDOR_ID = 142; + +// Bid Status +export const BID_SUCCESS = 1; +export const BID_NOBID = 2; +export const BID_TIMEOUT = 3; +export const SUCCESS_AFTER_AUCTION = 5; +export const NOBID_AFTER_AUCTION = 6; +export const TIMEOUT_AFTER_AUCTION = 7; +export const BID_FLOOR_REJECTED = 12; + +export const DBF_PRIORITY = { + [BID_SUCCESS]: 4, + [BID_NOBID]: 3, + [SUCCESS_AFTER_AUCTION]: 2, + [BID_TIMEOUT]: 1, + [NOBID_AFTER_AUCTION]: 1, + [TIMEOUT_AFTER_AUCTION]: 0, + [BID_FLOOR_REJECTED]: 0 +}; + +// Properties +export const SEND_ALL_BID_PROP = 'enableSendAllBids'; +export const AUCTION_OPTIONS = 'auctionOptions'; + +// Errors +export const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; +export const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; +export const PBS_ERROR_STATUS_START = 2000; +export const WINNING_BID_ABSENT_ERROR = 'winning_bid_absent'; +export const WINNING_AUCTION_MISSING_ERROR = 'winning_auction_missing'; +export const ERROR_IWB_BID_MISSING = 'iwb_bid_missing'; +// Config +export const CONFIG_PENDING = 0; +export const CONFIG_PASS = 1; +export const CONFIG_ERROR = 3; +export const DEFAULT_LOGGING_PERCENT = 50; +export const CONFIG_URL = 'https://prebid.media.net/rtb/prebid/analytics/config'; +// Dummy Bidder +export const DUMMY_BIDDER = '-2'; + +// Video Constants +export const VIDEO_UUID_PENDING = 9999; + +export const VIDEO_CONTEXT = { + INSTREAM: 'instream', + OUTSTREAM: 'outstream' +} + +export const VIDEO_PLACEMENT = { + [VIDEO_CONTEXT.INSTREAM]: 1, + [VIDEO_CONTEXT.OUTSTREAM]: 6 +} + +// Log Types +export const LOG_APPR = 'APPR'; +export const LOG_RA = 'RA'; diff --git a/libraries/medianetUtils/logKeys.js b/libraries/medianetUtils/logKeys.js new file mode 100644 index 00000000000..27a2dff52e2 --- /dev/null +++ b/libraries/medianetUtils/logKeys.js @@ -0,0 +1,185 @@ +import { + calculateRoundTripTime, + getBidResponseSize, + getRequestedSizes, + getTopWindowReferrer, + getWindowSize, + pick, +} from './utils.js'; +import { config } from '../../src/config.js'; +import { AUCTION_COMPLETED, AUCTION_IN_PROGRESS } from '../../src/auction.js'; +import { safeJSONEncode, deepAccess } from '../../src/utils.js'; +import { getProcessedParams, mergeFieldsToLog } from './logger.js'; +import { + ANALYTICS_VERSION, + AUCTION_OPTIONS, LOG_APPR, LOG_RA, + MEDIANET, + PREBID_VERSION, + TIMEOUT_AFTER_AUCTION, + VIDEO_PLACEMENT, +} from './constants.js'; + +export const KeysMap = { + Pick: { + Auction: [ + 'adSlots', () => ({}), + 'bidsRequested', () => [], + 'bidsReceived', () => [], + 'responseBids', () => [], + 'bidsTimeout', () => [], + 'noBids', () => [], + 'psiBids', () => [], + 'bidderRequests as pendingRequests', (bidderRequests) => bidderRequests.length, + 'hasEnded', () => false, + 'auctionId', + 'auctionStatus', + 'timestamp', + 'timeout', + 'bidderRequests.0.ortb2.sup_log', + 'bidderRequests.0.bids.0.floorData', + 'bidderRequests.0.refererInfo', + 'bidderRequests.0 as consentInfo', (consentInfo) => pick(consentInfo, ['gdprConsent', 'uspConsent']), + ], + AdSlot: [ + 'code', + 'ext as adext', + 'logged', () => ({[LOG_APPR]: false, [LOG_RA]: false}), + 'supcrid', (_, __, adUnit) => adUnit.emsCode || adUnit.code, + 'ortb2Imp', + ], + BidRequest: [ + // from bidRequest + 'bidder', + 'src', + 'params', + 'bidId', + 'bidId as originalRequestId', + 'adUnitCode', + 'mediaTypes', (mediaTypes) => Object.keys(mediaTypes), + 'iwb', () => 0, + 'winner', () => 0, + 'status', () => TIMEOUT_AFTER_AUCTION, + 'responseReceived', () => false, + 'sizes', (_, __, bidRequest) => getRequestedSizes(bidRequest), + 'ext', () => ({}), + ], + BidResponse: [ + 'originalCurrency', + 'originalRequestId', + 'requestId', + // multi-bid + 'originalBidder', + // from bidderRequest + 'bidderCode', + 'currency', + 'adId', + 'snm as status', + 'mediaType', + 'cpm', + 'timeToRespond', + 'dealId', + 'meta', + 'originalCpm', + 'bidderCode', + 'creativeId', + 'latestTargetedAuctionId', + 'floorData', + 'width', + 'height', + 'size', (size, logObj) => size || getBidResponseSize(logObj.width, logObj.height), + 'ext', + ] + }, + Log: { + Bid: [ + 'meta.advertiserDomains as advurl', (advertiserDomains = []) => advertiserDomains.join(','), + 'currMul as omul', + 'originalCurrency as icurr', + 'inCurrMul as imul', + 'mediaTypes as req_mtype', (mediaTypes) => mediaTypes.join('|'), + 'mediaType as res_mtype', + 'mediaType as mtype', (mediaType, __, {mediaTypes}) => mediaType || mediaTypes.join('|'), + 'ext.seat as ortbseat', + 'ext.int_dsp_id as mx_int_dsp_id', + 'ext.int_agency_id as mx_int_agency_id', + 'ext.pvid as mpvid', + 'ext.crid', (crid, _, bidObj) => crid || deepAccess(bidObj.params, 'crid'), + 'ext', (ext, _, bidObj) => safeJSONEncode(bidObj.bidder === MEDIANET ? ext : {}), + 'requestId as reqid', (requestId, _, bidObj) => requestId || bidObj.bidId, + 'originalRequestId as ogReqId', + 'adId as adid', + 'originalBidder as og_pvnm', + 'bidderCode as pvnm', (bidderCode, _, {bidder}) => bidderCode || bidder, + 'src', + 'originalCpm as ogbdp', + 'bdp', (bdp, _, bidObj) => bdp || bidObj.cpm, + 'cpm as cbdp', + 'dfpbd', + 'dealId as dId', + 'winner', + 'currency as curr', + 'timeToRespond as rests', + 'status', + 'iwb', + 'floorData.floorValue as bidflr', + 'floorData.floorRule as flrrule', + 'floorRuleValue as flrRulePrice', + 'serverLatencyMillis as rtime', + 'creativeId as pcrid', + 'dbf', + 'latestTargetedAuctionId as lacid', + 'utime', + 'metrics as ltime', (metrics, logObj) => logObj.rests || calculateRoundTripTime(metrics), + 'bidder as issec', (bidder) => config.getConfig(AUCTION_OPTIONS)?.secondaryBidders?.includes?.(bidder) ? 1 : 0, + 'sizes as szs', (sizes) => sizes.join('|'), + 'size', (size, _, bidObj) => (bidObj.res_sizes || [size]).join('|'), + 'params', (params, _, bidObj) => getProcessedParams(params, bidObj.status), + ], + AdSlot: [ + 'supcrid', + 'code as og_supcrid', + 'context as vplcmtt', (context) => VIDEO_PLACEMENT[context] || 0, + 'ortb2Imp.instl as oop', + 'targeting as targ', (targeting) => safeJSONEncode(targeting), + 'adext', (adext) => encodeURIComponent(safeJSONEncode(adext)), + ], + Auction: [ + 'auctionId as acid', + 'sup_log', + 'consentInfo.gdprConsent.consentString as gdprConsent', + 'consentInfo.uspConsent as ccpa', + 'consentInfo.gdprConsent.gdprApplies as gdpr', (gdprApplies) => (gdprApplies ? '1' : '0'), + 'coppa', () => (config.getConfig('coppa') === true ? 1 : 0), + 'hasEnded as aucstatus', (hasEnded) => (hasEnded ? AUCTION_COMPLETED : AUCTION_IN_PROGRESS), + 'availableUids as uid_mod_avb', (availableUids) => safeJSONEncode(availableUids), + 'uidValues as id_details', (uidValues) => safeJSONEncode(uidValues), + 'refererInfo.topmostLocation as requrl', + 'refererInfo.domain as dn', + 'refererInfo.ref', getTopWindowReferrer, + 'screen', getWindowSize, + 'timeout as tmax', + 'sts', (_, __, auctionObj) => auctionObj.auctionStartTime - auctionObj.timestamp, + 'ets', (_, __, auctionObj) => auctionObj.auctionEndTime - auctionObj.timestamp || -1, + 'floorData.modelVersion as flrver', + 'floorData as flrdata', (floorData) => mergeFieldsToLog(pick(floorData, [ + 'location as ln', + 'skipped as skp', + 'skipRate as sr', + 'fetchStatus as fs', + 'enforcements.enforceJS as enfj', + 'enforcements.floorDeals as enfd' + ])) + ], + Globals: [ + 'cid', + 'ajaxState as ajx', + 'pubLper as plper', + 'loggingPercent as lper', (loggingPercent) => Math.round(100 / loggingPercent), + 'enableDbf', () => 1, + 'flt', () => 1, + 'pbv', () => PREBID_VERSION, + 'pbav', () => ANALYTICS_VERSION, + 'coppa', () => (config.getConfig('coppa') === true ? 1 : 0) + ] + } +}; diff --git a/libraries/medianetUtils/logger.js b/libraries/medianetUtils/logger.js new file mode 100644 index 00000000000..e394fb50c26 --- /dev/null +++ b/libraries/medianetUtils/logger.js @@ -0,0 +1,111 @@ +import { flattenObj, formatQS as mnFormatQS, pick } from './utils.js'; +import { formatQS, triggerPixel, isPlainObject } from '../../src/utils.js'; +import { + ANALYTICS_VERSION, BID_SUCCESS, + EVENT_PIXEL_URL, LOG_APPR, + LOG_EVT_ID, + LOG_TYPE_ID, + mnetGlobals, POST_ENDPOINT, + PREBID_VERSION +} from './constants.js'; +import { ajax, sendBeacon } from '../../src/ajax.js'; +import { getRefererInfo } from '../../src/refererDetection.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; + +export function shouldLogAPPR(auctionData, adUnitId) { + const adSlot = auctionData.adSlots[adUnitId]; + return ( + ( + mnetGlobals.configuration.shouldLogAPPR + ) && + !adSlot.logged[LOG_APPR] + ); +} + +// common error logger for medianet analytics and bid adapter +export function errorLogger(event, data = undefined, analytics = true) { + const { name, cid, value, relatedData, logData, project } = isPlainObject(event) ? {...event, logData: data} : { name: event, relatedData: data }; + const refererInfo = mnetGlobals.refererInfo || getRefererInfo(); + const errorData = Object.assign({}, + { + logid: LOG_TYPE_ID, + evtid: LOG_EVT_ID, + project: project || (analytics ? 'prebidanalytics' : 'prebid'), + dn: refererInfo.domain || '', + requrl: refererInfo.topmostLocation || '', + pbav: getGlobal().medianetGlobals.analyticsEnabled ? ANALYTICS_VERSION : '', + pbver: PREBID_VERSION, + // To handle media.net alias bidderAdapter (params.cid) code errors + cid: cid || mnetGlobals.configuration.cid || '', + event: name || '', + value: value || '', + rd: relatedData || '', + }, + logData); + const loggingHost = analytics ? EVENT_PIXEL_URL : POST_ENDPOINT; + const payload = analytics ? mnFormatQS(errorData) : formatQS(errorData); + + function send() { + if (!analytics) { + fireAjaxLog(loggingHost, payload, pick(errorData, ['cid', 'project', 'event as value'])); + return; + } + const pixelUrl = getUrl(); + mnetGlobals.errorQueue.push(pixelUrl); + triggerPixel(pixelUrl); + } + + function getUrl() { + return loggingHost + '?' + payload; + } + + return { + send, + getUrl + }; +} + +export function getLoggingPayload(queryParams) { + return `logid=kfk&evtid=prebid_analytics_events_client&${queryParams}`; +} + +export function firePostLog(url, payload) { + try { + mnetGlobals.logsQueue.push(url + '?' + payload); + const isSent = sendBeacon(url, payload); + if (!isSent) { + fireAjaxLog(url, payload); + errorLogger('sb_log_failed').send(); + } + } catch (e) { + fireAjaxLog(url, payload); + errorLogger('sb_not_supported').send(); + } +} + +export function fireAjaxLog(url, payload, errorData = {}) { + ajax(url, + { + success: () => undefined, + error: (_, {reason}) => errorLogger(Object.assign(errorData, {name: 'ajax_log_failed', relatedData: reason})).send() + }, + payload, + { + method: 'POST', + } + ); +} + +export function mergeFieldsToLog(objParams) { + const logParams = Object.keys(objParams).map((param) => { + const value = objParams[param]; + return `${param}=${value === undefined ? '' : value}`; + }); + return logParams.join('||'); +} + +export function getProcessedParams(params, status) { + if (params === undefined || status !== BID_SUCCESS) return ''; + const clonedFlattenParams = flattenObj(params, '', {}); + return JSON.stringify(clonedFlattenParams); +} diff --git a/libraries/medianetUtils/utils.js b/libraries/medianetUtils/utils.js new file mode 100644 index 00000000000..a3d67ded450 --- /dev/null +++ b/libraries/medianetUtils/utils.js @@ -0,0 +1,139 @@ +import { _map, deepAccess, isFn, isPlainObject, uniques } from '../../src/utils.js'; +import {mnetGlobals} from './constants.js'; +import {getViewportSize} from '../viewport/viewport.js'; + +export function findBidObj(list = [], key, value) { + return list.find((bid) => { + return bid[key] === value; + }); +} + +export function filterBidsListByFilters(list = [], filters) { + return list.filter((bid) => { + return Object.entries(filters).every(([key, value]) => bid[key] === value); + }); +} + +export function flattenObj(obj, parent, res = {}) { + for (let key in obj) { + if (Array.isArray(obj[key])) { + continue; + } + const propName = parent ? parent + '.' + key : key; + if (typeof obj[key] == 'object') { + flattenObj(obj[key], propName, res); + } else { + res[propName] = String(obj[key]); + } + } + return res; +} + +export function formatQS(data) { + return _map(data, (value, key) => { + if (value === undefined) { + return key + '='; + } + if (isPlainObject(value)) { + value = JSON.stringify(value); + } + return key + '=' + encodeURIComponent(value); + }).join('&'); +} + +export function getWindowSize() { + const { width, height } = getViewportSize(); + let w = width || -1; + let h = height || -1; + return `${w}x${h}`; +} + +export function getRequestedSizes({ mediaTypes, sizes }) { + const banner = deepAccess(mediaTypes, 'banner.sizes') || sizes || []; + const native = deepAccess(mediaTypes, 'native') ? [[1, 1]] : []; + const playerSize = deepAccess(mediaTypes, 'video.playerSize') || []; + let video = []; + if (playerSize.length === 2) { + video = [playerSize]; + } + return [...banner, ...native, ...video].filter(uniques).map((size) => size.join('x')); +} + +export function getBidResponseSize(width, height) { + if (isNaN(width) || isNaN(height)) { + return ''; + } + return width + 'x' + height; +} + +export function calculateRoundTripTime(metrics) { + if (!metrics || !isFn(metrics.getMetrics)) { + return -1; + } + const prebidMetrics = metrics.getMetrics(); + const ltime = + prebidMetrics['adapter.client.total'] || + prebidMetrics['adapter.s2s.total']?.[0] || + prebidMetrics['adapter.s2s.total'] || + -1; + return parseFloat(ltime.toFixed(2)); +} + +export function pick(context, properties, omitKeys = false) { + if (typeof context !== 'object' || context === null) return {}; + const acc = {}; + properties.forEach((prop, index) => { + if (typeof prop === 'function') { + return; + } + + let value, alias; + let [key, aliasPart] = prop.split(/\sas\s/i); + key = key.trim(); + alias = aliasPart?.trim() || key.split('.').pop(); + + value = deepAccess(context, key); + + if (typeof properties[index + 1] === 'function') { + value = properties[index + 1](value, acc, context); + } + + if (value !== undefined || !omitKeys) { + acc[alias] = value; + } + }); + + return acc; +} + +export const onHidden = (cb, once = true) => { + const onHiddenOrPageHide = (event) => { + if (document.visibilityState === 'hidden') { + cb(event); + if (once) { + window.removeEventListener('visibilitychange', onHiddenOrPageHide, true); + window.removeEventListener('pagehide', onHiddenOrPageHide, true); + } + } + }; + window.addEventListener('visibilitychange', onHiddenOrPageHide, true); + // Some browsers have buggy implementations of visibilitychange, + // so we use pagehide in addition, just to be safe. + window.addEventListener('pagehide', onHiddenOrPageHide, true); + + // if the document is already hidden + onHiddenOrPageHide({}); +}; + +export function getTopWindowReferrer(ref) { + try { + if (ref) return ref; + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} + +export function isSampledForLogging() { + return Math.random() * 100 < parseFloat(mnetGlobals.configuration.loggingPercent); +} diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index c0448f4f056..69dfaab3db5 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -1,977 +1,877 @@ import { - _map, deepAccess, - getWindowTop, + deepSetValue, groupBy, - isEmpty, + isFn, isPlainObject, logError, logInfo, - triggerPixel, - uniques, - deepSetValue, + parseUrl, + safeJSONEncode, } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {config} from '../src/config.js'; import adapterManager from '../src/adapterManager.js'; -import { BID_STATUS, EVENTS, TARGETING_KEYS } from '../src/constants.js'; -import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {BID_STATUS, EVENTS, REJECTION_REASON, S2S, TARGETING_KEYS} from '../src/constants.js'; import {getRefererInfo} from '../src/refererDetection.js'; -import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; -import {includes} from '../src/polyfill.js'; +import {ajax} from '../src/ajax.js'; +import {getPriceByGranularity} from '../src/auction.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import {registerVastTrackers} from '../libraries/vastTrackers/vastTrackers.js'; +import { + filterBidsListByFilters, + findBidObj, + formatQS, getBidResponseSize, + getRequestedSizes, + isSampledForLogging, + onHidden, + pick, +} from '../libraries/medianetUtils/utils.js'; +import { + errorLogger, + firePostLog, + getLoggingPayload, + shouldLogAPPR +} from '../libraries/medianetUtils/logger.js'; +import {KeysMap} from '../libraries/medianetUtils/logKeys.js'; +import { + LOGGING_DELAY, + BID_FLOOR_REJECTED, + BID_NOBID, + BID_SUCCESS, + BID_TIMEOUT, + CONFIG_ERROR, + CONFIG_PASS, + CONFIG_PENDING, + CONFIG_URL, + DBF_PRIORITY, + DEFAULT_LOGGING_PERCENT, + DUMMY_BIDDER, + ERROR_CONFIG_FETCH, + ERROR_CONFIG_JSON_PARSE, + GET_ENDPOINT, + GLOBAL_VENDOR_ID, + LOG_APPR, + LOG_RA, + mnetGlobals, + NOBID_AFTER_AUCTION, + PBS_ERROR_STATUS_START, + POST_ENDPOINT, + SEND_ALL_BID_PROP, + SUCCESS_AFTER_AUCTION, + TIMEOUT_AFTER_AUCTION, + VIDEO_CONTEXT, + VIDEO_UUID_PENDING, + WINNING_AUCTION_MISSING_ERROR, + WINNING_BID_ABSENT_ERROR, ERROR_IWB_BID_MISSING +} from '../libraries/medianetUtils/constants.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import {convertCurrency} from '../libraries/currencyUtils/currency.js'; -import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {ADPOD} from '../src/mediaTypes.js'; -import { getViewportSize } from '../libraries/viewport/viewport.js'; - -const analyticsType = 'endpoint'; -const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; -const CONFIG_URL = 'https://prebid.media.net/rtb/prebid/analytics/config'; -const EVENT_PIXEL_URL = 'https://qsearch-a.akamaihd.net/log'; -const DEFAULT_LOGGING_PERCENT = 50; -const ANALYTICS_VERSION = '1.0.0'; - -const PRICE_GRANULARITY = { - 'auto': 'pbAg', - 'custom': 'pbCg', - 'dense': 'pbDg', - 'low': 'pbLg', - 'medium': 'pbMg', - 'high': 'pbHg', -}; - -const MEDIANET_BIDDER_CODE = 'medianet'; -const PREBID_VERSION = '$prebid.version$' -const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; -const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; -const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; -const ERROR_WINNING_AUCTION_MISSING = 'winning_auction_missing'; -const BID_SUCCESS = 1; -const BID_NOBID = 2; -const BID_TIMEOUT = 3; -const BID_FLOOR_REJECTED = 12; -const DUMMY_BIDDER = '-2'; - -const CONFIG_PENDING = 0; -const CONFIG_PASS = 1; -const CONFIG_ERROR = 3; - -const VALID_URL_KEY = ['canonical_url', 'og_url', 'twitter_url']; -const DEFAULT_URL_KEY = 'topmostLocation'; - -const LOG_TYPE = { - APPR: 'APPR', - RA: 'RA' -}; - -let auctions = {}; -let config; -let pageDetails; -let logsQueue = []; -let errorQueue = []; - -class ErrorLogger { - constructor(event, additionalData) { - this.event = event; - this.logid = 'kfk'; - this.evtid = 'projectevents'; - this.project = 'prebidanalytics'; - this.dn = pageDetails.domain || ''; - this.requrl = pageDetails.topmostLocation || ''; - this.pbversion = PREBID_VERSION; - this.cid = config.cid || ''; - this.rd = additionalData; - } - - send() { - let url = EVENT_PIXEL_URL + '?' + formatQS(this); - errorQueue.push(url); - triggerPixel(url); - } -} -class Configure { - constructor(cid) { - this.cid = cid; - this.pubLper = -1; - this.ajaxState = CONFIG_PENDING; - this.loggingPercent = DEFAULT_LOGGING_PERCENT; - this.urlToConsume = DEFAULT_URL_KEY; - this.debug = false; - this.gdprConsent = undefined; - this.gdprApplies = undefined; - this.uspConsent = undefined; - this.shouldBeLogged = {}; - this.mnetDebugConfig = ''; - } +// General Constants +const ADAPTER_CODE = 'medianetAnalytics'; - getLoggingData() { - return { - cid: this.cid, - lper: Math.round(100 / this.loggingPercent), - plper: this.pubLper, - gdpr: this.gdprApplies ? '1' : '0', - gdprConsent: this.gdprConsent, - ccpa: this.uspConsent, - ajx: this.ajaxState, - pbv: PREBID_VERSION, - pbav: ANALYTICS_VERSION, - flt: 1, - enableDbf: 1 +const LoggingEvents = { + SETUP_LISTENERS: 'setupListeners', + CONFIG_INIT: 'loadConfig', + FETCH_CONFIG: 'fetchConfig', + ...EVENTS, +}; +// =============================[ CONFIGURATION ]================================= +function fetchAnalyticsConfig() { + function updateLoggingPercentage(response) { + if (!isNaN(parseInt(response.percentage, 10))) { + mnetGlobals.configuration.loggingPercent = response.percentage; } } - _configURL() { - return CONFIG_URL + '?cid=' + encodeURIComponent(this.cid) + '&dn=' + encodeURIComponent(pageDetails.domain); + function parseConfig(loggingConfig) { + const domain = deepAccess(loggingConfig, 'domain.' + mnetGlobals.refererInfo.domain); + updateLoggingPercentage(domain || loggingConfig); + + mnetGlobals.configuration.shouldLogAPPR = isSampledForLogging(); + mnetGlobals.configuration.ajaxState = CONFIG_PASS; } - _parseResponse(response) { + function success(response) { try { - response = JSON.parse(response); - this.setDataFromResponse(response); - this.overrideDomainLevelData(response); - this.overrideToDebug(this.mnetDebugConfig); - this.urlToConsume = includes(VALID_URL_KEY, response.urlKey) ? response.urlKey : this.urlToConsume; - this.ajaxState = CONFIG_PASS; + parseConfig(JSON.parse(response)); } catch (e) { - this.ajaxState = CONFIG_ERROR; - /* eslint no-new: "error" */ - new ErrorLogger(ERROR_CONFIG_JSON_PARSE, e).send(); + mnetGlobals.configuration.ajaxState = CONFIG_ERROR; + errorLogger(ERROR_CONFIG_JSON_PARSE, e).send(); } } - setDataFromResponse(response) { - if (!isNaN(parseInt(response.percentage, 10))) { - this.loggingPercent = response.percentage; - } + function error() { + mnetGlobals.configuration.ajaxState = CONFIG_ERROR; + errorLogger(ERROR_CONFIG_FETCH).send(); } - overrideDomainLevelData(response) { - const domain = deepAccess(response, 'domain.' + pageDetails.domain); - if (domain) { - this.setDataFromResponse(domain); - } + function getConfigURL() { + return `${CONFIG_URL}?${(formatQS({ + cid: mnetGlobals.configuration.cid, + dn: mnetGlobals.refererInfo.domain + }))}` } - overrideToDebug(response) { - if (response === '') return; - try { - this.setDataFromResponse(JSON.parse(decodeURIComponent(response))); - } catch (e) { - } + // Debugging and default settings + const urlObj = parseUrl(mnetGlobals.refererInfo.topmostLocation); + if (deepAccess(urlObj, 'search.medianet_test') || urlObj.hostname === 'localhost') { + Object.assign(mnetGlobals.configuration, { + loggingPercent: 100, + shouldLogAPPR: true, + ajaxState: CONFIG_PASS, + debug: true, + }); + return; } - _errorFetch() { - this.ajaxState = CONFIG_ERROR; - new ErrorLogger(ERROR_CONFIG_FETCH).send(); + if (mnetGlobals.configuration.loggingConfig) { + mnetGlobals.configuration.loggingDelay = mnetGlobals.configuration.loggingConfig.loggingDelay || mnetGlobals.configuration.loggingDelay; + parseConfig(mnetGlobals.configuration.loggingConfig); + return; } - init() { - // Forces Logging % to 100% - let urlObj = URL.parseUrl(pageDetails.topmostLocation); - if (deepAccess(urlObj, 'search.medianet_test') || urlObj.hostname === 'localhost') { - this.loggingPercent = 100; - this.ajaxState = CONFIG_PASS; - this.debug = true; - return; + ajax(getConfigURL(), { success, error }); +} + +function initConfiguration(eventType, configuration) { + mnetGlobals.refererInfo = getRefererInfo(); + // Holds configuration details + mnetGlobals.configuration = { + ...mnetGlobals.configuration, + pubLper: configuration.options.sampling || '', + ajaxState: CONFIG_PENDING, + shouldLogAPPR: false, + debug: false, + loggingPercent: DEFAULT_LOGGING_PERCENT, + enabledUids: [], + commonParams: configuration.commonParams, + loggingDelay: LOGGING_DELAY, + ...configuration.options, + }; + mnetGlobals.eventQueue.enqueueEvent(LoggingEvents.SETUP_LISTENERS, mnetGlobals.configuration); + mnetGlobals.eventQueue.enqueueEvent(LoggingEvents.FETCH_CONFIG, mnetGlobals.configuration); +} + +// ======================[ LOGGING AND TRACKING ]=========================== +function doLogging(auctionObj, adUnitCode, logType, bidObj) { + const queryParams = getQueryString(auctionObj, adUnitCode, logType, bidObj); + // Use the generated queryParams for logging + const payload = getLoggingPayload(queryParams); + firePostLog(POST_ENDPOINT, payload); + auctionObj.adSlots[adUnitCode].logged[logType] = true; +} + +function getQueryString(auctionObj, adUnitCode, logType, winningBidObj) { + const commonParams = getCommonParams(auctionObj, adUnitCode, logType); + const bidParams = getBidParams(auctionObj, adUnitCode, winningBidObj); + const queryString = formatQS(commonParams); + let bidStrings = bidParams.map((bid) => `&${formatQS(bid)}`).join(''); + return `${queryString}${bidStrings}`; +} + +function getErrorTracker(bidResponse, error) { + const stack = { + acid: bidResponse.auctionId, + bidId: bidResponse.requestId, + crid: bidResponse.creativeId, + ttl: bidResponse.ttl, + bidder: bidResponse.bidderCode || bidResponse.adapterCode, + context: bidResponse.context, + }; + return [ + { + event: 'impressions', + url: errorLogger('vast_tracker_handler_' + error, stack).getUrl(), + }, + ]; +} + +function vastTrackerHandler(bidResponse, { auction, bidRequest }) { + if (!config.getConfig('cache')?.url) return []; + try { + if (auction) { + mnetGlobals.eventQueue.enqueueEvent(EVENTS.AUCTION_INIT, auction); } - if (deepAccess(urlObj, 'search.mnet_setconfig')) { - this.mnetDebugConfig = deepAccess(urlObj, 'search.mnet_setconfig'); + const bidderRequest = findBidObj(auction.bidderRequests, 'bidderRequestId', bidRequest?.bidderRequestId); + if (bidderRequest) { + mnetGlobals.eventQueue.enqueueEvent(EVENTS.BID_REQUESTED, bidderRequest); } - ajax( - this._configURL(), + const auctionObject = mnetGlobals.auctions[bidResponse.auctionId]; + if (!auctionObject) { + return getErrorTracker(bidResponse, 'missing_auction'); + } + const requestId = bidResponse.originalRequestId || bidResponse.requestId; + const bidRequestObj = findBidObj(auctionObject.bidsRequested, 'bidId', requestId); + if (!bidRequestObj) { + return getErrorTracker(bidResponse, 'missing_bidrequest'); + } + const context = auctionObject.adSlots[bidRequestObj?.adUnitCode]?.context; + if (context !== VIDEO_CONTEXT.INSTREAM) { + return []; + } + bidRequestObj.status = VIDEO_UUID_PENDING; + const { validBidResponseObj } = processBidResponse(auctionObject, bidRequestObj, bidResponse); + const queryParams = getQueryString(auctionObject, bidRequestObj.adUnitCode, LOG_RA, validBidResponseObj); + return [ { - success: this._parseResponse.bind(this), - error: this._errorFetch.bind(this) - } - ); + event: 'impressions', + url: `${GET_ENDPOINT}?${getLoggingPayload(queryParams)}`, + }, + ]; + } catch (e) { + errorLogger('vast_tracker_handler_error', e).send(); + return []; } } -class PageDetail { - constructor () { - const ogUrl = this._getUrlFromSelector('meta[property="og:url"]', 'content'); - const twitterUrl = this._getUrlFromSelector('meta[name="twitter:url"]', 'content'); - const refererInfo = getRefererInfo(); - - // TODO: are these the right refererInfo values? - this.domain = refererInfo.domain; - this.page = refererInfo.page; - this.is_top = refererInfo.reachedTop; - this.referrer = refererInfo.ref || window.document.referrer; - this.canonical_url = refererInfo.canonicalUrl; - this.og_url = ogUrl; - this.twitter_url = twitterUrl; - this.topmostLocation = refererInfo.topmostLocation; - this.screen = this._getWindowSize(); +function processBidResponse(auctionObj, bidRequest, bidResponse) { + bidRequest.bidTs = Date.now(); + bidRequest.responseReceived = true; + // timeout bid can be there for the bidResponse came after auctionEnd + // or it can be from a multi-bid response with a different requestId + let bidObj = findBidObj(auctionObj.bidsReceived, 'bidId', bidResponse.requestId); + let bidIdAlreadyPresent = true; + if (!bidObj || bidObj.status === BID_SUCCESS) { + bidObj = Object.assign({}, bidRequest); + bidIdAlreadyPresent = false; } - _getWindowSize() { - const { width, height } = getViewportSize(); - let w = width || -1; - let h = height || -1; - return `${w}x${h}`; - } + Object.assign( + bidObj, + pick(bidResponse, KeysMap.Pick.BidResponse), + getDfpCurrencyInfo(bidResponse), + ); - _getAttributeFromSelector(selector, attribute) { - try { - let doc = getWindowTop().document; - let element = doc.querySelector(selector); - if (element !== null && element[attribute]) { - return element[attribute]; - } - } catch (e) {} + if (bidObj.status === BID_STATUS.BID_REJECTED) { + bidObj.status = BID_FLOOR_REJECTED; + } else { + bidObj.status = auctionObj.hasEnded ? SUCCESS_AFTER_AUCTION : BID_SUCCESS; } + bidRequest.status = bidObj.status; + return { validBidResponseObj: bidObj, bidIdAlreadyPresent }; +} - _getAbsoluteUrl(url) { - let aTag = getWindowTop().document.createElement('a'); - aTag.href = url; +function getLoggingBids(auctionObj, adUnitCode) { + const receivedResponse = buildBidResponseMap(auctionObj, adUnitCode); + const dummyBids = getDummyBids(auctionObj, adUnitCode, receivedResponse); - return aTag.href; - } + return [...auctionObj.psiBids, ...auctionObj.bidsReceived, ...dummyBids].filter( + (bid) => bid.adUnitCode === adUnitCode + ); +} - _getUrlFromSelector(selector, attribute) { - let attr = this._getAttributeFromSelector(selector, attribute); - return attr && this._getAbsoluteUrl(attr); +function getBidParams(auctionObj, adUnitCode, winningBidObj) { + let loggableBids = []; + if (winningBidObj) { + const responseInfoMap = mnetGlobals.infoByAdIdMap[winningBidObj.adId] || {}; + const bidLogData = Object.assign( + {}, + pick(winningBidObj, KeysMap.Log.Bid), + pick(responseInfoMap.srrEvt, ['lineItemId as lid', 'creativeId as crtvid'], true) + ); + loggableBids.push(bidLogData); + } else { + // For logging all bids + loggableBids = setDbf(getLoggingBids(auctionObj, adUnitCode)) + .map((bidObj) => pick(bidObj, KeysMap.Log.Bid)) + .map(({ winner, ...restParams }) => restParams); } + return loggableBids; +} - getLoggingData() { - return { - requrl: this[config.urlToConsume] || this.topmostLocation, - dn: this.domain, - ref: this.referrer, - screen: this.screen - } - } +function getDummyBids(auctionObj, adUnitCode, receivedResponse) { + const emptyBids = []; + + auctionObj.bidsRequested + .forEach((bid) => { + if (bid.adUnitCode !== adUnitCode) return + const emptySizes = bid.sizes.filter( + (size) => !deepAccess(receivedResponse, `${bid.bidId}.${size}`) + ); + + if (emptySizes.length > 0) { + const bidObj = Object.assign({}, bid, { + res_sizes: emptySizes, + status: bid.status === BID_SUCCESS ? BID_NOBID : bid.status, + iwb: 0, + }); + emptyBids.push(bidObj); + } + }); + + return emptyBids; } -class AdSlot { - constructor(tmax, supplyAdCode, context, adext) { - this.tmax = tmax; - this.supplyAdCode = supplyAdCode; - this.context = context; - this.adext = adext; - this.logged = {}; - this.targeting = undefined; - this.medianetPresent = 0; - } +function setDbf(bids) { + const highestBids = {}; - getShouldBeLogged(logType) { - if (!config.shouldBeLogged.hasOwnProperty(logType)) { - config.shouldBeLogged[logType] = isSampled(); + bids.forEach((bid) => { + bid.dbf = 0; // Default all dbf to 0 + if (isHigher(bid, highestBids[bid.bidder])) { + highestBids[bid.bidder] = bid; } - return config.shouldBeLogged[logType]; - } + }); - getLoggingData() { - return Object.assign({ - supcrid: this.supplyAdCode, - tmax: this.tmax, - targ: JSON.stringify(this.targeting), - ismn: this.medianetPresent, - vplcmtt: this.getVideoPlacement(), - }, - this.adext && {'adext': JSON.stringify(this.adext)}, - ); - } - getVideoPlacement() { - switch (this.context) { - case INSTREAM: - return 1 - case OUTSTREAM: - return 6 - case ADPOD: - return 7 - default: - return 0 - } - } + // Mark the highest-priority bids as dbf = 1 + Object.values(highestBids).forEach((bid) => { + bid.dbf = 1; + }); + + return bids; } -class BidWrapper { - constructor() { - this.bidReqs = []; - this.bidObjs = []; - } +function isHigher(newBid, currentBid = {}) { + const newPriority = DBF_PRIORITY[newBid.status] ?? 0; + const currentPriority = DBF_PRIORITY[currentBid.status] ?? -1; - findReqBid(bidId) { - return this.bidReqs.find(bid => { - return bid['bidId'] === bidId - }); - } + return ( + newPriority > currentPriority || + (newPriority === currentPriority && (newBid.cpm ?? 0) > (currentBid.cpm ?? -1)) + ); +} - findBidObj(key, value) { - return this.bidObjs.find(bid => { - return bid[key] === value - }); - } +function markWinningBidsAndImpressionStatus(auctionObj) { + const sendAllBidsEnabled = config.getConfig(SEND_ALL_BID_PROP) === true; - addBidReq(bidRequest) { - this.bidReqs.push(bidRequest) - } + const updatePsiBid = (winner, adUnitCode, winnersAdIds) => { + const psiBidObj = findBidObj(auctionObj.psiBids, 'adUnitCode', adUnitCode); + if (!psiBidObj) { + return; + } + if (winnersAdIds.length > 0) { + psiBidObj.iwb = 1; + psiBidObj.width = deepAccess(winner, 'width') ?? null; + psiBidObj.height = deepAccess(winner, 'height') ?? null; + psiBidObj.size = getBidResponseSize(psiBidObj.width, psiBidObj.height); + } + const bidsRequested = filterBidsListByFilters(auctionObj.bidsRequested, { adUnitCode }); + const bidsTimeout = filterBidsListByFilters(auctionObj.bidsTimeout, { adUnitCode }); - addBidObj(bidObj) { - if (!(bidObj instanceof Bid)) { - bidObj = Bid.getInstance(bidObj); + if (bidsRequested.length === bidsTimeout.length) { + psiBidObj.status = BID_TIMEOUT; } - const bidReq = this.findReqBid(bidObj.bidId); - if (bidReq instanceof Bid) { - bidReq.used = true; + }; + + const markValidBidsAsWinners = (winnersAdIds) => { + if (!sendAllBidsEnabled) { + return; } - this.bidObjs.push(bidObj); - } + winnersAdIds.forEach((adId) => { + const sendAllWinnerBid = findBidObj(auctionObj.bidsReceived, 'adId', adId); + if (sendAllWinnerBid) { + sendAllWinnerBid.iwb = 1; + } + }); + }; - getAdSlotBidRequests(adSlot) { - return this.bidReqs.filter((bid) => bid.adUnitCode === adSlot); + const checkWinnersForIwb = (winner, winningBidObj) => { + // bid-cache can be enabled + const fromSameAuction = (winner?.auctionId === auctionObj.auctionId); + if (fromSameAuction && !winningBidObj) { + errorLogger(ERROR_IWB_BID_MISSING, pick(winner, ['adId', 'auctionId', 'bidder', 'requestId', 'cpm', 'adUnitCode'])).send(); + } } - getAdSlotBidResponses(adSlot) { - return this.bidObjs.filter((bid) => bid.adUnitCode === adSlot); - } + Object.keys(auctionObj.adSlots).forEach((adUnitCode) => { + const winner = getGlobal().getHighestCpmBids(adUnitCode)[0]; + const winningBid = findBidObj(auctionObj.bidsReceived, 'adId', winner?.adId); + if (winningBid && winningBid.status === BID_SUCCESS) { + winningBid.iwb = 1; + } + checkWinnersForIwb(winner, winningBid); + + const targetingForAdUnitCode = getGlobal().getAdserverTargetingForAdUnitCode(adUnitCode); + auctionObj.adSlots[adUnitCode].targeting = targetingForAdUnitCode; + const winnersAdIds = []; + Object.keys(targetingForAdUnitCode).forEach((key) => { + if (key.includes(TARGETING_KEYS.AD_ID)) { + winnersAdIds.push(targetingForAdUnitCode[key]); + } + }); + markValidBidsAsWinners(winnersAdIds); + updatePsiBid(winner, adUnitCode, winnersAdIds); + }); +} +// =====================[ S2S ]====================== +function addS2sInfo(auctionObj, bidderRequests) { + bidderRequests.forEach((bidderRequest) => { + bidderRequest.bids.forEach((bidRequest) => { + if (bidRequest.src !== S2S.SRC) return; + + const bidObjs = filterBidsListByFilters(auctionObj.bidsReceived, {bidId: bidRequest.bidId}); + + bidObjs.forEach((bidObj) => { + bidObj.serverLatencyMillis = bidderRequest.serverResponseTimeMs; + const serverError = deepAccess(bidderRequest, `serverErrors.0`); + if (serverError && bidObj.status !== BID_SUCCESS) { + bidObj.status = PBS_ERROR_STATUS_START + serverError.code; + } + }); + }); + }); } -class Bid { - constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, resSizes) { - this.bidId = bidId; - this.bidder = bidder; - this.src = src; - this.start = start; - this.adUnitCode = adUnitCode; - this.allMediaTypeSizes = allMediaTypeSizes; - this.iwb = 0; - this.winner = 0; - this.status = bidder === DUMMY_BIDDER ? BID_SUCCESS : BID_TIMEOUT; - this.ext = {}; - this.originalCpm = undefined; - this.cpm = undefined; - this.dfpbd = undefined; - this.width = undefined; - this.height = undefined; - this.mediaType = mediaType; - this.timeToRespond = undefined; - this.dealId = undefined; - this.creativeId = undefined; - this.adId = undefined; - this.currency = undefined; - this.crid = undefined; - this.pubcrid = undefined; - this.mpvid = undefined; - this.floorPrice = undefined; - this.floorRule = undefined; - this.serverLatencyMillis = undefined; - this.used = false; - this.originalRequestId = bidId; - this.requestId = undefined; - this.advUrl = undefined; - this.latestAcid = undefined; - this.originalCurrency = undefined; - this.currMul = undefined; - this.inCurrMul = undefined; - this.res_mtype = undefined; - this.res_sizes = resSizes; - this.req_mtype = mediaType; - } +// =========================[ HELPERS ]========================== +function getAllMediaTypesAndSizes(adUnits) { + const allMTypes = new Set(); + const allSizes = new Set(); - get size() { - if (!this.width || !this.height) { - return ''; - } - return this.width + 'x' + this.height; - } + adUnits.forEach(({ mediaTypes, sizes }) => { + Object.keys(mediaTypes).forEach((mType) => allMTypes.add(mType)); + getRequestedSizes({ mediaTypes, sizes }).forEach((size) => allSizes.add(size)); + }); - static getInstance(bidProps) { - const bidObj = new Bid(); - return bidProps && Object.assign(bidObj, bidProps); - } + return { allMTypes: [...allMTypes], allSizes: [...allSizes] }; +} - getLoggingData() { - return { - reqId: this.requestId || this.bidId, - ogReqId: this.originalRequestId, - adid: this.adId, - pvnm: this.bidder, - src: this.src, - ogbdp: this.originalCpm, - bdp: this.cpm, - cbdp: this.dfpbd, - dfpbd: this.dfpbd, - szs: this.allMediaTypeSizes.join('|'), - size: (this.res_sizes || [this.size]).join('|'), - mtype: this.mediaType, - dId: this.dealId, - winner: this.winner, - curr: this.currency, - rests: this.timeToRespond, - status: this.status, - iwb: this.iwb, - crid: this.crid, - pubcrid: this.pubcrid, - mpvid: this.mpvid, - bidflr: this.floorPrice, - flrrule: this.floorRule, - ext: JSON.stringify(this.ext), - rtime: this.serverLatencyMillis, - advurl: this.advUrl, - lacid: this.latestAcid, - icurr: this.originalCurrency, - imul: this.inCurrMul, - omul: this.currMul, - res_mtype: this.res_mtype, - req_mtype: this.req_mtype - } - } +function getAdSlot(adUnits) { + const context = adUnits.find((adUnit) => !!deepAccess(adUnit, 'mediaTypes.video.context'))?.mediaTypes.video.context; + return Object.assign( + {}, + pick(adUnits[0], [...KeysMap.Pick.AdSlot, 'context', () => context]), + getAllMediaTypesAndSizes(adUnits) + ); } -class Auction { - constructor(acid) { - this.acid = acid; - this.status = AUCTION_IN_PROGRESS; - this.bidWrapper = new BidWrapper(); - this.adSlots = {}; - this.auctionInitTime = undefined; - this.auctionStartTime = undefined; - this.setTargetingTime = undefined; - this.auctionEndTime = undefined; - this.bidWonTime = undefined; - this.floorData = {}; - } +function buildBidResponseMap(auctionObj, adUnitCode) { + const responses = [].concat(auctionObj.bidsReceived, auctionObj.psiBids).filter((bid) => bid.adUnitCode === adUnitCode); + const receivedResponse = {}; - hasEnded() { - return this.status === AUCTION_COMPLETED; - } + // Set true in map for success bids + responses + .forEach((bid) => { + if (!bid.size) return; + const sizeKey = `${bid.bidId}.${bid.size}`; + deepSetValue(receivedResponse, sizeKey, true); + }); - getLoggingData() { - return { - sts: this.auctionStartTime - this.auctionInitTime, - ets: this.auctionEndTime - this.auctionInitTime, - tts: this.setTargetingTime - this.auctionInitTime, - wts: this.bidWonTime - this.auctionInitTime, - aucstatus: this.status, - acid: this.acid, - flrdata: this._mergeFieldsToLog({ - ln: this.floorData.location, - skp: this.floorData.skipped, - enfj: deepAccess(this.floorData, 'enforcements.enforceJS'), - enfd: deepAccess(this.floorData, 'enforcements.floorDeals'), - sr: this.floorData.skipRate, - fs: this.floorData.fetchStatus - }), - flrver: this.floorData.modelVersion - } - } + // For non-success bids: + // 1) set bid.res_sizes = (sizes for which no successful bid received) + // 2) set true in map + responses + .forEach((bid) => { + if (bid.size) return; + bid.res_sizes = bid.sizes.filter( + (size) => !deepAccess(receivedResponse, `${bid.bidId}.${size}`) + ); + bid.res_sizes.forEach((size) => + deepSetValue(receivedResponse, `${bid.bidId}.${size}`, true) + ); + }); + + return receivedResponse; +} - addSlot({ adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, tmax, adext, context }) { - if (adUnitCode && this.adSlots[adUnitCode] === undefined) { - this.adSlots[adUnitCode] = new AdSlot(tmax, supplyAdCode, context, adext); - this.addBidObj(new Bid('-1', DUMMY_BIDDER, 'client', Date.now(), adUnitCode, mediaTypes, allMediaTypeSizes)); +function getDfpCurrencyInfo(bidResponse) { + function convertCurrency(price, fromCurrency, toCurrency) { + try { + return getGlobal().convertCurrency?.(price, fromCurrency, toCurrency) || price; + } catch (e) { + logError(`Currency conversion failed: ${fromCurrency} -> ${toCurrency} for price ${price}`); + return price; } } - addBid(bid) { - this.bidWrapper.addBidReq(bid); + let { source, ext, cpm, originalCpm, currency = '', originalCurrency = '', adserverTargeting } = bidResponse; + currency = currency.toUpperCase(); + originalCurrency = (originalCurrency || currency).toUpperCase(); + originalCpm = originalCpm || cpm; + // https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#original-bid-cpm + if (source === S2S.SRC) { + originalCurrency = deepAccess(ext, 'origbidcur') || originalCurrency; + originalCpm = deepAccess(ext, 'origbidcpm') || originalCpm; } - addBidObj(bidObj) { - this.bidWrapper.addBidObj(bidObj) - } + let currMul = 1; + let inCurrMul = 1; - findReqBid(bidId) { - return this.bidWrapper.findReqBid(bidId) + if (currency !== 'USD') { + cpm = convertCurrency(cpm, currency, 'USD'); + currMul = convertCurrency(1, 'USD', currency); } - findBidObj(key, value) { - return this.bidWrapper.findBidObj(key, value) + if (originalCurrency !== 'USD') { + originalCpm = convertCurrency(originalCpm, originalCurrency, 'USD'); + inCurrMul = convertCurrency(1, 'USD', originalCurrency); } - getAdSlotBidRequests(adSlot) { - return this.bidWrapper.getAdSlotBidRequests(adSlot); + let bdp = mnetGlobals.bdpMap[bidResponse.adId]; + if (bdp) { + bdp = convertCurrency(bdp, currency, 'USD'); } - getAdSlotBidResponses(adSlot) { - return this.bidWrapper.getAdSlotBidResponses(adSlot); + // dfpBd + let dfpbd = deepAccess(adserverTargeting, `${TARGETING_KEYS.PRICE_BUCKET}`); + if (!dfpbd) { + const priceGranularityKey = getPriceByGranularity(bidResponse); + dfpbd = bidResponse[priceGranularityKey] || bidResponse.cpm; } - - _mergeFieldsToLog(objParams) { - let logParams = []; - let value; - for (const param of Object.keys(objParams)) { - value = objParams[param]; - logParams.push(param + '=' + (value === undefined ? '' : value)); - } - return logParams.join('||'); + if (currency !== 'USD' && dfpbd) { + dfpbd = convertCurrency(dfpbd, currency, 'USD'); } -} -function auctionInitHandler({auctionId, adUnits, timeout, timestamp, bidderRequests}) { - if (auctionId && auctions[auctionId] === undefined) { - auctions[auctionId] = new Auction(auctionId); - auctions[auctionId].auctionInitTime = timestamp; - } - addAddSlots(auctionId, adUnits, timeout); - const floorData = deepAccess(bidderRequests, '0.bids.0.floorData'); - if (floorData) { - auctions[auctionId].floorData = {...floorData}; - } + return { + originalCpm, + bdp, + cpm, + dfpbd, + currMul, + inCurrMul, + }; } -function addAddSlots(auctionId, adUnits, tmax) { - adUnits = adUnits || []; - const groupedAdUnits = groupBy(adUnits, 'code'); - Object.keys(groupedAdUnits).forEach((adUnitCode) => { - const adUnits = groupedAdUnits[adUnitCode]; - const supplyAdCode = deepAccess(adUnits, '0.adUnitCode') || adUnitCode; - let context = ''; - let adext = {}; - - const mediaTypeMap = {}; - const oSizes = {banner: [], video: []}; - adUnits.forEach(({mediaTypes, sizes, ext}) => { - mediaTypes = mediaTypes || {}; - adext = Object.assign(adext, ext || deepAccess(mediaTypes, 'banner.ext')); - context = deepAccess(mediaTypes, 'video.context') || context; - Object.keys(mediaTypes).forEach((mediaType) => mediaTypeMap[mediaType] = 1); - const sizeObject = _getSizes(mediaTypes, sizes); - sizeObject.banner.forEach(size => oSizes.banner.push(size)); - sizeObject.video.forEach(size => oSizes.video.push(size)); - }); - - adext = isEmpty(adext) ? undefined : adext; - oSizes.banner = oSizes.banner.filter(uniques); - oSizes.video = oSizes.video.filter(uniques); - oSizes.native = mediaTypeMap.native === 1 ? [[1, 1].join('x')] : []; - const allMediaTypeSizes = [].concat(oSizes.banner, oSizes.native, oSizes.video); - const mediaTypes = Object.keys(mediaTypeMap).join('|'); - auctions[auctionId].addSlot({adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, context, tmax, adext}); +/** + * Generates auction-level, slot-level, and config-level parameters. + */ +function getCommonParams(auctionObj, adUnitCode, logType) { + const adSlotObj = auctionObj.adSlots[adUnitCode] || {}; + let commonParams = Object.assign( + { lgtp: logType }, + pick(mnetGlobals.configuration, KeysMap.Log.Globals), + pick(auctionObj, KeysMap.Log.Auction), + pick(adSlotObj, KeysMap.Log.AdSlot), + mnetGlobals.configuration.commonParams + ); + if (logType === LOG_RA) { + commonParams.lper = 1; + } + Object.keys(commonParams).forEach((key) => { + if (commonParams[key] === undefined) { + delete commonParams[key]; + } }); + return commonParams; } -function bidRequestedHandler({ auctionId, auctionStart, bids, start, uspConsent, gdpr }) { - if (!(auctions[auctionId] instanceof Auction)) { - return; - } +function setupSlotResponseReceivedListener() { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().addEventListener('slotResponseReceived', (slotEvent) => { + if (!slotEvent.slot || !isFn(slotEvent.slot.getResponseInformation)) return; - config.gdprApplies = !!(gdpr && gdpr.gdprApplies); - if (config.gdprApplies) { - config.gdprConsent = gdpr.consentString || ''; - } + const slot = slotEvent.slot; + const slotInf = slot.getResponseInformation(); - config.uspConsent = config.uspConsent || uspConsent; - - auctions[auctionId].auctionStartTime = auctionStart; - bids.forEach(bid => { - const { adUnitCode, bidder, bidId, src, mediaTypes, sizes } = bid; - const sizeObject = _getSizes(mediaTypes, sizes); - const requestSizes = [].concat(sizeObject.banner, sizeObject.native, sizeObject.video); - const bidObj = new Bid(bidId, bidder, src, start, adUnitCode, mediaTypes && Object.keys(mediaTypes).join('|'), requestSizes); - auctions[auctionId].addBid(bidObj); - if (bidder === MEDIANET_BIDDER_CODE) { - bidObj.crid = deepAccess(bid, 'params.crid'); - bidObj.pubcrid = deepAccess(bid, 'params.crid'); - auctions[auctionId].adSlots[adUnitCode].medianetPresent = 1; - } + const setSlotResponseInf = (adId) => { + mnetGlobals.infoByAdIdMap[adId] = mnetGlobals.infoByAdIdMap[adId] || {}; + mnetGlobals.infoByAdIdMap[adId].srrEvt = slotInf; + }; + + slot.getTargetingKeys() + .filter((key) => key.startsWith(TARGETING_KEYS.AD_ID)) + .forEach((key) => setSlotResponseInf(slot.getTargeting(key)[0])); + }); }); } -function _getSizes(mediaTypes, sizes) { - const banner = deepAccess(mediaTypes, 'banner.sizes') || sizes || []; - const native = deepAccess(mediaTypes, 'native') ? [[1, 1]] : []; - const playerSize = deepAccess(mediaTypes, 'video.playerSize') || []; - let video = []; - if (playerSize.length === 2) { - video = [playerSize] - } - return { - banner: banner.map(size => size.join('x')), - native: native.map(size => size.join('x')), - video: video.map(size => size.join('x')) +// ======================[ EVENT QUEUE PROCESSING ]======================= +const eventQueue = () => { + function enqueueEvent(eventType, args) { + if (mnetGlobals.configuration.debug) { + logInfo(eventType, args); + } + processEventQueue(eventType, args); } -} - -function bidResponseHandler(bid) { - const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder, meta } = bid; - let {originalCpm, creativeId, adId, currency, originalCurrency} = bid; - if (!(auctions[auctionId] instanceof Auction)) { - return; + function processEventQueue(eventType, args) { + try { + const handler = eventListeners[eventType]; + if (!handler) { + return; + } + handler(eventType, args); + } catch (e) { + errorLogger(`${eventType}_handler_error`, e).send(); + } } - const reqId = originalRequestId || requestId; - const bidReq = auctions[auctionId].findReqBid(reqId); - if (!(bidReq instanceof Bid)) return; + return { + enqueueEvent, + processEventQueue, + }; +}; - let bidObj = auctions[auctionId].findBidObj('bidId', requestId); - let isBidOverridden = true; - if (!bidObj || bidObj.status === BID_SUCCESS) { - bidObj = {}; - isBidOverridden = false; - } - currency = currency ? currency.toUpperCase() : ''; - originalCurrency = originalCurrency ? originalCurrency.toUpperCase() : currency; - Object.assign(bidObj, bidReq, - { cpm, width, height, mediaType, timeToRespond, dealId, creativeId, originalRequestId, requestId }, - { adId, currency, originalCurrency } - ); - bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue'); - bidObj.floorRule = deepAccess(bid, 'floorData.floorRule'); - bidObj.originalCpm = originalCpm || cpm; - bidObj.advUrl = meta && meta.advertiserDomains && meta.advertiserDomains.join(','); - bidObj.currMul = 1; - bidObj.inCurrMul = 1; - if (bidObj.originalCurrency !== 'USD') { - bidObj.originalCpm = exchangeCurrency(bidObj.originalCpm, bidObj.originalCurrency, 'USD'); - bidObj.inCurrMul = exchangeCurrency(1, 'USD', bidObj.originalCurrency) - } - if (bidObj.currency !== 'USD') { - bidObj.cpm = exchangeCurrency(bidObj.cpm, bidObj.currency, 'USD'); - bidObj.currMul = exchangeCurrency(1, 'USD', bidObj.currency) - } - let dfpbd = deepAccess(bid, 'adserverTargeting.hb_pb'); - if (!dfpbd) { - let priceGranularity = getPriceGranularity(bid); - let priceGranularityKey = PRICE_GRANULARITY[priceGranularity]; - dfpbd = bid[priceGranularityKey] || cpm; - } - bidObj.dfpbd = dfpbd; - if (bid.status === BID_STATUS.BID_REJECTED) { - bidObj.status = BID_FLOOR_REJECTED; - } else { - bidObj.status = BID_SUCCESS; +// ======================[ AUCTION EVENT HANDLERS ]===================== +function auctionInitHandler(eventType, auction) { + let auctionObj = mnetGlobals.auctions[auction.auctionId]; + if (auctionObj) { + return; } + auctionObj = pick(auction, KeysMap.Pick.Auction); + // addAddSlots + Object + .values(groupBy(auction.adUnits, 'code')) + .map(getAdSlot) + .forEach(adSlot => { + // Assign adSlot to auctionObj.adSlots + auctionObj.adSlots[adSlot.code] = adSlot; + // Push the PSI bid + auctionObj.psiBids.push({ + src: 'client', + bidId: '-1', + originalRequestId: '-1', + bidder: DUMMY_BIDDER, + mediaTypes: adSlot.allMTypes, + sizes: adSlot.allSizes, + size: getBidResponseSize(-1, -1), + width: -1, + height: -1, + iwb: 0, + status: BID_SUCCESS, + adUnitCode: adSlot.code, + }); + }); - if (bidder === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { - Object.assign( - bidObj, - { 'ext': bid.ext }, - { 'mpvid': bid.ext.pvid }, - bid.ext.crid && { 'crid': bid.ext.crid } - ); - } - if (typeof bid.serverResponseTimeMs !== 'undefined') { - bidObj.serverLatencyMillis = bid.serverResponseTimeMs; - } - bidObj.res_mtype = mediaType; - !isBidOverridden && auctions[auctionId].addBidObj(bidObj); + // addUidData + const userIds = deepAccess(auction.bidderRequests, '0.bids.0.userId'); + if (isPlainObject(userIds)) { + const enabledUids = mnetGlobals.configuration.enabledUids || []; + auctionObj.availableUids = Object.keys(userIds).sort(); + auctionObj.uidValues = auctionObj.availableUids + .filter((key) => enabledUids.includes(key)) + .map((key) => `${key}##${safeJSONEncode(userIds[key])}`); + } + mnetGlobals.refererInfo = auctionObj.refererInfo; + mnetGlobals.auctions[auction.auctionId] = auctionObj; } -function exchangeCurrency(price, fromCurrency, toCurrency) { - try { - return convertCurrency(price, fromCurrency, toCurrency, false).toFixed(4) - } catch (e) { - logError(`Media.net Analytics Adapter: Could not convert ${fromCurrency} to ${toCurrency} for price ${price}`); - } - return price; +function auctionEndHandler(eventType, auctionEndData) { + const auctionObj = mnetGlobals.auctions[auctionEndData.auctionId]; + if (!auctionObj) return; + + auctionObj.hasEnded = true; + auctionObj.auctionEndTime = auctionEndData.auctionEnd; + markWinningBidsAndImpressionStatus(auctionObj); + const execute = () => { + addS2sInfo(auctionObj, auctionEndData.bidderRequests); + Object.keys(auctionObj.adSlots).forEach((adUnitCode) => { + shouldLogAPPR(auctionObj, adUnitCode) && doLogging(auctionObj, adUnitCode, LOG_APPR); + }); + }; + const timeout = auctionObj.pendingRequests === 0 ? 0 : mnetGlobals.configuration.loggingDelay; + auctionObj.loggerTimeout = timeout; + Promise.race([ + new Promise((resolve) => setTimeout(resolve, timeout)), + new Promise((resolve) => onHidden(resolve)), + ]).finally(execute); } -function noBidResponseHandler({ auctionId, bidId }) { - if (!(auctions[auctionId] instanceof Auction)) { - return; - } - if (auctions[auctionId].hasEnded()) { - return; - } - const bidReq = auctions[auctionId].findReqBid(bidId); - if (!(bidReq instanceof Bid) || bidReq.used) { - return; - } - const bidObj = {...bidReq}; - bidObj.status = BID_NOBID; - auctions[auctionId].addBidObj(bidObj); +function bidRequestedHandler(eventType, bidRequestedData) { + const auctionObj = mnetGlobals.auctions[bidRequestedData.auctionId]; + if (!auctionObj) return; + auctionObj.auctionStartTime = bidRequestedData.auctionStart; + bidRequestedData.bids + // In the case of video (instream), the `bidRequested` object might already exist. + .filter(({ bidId }) => !findBidObj(auctionObj.bidsRequested, 'bidId', bidId)) + .forEach((bidRequested) => { + const bidRequestObj = Object.assign({}, + pick(bidRequested, KeysMap.Pick.BidRequest), + ); + auctionObj.bidsRequested.push(bidRequestObj); + }); } -function bidTimeoutHandler(timedOutBids) { - timedOutBids.map(({bidId, auctionId}) => { - if (!(auctions[auctionId] instanceof Auction)) { - return; - } - const bidReq = auctions[auctionId].findReqBid(bidId); - if (!(bidReq instanceof Bid) || bidReq.used) { - return; - } - const bidObj = {...bidReq}; - bidObj.status = BID_TIMEOUT; - auctions[auctionId].addBidObj(bidObj); - }) -} +function bidResponseHandler(eventType, validBidResponse) { + const auctionObj = mnetGlobals.auctions[validBidResponse.auctionId]; + if (!auctionObj) return; -function auctionEndHandler({auctionId, auctionEnd}) { - if (!(auctions[auctionId] instanceof Auction)) { - return; - } - auctions[auctionId].status = AUCTION_COMPLETED; - auctions[auctionId].auctionEndTime = auctionEnd; -} + const requestId = validBidResponse.originalRequestId || validBidResponse.requestId; + const bidRequest = findBidObj(auctionObj.bidsRequested, 'bidId', requestId); + if (!bidRequest) return; -function setTargetingHandler(params) { - for (const adunit of Object.keys(params)) { - for (const auctionId of Object.keys(auctions)) { - let auctionObj = auctions[auctionId]; - let adunitObj = auctionObj.adSlots[adunit]; - if (!(adunitObj instanceof AdSlot)) { - continue; - } - adunitObj.targeting = params[adunit]; - auctionObj.setTargetingTime = Date.now(); - let targetingObj = Object.keys(params[adunit]).reduce((result, key) => { - if (key.indexOf(TARGETING_KEYS.AD_ID) !== -1) { - result[key] = params[adunit][key] - } - return result; - }, {}); - const winnerAdId = params[adunit][TARGETING_KEYS.AD_ID]; - let winningBid; - let bidAdIds = Object.keys(targetingObj).map(k => targetingObj[k]); - auctionObj.bidWrapper.bidObjs.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { - bid.iwb = 1; - if (bid.adId === winnerAdId) { - winningBid = bid; - } - }); - auctionObj.bidWrapper.bidObjs.forEach(bid => { - if (bid.bidder === DUMMY_BIDDER && bid.adUnitCode === adunit) { - bid.iwb = bidAdIds.length === 0 ? 0 : 1; - bid.width = deepAccess(winningBid, 'width'); - bid.height = deepAccess(winningBid, 'height'); - } - }); - sendEvent(auctionId, adunit, LOG_TYPE.APPR); - } + const { bidIdAlreadyPresent = true, validBidResponseObj } = processBidResponse( + auctionObj, + bidRequest, + validBidResponse + ); + auctionObj.responseBids.push(validBidResponseObj); + if (!bidIdAlreadyPresent && validBidResponseObj) { + auctionObj.bidsReceived.push(validBidResponseObj); } } -function bidWonHandler(bid) { - const { auctionId, adUnitCode, adId, bidder, requestId, originalRequestId } = bid; - if (!(auctions[auctionId] instanceof Auction)) { - new ErrorLogger(ERROR_WINNING_AUCTION_MISSING, { +function bidWonHandler(eventType, winner) { + const { auctionId, adUnitCode, adId, bidder, requestId, originalRequestId } = winner; + const auctionObj = mnetGlobals.auctions[auctionId]; + if (!auctionObj) { + errorLogger(WINNING_AUCTION_MISSING_ERROR, { adId, auctionId, adUnitCode, bidder, requestId, - originalRequestId + originalRequestId, }).send(); return; } - let bidObj = auctions[auctionId].findBidObj('adId', adId); - if (!(bidObj instanceof Bid)) { - new ErrorLogger(ERROR_WINNING_BID_ABSENT, { + const bidObj = findBidObj(auctionObj.bidsReceived, 'adId', adId); + if (!bidObj) { + errorLogger(WINNING_BID_ABSENT_ERROR, { adId, auctionId, adUnitCode, bidder, requestId, - originalRequestId + originalRequestId, }).send(); return; } - bidObj.latestAcid = bid.latestTargetedAuctionId; - auctions[auctionId].bidWonTime = Date.now(); - bidObj.winner = 1; - sendEvent(auctionId, adUnitCode, LOG_TYPE.RA, bidObj.adId); -} - -function isSampled() { - return Math.random() * 100 < parseFloat(config.loggingPercent); -} - -function isValidAuctionAdSlot(acid, adtag) { - return (auctions[acid] instanceof Auction) && (auctions[acid].adSlots[adtag] instanceof AdSlot); -} - -function sendEvent(id, adunit, logType, adId) { - if (!isValidAuctionAdSlot(id, adunit)) { - return; - } - if (logType === LOG_TYPE.RA) { - fireAuctionLog(id, adunit, logType, adId); - } else { - fireApPrLog(id, adunit, logType) - } + auctionObj.bidWonTime = Date.now(); + // Update the bidObj with the winner details + Object.assign( + bidObj, + pick(winner, [ + 'latestTargetedAuctionId', + 'winner', () => 1, + 'utime', () => (bidObj.bidTs ? Date.now() - bidObj.bidTs : undefined) + ]) + ); + doLogging(auctionObj, adUnitCode, LOG_RA, bidObj); } -function fireApPrLog(auctionId, adUnitName, logType) { - const adSlot = auctions[auctionId].adSlots[adUnitName]; - if (adSlot.getShouldBeLogged(logType) && !adSlot.logged[logType]) { - fireAuctionLog(auctionId, adUnitName, logType); - adSlot.logged[logType] = true; +function bidRejectedHandler(eventType, bidRejected) { + const auctionObj = mnetGlobals.auctions[bidRejected.auctionId]; + if (!auctionObj) return; + if (bidRejected.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET) { + bidRejected.snm = BID_STATUS.BID_REJECTED; + bidResponseHandler(eventType, bidRejected); } } -function getCommonLoggingData(acid, adtag) { - let commonParams = Object.assign(pageDetails.getLoggingData(), config.getLoggingData()); - let adunitParams = auctions[acid].adSlots[adtag].getLoggingData(); - let auctionParams = auctions[acid].getLoggingData(); - return Object.assign(commonParams, adunitParams, auctionParams); -} +function noBidResponseHandler(eventType, noBid) { + const auctionObj = mnetGlobals.auctions[noBid.auctionId]; + if (!auctionObj) return; -function getResponseSizeMap(acid, adtag) { - const responses = auctions[acid].getAdSlotBidResponses(adtag); - const receivedResponse = {}; - // Set true in map for success bids - responses.filter((bid) => bid.size !== '') - .forEach((bid) => deepSetValue(receivedResponse, `${bid.bidId}.${bid.size}`, true)); + const bidRequest = findBidObj(auctionObj.bidsRequested, 'bidId', noBid.bidId); + if (!bidRequest || bidRequest.responseReceived) return; - // For non-success bids: - // 1) set bid.res_sizes = (sizes for which no successful bid received) - // 2) set true in map - responses.filter((bid) => bid.size === '').forEach((bid) => { - bid.res_sizes = bid.allMediaTypeSizes.filter((size) => !deepAccess(receivedResponse, `${bid.bidId}.${size}`)); - bid.allMediaTypeSizes.forEach((size) => deepSetValue(receivedResponse, `${bid.bidId}.${size}`, true)); + const status = auctionObj.hasEnded ? NOBID_AFTER_AUCTION : BID_NOBID; + const noBidObj = Object.assign({}, bidRequest, { + status, + metrics: noBid.metrics, }); - return receivedResponse; + bidRequest.status = status; + bidRequest.responseReceived = true; + auctionObj.noBids.push(noBidObj); + auctionObj.bidsReceived.push(noBidObj); } -function getDummyBids(acid, adtag, receivedResponse) { - const emptyBids = []; - auctions[acid].getAdSlotBidRequests(adtag).forEach(({ bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, status }) => { - const emptySizes = allMediaTypeSizes.filter((size) => !deepAccess(receivedResponse, `${bidId}.${size}`)); - if (bidder !== DUMMY_BIDDER && emptySizes.length > 0) { - const bid = new Bid(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, emptySizes); - bid.status = status === BID_SUCCESS ? BID_NOBID : status; - emptyBids.push(bid); - } +function bidTimeoutHandler(eventType, timedOutBids) { + const auctionId = deepAccess(timedOutBids, '0.auctionId'); + const auctionObj = mnetGlobals.auctions[auctionId]; + if (!auctionObj) return; + + const status = auctionObj.hasEnded ? TIMEOUT_AFTER_AUCTION : BID_TIMEOUT; + timedOutBids.forEach((timedOutBid) => { + const bidRequest = findBidObj(auctionObj.bidsRequested, 'bidId', timedOutBid.bidId); + if (!bidRequest) return; + const timedOutObj = Object.assign({}, bidRequest, { + status, + metrics: timedOutBid.metrics, + }); + bidRequest.status = status; + bidRequest.responseReceived = true; + auctionObj.bidsTimeout.push(timedOutObj); + auctionObj.bidsReceived.push(timedOutObj); }); - return emptyBids; } -function getLoggingBids(acid, adtag) { - const receivedResponse = getResponseSizeMap(acid, adtag); - const dummyBids = getDummyBids(acid, adtag, receivedResponse); - - return [...auctions[acid].getAdSlotBidResponses(adtag), ...dummyBids]; +function bidderDoneHandler(eventType, args) { + const auctionObj = mnetGlobals.auctions[args.auctionId]; + if (!auctionObj) return; + auctionObj.pendingRequests--; } -function fireAuctionLog(acid, adtag, logType, adId) { - let commonParams = getCommonLoggingData(acid, adtag); - commonParams.lgtp = logType; - let targeting = deepAccess(commonParams, 'targ'); - - Object.keys(commonParams).forEach((key) => (commonParams[key] == null) && delete commonParams[key]); - delete commonParams.targ; - - let bidParams; - - if (logType === LOG_TYPE.RA) { - const winningBidObj = auctions[acid].findBidObj('adId', adId); - if (!winningBidObj) return; - const winLogData = winningBidObj.getLoggingData(); - bidParams = [winLogData]; - commonParams.lper = 1; - } else { - bidParams = getLoggingBids(acid, adtag).map((bid) => bid.getLoggingData()) - delete commonParams.wts; - } - let mnetPresent = bidParams.filter(b => b.pvnm === MEDIANET_BIDDER_CODE).length > 0; - if (!mnetPresent) { - bidParams = bidParams.map(({mpvid, crid, ext, pubcrid, ...restParams}) => restParams); - } - - let url = formatQS(commonParams) + '&'; - bidParams.forEach(function(bidParams) { - url = url + formatQS(bidParams) + '&'; - }); - url = url + formatQS({targ: targeting}); - firePixel(url); +function adRenderFailedHandler(eventType, args) { + const {reason, message, bid: { + auctionId, + adUnitCode, + bidder, + creativeId}} = args; + errorLogger(eventType, { + reason, + message, + auctionId, + adUnitCode, + bidder, + creativeId, + }).send(); } -function formatQS(data) { - return _map(data, (value, key) => { - if (value === undefined) { - return key + '='; - } - if (isPlainObject(value)) { - value = JSON.stringify(value); - } - return key + '=' + encodeURIComponent(value); - }).join('&'); +function adRenderSucceededHandler(eventType, args) { + const {bid: {auctionId, adUnitCode, bidder, creativeId}} = args; + errorLogger(eventType, { + auctionId, + adUnitCode, + bidder, + creativeId, + }).send(); } -function firePixel(qs) { - logsQueue.push(ENDPOINT + '&' + qs); - triggerPixel(ENDPOINT + '&' + qs); +function staleRenderHandler(eventType, args) { + const { auctionId, adUnitCode, bidder, creativeId, adId, cpm } = args; + errorLogger(eventType, { + adId, + auctionId, + adUnitCode, + bidder, + creativeId, + cpm, + }).send(); } -class URL { - static parseUrl(url) { - let parsed = document.createElement('a'); - parsed.href = decodeURIComponent(url); - return { - hostname: parsed.hostname, - search: URL.parseQS(parsed.search || ''), - host: parsed.host || window.location.host - }; - } - static parseQS(query) { - return !query ? {} : query - .replace(/^\?/, '') - .split('&') - .reduce((acc, criteria) => { - let [k, v] = criteria.split('='); - if (/\[\]$/.test(k)) { - k = k.replace('[]', ''); - acc[k] = acc[k] || []; - acc[k].push(v); - } else { - acc[k] = v || ''; - } - return acc; - }, {}); - } -} +// ======================[ EVENT LISTENER MAP ]===================== +// Define listener functions for each event +const eventListeners = { + [LoggingEvents.SETUP_LISTENERS]: setupListeners, + [LoggingEvents.CONFIG_INIT]: initConfiguration, + [LoggingEvents.FETCH_CONFIG]: fetchAnalyticsConfig, + [LoggingEvents.AUCTION_INIT]: auctionInitHandler, + [LoggingEvents.AUCTION_END]: auctionEndHandler, + [LoggingEvents.BID_REQUESTED]: bidRequestedHandler, + [LoggingEvents.BID_RESPONSE]: bidResponseHandler, + [LoggingEvents.BID_REJECTED]: bidRejectedHandler, + [LoggingEvents.NO_BID]: noBidResponseHandler, + [LoggingEvents.BIDDER_DONE]: bidderDoneHandler, + [LoggingEvents.BID_TIMEOUT]: bidTimeoutHandler, + [LoggingEvents.BID_WON]: bidWonHandler, + [LoggingEvents.AD_RENDER_FAILED]: adRenderFailedHandler, + [LoggingEvents.AD_RENDER_SUCCEEDED]: adRenderSucceededHandler, + [LoggingEvents.STALE_RENDER]: staleRenderHandler, +}; -let medianetAnalytics = Object.assign(adapter({URL, analyticsType}), { +let medianetAnalytics = Object.assign(adapter({ analyticsType: 'endpoint' }), { getlogsQueue() { - return logsQueue; + return mnetGlobals.logsQueue; }, + getErrorQueue() { - return errorQueue; + return mnetGlobals.errorQueue; + }, + + getVastTrackerHandler() { + return vastTrackerHandler; }, + clearlogsQueue() { - logsQueue = []; - errorQueue = []; - auctions = {}; + mnetGlobals.logsQueue = []; + mnetGlobals.errorQueue = []; + mnetGlobals.auctions = {}; }, + track({ eventType, args }) { - if (config.debug) { - logInfo(eventType, args); - } - switch (eventType) { - case EVENTS.AUCTION_INIT: { - auctionInitHandler(args); - break; - } - case EVENTS.BID_REQUESTED: { - bidRequestedHandler(args); - break; - } - case EVENTS.BID_RESPONSE: { - bidResponseHandler(args); - break; - } - case EVENTS.BID_TIMEOUT: { - bidTimeoutHandler(args); - break; - } - case EVENTS.NO_BID: { - noBidResponseHandler(args); - break; - } - case EVENTS.AUCTION_END: { - auctionEndHandler(args); - break; - } - case EVENTS.SET_TARGETING: { - setTargetingHandler(args); - break; - } - case EVENTS.BID_WON: { - bidWonHandler(args); - break; - } - } - }}); + mnetGlobals.eventQueue.enqueueEvent(eventType, args); + }, +}); -medianetAnalytics.originEnableAnalytics = medianetAnalytics.enableAnalytics; +function setupListeners() { + setupSlotResponseReceivedListener(); + registerVastTrackers(MODULE_TYPE_ANALYTICS, ADAPTER_CODE, vastTrackerHandler); +} +medianetAnalytics.originEnableAnalytics = medianetAnalytics.enableAnalytics; medianetAnalytics.enableAnalytics = function (configuration) { if (!configuration || !configuration.options || !configuration.options.cid) { logError('Media.net Analytics adapter: cid is required.'); @@ -979,20 +879,17 @@ medianetAnalytics.enableAnalytics = function (configuration) { } getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; getGlobal().medianetGlobals.analyticsEnabled = true; - - pageDetails = new PageDetail(); - - config = new Configure(configuration.options.cid); - config.pubLper = configuration.options.sampling || ''; - config.init(); + getGlobal().medianetGlobals.analytics = mnetGlobals; + mnetGlobals.eventQueue = eventQueue(); + mnetGlobals.eventQueue.enqueueEvent(LoggingEvents.CONFIG_INIT, configuration); configuration.options.sampling = 1; medianetAnalytics.originEnableAnalytics(configuration); }; adapterManager.registerAnalyticsAdapter({ adapter: medianetAnalytics, - code: 'medianetAnalytics', - gvlid: 142, + code: ADAPTER_CODE, + gvlid: GLOBAL_VENDOR_ID, }); export default medianetAnalytics; diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index dc5ccab4bc2..38e8a17f442 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,7 +1,5 @@ import { deepAccess, - formatQS, - getWindowTop, isArray, isEmpty, isEmptyStr, @@ -10,19 +8,20 @@ import { logInfo, safeJSONEncode, deepClone, - deepSetValue + deepSetValue, getWindowTop } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; -import {ajax} from '../src/ajax.js'; import {getViewportCoordinates} from '../libraries/viewport/viewport.js'; -import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import {filterBidsListByFilters, getTopWindowReferrer} from '../libraries/medianetUtils/utils.js'; +import {errorLogger} from '../libraries/medianetUtils/logger.js'; +import {GLOBAL_VENDOR_ID, MEDIANET} from '../libraries/medianetUtils/constants.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getBoundingClientRect} from '../libraries/boundingClientRect/boundingClientRect.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -30,7 +29,7 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid */ -const BIDDER_CODE = 'medianet'; +const BIDDER_CODE = MEDIANET; const TRUSTEDSTACK_CODE = 'trustedstack'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; const TRUSTEDSTACK_URL = 'https://prebid.trustedstack.com/rtb/trustedstack'; @@ -46,10 +45,10 @@ export const EVENTS = { SET_TARGETING: 'client_set_targeting', BIDDER_ERROR: 'client_bidder_error' }; -export const EVENT_PIXEL_URL = 'https://navvy.media.net/log'; const OUTSTREAM = 'outstream'; let pageMeta; +let customerId; window.mnet = window.mnet || {}; window.mnet.queue = window.mnet.queue || []; @@ -60,26 +59,20 @@ const aliases = [ getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; - } -} - function siteDetails(site, bidderRequest) { const urlData = bidderRequest.refererInfo; site = site || {}; let siteData = { domain: site.domain || urlData.domain, page: site.page || urlData.page, - ref: site.ref || getTopWindowReferrer(), + ref: getTopWindowReferrer(site.ref), topMostLocation: urlData.topmostLocation, isTop: site.isTop || urlData.reachedTop }; - - return Object.assign(siteData, getPageMeta()); + if (!pageMeta) { + pageMeta = getPageMeta(); + } + return Object.assign(siteData, pageMeta); } function getPageMeta() { @@ -87,13 +80,9 @@ function getPageMeta() { return pageMeta; } let canonicalUrl = getUrlFromSelector('link[rel="canonical"]', 'href'); - let ogUrl = getUrlFromSelector('meta[property="og:url"]', 'content'); - let twitterUrl = getUrlFromSelector('meta[name="twitter:url"]', 'content'); pageMeta = Object.assign({}, canonicalUrl && { 'canonical_url': canonicalUrl }, - ogUrl && { 'og_url': ogUrl }, - twitterUrl && { 'twitter_url': twitterUrl } ); return pageMeta; @@ -121,10 +110,6 @@ function getAbsoluteUrl(url) { return aTag.href; } -function filterUrlsByType(urls, type) { - return urls.filter(url => url.type === type); -} - function transformSizes(sizes) { if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { return [getSize(sizes)]; @@ -277,7 +262,7 @@ function getBidFloorByType(bidRequest) { if (typeof bidRequest.getFloor === 'function') { [BANNER, VIDEO, NATIVE].forEach(mediaType => { if (bidRequest.mediaTypes.hasOwnProperty(mediaType)) { - if (mediaType == BANNER) { + if (mediaType === BANNER) { bidRequest.mediaTypes.banner.sizes.forEach( size => { setFloorInfo(bidRequest, mediaType, size, floorInfo) @@ -378,22 +363,6 @@ function fetchCookieSyncUrls(response) { return []; } -function getEventData(event) { - const params = {}; - const referrerInfo = getRefererInfo(); - params.logid = 'kfk'; - params.evtid = 'projectevents'; - params.project = 'prebid'; - params.pbver = '$prebid.version$'; - params.cid = getGlobal().medianetGlobals.cid || ''; - params.dn = encodeURIComponent(referrerInfo.domain || ''); - params.requrl = encodeURIComponent(referrerInfo.page || ''); - params.event = event.name || ''; - params.value = event.value || ''; - params.rd = event.related_data || ''; - return params; -} - function getBidData(bid) { const params = {}; params.acid = bid.auctionId || ''; @@ -407,7 +376,7 @@ function getBidData(bid) { return params; } -function getLoggingData(event, bids) { +function getLoggingData(bids) { const logData = {}; if (!isArray(bids)) { bids = []; @@ -419,26 +388,13 @@ function getLoggingData(event, bids) { logData[key].push(encodeURIComponent(bidData[key])); }); }); - return Object.assign({}, getEventData(event), logData) -} - -function fireAjaxLog(url, payload) { - ajax(url, - { - success: () => undefined, - error: () => undefined - }, - payload, - { - method: 'POST', - keepalive: true - } - ); + return logData; } function logEvent(event, data) { - const logData = getLoggingData(event, data); - fireAjaxLog(EVENT_PIXEL_URL, formatQS(logData)); + const logData = getLoggingData(data); + event.cid = customerId; + errorLogger(event, logData, false).send(); } function clearPageMeta() { @@ -451,7 +407,7 @@ function addRenderer(bid) { /* Adding renderer only when the context is Outstream and the provider has responded with a renderer. */ - if (videoContext == OUTSTREAM && vastTimeout) { + if (videoContext === OUTSTREAM && vastTimeout) { bid.renderer = newVideoRenderer(bid); } } @@ -482,7 +438,7 @@ function newVideoRenderer(bid) { export const spec = { code: BIDDER_CODE, - gvlid: 142, + gvlid: GLOBAL_VENDOR_ID, aliases, supportedMediaTypes: [BANNER, NATIVE, VIDEO], @@ -502,9 +458,7 @@ export const spec = { logError(`${BIDDER_CODE} : cid should be a string`); return false; } - - Object.assign(getGlobal().medianetGlobals, !getGlobal().medianetGlobals.cid && {cid: bid.params.cid}); - + customerId = bid.params.cid; return true; }, @@ -563,11 +517,11 @@ export const spec = { let cookieSyncUrls = fetchCookieSyncUrls(serverResponses); if (syncOptions.iframeEnabled) { - return filterUrlsByType(cookieSyncUrls, 'iframe'); + return filterBidsListByFilters(cookieSyncUrls, {type: 'iframe'}); } if (syncOptions.pixelEnabled) { - return filterUrlsByType(cookieSyncUrls, 'image'); + return filterBidsListByFilters(cookieSyncUrls, {type: 'image'}); } }, @@ -579,7 +533,7 @@ export const spec = { let eventData = { name: EVENTS.TIMEOUT_EVENT_NAME, value: timeoutData.length, - related_data: timeoutData[0].timeout || config.getConfig('bidderTimeout') + relatedData: timeoutData[0].timeout || config.getConfig('bidderTimeout') }; logEvent(eventData, timeoutData); } catch (e) {} @@ -615,7 +569,7 @@ export const spec = { try { let eventData = { name: EVENTS.BIDDER_ERROR, - related_data: `timedOut:${error.timedOut}|status:${error.status}|message:${error.reason.message}` + relatedData: `timedOut:${error.timedOut}|status:${error.status}|message:${error.reason.message}` }; logEvent(eventData, bidderRequest.bids); } catch (e) {} diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 85080f904e3..fa50252dadc 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -1,116 +1,300 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import medianetAnalytics from 'modules/medianetAnalyticsAdapter.js'; import * as utils from 'src/utils.js'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import * as events from 'src/events.js'; import {clearEvents} from 'src/events.js'; +import {deepAccess} from 'src/utils.js'; +import 'src/prebid.js'; +import {config} from 'src/config.js'; +import {REJECTION_REASON} from 'src/constants.js'; +import {getGlobal} from 'src/prebidGlobal.js'; +import sinon from "sinon"; +import * as mnUtils from '../../../libraries/medianetUtils/utils.js'; const { - AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, NO_BID, BID_TIMEOUT, AUCTION_END, SET_TARGETING, BID_WON + AUCTION_INIT, + BID_REQUESTED, + BID_RESPONSE, + NO_BID, + BID_TIMEOUT, + AUCTION_END, + SET_TARGETING, + BID_WON, + AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, + STALE_RENDER, + BID_REJECTED } = EVENTS; -const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; - -const MOCK = { - Ad_Units: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [], 'ext': {'prop1': 'value1'}}], - MULTI_FORMAT_TWIN_AD_UNITS: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'native': {'image': {'required': true, 'sizes': [150, 50]}}}, 'bids': [], 'ext': {'prop1': 'value1'}}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'video': {'playerSize': [640, 480], 'context': 'instream'}}, 'bids': [], 'ext': {'prop1': 'value1'}}], - TWIN_AD_UNITS: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 100]]}}, 'ask': '300x100', 'bids': [{'bidder': 'bidder1', 'params': {'siteId': '451465'}}]}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 100]]}}, 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}}]}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [{'bidder': 'bidder1', 'params': {'siteId': '451466'}}]}], - AUCTION_INIT: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'timeout': 6000}, - AUCTION_INIT_WITH_FLOOR: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'timestamp': 1584563605739, 'timeout': 6000, 'bidderRequests': [{'bids': [{ 'floorData': {'enforcements': {'enforceJS': true}} }]}]}, - BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, - MULTI_FORMAT_BID_REQUESTED: {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'video': {'playerSize': [640, 480], 'context': 'instream'}, 'native': {'image': {'required': true, 'sizes': [150, 50]}, 'title': {'required': true, 'len': 80}}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, - TWIN_AD_UNITS_BID_REQUESTED: [{'bidderCode': 'bidder1', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bidderRequestId': '16f0746ff657b5', 'bids': [{'bidder': 'bidder1', 'params': {'siteId': '451465'}, 'mediaTypes': {'banner': {'sizes': [[300, 100]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '9615b5d1-7a4f-4c65-9464-4178b91da9e3', 'sizes': [[300, 100]], 'bidId': '2984d18e18bdfe', 'bidderRequestId': '16f0746ff657b5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client', 'bidRequestsCount': 3, 'bidderRequestsCount': 2, 'bidderWinsCount': 0}, {'bidder': 'bidder1', 'params': {'siteId': '451466'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '8bd7c9f2-0fe6-4ac5-8f2a-7f4a88af1b71', 'sizes': [[300, 250]], 'bidId': '3dced609066035', 'bidderRequestId': '16f0746ff657b5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client', 'bidRequestsCount': 3, 'bidderRequestsCount': 2, 'bidderWinsCount': 0}], 'auctionStart': 1584563605739, 'timeout': 3000, 'start': 1584563605743}, {'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bidderRequestId': '4b45d1de1fa8fe', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 100]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '215c038e-3b6a-465b-8937-d32e2ad8de45', 'sizes': [[300, 250], [300, 100]], 'bidId': '58d34adcb09c99', 'bidderRequestId': '4b45d1de1fa8fe', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client', 'bidRequestsCount': 3, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1584563605739, 'timeout': 3000, 'start': 1584563605743}], - BID_RESPONSE: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, - AUCTION_END: {'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'auctionEnd': 1584563605739}, - SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, - NO_BID_SET_TARGETING: {'div-gpt-ad-1460505748561-0': {}}, - BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, - BID_WON_2: {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}, - BID_WON_UNKNOWN: {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fkk', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}, - NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, - BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}], - BIDS_SAME_REQ_DIFF_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], - BIDS_SAME_REQ_DIFF_CPM_SAME_TIME: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], - BIDS_SAME_REQ_EQUAL_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 286, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], - BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}], - BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}], - MULTI_BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'bidA2', 'originalBidder': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aebecc', 'originalRequestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 3.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 3.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '3.00', 'pbMg': '3.20', 'pbHg': '3.29', 'pbAg': '3.25', 'pbDg': '3.29', 'pbCg': '3.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '3.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}] -}; - -function performAuctionWithFloorConfig() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT_WITH_FLOOR, {adUnits: MOCK.Ad_Units})); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON); +function createBidResponse(bidderCode, requestId, cpm, adId = '3e6e4bce5c8fb3') { + return { + bidderCode, + width: 300, + height: 250, + adId, + requestId, + mediaType: 'banner', + source: 'client', + ext: {pvid: 123, crid: '321'}, + no_bid: false, + cpm, + ad: 'AD_CODE', + ttl: 180, + creativeId: 'Test1', + netRevenue: true, + currency: 'USD', + dfp_id: 'div-gpt-ad-1460505748561-0', + originalCpm: cpm, + originalCurrency: 'USD', + floorData: {floorValue: 1.10, floorRule: 'banner'}, + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + snm: 'SUCCESS', + responseTimestamp: 1584563606009, + requestTimestamp: 1584563605743, + bidder: 'medianet', + adUnitCode: 'div-gpt-ad-1460505748561-0', + timeToRespond: 266, + pbLg: '2.00', + pbMg: '2.20', + pbHg: '2.29', + pbAg: '2.25', + pbDg: '2.29', + pbCg: '2.00', + size: '300x250', + adserverTargeting: { + hb_bidder: 'medianet', + hb_adid: adId, + hb_pb: '1.8', + hb_size: '300x250', + hb_source: 'client', + hb_format: 'banner', + prebid_test: 1 + }, + params: [{cid: 'test123', crid: '451466393'}] + }; } -function performStandardAuctionWithWinner() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON); +function createBidRequest(bidderCode, auctionId, bidId, adUnits) { + return { + bidderCode, + auctionId, + bids: adUnits.map(adUnit => ({ + bidder: bidderCode, + params: {cid: 'TEST_CID', crid: '451466393'}, + mediaTypes: adUnit.mediaTypes, + adUnitCode: adUnit.code, + sizes: [(adUnit.mediaTypes.banner?.sizes || []), (adUnit.mediaTypes.native?.image?.sizes || []), (adUnit.mediaTypes.video?.playerSize || [])], + bidId, + auctionId, + src: 'client' + })), + auctionStart: Date.now(), + timeout: 6000, + uspConsent: '1YY', + start: Date.now() + }; } -function performMultiFormatAuctionWithNoBid() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.MULTI_FORMAT_TWIN_AD_UNITS})); - events.emit(BID_REQUESTED, MOCK.MULTI_FORMAT_BID_REQUESTED); - events.emit(NO_BID, MOCK.NO_BID); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); +function createNoBid(bidder, params) { + return { + bidder, + params, + mediaTypes: {banner: {sizes: [[300, 250]], ext: ['asdads']}}, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', + sizes: [[300, 250], [300, 600]], + bidId: '28248b0e6aece2', + bidderRequestId: '13fccf3809fe43', + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + src: 'client' + }; } -function performTwinAdUnitAuctionWithNoBid() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.TWIN_AD_UNITS})); - MOCK.TWIN_AD_UNITS_BID_REQUESTED.forEach(bidRequested => events.emit(BID_REQUESTED, bidRequested)); - MOCK.TWIN_AD_UNITS_BID_REQUESTED.forEach(bidRequested => bidRequested.bids.forEach(noBid => events.emit(NO_BID, noBid))); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); +function createBidTimeout(bidId, bidder, auctionId, params) { + return [{ + bidId: '28248b0e6aece2', + bidder: 'medianet', + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId, + params, + timeout: 6 + }]; } -function performStandardAuctionWithNoBid() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(NO_BID, MOCK.NO_BID); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); +function createBidWon(bidderCode, adId, requestId, cpm) { + return { + bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId, + requestId, + mediaType: 'banner', + source: 'client', + no_bid: false, + cpm, + ad: 'AD_CODE', + ttl: 180, + creativeId: 'Test1', + netRevenue: true, + currency: 'USD', + dfp_id: 'div-gpt-ad-1460505748561-0', + originalCpm: 1.1495, + originalCurrency: 'USD', + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + responseTimestamp: 1584563606009, + requestTimestamp: 1584563605743, + bidder: 'medianet', + adUnitCode: 'div-gpt-ad-1460505748561-0', + snm: 'SUCCESS', + timeToRespond: 266, + pbLg: '2.00', + pbMg: '2.20', + pbHg: '2.29', + pbAg: '2.25', + pbDg: '2.29', + pbCg: '2.00', + size: '300x250', + adserverTargeting: { + hb_bidder: 'medianet', + hb_adid: adId, + hb_pb: '2.00', + hb_size: '300x250', + hb_source: 'client', + hb_format: 'banner', + prebid_test: 1 + }, + status: 'rendered', + params: [{cid: 'test123', crid: '451466393'}] + }; } -function performStandardAuctionWithTimeout() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); -} +const BANNER_AD_UNIT = {code: 'div-gpt-ad-1460505748561-0', mediaTypes: {banner: {sizes: [[300, 250]]}}}; +const VIDEO_AD_UNIT = { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: {video: {playerSize: [640, 480], context: 'outstream'}} +}; +const INSTREAM_VIDEO_AD_UNIT = { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: {video: {playerSize: [640, 480], context: 'instream'}} +}; +const BANNER_NATIVE_AD_UNIT = { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: {banner: {sizes: [[300, 250]]}, native: {image: {required: true, sizes: [150, 50]}}} +}; +const BANNER_NATIVE_VIDEO_AD_UNIT = { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: {sizes: [[300, 250]]}, + video: {playerSize: [640, 480], context: 'outstream'}, + native: {image: {required: true, sizes: [150, 50]}, title: {required: true, len: 80}} + } +}; -function performStandardAuctionMultiBidWithSameRequestId(bidRespArray) { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - bidRespArray.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON); +function createS2SBidRequest(bidderCode, auctionId, bidId, adUnits) { + const bidRequest = createBidRequest(bidderCode, auctionId, bidId, adUnits); + // Update to mark as S2S + bidRequest.src = 's2s'; + bidRequest.bids.forEach(bid => { + bid.src = 's2s'; + }); + return bidRequest; } -function performStandardAuctionMultiBidResponseNoWin() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); - MOCK.BID_REQUESTS.forEach(bidReq => events.emit(BID_REQUESTED, bidReq)); - MOCK.BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); +function createS2SBidResponse(bidderCode, requestId, cpm, adId = '3e6e4bce5c8fb3') { + const bidResponse = createBidResponse(bidderCode, requestId, cpm, adId); + // Update to mark as S2S + bidResponse.source = 's2s'; + return bidResponse; } -function performMultiBidAuction() { - events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - MOCK.MULTI_BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); -} +const MOCK = { + AD_UNITS: [BANNER_NATIVE_AD_UNIT, VIDEO_AD_UNIT], + AUCTION_INIT: { + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + timestamp: 1584563605739, + timeout: 6000, + bidderRequests: [{bids: [{floorData: {enforcements: {enforceJS: true}}}]}] + }, + MNET_BID_REQUESTED: createBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [BANNER_NATIVE_VIDEO_AD_UNIT]), + MNET_BID_RESPONSE: createBidResponse('medianet', '28248b0e6aece2', 2.299), + COMMON_REQ_ID_BID_REQUESTS: [ + createBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [BANNER_AD_UNIT]), + createBidRequest('appnexus', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aecd5', [BANNER_AD_UNIT]) + ], + COMMON_REQ_ID_BID_RESPONSES: [ + createBidResponse('medianet', '28248b0e6aece2', 2.299, '3e6e4bce5c8fb3'), + createBidResponse('appnexus', '28248b0e6aecd5', 1.299, '3e6e4bce5c8fb4') + ], + COMMON_REQ_ID_BID_RESPONSES_EQUAL_CPM: [ + createBidResponse('medianet', '28248b0e6aece2', 2.299, '3e6e4bce5c8fb3'), + createBidResponse('medianet', '28248b0e6aece2', 2.299, '3e6e4bce5c8fb4') + ], + MULTI_BID_RESPONSES: [ + createBidResponse('medianet', '28248b0e6aece2', 2.299, '3e6e4bce5c8fb3'), + Object.assign(createBidResponse('medianet', '28248b0e6aebecc', 3.299, '3e6e4bce5c8fb4'), + {originalBidder: 'bidA2', originalRequestId: '28248b0e6aece2'}), + ], + MNET_NO_BID: createNoBid('medianet', {cid: 'test123', crid: '451466393', site: {}}), + MNET_BID_TIMEOUT: createBidTimeout('28248b0e6aece2', 'medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', [{ + cid: 'test123', + crid: '451466393', + site: {} + }, {cid: '8CUX0H51P', crid: '451466393', site: {}}]), + AUCTION_END: { + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + auctionEnd: 1584563605739, + bidderRequests: [createBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [BANNER_NATIVE_VIDEO_AD_UNIT])] + }, + MNET_BID_WON: createBidWon('medianet', '3e6e4bce5c8fb3', '28248b0e6aece2', 2.299), + BID_WON_UNKNOWN: createBidWon('appnexus', '3e6e4bce5c8fkk', '28248b0e6aecd5', 2.299), + MNET_SET_TARGETING: { + 'div-gpt-ad-1460505748561-0': { + prebid_test: '1', + hb_format: 'banner', + hb_source: 'client', + hb_size: '300x250', + hb_pb: '1.8', + hb_adid: '3e6e4bce5c8fb3', + hb_bidder: 'medianet', + hb_format_medianet: 'banner', + hb_source_medianet: 'client', + hb_size_medianet: '300x250', + hb_pb_medianet: '2.00', + hb_adid_medianet: '3e6e4bce5c8fb3', + hb_bidder_medianet: 'medianet' + } + }, + NO_BID_SET_TARGETING: {'div-gpt-ad-1460505748561-0': {}}, + // S2S mocks + MNET_S2S_BID_REQUESTED: createS2SBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [BANNER_AD_UNIT]), + MNET_S2S_BID_RESPONSE: createS2SBidResponse('medianet', '28248b0e6aece2', 2.299), + MNET_S2S_BID_WON: Object.assign({}, createBidWon('medianet', '3e6e4bce5c8fb3', '28248b0e6aece2', 2.299), {source: 's2s'}), + MNET_S2S_SET_TARGETING: { + 'div-gpt-ad-1460505748561-0': { + prebid_test: '1', + hb_format: 'banner', + hb_source: 's2s', + hb_size: '300x250', + hb_pb: '1.8', + hb_adid: '3e6e4bce5c8fb3', + hb_bidder: 'medianet', + hb_format_medianet: 'banner', + hb_source_medianet: 's2s', + hb_size_medianet: '300x250', + hb_pb_medianet: '2.00', + hb_adid_medianet: '3e6e4bce5c8fb3', + hb_bidder_medianet: 'medianet' + } + }, + // Currency conversion mocks + MNET_JPY_BID_RESPONSE: Object.assign({}, createBidResponse('medianet', '28248b0e6aece2', 250, '3e6e4bce5c8fb5'), { + currency: 'JPY', + originalCurrency: 'JPY', + originalCpm: 250 + }) +}; function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); @@ -131,8 +315,91 @@ function getQueryData(url, decode = false) { }, {}); } -describe('Media.net Analytics Adapter', function() { +function waitForPromiseResolve(promise) { + return new Promise((resolve, reject) => { + promise.then(resolve).catch(reject); + }); +} + +function performNoBidAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + events.emit(NO_BID, MOCK.MNET_NO_BID); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [MOCK.MNET_BID_REQUESTED]})); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); +} + +function performBidWonAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.MNET_BID_RESPONSE); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); + events.emit(BID_WON, MOCK.MNET_BID_WON); +} + +function performBidTimeoutAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + events.emit(BID_TIMEOUT, MOCK.MNET_BID_TIMEOUT); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [MOCK.MNET_BID_REQUESTED]})); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); +} + +function performAuctionWithSameRequestIdBids(bidRespArray) { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + bidRespArray.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [MOCK.MNET_BID_REQUESTED]})); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); + events.emit(BID_WON, MOCK.MNET_BID_WON); +} + +function performAuctionNoWin() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + MOCK.COMMON_REQ_ID_BID_REQUESTS.forEach(bidReq => events.emit(BID_REQUESTED, bidReq)); + MOCK.COMMON_REQ_ID_BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: MOCK.COMMON_REQ_ID_BID_REQUESTS})); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); +} + +function performMultiBidAuction() { + let bidRequest = createBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [BANNER_AD_UNIT]); + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, bidRequest); + MOCK.MULTI_BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [bidRequest]})); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); +} + +function performBidRejectedAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.MNET_BID_RESPONSE); + events.emit(BID_REJECTED, Object.assign({}, MOCK.MNET_BID_RESPONSE, { + rejectionReason: REJECTION_REASON.FLOOR_NOT_MET, + })); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [MOCK.MNET_BID_REQUESTED]})); +} + +function performS2SAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_S2S_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.MNET_S2S_BID_RESPONSE); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [MOCK.MNET_S2S_BID_REQUESTED]})); + events.emit(SET_TARGETING, MOCK.MNET_S2S_SET_TARGETING); + events.emit(BID_WON, MOCK.MNET_S2S_BID_WON); +} + +function performCurrencyConversionAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.MNET_JPY_BID_RESPONSE); + events.emit(AUCTION_END, Object.assign({}, MOCK.AUCTION_END, {bidderRequests: [MOCK.MNET_BID_REQUESTED]})); +} + +describe('Media.net Analytics Adapter', function () { let sandbox; + let clock; let CUSTOMER_ID = 'test123'; let VALID_CONFIGURATION = { options: { @@ -146,14 +413,16 @@ describe('Media.net Analytics Adapter', function() { beforeEach(function () { sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(); }); afterEach(function () { sandbox.restore(); + clock.restore(); }); - describe('Configuration', function() { - it('should log error if publisher id is not passed', function() { + describe('Configuration', function () { + it('should log error if publisher id is not passed', function () { sandbox.stub(utils, 'logError'); medianetAnalytics.enableAnalytics(); @@ -164,7 +433,7 @@ describe('Media.net Analytics Adapter', function() { ).to.be.true; }); - it('should not log error if valid config is passed', function() { + it('should not log error if valid config is passed', function () { sandbox.stub(utils, 'logError'); medianetAnalytics.enableAnalytics(VALID_CONFIGURATION); @@ -173,7 +442,7 @@ describe('Media.net Analytics Adapter', function() { }); }); - describe('Events', function() { + describe('VAST Tracking', function () { beforeEach(function () { medianetAnalytics.enableAnalytics({ options: { @@ -181,62 +450,329 @@ describe('Media.net Analytics Adapter', function() { } }); medianetAnalytics.clearlogsQueue(); + // Set config required for vastTrackerHandler + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); }); + afterEach(function () { - medianetAnalytics.clearlogsQueue(); medianetAnalytics.disableAnalytics(); + config.resetConfig(); }); - it('should not log if only Auction Init', function() { - medianetAnalytics.clearlogsQueue(); - medianetAnalytics.track({ AUCTION_INIT }) - expect(medianetAnalytics.getlogsQueue().length).to.equal(0); + it('should generate valid tracking URL for video bids', function () { + const VIDEO_AUCTION = Object.assign({}, MOCK.AUCTION_INIT, {adUnits: [INSTREAM_VIDEO_AD_UNIT]}); + const VIDEO_BID_REQUESTED = createBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [INSTREAM_VIDEO_AD_UNIT]); + const videoBidResponse = Object.assign({}, MOCK.MNET_BID_RESPONSE, { + mediaType: 'video', + playerSize: [640, 480] + }); + + events.emit(AUCTION_INIT, VIDEO_AUCTION); + events.emit(BID_REQUESTED, VIDEO_BID_REQUESTED); + events.emit(BID_RESPONSE, videoBidResponse); + + const trackers = medianetAnalytics.getVastTrackerHandler()(videoBidResponse, { + auction: VIDEO_AUCTION, + bidRequest: VIDEO_BID_REQUESTED.bids[0] + }); + + expect(trackers).to.be.an('array'); + expect(trackers.length).to.equal(1); + expect(trackers[0].event).to.equal('impressions'); + expect(trackers[0].url).to.include('https://'); + + const urlData = getQueryData(trackers[0].url); + expect(urlData.lgtp).to.equal('RA'); + expect(urlData.pvnm).to.include('medianet'); + expect(urlData.bdp).to.equal('2.299'); + expect(urlData.vplcmtt).to.equal('1'); }); - it('should have all applicable sizes in request', function() { - medianetAnalytics.clearlogsQueue(); - performMultiFormatAuctionWithNoBid(); - const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + it('should return error tracker when auction is missing', function () { + const videoBidResponse = Object.assign({}, MOCK.MNET_BID_RESPONSE, { + mediaType: 'video', + vastUrl: 'https://vast.example.com/vast.xml', + videoCacheKey: 'video_cache_123', + auctionId: 'missing_auction_id' // This auction ID doesn't exist + }); + + const trackers = medianetAnalytics.getVastTrackerHandler()(videoBidResponse, { + bidRequest: MOCK.MNET_BID_REQUESTED.bids[0], + auction: MOCK.AUCTION_INIT + }); + + expect(trackers).to.be.an('array'); + expect(trackers.length).to.equal(1); + expect(trackers[0].url).to.include('vast_tracker_handler_missing_auction'); + }); + + it('should return error tracker when bidrequest is missing', function () { + const VIDEO_AUCTION = Object.assign({}, MOCK.AUCTION_INIT, {adUnits: [INSTREAM_VIDEO_AD_UNIT]}); + const VIDEO_BID_REQUESTED = createBidRequest('medianet', '8e0d5245-deb3-406c-96ca-9b609e077ff7', '28248b0e6aece2', [INSTREAM_VIDEO_AD_UNIT]); + const videoBidResponse = Object.assign({}, MOCK.MNET_BID_RESPONSE, { + mediaType: 'video', + playerSize: [640, 480], + }); + + events.emit(AUCTION_INIT, VIDEO_AUCTION); + + const trackers = medianetAnalytics.getVastTrackerHandler()(videoBidResponse, { + auction: VIDEO_AUCTION, + bidRequest: VIDEO_BID_REQUESTED.bids[0] + }); + expect(trackers).to.be.an('array'); + expect(trackers.length).to.equal(1); + expect(trackers[0].url).to.include('missing_bidrequest'); + }); + }); + + describe('Events', function () { + beforeEach(function () { + sandbox.stub(mnUtils, 'onHidden').callsFake(() => { + }) + medianetAnalytics.enableAnalytics({ + options: { + cid: 'test123' + } + }); medianetAnalytics.clearlogsQueue(); - expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); - expect(noBidLog.szs).to.have.ordered.members([encodeURIComponent('300x250|1x1|640x480'), encodeURIComponent('300x250|1x1|640x480')]); - expect(noBidLog.vplcmtt).to.equal('1'); + }); + afterEach(function () { + sandbox.restore(); + medianetAnalytics.disableAnalytics(); + }); + + it('can handle multi bid module', function (done) { + performMultiBidAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + const queue = medianetAnalytics.getlogsQueue(); + expect(queue.length).equals(1); + const multiBidLog = queue.map((log) => getQueryData(log, true))[0]; + expect(multiBidLog.pvnm).to.have.ordered.members(['-2', 'medianet', 'medianet']); + expect(multiBidLog.status).to.have.ordered.members(['1', '1', '1']); + done(); + }).catch(done); }); - it('twin ad units should have correct sizes', function() { - performTwinAdUnitAuctionWithNoBid(); - const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log, true))[0]; - const banner = 'banner'; - expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'bidder1', 'bidder1', 'medianet']); - expect(noBidLog.mtype).to.have.ordered.members([banner, banner, banner, banner]); - expect(noBidLog.status).to.have.ordered.members(['1', '2', '2', '2']); - expect(noBidLog.size).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']); - expect(noBidLog.szs).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']); + it('should have all applicable sizes in request', function (done) { + performNoBidAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + const noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); + expect(noBidLog.szs).to.have.ordered.members([encodeURIComponent('300x250|1x1|640x480'), encodeURIComponent('300x250|1x1|640x480')]); + expect(noBidLog.vplcmtt).to.equal('6'); + done(); + }).catch(done); }); - it('AP log should fire only once', function() { - performStandardAuctionWithNoBid(); - events.emit(SET_TARGETING, MOCK.NO_BID_SET_TARGETING); - const logs = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log, true)); - expect(logs.length).to.equal(1); - expect(logs[0].lgtp).to.equal('APPR'); + it('AP log should fire only once', function (done) { + performNoBidAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + const logs = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log, true)); + expect(logs[0]).to.exist; + expect(logs[0].lgtp).to.equal('APPR'); + done(); + }).catch(done); }); - it('should have winner log in standard auction', function() { - medianetAnalytics.clearlogsQueue(); - performStandardAuctionWithWinner(); - let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1'); - medianetAnalytics.clearlogsQueue(); + it('should have no bid status', function (done) { + performNoBidAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + let noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + noBidLog = noBidLog[0]; + + expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'medianet']); + expect(noBidLog.iwb).to.have.ordered.members(['0', '0']); + expect(noBidLog.status).to.have.ordered.members(['1', '2']); + expect(noBidLog.src).to.have.ordered.members(['client', 'client']); + expect(noBidLog.curr).to.have.ordered.members(['', '']); + expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); + expect(noBidLog.ogbdp).to.have.ordered.members(['', '']); + expect(noBidLog.mpvid).to.have.ordered.members(['', '']); + expect(noBidLog.crid).to.have.ordered.members(['', '451466393']); + done(); + }).catch(done); + }); + + it('should have timeout status', function (done) { + performBidTimeoutAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + let timeoutLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + timeoutLog = timeoutLog[0]; + + expect(timeoutLog.pvnm).to.have.ordered.members(['-2', 'medianet']); + expect(timeoutLog.iwb).to.have.ordered.members(['0', '0']); + expect(timeoutLog.status).to.have.ordered.members(['3', '3']); + expect(timeoutLog.src).to.have.ordered.members(['client', 'client']); + expect(timeoutLog.curr).to.have.ordered.members(['', '']); + expect(timeoutLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); + expect(timeoutLog.ogbdp).to.have.ordered.members(['', '']); + expect(timeoutLog.mpvid).to.have.ordered.members(['', '']); + expect(timeoutLog.crid).to.have.ordered.members(['', '451466393']); + done(); + }).catch(done); + }); + it('should have correct bid values after and before bidCmpAdjustment', function (done) { + const bidCopy = utils.deepClone(MOCK.MNET_BID_RESPONSE); + bidCopy.cpm = bidCopy.originalCpm * 0.8; // Simulate bidCpmAdjustment + + // Emit events to simulate an auction + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.AD_UNITS})); + events.emit(BID_REQUESTED, MOCK.MNET_BID_REQUESTED); + events.emit(BID_RESPONSE, bidCopy); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.MNET_SET_TARGETING); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + const adjustedLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + + expect(adjustedLog.bdp[1]).to.equal(String(bidCopy.cpm)); + expect(adjustedLog.ogbdp[1]).to.equal(String(bidCopy.originalCpm)); + expect(adjustedLog.cbdp[1]).to.equal(String(bidCopy.cpm)); + expect(adjustedLog.dfpbd[1]).to.equal(String(deepAccess(bidCopy, 'adserverTargeting.hb_pb'))); + done(); + }).catch(done); + }); + + it('should pick winning bid if multibids with same request id', function (done) { + sandbox.stub(getGlobal(), 'getAdserverTargetingForAdUnitCode').returns({ + hb_pb: '2.299', + hb_adid: '3e6e4bce5c8fb3', + hb_pb_medianet: '2.299', + hb_adid_medianet: '3e6e4bce5c8fb3', + hb_pb_appnexus: '1.299', + hb_adid_appnexus: '3e6e4bce5c8fb4', + }); + performAuctionWithSameRequestIdBids(MOCK.COMMON_REQ_ID_BID_RESPONSES); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + medianetAnalytics.clearlogsQueue(); + + performAuctionWithSameRequestIdBids([...MOCK.COMMON_REQ_ID_BID_RESPONSES].reverse()); + clock.tick(2000); + + return waitForPromiseResolve(Promise.resolve()); + }).then(() => { + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + done(); + }).catch(done); + }); + + it('should pick winning bid if multibids with same request id and same time to respond', function (done) { + performAuctionWithSameRequestIdBids(MOCK.COMMON_REQ_ID_BID_RESPONSES_EQUAL_CPM); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + medianetAnalytics.clearlogsQueue(); + done(); + }).catch(done); + }); + + it('should ignore unknown winning bid and log error', function (done) { + performAuctionNoWin(); + events.emit(BID_WON, MOCK.BID_WON_UNKNOWN); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + let winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner); + let errors = medianetAnalytics.getErrorQueue().map((log) => getQueryData(log)); + expect(winningBids.length).equals(0); + expect(errors.length).equals(1); + expect(errors[0].event).equals('winning_bid_absent'); + done(); + }).catch(done); + }); + + it('should have correct bid status for bid rejected', function (done) { + performBidRejectedAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + let bidRejectedLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log))[0]; + expect(bidRejectedLog.pvnm).to.have.ordered.members(['-2', 'medianet', 'medianet', 'medianet']); + expect(bidRejectedLog.status).to.have.ordered.members(['1', '1', '12', '12']); + done(); + }).catch(done); + }); + + it('should handle S2S auction correctly', function (done) { + performS2SAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + const queue = medianetAnalytics.getlogsQueue(); + const s2sLog = queue.map((log) => getQueryData(log, true))[0]; + + // Verify S2S source is recorded correctly + expect(s2sLog.src).to.equal('s2s'); + expect(s2sLog.pvnm).to.include('medianet'); + expect(s2sLog.status).to.include('1'); + expect(s2sLog.winner).to.equal('1'); + + done(); + }).catch(done); + }); + + it('should handle currency conversion from JPY to USD', function (done) { + const prebidGlobal = getGlobal(); + prebidGlobal.convertCurrency = prebidGlobal.convertCurrency || function () { + }; + const convertStub = sandbox.stub(prebidGlobal, 'convertCurrency'); + convertStub.withArgs(250, 'JPY', 'USD').returns(2.25); + + performCurrencyConversionAuction(); + clock.tick(2000); + + waitForPromiseResolve(Promise.resolve()).then(() => { + const queue = medianetAnalytics.getlogsQueue(); + expect(queue.length).equals(1); + const currencyLog = queue.map((log) => getQueryData(log, true))[0]; + + expect(currencyLog.curr).to.have.ordered.members(['', 'JPY', '']); + expect(currencyLog.ogbdp).to.have.ordered.members(['', '2.25', '']); + expect(currencyLog.bdp).to.have.ordered.members(['', '2.25', '']); + expect(currencyLog.bdp).to.have.ordered.members(['', '2.25', '']); + expect(convertStub.calledWith(250, 'JPY', 'USD')).to.be.true; + done(); + }).catch(done).finally(() => { + convertStub.restore(); + }); + }); + + it('should have winner log in standard auction', function () { + performBidWonAuction(); + + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); expect(winnerLog.length).to.equal(1); + expect(winnerLog[0].lgtp).to.equal('RA'); }); - it('should have correct values in winner log', function() { - medianetAnalytics.clearlogsQueue(); - performStandardAuctionWithWinner(); - let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1'); - medianetAnalytics.clearlogsQueue(); + it('should have correct values in winner log', function () { + performBidWonAuction(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); expect(winnerLog[0]).to.include({ winner: '1', pvnm: 'medianet', @@ -247,7 +783,7 @@ describe('Media.net Analytics Adapter', function() { gdpr: '0', cid: 'test123', lper: '1', - ogbdp: '1.1495', + ogbdp: '2.299', flt: '1', supcrid: 'div-gpt-ad-1460505748561-0', mpvid: '123', @@ -255,124 +791,73 @@ describe('Media.net Analytics Adapter', function() { }); }); - it('should have correct bid floor data in winner log', function() { - medianetAnalytics.clearlogsQueue(); - performAuctionWithFloorConfig(); - let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1'); - medianetAnalytics.clearlogsQueue(); - + it('should have correct bid floor data in winner log', function (done) { + performBidWonAuction(); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); expect(winnerLog[0]).to.include({ winner: '1', curr: 'USD', - ogbdp: '1.1495', + ogbdp: '2.299', bidflr: '1.1', flrrule: 'banner', - flrdata: encodeURIComponent('ln=||skp=||enfj=true||enfd=||sr=||fs=') + flrdata: encodeURIComponent('ln=||skp=||sr=||fs=||enfj=true||enfd=') }); + done(); }); - it('should have no bid status', function() { - medianetAnalytics.clearlogsQueue(); - performStandardAuctionWithNoBid(); - let noBidLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); - noBidLog = noBidLog[0]; - - medianetAnalytics.clearlogsQueue(); - expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'medianet']); - expect(noBidLog.iwb).to.have.ordered.members(['0', '0']); - expect(noBidLog.status).to.have.ordered.members(['1', '2']); - expect(noBidLog.src).to.have.ordered.members(['client', 'client']); - expect(noBidLog.curr).to.have.ordered.members(['', '']); - expect(noBidLog.mtype).to.have.ordered.members(['banner', 'banner']); - expect(noBidLog.ogbdp).to.have.ordered.members(['', '']); - expect(noBidLog.mpvid).to.have.ordered.members(['', '']); - expect(noBidLog.crid).to.have.ordered.members(['', '451466393']); - }); - - it('should have timeout status', function() { - medianetAnalytics.clearlogsQueue(); - performStandardAuctionWithTimeout(); - let timeoutLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); - timeoutLog = timeoutLog[0]; - - medianetAnalytics.clearlogsQueue(); - expect(timeoutLog.pvnm).to.have.ordered.members(['-2', 'medianet']); - expect(timeoutLog.iwb).to.have.ordered.members(['0', '0']); - expect(timeoutLog.status).to.have.ordered.members(['1', '3']); - expect(timeoutLog.src).to.have.ordered.members(['client', 'client']); - expect(timeoutLog.curr).to.have.ordered.members(['', '']); - expect(timeoutLog.mtype).to.have.ordered.members(['banner', 'banner']); - expect(timeoutLog.ogbdp).to.have.ordered.members(['', '']); - expect(timeoutLog.mpvid).to.have.ordered.members(['', '']); - expect(timeoutLog.crid).to.have.ordered.members(['', '451466393']); - }); - - it('should pick winning bid if multibids with same request id', function() { - performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM); - let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fb3'); - medianetAnalytics.clearlogsQueue(); - - const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_DIFF_CPM).reverse(); - performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray); - winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fb3'); - }); - - it('should pick winning bid if multibids with same request id and same time to respond', function() { - performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM_SAME_TIME); - let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fb3'); - medianetAnalytics.clearlogsQueue(); + it('should log error on AD_RENDER_FAILED event', function () { + const errorData = { + reason: 'timeout', + message: 'Ad failed to render', + bid: { + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: 'medianet', + creativeId: 'Test1' + } + }; + events.emit(AD_RENDER_FAILED, errorData); + + const errors = medianetAnalytics.getErrorQueue().map((log) => getQueryData(log)); + expect(errors.length).to.equal(1); + const relatedData = JSON.parse(decodeURIComponent(errors[0].rd)); + expect(errors[0].event).to.equal(AD_RENDER_FAILED); + expect(relatedData.reason).to.equal('timeout'); + expect(relatedData.message).to.equal('Ad failed to render'); }); - it('should pick winning bid if multibids with same request id and equal cpm', function() { - performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM); - let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fb3'); - medianetAnalytics.clearlogsQueue(); - - const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_EQUAL_CPM).reverse(); - performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray); - winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fb3'); - }); + it('should log success on AD_RENDER_SUCCEEDED event', function () { + const successData = { + bid: { + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: 'medianet', + creativeId: 'Test1' + } + }; + events.emit(AD_RENDER_SUCCEEDED, successData); - it('should pick single winning bid per bid won', function() { - performStandardAuctionMultiBidResponseNoWin(); - const queue = medianetAnalytics.getlogsQueue(); - queue.length = 0; - - events.emit(BID_WON, MOCK.BID_WON); - let winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); - expect(winningBids[0].adid).equals(MOCK.BID_WON.adId); - expect(winningBids.length).equals(1); - events.emit(BID_WON, MOCK.BID_WON_2); - winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); - expect(winningBids[1].adid).equals(MOCK.BID_WON_2.adId); - expect(winningBids.length).equals(2); + const logs = medianetAnalytics.getErrorQueue().map((log) => getQueryData(log)); + expect(logs.length).to.equal(1); + expect(logs[0].event).to.equal(AD_RENDER_SUCCEEDED); }); - it('should ignore unknown winning bid and log error', function() { - performStandardAuctionMultiBidResponseNoWin(); - const queue = medianetAnalytics.getlogsQueue(); - queue.length = 0; - - events.emit(BID_WON, MOCK.BID_WON_UNKNOWN); - let winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); - let errors = medianetAnalytics.getErrorQueue().map((log) => getQueryData(log)); - expect(winningBids.length).equals(0); - expect(errors.length).equals(1); - expect(errors[0].event).equals(ERROR_WINNING_BID_ABSENT); + it('should log error on STALE_RENDER event', function () { + const staleData = { + auctionId: '8e0d5245-deb3-406c-96ca-9b609e077ff7', + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: 'medianet', + creativeId: 'Test1', + adId: '3e6e4bce5c8fb3', + cpm: 2.299 + }; + events.emit(STALE_RENDER, staleData); + + const errors = medianetAnalytics.getErrorQueue().map((log) => getQueryData(log)); + expect(errors.length).to.equal(1); + const relatedData = JSON.parse(decodeURIComponent(errors[0].rd)); + expect(errors[0].event).to.equal(STALE_RENDER); + expect(relatedData.adId).to.equal('3e6e4bce5c8fb3'); }); - - it('can handle multi bid module', function () { - performMultiBidAuction(); - const queue = medianetAnalytics.getlogsQueue(); - expect(queue.length).equals(1); - const multiBidLog = queue.map((log) => getQueryData(log, true))[0]; - expect(multiBidLog.pvnm).to.have.ordered.members(['-2', 'medianet', 'medianet']); - expect(multiBidLog.status).to.have.ordered.members(['1', '1', '1']); - }) }); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index b899ca2027c..b5b81f713e6 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect, assert} from 'chai'; -import {spec, EVENT_PIXEL_URL, EVENTS} from 'modules/medianetBidAdapter.js'; +import {spec, EVENTS} from '../../../modules/medianetBidAdapter.js'; +import {POST_ENDPOINT} from '../../../libraries/medianetUtils/constants.js'; import { makeSlot } from '../integration/faker/googletag.js'; -import { config } from 'src/config.js'; +import { config } from '../../../src/config.js'; import {server} from '../../mocks/xhr.js'; import {resetWinDimensions} from '../../../src/utils.js'; @@ -1255,8 +1256,6 @@ let VALID_BID_REQUEST = [{ } catch (e) {} PAGE_META.site = Object.assign(PAGE_META.site, { 'canonical_url': 'http://localhost:9999/canonical-test', - 'twitter_url': 'http://localhost:9999/twitter-test', - 'og_url': 'http://localhost:9999/fb-test' }); return PAGE_META; })(), @@ -2282,7 +2281,7 @@ describe('Media.net bid adapter', function () { const reqBody = new URLSearchParams(server.requests[0].requestBody); assert.equal(server.requests[0].method, 'POST'); - assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(server.requests[0].url, POST_ENDPOINT); assert.equal(reqBody.get('event'), EVENTS.TIMEOUT_EVENT_NAME); assert.equal(reqBody.get('rd'), timeoutData[0].timeout.toString()); assert.equal(reqBody.get('acid[]'), timeoutData[0].auctionId); @@ -2308,7 +2307,7 @@ describe('Media.net bid adapter', function () { const reqBody = new URLSearchParams(server.requests[0].requestBody); assert.equal(server.requests[0].method, 'POST'); - assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(server.requests[0].url, POST_ENDPOINT); assert.equal(reqBody.get('event'), EVENTS.BID_WON_EVENT_NAME); assert.equal(reqBody.get('value'), bid.cpm.toString()); assert.equal(reqBody.get('acid[]'), bid.auctionId); @@ -2335,7 +2334,7 @@ describe('Media.net bid adapter', function () { const reqBody = new URLSearchParams(server.requests[0].requestBody); assert.equal(server.requests[0].method, 'POST'); - assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(server.requests[0].url, POST_ENDPOINT); assert.equal(reqBody.get('event'), EVENTS.SET_TARGETING); assert.equal(reqBody.get('value'), bid.cpm.toString()); assert.equal(reqBody.get('acid[]'), bid.auctionId); @@ -2366,7 +2365,7 @@ describe('Media.net bid adapter', function () { const reqBody = new URLSearchParams(server.requests[0].requestBody); assert.equal(server.requests[0].method, 'POST'); - assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(server.requests[0].url, POST_ENDPOINT); assert.equal(reqBody.get('event'), EVENTS.BIDDER_ERROR); assert.equal(reqBody.get('rd'), `timedOut:${error.timedOut}|status:${error.status}|message:${error.reason.message}`); assert.equal(reqBody.get('acid[]'), bids[0].auctionId); From 767345f1f2e96bdc8e91b0d809c49d1407b0facc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 10 Apr 2025 07:34:17 -0700 Subject: [PATCH 1076/1097] Core: use 'async' hooks for asynchronous hooks (#12933) --- modules/prebidServerBidAdapter/index.js | 2 +- src/adapters/bidderFactory.js | 2 +- src/auction.js | 6 +++--- src/hook.js | 19 +++++++++++++++++++ test/spec/hook_spec.js | 15 +++++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 test/spec/hook_spec.js diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 5a9c5594ab1..be9158fe047 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -492,7 +492,7 @@ export function PrebidServer() { * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse */ -export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid, onFledge}) { +export const processPBSRequest = hook('async', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid, onFledge}) { let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index e4f6b046265..ae188ea08e5 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -382,7 +382,7 @@ const RESPONSE_PROPS = ['bids', 'paapi'] * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse * @param onCompletion {function()} invoked once when all bid requests have been processed */ -export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { +export const processBidderRequests = hook('async', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { const metrics = adapterMetrics(bidderRequest); onCompletion = metrics.startTiming('total').stopBefore(onCompletion); const tidGuard = guardTids(bidderRequest); diff --git a/src/auction.js b/src/auction.js index c6c47769d54..52847206500 100644 --- a/src/auction.js +++ b/src/auction.js @@ -83,7 +83,7 @@ import {batchAndStore, storeLocally} from './videoCache.js'; import {Renderer} from './Renderer.js'; import {config} from './config.js'; import {userSync} from './userSync.js'; -import {hook} from './hook.js'; +import {hook, ignoreCallbackArg} from './hook.js'; import {find, includes} from './polyfill.js'; import {OUTSTREAM} from './video.js'; import {VIDEO} from './mediaTypes.js'; @@ -428,13 +428,13 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a * @param bid * @param {function(String): void} reject a function that, when called, rejects `bid` with the given reason. */ -export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { +export const addBidResponse = ignoreCallbackArg(hook('async', function(adUnitCode, bid, reject) { if (!isValidPrice(bid)) { reject(REJECTION_REASON.PRICE_TOO_HIGH) } else { this.dispatch.call(null, adUnitCode, bid); } -}, 'addBidResponse'); +}, 'addBidResponse')); /** * Delay hook for adapter responses. diff --git a/src/hook.js b/src/hook.js index 3f01114935d..9a6604d656f 100644 --- a/src/hook.js +++ b/src/hook.js @@ -1,6 +1,10 @@ import funHooks from 'fun-hooks/no-eval/index.js'; import {defer} from './utils/promise.js'; +/** + * NOTE: you must not call `next` asynchronously from 'sync' hooks + * see https://github.com/snapwich/fun-hooks/issues/42 + */ export let hook = funHooks({ ready: funHooks.SYNC | funHooks.ASYNC | funHooks.QUEUE }); @@ -59,3 +63,18 @@ export function wrapHook(hook, wrapper) { ); return wrapper; } + +/** + * 'async' hooks expect the last argument to be a callback, and have special treatment for it if it's a function; + * which prevents it from being used as a normal argument in 'before' hooks - and presents a modified version of it + * to the hooked function. + * + * This returns a wrapper around a given 'async' hook that works around this, for when the last argument + * should be treated as a normal argument. + */ +export function ignoreCallbackArg(hook) { + return wrapHook(hook, function (...args) { + args.push(function () {}) + return hook.apply(this, args); + }) +} diff --git a/test/spec/hook_spec.js b/test/spec/hook_spec.js new file mode 100644 index 00000000000..8fa095ca3d4 --- /dev/null +++ b/test/spec/hook_spec.js @@ -0,0 +1,15 @@ +import {hook as makeHook, ignoreCallbackArg} from '../../src/hook.js'; + +describe('hooks', () => { + describe('ignoreCallbackArg', () => { + it('allows async hooks to treat last argument as a normal argument', () => { + let hk = ignoreCallbackArg(makeHook('async', () => null)); + hk.before((next, arg, fn) => { + fn(arg); + }) + hk('arg', (arg) => { + expect(arg).to.eql('arg'); + }) + }) + }) +}) From 4f64834d02cf7bb064c26ffc8b8088ea41e599e1 Mon Sep 17 00:00:00 2001 From: priyankadeshmane Date: Thu, 10 Apr 2025 20:07:01 +0530 Subject: [PATCH 1077/1097] PubmaticRTDProvider: read and apply configurations (#12984) * Update pubmaticAnalyticsAdapter.js * Update pubmaticAnalyticsAdapter_spec.js * Update pubmaticAnalyticsAdapter.js * Update pubmaticRtdProvider.js * Update pubmaticRtdProvider_spec.js * Update pubmaticRtdProvider_spec.js --- modules/pubmaticAnalyticsAdapter.js | 14 +- modules/pubmaticRtdProvider.js | 260 ++-- .../modules/pubmaticAnalyticsAdapter_spec.js | 6 + test/spec/modules/pubmaticRtdProvider_spec.js | 1099 +++++++++-------- 4 files changed, 754 insertions(+), 625 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index dc08595bc7d..7c52d59e3eb 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -17,7 +17,7 @@ const FLOOR_VALUES = { TIMEOUT: 'timeout' }; -/// /////////// CONSTANTS ////////////// +/// /////////// CONSTANTS /////////////// const ADAPTER_CODE = 'pubmatic'; const VENDOR_OPENWRAP = 'openwrap'; const DISPLAY_MANAGER = 'Prebid.js'; @@ -55,8 +55,8 @@ const BROWSER_MAP = [ { value: /(firefox)\/([\w\.]+)/i, key: 12 }, // Firefox { value: /\b(?:crios)\/([\w\.]+)/i, key: 1 }, // Chrome for iOS { value: /edg(?:e|ios|a)?\/([\w\.]+)/i, key: 2 }, // Edge - { value: /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i, key: 3 }, // Opera - { value: /(?:ms|\()(ie) ([\w\.]+)/i, key: 4 }, // Internet Explorer + { value: /(opera|opr)(?:.+version\/|[\/ ]+)([\w\.]+)/i, key: 3 }, // Opera + { value: /(?:ms|\()(ie) ([\w\.]+)|(?:trident\/[\w\.]+)/i, key: 4 }, // Internet Explorer { value: /fxios\/([-\w\.]+)/i, key: 5 }, // Firefox for iOS { value: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i, key: 6 }, // Facebook In-App Browser { value: / wv\).+(chrome)\/([\w\.]+)/i, key: 7 }, // Chrome WebView @@ -436,9 +436,9 @@ function executeBidsLoggerCall(e, highestCpmBids) { let outputObj = { s: [] }; let pixelURL = END_POINT_BID_LOGGER; - const user = e.bidderRequests?.length > 0 - ? e.bidderRequests.find(bidder => bidder?.bidderCode === ADAPTER_CODE)?.ortb2?.user?.ext || {} - : {}; + const country = e.bidderRequests?.length > 0 + ? e.bidderRequests.find(bidder => bidder?.bidderCode === ADAPTER_CODE)?.ortb2?.user?.ext?.ctr || '' + : ''; if (!auctionCache || auctionCache.sent) { return; @@ -458,7 +458,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['dm'] = DISPLAY_MANAGER; outputObj['dmv'] = '$prebid.version$' || '-1'; outputObj['bm'] = getBrowserType(); - outputObj['ctr'] = Object.keys(user)?.length ? user.ctr : ''; + outputObj['ctr'] = country || ''; if (floorData) { const floorRootValues = getFloorsCommonField(floorData?.floorRequestData); diff --git a/modules/pubmaticRtdProvider.js b/modules/pubmaticRtdProvider.js index 3f5edf4b7d8..80447e94274 100644 --- a/modules/pubmaticRtdProvider.js +++ b/modules/pubmaticRtdProvider.js @@ -1,5 +1,5 @@ import { submodule } from '../src/hook.js'; -import { logError, isStr, logMessage, isPlainObject, isEmpty, isFn, mergeDeep } from '../src/utils.js'; +import { logError, isStr, isPlainObject, isEmpty, isFn, mergeDeep } from '../src/utils.js'; import { config as conf } from '../src/config.js'; import { getDeviceType as fetchDeviceType, getOS } from '../libraries/userAgentUtils/index.js'; import { getLowEntropySUA } from '../src/fpd/sua.js'; @@ -30,16 +30,17 @@ const CONSTANTS = Object.freeze({ NIGHT: 'night', }, ENDPOINTS: { - FLOORS_BASEURL: `https://ads.pubmatic.com/AdServer/js/pwt/floors/`, - FLOORS_ENDPOINT: `/floors.json`, + BASEURL: 'https://ads.pubmatic.com/AdServer/js/pwt', + FLOORS: 'floors.json', + CONFIGS: 'config.json' } }); const BROWSER_REGEX_MAP = [ { regex: /\b(?:crios)\/([\w\.]+)/i, id: 1 }, // Chrome for iOS { regex: /(edg|edge)(?:e|ios|a)?(?:\/([\w\.]+))?/i, id: 2 }, // Edge - { regex: /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i, id: 3 }, // Opera - { regex: /(?:ms|\()(ie) ([\w\.]+)/i, id: 4 }, // Internet Explorer + { regex: /(opera|opr)(?:.+version\/|[\/ ]+)([\w\.]+)/i, id: 3 }, // Opera + { regex: /(?:ms|\()(ie) ([\w\.]+)|(?:trident\/[\w\.]+)/i, id: 4 }, // Internet Explorer { regex: /fxios\/([-\w\.]+)/i, id: 5 }, // Firefox for iOS { regex: /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i, id: 6 }, // Facebook In-App Browser { regex: / wv\).+(chrome)\/([\w\.]+)/i, id: 7 }, // Chrome WebView @@ -50,9 +51,31 @@ const BROWSER_REGEX_MAP = [ { regex: /(firefox)\/([\w\.]+)/i, id: 12 } // Firefox ]; -let _pubmaticFloorRulesPromise = null; +export const defaultValueTemplate = { + currency: 'USD', + skipRate: 0, + schema: { + fields: ['mediaType', 'size'] + } +}; + +let initTime; +let _fetchFloorRulesPromise = null; let _fetchConfigPromise = null; +export let configMerged; +// configMerged is a reference to the function that can resolve configMergedPromise whenever we want +let configMergedPromise = new Promise((resolve) => { configMerged = resolve; }); export let _country; +// Waits for a given promise to resolve within a timeout +export function withTimeout(promise, ms) { + let timeout; + const timeoutPromise = new Promise((resolve) => { + timeout = setTimeout(() => resolve(undefined), ms); + }); + + return Promise.race([promise.finally(() => clearTimeout(timeout)), timeoutPromise]); +} + // Utility Functions export const getCurrentTimeOfDay = () => { const currentHour = new Date().getHours(); @@ -82,80 +105,85 @@ export const getBrowserType = () => { } // Getter Functions - export const getOs = () => getOS().toString(); - export const getDeviceType = () => fetchDeviceType().toString(); - export const getCountry = () => _country; - export const getUtm = () => { const url = new URL(window.location?.href); const urlParams = new URLSearchParams(url?.search); return urlParams && urlParams.toString().includes(CONSTANTS.UTM) ? CONSTANTS.UTM_VALUES.TRUE : CONSTANTS.UTM_VALUES.FALSE; } -export const getFloorsConfig = (apiResponse) => { - let defaultFloorConfig = conf.getConfig()?.floors ?? {}; - - if (defaultFloorConfig?.endpoint) { - delete defaultFloorConfig.endpoint - } - defaultFloorConfig.data = { ...apiResponse }; - - const floorsConfig = { - floors: { - additionalSchemaFields: { - deviceType: getDeviceType, - timeOfDay: getCurrentTimeOfDay, - browser: getBrowserType, - os: getOs, - utm: getUtm, - country: getCountry, - }, - ...defaultFloorConfig - }, - }; - - return floorsConfig; -}; +export const getFloorsConfig = (floorsData, profileConfigs) => { + if (!isPlainObject(profileConfigs) || isEmpty(profileConfigs)) { + logError(`${CONSTANTS.LOG_PRE_FIX} profileConfigs is not an object or is empty`); + return undefined; + } -export const setFloorsConfig = (data) => { - if (data && isPlainObject(data) && !isEmpty(data)) { - conf.setConfig(getFloorsConfig(data)); - } else { - logMessage(CONSTANTS.LOG_PRE_FIX + 'The fetched floors data is empty.'); - } -}; + // Floor configs from adunit / setconfig + const defaultFloorConfig = conf.getConfig('floors') ?? {}; + if (defaultFloorConfig?.endpoint) { + delete defaultFloorConfig.endpoint; + } + // Plugin data from profile + const dynamicFloors = profileConfigs?.plugins?.dynamicFloors; -export const setPriceFloors = async (publisherId, profileId) => { - const apiResponse = await fetchFloorRules(publisherId, profileId); + // If plugin disabled or config not present, return undefined + if (!dynamicFloors?.enabled || !dynamicFloors?.config) { + return undefined; + } - if (!apiResponse) { - logError(CONSTANTS.LOG_PRE_FIX + 'Error while fetching floors: Empty response'); - } else { - setFloorsConfig(apiResponse); - } + let config = { ...dynamicFloors.config }; + + // default values provided by publisher on profile + const defaultValues = config.defaultValues ?? {}; + // If floorsData is not present, use default values + const finalFloorsData = floorsData ?? { ...defaultValueTemplate, values: { ...defaultValues } }; + + delete config.defaultValues; + // If skiprate is provided in configs, overwrite the value in finalFloorsData + (config.skipRate !== undefined) && (finalFloorsData.skipRate = config.skipRate); + + // merge default configs from page, configs + return { + floors: { + ...defaultFloorConfig, + ...config, + data: finalFloorsData, + additionalSchemaFields: { + deviceType: getDeviceType, + timeOfDay: getCurrentTimeOfDay, + browser: getBrowserType, + os: getOs, + utm: getUtm, + country: getCountry, + }, + }, + }; }; -export const fetchFloorRules = async (publisherId, profileId) => { - try { - const url = `${CONSTANTS.ENDPOINTS.FLOORS_BASEURL}${publisherId}/${profileId}${CONSTANTS.ENDPOINTS.FLOORS_ENDPOINT}`; +export const fetchData = async (publisherId, profileId, type) => { + try { + const endpoint = CONSTANTS.ENDPOINTS[type]; + const baseURL = (type == 'FLOORS') ? `${CONSTANTS.ENDPOINTS.BASEURL}/floors` : CONSTANTS.ENDPOINTS.BASEURL; + const url = `${baseURL}/${publisherId}/${profileId}/${endpoint}`; + const response = await fetch(url); - const response = await fetch(url); - if (!response?.ok) { - logError(CONSTANTS.LOG_PRE_FIX + 'Error while fetching floors: No response'); - } + if (!response.ok) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching ${type}: Not ok`); + return; + } - const cc = response.headers?.get('country_code'); - _country = cc ? cc.split(',')?.map(code => code.trim())[0] : undefined; + if (type === "FLOORS") { + const cc = response.headers?.get('country_code'); + _country = cc ? cc.split(',')?.map(code => code.trim())[0] : undefined; + } - const data = await response.json(); - return data; - } catch (error) { - logError(CONSTANTS.LOG_PRE_FIX + 'Error while fetching floors:', error); - } -} + return await response.json(); + } catch (error) { + logError(`${CONSTANTS.LOG_PRE_FIX} Error while fetching ${type}:`, error); + } +}; /** * Initialize the Pubmatic RTD Module. @@ -164,28 +192,43 @@ export const fetchFloorRules = async (publisherId, profileId) => { * @returns {boolean} */ const init = (config, _userConsent) => { - const publisherId = config?.params?.publisherId; - const profileId = config?.params?.profileId; - - if (!publisherId || !isStr(publisherId) || !profileId || !isStr(profileId)) { - logError( - `${CONSTANTS.LOG_PRE_FIX} ${!publisherId ? 'Missing publisher Id.' - : !isStr(publisherId) ? 'Publisher Id should be a string.' - : !profileId ? 'Missing profile Id.' - : 'Profile Id should be a string.' - }` - ); - return false; - } + initTime = Date.now(); // Capture the initialization time + const { publisherId, profileId } = config?.params || {}; + + if (!publisherId || !isStr(publisherId) || !profileId || !isStr(profileId)) { + logError( + `${CONSTANTS.LOG_PRE_FIX} ${!publisherId ? 'Missing publisher Id.' + : !isStr(publisherId) ? 'Publisher Id should be a string.' + : !profileId ? 'Missing profile Id.' + : 'Profile Id should be a string.' + }` + ); + return false; + } - if (!isFn(continueAuction)) { - logError(`${CONSTANTS.LOG_PRE_FIX} continueAuction is not a function. Please ensure to add priceFloors module.`); - return false; - } + if (!isFn(continueAuction)) { + logError(`${CONSTANTS.LOG_PRE_FIX} continueAuction is not a function. Please ensure to add priceFloors module.`); + return false; + } - _pubmaticFloorRulesPromise = setPriceFloors(publisherId, profileId); - return true; -} + _fetchFloorRulesPromise = fetchData(publisherId, profileId, "FLOORS"); + _fetchConfigPromise = fetchData(publisherId, profileId, "CONFIGS"); + + _fetchConfigPromise.then(async (profileConfigs) => { + const auctionDelay = conf.getConfig('realTimeData').auctionDelay; + const maxWaitTime = 0.8 * auctionDelay; + + const elapsedTime = Date.now() - initTime; + const remainingTime = Math.max(maxWaitTime - elapsedTime, 0); + const floorsData = await withTimeout(_fetchFloorRulesPromise, remainingTime); + + const floorsConfig = getFloorsConfig(floorsData, profileConfigs); + floorsConfig && conf.setConfig(floorsConfig); + configMerged(); + }); + + return true; +}; /** * @param {Object} reqBidsConfigObj @@ -193,35 +236,34 @@ const init = (config, _userConsent) => { * @param {Object} config * @param {Object} userConsent */ - const getBidRequestData = (reqBidsConfigObj, callback) => { - _pubmaticFloorRulesPromise.then(() => { - const hookConfig = { - reqBidsConfigObj, - context: this, - nextFn: () => true, - haveExited: false, - timer: null - }; - continueAuction(hookConfig); - if (_country) { - const ortb2 = { - user: { - ext: { - ctr: _country, + configMergedPromise.then(() => { + const hookConfig = { + reqBidsConfigObj, + context: this, + nextFn: () => true, + haveExited: false, + timer: null + }; + continueAuction(hookConfig); + if (_country) { + const ortb2 = { + user: { + ext: { + ctr: _country, + } + } } - } - } - mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { - [CONSTANTS.SUBMODULE_NAME]: ortb2 - }); - } - callback(); - }).catch((error) => { - logError(CONSTANTS.LOG_PRE_FIX, 'Error in updating floors :', error); - callback(); - }); + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { + [CONSTANTS.SUBMODULE_NAME]: ortb2 + }); + } + callback(); + }).catch((error) => { + logError(CONSTANTS.LOG_PRE_FIX, 'Error in updating floors :', error); + callback(); + }); } /** @type {RtdSubmodule} */ diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 8be7d773600..7e333bc2d78 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -576,6 +576,7 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.dm).to.equal(DISPLAY_MANAGER); expect(data.dmv).to.equal('$prebid.version$' || '-1'); + expect(data.ctr).not.to.be.null; expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.ffs).to.equal(1); @@ -796,6 +797,7 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.dm).to.equal(DISPLAY_MANAGER); expect(data.dmv).to.equal('$prebid.version$' || '-1'); + expect(data.ctr).not.to.be.null; expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.ffs).to.equal(1); @@ -877,6 +879,7 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.dm).to.equal(DISPLAY_MANAGER); expect(data.dmv).to.equal('$prebid.version$' || '-1'); + expect(data.ctr).not.to.be.null; expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -924,6 +927,7 @@ describe('pubmatic analytics adapter', function () { expect(requests.length).to.equal(2); // 1 logger and 1 win-tracker let request = requests[1]; // logger is executed late, trackers execute first let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ctr).not.to.be.null; expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 expect(data.ffs).to.equal(1); expect(data.fsrc).to.equal(2); @@ -1445,6 +1449,7 @@ describe('pubmatic analytics adapter', function () { expect(data.fmv).to.equal('floorModelTest'); expect(data.dm).to.equal(DISPLAY_MANAGER); expect(data.dmv).to.equal('$prebid.version$' || '-1'); + expect(data.ctr).not.to.be.null; expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1575,6 +1580,7 @@ describe('pubmatic analytics adapter', function () { expect(data.fmv).to.equal('floorModelTest'); expect(data.dm).to.equal(DISPLAY_MANAGER); expect(data.dmv).to.equal('$prebid.version$' || '-1'); + expect(data.ctr).not.to.be.null; expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); diff --git a/test/spec/modules/pubmaticRtdProvider_spec.js b/test/spec/modules/pubmaticRtdProvider_spec.js index 8e9fc0681c6..2506ddc7598 100644 --- a/test/spec/modules/pubmaticRtdProvider_spec.js +++ b/test/spec/modules/pubmaticRtdProvider_spec.js @@ -5,527 +5,608 @@ import * as suaModule from '../../../src/fpd/sua.js'; import { config as conf } from '../../../src/config'; import * as hook from '../../../src/hook.js'; import { - registerSubModule, pubmaticSubmodule, getFloorsConfig, setFloorsConfig, setPriceFloors, fetchFloorRules, - getCurrentTimeOfDay, getBrowserType, getOs, getDeviceType, getCountry, getUtm, _country + registerSubModule, pubmaticSubmodule, getFloorsConfig, fetchData, + getCurrentTimeOfDay, getBrowserType, getOs, getDeviceType, getCountry, getUtm, _country, + _profileConfigs, _floorsData, defaultValueTemplate, withTimeout, configMerged } from '../../../modules/pubmaticRtdProvider.js'; - -let sandbox; - -beforeEach(() => { - sandbox = sinon.createSandbox(); -}); - -afterEach(() => { - sandbox.restore(); -}); +import sinon from 'sinon'; describe('Pubmatic RTD Provider', () => { - describe('registerSubModule', () => { - it('should register RTD submodule provider', () => { - let submoduleStub = sinon.stub(hook, 'submodule'); - registerSubModule(); - assert(submoduleStub.calledOnceWith('realTimeData', pubmaticSubmodule)); - submoduleStub.restore(); - }); - }); - describe('submodule', () => { - describe('name', () => { - it('should be pubmatic', () => { - expect(pubmaticSubmodule.name).to.equal('pubmatic'); - }); - }); - }); - - describe('init', () => { - let logErrorStub; - let continueAuctionStub; - - const getConfig = () => ({ - params: { - publisherId: 'test-publisher-id', - profileId: 'test-profile-id' - }, - }); - - beforeEach(() => { - logErrorStub = sandbox.stub(utils, 'logError'); - continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); - }); - - it('should return false if publisherId is missing', () => { - const config = { - params: { - profileId: 'test-profile-id' - } - }; - expect(pubmaticSubmodule.init(config)).to.be.false; - }); - - it('should return false if profileId is missing', () => { - const config = { - params: { - publisherId: 'test-publisher-id' - } - }; - expect(pubmaticSubmodule.init(config)).to.be.false; - }); - - it('should return false if publisherId is not a string', () => { - const config = { - params: { - publisherId: 123, - profileId: 'test-profile-id' - } - }; - expect(pubmaticSubmodule.init(config)).to.be.false; - }); - - it('should return false if profileId is not a string', () => { - const config = { - params: { - publisherId: 'test-publisher-id', - profileId: 345 - } - }; - expect(pubmaticSubmodule.init(config)).to.be.false; - }); - - it('should initialize successfully with valid config', () => { - expect(pubmaticSubmodule.init(getConfig())).to.be.true; - }); - - it('should handle empty config object', () => { - expect(pubmaticSubmodule.init({})).to.be.false; - expect(logErrorStub.calledWith(sinon.match(/Missing publisher Id/))).to.be.true; - }); - - it('should return false if continueAuction is not a function', () => { - continueAuctionStub.value(undefined); - expect(pubmaticSubmodule.init(getConfig())).to.be.false; - expect(logErrorStub.calledWith(sinon.match(/continueAuction is not a function/))).to.be.true; - }); - }); - - describe('getCurrentTimeOfDay', () => { - let clock; + let sandbox; beforeEach(() => { - clock = sandbox.useFakeTimers(new Date('2024-01-01T12:00:00')); // Set fixed time for testing - }); - + sandbox = sinon.createSandbox(); + sandbox.stub(conf, 'getConfig').callsFake(() => { + return { + floors: { + 'enforcement': { + 'floorDeals': true, + 'enforceJS': true + } + }, + realTimeData: { + auctionDelay: 100 + } + }; + }); + }); + afterEach(() => { - clock.restore(); - }); - - const testTimes = [ - { hour: 6, expected: 'morning' }, - { hour: 13, expected: 'afternoon' }, - { hour: 18, expected: 'evening' }, - { hour: 22, expected: 'night' }, - { hour: 4, expected: 'night' } - ]; - - testTimes.forEach(({ hour, expected }) => { - it(`should return ${expected} at ${hour}:00`, () => { - clock.setSystemTime(new Date().setHours(hour)); - const result = getCurrentTimeOfDay(); - expect(result).to.equal(expected); - }); - }); - }); - - describe('getBrowserType', () => { - let userAgentStub, getLowEntropySUAStub; - - const USER_AGENTS = { - chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - firefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0', - edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/91.0.864.67 Safari/537.36', - safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.6 Mobile/15E148 Safari/604.1', - ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', - opera: 'Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.16', - unknown: 'UnknownBrowser/1.0' - }; - - beforeEach(() => { - userAgentStub = sandbox.stub(navigator, 'userAgent'); - getLowEntropySUAStub = sandbox.stub(suaModule, 'getLowEntropySUA').returns(undefined); - }); - - afterEach(() => { - userAgentStub.restore(); - getLowEntropySUAStub.restore(); - }); - - it('should detect Chrome', () => { - userAgentStub.value(USER_AGENTS.chrome); - expect(getBrowserType()).to.equal('9'); - }); - - it('should detect Firefox', () => { - userAgentStub.value(USER_AGENTS.firefox); - expect(getBrowserType()).to.equal('12'); - }); - - it('should detect Edge', () => { - userAgentStub.value(USER_AGENTS.edge); - expect(getBrowserType()).to.equal('2'); - }); - - it('should detect Internet Explorer', () => { - userAgentStub.value(USER_AGENTS.ie); - expect(getBrowserType()).to.equal('4'); - }); - - it('should detect Opera', () => { - userAgentStub.value(USER_AGENTS.opera); - expect(getBrowserType()).to.equal('3'); - }); - - it('should return 0 for unknown browser', () => { - userAgentStub.value(USER_AGENTS.unknown); - expect(getBrowserType()).to.equal('0'); - }); - - it('should return -1 when userAgent is null', () => { - userAgentStub.value(null); - expect(getBrowserType()).to.equal('-1'); - }); - }); - - describe('Utility functions', () => { - it('should set browser correctly', () => { - expect(getBrowserType()).to.be.a('string'); - }); - - it('should set OS correctly', () => { - expect(getOs()).to.be.a('string'); - }); - - it('should set device type correctly', () => { - expect(getDeviceType()).to.be.a('string'); - }); - - it('should set time of day correctly', () => { - expect(getCurrentTimeOfDay()).to.be.a('string'); - }); - - it('should set country correctly', () => { - expect(getCountry()).to.satisfy(value => typeof value === 'string' || value === undefined); - }); - - it('should set UTM correctly', () => { - expect(getUtm()).to.be.a('string'); - expect(getUtm()).to.be.oneOf(['0', '1']); - }); - }); - - describe('getFloorsConfig', () => { - it('should return correct config structure', () => { - const result = getFloorsConfig({}); - - expect(result.floors.data).to.deep.equal({}); - - // Verify the additionalSchemaFields structure - expect(result.floors.additionalSchemaFields).to.have.all.keys([ - 'deviceType', - 'timeOfDay', - 'browser', - 'os', - 'country', - 'utm' - ]); - - Object.values(result.floors.additionalSchemaFields).forEach(field => { - expect(field).to.be.a('function'); - }); - }); - - it('should merge apiResponse data correctly', () => { - const apiResponse = { - currency: 'USD', - schema: { fields: ['mediaType'] }, - values: { 'banner': 1.0 } - }; - - const result = getFloorsConfig(apiResponse); - - expect(result.floors.data).to.deep.equal(apiResponse); - }); - - it('should delete endpoint field in floors', () => { - const apiResponse = { - currency: 'USD', - schema: { fields: ['mediaType'] }, - endpoint: { - url: './floors.json' - }, - values: { 'banner': 1.0 } - }; - - const result = getFloorsConfig(apiResponse); - expect(result.floors).to.not.have.property('endpoint'); - }); - - it('should maintain correct function references', () => { - const result = getFloorsConfig({}); - - expect(result.floors.additionalSchemaFields.deviceType).to.equal(getDeviceType); - expect(result.floors.additionalSchemaFields.timeOfDay).to.equal(getCurrentTimeOfDay); - expect(result.floors.additionalSchemaFields.browser).to.equal(getBrowserType); - expect(result.floors.additionalSchemaFields.os).to.equal(getOs); - expect(result.floors.additionalSchemaFields.country).to.equal(getCountry); - expect(result.floors.additionalSchemaFields.utm).to.equal(getUtm); - }); - }); - - describe('setFloorsConfig', () => { - let logMessageStub; - let confStub; - - beforeEach(() => { - logMessageStub = sandbox.stub(utils, 'logMessage'); - confStub = sandbox.stub(conf, 'setConfig'); - }); - - it('should set config when valid data is provided', () => { - const validData = { - currency: 'USD', - schema: { fields: ['mediaType'] } - }; - - setFloorsConfig(validData); - - expect(confStub.called).to.be.true; - const calledWith = confStub.getCall(0).args[0]; - expect(calledWith).to.have.nested.property('floors.data.currency', 'USD'); - expect(calledWith).to.have.nested.property('floors.data.schema.fields[0]', 'mediaType'); - }); - - it('should log message when data is null', () => { - setFloorsConfig(null); - - expect(confStub.called).to.be.false; - expect(logMessageStub.called).to.be.true; - expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); - }); - - it('should log message when data is undefined', () => { - setFloorsConfig(undefined); - - expect(confStub.called).to.be.false; - expect(logMessageStub.called).to.be.true; - expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); - }); - - it('should log message when data is an empty object ', () => { - setFloorsConfig({}); - - expect(confStub.called).to.be.false; - expect(logMessageStub.called).to.be.true; - expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); - }); - - it('should log message when data is an array', () => { - setFloorsConfig([]); - - expect(confStub.called).to.be.false; - expect(logMessageStub.called).to.be.true; - expect(logMessageStub.getCall(0).args[0]).to.include('floors data is empty'); - }); - - it('should set config with complex floor data', () => { - const floorData = { - currency: 'USD', - schema: { - fields: ['mediaType', 'size'], - delimiter: '|' - }, - values: { - 'banner|300x250': 1.0, - 'banner|300x600': 2.0 - } - }; - - setFloorsConfig(floorData); - - expect(confStub.called).to.be.true; - const calledWith = confStub.getCall(0).args[0]; - expect(calledWith.floors.data).to.deep.equal(floorData); - }); - - it('should handle non-object data types', () => { - const invalidInputs = [ - 'string', - 123, - true, - () => { }, - Symbol('test') - ]; - - invalidInputs.forEach(input => { - setFloorsConfig(input); - expect(confStub.called).to.be.false; - expect(logMessageStub.called).to.be.true; - }); - }); - }); - - describe('Price Floor Functions', () => { - let logErrorStub; - let fetchStub; - let confStub; - - beforeEach(() => { - logErrorStub = sandbox.stub(utils, 'logError'); - fetchStub = sandbox.stub(window, 'fetch'); - confStub = sandbox.stub(conf, 'setConfig'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('fetchFloorRules', () => { - beforeEach(() => { - global._country = undefined; - }); - - it('should successfully fetch and parse floor rules', async () => { - const mockApiResponse = { - data: { - currency: 'USD', - modelGroups: [], - values: {} - } + sandbox.restore(); + }); + + describe('registerSubModule', () => { + it('should register RTD submodule provider', () => { + let submoduleStub = sinon.stub(hook, 'submodule'); + registerSubModule(); + assert(submoduleStub.calledOnceWith('realTimeData', pubmaticSubmodule)); + submoduleStub.restore(); + }); + }); + + describe('submodule', () => { + describe('name', () => { + it('should be pubmatic', () => { + expect(pubmaticSubmodule.name).to.equal('pubmatic'); + }); + }); + }); + + describe('init', () => { + let logErrorStub; + let continueAuctionStub; + + const getConfig = () => ({ + params: { + publisherId: 'test-publisher-id', + profileId: 'test-profile-id' + }, + }); + + beforeEach(() => { + logErrorStub = sandbox.stub(utils, 'logError'); + continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); + }); + + it('should return false if publisherId is missing', () => { + const config = { + params: { + profileId: 'test-profile-id' + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should return false if profileId is missing', () => { + const config = { + params: { + publisherId: 'test-publisher-id' + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should return false if publisherId is not a string', () => { + const config = { + params: { + publisherId: 123, + profileId: 'test-profile-id' + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should return false if profileId is not a string', () => { + const config = { + params: { + publisherId: 'test-publisher-id', + profileId: 345 + } + }; + expect(pubmaticSubmodule.init(config)).to.be.false; + }); + + it('should initialize successfully with valid config', () => { + expect(pubmaticSubmodule.init(getConfig())).to.be.true; + }); + + it('should handle empty config object', () => { + expect(pubmaticSubmodule.init({})).to.be.false; + expect(logErrorStub.calledWith(sinon.match(/Missing publisher Id/))).to.be.true; + }); + + it('should return false if continueAuction is not a function', () => { + continueAuctionStub.value(undefined); + expect(pubmaticSubmodule.init(getConfig())).to.be.false; + expect(logErrorStub.calledWith(sinon.match(/continueAuction is not a function/))).to.be.true; + }); + }); + + describe('getCurrentTimeOfDay', () => { + let clock; + + beforeEach(() => { + clock = sandbox.useFakeTimers(new Date('2024-01-01T12:00:00')); // Set fixed time for testing + }); + + afterEach(() => { + clock.restore(); + }); + + const testTimes = [ + { hour: 6, expected: 'morning' }, + { hour: 13, expected: 'afternoon' }, + { hour: 18, expected: 'evening' }, + { hour: 22, expected: 'night' }, + { hour: 4, expected: 'night' } + ]; + + testTimes.forEach(({ hour, expected }) => { + it(`should return ${expected} at ${hour}:00`, () => { + clock.setSystemTime(new Date().setHours(hour)); + const result = getCurrentTimeOfDay(); + expect(result).to.equal(expected); + }); + }); + }); + + describe('getBrowserType', () => { + let userAgentStub, getLowEntropySUAStub; + + const USER_AGENTS = { + chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + firefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0', + edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/91.0.864.67 Safari/537.36', + safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.6 Mobile/15E148 Safari/604.1', + ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', + opera: 'Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.16', + unknown: 'UnknownBrowser/1.0' }; - fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200, headers: { 'country_code': 'US' } })); - - const result = await fetchFloorRules('publisherId', 'profileId'); - expect(result).to.deep.equal(mockApiResponse); - expect(_country).to.equal('US'); - }); - - it('should correctly extract the first unique country code from response headers', async () => { - fetchStub.resolves(new Response(JSON.stringify({}), { - status: 200, - headers: { 'country_code': 'US,IN,US' } - })); - - await fetchFloorRules('publisherId', 'profileId'); - expect(_country).to.equal('US'); - }); - - it('should set _country to undefined if country_code header is missing', async () => { - fetchStub.resolves(new Response(JSON.stringify({}), { - status: 200 - })); - - await fetchFloorRules('publisherId', 'profileId'); - expect(_country).to.be.undefined; - }); - - it('should log error when JSON parsing fails', async () => { - fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); - - await fetchFloorRules('publisherId', 'profileId'); - expect(logErrorStub.called).to.be.true; - expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching floors'); - }); - - it('should log error when response is not ok', async () => { - fetchStub.resolves(new Response(null, { status: 500 })); - - await fetchFloorRules('publisherId', 'profileId'); - expect(logErrorStub.calledWith(sinon.match(/Error while fetching floors: No response/))).to.be.true; - }); - - it('should log error on network failure', async () => { - fetchStub.rejects(new Error('Network Error')); - - await fetchFloorRules('publisherId', 'profileId'); - expect(logErrorStub.called).to.be.true; - expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching floors'); - }); - }); - - describe('setPriceFloors', () => { - it('should log error for empty response', async () => { - fetchStub.resolves(new Response(null, { status: 200 })); - - await setPriceFloors(); - expect(logErrorStub.calledWith(sinon.match(/Error while fetching floors/))).to.be.true; - }); - - it('should successfully process valid response', async () => { - const mockApiResponse = { - data: { - currency: 'USD', - modelGroups: [], - values: {} - } + beforeEach(() => { + userAgentStub = sandbox.stub(navigator, 'userAgent'); + getLowEntropySUAStub = sandbox.stub(suaModule, 'getLowEntropySUA').returns(undefined); + }); + + afterEach(() => { + userAgentStub.restore(); + getLowEntropySUAStub.restore(); + }); + + it('should detect Chrome', () => { + userAgentStub.value(USER_AGENTS.chrome); + expect(getBrowserType()).to.equal('9'); + }); + + it('should detect Firefox', () => { + userAgentStub.value(USER_AGENTS.firefox); + expect(getBrowserType()).to.equal('12'); + }); + + it('should detect Edge', () => { + userAgentStub.value(USER_AGENTS.edge); + expect(getBrowserType()).to.equal('2'); + }); + + it('should detect Internet Explorer', () => { + userAgentStub.value(USER_AGENTS.ie); + expect(getBrowserType()).to.equal('4'); + }); + + it('should detect Opera', () => { + userAgentStub.value(USER_AGENTS.opera); + expect(getBrowserType()).to.equal('3'); + }); + + it('should return 0 for unknown browser', () => { + userAgentStub.value(USER_AGENTS.unknown); + expect(getBrowserType()).to.equal('0'); + }); + + it('should return -1 when userAgent is null', () => { + userAgentStub.value(null); + expect(getBrowserType()).to.equal('-1'); + }); + }); + + describe('Utility functions', () => { + it('should set browser correctly', () => { + expect(getBrowserType()).to.be.a('string'); + }); + + it('should set OS correctly', () => { + expect(getOs()).to.be.a('string'); + }); + + it('should set device type correctly', () => { + expect(getDeviceType()).to.be.a('string'); + }); + + it('should set time of day correctly', () => { + expect(getCurrentTimeOfDay()).to.be.a('string'); + }); + + it('should set country correctly', () => { + expect(getCountry()).to.satisfy(value => typeof value === 'string' || value === undefined); + }); + + it('should set UTM correctly', () => { + expect(getUtm()).to.be.a('string'); + expect(getUtm()).to.be.oneOf(['0', '1']); + }); + }); + + describe('getFloorsConfig', () => { + let floorsData, profileConfigs; + let sandbox; + let logErrorStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + logErrorStub = sandbox.stub(utils, 'logError'); + floorsData = { + "currency": "USD", + "floorProvider": "PM", + "floorsSchemaVersion": 2, + "modelGroups": [ + { + "modelVersion": "M_1", + "modelWeight": 100, + "schema": { + "fields": [ + "domain" + ] + }, + "values": { + "*": 2.00 + } + } + ], + "skipRate": 0 + }; + profileConfigs = { + 'plugins': { + 'dynamicFloors': { + 'enabled': true, + 'config': { + 'enforcement': { + 'floorDeals': false, + 'enforceJS': false + }, + 'floorMin': 0.1111, + 'skipRate': 11, + 'defaultValues': { + "*|*": 0.2 + } + } + } + } + } + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return correct config structure', () => { + const result = getFloorsConfig(floorsData, profileConfigs); + + expect(result.floors).to.be.an('object'); + expect(result.floors).to.be.an('object'); + expect(result.floors).to.have.property('enforcement'); + expect(result.floors.enforcement).to.have.property('floorDeals', false); + expect(result.floors.enforcement).to.have.property('enforceJS', false); + expect(result.floors).to.have.property('floorMin', 0.1111); + + // Verify the additionalSchemaFields structure + expect(result.floors.additionalSchemaFields).to.have.all.keys([ + 'deviceType', + 'timeOfDay', + 'browser', + 'os', + 'country', + 'utm' + ]); + + Object.values(result.floors.additionalSchemaFields).forEach(field => { + expect(field).to.be.a('function'); + }); + }); + + it('should return undefined when plugin is disabled', () => { + profileConfigs.plugins.dynamicFloors.enabled = false; + const result = getFloorsConfig(floorsData, profileConfigs); + + expect(result).to.equal(undefined); + }); + + it('should initialise default values to empty object when not available', () => { + profileConfigs.plugins.dynamicFloors.config.defaultValues = undefined; + floorsData = undefined; + const result = getFloorsConfig(floorsData, profileConfigs); + + expect(result.floors.data).to.have.property('currency', 'USD'); + expect(result.floors.data).to.have.property('skipRate', 11); + expect(result.floors.data.schema).to.deep.equal(defaultValueTemplate.schema); + expect(result.floors.data.value).to.deep.equal(defaultValueTemplate.value); + }); + + it('should replace skipRate from config to data when avaialble', () => { + const result = getFloorsConfig(floorsData, profileConfigs); + + expect(result.floors.data).to.have.property('skipRate', 11); + }); + + it('should not replace skipRate from config to data when not avaialble', () => { + delete profileConfigs.plugins.dynamicFloors.config.skipRate; + const result = getFloorsConfig(floorsData, profileConfigs); + + expect(result.floors.data).to.have.property('skipRate', 0); + }); + + it('should maintain correct function references', () => { + const result = getFloorsConfig(floorsData, profileConfigs); + + expect(result.floors.additionalSchemaFields.deviceType).to.equal(getDeviceType); + expect(result.floors.additionalSchemaFields.timeOfDay).to.equal(getCurrentTimeOfDay); + expect(result.floors.additionalSchemaFields.browser).to.equal(getBrowserType); + expect(result.floors.additionalSchemaFields.os).to.equal(getOs); + expect(result.floors.additionalSchemaFields.country).to.equal(getCountry); + expect(result.floors.additionalSchemaFields.utm).to.equal(getUtm); + }); + + it('should log error when profileConfigs is not an object', () => { + profileConfigs = 'invalid'; + const result = getFloorsConfig(floorsData, profileConfigs); + expect(result).to.be.undefined; + expect(logErrorStub.calledWith(sinon.match(/profileConfigs is not an object or is empty/))).to.be.true; + }); + }); + + describe('fetchData for configs', () => { + let logErrorStub; + let fetchStub; + let confStub; + + beforeEach(() => { + logErrorStub = sandbox.stub(utils, 'logError'); + fetchStub = sandbox.stub(window, 'fetch'); + confStub = sandbox.stub(conf, 'setConfig'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should successfully fetch profile configs', async () => { + const mockApiResponse = { + "profileName": "profie name", + "desc": "description", + "plugins": { + "dynamicFloors": { + "enabled": false + } + } + }; + + fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200 })); + + const result = await fetchData('1234', '123', 'CONFIGS'); + expect(result).to.deep.equal(mockApiResponse); + }); + + it('should log error when JSON parsing fails', async () => { + fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); + + await fetchData('1234', '123', 'CONFIGS'); + expect(logErrorStub.called).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching CONFIGS:'); + }); + + it('should log error when response is not ok', async () => { + fetchStub.resolves(new Response(null, { status: 500 })); + + await fetchData('1234', '123', 'CONFIGS'); + expect(logErrorStub.calledWith(sinon.match(/Error while fetching CONFIGS: Not ok/))).to.be.true; + }); + + it('should log error on network failure', async () => { + fetchStub.rejects(new Error('Network Error')); + + await fetchData('1234', '123', 'CONFIGS'); + expect(logErrorStub.called).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching CONFIGS'); + }); + }); + + describe('fetchData for floors', () => { + let logErrorStub; + let fetchStub; + let confStub; + + beforeEach(() => { + logErrorStub = sandbox.stub(utils, 'logError'); + fetchStub = sandbox.stub(window, 'fetch'); + confStub = sandbox.stub(conf, 'setConfig'); + global._country = undefined; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should successfully fetch and parse floor rules', async () => { + const mockApiResponse = { + data: { + currency: 'USD', + modelGroups: [], + values: {} + } + }; + + fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200, headers: { 'country_code': 'US' } })); + + const result = await fetchData('1234', '123', 'FLOORS'); + expect(result).to.deep.equal(mockApiResponse); + expect(_country).to.equal('US'); + }); + + it('should correctly extract the first unique country code from response headers', async () => { + fetchStub.resolves(new Response(JSON.stringify({}), { + status: 200, + headers: { 'country_code': 'US,IN,US' } + })); + + await fetchData('1234', '123', 'FLOORS'); + expect(_country).to.equal('US'); + }); + + it('should set _country to undefined if country_code header is missing', async () => { + fetchStub.resolves(new Response(JSON.stringify({}), { + status: 200 + })); + + await fetchData('1234', '123', 'FLOORS'); + expect(_country).to.be.undefined; + }); + + it('should log error when JSON parsing fails', async () => { + fetchStub.resolves(new Response('Invalid JSON', { status: 200 })); + + await fetchData('1234', '123', 'FLOORS'); + expect(logErrorStub.called).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching FLOORS'); + }); + + it('should log error when response is not ok', async () => { + fetchStub.resolves(new Response(null, { status: 500 })); + + await fetchData('1234', '123', 'FLOORS'); + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching FLOORS'); + }); + + it('should log error on network failure', async () => { + fetchStub.rejects(new Error('Network Error')); + + await fetchData('1234', '123', 'FLOORS'); + expect(logErrorStub.called).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching FLOORS'); + }); + }); + + describe('getBidRequestData', function () { + let callback, continueAuctionStub, mergeDeepStub, logErrorStub; + + const reqBidsConfigObj = { + adUnits: [{ code: 'ad-slot-code-0' }], + auctionId: 'auction-id-0', + ortb2Fragments: { + bidder: { + user: { + ext: { + ctr: 'US', + } + } + } + } }; - fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200 })); - await setPriceFloors('publisherId', 'profileId'); - - expect(fetchStub.called).to.be.true; - expect(confStub.called).to.be.true; - }); - }); - }); - - describe('getBidRequestData', () => { - let _pubmaticFloorRulesPromiseMock; - let continueAuctionStub; - let callback = sinon.spy(); - - const reqBidsConfigObj = { - adUnits: [{ code: 'ad-slot-code-0' }], - auctionId: 'auction-id-0', - ortb2Fragments: { - bidder: { - user: { - ext: { - ctr: 'US', + const ortb2 = { + user: { + ext: { + ctr: 'US', + } } - } } - } - }; - - const ortb2 = { - user: { - ext: { - ctr: 'US', - } - } - } - - const hookConfig = { - reqBidsConfigObj, - context: this, - nextFn: () => true, - haveExited: false, - timer: null - }; - - beforeEach(() => { - continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); - }); + + const hookConfig = { + reqBidsConfigObj, + context: this, + nextFn: () => true, + haveExited: false, + timer: null + }; - it('should call continueAuction once after _pubmaticFloorRulesPromise. Also getBidRequestData executed only once', async () => { - _pubmaticFloorRulesPromiseMock = Promise.resolve(); - pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); - await _pubmaticFloorRulesPromiseMock; - expect(continueAuctionStub.called); - expect( - continueAuctionStub.alwaysCalledWith( - hookConfig - ) - ); - expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.include(ortb2); + beforeEach(() => { + callback = sinon.spy(); + continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction'); + logErrorStub = sandbox.stub(utils, 'logError'); + + global.configMergedPromise = Promise.resolve(); + }); + + afterEach(() => { + sandbox.restore(); // Restore all stubs/spies + }); + + it('should call continueAuction with correct hookConfig', async function () { + configMerged(); + await pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); + + expect(continueAuctionStub.called).to.be.true; + expect(continueAuctionStub.firstCall.args[0]).to.have.property('reqBidsConfigObj', reqBidsConfigObj); + expect(continueAuctionStub.firstCall.args[0]).to.have.property('haveExited', false); + }); + + // it('should merge country data into ortb2Fragments.bidder', async function () { + // configMerged(); + // global._country = 'US'; + // pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); + + // expect(reqBidsConfigObj.ortb2Fragments.bidder).to.have.property('pubmatic'); + // // expect(reqBidsConfigObj.ortb2Fragments.bidder.pubmatic.user.ext.ctr).to.equal('US'); + // }); + + it('should call callback once after execution', async function () { + configMerged(); + await pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback); + + expect(callback.called).to.be.true; + }); + }); + + describe('withTimeout', function () { + it('should resolve with the original promise value if it resolves before the timeout', async function () { + const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 50)); + const result = await withTimeout(promise, 100); + expect(result).to.equal('success'); + }); + + it('should resolve with undefined if the promise takes longer than the timeout', async function () { + const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 200)); + const result = await withTimeout(promise, 100); + expect(result).to.be.undefined; + }); + + it('should properly handle rejected promises', async function () { + const promise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failure')), 50)); + try { + await withTimeout(promise, 100); + } catch (error) { + expect(error.message).to.equal('Failure'); + } + }); + + it('should resolve with undefined if the original promise is rejected but times out first', async function () { + const promise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failure')), 200)); + const result = await withTimeout(promise, 100); + expect(result).to.be.undefined; + }); + + it('should clear the timeout when the promise resolves before the timeout', async function () { + const clock = sinon.useFakeTimers(); + const clearTimeoutSpy = sinon.spy(global, 'clearTimeout'); + + const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 50)); + const resultPromise = withTimeout(promise, 100); + + clock.tick(50); + await resultPromise; + + expect(clearTimeoutSpy.called).to.be.true; + + clearTimeoutSpy.restore(); + clock.restore(); + }); }); - }); }); From 66dd5ad4cd55ebf852f07e2e28c0f5e9cf120aac Mon Sep 17 00:00:00 2001 From: janzych-smart <103245435+janzych-smart@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:04:49 +0200 Subject: [PATCH 1078/1097] Equativ Bid Adapter: add DSP cookie sync (#12787) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add user sync * use local storage instead of cookies * update equativ adapter u.t. * fix equativ adapter u.t. --------- Co-authored-by: Krzysztof Sokół --- modules/equativBidAdapter.js | 32 +++++--- test/spec/modules/equativBidAdapter_spec.js | 90 +++++++++++++-------- 2 files changed, 78 insertions(+), 44 deletions(-) diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js index 4cc53a7b4ac..77c37f5f79f 100644 --- a/modules/equativBidAdapter.js +++ b/modules/equativBidAdapter.js @@ -1,4 +1,5 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -14,7 +15,9 @@ const BIDDER_CODE = 'equativ'; const COOKIE_SYNC_ORIGIN = 'https://apps.smartadserver.com'; const COOKIE_SYNC_URL = `${COOKIE_SYNC_ORIGIN}/diff/templates/asset/csync.html`; const LOG_PREFIX = 'Equativ:'; -const PID_COOKIE_NAME = 'eqt_pid'; +const PID_STORAGE_NAME = 'eqt_pid'; + +let nwid = 0; let impIdMap = {}; @@ -159,18 +162,28 @@ export const spec = { * @param syncOptions * @returns {{type: string, url: string}[]} */ - getUserSyncs: (syncOptions) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent) => { if (syncOptions.iframeEnabled) { window.addEventListener('message', function handler(event) { - if (event.origin === COOKIE_SYNC_ORIGIN && event.data.pid) { - const exp = new Date(); - exp.setTime(Date.now() + 31536000000); // in a year - storage.setCookie(PID_COOKIE_NAME, event.data.pid, exp.toUTCString()); + if (event.origin === COOKIE_SYNC_ORIGIN && event.data.action === 'getConsent') { + event.source.postMessage({ + action: 'consentResponse', + id: event.data.id, + consents: gdprConsent.vendorData.vendor.consents + }, event.origin); + + if (event.data.pid) { + storage.setDataInLocalStorage(PID_STORAGE_NAME, event.data.pid); + } + this.removeEventListener('message', handler); } }); - return [{ type: 'iframe', url: COOKIE_SYNC_URL }]; + let url = tryAppendQueryString(COOKIE_SYNC_URL + '?', 'nwid', nwid); + url = tryAppendQueryString(url, 'gdpr', (gdprConsent.gdprApplies ? '1' : '0')); + + return [{ type: 'iframe', url }]; } return []; @@ -257,7 +270,8 @@ export const converter = ortbConverter({ const req = buildRequest(splitImps, bidderRequest, context); let env = ['ortb2.site.publisher', 'ortb2.app.publisher', 'ortb2.dooh.publisher'].find(propPath => deepAccess(bid, propPath)) || 'ortb2.site.publisher'; - deepSetValue(req, env.replace('ortb2.', '') + '.id', deepAccess(bid, env + '.id') || bid.params.networkId); + nwid = deepAccess(bid, env + '.id') || bid.params.networkId; + deepSetValue(req, env.replace('ortb2.', '') + '.id', nwid); [ { path: 'mediaTypes.video', props: ['mimes', 'placement'] }, @@ -273,7 +287,7 @@ export const converter = ortbConverter({ } }); - const pid = storage.getCookie(PID_COOKIE_NAME); + const pid = storage.getDataFromLocalStorage(PID_STORAGE_NAME); if (pid) { deepSetValue(req, 'user.buyeruid', pid); } diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js index 3cc730d39d1..8744ec8f0a9 100644 --- a/test/spec/modules/equativBidAdapter_spec.js +++ b/test/spec/modules/equativBidAdapter_spec.js @@ -123,7 +123,7 @@ describe('Equativ bid adapter tests', () => { nativeOrtbRequest, bidder: 'equativ', params: { - networkId: 777, + networkId: 111, }, requestId: 'equativ_native_reqid_42', ortb2Imp: { @@ -424,25 +424,25 @@ describe('Equativ bid adapter tests', () => { }); it('should read and send pid as buyeruid', () => { - const cookieData = { + const localStorageData = { 'eqt_pid': '7789746781' }; - const getCookieStub = sinon.stub(storage, 'getCookie'); - getCookieStub.callsFake(cookieName => cookieData[cookieName]); + const getDataFromLocalStorage = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataFromLocalStorage.callsFake(name => localStorageData[name]); const request = spec.buildRequests( DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST )[0]; - expect(request.data.user).to.have.property('buyeruid').that.eq(cookieData['eqt_pid']); + expect(request.data.user).to.have.property('buyeruid').that.eq(localStorageData['eqt_pid']); - getCookieStub.restore(); + getDataFromLocalStorage.restore(); }); it('should not send buyeruid', () => { - const getCookieStub = sinon.stub(storage, 'getCookie'); - getCookieStub.callsFake(() => null); + const getDataFromLocalStorage = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataFromLocalStorage.callsFake(() => null); const request = spec.buildRequests( DEFAULT_BANNER_BID_REQUESTS, @@ -451,12 +451,12 @@ describe('Equativ bid adapter tests', () => { expect(request.data).to.not.have.property('user'); - getCookieStub.restore(); + getDataFromLocalStorage.restore(); }); it('should pass buyeruid defined in config', () => { - const getCookieStub = sinon.stub(storage, 'getCookie'); - getCookieStub.callsFake(() => undefined); + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataFromLocalStorageStub.callsFake(() => undefined); const bidRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, @@ -470,7 +470,7 @@ describe('Equativ bid adapter tests', () => { expect(request.data.user.buyeruid).to.deep.eq(bidRequest.ortb2.user.buyeruid); - getCookieStub.restore(); + getDataFromLocalStorageStub.restore(); }); it('should build a video request properly under normal circumstances', () => { @@ -752,11 +752,11 @@ describe('Equativ bid adapter tests', () => { }); describe('getUserSyncs', () => { - let setCookieStub; + let setDataInLocalStorageStub; - beforeEach(() => setCookieStub = sinon.stub(storage, 'setCookie')); + beforeEach(() => setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage')); - afterEach(() => setCookieStub.restore()); + afterEach(() => setDataInLocalStorageStub.restore()); it('should return empty array if iframe sync not enabled', () => { const syncs = spec.getUserSyncs({}, SAMPLE_RESPONSE); @@ -764,75 +764,95 @@ describe('Equativ bid adapter tests', () => { }); it('should retrieve and save user pid', (done) => { - const userSyncs = spec.getUserSyncs( + spec.getUserSyncs( { iframeEnabled: true }, - SAMPLE_RESPONSE + SAMPLE_RESPONSE, + { gdprApplies: true, vendorData: { vendor: { consents: {} } } } ); window.dispatchEvent(new MessageEvent('message', { data: { + action: 'getConsent', pid: '7767825890726' }, - origin: 'https://apps.smartadserver.com' + origin: 'https://apps.smartadserver.com', + source: window })); - const exp = new Date(); - exp.setTime(Date.now() + 31536000000); - setTimeout(() => { - expect(setCookieStub.calledOnce).to.be.true; - expect(setCookieStub.calledWith('eqt_pid', '7767825890726', exp.toUTCString())).to.be.true; + expect(setDataInLocalStorageStub.calledOnce).to.be.true; + expect(setDataInLocalStorageStub.calledWith('eqt_pid', '7767825890726')).to.be.true; done(); }); }); - it('should not save user pid coming from not origin', (done) => { - const userSyncs = spec.getUserSyncs( + it('should not save user pid coming from incorrect origin', (done) => { + spec.getUserSyncs( { iframeEnabled: true }, - SAMPLE_RESPONSE + SAMPLE_RESPONSE, + { gdprApplies: true, vendorData: { vendor: { consents: {} } } } ); window.dispatchEvent(new MessageEvent('message', { data: { + action: 'getConsent', pid: '7767825890726' }, - origin: 'https://another-origin.com' + origin: 'https://another-origin.com', + source: window })); setTimeout(() => { - expect(setCookieStub.notCalled).to.be.true; + expect(setDataInLocalStorageStub.notCalled).to.be.true; done(); }); }); it('should not save empty pid', (done) => { - const userSyncs = spec.getUserSyncs( + spec.getUserSyncs( { iframeEnabled: true }, - SAMPLE_RESPONSE + SAMPLE_RESPONSE, + { gdprApplies: true, vendorData: { vendor: { consents: {} } } } ); window.dispatchEvent(new MessageEvent('message', { data: { + action: 'getConsent', pid: '' }, - origin: 'https://apps.smartadserver.com' + origin: 'https://apps.smartadserver.com', + source: window })); setTimeout(() => { - expect(setCookieStub.notCalled).to.be.true; + expect(setDataInLocalStorageStub.notCalled).to.be.true; done(); }); }); - it('should return array including iframe cookie sync object', () => { + it('should return array including iframe cookie sync object (gdprApplies=true)', () => { + const syncs = spec.getUserSyncs( + { iframeEnabled: true }, + SAMPLE_RESPONSE, + { gdprApplies: true } + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0]).to.deep.equal({ + type: 'iframe', + url: 'https://apps.smartadserver.com/diff/templates/asset/csync.html?nwid=111&gdpr=1&' + }); + }); + + it('should return array including iframe cookie sync object (gdprApplies=false)', () => { const syncs = spec.getUserSyncs( { iframeEnabled: true }, - SAMPLE_RESPONSE + SAMPLE_RESPONSE, + { gdprApplies: false } ); expect(syncs).to.have.lengthOf(1); expect(syncs[0]).to.deep.equal({ type: 'iframe', - url: 'https://apps.smartadserver.com/diff/templates/asset/csync.html' + url: 'https://apps.smartadserver.com/diff/templates/asset/csync.html?nwid=111&gdpr=0&' }); }); }); From 34e9de006052aba4fd8bc40292eb1688fed31986 Mon Sep 17 00:00:00 2001 From: "pratik.ta" <143182729+Pratik3307@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:52:47 +0530 Subject: [PATCH 1079/1097] Fix: vastTracker url to have updated cpm (#12833) --- libraries/vastTrackers/vastTrackers.js | 14 +++++++------- test/spec/libraries/vastTrackers_spec.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/vastTrackers/vastTrackers.js b/libraries/vastTrackers/vastTrackers.js index b8fc829a89a..b74bdd1aac5 100644 --- a/libraries/vastTrackers/vastTrackers.js +++ b/libraries/vastTrackers/vastTrackers.js @@ -1,4 +1,4 @@ -import {addBidResponse} from '../../src/auction.js'; +import {callPrebidCache} from '../../src/auction.js'; import {VIDEO} from '../../src/mediaTypes.js'; import {logError} from '../../src/utils.js'; import {isActivityAllowed} from '../../src/activities/rules.js'; @@ -15,20 +15,20 @@ export function reset() { export function enable() { if (!enabled) { - addBidResponse.before(addTrackersToResponse); + callPrebidCache.before(addTrackersToResponse); enabled = true; } } export function disable() { if (enabled) { - addBidResponse.getHooks({hook: addTrackersToResponse}).remove(); + callPrebidCache.getHooks({hook: addTrackersToResponse}).remove(); enabled = false; } } -export function responseHook({index = auctionManager.index} = {}) { - return function addTrackersToResponse(next, adUnitcode, bidResponse, reject) { +export function cacheVideoBidHook({index = auctionManager.index} = {}) { + return function addTrackersToResponse(next, auctionInstance, bidResponse, afterBidAdded, videoMediaType) { if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { const vastTrackers = getVastTrackers(bidResponse, {index}); if (vastTrackers) { @@ -39,11 +39,11 @@ export function responseHook({index = auctionManager.index} = {}) { } } } - next(adUnitcode, bidResponse, reject); + next(auctionInstance, bidResponse, afterBidAdded, videoMediaType); } } -const addTrackersToResponse = responseHook(); +const addTrackersToResponse = cacheVideoBidHook(); enable(); export function registerVastTrackers(moduleType, moduleName, trackerFn) { diff --git a/test/spec/libraries/vastTrackers_spec.js b/test/spec/libraries/vastTrackers_spec.js index 3e8a8456e9c..a359f32a2d4 100644 --- a/test/spec/libraries/vastTrackers_spec.js +++ b/test/spec/libraries/vastTrackers_spec.js @@ -4,7 +4,7 @@ import { getVastTrackers, insertVastTrackers, registerVastTrackers, - reset, responseHook, + reset, cacheVideoBidHook, disable } from 'libraries/vastTrackers/vastTrackers.js'; import {MODULE_TYPE_ANALYTICS} from '../../../src/activities/modules.js'; @@ -79,7 +79,7 @@ describe('vast trackers', () => { if (FEATURES.VIDEO) { it('should add trackers to bid response', () => { - responseHook({index})(sinon.stub(), 'au', bid); + cacheVideoBidHook({index})(sinon.stub(), 'au', bid); expect(bid.vastImpUrl).to.eql([ 'https://vasttracking.mydomain.com/vast?cpm=1' ]) From f3489880e7b1765cc2f86344fbc855d3c5053631 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 10 Apr 2025 18:37:04 +0000 Subject: [PATCH 1080/1097] Prebid 9.39.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d999fcef68..8088b28109c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.39.0-pre", + "version": "9.39.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.39.0-pre", + "version": "9.39.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index 25b9acb3cc0..f41b3fae314 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.39.0-pre", + "version": "9.39.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From ee160790331ffbd86919564d883efe04c34dd7bf Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 10 Apr 2025 18:37:04 +0000 Subject: [PATCH 1081/1097] Increment version to 9.40.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8088b28109c..190b5ee25ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.39.0", + "version": "9.40.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.39.0", + "version": "9.40.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index f41b3fae314..fa57e545b01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.39.0", + "version": "9.40.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 214792e81ed7483117da770f8f04663551c9b194 Mon Sep 17 00:00:00 2001 From: carsten1980 <45483737+carsten1980@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:16:21 +0200 Subject: [PATCH 1082/1097] AdSpirit Bid Adapter : updated version with native support (#12776) * Video is added * Update adspiritBidAdapter.js Updates js file with native support * Update adspiritBidAdapter.md Updated md file with new information related to native * Update adspiritBidAdapter_spec.js Adspirit updated test case with native support * Update adspiritBidAdapter.md A new description related to native has been added to adspiritBidAdapter.md. * Update adspiritBidAdapter.js adspirit.js file updated with getWinDimensions * Update adspiritBidAdapter_spec.js Updated Test cases with Native support * Update adspiritBidAdapter.js adspirit.js file updated with screen width and height --------- Co-authored-by: sivamatta94 --- modules/adspiritBidAdapter.js | 261 +++++++-- modules/adspiritBidAdapter.md | 87 ++- test/spec/modules/adspiritBidAdapter_spec.js | 544 ++++++++++++------- 3 files changed, 634 insertions(+), 258 deletions(-) diff --git a/modules/adspiritBidAdapter.js b/modules/adspiritBidAdapter.js index 18325186eb8..b7647a7d491 100644 --- a/modules/adspiritBidAdapter.js +++ b/modules/adspiritBidAdapter.js @@ -1,7 +1,8 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; - +import { getGlobal } from '../src/prebidGlobal.js'; +const { getWinDimensions } = utils; const RTB_URL = '/rtb/getbid.php?rtbprovider=prebid'; const SCRIPT_URL = '/adasync.min.js'; @@ -18,93 +19,251 @@ export const spec = { } return true; }, - + getScriptUrl: function () { + return SCRIPT_URL; + }, buildRequests: function (validBidRequests, bidderRequest) { let requests = []; - const windowDimensions = utils.getWinDimensions(); + let prebidVersion = getGlobal().version; + const win = getWinDimensions(); + for (let i = 0; i < validBidRequests.length; i++) { let bidRequest = validBidRequests[i]; bidRequest.adspiritConId = spec.genAdConId(bidRequest); let reqUrl = spec.getBidderHost(bidRequest); let placementId = utils.getBidIdParameter('placementId', bidRequest.params); - reqUrl = '//' + reqUrl + RTB_URL + '&pid=' + placementId + + + reqUrl = '//' + reqUrl + RTB_URL + + '&pid=' + placementId + '&ref=' + encodeURIComponent(bidderRequest.refererInfo.topmostLocation) + - '&scx=' + (screen.width) + - '&scy=' + (screen.height) + - '&wcx=' + (windowDimensions.innerWidth || windowDimensions.document.documentElement.clientWidth) + - '&wcy=' + (windowDimensions.innerHeight || windowDimensions.document.documentElement.clientHeight) + + '&scx=' + (win.screen?.width || 0) + + '&scy=' + (win.screen?.height || 0) + + '&wcx=' + win.innerWidth + + '&wcy=' + win.innerHeight + '&async=' + bidRequest.adspiritConId + '&t=' + Math.round(Math.random() * 100000); - let data = {}; + let gdprApplies = bidderRequest.gdprConsent ? (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) : 0; + let gdprConsentString = bidderRequest.gdprConsent ? encodeURIComponent(bidderRequest.gdprConsent.consentString) : ''; - if (bidderRequest && bidderRequest.gdprConsent) { - const gdprConsentString = bidderRequest.gdprConsent.consentString; - reqUrl += '&gdpr=' + encodeURIComponent(gdprConsentString); + if (bidderRequest.gdprConsent) { + reqUrl += '&gdpr=' + gdprApplies + '&gdpr_consent=' + gdprConsentString; } - if (bidRequest.schain && bidderRequest.schain) { - data.schain = bidRequest.schain; + let openRTBRequest = { + id: bidderRequest.auctionId, + at: 1, + cur: ['EUR'], + imp: [{ + id: bidRequest.bidId, + bidfloor: bidRequest.params.bidfloor !== undefined ? parseFloat(bidRequest.params.bidfloor) : 0, + bidfloorcur: 'EUR', + secure: 1, + banner: (bidRequest.mediaTypes.banner && bidRequest.mediaTypes.banner.sizes?.length > 0) ? { + format: bidRequest.mediaTypes.banner.sizes.map(size => ({ + w: size[0], + h: size[1] + })) + } : undefined, + native: (bidRequest.mediaTypes.native) ? { + request: JSON.stringify({ + ver: '1.2', + assets: bidRequest.mediaTypes.native.ortb?.assets?.length + ? bidRequest.mediaTypes.native.ortb.assets + : [ + { id: 1, required: 1, title: { len: 100 } }, + { id: 2, required: 1, img: { type: 3, wmin: 1200, hmin: 627, mimes: ['image/png', 'image/gif', 'image/jpeg'] } }, + { id: 4, required: 1, data: {type: 2, len: 150} }, + { id: 3, required: 0, data: {type: 12, len: 50} }, + { id: 6, required: 0, data: {type: 1, len: 50} }, + { id: 5, required: 0, img: { type: 1, wmin: 50, hmin: 50, mimes: ['image/png', 'image/gif', 'image/jpeg'] } } + + ] + }) + } : undefined, + ext: { + placementId: bidRequest.params.placementId + } + }], + + site: { + id: bidRequest.params.siteId || '', + domain: new URL(bidderRequest.refererInfo.topmostLocation).hostname, + page: bidderRequest.refererInfo.topmostLocation, + publisher: { + id: bidRequest.params.publisherId || '', + name: bidRequest.params.publisherName || '' + } + }, + user: { + id: bidRequest.userId || '', + data: bidRequest.userData || [], + ext: { + consent: gdprConsentString || '' + } + }, + device: { + ua: navigator.userAgent, + language: (navigator.language || '').split('-')[0], + w: win.innerWidth, + h: win.innerHeight, + geo: { + lat: bidderRequest?.geo?.lat || 0, + lon: bidderRequest?.geo?.lon || 0, + country: bidderRequest?.geo?.country || '' + } + }, + regs: { + ext: { + gdpr: gdprApplies ? 1 : 0, + gdpr_consent: gdprConsentString || '' + } + }, + ext: { + oat: 1, + prebidVersion: prebidVersion, + adUnitCode: { + prebidVersion: prebidVersion, + code: bidRequest.adUnitCode, + mediaTypes: bidRequest.mediaTypes + } + } + }; + + if (bidRequest.schain) { + openRTBRequest.source = { + ext: { + schain: bidRequest.schain + } + }; } requests.push({ - method: 'GET', + method: 'POST', url: reqUrl, - data: data, + data: JSON.stringify(openRTBRequest), + headers: { 'Content-Type': 'application/json' }, bidRequest: bidRequest }); } + return requests; }, - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; - let bidObj = bidRequest.bidRequest; + const bidObj = bidRequest.bidRequest; + let host = spec.getBidderHost(bidObj); - if (!serverResponse || !serverResponse.body || !bidObj) { - utils.logWarn(`No valid bids from ${spec.code} bidder!`); + if (!serverResponse || !serverResponse.body) { + utils.logWarn(`adspirit: Empty response from bidder`); return []; } - let adData = serverResponse.body; - let cpm = adData.cpm; + if (serverResponse.body.seatbid) { + serverResponse.body.seatbid.forEach(seat => { + seat.bid.forEach(bid => { + const bidResponse = { + requestId: bidObj.bidId, + cpm: bid.price, + width: bid.w || 1, + height: bid.h || 1, + creativeId: bid.crid || bid.impid, + currency: serverResponse.body.cur || 'EUR', + netRevenue: true, + ttl: bid.exp || 300, + meta: { + advertiserDomains: bid.adomain || [] + } + }; - if (!cpm) { - return []; - } + let adm = bid.adm; + if (typeof adm === 'string' && adm.trim().startsWith('{')) { + adm = JSON.parse(adm || '{}'); + if (typeof adm !== 'object') adm = null; + } - let host = spec.getBidderHost(bidObj); + if (adm?.native?.assets) { + const getAssetValue = (id, type) => { + const assetList = adm.native.assets.filter(a => a.id === id); + if (assetList.length === 0) return ''; + return assetList[0][type]?.text || assetList[0][type]?.value || assetList[0][type]?.url || ''; + }; - const bidResponse = { - requestId: bidObj.bidId, - cpm: cpm, - width: adData.w, - height: adData.h, - creativeId: bidObj.params.placementId, - currency: 'EUR', - netRevenue: true, - ttl: 300, - meta: { - advertiserDomains: bidObj && bidObj.adomain ? bidObj.adomain : [] - } - }; - - if ('mediaTypes' in bidObj && 'native' in bidObj.mediaTypes) { - bidResponse.native = { - title: adData.title, - body: adData.body, - cta: adData.cta, - image: { url: adData.image }, - clickUrl: adData.click, - impressionTrackers: [adData.view] - }; - bidResponse.mediaType = NATIVE; + const duplicateTracker = {}; + + bidResponse.native = { + title: getAssetValue(1, 'title'), + body: getAssetValue(4, 'data'), + cta: getAssetValue(3, 'data'), + image: { url: getAssetValue(2, 'img') || '' }, + icon: { url: getAssetValue(5, 'img') || '' }, + sponsoredBy: getAssetValue(6, 'data'), + clickUrl: adm.native.link?.url || '', + impressionTrackers: Array.isArray(adm.native.imptrackers) ? adm.native.imptrackers : [] + }; + + const predefinedAssetIds = Object.entries(bidResponse.native) + .filter(([key, value]) => key !== 'clickUrl' && key !== 'impressionTrackers') + .map(([key, value]) => adm.native.assets.find(asset => + typeof value === 'object' ? value.url === asset?.img?.url : value === asset?.data?.value + )?.id) + .filter(id => id !== undefined); + + adm.native.assets.forEach(asset => { + const type = Object.keys(asset).find(k => k !== 'id'); + + if (!duplicateTracker[asset.id]) { + duplicateTracker[asset.id] = 1; + } else { + duplicateTracker[asset.id]++; + } + + if (predefinedAssetIds.includes(asset.id) && duplicateTracker[asset.id] === 1) return; + + if (type && asset[type]) { + const value = asset[type].text || asset[type].value || asset[type].url || ''; + + if (type === 'img') { + bidResponse.native[`image_${asset.id}_extra${duplicateTracker[asset.id] - 1}`] = { + url: value, width: asset.img.w || null, height: asset.img.h || null + }; + } else { + bidResponse.native[`data_${asset.id}_extra${duplicateTracker[asset.id] - 1}`] = value; + } + } + }); + + bidResponse.mediaType = NATIVE; + } + + bidResponses.push(bidResponse); + }); + }); } else { + let adData = serverResponse.body; + let cpm = adData.cpm; + + if (!cpm) return []; + const bidResponse = { + requestId: bidObj.bidId, + cpm: cpm, + width: adData.w, + height: adData.h, + creativeId: bidObj.params.placementId, + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: adData.adomain || [] + } + }; let adm = '' + adData.adm; bidResponse.ad = adm; bidResponse.mediaType = BANNER; + + bidResponses.push(bidResponse); } - bidResponses.push(bidResponse); return bidResponses; }, getBidderHost: function (bid) { diff --git a/modules/adspiritBidAdapter.md b/modules/adspiritBidAdapter.md index 698ed9b4a0e..ea21dbe70e5 100644 --- a/modules/adspiritBidAdapter.md +++ b/modules/adspiritBidAdapter.md @@ -23,31 +23,74 @@ Each adunit with `adspirit` adapter has to have `placementId` and `host`. ## Sample Banner Ad Unit ```javascript - var adUnits = [ - { - code: 'display-div', - - mediaTypes: { - banner: { - sizes: [[300, 250]] //a display size - } - }, - - bids: [ - { - bidder: "adspirit", - params: { - placementId: '7', //Please enter your placementID - host: 'test.adspirit.de' //your host details from Adspirit - } - } - ] - } - ]; - + var adUnits = [ + // Banner Ad Unit + { + code: 'display-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] // A display size + } + }, + bids: [ + { + bidder: "adspirit", + params: { + placementId: '7', // Please enter your placementID + host: 'test.adspirit.de' // Your host details from Adspirit + } + } + ] + }, + // Native Ad Unit + { + code: 'native-div', + mediaTypes: { + native: { + ortb: { + request: { + ver: "1.2", + assets: [ + { id: 1, required: 1, title: { len: 100 } }, // Title + { id: 2, required: 1, img: { type: 3, wmin: 1200, hmin: 627, mimes: ["image/png", "image/gif", "image/jpeg"] } }, // Main Image + { id: 4, required: 1, data: { type: 2, len: 150 } }, // Body Text + { id: 3, required: 0, data: { type: 12, len:50 } }, // CTA Text + { id: 6, required: 0, data: { type: 1, len:50 } }, // Sponsored By + { id: 5, required: 0, img: { type: 1, wmin: 50, hmin: 50, mimes: ["image/png", "image/gif", "image/jpeg"] } } // Icon Image + ] + } + } + } + }, + bids: [ + { + bidder: 'adspirit', + params: { + placementId: '99', + host: 'test.adspirit.de', + bidfloor: 0.1 + } + } + ] + } +]; ``` +### Short description in five points for native +1. Title (id:1): This is the main heading of the ad, and it should be mandatory with a maximum length of 100 characters. + +2. Main Image (id:2): This is the main image that represents the ad content and should be in PNG, GIF, or JPEG format, with the following dimensions: wmin: 1200 and hmin: 627. + +3. Body Text (id:4): A brief description of the ad. The Body Text should have a maximum length of 150 characters. + +4. CTA (Call to Action) (id:3): A short phrase prompting user action, such as "Shop Now", "Get More Info", etc. + +5. Sponsored By (id:6): The advertiser or brand name promoting the ad. + +6. Click URL: This is the landing page URL where the user will be redirected after clicking the ad. + +In the Adspirit adapter, Title, Main Image, and Body Text are mandatory fields. ### Privacy Policies General Data Protection Regulation(GDPR) is supported by default. diff --git a/test/spec/modules/adspiritBidAdapter_spec.js b/test/spec/modules/adspiritBidAdapter_spec.js index 022a26da60e..e41cf72abf7 100644 --- a/test/spec/modules/adspiritBidAdapter_spec.js +++ b/test/spec/modules/adspiritBidAdapter_spec.js @@ -3,14 +3,15 @@ import { spec } from 'modules/adspiritBidAdapter.js'; import * as utils from 'src/utils.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from 'src/mediaTypes.js'; +const { getWinDimensions } = utils; const RTB_URL = '/rtb/getbid.php?rtbprovider=prebid'; const SCRIPT_URL = '/adasync.min.js'; -describe('Adspirit Bidder Spec', function () { +describe('Adspirit Bidder Spec', function () { // isBidRequestValid ---case describe('isBidRequestValid', function () { it('should return true if the bid request is valid', function () { - const validBid = { bidder: 'adspirit', params: { placementId: '57', host: 'test.adspirit.de' } }; + const validBid = { bidder: 'adspirit', params: { placementId: '99', host: 'test.adspirit.de' } }; const result = spec.isBidRequestValid(validBid); expect(result).to.be.true; }); @@ -41,252 +42,425 @@ describe('Adspirit Bidder Spec', function () { expect(result).to.be.null; }); }); + // getScriptUrl + describe('Adspirit Bid Adapter', function () { + describe('getScriptUrl', function () { + it('should return the correct script URL', function () { + expect(spec.getScriptUrl()).to.equal('/adasync.min.js'); + }); + }); + }); // Test cases for buildRequests - describe('buildRequests', function () { - const bidRequestWithGDPRAndSchain = [ - { - id: '26c1ee0038ac11', - bidder: 'adspirit', - params: { - placementId: '57' - }, - schain: { - ver: '1.0', - nodes: [ - { - asi: 'exchange1.com', - sid: '1234', - hp: 1, - rid: 'bidRequest123', - name: 'Publisher', - domain: 'publisher.com' - }, - { - asi: 'network1.com', - sid: '5678', - hp: 1, - rid: 'bidderRequest123', - name: 'Network', - domain: 'network1.com' - } - ] - } - } - ]; - - const mockBidderRequestWithGDPR = { - refererInfo: { - topmostLocation: 'test.adspirit.de' - }, - gdprConsent: { - gdprApplies: true, - consentString: 'consentString' - }, - schain: { - ver: '1.0', - nodes: [ - { - asi: 'network1.com', - sid: '5678', - hp: 1, - rid: 'bidderRequest123', - name: 'Network', - domain: 'network1.com' - } - ] - } - }; + describe('Adspirit Bidder Spec', function () { + let originalInnerWidth; + let originalInnerHeight; + let originalClientWidth; + let originalClientHeight; - it('should construct valid bid requests with GDPR consent and schain', function () { - const requests = spec.buildRequests(bidRequestWithGDPRAndSchain, mockBidderRequestWithGDPR); - expect(requests).to.be.an('array').that.is.not.empty; - const request = requests[0]; - expect(request.method).to.equal('GET'); - expect(request.url).to.include('test.adspirit.de'); - expect(request.url).to.include('pid=57'); - expect(request.data).to.have.property('schain'); - expect(request.data.schain).to.be.an('object'); - if (request.data.schain && Array.isArray(request.data.schain.nodes)) { - const nodeWithGdpr = request.data.schain.nodes.find(node => node.gdpr); - if (nodeWithGdpr) { - expect(nodeWithGdpr).to.have.property('gdpr'); - expect(nodeWithGdpr.gdpr).to.be.an('object'); - expect(nodeWithGdpr.gdpr).to.have.property('applies', true); - expect(nodeWithGdpr.gdpr).to.have.property('consent', 'consentString'); + beforeEach(() => { + originalInnerWidth = window.innerWidth; + originalInnerHeight = window.innerHeight; + originalClientWidth = document.documentElement.clientWidth; + originalClientHeight = document.documentElement.clientHeight; + + Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1024 }); + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 768 }); + Object.defineProperty(document.documentElement, 'clientWidth', { writable: true, configurable: true, value: 800 }); + Object.defineProperty(document.documentElement, 'clientHeight', { writable: true, configurable: true, value: 600 }); + }); + + afterEach(() => { + Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: originalInnerWidth }); + Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: originalInnerHeight }); + Object.defineProperty(document.documentElement, 'clientWidth', { writable: true, configurable: true, value: originalClientWidth }); + Object.defineProperty(document.documentElement, 'clientHeight', { writable: true, configurable: true, value: originalClientHeight }); + }); + + it('should correctly capture window and document dimensions in payload', function () { + const bidRequest = [ + { + bidId: '26c1ee0038ac11', + bidder: 'adspirit', + params: { placementId: '99', host: 'test.adspirit.de' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } + } } - } + ]; + const mockBidderRequest = { refererInfo: { topmostLocation: 'https://test.adspirit.com' } }; + const requests = spec.buildRequests(bidRequest, mockBidderRequest); + const request = requests[0]; + const requestData = JSON.parse(request.data); }); - it('should construct valid bid requests without GDPR consent and schain', function () { - const bidRequestWithoutGDPR = [ + it('should correctly fall back to document dimensions if window dimensions are not available', function () { + const bidRequest = [ { - id: '26c1ee0038ac11', + bidId: '26c1ee0038ac11', bidder: 'adspirit', - params: { - placementId: '57' + params: { placementId: '99', host: 'test.adspirit.de' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + } + ]; + const mockBidderRequest = { refererInfo: { topmostLocation: 'https://test.adspirit.com' } }; + delete global.window.innerWidth; + delete global.window.innerHeight; + const requests = spec.buildRequests(bidRequest, mockBidderRequest); + const request = requests[0]; + const requestData = JSON.parse(request.data); + }); + it('should correctly add GDPR consent parameters to the request', function () { + const bidRequest = [ + { + bidId: '26c1ee0038ac11', + bidder: 'adspirit', + params: { placementId: '99', host: 'test.adspirit.de' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } } } ]; - const mockBidderRequestWithoutGDPR = { - refererInfo: { - topmostLocation: 'test.adspirit.de' + const mockBidderRequest = { + refererInfo: { topmostLocation: 'https://test.adspirit.com' }, + gdprConsent: { + gdprApplies: true, + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' } }; - const requests = spec.buildRequests(bidRequestWithoutGDPR, mockBidderRequestWithoutGDPR); - expect(requests).to.be.an('array').that.is.not.empty; + const requests = spec.buildRequests(bidRequest, mockBidderRequest); + const request = requests[0]; + expect(request.url).to.include('&gdpr=1'); + expect(request.url).to.include('&gdpr_consent=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'); + const requestData = JSON.parse(request.data); + expect(requestData.regs.ext.gdpr).to.equal(1); + expect(requestData.regs.ext.gdpr_consent).to.equal(mockBidderRequest.gdprConsent.consentString); + }); + + it('should correctly include schain in the OpenRTB request if provided', function () { + const bidRequest = [ + { + bidId: '26c1ee0038ac11', + bidder: 'adspirit', + params: { placementId: '99', host: 'test.adspirit.de' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'publisher.com', + sid: '1234', + hp: 1 + } + ] + } + } + ]; + + const mockBidderRequest = { refererInfo: { topmostLocation: 'https://test.adspirit.com' } }; + const requests = spec.buildRequests(bidRequest, mockBidderRequest); const request = requests[0]; - expect(request.method).to.equal('GET'); - expect(request.url).to.include('test.adspirit.de'); - expect(request.url).to.include('pid=57'); - expect(request.data).to.deep.equal({}); + const requestData = JSON.parse(request.data); + expect(requestData.source).to.exist; + expect(requestData.source.ext).to.exist; + expect(requestData.source.ext.schain).to.deep.equal(bidRequest[0].schain); + }); + it('should correctly handle bidfloor values (valid, missing, and non-numeric)', function () { + const bidRequest = [ + { + bidId: 'validBidfloor', + bidder: 'adspirit', + params: { placementId: '99', host: 'test.adspirit.de', bidfloor: '1.23' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + }, + { + bidId: 'missingBidfloor', + bidder: 'adspirit', + params: { placementId: '100', host: 'test.adspirit.de' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + }, + { + bidId: 'invalidBidfloor', + bidder: 'adspirit', + params: { placementId: '101', host: 'test.adspirit.de', bidfloor: 'abc' }, + adUnitCode: 'banner-div', + mediaTypes: { + banner: { sizes: [[300, 250]] } + } + } + ]; + + const mockBidderRequest = { refererInfo: { topmostLocation: 'https://test.adspirit.com' } }; + const requests = spec.buildRequests(bidRequest, mockBidderRequest); + const requestData = requests.map(req => JSON.parse(req.data)); + expect(requestData[0].imp[0].bidfloor).to.equal(1.23); + expect(requestData[1].imp[0].bidfloor).to.equal(0); + expect(requestData[2].imp[0].bidfloor || 0).to.equal(0); + }); + it('should correctly add and handle banner/native media types', function () { + const bidRequest = [ + { + bidId: 'validBannerNative', + bidder: 'adspirit', + params: { placementId: '99', host: 'test.adspirit.de' }, + adUnitCode: 'test-div', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + native: { + ortb: { + request: { + assets: [{ id: 1, required: 1, title: { len: 100 } }] + } + } + } + } + }, + { + bidId: 'noBanner', + bidder: 'adspirit', + params: { placementId: '100', host: 'test.adspirit.de' }, + adUnitCode: 'no-banner-div', + mediaTypes: { + banner: {} + } + }, + { + bidId: 'emptyNative', + bidder: 'adspirit', + params: { placementId: '101', host: 'test.adspirit.de' }, + adUnitCode: 'empty-native-div', + mediaTypes: { + native: { + ortb: { + request: { + assets: [] + } + } + } + } + } + ]; + + const mockBidderRequest = { refererInfo: { topmostLocation: 'https://test.adspirit.com' } }; + const requests = spec.buildRequests(bidRequest, mockBidderRequest); + const requestData = requests.map(req => JSON.parse(req.data)); + + expect(requestData[0].imp[0]).to.have.property('banner'); + expect(requestData[0].imp[0].banner.format).to.deep.equal([{ w: 300, h: 250 }]); + + expect(requestData[0].imp[0]).to.have.property('native'); + expect(JSON.parse(requestData[0].imp[0].native.request).assets).to.deep.equal([ + { id: 1, required: 1, title: { len: 100 } }, + { id: 2, required: 1, img: { type: 3, wmin: 1200, hmin: 627, mimes: ['image/png', 'image/gif', 'image/jpeg'] } }, + { id: 4, required: 1, data: { type: 2, len: 150 } }, + { id: 3, required: 0, data: { type: 12, len: 50 } }, + { id: 6, required: 0, data: { type: 1, len: 50 } }, + { id: 5, required: 0, img: { type: 1, wmin: 50, hmin: 50, mimes: ['image/png', 'image/gif', 'image/jpeg'] } } + ]); + + expect(requestData[1].imp[0]).to.not.have.property('banner'); + + expect(requestData[2].imp[0]).to.have.property('native'); + expect(JSON.parse(requestData[2].imp[0].native.request).assets).to.deep.equal([ + { id: 1, required: 1, title: { len: 100 } }, + { id: 2, required: 1, img: { type: 3, wmin: 1200, hmin: 627, mimes: ['image/png', 'image/gif', 'image/jpeg'] } }, + { id: 4, required: 1, data: { type: 2, len: 150 } }, + { id: 3, required: 0, data: { type: 12, len: 50 } }, + { id: 6, required: 0, data: { type: 1, len: 50 } }, + { id: 5, required: 0, img: { type: 1, wmin: 50, hmin: 50, mimes: ['image/png', 'image/gif', 'image/jpeg'] } } + ]); }); }); - // interpretResponse For Native + // interpretResponse describe('interpretResponse', function () { - const nativeBidRequestMock = { + const validBidRequestMock = { bidRequest: { bidId: '123456', + bidder: 'adspirit', params: { placementId: '57', adomain: ['test.adspirit.de'] - }, - mediaTypes: { - native: true } } }; - it('should handle native media type bids and missing cpm in the server response body', function () { - const serverResponse = { - body: { - w: 320, - h: 50, - title: 'Ad Title', - body: 'Ad Body', - cta: 'Click Here', - image: 'img_url', - click: 'click_url', - view: 'view_tracker_url' - } - }; + it('should return an empty array when serverResponse is missing', function () { + const result = spec.interpretResponse(null, validBidRequestMock); + expect(result).to.be.an('array').that.is.empty; + }); - const result = spec.interpretResponse(serverResponse, nativeBidRequestMock); - expect(result.length).to.equal(0); + it('should return an empty array when serverResponse.body is missing', function () { + const result = spec.interpretResponse({}, validBidRequestMock); + expect(result).to.be.an('array').that.is.empty; }); - it('should handle native media type bids', function () { + it('should correctly parse a valid banner ad response', function () { const serverResponse = { body: { - cpm: 1.0, - w: 320, - h: 50, - title: 'Ad Title', - body: 'Ad Body', - cta: 'Click Here', - image: 'img_url', - click: 'click_url', - view: 'view_tracker_url' + cpm: 2.0, + w: 728, + h: 90, + adm: '
Banner Ad Content
', + adomain: ['siva.adspirit.de'] } }; - const result = spec.interpretResponse(serverResponse, nativeBidRequestMock); + const result = spec.interpretResponse(serverResponse, validBidRequestMock); expect(result.length).to.equal(1); const bid = result[0]; expect(bid).to.include({ requestId: '123456', - cpm: 1.0, - width: 320, - height: 50, - creativeId: '57', + cpm: 2.0, + width: 728, + height: 90, currency: 'EUR', netRevenue: true, - ttl: 300, - mediaType: 'native' + ttl: 300 }); - expect(bid.native).to.deep.include({ - title: 'Ad Title', - body: 'Ad Body', - cta: 'Click Here', - image: { url: 'img_url' }, - clickUrl: 'click_url', - impressionTrackers: ['view_tracker_url'] - }); - }); - - const bannerBidRequestMock = { - bidRequest: { - bidId: '123456', - params: { - placementId: '57', - adomain: ['siva.adspirit.de'] - }, - mediaTypes: { - banner: true - } - } - }; - - // Test cases for various scenarios - it('should return empty array when serverResponse is missing', function () { - const result = spec.interpretResponse(null, { bidRequest: {} }); - expect(result).to.be.an('array').that.is.empty; - }); - - it('should return empty array when serverResponse.body is missing', function () { - const result = spec.interpretResponse({}, { bidRequest: {} }); - expect(result).to.be.an('array').that.is.empty; - }); - - it('should return empty array when bidObj is missing', function () { - const result = spec.interpretResponse({ body: { cpm: 1.0 } }, { bidRequest: null }); - expect(result).to.be.an('array').that.is.empty; - }); - it('should return empty array when all required parameters are missing', function () { - const result = spec.interpretResponse(null, { bidRequest: null }); - expect(result).to.be.an('array').that.is.empty; + expect(bid).to.have.property('mediaType', 'banner'); + expect(bid.ad).to.include(''); + expect(bid.ad).to.include('
Banner Ad Content
'); }); - it('should handle banner media type bids and missing cpm in the server response body', function () { - const serverResponseBanner = { + it('should return empty array if banner ad response has missing CPM', function () { + const serverResponse = { body: { w: 728, h: 90, adm: '
Ad Content
' } }; - const result = spec.interpretResponse(serverResponseBanner, bannerBidRequestMock); + const result = spec.interpretResponse(serverResponse, validBidRequestMock); expect(result.length).to.equal(0); }); - it('should handle banner media type bids', function () { + it('should correctly handle default values for width, height, creativeId, currency, and advertiserDomains', function () { const serverResponse = { body: { - cpm: 2.0, - w: 728, - h: 90, - adm: '
Ad Content
' + seatbid: [{ + bid: [{ + impid: '123456', + price: 1.8, + crid: undefined, + w: undefined, + h: undefined, + adomain: undefined + }] + }], + cur: undefined } }; - const result = spec.interpretResponse(serverResponse, bannerBidRequestMock); + + const validBidRequestMock = { + bidRequest: { + bidId: '987654', + params: { placementId: '57' } + } + }; + + const result = spec.interpretResponse(serverResponse, validBidRequestMock); expect(result.length).to.equal(1); + const bid = result[0]; - expect(bid).to.include({ - requestId: '123456', - cpm: 2.0, - width: 728, - height: 90, - creativeId: '57', - currency: 'EUR', - netRevenue: true, - ttl: 300, - mediaType: 'banner' + + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + + expect(bid.creativeId).to.equal('123456'); + expect(bid.currency).to.equal('EUR'); + expect(bid.meta.advertiserDomains).to.deep.equal([]); + }); + + it('should correctly parse a valid native ad response, ensuring all assets are loaded dynamically with extra fields', function () { + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: '123456', + price: 1.5, + w: 320, + h: 50, + crid: 'creative789', + adomain: ['test.adspirit.de'], + adm: JSON.stringify({ + native: { + assets: [ + { id: 1, title: { text: 'Primary Title' } }, + { id: 4, data: { value: 'Main Description' } }, + { id: 4, data: { value: 'Extra Description' } }, + { id: 3, data: { value: 'Main CTA' } }, + { id: 3, data: { value: 'Additional CTA' } }, + { id: 2, img: { url: 'https://example.com/main-image.jpg', w: 100, h: 100 } }, + { id: 2, img: { url: 'https://example.com/extra-image.jpg', w: 200, h: 200 } }, + { id: 5, img: { url: 'https://example.com/icon-main.jpg', w: 50, h: 50 } }, + { id: 5, img: { url: 'https://example.com/icon-extra.jpg', w: 60, h: 60 } }, + { id: 6, data: { value: 'Main Sponsor' } }, + { id: 6, data: { value: 'Secondary Sponsor' } } + ], + link: { url: 'https://clickurl.com' }, + imptrackers: ['https://tracker.com/impression'] + } + }) + }] + }], + cur: 'EUR' + } + }; + + const validBidRequestMock = { + bidRequest: { + bidId: '987654', + params: { placementId: '57' } + } + }; + + const result = spec.interpretResponse(serverResponse, validBidRequestMock); + expect(result.length).to.equal(1); + + const bid = result[0]; + + expect(bid.native.title).to.equal('Primary Title'); + expect(bid.native.body).to.equal('Main Description'); + expect(bid.native['data_4_extra1']).to.equal('Extra Description'); + + expect(bid.native.cta).to.equal('Main CTA'); + expect(bid.native['data_3_extra1']).to.equal('Additional CTA'); + + expect(bid.native.sponsoredBy).to.equal('Main Sponsor'); + expect(bid.native['data_6_extra1']).to.equal('Secondary Sponsor'); + expect(bid.native.image.url).to.equal('https://example.com/main-image.jpg'); + expect(bid.native['image_2_extra1']).to.deep.equal({ + url: 'https://example.com/extra-image.jpg', + width: 200, + height: 200 + }); + + expect(bid.native.icon.url).to.equal('https://example.com/icon-main.jpg'); + expect(bid.native['image_5_extra1']).to.deep.equal({ + url: 'https://example.com/icon-extra.jpg', + width: 60, + height: 60 }); - expect(bid.ad).to.equal('
Ad Content
'); + expect(bid.native.impressionTrackers).to.deep.equal(['https://tracker.com/impression']); }); }); }); From 00267f157e6dfd133927d5d514128435ab161ab0 Mon Sep 17 00:00:00 2001 From: Alexandr Kim <47887567+alexandr-kim-vl@users.noreply.github.com> Date: Fri, 11 Apr 2025 00:42:03 +0500 Subject: [PATCH 1083/1097] semantiqRtdProvider: avoid adding default company ID if companyId parameter is present (#12985) Co-authored-by: Alexandr Kim --- modules/semantiqRtdProvider.js | 8 ++------ test/spec/modules/semantiqRtdProvider_spec.js | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/semantiqRtdProvider.js b/modules/semantiqRtdProvider.js index dd79ac3c96f..6f9d4cb6487 100644 --- a/modules/semantiqRtdProvider.js +++ b/modules/semantiqRtdProvider.js @@ -15,7 +15,7 @@ const LOG_PREFIX = '[SemantIQ RTD Module]: '; const KEYWORDS_URL = 'https://api.adnz.co/api/ws-semantiq/page-keywords'; const STORAGE_KEY = `adnz_${SUBMODULE_NAME}`; const AUDIENZZ_COMPANY_ID = 1; -const REQUIRED_TENANT_IDS = [AUDIENZZ_COMPANY_ID]; +const FALLBACK_TENANT_IDS = [AUDIENZZ_COMPANY_ID]; const AUDIENZZ_GLOBAL_VENDOR_ID = 783; const DEFAULT_TIMEOUT = 1000; @@ -58,13 +58,9 @@ const getPageUrl = () => getWindowLocation().href; * @returns {number[]} */ const getTenantIds = (companyId) => { - if (!companyId) { - return REQUIRED_TENANT_IDS; - } - const companyIdArray = Array.isArray(companyId) ? companyId : [companyId]; - return [...REQUIRED_TENANT_IDS, ...companyIdArray]; + return companyIdArray.filter(Boolean).length ? companyIdArray : FALLBACK_TENANT_IDS; }; /** diff --git a/test/spec/modules/semantiqRtdProvider_spec.js b/test/spec/modules/semantiqRtdProvider_spec.js index 16f82b8c274..c13c2817167 100644 --- a/test/spec/modules/semantiqRtdProvider_spec.js +++ b/test/spec/modules/semantiqRtdProvider_spec.js @@ -106,7 +106,7 @@ describe('semantiqRtdProvider', () => { const requestUrl = new URL(server.requests[0].url); - expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('1,13'); + expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('13'); }); it('allows to specify multiple company IDs as a parameter', async () => { @@ -136,7 +136,7 @@ describe('semantiqRtdProvider', () => { const requestUrl = new URL(server.requests[0].url); - expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('1,13,23'); + expect(requestUrl.searchParams.get('tenantIds')).to.be.equal('13,23'); }); it('gets keywords from the cache if the data is present in the storage', async () => { From 7fb331e7d9de6da568ced864d5cd954daac07c66 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Fri, 11 Apr 2025 03:09:55 -0700 Subject: [PATCH 1084/1097] Previous auction module: added new highestBidCurrency field to payloads (#12988) * added new highestBidCurrency field to prev auct info payloads * changed bidderErrorCode to rejectionReason --- modules/previousAuctionInfo/index.js | 3 +- test/spec/modules/previousAuctionInfo_spec.js | 43 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/previousAuctionInfo/index.js b/modules/previousAuctionInfo/index.js index 68960ea8611..3846a46812a 100644 --- a/modules/previousAuctionInfo/index.js +++ b/modules/previousAuctionInfo/index.js @@ -87,11 +87,12 @@ export const onAuctionEndHandler = (auctionDetails) => { source: 'pbjs', adUnitCode: bid.adUnitCode, highestBidCpm: highestBidsByAdUnitCode[bid.adUnitCode]?.cpm || null, + highestBidCurrency: highestBidsByAdUnitCode[bid.adUnitCode]?.currency || null, bidderCpm: receivedBidsMap[bid.bidId]?.cpm || null, bidderOriginalCpm: receivedBidsMap[bid.bidId]?.originalCpm || null, bidderCurrency: receivedBidsMap[bid.bidId]?.currency || null, bidderOriginalCurrency: receivedBidsMap[bid.bidId]?.originalCurrency || null, - bidderErrorCode: rejectedBidsMap[bid.bidId]?.rejectionReason || null, + rejectionReason: rejectedBidsMap[bid.bidId]?.rejectionReason || null, timestamp: auctionDetails.timestamp, transactionId: bid.transactionId, // this field gets removed before injecting previous auction info into the bid stream } diff --git a/test/spec/modules/previousAuctionInfo_spec.js b/test/spec/modules/previousAuctionInfo_spec.js index 159b10a77d3..d1003c0082a 100644 --- a/test/spec/modules/previousAuctionInfo_spec.js +++ b/test/spec/modules/previousAuctionInfo_spec.js @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; import * as events from 'src/events.js'; import {CONFIG_NS, resetPreviousAuctionInfo, startAuctionHook} from '../../../modules/previousAuctionInfo'; +import { REJECTION_REASON } from '../../../src/constants.js'; describe('previous auction info', () => { let sandbox; @@ -90,11 +91,12 @@ describe('previous auction info', () => { source: 'pbjs', adUnitCode: 'adUnit1', highestBidCpm: 2, + highestBidCurrency: 'EUR', bidderCpm: 2, bidderOriginalCpm: 2.1, bidderCurrency: 'EUR', bidderOriginalCurrency: 'EUR', - bidderErrorCode: null, + rejectionReason: null, timestamp: auctionDetails.timestamp }); }); @@ -109,17 +111,21 @@ describe('previous auction info', () => { expect(previousAuctionInfo.auctionState['testBidder1'][0]).to.include({ bidId: 'bid123', highestBidCpm: 2, + highestBidCurrency: 'EUR', adUnitCode: 'adUnit1', bidderCpm: 1, - bidderCurrency: 'USD' + bidderCurrency: 'USD', + rejectionReason: null, }); expect(previousAuctionInfo.auctionState['testBidder3'][0]).to.include({ bidId: 'bidxyz', highestBidCpm: 3, + highestBidCurrency: 'USD', adUnitCode: 'adUnit2', bidderCpm: 3, - bidderCurrency: 'USD' + bidderCurrency: 'USD', + rejectionReason: null, }); }); @@ -130,6 +136,37 @@ describe('previous auction info', () => { expect(previousAuctionInfo.auctionState).to.have.property('testBidder1'); expect(previousAuctionInfo.auctionState).to.not.have.property('testBidder2'); }); + + it('should include rejectionReason string if the bid was rejected', () => { + const auctionDetailsWithRejectedBid = { + auctionId: 'auctionXYZ', + bidsReceived: [], + bidsRejected: [ + { requestId: 'bid456', rejectionReason: REJECTION_REASON.FLOOR_NOT_MET } // string from REJECTION_REASON + ], + bidderRequests: [ + { + bidderCode: 'testBidder1', + bidderRequestId: 'req1', + bids: [ + { bidId: 'bid456', adUnitCode: 'adUnit1' } + ] + } + ], + timestamp: Date.now(), + }; + + config.setConfig({ [CONFIG_NS]: { enabled: true, bidders: ['testBidder1'] } }); + previousAuctionInfo.onAuctionEndHandler(auctionDetailsWithRejectedBid); + + const stored = previousAuctionInfo.auctionState['testBidder1'][0]; + expect(stored).to.include({ + bidId: 'bid456', + rejectionReason: REJECTION_REASON.FLOOR_NOT_MET, + bidderCpm: null, + highestBidCpm: null + }); + }); }); describe('startAuctionHook', () => { From c034daad413b04b45aaff286ef223c7c074ca42b Mon Sep 17 00:00:00 2001 From: Snigel <108489367+snigelweb@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:38:51 +0200 Subject: [PATCH 1085/1097] Snigel Bid Adapter: delegate consent-related checks to user sync iframe (#12990) --- modules/snigelBidAdapter.js | 26 +++-------- test/spec/modules/snigelBidAdapter_spec.js | 50 ++++++++++++---------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index cccd809ad8b..0f6d8d29e4c 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -2,7 +2,6 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject, inIframe, getDNT, generateUUID} from '../src/utils.js'; -import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {getStorageManager} from '../src/storageManager.js'; import { getViewportSize } from '../libraries/viewport/viewport.js'; @@ -110,8 +109,8 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { const syncUrl = getSyncUrl(responses || []); - if (syncUrl && syncOptions.iframeEnabled && hasSyncConsent(gdprConsent, uspConsent, gppConsent)) { - return [{type: 'iframe', url: getSyncEndpoint(syncUrl, gdprConsent)}]; + if (syncUrl && syncOptions.iframeEnabled) { + return [{type: 'iframe', url: getSyncEndpoint(syncUrl, gdprConsent, uspConsent, gppConsent)}]; } }, }; @@ -202,21 +201,6 @@ function mapIdToRequestId(id, bidRequest) { return bidRequest.bidderRequest.bids.filter((bid) => bid.adUnitCode === id)[0].bidId; } -function hasUspConsent(uspConsent) { - return typeof uspConsent !== 'string' || !(uspConsent[0] === '1' && uspConsent[2] === 'Y'); -} - -function hasGppConsent(gppConsent) { - return ( - !(gppConsent && Array.isArray(gppConsent.applicableSections)) || - gppConsent.applicableSections.every((section) => typeof section === 'number' && section <= 5) - ); -} - -function hasSyncConsent(gdprConsent, uspConsent, gppConsent) { - return hasPurpose1Consent(gdprConsent) && hasUspConsent(uspConsent) && hasGppConsent(gppConsent); -} - function hasFullGdprConsent(gdprConsent) { try { const purposeConsents = Object.values(gdprConsent.vendorData.purpose.consents); @@ -234,10 +218,12 @@ function getSyncUrl(responses) { return getConfig(`${BIDDER_CODE}.syncUrl`) || deepAccess(responses[0], 'body.syncUrl'); } -function getSyncEndpoint(url, gdprConsent) { +function getSyncEndpoint(url, gdprConsent, uspConsent, gppConsent) { return `${url}?gdpr=${gdprConsent?.gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent( gdprConsent?.consentString || '' - )}`; + )}&gpp_sid=${gppConsent?.applicableSections?.join(',') || ''}&gpp=${encodeURIComponent( + gppConsent?.gppString || '' + )}&us_privacy=${uspConsent || ''}`; } function getSessionId() { diff --git a/test/spec/modules/snigelBidAdapter_spec.js b/test/spec/modules/snigelBidAdapter_spec.js index 69ab85ba825..faeba529abe 100644 --- a/test/spec/modules/snigelBidAdapter_spec.js +++ b/test/spec/modules/snigelBidAdapter_spec.js @@ -36,6 +36,7 @@ const makeBidderRequest = function (overrides) { const DUMMY_USP_CONSENT = '1YYN'; const DUMMY_GDPR_CONSENT_STRING = 'BOSSotLOSSotLAPABAENBc-AAAAgR7_______9______9uz_Gv_v_f__33e8__9v_l_7_-___u_-33d4-_1vX99yfm1-7ftr3tp_86ues2_XqK_9oIiA'; +const DUMMY_GPP_CONSENT_STRING = 'DBABrw~BAAAAAAAAABA.QA~BAAAAABA.QA'; describe('snigelBidAdapter', function () { describe('isBidRequestValid', function () { @@ -310,7 +311,7 @@ describe('snigelBidAdapter', function () { expect(syncs).to.be.undefined; }); - it('should not return any user syncs if GDPR applies and the user did not consent to purpose one', function () { + it("should return an iframe specific to the publisher's property if all conditions are met", function () { const response = { body: { id: BASE_BIDDER_REQUEST.bidderRequestId, @@ -323,19 +324,19 @@ describe('snigelBidAdapter', function () { iframeEnabled: true, }; const gdprConsent = { - gdprApplies: true, - vendorData: { - purpose: { - consents: {1: false}, - }, - }, + gdprApplies: false, }; const syncs = spec.getUserSyncs(syncOptions, [response], gdprConsent); - expect(syncs).to.be.undefined; + expect(syncs).to.be.an('array').and.of.length(1); + const sync = syncs[0]; + expect(sync).to.have.property('type'); + expect(sync.type).to.equal('iframe'); + expect(sync).to.have.property('url'); + expect(sync.url).to.equal('https://somesyncurl?gdpr=0&gdpr_consent=&gpp_sid=&gpp=&us_privacy='); }); - it("should return an iframe specific to the publisher's property if all conditions are met", function () { + it('should pass GDPR applicability and consent string as query parameters', function () { const response = { body: { id: BASE_BIDDER_REQUEST.bidderRequestId, @@ -348,7 +349,13 @@ describe('snigelBidAdapter', function () { iframeEnabled: true, }; const gdprConsent = { - gdprApplies: false, + gdprApplies: true, + consentString: DUMMY_GDPR_CONSENT_STRING, + vendorData: { + purpose: { + consents: {1: true}, + }, + }, }; const syncs = spec.getUserSyncs(syncOptions, [response], gdprConsent); @@ -357,10 +364,12 @@ describe('snigelBidAdapter', function () { expect(sync).to.have.property('type'); expect(sync.type).to.equal('iframe'); expect(sync).to.have.property('url'); - expect(sync.url).to.equal('https://somesyncurl?gdpr=0&gdpr_consent='); + expect(sync.url).to.equal( + `https://somesyncurl?gdpr=1&gdpr_consent=${DUMMY_GDPR_CONSENT_STRING}&gpp_sid=&gpp=&us_privacy=` + ); }); - it('should pass GDPR applicability and consent string as query parameters', function () { + it('should pass GPP section IDs and consent string as query parameters', function () { const response = { body: { id: BASE_BIDDER_REQUEST.bidderRequestId, @@ -372,23 +381,20 @@ describe('snigelBidAdapter', function () { const syncOptions = { iframeEnabled: true, }; - const gdprConsent = { - gdprApplies: true, - consentString: DUMMY_GDPR_CONSENT_STRING, - vendorData: { - purpose: { - consents: {1: true}, - }, - }, + const gppConsent = { + applicableSections: [7, 8], + gppString: DUMMY_GPP_CONSENT_STRING, }; - const syncs = spec.getUserSyncs(syncOptions, [response], gdprConsent); + const syncs = spec.getUserSyncs(syncOptions, [response], undefined, undefined, gppConsent); expect(syncs).to.be.an('array').and.of.length(1); const sync = syncs[0]; expect(sync).to.have.property('type'); expect(sync.type).to.equal('iframe'); expect(sync).to.have.property('url'); - expect(sync.url).to.equal(`https://somesyncurl?gdpr=1&gdpr_consent=${DUMMY_GDPR_CONSENT_STRING}`); + expect(sync.url).to.equal( + `https://somesyncurl?gdpr=0&gdpr_consent=&gpp_sid=7,8&gpp=${DUMMY_GPP_CONSENT_STRING}&us_privacy=` + ); }); it('should omit session ID if no device access', function () { From a55f2ac1badb974996141f81305984d7eb9f7583 Mon Sep 17 00:00:00 2001 From: Alexandr Kim <47887567+alexandr-kim-vl@users.noreply.github.com> Date: Sat, 12 Apr 2025 00:51:46 +0500 Subject: [PATCH 1086/1097] semantiqRtdProvider: dispatch page impression event on initialization (#12989) Co-authored-by: Alexandr Kim --- modules/semantiqRtdProvider.js | 58 +++++++++++++++---- test/spec/modules/semantiqRtdProvider_spec.js | 43 +++++++++++++- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/modules/semantiqRtdProvider.js b/modules/semantiqRtdProvider.js index 6f9d4cb6487..5a46b019cc0 100644 --- a/modules/semantiqRtdProvider.js +++ b/modules/semantiqRtdProvider.js @@ -1,8 +1,8 @@ import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; -import { ajax } from '../src/ajax.js'; +import { ajax, fetch } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; -import { getWindowLocation, logError, logInfo, logWarn, mergeDeep } from '../src/utils.js'; +import { generateUUID, getWindowLocation, logError, logInfo, logWarn, mergeDeep } from '../src/utils.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -10,14 +10,13 @@ import { getWindowLocation, logError, logInfo, logWarn, mergeDeep } from '../src const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'semantiq'; - const LOG_PREFIX = '[SemantIQ RTD Module]: '; const KEYWORDS_URL = 'https://api.adnz.co/api/ws-semantiq/page-keywords'; +const EVENT_COLLECTOR_URL = 'https://api.adnz.co/api/ws-clickstream-collector/submit'; const STORAGE_KEY = `adnz_${SUBMODULE_NAME}`; const AUDIENZZ_COMPANY_ID = 1; const FALLBACK_TENANT_IDS = [AUDIENZZ_COMPANY_ID]; const AUDIENZZ_GLOBAL_VENDOR_ID = 783; - const DEFAULT_TIMEOUT = 1000; export const storage = getStorageManager({ @@ -53,11 +52,12 @@ const getStorageKeywords = (pageUrl) => { const getPageUrl = () => getWindowLocation().href; /** - * Gets tenant IDs based on the customer company ID - * @param {number | number[] | undefined} companyId + * Gets tenant IDs based on the module params + * @param {Object} params * @returns {number[]} */ -const getTenantIds = (companyId) => { +const getTenantIds = (params = {}) => { + const { companyId } = params; const companyIdArray = Array.isArray(companyId) ? companyId : [companyId]; return companyIdArray.filter(Boolean).length ? companyIdArray : FALLBACK_TENANT_IDS; @@ -76,8 +76,7 @@ const getKeywords = (params) => new Promise((resolve, reject) => { return resolve(storageKeywords); } - const { companyId } = params; - const tenantIds = getTenantIds(companyId); + const tenantIds = getTenantIds(params); const searchParams = new URLSearchParams(); searchParams.append('url', pageUrl); @@ -142,13 +141,52 @@ export const getOrtbKeywords = (keywords) => Object.entries(keywords).reduce((ac return ortbKeywordString ? [...acc, ortbKeywordString] : acc; }, []).join(','); +/** + * Dispatches a page impression event to the SemantIQ service. + * + * @param {number} companyId + * @returns {Promise} + */ +const dispatchPageImpressionEvent = (companyId) => { + window.audienzz = window.audienzz || {}; + window.audienzz.collectorPageImpressionId = window.audienzz.collectorPageImpressionId || generateUUID(); + const pageImpressionId = window.audienzz.collectorPageImpressionId; + const pageUrl = getPageUrl(); + + const payload = { + company_id: companyId, + event_id: generateUUID(), + event_timestamp: new Date().toISOString(), + event_type: 'pageImpression', + page_impression_id: pageImpressionId, + source: 'semantiqPrebidModule', + url: pageUrl, + }; + + return fetch(EVENT_COLLECTOR_URL, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, + keepalive: true, + }); +}; + /** * Module init * @param {Object} config * @param {Object} userConsent * @return {boolean} */ -const init = (config, userConsent) => true; +const init = (config, userConsent) => { + const { params = {} } = config; + const [mainCompanyId] = getTenantIds(params); + + dispatchPageImpressionEvent(mainCompanyId); + + return true; +}; /** * Receives real-time data from SemantIQ service. diff --git a/test/spec/modules/semantiqRtdProvider_spec.js b/test/spec/modules/semantiqRtdProvider_spec.js index c13c2817167..d9fd0098273 100644 --- a/test/spec/modules/semantiqRtdProvider_spec.js +++ b/test/spec/modules/semantiqRtdProvider_spec.js @@ -22,11 +22,52 @@ describe('semantiqRtdProvider', () => { describe('init', () => { it('returns true on initialization', () => { - const initResult = semantiqRtdSubmodule.init(); + const initResult = semantiqRtdSubmodule.init({}); expect(initResult).to.be.true; }); }); + describe('pageImpression event', () => { + it('dispatches an event on initialization', () => { + getWindowLocationStub.returns(new URL('https://example.com/article')); + + semantiqRtdSubmodule.init({ params: { companyId: 5 } }); + + const body = JSON.parse(server.requests[0].requestBody); + + expect(server.requests[0].url).to.be.equal('https://api.adnz.co/api/ws-clickstream-collector/submit'); + expect(server.requests[0].method).to.be.equal('POST'); + + expect(body.company_id).to.be.equal(5); + expect(body.event_id).not.to.be.empty; + expect(body.event_timestamp).not.to.be.empty; + expect(body.event_type).to.be.equal('pageImpression'); + expect(body.page_impression_id).not.to.be.empty; + expect(body.source).to.be.equal('semantiqPrebidModule'); + expect(body.url).to.be.equal('https://example.com/article'); + }); + + it('uses the correct company ID', () => { + semantiqRtdSubmodule.init({ params: { companyId: 555 } }); + semantiqRtdSubmodule.init({ params: { companyId: [111, 222, 333] } }); + + const body1 = JSON.parse(server.requests[0].requestBody); + const body2 = JSON.parse(server.requests[1].requestBody); + + expect(body1.company_id).to.be.equal(555); + expect(body2.company_id).to.be.equal(111); + }); + + it('uses cached page impression ID if present', () => { + window.audienzz = { collectorPageImpressionId: 'cached-guid' }; + semantiqRtdSubmodule.init({ params: { companyId: 5 } }); + + const body = JSON.parse(server.requests[0].requestBody); + + expect(body.page_impression_id).to.be.equal('cached-guid'); + }); + }); + describe('convertSemantiqKeywordToOrtb', () => { it('converts SemantIQ keywords properly', () => { expect(convertSemantiqKeywordToOrtb('foo', 'bar')).to.be.equal('foo=bar'); From 253cec87c9c8856d3ea1b5723e49562a6a61c59f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 11 Apr 2025 12:52:21 -0700 Subject: [PATCH 1087/1097] PAAPI: support `createAuctionNonce` and `getInterestGroupAdAuctionData` (#12839) * PAAPI: support for auction nonces in buildPAAPIConfigs * core: allow async processing around buildRequests * PAAPI: respect greedyPromise in parallelPaapiProcessing * PAAPI: resolve nonces on adapter request & response * resolve auction nonces * Use async hook for processBidderRequest (https://github.com/snapwich/fun-hooks/issues/42) * lint * remove superfluous setTimeotu * revert changes * PAAPI: introduce paapiParameters API * remove unnecessary postBuildRequests * add support for getInterestGroupAdAuctionData * Fix unnecessary this aliasing * add serverResponse to async signals --- modules/paapi.js | 76 +++- test/spec/auctionmanager_spec.js | 662 +++++++++++++++---------------- test/spec/modules/paapi_spec.js | 117 +++++- 3 files changed, 504 insertions(+), 351 deletions(-) diff --git a/modules/paapi.js b/modules/paapi.js index 0f1144691fc..94e720039f8 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -21,7 +21,7 @@ import {keyCompare, maximum, minimum} from '../src/utils/reducers.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {auctionStore} from '../libraries/weakStore/weakStore.js'; import {adapterMetrics, guardTids} from '../src/adapters/bidderFactory.js'; -import {defer} from '../src/utils/promise.js'; +import {defer, PbPromise} from '../src/utils/promise.js'; import {auctionManager} from '../src/auctionManager.js'; const MODULE = 'PAAPI'; @@ -83,7 +83,9 @@ function attachHandlers() { getHook('addPaapiConfig').before(addPaapiConfigHook); getHook('makeBidRequests').before(addPaapiData); getHook('makeBidRequests').after(markForFledge); - getHook('processBidderRequests').before(parallelPaapiProcessing); + getHook('processBidderRequests').before(parallelPaapiProcessing, 9); + // resolve params before parallel processing + getHook('processBidderRequests').before(buildPAAPIParams, 10); getHook('processBidderRequests').before(adAuctionHeadersHook); events.on(EVENTS.AUCTION_INIT, onAuctionInit); events.on(EVENTS.AUCTION_END, onAuctionEnd); @@ -94,6 +96,7 @@ function detachHandlers() { getHook('makeBidRequests').getHooks({hook: addPaapiData}).remove(); getHook('makeBidRequests').getHooks({hook: markForFledge}).remove(); getHook('processBidderRequests').getHooks({hook: parallelPaapiProcessing}).remove(); + getHook('processBidderRequests').getHooks({hook: buildPAAPIParams}).remove(); getHook('processBidderRequests').getHooks({hook: adAuctionHeadersHook}).remove(); events.off(EVENTS.AUCTION_INIT, onAuctionInit); events.off(EVENTS.AUCTION_END, onAuctionEnd); @@ -494,6 +497,8 @@ export function addPaapiData(next, adUnits, ...args) { next(adUnits, ...args); } +export const NAVIGATOR_APIS = ['createAuctionNonce', 'getInterestGroupAdAuctionData']; + export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { bidderRequests.forEach((bidderReq) => { @@ -504,12 +509,26 @@ export function markForFledge(next, bidderRequests) { componentSeller: !!moduleConfig.componentSeller?.auctionConfig } }); + if (enabled) { + NAVIGATOR_APIS.forEach(method => { + bidderReq.paapi[method] = (...args) => new AsyncPAAPIParam(() => navigator[method](...args)) + }) + } }); } next(bidderRequests); } -export const ASYNC_SIGNALS = ['auctionSignals', 'sellerSignals', 'perBuyerSignals', 'perBuyerTimeouts', 'directFromSellerSignals', 'perBuyerCurrencies', 'perBuyerCumulativeTimeouts']; +export const ASYNC_SIGNALS = [ + 'auctionSignals', + 'sellerSignals', + 'perBuyerSignals', + 'perBuyerTimeouts', + 'directFromSellerSignals', + 'perBuyerCurrencies', + 'perBuyerCumulativeTimeouts', + 'serverResponse' +]; const validatePartialConfig = (() => { const REQUIRED_SYNC_SIGNALS = [ @@ -537,6 +556,20 @@ const validatePartialConfig = (() => { } })() +function callAdapterApi(spec, method, bids, bidderRequest) { + const metrics = adapterMetrics(bidderRequest); + const tidGuard = guardTids(bidderRequest); + let result; + metrics.measureTime(method, () => { + try { + result = spec[method](bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest)) + } catch (e) { + logError(`Error invoking "${method}":`, e); + } + }); + return result; +} + /** * Adapters can provide a `spec.buildPAAPIConfigs(validBidRequests, bidderRequest)` to be included in PAAPI auctions * that can be started in parallel with contextual auctions. @@ -579,16 +612,7 @@ export function parallelPaapiProcessing(next, spec, bids, bidderRequest, ...args }); if (enabled && spec.buildPAAPIConfigs) { - const metrics = adapterMetrics(bidderRequest); - const tidGuard = guardTids(bidderRequest); - let partialConfigs; - metrics.measureTime('buildPAAPIConfigs', () => { - try { - partialConfigs = spec.buildPAAPIConfigs(bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest)) - } catch (e) { - logError(`Error invoking "buildPAAPIConfigs":`, e); - } - }); + const partialConfigs = callAdapterApi(spec, 'buildPAAPIConfigs', bids, bidderRequest) const requestsById = Object.fromEntries(bids.map(bid => [bid.bidId, bid])); (partialConfigs ?? []).forEach(({bidId, config, igb}) => { const bidRequest = requestsById.hasOwnProperty(bidId) && requestsById[bidId]; @@ -671,6 +695,32 @@ export function parallelPaapiProcessing(next, spec, bids, bidderRequest, ...args return next.call(this, spec, bids, bidderRequest, ...args); } +export class AsyncPAAPIParam { + constructor(resolve) { + this.resolve = resolve; + } +} + +export function buildPAAPIParams(next, spec, bids, bidderRequest, ...args) { + if (bidderRequest.paapi?.enabled && spec.paapiParameters) { + const params = callAdapterApi(spec, 'paapiParameters', bids, bidderRequest); + return PbPromise.all( + Object.entries(params ?? {}).map(([key, value]) => + value instanceof AsyncPAAPIParam + ? value.resolve().then(result => [key, result]) + : Promise.resolve([key, value])) + ).then(resolved => { + bidderRequest.paapi.params = Object.fromEntries(resolved); + }).catch(err => { + logError(`Could not resolve PAAPI parameters`, err); + }).then(() => { + next.call(this, spec, bids, bidderRequest, ...args); + }) + } else { + next.call(this, spec, bids, bidderRequest, ...args); + } +} + export function onAuctionInit({auctionId}) { if (moduleConfig.parallel) { auctionManager.index.getAuction({auctionId}).requestsDone.then(() => { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index e2da420c939..40c25df0089 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -290,71 +290,71 @@ describe('auctionmanager.js', function () { it('Custom configuration for all bidders', function () { $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - // change default here - return bidResponse.pbHg; - } - }, { - key: TARGETING_KEYS.SIZE, - val: function (bidResponse) { - return bidResponse.size; - } - }, - { - key: TARGETING_KEYS.SOURCE, - val: function (bidResponse) { - return bidResponse.source; - } - }, - { - key: TARGETING_KEYS.FORMAT, - val: function (bidResponse) { - return bidResponse.mediaType; - } - }, - { - key: TARGETING_KEYS.ADOMAIN, - val: function (bidResponse) { - return bidResponse.meta.advertiserDomains[0]; - } - }, - { - key: TARGETING_KEYS.CRID, - val: function (bidResponse) { - return bidResponse.creativeId; - } - }, - { - key: TARGETING_KEYS.DSP, - val: function (bidResponse) { - return bidResponse.meta.networkId; - } - }, - { - key: TARGETING_KEYS.ACAT, - val: function (bidResponse) { - return bidResponse.meta.primaryCatId; + { + standard: { + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + // change default here + return bidResponse.pbHg; + } + }, { + key: TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } + }, + { + key: TARGETING_KEYS.SOURCE, + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: TARGETING_KEYS.FORMAT, + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + { + key: TARGETING_KEYS.ADOMAIN, + val: function (bidResponse) { + return bidResponse.meta.advertiserDomains[0]; + } + }, + { + key: TARGETING_KEYS.CRID, + val: function (bidResponse) { + return bidResponse.creativeId; + } + }, + { + key: TARGETING_KEYS.DSP, + val: function (bidResponse) { + return bidResponse.meta.networkId; + } + }, + { + key: TARGETING_KEYS.ACAT, + val: function (bidResponse) { + return bidResponse.meta.primaryCatId; + } } - } - ] + ] - } - }; + } + }; var expected = getDefaultExpected(bid); expected[TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg; @@ -375,82 +375,82 @@ describe('auctionmanager.js', function () { videoBid.videoCacheKey = 'abc123def'; $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - return bidResponse.pbMg; - } - }, { - key: TARGETING_KEYS.SIZE, - val: function (bidResponse) { - return bidResponse.size; - } - }, - { - key: TARGETING_KEYS.SOURCE, - val: function (bidResponse) { - return bidResponse.source; - } - }, - { - key: TARGETING_KEYS.FORMAT, - val: function (bidResponse) { - return bidResponse.mediaType; - } - }, - { - key: TARGETING_KEYS.UUID, - val: function (bidResponse) { - return bidResponse.videoCacheKey; - } - }, - { - key: TARGETING_KEYS.CACHE_ID, - val: function (bidResponse) { - return bidResponse.videoCacheKey; - } - }, - { - key: TARGETING_KEYS.ADOMAIN, - val: function (bidResponse) { - return bidResponse.meta.advertiserDomains[0]; - } - }, - { - key: TARGETING_KEYS.CRID, - val: function (bidResponse) { - return bidResponse.creativeId; - } - }, - { - key: TARGETING_KEYS.DSP, - val: function (bidResponse) { - return bidResponse.meta.networkId; - } - }, - { - key: TARGETING_KEYS.ACAT, - val: function (bidResponse) { - return bidResponse.meta.primaryCatId; - } - } - ] + { + standard: { + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + return bidResponse.pbMg; + } + }, { + key: TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } + }, + { + key: TARGETING_KEYS.SOURCE, + val: function (bidResponse) { + return bidResponse.source; + } + }, + { + key: TARGETING_KEYS.FORMAT, + val: function (bidResponse) { + return bidResponse.mediaType; + } + }, + { + key: TARGETING_KEYS.UUID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + }, + { + key: TARGETING_KEYS.CACHE_ID, + val: function (bidResponse) { + return bidResponse.videoCacheKey; + } + }, + { + key: TARGETING_KEYS.ADOMAIN, + val: function (bidResponse) { + return bidResponse.meta.advertiserDomains[0]; + } + }, + { + key: TARGETING_KEYS.CRID, + val: function (bidResponse) { + return bidResponse.creativeId; + } + }, + { + key: TARGETING_KEYS.DSP, + val: function (bidResponse) { + return bidResponse.meta.networkId; + } + }, + { + key: TARGETING_KEYS.ACAT, + val: function (bidResponse) { + return bidResponse.meta.primaryCatId; + } + } + ] - } - }; + } + }; let expected = getDefaultExpected(videoBid); @@ -461,35 +461,35 @@ describe('auctionmanager.js', function () { it('Custom configuration for one bidder', function () { $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - // change default here - return bidResponse.pbHg; - } - }, { - key: TARGETING_KEYS.SIZE, - val: function (bidResponse) { - return bidResponse.size; + { + appnexus: { + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + // change default here + return bidResponse.pbHg; + } + }, { + key: TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } } - } - ] + ] - } - }; + } + }; var expected = getDefaultExpected(bid); expected[TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg; @@ -500,35 +500,35 @@ describe('auctionmanager.js', function () { it('Custom configuration for one bidder - not matched', function () { $$PREBID_GLOBAL$$.bidderSettings = - { - nonExistentBidder: { - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - // change default here - return bidResponse.pbHg; - } - }, { - key: TARGETING_KEYS.SIZE, - val: function (bidResponse) { - return bidResponse.size; + { + nonExistentBidder: { + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + // change default here + return bidResponse.pbHg; + } + }, { + key: TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } } - } - ] + ] - } - }; + } + }; var expected = getDefaultExpected(bid); var response = getKeyValueTargetingPairs(bid.bidderCode, bid); @@ -555,38 +555,38 @@ describe('auctionmanager.js', function () { it('Custom bidCpmAdjustment for one bidder and inherit standard but doesn\'t use standard bidCpmAdjustment', function () { $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - bidCpmAdjustment: function (bidCpm) { - return bidCpm * 0.7; - }, - }, - standard: { - bidCpmAdjustment: function (bidCpm) { - return 200; + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, }, - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - // change default here - return 10.00; + standard: { + bidCpmAdjustment: function (bidCpm) { + return 200; + }, + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + // change default here + return 10.00; + } } - } - ] + ] - } - }; + } + }; var expected = getDefaultExpected(bid, [TARGETING_KEYS.BIDDER, TARGETING_KEYS.AD_ID]); expected[TARGETING_KEYS.PRICE_BUCKET] = 10.0; @@ -603,13 +603,13 @@ describe('auctionmanager.js', function () { assert.equal(bid.cpm, 0.5); $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - bidCpmAdjustment: function (bidCpm) { - return bidCpm * 0.5; + { + standard: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.5; + } } - } - }; + }; adjustBids(bid) assert.equal(bid.cpm, 0.25); @@ -617,60 +617,60 @@ describe('auctionmanager.js', function () { it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () { $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - bidCpmAdjustment: function (bidCpm) { - return bidCpm * 0.7; - }, - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - // change default here - return 15.00; - } - } - ] - }, - standard: { - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + // change default here + return 15.00; + } } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - // change default here - return 10.00; + ] + }, + standard: { + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + // change default here + return 10.00; + }, }, - }, - { - key: TARGETING_KEYS.SIZE, - val: function (bidResponse) { - return bidResponse.size; + { + key: TARGETING_KEYS.SIZE, + val: function (bidResponse) { + return bidResponse.size; + } } - } - ] + ] - } - }; + } + }; var expected = getDefaultExpected(bid, [TARGETING_KEYS.BIDDER, TARGETING_KEYS.AD_ID, TARGETING_KEYS.SIZE]); expected[TARGETING_KEYS.PRICE_BUCKET] = 15.0; @@ -680,29 +680,29 @@ describe('auctionmanager.js', function () { it('sendStandardTargeting=false, and inherit custom', function () { $$PREBID_GLOBAL$$.bidderSettings = - { - appnexus: { - sendStandardTargeting: false, - adserverTargeting: [ - { - key: TARGETING_KEYS.BIDDER, - val: function (bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: TARGETING_KEYS.AD_ID, - val: function (bidResponse) { - return bidResponse.adId; - } - }, { - key: TARGETING_KEYS.PRICE_BUCKET, - val: function (bidResponse) { - return bidResponse.pbHg; + { + appnexus: { + sendStandardTargeting: false, + adserverTargeting: [ + { + key: TARGETING_KEYS.BIDDER, + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: TARGETING_KEYS.AD_ID, + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: TARGETING_KEYS.PRICE_BUCKET, + val: function (bidResponse) { + return bidResponse.pbHg; + } } - } - ] - } - }; + ] + } + }; var expected = getDefaultExpected(bid); expected[TARGETING_KEYS.PRICE_BUCKET] = 5.57; @@ -713,21 +713,21 @@ describe('auctionmanager.js', function () { it('suppressEmptyKeys=true', function() { $$PREBID_GLOBAL$$.bidderSettings = - { - standard: { - suppressEmptyKeys: true, - adserverTargeting: [ - { - key: 'aKeyWithAValue', - val: 42 - }, - { - key: 'aKeyWithAnEmptyValue', - val: '' - } - ] - } - }; + { + standard: { + suppressEmptyKeys: true, + adserverTargeting: [ + { + key: 'aKeyWithAValue', + val: 42 + }, + { + key: 'aKeyWithAnEmptyValue', + val: '' + } + ] + } + }; var expected = { 'aKeyWithAValue': 42 @@ -748,24 +748,24 @@ describe('auctionmanager.js', function () { assert.equal(bid.cpm, 0.5); $$PREBID_GLOBAL$$.bidderSettings = - { - brealtime: { - bidCpmAdjustment: function (bidCpm, bidObj) { - assert.deepEqual(bidObj, bid); - if (bidObj.adUnitCode === 'negative') { - return bidCpm * -0.5; - } - if (bidObj.adUnitCode === 'zero') { - return 0; - } - return bidCpm * 0.5; + { + brealtime: { + bidCpmAdjustment: function (bidCpm, bidObj) { + assert.deepEqual(bidObj, bid); + if (bidObj.adUnitCode === 'negative') { + return bidCpm * -0.5; + } + if (bidObj.adUnitCode === 'zero') { + return 0; + } + return bidCpm * 0.5; + }, }, - }, - standard: { - adserverTargeting: [ - ] - } - }; + standard: { + adserverTargeting: [ + ] + } + }; // negative bid.adUnitCode = 'negative'; diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 3e390130935..56e5449a524 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -10,12 +10,12 @@ import { adAuctionHeadersHook, addPaapiConfigHook, addPaapiData, - ASYNC_SIGNALS, + ASYNC_SIGNALS, AsyncPAAPIParam, buildPAAPIParams, buyersToAuctionConfigs, getPAAPIConfig, getPAAPISize, IGB_TO_CONFIG, - mergeBuyers, + mergeBuyers, NAVIGATOR_APIS, onAuctionInit, parallelPaapiProcessing, parseExtIgi, @@ -33,6 +33,7 @@ import {getGlobal} from '../../../src/prebidGlobal.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {AuctionIndex} from '../../../src/auctionIndex.js'; +import {buildActivityParams} from '../../../src/activities/params.js'; describe('paapi module', () => { let sandbox; @@ -682,6 +683,22 @@ describe('paapi module', () => { }); describe('makeBidRequests', () => { + before(() => { + NAVIGATOR_APIS.forEach(method => { + if (navigator[method] == null) { + navigator[method] = () => null; + after(() => { + delete navigator[method]; + }) + } + }) + }); + beforeEach(() => { + NAVIGATOR_APIS.forEach(method => { + sandbox.stub(navigator, method) + }) + }); + function mark() { return Object.fromEntries( adapterManager.makeBidRequests( @@ -695,12 +712,27 @@ describe('paapi module', () => { ); } - function expectFledgeFlags(...enableFlags) { + async function testAsyncParams(bidderRequest) { + for (const method of NAVIGATOR_APIS) { + navigator[method].returns('result'); + expect(await bidderRequest.paapi[method]('arg').resolve()).to.eql('result'); + sinon.assert.calledWith(navigator[method], 'arg'); + } + } + + async function expectFledgeFlags(...enableFlags) { const bidRequests = mark(); expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); + if (bidRequests.appnexus.paapi?.enabled) { + await testAsyncParams(bidRequests.appnexus) + } bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); + if (bidRequests.rubicon.paapi?.enabled) { + testAsyncParams(bidRequests.rubicon); + } + bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { @@ -714,7 +746,7 @@ describe('paapi module', () => { } describe('with setConfig()', () => { - it('should set paapi.enabled correctly per bidder', function () { + it('should set paapi.enabled correctly per bidder', async function () { config.setConfig({ bidderSequence: 'fixed', paapi: { @@ -723,10 +755,10 @@ describe('paapi module', () => { defaultForSlots: 1, } }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: 0}); + await expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: 0}); }); - it('should set paapi.enabled correctly for all bidders', function () { + it('should set paapi.enabled correctly for all bidders', async function () { config.setConfig({ bidderSequence: 'fixed', paapi: { @@ -734,7 +766,7 @@ describe('paapi module', () => { defaultForSlots: 1, } }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + await expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); }); Object.entries({ @@ -1163,6 +1195,77 @@ describe('paapi module', () => { }); }); + describe('buildPaapiParameters', () => { + let next, bidderRequest, spec, bids; + beforeEach(() => { + next = sinon.stub(); + spec = {}; + bidderRequest = {paapi: {enabled: true}}; + bids = []; + }); + + function runParamHook() { + return Promise.resolve(buildPAAPIParams(next, spec, bids, bidderRequest)); + } + + Object.entries({ + 'has no paapiParameters': () => null, + 'returns empty parameter map'() { + spec.paapiParameters = () => ({}) + }, + 'returns null parameter map'() { + spec.paapiParameters = () => null + }, + 'returns params, but PAAPI is disabled'() { + bidderRequest.paapi.enabled = false; + spec.paapiParameters = () => ({param: new AsyncPAAPIParam()}) + } + }).forEach(([t, setup]) => { + it(`should do nothing if spec ${t}`, async () => { + setup(); + await runParamHook(); + sinon.assert.calledWith(next, spec, bids, bidderRequest); + }) + }) + + describe('when paapiParameters returns a map', () => { + let params; + beforeEach(() => { + spec.paapiParameters = sinon.stub().callsFake(() => params); + }); + it('should be invoked with bids & bidderRequest', async () => { + await runParamHook(); + sinon.assert.calledWith(spec.paapiParameters, bids, bidderRequest); + }); + it('should leave most things (including promises) untouched', async () => { + params = { + 'p1': 'scalar', + 'p2': Promise.resolve() + } + await runParamHook(); + expect(bidderRequest.paapi.params).to.eql(params); + }); + it('should resolve async PAAPI parameeters', async () => { + params = { + 'resolved': new AsyncPAAPIParam(() => Promise.resolve('value')), + } + await runParamHook(); + expect(bidderRequest.paapi.params).to.eql({ + 'resolved': 'value' + }) + }) + + it('should still call next if the resolution fails', async () => { + params = { + error: new AsyncPAAPIParam(() => Promise.reject(new Error())) + } + await runParamHook(); + sinon.assert.called(next); + expect(bidderRequest.paapi.params).to.not.exist; + }) + }) + }) + describe('parallel PAAPI auctions', () => { describe('parallellPaapiProcessing', () => { let next, spec, bids, bidderRequest, restOfTheArgs, mockConfig, mockAuction, bidsReceived, bidderRequests, adUnitCodes, adUnits; From 20615295b823f40682b12b3aea62da875a5ee523 Mon Sep 17 00:00:00 2001 From: SebRobert Date: Tue, 15 Apr 2025 03:59:00 +0200 Subject: [PATCH 1088/1097] BeOp Bid Adapter: support getUserSyncs (#12944) * Add getUserSyncs in BeOpBidAdapter * Migrate domain from beop.io to collectiveaudience.co * Multiple Frame tracking capacity --- modules/beopBidAdapter.js | 43 ++++++++++++--- modules/beopBidAdapter.md | 2 +- test/spec/modules/beopBidAdapter_spec.js | 68 ++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 11 deletions(-) diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index a24579af9a9..e58cf0f1708 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -18,10 +18,11 @@ import { getStorageManager } from '../src/storageManager.js'; * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ const BIDDER_CODE = 'beop'; -const ENDPOINT_URL = 'https://hb.beop.io/bid'; +const ENDPOINT_URL = 'https://hb.collectiveaudience.co/bid'; const COOKIE_NAME = 'beopid'; const TCF_VENDOR_ID = 666; @@ -96,6 +97,7 @@ export const spec = { gdpr_applies: gdpr ? gdpr.gdprApplies : false, tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null, eids: firstSlot.eids, + pv: '$prebid.version$' }; const payloadString = JSON.stringify(payloadObject); @@ -121,7 +123,7 @@ export const spec = { logWarn(BIDDER_CODE + ': timed out request'); triggerPixel(buildUrl({ protocol: 'https', - hostname: 't.beop.io', + hostname: 't.collectiveaudience.co', pathname: '/bid', search: trackingParams })); @@ -135,19 +137,47 @@ export const spec = { logInfo(BIDDER_CODE + ': won request'); triggerPixel(buildUrl({ protocol: 'https', - hostname: 't.beop.io', + hostname: 't.collectiveaudience.co', pathname: '/bid', search: trackingParams })); }, - onSetTargeting: function(bid) {} + + /** + * User syncs. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {UserSync[]} An array of syncs that should be executed. + */ + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = []; + + if (serverResponses.length > 0) { + const body = serverResponses[0].body; + + if (syncOptions.iframeEnabled && Array.isArray(body.sync_frames)) { + body.sync_frames.forEach(url => { + syncs.push({ type: 'iframe', url }); + }); + } + + if (syncOptions.pixelEnabled && Array.isArray(body.sync_pixels)) { + body.sync_pixels.forEach(url => { + syncs.push({ type: 'image', url }); + }); + } + } + + return syncs; + } } function buildTrackingParams(data, info, value) { let params = Array.isArray(data.params) ? data.params[0] : data.params; const pageUrl = getPageUrl(null, window); return { - pid: params.accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : params.accountId, + pid: params.accountId ?? (data.ad?.match(/account: \“([a-f\d]{24})\“/)?.[1] ?? ''), nid: params.networkId, nptnid: params.networkPartnerId, bid: data.bidId || data.requestId, @@ -155,7 +185,8 @@ function buildTrackingParams(data, info, value) { se_ca: 'bid', se_ac: info, se_va: value, - url: pageUrl + url: pageUrl, + pv: '$prebid.version$' }; } diff --git a/modules/beopBidAdapter.md b/modules/beopBidAdapter.md index 53d2542b69c..dd723af10d5 100644 --- a/modules/beopBidAdapter.md +++ b/modules/beopBidAdapter.md @@ -2,7 +2,7 @@ **Module Name** : BeOp Bidder Adapter **Module Type** : Bidder Adapter -**Maintainer** : tech@beop.io +**Maintainer** : tech@collectiveaudience.co # Description diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 3f06cd04910..e0acde0aa21 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -6,7 +6,7 @@ import { setConfig as setCurrencyConfig } from '../../../modules/currency'; import { addFPDToBidderRequest } from '../../helpers/fpd'; const utils = require('src/utils'); -const ENDPOINT = 'https://hb.beop.io/bid'; +const ENDPOINT = 'https://hb.collectiveaudience.co/bid'; let validBid = { 'bidder': 'beop', @@ -239,7 +239,7 @@ describe('BeOp Bid Adapter tests', () => { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onTimeout({params: {accountId: '5a8af500c9e77c00017e4cad'}, timeout: 2000}); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.collectiveaudience.co'); expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid'); expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=timeout'); expect(triggerPixelStub.getCall(0).args[0]).to.include('pid=5a8af500c9e77c00017e4cad'); @@ -251,7 +251,7 @@ describe('BeOp Bid Adapter tests', () => { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onBidWon({params: {accountId: '5a8af500c9e77c00017e4cad'}, cpm: 1.2}); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.collectiveaudience.co'); expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid'); expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won'); expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad'); @@ -262,7 +262,7 @@ describe('BeOp Bid Adapter tests', () => { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onBidWon({params: [{accountId: '5a8af500c9e77c00017e4cad'}], cpm: 1.2}); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.collectiveaudience.co'); expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid'); expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won'); expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad'); @@ -350,4 +350,64 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.fg).to.exist; }) }) + describe('getUserSyncs', function () { + it('should return iframe sync when iframeEnabled and syncFrame provided', function () { + const syncOptions = { iframeEnabled: true, pixelEnabled: false }; + const serverResponses = [{ body: { sync_frames: ['https://example.com/sync_frame', 'https://example2.com/sync_second'] } }]; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.have.length(2); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://example.com/sync_frame'); + }); + + it('should return pixel syncs when pixelEnabled and syncPixels provided', function () { + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + const serverResponses = [{ + body: { + sync_pixels: [ + 'https://example.com/pixel1', + 'https://example.com/pixel2' + ] + } + }]; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.have.length(2); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://example.com/pixel1'); + expect(syncs[1].url).to.equal('https://example.com/pixel2'); + }); + + it('should return both iframe and pixel syncs when both options are enabled', function () { + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + const serverResponses = [{ + body: { + sync_frames: ['https://example.com/sync_frame'], + sync_pixels: ['https://example.com/pixel1'] + } + }]; + + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + + expect(syncs).to.have.length(2); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[1].type).to.equal('image'); + }); + + it('should return empty array when no serverResponses', function () { + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + const syncs = spec.getUserSyncs(syncOptions, []); + expect(syncs).to.be.an('array').that.is.empty; + }); + + it('should return empty array when no syncFrame or syncPixels provided', function () { + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + const serverResponses = [{ body: {} }]; + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).to.be.an('array').that.is.empty; + }); + }); }); From 845c5052ff6d0794a47da0d29b4372173ca7e394 Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Tue, 15 Apr 2025 07:59:13 +0530 Subject: [PATCH 1089/1097] PubMatic Adapter : Using ORTB Converter library for request/response handling (#12814) * Pubmatic Adapter with ORTB Converter Library * Pubmatic Adapter with ORTB Converter Library * Support media type based floor * Support media type based floor * Linting fixes * Added cpm adjustment details to next auction * Added test cases for cpm adjustment * Fix for undefined adslot case --- modules/pubmaticBidAdapter.js | 1838 ++---- test/spec/modules/pubmaticBidAdapter_spec.js | 5364 +++--------------- 2 files changed, 1443 insertions(+), 5759 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a8501ea0593..a3c97682d5b 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,10 +1,11 @@ -import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, uniques, isPlainObject, isInteger, generateUUID, isFn } from '../src/utils.js'; +import { logWarn, isStr, isArray, deepAccess, deepSetValue, isBoolean, isInteger, logInfo, logError, deepClone, uniques, generateUUID, isPlainObject, isFn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; -import { NATIVE_IMAGE_TYPES, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS, NATIVE_ASSET_TYPES } from '../src/constants.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS } from '../src/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -22,138 +23,131 @@ const AUCTION_TYPE = 1; const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; -const DEFAULT_TTL = 360; -const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application const MSG_VIDEO_PLCMT_MISSING = 'Video.plcmt param missing'; - +const PREBID_NATIVE_DATA_KEY_VALUES = Object.values(PREBID_NATIVE_DATA_KEYS_TO_ORTB); +const DEFAULT_TTL = 360; const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender 'yob': '', // User year of birth 'lat': '', // User location - Latitude 'lon': '', // User Location - Longitude - 'wiid': '', // OpenWrap Wrapper Impression ID - 'profId': '', // OpenWrap Legacy: Profile ID - 'verId': '' // OpenWrap Legacy: version ID + 'wiid': '' // OpenWrap Wrapper Impression ID }; -const DATA_TYPES = { - 'NUMBER': 'number', - 'STRING': 'string', - 'BOOLEAN': 'boolean', - 'ARRAY': 'array', - 'OBJECT': 'object' -}; -const VIDEO_CUSTOM_PARAMS = { - 'mimes': DATA_TYPES.ARRAY, - 'minduration': DATA_TYPES.NUMBER, - 'maxduration': DATA_TYPES.NUMBER, - 'startdelay': DATA_TYPES.NUMBER, - 'playbackmethod': DATA_TYPES.ARRAY, - 'api': DATA_TYPES.ARRAY, - 'protocols': DATA_TYPES.ARRAY, - 'w': DATA_TYPES.NUMBER, - 'h': DATA_TYPES.NUMBER, - 'battr': DATA_TYPES.ARRAY, - 'linearity': DATA_TYPES.NUMBER, - 'placement': DATA_TYPES.NUMBER, - 'plcmt': DATA_TYPES.NUMBER, - 'minbitrate': DATA_TYPES.NUMBER, - 'maxbitrate': DATA_TYPES.NUMBER, - 'skip': DATA_TYPES.NUMBER, - 'pos': DATA_TYPES.NUMBER -} - -const NATIVE_ASSET_IMAGE_TYPE = { - 'ICON': 1, - 'IMAGE': 3 -} -const BANNER_CUSTOM_PARAMS = { - 'battr': DATA_TYPES.ARRAY, - 'pos': DATA_TYPES.NUMBER, -} - -const NET_REVENUE = true; -const dealChannelValues = { +const dealChannel = { 1: 'PMP', 5: 'PREF', 6: 'PMPG' }; -// BB stands for Blue BillyWig -const BB_RENDERER = { - bootstrapPlayer: function(bid) { - const config = { - code: bid.adUnitCode, - }; - - if (bid.vastXml) config.vastXml = bid.vastXml; - else if (bid.vastUrl) config.vastUrl = bid.vastUrl; - - if (!bid.vastXml && !bid.vastUrl) { - logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`); - return; - } - - const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode); - - const ele = document.getElementById(bid.adUnitCode); // NB convention - - let renderer; +const MEDIATYPE_TTL = { + 'banner': 360, + 'video': 1800, + 'native': 1800 +}; - for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) { - if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) { - renderer = window.bluebillywig.renderers[rendererIndex]; - break; - } - } +let conf = {}; +let blockedIabCategories = []; +let allowedIabCategories = []; +let pubId = 0; +export let cpmAdjustment; - if (renderer) renderer.bootstrap(config, ele); - else logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_TTL }, - newRenderer: function(rendererCode, adUnitCode) { - var rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode); - const renderer = Renderer.install({ - url: rendererUrl, - loaded: false, - adUnitCode + imp(buildImp, bidRequest, context) { + const { kadfloor, currency, adSlot = '', deals, dctr, pmzoneid, hashedKey } = bidRequest.params; + const { adUnitCode, mediaTypes, rtd } = bidRequest; + const imp = buildImp(bidRequest, context); + if (deals) addPMPDeals(imp, deals); + if (dctr) addDealCustomTargetings(imp, dctr); + if (rtd?.jwplayer) addJWPlayerSegmentData(imp, rtd.jwplayer); + imp.bidfloor = _parseSlotParam('kadfloor', kadfloor); + imp.bidfloorcur = currency ? _parseSlotParam('currency', currency) : DEFAULT_CURRENCY; + setFloorInImp(imp, bidRequest); + if (imp.hasOwnProperty('banner')) updateBannerImp(imp.banner, adSlot); + if (imp.hasOwnProperty('video')) updateVideoImp(imp.video, mediaTypes?.video, adUnitCode, imp); + if (imp.hasOwnProperty('native')) updateNativeImp(imp, mediaTypes?.native); + // Check if the imp object does not have banner, video, or native + if (!imp.hasOwnProperty('banner') && !imp.hasOwnProperty('video') && !imp.hasOwnProperty('native')) { + return null; + } + if (pmzoneid) imp.ext.pmZoneId = pmzoneid; + setImpTagId(imp, adSlot.trim(), hashedKey); + setImpFields(imp); + // check for battr data types + ['banner', 'video', 'native'].forEach(key => { + if (imp[key]?.battr && !Array.isArray(imp[key].battr)) { + delete imp[key].battr; + } }); - - try { - renderer.setRender(BB_RENDERER.outstreamRender); - } catch (err) { - logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + if (blockedIabCategories.length || request.bcat) { + const validatedBCategories = validateBlockedCategories([...(blockedIabCategories || []), ...(request.bcat || [])]); + if (validatedBCategories.length) request.bcat = validatedBCategories; + } + if (allowedIabCategories.length || request.acat) { + const validatedACategories = validateAllowedCategories([...(allowedIabCategories || []), ...(request.acat || [])]); + if (validatedACategories.length) request.acat = validatedACategories; + } + reqLevelParams(request); + updateUserSiteDevice(request, context?.bidRequests); + addExtenstionParams(request); + const marketPlaceEnabled = bidderRequest?.bidderCode + ? bidderSettings.get(bidderRequest.bidderCode, 'allowAlternateBidderCodes') : undefined; + if (marketPlaceEnabled) updateRequestExt(request, bidderRequest); + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bidResponse.meta) bidResponse.meta.mediaType = bidResponse.mediaType; + updateResponseWithCustomFields(bidResponse, bid, context); + const { mediaType, playerWidth, playerHeight } = bidResponse; + const { params, adUnitCode, mediaTypes } = context?.bidRequest; + if (mediaType === VIDEO) { + if (!bidResponse.width) bidResponse.width = playerWidth; + if (!bidResponse.height) bidResponse.height = playerHeight; + const { context, maxduration } = mediaTypes[mediaType]; + if (context === 'outstream' && params.outstreamAU && adUnitCode) { + bidResponse.rendererCode = params.outstreamAU; + bidResponse.renderer = BB_RENDERER.newRenderer(bidResponse.rendererCode, adUnitCode); + } + assignDealTier(bidResponse, context, maxduration); + } + if (mediaType === NATIVE && bid.adm) { + try { + const adm = JSON.parse(bid.adm.replace(/\\/g, '')); + bidResponse.native = { ortb: { ...adm.native } }; + } catch (ex) { + logWarn(`${LOG_WARN_PREFIX}Error: Cannot parse native response for ad response: ${bid.adm}`); + return; + } + bidResponse.width = bid.w || DEFAULT_WIDTH; + bidResponse.height = bid.h || DEFAULT_HEIGHT; } - - return renderer; + return bidResponse; }, - outstreamRender: function(bid) { - bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) }); + response(buildResponse, bidResponses, ortbResponse, context) { + return buildResponse(bidResponses, ortbResponse, context); }, - getRendererId: function(pub, renderer) { - return `${pub}-${renderer}`; // NB convention! + overrides: { + imp: { + bidfloor: false, + extBidfloor: false + }, + bidResponse: { + native: false + } } -}; - -const MEDIATYPE = [ - BANNER, - VIDEO, - NATIVE -] - -const MEDIATYPE_TTL = { - 'banner': 360, - 'video': 1800, - 'native': 1800 -}; - -let publisherId = 0; -let isInvalidNativeRequest = false; -let biddersList = ['pubmatic']; -const allBiddersList = ['all']; -export let cpmAdjustment; +}); export function _calculateBidCpmAdjustment(bid) { if (!bid) return; @@ -191,661 +185,68 @@ export function _calculateBidCpmAdjustment(bid) { : cpmAdjustment.adjustment.push(adjustmentEntry); } -export function _getDomainFromURL(url) { - let anchor = document.createElement('a'); - anchor.href = url; - return anchor.hostname; -} - -function _parseSlotParam(paramName, paramValue) { - if (!isStr(paramValue)) { - paramValue && logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); - return UNDEFINED; - } - - switch (paramName) { - case 'pmzoneid': - return paramValue.split(',').slice(0, 50).map(id => id.trim()).join(); - case 'kadfloor': - return parseFloat(paramValue) || UNDEFINED; - case 'lat': - return parseFloat(paramValue) || UNDEFINED; - case 'lon': - return parseFloat(paramValue) || UNDEFINED; - case 'yob': - return parseInt(paramValue) || UNDEFINED; - default: - return paramValue; - } -} - -function _cleanSlot(slotName) { - if (isStr(slotName)) { - return slotName.replace(/^\s+/g, '').replace(/\s+$/g, ''); - } - if (slotName) { - logWarn(BIDDER_CODE + ': adSlot must be a string. Ignoring adSlot'); - } - return ''; -} - -function _parseAdSlot(bid) { - bid.params.adUnit = ''; - bid.params.adUnitIndex = '0'; - bid.params.width = 0; - bid.params.height = 0; - bid.params.adSlot = _cleanSlot(bid.params.adSlot); - - var slot = bid.params.adSlot; - var splits = slot.split(':'); - - slot = splits[0]; - if (splits.length == 2) { - bid.params.adUnitIndex = splits[1]; - } - // check if size is mentioned in sizes array. in that case do not check for @ in adslot - splits = slot.split('@'); - bid.params.adUnit = splits[0]; - if (splits.length > 1) { - // i.e size is specified in adslot, so consider that and ignore sizes array - splits = splits[1].split('x'); - if (splits.length != 2) { - logWarn(LOG_WARN_PREFIX + 'AdSlot Error: adSlot not in required format'); - return; - } - bid.params.width = parseInt(splits[0], 10); - bid.params.height = parseInt(splits[1], 10); - } else if (bid.hasOwnProperty('mediaTypes') && - bid.mediaTypes.hasOwnProperty(BANNER) && - bid.mediaTypes.banner.hasOwnProperty('sizes')) { - var i = 0; - var sizeArray = []; - for (;i < bid.mediaTypes.banner.sizes.length; i++) { - if (bid.mediaTypes.banner.sizes[i].length === 2) { // sizes[i].length will not be 2 in case where size is set as fluid, we want to skip that entry - sizeArray.push(bid.mediaTypes.banner.sizes[i]); - } - } - bid.mediaTypes.banner.sizes = sizeArray; - if (bid.mediaTypes.banner.sizes.length >= 1) { - // set the first size in sizes array in bid.params.width and bid.params.height. These will be sent as primary size. - // The rest of the sizes will be sent in format array. - bid.params.width = bid.mediaTypes.banner.sizes[0][0]; - bid.params.height = bid.mediaTypes.banner.sizes[0][1]; - bid.mediaTypes.banner.sizes = bid.mediaTypes.banner.sizes.splice(1, bid.mediaTypes.banner.sizes.length - 1); +const handleImageProperties = asset => { + const imgProps = {}; + if (asset.aspect_ratios && isArray(asset.aspect_ratios) && asset.aspect_ratios.length) { + const { min_width: minWidth, min_height: minHeight } = asset.aspect_ratios[0]; + if (isInteger(minWidth) && isInteger(minHeight)) { + imgProps.wmin = minWidth; + imgProps.hmin = minHeight; } + // eslint-disable-next-line camelcase + imgProps.ext = { aspectratios: asset.aspect_ratios.filter(({ ratio_width, ratio_height }) => ratio_width && ratio_height).map(({ ratio_width, ratio_height }) => `${ratio_width}:${ratio_height}`) }; } -} - -function _initConf(refererInfo) { - return { - // TODO: do the fallbacks make sense here? - pageURL: refererInfo?.page || window.location.href, - refURL: refererInfo?.ref || window.document.referrer - }; -} - -function _handleCustomParams(params, conf) { - if (!conf.kadpageurl) { - conf.kadpageurl = conf.pageURL; - } - - var key, value, entry; - for (key in CUSTOM_PARAMS) { - if (CUSTOM_PARAMS.hasOwnProperty(key)) { - value = params[key]; - if (value) { - entry = CUSTOM_PARAMS[key]; - - if (typeof entry === 'object') { - // will be used in future when we want to process a custom param before using - // 'keyname': {f: function() {}} - value = entry.f(value, conf); - } - - if (isStr(value)) { - conf[key] = value; - } else { - logWarn(LOG_WARN_PREFIX + 'Ignoring param : ' + key + ' with value : ' + CUSTOM_PARAMS[key] + ', expects string-value, found ' + typeof value); - } - } - } - } - return conf; -} - -export function getDeviceConnectionType() { - let connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection); - switch (connection?.effectiveType) { - case 'ethernet': - return 1; - case 'wifi': - return 2; - case 'slow-2g': - case '2g': - return 4; - case '3g': - return 5; - case '4g': - return 6; - default: - return 0; - } -} - -function _createOrtbTemplate(conf) { - return { - id: '' + new Date().getTime(), - at: AUCTION_TYPE, - cur: [DEFAULT_CURRENCY], - imp: [], - site: { - page: conf.pageURL, - ref: conf.refURL, - publisher: {} - }, - device: { - ua: navigator.userAgent, - js: 1, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - h: screen.height, - w: screen.width, - language: navigator.language, - connectiontype: getDeviceConnectionType() - }, - user: {}, - ext: {} - }; -} - -function _checkParamDataType(key, value, datatype) { - var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; - var functionToExecute; - switch (datatype) { - case DATA_TYPES.BOOLEAN: - functionToExecute = isBoolean; - break; - case DATA_TYPES.NUMBER: - functionToExecute = isNumber; - break; - case DATA_TYPES.STRING: - functionToExecute = isStr; - break; - case DATA_TYPES.ARRAY: - functionToExecute = isArray; - break; - } - if (functionToExecute(value)) { - return value; + imgProps.w = asset.w || asset.width; + imgProps.h = asset.h || asset.height; + if (asset.sizes && asset.sizes.length === 2 && isInteger(asset.sizes[0]) && isInteger(asset.sizes[1])) { + imgProps.w = asset.sizes[0]; + imgProps.h = asset.sizes[1]; + delete imgProps.wmin; + delete imgProps.hmin; } - logWarn(LOG_WARN_PREFIX + errMsg); - return UNDEFINED; + asset.ext && (imgProps.ext = asset.ext); + asset.mimes && (imgProps.mimes = asset.mimes); + return imgProps; } -// TODO delete this code when removing native 1.1 support -const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { - 'desc': 'desc', - 'desc2': 'desc2', - 'body': 'desc', - 'body2': 'desc2', - 'sponsoredBy': 'sponsored', - 'cta': 'ctatext', - 'rating': 'rating', - 'address': 'address', - 'downloads': 'downloads', - 'likes': 'likes', - 'phone': 'phone', - 'price': 'price', - 'salePrice': 'saleprice', - 'displayUrl': 'displayurl', - 'saleprice': 'saleprice', - 'displayurl': 'displayurl' -}; - -const PREBID_NATIVE_DATA_KEY_VALUES = Object.values(PREBID_NATIVE_DATA_KEYS_TO_ORTB); - -// TODO remove this function when the support for 1.1 is removed -/** - * Copy of the function toOrtbNativeRequest from core native.js to handle the title len/length - * and ext and mimes parameters from legacy assets. - * @param {object} legacyNativeAssets - * @returns an OpenRTB format of the same bid request - */ -export function toOrtbNativeRequest(legacyNativeAssets) { - if (!legacyNativeAssets && !isPlainObject(legacyNativeAssets)) { - logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or not an object: ${legacyNativeAssets}`); - isInvalidNativeRequest = true; - return; - } - const ortb = { - ver: '1.2', - assets: [] - }; +const toOrtbNativeRequest = legacyNativeAssets => { + const ortb = { ver: '1.2', assets: [] }; for (let key in legacyNativeAssets) { - // skip conversion for non-asset keys if (NATIVE_KEYS_THAT_ARE_NOT_ASSETS.includes(key)) continue; if (!NATIVE_KEYS.hasOwnProperty(key) && !PREBID_NATIVE_DATA_KEY_VALUES.includes(key)) { - logWarn(`${LOG_WARN_PREFIX}: Unrecognized native asset code: ${key}. Asset will be ignored.`); + logWarn(`${LOG_WARN_PREFIX}: Unrecognized asset: ${key}. Ignored.`); continue; } const asset = legacyNativeAssets[key]; - let required = 0; - if (asset.required && isBoolean(asset.required)) { - required = Number(asset.required); - } - const ortbAsset = { - id: ortb.assets.length, - required - }; - // data cases + const required = asset.required && isBoolean(asset.required) ? 1 : 0; + const ortbAsset = { id: ortb.assets.length, required }; + if (key in PREBID_NATIVE_DATA_KEYS_TO_ORTB) { - ortbAsset.data = { - type: NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]] - } - if (asset.len || asset.length) { - ortbAsset.data.len = asset.len || asset.length; - } - if (asset.ext) { - ortbAsset.data.ext = asset.ext; - } - // icon or image case + ortbAsset.data = { type: NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]], ...asset.len && { len: asset.len }, ...asset.ext && { ext: asset.ext } }; } else if (key === 'icon' || key === 'image') { ortbAsset.img = { type: key === 'icon' ? NATIVE_IMAGE_TYPES.ICON : NATIVE_IMAGE_TYPES.MAIN, - } - // if min_width and min_height are defined in aspect_ratio, they are preferred - if (asset.aspect_ratios) { - if (!isArray(asset.aspect_ratios)) { - logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's not a an array: ${asset.aspect_ratios}`); - } else if (!asset.aspect_ratios.length) { - logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's empty: ${asset.aspect_ratios}`); - } else { - const { min_width: minWidth, min_height: minHeight } = asset.aspect_ratios[0]; - if (!isInteger(minWidth) || !isInteger(minHeight)) { - logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios min_width or min_height are invalid: ${minWidth}, ${minHeight}`); - } else { - ortbAsset.img.wmin = minWidth; - ortbAsset.img.hmin = minHeight; - } - const aspectRatios = asset.aspect_ratios - .filter((ar) => ar.ratio_width && ar.ratio_height) - .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); - if (aspectRatios.length > 0) { - ortbAsset.img.ext = { - aspectratios: aspectRatios - } - } - } - } - - ortbAsset.img.w = asset.w || asset.width; - ortbAsset.img.h = asset.h || asset.height; - ortbAsset.img.wmin = asset.wmin || asset.minimumWidth || (asset.minsizes ? asset.minsizes[0] : UNDEFINED); - ortbAsset.img.hmin = asset.hmin || asset.minimumHeight || (asset.minsizes ? asset.minsizes[1] : UNDEFINED); - - // if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin - if (asset.sizes) { - if (asset.sizes.length !== 2 || !isInteger(asset.sizes[0]) || !isInteger(asset.sizes[1])) { - logWarn(`${LOG_WARN_PREFIX}: image.sizes was passed, but its value is not an array of integers: ${asset.sizes}`); - } else { - logInfo(`${LOG_WARN_PREFIX}: if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin`); - ortbAsset.img.w = asset.sizes[0]; - ortbAsset.img.h = asset.sizes[1]; - delete ortbAsset.img.hmin; - delete ortbAsset.img.wmin; - } - } - asset.ext && (ortbAsset.img.ext = asset.ext); - asset.mimes && (ortbAsset.img.mimes = asset.mimes); - // title case + ...handleImageProperties(asset) + }; } else if (key === 'title') { - ortbAsset.title = { - // in openRTB, len is required for titles, while in legacy prebid was not. - // for this reason, if len is missing in legacy prebid, we're adding a default value of 140. - len: asset.len || asset.length || 140 - } - asset.ext && (ortbAsset.title.ext = asset.ext); - // all extensions to the native bid request are passed as is + ortbAsset.title = { len: asset.len || 140, ...asset.ext && { ext: asset.ext } }; } else if (key === 'ext') { ortbAsset.ext = asset; - // in `ext` case, required field is not needed delete ortbAsset.required; } ortb.assets.push(ortbAsset); } - - if (ortb.assets.length < 1) { - logWarn(`${LOG_WARN_PREFIX}: Could not find any valid asset`); - isInvalidNativeRequest = true; - return; - } - return ortb; } -// TODO delete this code when removing native 1.1 support - -function _createNativeRequest(params) { - var nativeRequestObject; - - // TODO delete this code when removing native 1.1 support - if (!params.ortb) { // legacy assets definition found - nativeRequestObject = toOrtbNativeRequest(params); - } else { // ortb assets definition found - params = params.ortb; - // TODO delete this code when removing native 1.1 support - nativeRequestObject = { ver: '1.2', ...params, assets: [] }; - const { assets } = params; - - const isValidAsset = (asset) => asset.title || asset.img || asset.data || asset.video; - - if (assets.length < 1 || !assets.some(asset => isValidAsset(asset))) { - logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or contains some invalid object`); - isInvalidNativeRequest = true; - return nativeRequestObject; - } - - assets.forEach(asset => { - var assetObj = asset; - if (assetObj.img) { - if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.IMAGE) { - assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); - assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); - assetObj.wmin = assetObj.wmin || assetObj.minimumWidth || (assetObj.minsizes ? assetObj.minsizes[0] : UNDEFINED); - assetObj.hmin = assetObj.hmin || assetObj.minimumHeight || (assetObj.minsizes ? assetObj.minsizes[1] : UNDEFINED); - } else if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.ICON) { - assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); - assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); - } - } - - if (assetObj && assetObj.id !== undefined && isValidAsset(assetObj)) { - nativeRequestObject.assets.push(assetObj); - } - } - ); - } - return nativeRequestObject; -} - -function _createBannerRequest(bid) { - var sizes = bid.mediaTypes.banner.sizes; - var format = []; - var bannerObj; - if (sizes !== UNDEFINED && isArray(sizes)) { - bannerObj = {}; - if (!bid.params.width && !bid.params.height) { - if (sizes.length === 0) { - // i.e. since bid.params does not have width or height, and length of sizes is 0, need to ignore this banner imp - bannerObj = UNDEFINED; - logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); - return bannerObj; - } else { - bannerObj.w = parseInt(sizes[0][0], 10); - bannerObj.h = parseInt(sizes[0][1], 10); - sizes = sizes.splice(1, sizes.length - 1); - } - } else { - bannerObj.w = bid.params.width; - bannerObj.h = bid.params.height; - } - if (sizes.length > 0) { - format = []; - sizes.forEach(function (size) { - if (size.length > 1) { - format.push({ w: size[0], h: size[1] }); - } - }); - if (format.length > 0) { - bannerObj.format = format; - } - } - bannerObj.pos = 0; - bannerObj.topframe = inIframe() ? 0 : 1; - - // Adding Banner custom params - const bannerCustomParams = {...deepAccess(bid, 'ortb2Imp.banner')}; - for (let key in BANNER_CUSTOM_PARAMS) { - if (bannerCustomParams.hasOwnProperty(key)) { - bannerObj[key] = _checkParamDataType(key, bannerCustomParams[key], BANNER_CUSTOM_PARAMS[key]); - } - } - } else { - logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); - bannerObj = UNDEFINED; - } - return bannerObj; -} - -export function checkVideoPlacement(videoData, adUnitCode) { - // Check for video.placement property. If property is missing display log message. - if (FEATURES.VIDEO && !deepAccess(videoData, 'plcmt')) { - logWarn(MSG_VIDEO_PLCMT_MISSING + ' for ' + adUnitCode); - }; -} -function _createVideoRequest(bid) { - var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); - var videoObj; - - if (FEATURES.VIDEO && videoData !== UNDEFINED) { - videoObj = {}; - checkVideoPlacement(videoData, bid.adUnitCode); - for (var key in VIDEO_CUSTOM_PARAMS) { - if (videoData.hasOwnProperty(key)) { - videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); - } - } - // read playersize and assign to h and w. - if (isArray(bid.mediaTypes.video.playerSize[0])) { - videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); - videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); - } else if (isNumber(bid.mediaTypes.video.playerSize[0])) { - videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); - videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); - } - } else { - videoObj = UNDEFINED; - logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); - } - return videoObj; -} - -// support for PMP deals -function _addPMPDealsInImpression(impObj, bid) { - if (bid.params.deals) { - if (isArray(bid.params.deals)) { - bid.params.deals.forEach(function(dealId) { - if (isStr(dealId) && dealId.length > 3) { - if (!impObj.pmp) { - impObj.pmp = { private_auction: 0, deals: [] }; - } - impObj.pmp.deals.push({ id: dealId }); - } else { - logWarn(LOG_WARN_PREFIX + 'Error: deal-id present in array bid.params.deals should be a strings with more than 3 charaters length, deal-id ignored: ' + dealId); - } - }); - } else { - logWarn(LOG_WARN_PREFIX + 'Error: bid.params.deals should be an array of strings.'); - } - } -} - -function _addDealCustomTargetings(imp, bid) { - var dctr = ''; - var dctrLen; - if (bid.params.dctr) { - dctr = bid.params.dctr; - if (isStr(dctr) && dctr.length > 0) { - var arr = dctr.split('|'); - dctr = ''; - arr.forEach(val => { - dctr += (val.length > 0) ? (val.trim() + '|') : ''; - }); - dctrLen = dctr.length; - if (dctr.substring(dctrLen, dctrLen - 1) === '|') { - dctr = dctr.substring(0, dctrLen - 1); - } - imp.ext['key_val'] = dctr.trim(); - } else { - logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); - } - } -} - -function _addJWPlayerSegmentData(imp, bid) { - var jwSegData = (bid.rtd && bid.rtd.jwplayer && bid.rtd.jwplayer.targeting) || undefined; - var jwPlayerData = ''; - const jwMark = 'jw-'; - - if (jwSegData === undefined || jwSegData === '' || !jwSegData.hasOwnProperty('segments')) return; - - var maxLength = jwSegData.segments.length; - - jwPlayerData += jwMark + 'id=' + jwSegData.content.id; // add the content id first - - for (var i = 0; i < maxLength; i++) { - jwPlayerData += '|' + jwMark + jwSegData.segments[i] + '=1'; - } - - var ext; - - ext = imp.ext; - ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; -} - -function _createImpressionObject(bid, bidderRequest) { - var impObj = {}; - var bannerObj; - var videoObj; - var nativeObj = {}; - var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; - var mediaTypes = ''; - var format = []; - var isFledgeEnabled = bidderRequest?.paapi?.enabled; - - impObj = { - id: bid.bidId, - tagid: bid.params.adUnit || undefined, - bidfloor: _parseSlotParam('kadfloor', bid.params.kadfloor), - secure: 1, - ext: { - pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) - }, - bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY, - displaymanager: 'Prebid.js', - displaymanagerver: '$prebid.version$', // prebid version - pmp: bid.ortb2Imp?.pmp || undefined - }; - - _addPMPDealsInImpression(impObj, bid); - _addDealCustomTargetings(impObj, bid); - _addJWPlayerSegmentData(impObj, bid); - if (bid.hasOwnProperty('mediaTypes')) { - for (mediaTypes in bid.mediaTypes) { - switch (mediaTypes) { - case BANNER: - bannerObj = _createBannerRequest(bid); - if (bannerObj !== UNDEFINED) { - impObj.banner = bannerObj; - } - break; - case NATIVE: - // TODO uncomment below line when removing native 1.1 support - // nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeOrtbRequest)); - // TODO delete below line when removing native 1.1 support - nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); - if (!isInvalidNativeRequest) { - impObj.native = nativeObj; - } else { - logWarn(LOG_WARN_PREFIX + 'Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); - isInvalidNativeRequest = false; - } - break; - case FEATURES.VIDEO && VIDEO: - videoObj = _createVideoRequest(bid); - if (videoObj !== UNDEFINED) { - impObj.video = videoObj; - } - break; - } - } - } else { - // mediaTypes is not present, so this is a banner only impression - // this part of code is required for older testcases with no 'mediaTypes' to run succesfully. - bannerObj = { - pos: 0, - w: bid.params.width, - h: bid.params.height, - topframe: inIframe() ? 0 : 1 - }; - if (isArray(sizes) && sizes.length > 1) { - sizes = sizes.splice(1, sizes.length - 1); - sizes.forEach(size => { - format.push({ - w: size[0], - h: size[1] - }); - }); - bannerObj.format = format; - } - impObj.banner = bannerObj; - } - - _addImpressionFPD(impObj, bid); - - _addFloorFromFloorModule(impObj, bid); - - _addFledgeflag(impObj, bid, isFledgeEnabled) - - return impObj.hasOwnProperty(BANNER) || - impObj.hasOwnProperty(NATIVE) || - (FEATURES.VIDEO && impObj.hasOwnProperty(VIDEO)) ? impObj : UNDEFINED; -} - -function _addFledgeflag(impObj, bid, isFledgeEnabled) { - if (isFledgeEnabled) { - impObj.ext = impObj.ext || {}; - if (bid?.ortb2Imp?.ext?.ae !== undefined) { - impObj.ext.ae = bid.ortb2Imp.ext.ae; - } - } else { - if (impObj.ext?.ae) { - delete impObj.ext.ae; - } - } -} - -function _addImpressionFPD(imp, bid) { - const ortb2 = {...deepAccess(bid, 'ortb2Imp.ext.data')}; - Object.keys(ortb2).forEach(prop => { - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - if (prop === 'pbadslot') { - if (typeof ortb2[prop] === 'string' && ortb2[prop]) deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); - } else if (prop === 'adserver') { - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adslot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = deepAccess(ortb2, `adserver.${name}`); - if (typeof value === 'string' && value) { - deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); - // copy GAM ad unit id as imp[].ext.dfp_ad_unit_code - if (name === 'adslot') { - deepSetValue(imp, `ext.dfp_ad_unit_code`, value); - } - } - }); - } else { - deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); - } - }); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); - gpid && deepSetValue(imp, `ext.gpid`, gpid); +const setImpFields = imp => { + imp.displaymanager ||= 'Prebid.js'; + imp.displaymanagerver ||= '$prebid.version$'; + const gptAdSlot = imp.ext?.data?.adserver?.adslot; + if (gptAdSlot) imp.ext.dfp_ad_unit_code = gptAdSlot; + // Delete ext.data in case of no-adserver + if (imp.ext?.data && Object.keys(imp.ext.data).length === 0) delete imp.ext.data } function removeGranularFloor(imp, mediaTypes) { @@ -856,286 +257,389 @@ function removeGranularFloor(imp, mediaTypes) { }) } -function _addFloorFromFloorModule(impObj, bid) { +const setFloorInImp = (imp, bid) => { let bidFloor = -1; let requestedMediatypes = Object.keys(bid.mediaTypes); let isMultiFormatRequest = requestedMediatypes.length > 1 - // get lowest floor from floorModule if (typeof bid.getFloor === 'function' && !config.getConfig('pubmatic.disableFloors')) { [BANNER, VIDEO, NATIVE].forEach(mediaType => { - if (impObj.hasOwnProperty(mediaType)) { - let sizesArray = []; + if (!imp.hasOwnProperty(mediaType)) return; - if (mediaType === 'banner') { - if (impObj[mediaType].w && impObj[mediaType].h) { - sizesArray.push([impObj[mediaType].w, impObj[mediaType].h]); - } - if (isArray(impObj[mediaType].format)) { - impObj[mediaType].format.forEach(size => sizesArray.push([size.w, size.h])); - } - } - - if (sizesArray.length === 0) { - sizesArray.push('*') - } + const sizes = (mediaType === 'banner' + ? imp[mediaType]?.format?.map(({ w, h }) => [w, h]) + : ['*']) || ['*']; - sizesArray.forEach(size => { - let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: size }); - logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, ' and size:', size, ' is: currency', floorInfo.currency, 'floor', floorInfo.floor); - if (isPlainObject(floorInfo) && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { - let mediaTypeFloor = parseFloat(floorInfo.floor); - if (isMultiFormatRequest && mediaType !== BANNER) { - logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, 'is : ', mediaTypeFloor, 'with currency :', impObj.bidfloorcur); - impObj[mediaType]['ext'] = {'bidfloor': mediaTypeFloor, 'bidfloorcur': impObj.bidfloorcur}; - } - logInfo(LOG_WARN_PREFIX, 'floor from floor module:', mediaTypeFloor, 'previous floor value', bidFloor, 'Min:', Math.min(mediaTypeFloor, bidFloor)); - if (bidFloor === -1) { - bidFloor = mediaTypeFloor; - } else { - bidFloor = Math.min(mediaTypeFloor, bidFloor) - } - logInfo(LOG_WARN_PREFIX, 'new floor value:', bidFloor); + sizes.forEach(size => { + const floorInfo = bid.getFloor({ currency: imp.bidfloorcur, mediaType, size }); + logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, ' and size:', size, ' is: currency', floorInfo.currency, 'floor', floorInfo.floor); + + if (isPlainObject(floorInfo) && floorInfo?.currency === imp.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + const mediaTypeFloor = parseFloat(floorInfo.floor); + if (isMultiFormatRequest && mediaType !== BANNER) { + logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, 'is : ', mediaTypeFloor, 'with currency :', imp.bidfloorcur); + imp[mediaType]['ext'] = {'bidfloor': mediaTypeFloor, 'bidfloorcur': imp.bidfloorcur}; } - }); - if (isMultiFormatRequest && mediaType === BANNER) { - impObj[mediaType]['ext'] = {'bidfloor': bidFloor, 'bidfloorcur': impObj.bidfloorcur}; + logInfo(LOG_WARN_PREFIX, 'floor from floor module:', mediaTypeFloor, 'previous floor value', bidFloor, 'Min:', Math.min(mediaTypeFloor, bidFloor)); + bidFloor = bidFloor === -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor); + logInfo(LOG_WARN_PREFIX, 'new floor value:', bidFloor); } + }); + if (isMultiFormatRequest && mediaType === BANNER) { + imp[mediaType]['ext'] = {'bidfloor': bidFloor, 'bidfloorcur': imp.bidfloorcur}; } }); } - // get highest from impObj.bidfllor and floor from floor module - // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 - if (impObj.bidfloor) { - logInfo(LOG_WARN_PREFIX, 'floor from floor module:', bidFloor, 'impObj.bidfloor', impObj.bidfloor, 'Max:', Math.max(bidFloor, impObj.bidfloor)); - bidFloor = Math.max(bidFloor, impObj.bidfloor) + // Determine the highest value between imp.bidfloor and the floor from the floor module. + // Since we're using Math.max, it's safe if no floor is returned from the floor module, as bidFloor defaults to -1. + if (imp.bidfloor) { + logInfo(LOG_WARN_PREFIX, 'Comparing floors:', 'from floor module:', bidFloor, 'impObj.bidfloor:', imp.bidfloor, 'Max:', Math.max(bidFloor, imp.bidfloor)); + bidFloor = Math.max(bidFloor, imp.bidfloor); } - // assign value only if bidFloor is > 0 - impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : UNDEFINED); - logInfo(LOG_WARN_PREFIX, 'new impObj.bidfloor value:', impObj.bidfloor); + // Set imp.bidfloor only if bidFloor is greater than 0. + imp.bidfloor = (bidFloor > 0) ? bidFloor : UNDEFINED; + logInfo(LOG_WARN_PREFIX, 'Updated imp.bidfloor:', imp.bidfloor); // remove granular floor if impression level floor is same as granular - if (isMultiFormatRequest) removeGranularFloor(impObj, requestedMediatypes); + if (isMultiFormatRequest) removeGranularFloor(imp, requestedMediatypes); } -function _handleEids(payload, validBidRequests) { - let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); - if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { - deepSetValue(payload, 'user.eids', bidUserIdAsEids); +const updateBannerImp = (bannerObj, adSlot) => { + let slot = adSlot.split(':'); + let splits = slot[0]?.split('@'); + splits = splits?.length == 2 ? splits[1].split('x') : splits.length == 3 ? splits[2].split('x') : []; + const primarySize = bannerObj.format[0]; + if (splits.length !== 2 || (parseInt(splits[0]) == 0 && parseInt(splits[1]) == 0)) { + bannerObj.w = primarySize.w; + bannerObj.h = primarySize.h; + } else { + bannerObj.w = parseInt(splits[0]); + bannerObj.h = parseInt(splits[1]); } + + bannerObj.format = bannerObj.format.filter( + (item) => !(item.w === bannerObj.w && item.h === bannerObj.h) + ); + bannerObj.pos ??= 0; } -export function setTTL(bid, newBid) { - let ttl = MEDIATYPE_TTL[newBid?.mediaType] || DEFAULT_TTL; - newBid.ttl = bid.exp || ttl; +const setImpTagId = (imp, adSlot, hashedKey) => { + const splits = adSlot.split(':')[0].split('@'); + imp.tagid = hashedKey || splits[0]; } -// Setting IBV & meta.mediaType field into the bid response -export function setIBVField(bid, newBid) { - if (bid?.ext?.ibv) { - newBid.ext = newBid.ext || {}; - newBid.ext['ibv'] = bid.ext.ibv; +const updateNativeImp = (imp, nativeParams) => { + if (!nativeParams?.ortb) { + imp.native.request = JSON.stringify(toOrtbNativeRequest(nativeParams)); + } + if (nativeParams?.ortb) { + let nativeConfig = JSON.parse(imp.native.request); + const { assets } = nativeConfig; + if (!assets?.some(asset => asset.title || asset.img || asset.data || asset.video)) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or contains invalid objects`); + delete imp.native; + } else { + imp.native.request = JSON.stringify({ ver: '1.2', ...nativeConfig }); + } + } +} - // Overriding the mediaType field in meta with the `video` value if bid.ext.ibv is present - newBid.meta = newBid.meta || {}; - newBid.meta.mediaType = VIDEO; +const updateVideoImp = (videoImp, videoParams, adUnitCode, imp) => { + if (!deepAccess(videoParams, 'plcmt')) { + logWarn(MSG_VIDEO_PLCMT_MISSING + ' for ' + adUnitCode); + }; + if (!videoParams || (!videoImp.w && !videoImp.h)) { + delete imp.video; + logWarn(`${LOG_WARN_PREFIX}Error: Missing ${!videoParams ? 'video config params' : 'video size params (playersize or w&h)'} for adunit: ${adUnitCode} with mediaType set as video. Ignoring video impression in the adunit.`); } } -function _checkMediaType(bid, newBid) { - // Create a regex here to check the strings - if (bid.ext && bid.ext['bidtype'] != undefined) { - newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; +const addJWPlayerSegmentData = (imp, jwplayer) => { + const jwSegData = jwplayer?.targeting; + if (!jwSegData || !jwSegData.segments?.length) return; + const jwMark = 'jw-'; + const contentId = `${jwMark}id=${jwSegData.content.id}`; + const segmentData = jwSegData.segments.map(segment => `${jwMark}${segment}=1`).join('|'); + const jwPlayerData = `${contentId}|${segmentData}`; + imp.ext = imp.ext || {}; + imp.ext.key_val = imp.ext.key_val ? `${imp.ext.key_val}|${jwPlayerData}` : jwPlayerData; +}; + +const addDealCustomTargetings = (imp, dctr) => { + if (isStr(dctr) && dctr.length > 0) { + const arr = dctr.split('|').filter(val => val.trim().length > 0); + dctr = arr.map(val => val.trim()).join('|'); + imp.ext['key_val'] = dctr; } else { - logInfo(LOG_WARN_PREFIX + 'bid.ext.bidtype does not exist, checking alternatively for mediaType'); - var adm = bid.adm; - var admStr = ''; - var videoRegex = new RegExp(/VAST\s+version/); - if (adm.indexOf('span class="PubAPIAd"') >= 0) { - newBid.mediaType = BANNER; - } else if (FEATURES.VIDEO && videoRegex.test(adm)) { - newBid.mediaType = VIDEO; - } else { - try { - admStr = JSON.parse(adm.replace(/\\/g, '')); - if (admStr && admStr.native) { - newBid.mediaType = NATIVE; - } - } catch (e) { - logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + adm); - } - } + logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); } } -function _parseNativeResponse(bid, newBid) { - if (bid.hasOwnProperty('adm')) { - var adm = ''; - try { - adm = JSON.parse(bid.adm.replace(/\\/g, '')); - } catch (ex) { - logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); - return; - } - newBid.native = { - ortb: { ...adm.native } - }; - newBid.mediaType = NATIVE; - if (!newBid.width) { - newBid.width = DEFAULT_WIDTH; - } - if (!newBid.height) { - newBid.height = DEFAULT_HEIGHT; - } +const addPMPDeals = (imp, deals) => { + if (!isArray(deals)) { + logWarn(`${LOG_WARN_PREFIX}Error: bid.params.deals should be an array of strings.`); + return; } + deals.forEach(deal => { + if (typeof deal === 'string' && deal.length > 3) { + if (!imp.pmp) { + imp.pmp = { private_auction: 0, deals: [] }; + } + imp.pmp.deals.push({ id: deal }); + } else { + logWarn(`${LOG_WARN_PREFIX}Error: deal-id present in array bid.params.deals should be a string with more than 3 characters length, deal-id ignored: ${deal}`); + } + }); } -function _blockedIabCategoriesValidation(payload, blockedIabCategories) { - blockedIabCategories = blockedIabCategories - .filter(function(category) { - if (typeof category === 'string') { // only strings - return true; - } else { - logWarn(LOG_WARN_PREFIX + 'bcat: Each category should be a string, ignoring category: ' + category); - return false; - } - }) - .map(category => category.trim()) // trim all - .filter(function(category, index, arr) { // more than 3 charaters length - if (category.length > 3) { - return arr.indexOf(category) === index; // unique value only - } else { - logWarn(LOG_WARN_PREFIX + 'bcat: Each category should have a value of a length of more than 3 characters, ignoring category: ' + category) - } - }); - if (blockedIabCategories.length > 0) { - logWarn(LOG_WARN_PREFIX + 'bcat: Selected: ', blockedIabCategories); - payload.bcat = blockedIabCategories; +const updateRequestExt = (req, bidderRequest) => { + const allBiddersList = ['all']; + let allowedBiddersList = bidderSettings.get(bidderRequest.bidderCode, 'allowedAlternateBidderCodes'); + const biddersList = isArray(allowedBiddersList) + ? allowedBiddersList.map(val => val.trim().toLowerCase()).filter(uniques) + : allBiddersList; + req.ext.marketplace = { + allowedbidders: (biddersList.includes('*') || biddersList.includes('all')) ? allBiddersList : [...new Set(['pubmatic', ...biddersList.filter(val => val && val.trim())])] } } -function _allowedIabCategoriesValidation(payload, allowedIabCategories) { - allowedIabCategories = allowedIabCategories - .filter(function(category) { - if (typeof category === 'string') { // returns only strings - return true; - } else { - logWarn(LOG_WARN_PREFIX + 'acat: Each category should be a string, ignoring category: ' + category); - return false; - } - }) - .map(category => category.trim()) // trim all categories - .filter((category, index, arr) => arr.indexOf(category) === index); // return unique values only +const reqLevelParams = (req) => { + deepSetValue(req, 'at', AUCTION_TYPE); + deepSetValue(req, 'cur', [DEFAULT_CURRENCY]); + req.test = window.location.href.includes('pubmaticTest=true') ? 1 : undefined; + if (req.source && !Object.keys(req.source).length) delete req.source; + if (req.app?.publisher) req.app.publisher.id = pubId; +}; + +const updateUserSiteDevice = (req, bidRequest) => { + const { gender, yob, pubId, refURL, kadpageurl } = conf; + const { user } = req; + if (req.device) { + Object.assign(req.device, { js: 1, connectiontype: getConnectionType() }); + } + req.user = { + ...req.user, + gender: user?.gender || gender?.trim() || UNDEFINED, + yob: user?.yob || _parseSlotParam('yob', yob) + }; - if (allowedIabCategories.length > 0) { - logWarn(LOG_WARN_PREFIX + 'acat: Selected: ', allowedIabCategories); - payload.ext.acat = allowedIabCategories; + // start - IH eids for Prebid + const userIdAsEids = deepAccess(bidRequest, '0.userIdAsEids'); + if (bidRequest.length && userIdAsEids?.length && !req.user.ext?.eids) { + req.user.ext = req.user.ext || {}; + req.user.ext.eids = userIdAsEids; + } // end - IH eids for Prebid + + if (req.site?.publisher) { + req.site.ref = req.site.ref || refURL; + req.site.publisher.id = pubId?.trim(); + } + // if kadpageurl present then update site.page url with kadpageurl + if (req.site?.page && kadpageurl) req.site.page = kadpageurl.trim(); + // Check if geo information is present in device or user object + if (req.device.geo && !req.user.geo) { + req.user.geo = req.device.geo; + } else if (req.user.geo && !req.device.geo) { + req.device.geo = req.user.geo; } } -function _assignRenderer(newBid, request) { - let bidParams, context, adUnitCode; - if (request.bidderRequest && request.bidderRequest.bids) { - for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { - if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { - bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; +const updateResponseWithCustomFields = (res, bid, ctx) => { + const { ortbRequest, seatbid } = ctx; + res.referrer = ortbRequest.site?.ref || ''; + res.sspID = res.partnerImpId = bid.id || ''; + res.ad = bid.adm; + res.pm_dspid = bid.ext?.dspid ? bid.ext.dspid : null; + res.pm_seat = seatbid.seat; + if (!res.creativeId) res.creativeId = bid.id; + if (res.ttl == DEFAULT_TTL) res.ttl = MEDIATYPE_TTL[res.mediaType]; + if (bid.dealid) { + res.dealChannel = bid.ext?.deal_channel ? dealChannel[bid.ext.deal_channel] || null : 'PMP'; + } + if (seatbid.ext?.buyid) { + res.adserverTargeting = { 'hb_buyid_pubmatic': seatbid.ext.buyid } + } + if (bid.ext?.marketplace) { + res.bidderCode = bid.ext.marketplace; + } - if (FEATURES.VIDEO) { - context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; - } - adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; - } - } - if (context && context === 'outstream' && bidParams && bidParams.outstreamAU && adUnitCode) { - newBid.rendererCode = bidParams.outstreamAU; - newBid.renderer = BB_RENDERER.newRenderer(newBid.rendererCode, adUnitCode); - } + // add meta fields + // NOTE: We will not recieve below fields from the translator response also not sure on what will be the key names for these in the response, + // when we needed we can add it back. + // New fields added, assignee fields name may change + // if (bid.ext.networkName) res.meta.networkName = bid.ext.networkName; + // if (bid.ext.advertiserName) res.meta.advertiserName = bid.ext.advertiserName; + // if (bid.ext.agencyName) res.meta.agencyName = bid.ext.agencyName; + // if (bid.ext.brandName) res.meta.brandName = bid.ext.brandName; + if (bid.ext) { + const { dspid, dchain, dsa, ibv } = bid.ext; + if (dspid) res.meta.networkId = res.meta.demandSource = dspid; + if (dchain) res.meta.dchain = dchain; + if (dsa && Object.keys(dsa).length) res.meta.dsa = dsa; + if (ibv) { + res.ext = res.ext || {}; + res.ext['ibv'] = ibv; + res.meta.mediaType = VIDEO; + } + } + + const advid = seatbid.seat || bid.ext?.advid; + if (advid) res.meta.advertiserId = res.meta.agencyId = res.meta.buyerId = advid; + + if (isNonEmptyArray(bid.adomain)) { + res.meta.clickUrl = res.meta.brandId = bid.adomain[0]; + } +} + +const addExtenstionParams = (req) => { + const { profId, verId, wiid, transactionId } = conf; + req.ext = { + epoch: new Date().getTime(), // Sending epoch timestamp in request.ext object + wrapper: { + profile: profId ? parseInt(profId) : undefined, + version: verId ? parseInt(verId) : undefined, + wiid: wiid, + wv: '$$REPO_AND_VERSION$$', + transactionId, + wp: 'pbjs' + }, + cpmAdjustment: cpmAdjustment } } /** * In case of adpod video context, assign prebiddealpriority to the dealtier property of adpod-video bid, * so that adpod module can set the hb_pb_cat_dur targetting key. - * @param {*} newBid * @param {*} bid - * @param {*} request + * @param {*} context + * @param {*} maxduration * @returns */ -export function assignDealTier(newBid, bid, request) { +const assignDealTier = (bid, context, maxduration) => { if (!bid?.ext?.prebiddealpriority || !FEATURES.VIDEO) return; - const bidRequest = getBidRequest(newBid.requestId, [request.bidderRequest]); - const videoObj = deepAccess(bidRequest, 'mediaTypes.video'); - if (videoObj?.context != ADPOD) return; + if (context != ADPOD) return; - const duration = bid?.ext?.video?.duration || videoObj?.maxduration; + const duration = bid?.ext?.video?.duration || maxduration; // if (!duration) return; - newBid.video = { + bid.video = { context: ADPOD, durationSeconds: duration, dealTier: bid.ext.prebiddealpriority }; } -function isNonEmptyArray(test) { - if (isArray(test) === true) { - if (test.length > 0) { - return true; - } - } - return false; +const validateAllowedCategories = (acat) => { + return [...new Set( + acat + .filter(item => { + if (typeof item === 'string') { + return true; + } else { + logWarn(LOG_WARN_PREFIX + 'acat: Each category should be a string, ignoring category: ' + item); + } + }) + .map(item => item.trim()) + )]; +}; + +const validateBlockedCategories = (bcats) => { + bcats = bcats.map(item => typeof item === 'string' ? item.trim() : item); + const droppedCategories = bcats.filter(item => typeof item !== 'string' || item.length < 3); + logWarn(LOG_WARN_PREFIX + 'bcat: Each category must be a string with a length greater than 3, ignoring ' + droppedCategories); + return [...new Set(bcats.filter(item => typeof item === 'string' && item.length >= 3))]; } -/** - * Prepare meta object to pass as params - * @param {*} br : bidResponse - * @param {*} bid : bids - */ -export function prepareMetaObject(br, bid, seat) { - br.meta = br.meta || {}; +const getConnectionType = () => { + let connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection); + const types = { ethernet: 1, wifi: 2, 'slow-2g': 4, '2g': 4, '3g': 5, '4g': 6 }; + return types[connection?.effectiveType] || 0; +} - if (bid.ext && bid.ext.dspid) { - br.meta.networkId = bid.ext.dspid; - br.meta.demandSource = bid.ext.dspid; - } +// BB stands for Blue BillyWig +const BB_RENDERER = { + bootstrapPlayer: function(bid) { + const config = { + code: bid.adUnitCode, + vastXml: bid.vastXml || null, + vastUrl: bid.vastUrl || null, + }; - // NOTE: We will not recieve below fields from the translator response also not sure on what will be the key names for these in the response, - // when we needed we can add it back. - // New fields added, assignee fields name may change - // if (bid.ext.networkName) br.meta.networkName = bid.ext.networkName; - // if (bid.ext.advertiserName) br.meta.advertiserName = bid.ext.advertiserName; - // if (bid.ext.agencyName) br.meta.agencyName = bid.ext.agencyName; - // if (bid.ext.brandName) br.meta.brandName = bid.ext.brandName; - if (bid.ext && bid.ext.dchain) { - br.meta.dchain = bid.ext.dchain; - } + if (!config.vastXml && !config.vastUrl) { + logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`); + return; + } - const advid = seat || (bid.ext && bid.ext.advid); - if (advid) { - br.meta.advertiserId = advid; - br.meta.agencyId = advid; - br.meta.buyerId = advid; - } + const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode); + const ele = document.getElementById(bid.adUnitCode); // NB convention - if (bid.adomain && isNonEmptyArray(bid.adomain)) { - br.meta.advertiserDomains = bid.adomain; - br.meta.clickUrl = bid.adomain[0]; - br.meta.brandId = bid.adomain[0]; - } + const renderer = window.bluebillywig.renderers.find(r => r._id === rendererId); + if (renderer) renderer.bootstrap(config, ele); + else logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`); + }, - if (bid.cat && isNonEmptyArray(bid.cat)) { - br.meta.secondaryCatIds = bid.cat; - br.meta.primaryCatId = bid.cat[0]; + newRenderer: function(rendererCode, adUnitCode) { + const rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode); + const renderer = Renderer.install({ url: rendererUrl, loaded: false, adUnitCode }); + try { + renderer.setRender(BB_RENDERER.outstreamRender); + } catch (err) { + logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err); + } + return renderer; + }, + + outstreamRender: function(bid) { + bid.renderer.push(() => BB_RENDERER.bootstrapPlayer(bid)); + }, + + getRendererId: function(pub, renderer) { + return `${pub}-${renderer}`; // NB convention! } +}; - if (bid.ext && bid.ext.dsa && Object.keys(bid.ext.dsa).length) { - br.meta.dsa = bid.ext.dsa; +function _parseSlotParam(paramName, paramValue) { + if (!isStr(paramValue)) { + paramValue && logWarn(LOG_WARN_PREFIX + 'Ignoring param key: ' + paramName + ', expects string-value, found ' + typeof paramValue); + return UNDEFINED; } - // Initializing meta.mediaType field to the actual bidType returned by the bidder - if (br.mediaType) { - br.meta.mediaType = br.mediaType; + const parsers = { + pmzoneid: () => paramValue.split(',').slice(0, 50).map(id => id.trim()).join(), + kadfloor: () => parseFloat(paramValue), + lat: () => parseFloat(paramValue), + lon: () => parseFloat(paramValue), + yob: () => parseInt(paramValue) + }; + return parsers[paramName]?.() || paramValue; +} + +function isNonEmptyArray(test) { + if (isArray(test) === true) { + if (test.length > 0) { + return true; + } } + return false; } +const getPublisherId = (bids) => + Array.isArray(bids) && bids.length > 0 + ? bids.find(bid => bid.params?.publisherId?.trim())?.params.publisherId || null + : null; + +const _handleCustomParams = (params, conf) => { + Object.keys(CUSTOM_PARAMS).forEach(key => { + const value = params[key]; + if (value) { + if (isStr(value)) { + conf[key] = value; + } else { + logWarn(`${LOG_WARN_PREFIX}Ignoring param: ${key} with value: ${CUSTOM_PARAMS[key]}, expects string value, found ${typeof value}`); + } + } + }); + return conf; +}; + export const spec = { code: BIDDER_CODE, gvlid: 76, @@ -1147,291 +651,84 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: bid => { - if (bid && bid.params) { - if (!isStr(bid.params.publisherId)) { - logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric (wrap it in quotes in your config). Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + if (!(bid && bid.params)) return false; + const { publisherId } = bid.params; + const mediaTypes = bid.mediaTypes || {}; + const videoMediaTypes = mediaTypes[VIDEO] || {}; + if (!isStr(publisherId)) { + logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be numeric (wrap it in quotes in your config). Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + if (FEATURES.VIDEO && mediaTypes.hasOwnProperty(VIDEO)) { + // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array + const mediaTypesVideoMimes = deepAccess(bid, 'mediaTypes.video.mimes'); + const paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); + if (!isNonEmptyArray(mediaTypesVideoMimes) && !isNonEmptyArray(paramsVideoMimes)) { + logWarn(LOG_WARN_PREFIX + 'Error: For video ads, bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); return false; } - // video ad validation - if (FEATURES.VIDEO && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { - // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array - let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); - let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); - if (isNonEmptyArray(mediaTypesVideoMimes) === false && isNonEmptyArray(paramsVideoMimes) === false) { - logWarn(LOG_WARN_PREFIX + 'Error: For video ads, bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); - return false; - } - - if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { - logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid); - return false; - } - - if (bid.mediaTypes[VIDEO].context === 'outstream' && - !isStr(bid.params.outstreamAU) && - !bid.hasOwnProperty('renderer') && - !bid.mediaTypes[VIDEO].hasOwnProperty('renderer')) { - // we are here since outstream ad-unit is provided without outstreamAU and renderer - // so it is not a valid video ad-unit - // but it may be valid banner or native ad-unit - // so if mediaType banner or Native is present then we will remove media-type video and return true - - if (bid.mediaTypes.hasOwnProperty(BANNER) || bid.mediaTypes.hasOwnProperty(NATIVE)) { - delete bid.mediaTypes[VIDEO]; - logWarn(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting mediatype Video of bid: `, bid); - return true; - } else { - logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); - return false; - } + if (!videoMediaTypes.context) { + logError(`${LOG_WARN_PREFIX}: No context specified in bid. Rejecting bid: `, bid); + return false; + } + if (videoMediaTypes.context === 'outstream' && !isStr(bid.params.outstreamAU) && +!bid.renderer && !videoMediaTypes.renderer) { + if (mediaTypes.hasOwnProperty(BANNER) || mediaTypes.hasOwnProperty(NATIVE)) { + delete mediaTypes[VIDEO]; + logWarn(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting mediatype Video of bid: `, bid); + return true; } + logError(`${LOG_WARN_PREFIX}: for "outstream" bids either outstreamAU parameter must be provided or ad unit supplied renderer is required. Rejecting bid: `, bid); + return false; } - return true; } - return false; + return true; }, /** * Make a server request from the list of BidRequests. * + * @param {validBidRequests} - an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - // validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - var refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; + const { page, ref } = bidderRequest?.refererInfo || {}; + const { publisherId, profId, verId } = bidderRequest?.bids?.[0]?.params || {}; + pubId = publisherId?.trim() || getPublisherId(bidderRequest?.bids)?.trim(); + const wiid = generateUUID(); + let bid; + blockedIabCategories = []; + allowedIabCategories = []; + conf = { + pageURL: page || window.location.href, + refURL: ref || window.document.referrer, + pubId, + kadpageurl: page || window.location.href, + profId: profId, + verId: verId } - var conf = _initConf(refererInfo); - var payload = _createOrtbTemplate(conf); - var bidCurrency = ''; - var dctrArr = []; - var bid; - var blockedIabCategories = []; - var allowedIabCategories = []; - var wiid = generateUUID(); - validBidRequests.forEach(originalBid => { originalBid.params.wiid = originalBid.params.wiid || bidderRequest.auctionId || wiid; bid = deepClone(originalBid); - bid.params.adSlot = bid.params.adSlot || ''; - _parseAdSlot(bid); - if ((bid.mediaTypes && bid.mediaTypes.hasOwnProperty('video')) || bid.params.hasOwnProperty('video')) { - // Nothing to do - } else { - // If we have a native mediaType configured alongside banner, its ok if the banner size is not set in width and height - // The corresponding banner imp object will not be generated, but we still want the native object to be sent, hence the following check - if (!(bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(NATIVE)) && bid.params.width === 0 && bid.params.height === 0) { - logWarn(LOG_WARN_PREFIX + 'Skipping the non-standard adslot: ', bid.params.adSlot, JSON.stringify(bid)); - return; - } - } - conf.pubId = conf.pubId || bid.params.publisherId; - conf = _handleCustomParams(bid.params, conf); + _handleCustomParams(bid.params, conf); conf.transactionId = bid.ortb2Imp?.ext?.tid; - if (bidCurrency === '') { - bidCurrency = bid.params.currency || UNDEFINED; - } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { - logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); - } - bid.params.currency = bidCurrency; - // check if dctr is added to more than 1 adunit - if (bid.params.hasOwnProperty('dctr') && isStr(bid.params.dctr)) { - dctrArr.push(bid.params.dctr); - } - if (bid.params.hasOwnProperty('bcat') && isArray(bid.params.bcat)) { - blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); + const { bcat, acat } = bid.params; + if (bcat) { + blockedIabCategories = blockedIabCategories.concat(bcat); } - if (bid.params.hasOwnProperty('acat') && isArray(bid.params.acat)) { - allowedIabCategories = allowedIabCategories.concat(bid.params.acat); - } - var impObj = _createImpressionObject(bid, bidderRequest); - if (impObj) { - payload.imp.push(impObj); - } - }); - - if (payload.imp.length == 0) { - return; - } - - payload.site.publisher.id = conf.pubId.trim(); - publisherId = conf.pubId.trim(); - payload.ext.wrapper = {}; - payload.ext.wrapper.profile = parseInt(conf.profId) || UNDEFINED; - payload.ext.wrapper.version = parseInt(conf.verId) || UNDEFINED; - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - payload.ext.wrapper.wiid = conf.wiid || bidderRequest.auctionId; - // eslint-disable-next-line no-undef - payload.ext.wrapper.wv = $$REPO_AND_VERSION$$; - payload.ext.wrapper.transactionId = conf.transactionId; - payload.ext.wrapper.wp = 'pbjs'; - // Set cpmAdjustment of last auction - payload.ext.cpmAdjustment = cpmAdjustment; - const allowAlternateBidder = bidderRequest ? bidderSettings.get(bidderRequest.bidderCode, 'allowAlternateBidderCodes') : undefined; - if (allowAlternateBidder !== undefined) { - payload.ext.marketplace = {}; - if (bidderRequest && allowAlternateBidder == true) { - let allowedBiddersList = bidderSettings.get(bidderRequest.bidderCode, 'allowedAlternateBidderCodes'); - if (isArray(allowedBiddersList)) { - allowedBiddersList = allowedBiddersList.map(val => val.trim().toLowerCase()).filter(val => !!val).filter(uniques); - biddersList = allowedBiddersList.includes('*') ? allBiddersList : [...biddersList, ...allowedBiddersList]; - } else { - biddersList = allBiddersList; - } + if (acat) { + allowedIabCategories = allowedIabCategories.concat(acat); } - payload.ext.marketplace.allowedbidders = biddersList.filter(uniques); - } - - payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); - payload.user.geo = {}; - // TODO: fix lat and long to only come from request object, not params - payload.user.yob = _parseSlotParam('yob', conf.yob); - payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); - payload.site.domain = _getDomainFromURL(payload.site.page); - - // add the content object from config in request - if (typeof config.getConfig('content') === 'object') { - payload.site.content = config.getConfig('content'); - } - - // merge the device from config.getConfig('device') - if (typeof config.getConfig('device') === 'object') { - payload.device = Object.assign(payload.device, config.getConfig('device')); - } - - // update device.language to ISO-639-1-alpha-2 (2 character language) - payload.device.language = payload.device.language && payload.device.language.split('-')[0]; - - // passing transactionId in source.tid - deepSetValue(payload, 'source.tid', bidderRequest?.ortb2?.source?.tid); - - // test bids - if (window.location.href.indexOf('pubmaticTest=true') !== -1) { - payload.test = 1; - } - - // adding schain object - if (validBidRequests[0].schain) { - deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); - } - - // Attaching GDPR Consent Params - if (bidderRequest && bidderRequest.gdprConsent) { - deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); - } - - // CCPA - if (bidderRequest && bidderRequest.uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - // Attaching GPP Consent Params - if (bidderRequest?.gppConsent?.gppString) { - deepSetValue(payload, 'regs.gpp', bidderRequest.gppConsent.gppString); - deepSetValue(payload, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); - } else if (bidderRequest?.ortb2?.regs?.gpp) { - deepSetValue(payload, 'regs.gpp', bidderRequest.ortb2.regs.gpp); - deepSetValue(payload, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); - } - - // coppa compliance - if (config.getConfig('coppa') === true) { - deepSetValue(payload, 'regs.coppa', 1); - } - - // dsa - if (bidderRequest?.ortb2?.regs?.ext?.dsa) { - deepSetValue(payload, 'regs.ext.dsa', bidderRequest.ortb2.regs.ext.dsa); - } - - _handleEids(payload, validBidRequests); - - // First Party Data - const commonFpd = (bidderRequest && bidderRequest.ortb2) || {}; - const { user, device, site, bcat, badv } = commonFpd; - if (site) { - const { page, domain, ref } = payload.site; - mergeDeep(payload, {site: site}); - payload.site.page = page; - payload.site.domain = domain; - payload.site.ref = ref; - } - if (user) { - mergeDeep(payload, {user: user}); - } - if (badv) { - mergeDeep(payload, {badv: badv}); - } - if (bcat) { - blockedIabCategories = blockedIabCategories.concat(bcat); - } - // check if fpd ortb2 contains device property with sua object - if (device?.sua) { - payload.device.sua = device?.sua; - } - - if (device?.ext?.cdep) { - deepSetValue(payload, 'device.ext.cdep', device.ext.cdep); - } - - if (user?.geo && device?.geo) { - payload.device.geo = { ...payload.device.geo, ...device.geo }; - payload.user.geo = { ...payload.user.geo, ...user.geo }; - } else { - if (user?.geo || device?.geo) { - payload.user.geo = payload.device.geo = (user?.geo ? { ...payload.user.geo, ...user.geo } : { ...payload.user.geo, ...device.geo }); - } - } - - // if present, merge device object from ortb2 into `payload.device` - if (bidderRequest?.ortb2?.device) { - mergeDeep(payload.device, bidderRequest.ortb2.device); - } - - if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { - const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; - _allowedIabCategoriesValidation(payload, acatParams); - } else if (allowedIabCategories.length) { - _allowedIabCategoriesValidation(payload, allowedIabCategories); - } - _blockedIabCategoriesValidation(payload, blockedIabCategories); - - // Check if bidderRequest has timeout property if present send timeout as tmax value to translator request - // bidderRequest has timeout property if publisher sets during calling requestBids function from page - // if not bidderRequest contains global value set by Prebid - if (bidderRequest?.timeout) { - payload.tmax = bidderRequest.timeout; - } else { - payload.tmax = window?.PWT?.versionDetails?.timeout; - } - - // Sending epoch timestamp in request.ext object - payload.ext.epoch = new Date().getTime(); - - // Note: Do not move this block up - // if site object is set in Prebid config then we need to copy required fields from site into app and unset the site object - if (typeof config.getConfig('app') === 'object') { - payload.app = config.getConfig('app'); - // not copying domain from site as it is a derived value from page - payload.app.publisher = payload.site.publisher; - payload.app.ext = payload.site.ext || UNDEFINED; - // We will also need to pass content object in app.content if app object is also set into the config; - // BUT do not use content object from config if content object is present in app as app.content - if (typeof payload.app.content !== 'object') { - payload.app.content = payload.site.content || UNDEFINED; - } - delete payload.site; - } + }) + const data = converter.toORTB({ validBidRequests, bidderRequest }); - return { + let serverRequest = { method: 'POST', url: ENDPOINT, - data: JSON.stringify(payload), + data: data, bidderRequest: bidderRequest }; + return data?.imp?.length ? serverRequest : null; }, /** @@ -1441,122 +738,39 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (response, request) => { - const bidResponses = []; - var respCur = DEFAULT_CURRENCY; - let parsedRequest = JSON.parse(request.data); - let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; - try { - if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { - // Supporting multiple bid responses for same adSize - respCur = response.body.cur || respCur; - response.body.seatbid.forEach(seatbidder => { - seatbidder.bid && - isArray(seatbidder.bid) && - seatbidder.bid.forEach(bid => { - let newBid = { - requestId: bid.impid, - cpm: parseFloat((bid.price || 0).toFixed(2)), - width: bid.w, - height: bid.h, - creativeId: bid.crid || bid.id, - dealId: bid.dealid, - currency: respCur, - netRevenue: NET_REVENUE, - ttl: DEFAULT_TTL, - referrer: parsedReferrer, - ad: bid.adm, - pm_seat: seatbidder.seat || null, - pm_dspid: bid.ext && bid.ext.dspid ? bid.ext.dspid : null, - partnerImpId: bid.id || '' // partner impression Id - }; - if (parsedRequest.imp && parsedRequest.imp.length > 0) { - parsedRequest.imp.forEach(req => { - if (bid.impid === req.id) { - _checkMediaType(bid, newBid); - setTTL(bid, newBid); - switch (newBid.mediaType) { - case BANNER: - break; - case FEATURES.VIDEO && VIDEO: - newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; - newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; - newBid.vastXml = bid.adm; - _assignRenderer(newBid, request); - assignDealTier(newBid, bid, request); - break; - case NATIVE: - _parseNativeResponse(bid, newBid); - break; - } - } - }); - } - prepareMetaObject(newBid, bid, seatbidder.seat); - setIBVField(bid, newBid); - if (bid.ext && bid.ext.deal_channel) { - newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; - } - - // adserverTargeting - if (seatbidder.ext && seatbidder.ext.buyid) { - newBid.adserverTargeting = { - 'hb_buyid_pubmatic': seatbidder.ext.buyid - }; - } - - // if from the server-response the bid.ext.marketplace is set then - // submit the bid to Prebid as marketplace name - if (bid.ext && !!bid.ext.marketplace) { - newBid.bidderCode = bid.ext.marketplace; - } - - bidResponses.push(newBid); - }); - }); - } - let fledgeAuctionConfigs = deepAccess(response.body, 'ext.fledge_auction_configs'); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return { - bidId, - config: Object.assign({ - auctionSignals: {}, - }, cfg) - } - }); - return { - bids: bidResponses, - paapi: fledgeAuctionConfigs, - } - } - } catch (error) { - logError(error); - } - - return bidResponses; + const { bids } = converter.fromORTB({ response: response.body, request: request.data }); + const fledgeAuctionConfigs = deepAccess(response.body, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + return { + bids, + paapi: Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => ({ + bidId, + config: { auctionSignals: {}, ...cfg } + })) + }; + } + return bids; }, /** * Register User Sync. */ getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { - let syncurl = '' + publisherId; + let syncurl = pubId; // Attaching GDPR Consent Params in UserSync url if (gdprConsent) { - syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + syncurl += `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`; } // CCPA if (uspConsent) { - syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + syncurl += `&us_privacy=${encodeURIComponent(uspConsent)}`; } // GPP Consent if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { - syncurl += '&gpp=' + encodeURIComponent(gppConsent.gppString); - syncurl += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); + syncurl += `&gpp=${encodeURIComponent(gppConsent.gppString)}&gpp_sid=${encodeURIComponent(gppConsent.applicableSections.join(','))}`; } // coppa compliance @@ -1564,17 +778,9 @@ export const spec = { syncurl += '&coppa=1'; } - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: USER_SYNC_URL_IFRAME + syncurl - }]; - } else { - return [{ - type: 'image', - url: USER_SYNC_URL_IMAGE + syncurl - }]; - } + const type = syncOptions.iframeEnabled ? 'iframe' : 'image'; + const url = (type === 'iframe' ? USER_SYNC_URL_IFRAME : USER_SYNC_URL_IMAGE) + syncurl; + return [{ type, url }]; }, onBidWon: (bid) => { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 674b9b1c13b..c468470d201 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,4585 +1,1063 @@ import { expect } from 'chai'; -import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField, setTTL, cpmAdjustment } from 'modules/pubmaticBidAdapter.js'; +import { spec, cpmAdjustment } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; -import { config } from 'src/config.js'; -import { createEidsArray } from 'modules/userId/eids.js'; import { bidderSettings } from 'src/bidderSettings.js'; -const constants = require('src/constants.js'); -describe('PubMatic adapter', function () { - let bidRequests; - let videoBidRequests; - let multipleMediaRequests; - let bidResponses; - let nativeBidRequests; - let nativeBidRequestsWithAllParams; - let nativeBidRequestsWithoutAsset; - let nativeBidRequestsWithRequiredParam; - let nativeBidResponse; - let validnativeBidImpression; - let validnativeBidImpressionWithRequiredParam; - let nativeBidImpressionWithoutRequiredParams; - let validnativeBidImpressionWithAllParams; - let bannerAndVideoBidRequests; - let bannerAndNativeBidRequests; - let videoAndNativeBidRequests; - let bannerVideoAndNativeBidRequests; - let bannerBidResponse; - let videoBidResponse; - let schainConfig; - let outstreamBidRequest; - let validOutstreamBidRequest; - let outstreamVideoBidResponse; - - beforeEach(function () { - schainConfig = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'indirectseller.com', - 'sid': '00001', - 'hp': 1 - }, - - { - 'asi': 'indirectseller-2.com', - 'sid': '00002', - 'hp': 2 - } - ] - }; - - bidRequests = [ - { - bidder: 'pubmatic', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]] - } - }, - params: { - publisherId: '5670', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - dctr: 'key1:val1,val2|key2:val1' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - ortb2Imp: { - ext: { - tid: '92489f71-1bf2-49a0-adf9-000cea934729', - gpid: '/1111/homepage-leftnav' - } - }, - schain: schainConfig - } - ]; - - videoBidRequests = - [ - { - code: 'video1', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream' - } - }, - bidder: 'pubmatic', - bidId: '22bddb28db77d', - params: { - publisherId: '5890', - adSlot: 'Div1@0x0', // ad_id or tagid - wiid: 'new-unique-wiid', - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30, - startdelay: 5, - playbackmethod: [1, 3], - api: [1, 2], - protocols: [2, 3], - battr: [13, 14], - linearity: 1, - placement: 2, - plcmt: 1, - minbitrate: 10, - maxbitrate: 10 - } - } +describe('PubMatic adapter', () => { + let firstBid, videoBid, firstResponse, response, videoResponse; + let request = {}; + firstBid = { + adUnitCode: 'Div1', + bidder: 'pubmatic', + mediaTypes: { + banner: { + sizes: [[728, 90], [160, 600]], + pos: 1 } - ]; - - multipleMediaRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200' - }, - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } + }, + params: { + publisherId: '5670', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + // kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'AUD', + dctr: 'key1:val1,val2|key2:val1', + deals: ['deal-1', 'deal-2'] + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [ + [300, 250], + [300, 600], + ['fluid'] + ], + bidId: '3736271c3c4b27', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + ortb2: { + device: { + w: 1200, + h: 1800, + sua: {}, + language: 'en', + js: 1, + connectiontype: 6 }, - { - code: 'div-instream', - mediaTypes: { - video: { - context: 'instream', - playerSize: [300, 250] - }, - }, - bidder: 'pubmatic', - params: { - publisherId: '5890', - adSlot: 'Div1@640x480', // ad_id or tagid - wiid: '1234567890', - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30, - startdelay: 15, - playbackmethod: [1, 3], - api: [1, 2], - protocols: [2, 3], - w: 640, - h: 480, - battr: [13, 14], - linearity: 1, - placement: 2, - plcmt: 1, - minbitrate: 100, - maxbitrate: 4096 - } - } - } - ]; - - nativeBidRequests = [{ - code: '/19968336/prebid_native_example_1', - sizes: [ - [300, 250] - ], - mediaTypes: { - native: { - title: { - required: true, - length: 80 - }, - image: { - required: true, - sizes: [300, 250] + site: {domain: 'ebay.com', page: 'https://ebay.com'}, + source: {} + }, + ortb2Imp: { + ext: { + tid: '92489f71-1bf2-49a0-adf9-000cea934729', + gpid: '/1111/homepage-leftnav', + data: { + pbadslot: '/1111/homepage-leftnav', + adserver: { + name: 'gam', + adslot: '/1111/homepage-leftnav' }, - sponsoredBy: { - required: true + customData: { + id: 'id-1' } } - }, - nativeParams: { - title: { required: true, length: 80 }, - image: { required: true, sizes: [300, 250] }, - sponsoredBy: { required: true } - }, - nativeOrtbRequest: { - ver: '1.2', - assets: [ - { id: 0, required: 1, title: {len: 140} }, - { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, - { id: 2, required: 1, data: { type: 1 } } - ] - }, - bidder: 'pubmatic', - params: { - publisherId: '5670', - adSlot: '/43743431/NativeAutomationPrebid@1x1', - wiid: 'new-unique-wiid' - }, - bidId: '2a5571261281d4', - requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', - bidderRequestId: '1c56ad30b9b8ca8', - }]; - - nativeBidRequestsWithAllParams = [{ - code: '/19968336/prebid_native_example_1', - sizes: [ - [300, 250] - ], - mediaTypes: { - native: { - title: {required: true, len: 80, ext: {'title1': 'title2'}}, - icon: {required: true, sizes: [50, 50], ext: {'icon1': 'icon2'}}, - image: {required: true, sizes: [728, 90], ext: {'image1': 'image2'}, 'mimes': ['image/png', 'image/gif']}, - sponsoredBy: {required: true, len: 10, ext: {'sponsor1': 'sponsor2'}}, - body: {required: true, len: 10, ext: {'body1': 'body2'}}, - rating: {required: true, len: 10, ext: {'rating1': 'rating2'}}, - likes: {required: true, len: 10, ext: {'likes1': 'likes2'}}, - downloads: {required: true, len: 10, ext: {'downloads1': 'downloads2'}}, - price: {required: true, len: 10, ext: {'price1': 'price2'}}, - saleprice: {required: true, len: 10, ext: {'saleprice1': 'saleprice2'}}, - phone: {required: true, len: 10, ext: {'phone1': 'phone2'}}, - address: {required: true, len: 10, ext: {'address1': 'address2'}}, - desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, - displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} - } - }, - nativeParams: { - title: {required: true, len: 80, ext: {'title1': 'title2'}}, - icon: {required: true, sizes: [50, 50], ext: {'icon1': 'icon2'}}, - image: {required: true, sizes: [728, 90], ext: {'image1': 'image2'}, 'mimes': ['image/png', 'image/gif']}, - sponsoredBy: {required: true, len: 10, ext: {'sponsor1': 'sponsor2'}}, - body: {required: true, len: 10, ext: {'body1': 'body2'}}, - rating: {required: true, len: 10, ext: {'rating1': 'rating2'}}, - likes: {required: true, len: 10, ext: {'likes1': 'likes2'}}, - downloads: {required: true, len: 10, ext: {'downloads1': 'downloads2'}}, - price: {required: true, len: 10, ext: {'price1': 'price2'}}, - saleprice: {required: true, len: 10, ext: {'saleprice1': 'saleprice2'}}, - phone: {required: true, len: 10, ext: {'phone1': 'phone2'}}, - address: {required: true, len: 10, ext: {'address1': 'address2'}}, - desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, - displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} - }, - nativeOrtbRequest: { - 'ver': '1.2', - 'assets': [ - {'id': 0, 'required': 1, 'title': {'len': 80}}, - {'id': 1, 'required': 1, 'img': {'type': 1, 'w': 50, 'h': 50}}, - {'id': 2, 'required': 1, 'img': {'type': 3, 'w': 728, 'h': 90}}, - {'id': 3, 'required': 1, 'data': {'type': 1, 'len': 10}}, - {'id': 4, 'required': 1, 'data': {'type': 2, 'len': 10}}, - {'id': 5, 'required': 1, 'data': {'type': 3, 'len': 10}}, - {'id': 6, 'required': 1, 'data': {'type': 4, 'len': 10}}, - {'id': 7, 'required': 1, 'data': {'type': 5, 'len': 10}}, - {'id': 8, 'required': 1, 'data': {'type': 6, 'len': 10}}, - {'id': 9, 'required': 1, 'data': {'type': 8, 'len': 10}}, - {'id': 10, 'required': 1, 'data': {'type': 9, 'len': 10}} - ] - }, - bidder: 'pubmatic', - params: { - publisherId: '5670', - adSlot: '/43743431/NativeAutomationPrebid@1x1', - wiid: 'new-unique-wiid' - }, - bidId: '2a5571261281d4', - requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', - bidderRequestId: '1c56ad30b9b8ca8', - }]; - - nativeBidRequestsWithoutAsset = [{ - code: '/19968336/prebid_native_example_1', - sizes: [ - [300, 250] - ], - mediaTypes: { - native: { - type: 'image' - } - }, - nativeParams: { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }, - bidder: 'pubmatic', - params: { - publisherId: '5670', - adSlot: '/43743431/NativeAutomationPrebid@1x1', - wiid: 'new-unique-wiid' } - }]; - - nativeBidRequestsWithRequiredParam = [{ - code: '/19968336/prebid_native_example_1', - sizes: [ - [300, 250] - ], - mediaTypes: { - native: { - title: { - required: false, - length: 80 - }, - image: { - required: false, - sizes: [300, 250] - }, - sponsoredBy: { - required: true - } - } - }, - nativeParams: { - title: { required: false, length: 80 }, - image: { required: false, sizes: [300, 250] }, - sponsoredBy: { required: true } + }, + } + videoBid = { + 'seat': 'seat-id', + 'ext': { + 'buyid': 'BUYER-ID-987' + }, + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '3736271c3c4b27', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', + 'adomain': ['blackrock.com'], + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6, + 'advid': 976, + 'dspid': 123 }, - nativeOrtbRequest: { - ver: '1.2', - assets: [ - { id: 0, required: 0, title: {len: 140} }, - { id: 1, required: 0, img: {type: 3, w: 300, h: 250} }, - { id: 2, required: 1, data: {type: 1} } - ] + 'dealid': 'PUBDEAL1', + 'mtype': 2 + }] + }; + firstResponse = { + 'seat': 'seat-id', + 'ext': { + 'buyid': 'BUYER-ID-987' + }, + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '3736271c3c4b27', + 'price': 1.3, + 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['blackrock.com'], + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6, + 'advid': 976, + 'dspid': 123 }, - bidder: 'pubmatic', - params: { - publisherId: '5670', - adSlot: '/43743431/NativeAutomationPrebid@1x1', - wiid: 'new-unique-wiid' - } - }]; - - bannerAndVideoBidRequests = [ - { - code: 'div-banner-video', - ortb2Imp: { - banner: { - pos: 1 - } - }, - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream', - pos: 2 - }, - banner: { - sizes: [[300, 250], [300, 600]], - pos: 1 - } - }, - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - dctr: 'key1:val1,val2|key2:val1', - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30, - startdelay: 15, - playbackmethod: [1, 3], - api: [1, 2], - protocols: [2, 3], - w: 640, - h: 480, - battr: [13, 14], - linearity: 1, - placement: 2, - plcmt: 1, - minbitrate: 100, - maxbitrate: 4096 - } - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[728, 90]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - - bannerAndNativeBidRequests = [ - { - code: 'div-banner-native', - mediaTypes: { - native: { - title: { - required: true, - length: 80 - }, - image: { - required: true, - sizes: [300, 250] - }, - sponsoredBy: { - required: true - } - }, - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - nativeParams: { - title: { required: true, length: 80 }, - image: { required: true, sizes: [300, 250] }, - sponsoredBy: { required: true } - }, - nativeOrtbRequest: { - ver: '1.2', - assets: [ - {id: 0, required: 1, title: {len: 140}}, - {id: 1, required: 1, img: {type: 3, w: 300, h: 250}}, - {id: 2, required: 1, data: {type: 1}} - ] - }, - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - dctr: 'key1:val1,val2|key2:val1' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[728, 90]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - - videoAndNativeBidRequests = [ - { - code: 'div-video-native', - mediaTypes: { - native: { - title: { - required: true, - length: 80 - }, - image: { - required: true, - sizes: [300, 250] - }, - sponsoredBy: { - required: true - } - }, - video: { - playerSize: [640, 480], - context: 'instream' - } - }, - nativeParams: { - title: { required: true, length: 80 }, - image: { required: true, sizes: [300, 250] }, - sponsoredBy: { required: true } - }, - nativeOrtbRequest: { - ver: '1.2', - assets: [ - { id: 0, required: 1, title: {len: 140} }, - { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, - { id: 2, required: 1, data: { type: 1 } } - ] - }, - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - wiid: 'new-unique-wiid', - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30, - startdelay: 15, - playbackmethod: [1, 3], - api: [1, 2], - protocols: [2, 3], - w: 640, - h: 480, - battr: [13, 14], - linearity: 1, - placement: 2, - plcmt: 1, - minbitrate: 100, - maxbitrate: 4096 - } - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[728, 90]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - - bannerVideoAndNativeBidRequests = [ - { - code: 'div-video-native', - mediaTypes: { - native: { - title: { - required: true, - length: 80 - }, - image: { - required: true, - sizes: [300, 250] - }, - sponsoredBy: { - required: true - } - }, - video: { - playerSize: [640, 480], - context: 'instream' - }, - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - nativeParams: { - title: { required: true, length: 80 }, - image: { required: true, sizes: [300, 250] }, - sponsoredBy: { required: true } - }, - nativeOrtbRequest: { - ver: '1.2', - assets: [ - { id: 0, required: 1, title: {len: 80} }, - { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, - { id: 2, required: 1, data: { type: 1 } } - ] - }, - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - wiid: 'new-unique-wiid', - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30, - startdelay: 15, - playbackmethod: [1, 3], - api: [1, 2], - protocols: [2, 3], - w: 640, - h: 480, - battr: [13, 14], - linearity: 1, - placement: 2, - plcmt: 1, - minbitrate: 100, - maxbitrate: 4096 - } - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[728, 90]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - - bidResponses = { - 'body': { - 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', - 'seatbid': [{ - 'seat': 'seat-id', - 'ext': { - 'buyid': 'BUYER-ID-987' - }, - 'bid': [{ - 'id': '74858439-49D7-4169-BA5D-44A046315B2F', - 'impid': '22bddb28db77d', - 'price': 1.3, - 'adm': 'image3.pubmatic.com Layer based creative', - 'adomain': ['blackrock.com'], - 'h': 250, - 'w': 300, - 'ext': { - 'deal_channel': 6, - 'advid': 976, - 'dspid': 123, - 'dchain': 'dchain' - } - }] - }, { - 'ext': { - 'buyid': 'BUYER-ID-789' - }, - 'bid': [{ - 'id': '74858439-49D7-4169-BA5D-44A046315BEF', - 'impid': '22bddb28db77e', - 'price': 1.7, - 'adm': 'image3.pubmatic.com Layer based creative', - 'adomain': ['hivehome.com'], - 'h': 250, - 'w': 300, - 'ext': { - 'deal_channel': 5, - 'advid': 832, - 'dspid': 422 - } - }] - }] - } - }; - - nativeBidResponse = { - 'body': { - 'id': '1544691825939', - 'seatbid': [{ - 'bid': [{ - 'id': 'B68287E1-DC39-4B38-9790-FE4F179739D6', - 'impid': '2a5571261281d4', - 'price': 0.01, - 'adm': "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Native Test Title\"}},{\"id\":2,\"img\":{\"h\":627,\"url\":\"http://stagingpub.net/native_ads/PM-Native-Ad-1200x627.png\",\"w\":1200}},{\"data\":{\"value\":\"Sponsored By PubMatic\"},\"id\":4}],\"imptrackers\":[\"http://imptracker.com/main/9bde02d0-6017-11e4-9df7-005056967c35\",\"http://172.16.4.213/AdServer/AdDisplayTrackerServlet?operId=1&pubId=5890&siteId=5892&adId=6016&adType=12&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=7&kltstamp=1544692761&indirectAdId=0&adServerOptimizerId=2&ranreq=0.1&kpbmtpfact=1.000000&dcId=1&tldId=0&passback=0&svr=MADS1107&ekefact=GSQSXOLKDgBAvRnoiNj0LxtpAnNEO30u1ZI5sITloOsP7gzh&ekaxefact=GSQSXAXLDgD0fOZLCjgbnVJiyS3D65dqDkxfs2ArpC3iugXw&ekpbmtpfact=GSQSXCDLDgB5mcooOvXtCKmx7TnNDJDY2YuHFOL3o9ceoU4H&crID=campaign111&lpu=advertiserdomain.com&ucrid=273354366805642829&campaignId=16981&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=6&wbId=0&wrId=0&wAdvID=1&isRTB=1&rtbId=C09BB577-B8C1-4C3E-A0FF-73F6F631C80A&imprId=B68287E1-DC39-4B38-9790-FE4F179739D6&oid=B68287E1-DC39-4B38-9790-FE4F179739D6&pageURL=http%3A%2F%2Ftest.com%2FTestPages%2Fnativead.html\"],\"jstracker\":\" ', - 'h': 250, - 'w': 300, - 'ext': { - 'deal_channel': 6 - } - }] - }] - } - }; - - videoBidResponse = { - 'body': { - 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', - 'seatbid': [{ - 'bid': [{ - 'id': '74858439-49D7-4169-BA5D-44A046315B2F', - 'impid': '22bddb28db77d', - 'price': 1.3, - 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', - 'h': 250, - 'w': 300, - 'ext': { - 'deal_channel': 6 - } - }] - }] - } - }; - outstreamBidRequest = - [ - { - code: 'video1', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream' - } - }, - bidder: 'pubmatic', - bidId: '47acc48ad47af5', - requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - params: { - publisherId: '5670', - outstreamAU: 'pubmatic-test', - adSlot: 'Div1@0x0', // ad_id or tagid - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30 - } - } - } - ]; - - validOutstreamBidRequest = { - auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', - auctionStart: 1585918458868, - bidderCode: 'pubmatic', - bidderRequestId: '47acc48ad47af5', - bids: [{ - adUnitCode: 'video1', - auctionId: '92489f71-1bf2-49a0-adf9-000cea934729', - bidId: '47acc48ad47af5', - bidRequestsCount: 1, - bidder: 'pubmatic', - bidderRequestId: '47acc48ad47af5', - mediaTypes: { - video: { - context: 'outstream' - } - }, - params: { - publisherId: '5670', - outstreamAU: 'pubmatic-test', - adSlot: 'Div1@0x0', // ad_id or tagid - video: { - mimes: ['video/mp4', 'video/x-flv'], - skippable: true, - minduration: 5, - maxduration: 30 - } - }, - sizes: [[768, 432], [640, 480], [630, 360]], - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - }], - start: 11585918458869, - timeout: 3000 - }; - - outstreamVideoBidResponse = { - 'body': { - 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', - 'seatbid': [{ - 'bid': [{ - 'id': '0fb4905b-1234-4152-86be-c6f6d259ba99', - 'impid': '47acc48ad47af5', - 'price': 1.3, - 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', - 'h': 250, - 'w': 300, - 'ext': { - 'deal_channel': 6 - } - }] - }] - } - }; - }); - - describe('implementation', function () { - describe('Bid validations', function () { - it('valid bid case', function () { - let validBid = { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0' - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - - it('invalid bid case: publisherId not passed', function () { - let validBid = { - bidder: 'pubmatic', - params: { - adSlot: '/15671365/DMDemo@300x250:0' - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('invalid bid case: publisherId is not string', function () { - let validBid = { - bidder: 'pubmatic', - params: { - publisherId: 301, - adSlot: '/15671365/DMDemo@300x250:0' - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('valid bid case: adSlot is not passed', function () { - let validBid = { - bidder: 'pubmatic', - params: { - publisherId: '301' - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); - }); - - if (FEATURES.VIDEO) { - it('should check for context if video is present', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }) - - it('should return false if context is not present in video', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' - }, - 'mediaTypes': { - 'video': { - 'w': 640, - 'h': 480, - 'protocols': [1, 2, 5], - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }) - - it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': {} - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }; - - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = 'string'; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); - - delete bid.params.video.mimes; // Undefined - delete bid.mediaTypes.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); - - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); - - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video = {mimes: 'string'}; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); - - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); - - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); - - delete bid.mediaTypes.video.mimes; // Undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); - - delete bid.mediaTypes.video.mimes; // Undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { - const getThebid = function() { - let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); - bid.params.outstreamAU = 'pubmatic-test'; - bid.renderer = ' '; // we are only checking if this key is set or not - bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not - return bid; - } - - // true: when all are present - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : Y - // bid.mediaTypes.video.renderer : Y - let bid = getThebid(); - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : Y - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : Y - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // false: none present; only outstream - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - // true: none present; outstream + Banner - // mdiatype: outstream, banner - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: none present; outstream + Native - // mdiatype: outstream, native - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.native = {} - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - } - }); - - describe('Request formation', function () { - it('buildRequests function should not modify original bidRequests object', function () { - let originalBidRequests = utils.deepClone(bidRequests); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - expect(bidRequests).to.deep.equal(originalBidRequests); - }); - - it('buildRequests function should not modify original nativebidRequests object', function () { - let originalBidRequests = utils.deepClone(nativeBidRequests); - let request = spec.buildRequests(nativeBidRequests, { - auctionId: 'new-auction-id' - }); - expect(nativeBidRequests).to.deep.equal(originalBidRequests); - }); - - it('Endpoint checking', function () { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - expect(request.url).to.equal('https://hbopenbid.pubmatic.com/translator?source=prebid-client'); - expect(request.method).to.equal('POST'); - }); - - it('should return bidderRequest property', function() { - let request = spec.buildRequests(bidRequests, validOutstreamBidRequest); - expect(request.bidderRequest).to.equal(validOutstreamBidRequest); - }); - - it('bidderRequest should be undefined if bidderRequest is not present', function() { - let request = spec.buildRequests(bidRequests); - expect(request.bidderRequest).to.be.undefined; - }); - - it('test flag not sent when pubmaticTest=true is absent in page url', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.test).to.equal(undefined); - }); - - it('Request params check', function () { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - ortb2: { - source: { - tid: 'source-tid' - }, - device: { - geo: { - lat: '36.5189', - lon: '-76.4063' - } - }, - user: { - geo: { - lat: '26.8915', - lon: '-56.6340' - } - }, - } - }); - let data = JSON.parse(request.data); - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId - expect(data.source.tid).to.equal('source-tid'); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); - expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); - expect(data.ext.epoch).to.exist; - expect(data.imp[0].displaymanager).to.equal('Prebid.js'); - expect(data.imp[0].displaymanagerver).to.equal('$prebid.version$'); - }); - - it('Set tmax from global config if not set by requestBids method', function() { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - bidderTimeout: 3000 - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', timeout: 3000 - }); - let data = JSON.parse(request.data); - expect(data.tmax).to.deep.equal(3000); - sandbox.restore(); - }); - describe('Marketplace parameters', function() { - let bidderSettingStub; - beforeEach(function() { - bidderSettingStub = sinon.stub(bidderSettings, 'get'); - }); - - afterEach(function() { - bidderSettingStub.restore(); - }); - - it('should not be present when allowAlternateBidderCodes is undefined', function () { - bidderSettingStub.returns(undefined); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.ext.marketplace).to.equal(undefined); - }); - - it('should be pubmatic and groupm when allowedAlternateBidderCodes is \'groupm\'', function () { - bidderSettingStub.withArgs('pubmatic', 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs('pubmatic', 'allowedAlternateBidderCodes').returns(['groupm']); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - bidderCode: 'pubmatic' - }); - let data = JSON.parse(request.data); - expect(data.ext.marketplace.allowedbidders).to.be.an('array'); - expect(data.ext.marketplace.allowedbidders.length).to.equal(2); - expect(data.ext.marketplace.allowedbidders[0]).to.equal('pubmatic'); - expect(data.ext.marketplace.allowedbidders[1]).to.equal('groupm'); - }); - - it('should be ALL by default', function () { - bidderSettingStub.returns(true); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.ext.marketplace.allowedbidders).to.be.an('array'); - expect(data.ext.marketplace.allowedbidders[0]).to.equal('all'); - }); - - it('should be ALL when allowedAlternateBidderCodes is \'*\'', function () { - bidderSettingStub.withArgs('pubmatic', 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs('pubmatic', 'allowedAlternateBidderCodes').returns(['*']); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - bidderCode: 'pubmatic' - }); - let data = JSON.parse(request.data); - expect(data.ext.marketplace.allowedbidders).to.be.an('array'); - expect(data.ext.marketplace.allowedbidders[0]).to.equal('all'); - }); - }) - - it('Set content from config, set site.content', function() { - let sandbox = sinon.sandbox.create(); - const content = { - 'id': 'alpha-numeric-id' - }; - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - content: content - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.site.content).to.deep.equal(content); - sandbox.restore(); - }); - - it('Merge the device info from config', function() { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - device: { - 'newkey': 'new-device-data' - } - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.device.js).to.equal(1); - expect(data.device.dnt).to.equal((navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0); - expect(data.device.h).to.equal(screen.height); - expect(data.device.w).to.equal(screen.width); - expect(data.device.language).to.equal(navigator.language.split('-')[0]); - expect(data.device.newkey).to.equal('new-device-data');// additional data from config - sandbox.restore(); - }); - - it('Merge the device info from config; data from config overrides the info we have gathered', function() { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - device: { - newkey: 'new-device-data', - language: 'MARATHI' - } - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.device.js).to.equal(1); - expect(data.device.dnt).to.equal((navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0); - expect(data.device.h).to.equal(screen.height); - expect(data.device.w).to.equal(screen.width); - expect(data.device.language).to.equal('MARATHI');// // data overriding from config - expect(data.device.newkey).to.equal('new-device-data');// additional data from config - sandbox.restore(); - }); - - it('Set app from config, copy publisher and ext from site, unset site', function() { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - app: { - bundle: 'org.prebid.mobile.demoapp', - domain: 'prebid.org' - } - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); - expect(data.app.domain).to.equal('prebid.org'); - expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); - expect(data.site).to.not.exist; - sandbox.restore(); - }); - - it('Set app, content from config, copy publisher and ext from site, unset site, config.content in app.content', function() { - let sandbox = sinon.sandbox.create(); - const content = { - 'id': 'alpha-numeric-id' - }; - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - content: content, - app: { - bundle: 'org.prebid.mobile.demoapp', - domain: 'prebid.org' - } - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); - expect(data.app.domain).to.equal('prebid.org'); - expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); - expect(data.app.content).to.deep.equal(content); - expect(data.site).to.not.exist; - sandbox.restore(); - }); - - it('Set app.content, content from config, copy publisher and ext from site, unset site, config.app.content in app.content', function() { - let sandbox = sinon.sandbox.create(); - const content = { - 'id': 'alpha-numeric-id' - }; - const appContent = { - id: 'app-content-id-2' - }; - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - content: content, - app: { - bundle: 'org.prebid.mobile.demoapp', - domain: 'prebid.org', - content: appContent - } - }; - return config[key]; - }); - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.app.bundle).to.equal('org.prebid.mobile.demoapp'); - expect(data.app.domain).to.equal('prebid.org'); - expect(data.app.publisher.id).to.equal(bidRequests[0].params.publisherId); - expect(data.app.content).to.deep.equal(appContent); - expect(data.site).to.not.exist; - sandbox.restore(); - }); - - it('Request params check: without adSlot', function () { - delete bidRequests[0].params.adSlot; - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - ortb2: { - device: { - geo: { - lat: '36.5189', - lon: '-76.4063' - } - }, - user: { - geo: { - lat: '26.8915', - lon: '-56.6340' - } - }, - } - }); - let data = JSON.parse(request.data); - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.deep.equal(undefined); // tagid - expect(data.imp[0].banner.w).to.equal(728); // width - expect(data.imp[0].banner.h).to.equal(90); // height - expect(data.imp[0].banner.format).to.deep.equal([{w: 160, h: 600}]); - expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); - expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); - expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - }); - - it('Request params multi size format object check', function () { - let bidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD' - }, - placementCode: '/19968336/header-bid-tag-1', - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - } - ]; - /* case 1 - size passed in adslot */ - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - - /* case 2 - size passed in adslot as well as in sizes array */ - bidRequests[0].sizes = [[300, 600], [300, 250]]; - bidRequests[0].mediaTypes = { - banner: { - sizes: [[300, 600], [300, 250]] - } - }; - request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - - /* case 3 - size passed in sizes but not in adslot */ - bidRequests[0].params.adSlot = '/15671365/DMDemo'; - bidRequests[0].sizes = [[300, 250], [300, 600]]; - bidRequests[0].mediaTypes = { - banner: { - sizes: [[300, 250], [300, 600]] - } - }; - request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].banner.format).exist.and.to.be.an('array'); - expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); - expect(data.imp[0].banner.format[0].w).to.equal(300); // width - expect(data.imp[0].banner.format[0].h).to.equal(600); // height - }); - - it('Request params currency check', function () { - let multipleBidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'GBP' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - } - ]; - - /* case 1 - - currency specified in both adunits - output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency - - */ - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); - - /* case 2 - - currency specified in only 1st adunit - output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency - - */ - delete multipleBidRequests[1].params.currency; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); - - /* case 3 - - currency specified in only 1st adunit - output: imp[0] and imp[1] both use default currency - USD - - */ - delete multipleBidRequests[0].params.currency; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.imp[1].bidfloorcur).to.equal('USD'); - - /* case 4 - - currency not specified in 1st adunit but specified in 2nd adunit - output: imp[0] and imp[1] both use default currency - USD - - */ - multipleBidRequests[1].params.currency = 'AUD'; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.imp[1].bidfloorcur).to.equal('USD'); - }); - - it('Pass auctiondId as wiid if wiid is not passed in params', function () { - let bidRequest = { - auctionId: 'new-auction-id', - ortb2: { - device: { - geo: { - lat: '36.5189', - lon: '-76.4063' - } - }, - user: { - geo: { - lat: '26.8915', - lon: '-56.6340' - } - }, - } - }; - delete bidRequests[0].params.wiid; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal('new-auction-id'); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); - expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - }); - - it('Request params check with GDPR Consent', function () { - let bidRequest = { - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - }, - ortb2: { - device: { - geo: { - lat: '36.5189', - lon: '-76.4063' - } - }, - user: { - geo: { - lat: '26.8915', - lon: '-56.6340' - } - }, - } - }; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.user.ext.consent).to.equal('kjfdniwjnifwenrif3'); - expect(data.regs.ext.gdpr).to.equal(1); - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - }); - - it('Request params check with USP/CCPA Consent', function () { - let bidRequest = { - uspConsent: '1NYN', - ortb2: { - device: { - geo: { - lat: '36.5189', - lon: '-76.4063' - } - }, - user: { - geo: { - lat: '26.8915', - lon: '-56.6340' - } - }, - } - }; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.regs.ext.us_privacy).to.equal('1NYN');// USP/CCPAs - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); - expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - - // second request without USP/CCPA - let request2 = spec.buildRequests(bidRequests, {}); - let data2 = JSON.parse(request2.data); - expect(data2.regs).to.equal(undefined);// USP/CCPAs - }); - - it('Request params should include DSA signals if present', function () { - const dsa = { - dsarequired: 3, - pubrender: 0, - datatopub: 2, - transparency: [ - { - domain: 'platform1domain.com', - dsaparams: [1] - }, - { - domain: 'SSP2domain.com', - dsaparams: [1, 2] - } - ] - }; - - let bidRequest = { - ortb2: { - regs: { - ext: { - dsa - } - } - } - }; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - assert.deepEqual(data.regs.ext.dsa, dsa); - }); - - it('Request params check with JW player params', function() { - let bidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - dctr: 'key1=val1|key2=val2,val3' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - rtd: { - jwplayer: { - targeting: { - content: { id: 'jw_d9J2zcaA' }, - segments: ['80011026', '80011035'] - } - } - }, - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - }]; - let key_val_output = 'key1=val1|key2=val2,val3|jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1' - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].ext).to.exist.and.to.be.an('object'); - expect(data.imp[0].ext.key_val).to.exist.and.to.equal(key_val_output); - - // jw player data not available. Only dctr sent. - delete bidRequests[0].rtd; - request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - - expect(data.imp[0].ext).to.exist.and.to.be.an('object'); // dctr parameter - expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); - - // jw player data is available, but dctr is not present - bidRequests[0].rtd = { - jwplayer: { - targeting: { - content: { id: 'jw_d9J2zcaA' }, - segments: ['80011026', '80011035'] - } - } - }; - - delete bidRequests[0].params.dctr; - key_val_output = 'jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'; - request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - - expect(data.imp[0].ext).to.exist.and.to.be.an('object'); - expect(data.imp[0].ext.key_val).to.exist.and.to.equal(key_val_output); - }); - - describe('FPD', function() { - let newRequest; - - describe('ortb2.site should not override page, domain & ref values', function() { - it('When above properties are present in ortb2.site', function() { - const ortb2 = { - site: { - domain: 'page.example.com', - page: 'https://page.example.com/here.html', - ref: 'https://page.example.com/here.html' - } - }; - const request = spec.buildRequests(bidRequests, {ortb2}); - let data = JSON.parse(request.data); - expect(data.site.domain).not.equal('page.example.com'); - expect(data.site.page).not.equal('https://page.example.com/here.html'); - expect(data.site.ref).not.equal('https://page.example.com/here.html'); - }); - - it('When above properties are absent in ortb2.site', function () { - const ortb2 = { - site: {} - }; - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - ortb2 - }); - let data = JSON.parse(request.data); - let response = spec.interpretResponse(bidResponses, request); - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); - expect(data.site.domain).to.equal(_getDomainFromURL(data.site.page)); - expect(response[0].referrer).to.equal(data.site.ref); - }); - - it('With some extra properties in ortb2.site', function() { - const ortb2 = { - site: { - domain: 'page.example.com', - page: 'https://page.example.com/here.html', - ref: 'https://page.example.com/here.html', - cat: ['IAB2'], - sectioncat: ['IAB2-2'] - } - }; - const request = spec.buildRequests(bidRequests, {ortb2}); - let data = JSON.parse(request.data); - expect(data.site.domain).not.equal('page.example.com'); - expect(data.site.page).not.equal('https://page.example.com/here.html'); - expect(data.site.ref).not.equal('https://page.example.com/here.html'); - expect(data.site.cat).to.deep.equal(['IAB2']); - expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); - }); - }); - - it('ortb2.site should be merged except page, domain & ref in the request', function() { - const ortb2 = { - site: { - cat: ['IAB2'], - sectioncat: ['IAB2-2'] - } - }; - const request = spec.buildRequests(bidRequests, {ortb2}); - let data = JSON.parse(request.data); - expect(data.site.cat).to.deep.equal(['IAB2']); - expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); - }); - - it('ortb2.user should be merged in the request', function() { - const ortb2 = { - user: { - yob: 1985 - } - }; - const request = spec.buildRequests(bidRequests, {ortb2}); - let data = JSON.parse(request.data); - expect(data.user.yob).to.equal(1985); - }); - - it('ortb2.badv should be merged in the request', function() { - const ortb2 = { - badv: ['example.com'] - }; - const request = spec.buildRequests(bidRequests, {ortb2}); - let data = JSON.parse(request.data); - expect(data.badv).to.deep.equal(['example.com']); - }); - - describe('ortb2Imp', function() { - describe('ortb2Imp.ext.gpid', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should send gpid if imp[].ext.gpid is specified', function() { - bidRequests[0].ortb2Imp = { - ext: { - gpid: 'ortb2Imp.ext.gpid' - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext).to.have.property('gpid'); - expect(data.imp[0].ext.gpid).to.equal('ortb2Imp.ext.gpid'); - }); - - it('should not send if imp[].ext.gpid is not specified', function() { - bidRequests[0].ortb2Imp = { ext: { } }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext).to.not.have.property('gpid'); - }); - }); - - describe('ortb2Imp.ext.data.pbadslot', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should not send if imp[].ext.data object is invalid', function() { - bidRequests[0].ortb2Imp = { - ext: {} - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext).to.not.have.property('data'); - }); - - it('should not send if imp[].ext.data.pbadslot is undefined', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('should not send if imp[].ext.data.pbadslot is empty string', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - pbadslot: '' - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('should send if imp[].ext.data.pbadslot is string', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - pbadslot: 'abcd' - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext.data).to.have.property('pbadslot'); - expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); - }); - }); - - describe('ortb2Imp.ext.data.adserver', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should not send if imp[].ext.data object is invalid', function() { - bidRequests[0].ortb2Imp = { - ext: {} - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext).to.not.have.property('data'); - }); - - it('should not send if imp[].ext.data.adserver is undefined', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('adserver'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('should send', function() { - let adSlotValue = 'abc'; - bidRequests[0].ortb2Imp = { - ext: { - data: { - adserver: { - name: 'GAM', - adslot: adSlotValue - } - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); - expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); - expect(data.imp[0].ext.dfp_ad_unit_code).to.equal(adSlotValue); - }); - }); - - describe('ortb2Imp.ext.data.other', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should not send if imp[].ext.data object is invalid', function() { - bidRequests[0].ortb2Imp = { - ext: {} - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext).to.not.have.property('data'); - }); - - it('should not send if imp[].ext.data.other is undefined', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('other'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('ortb2Imp.ext.data.other', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - other: 1234 - } - } - }; - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.imp[0].ext.data.other).to.equal(1234); - }); - }); - }); - }); - - describe('setting imp.floor using floorModule', function() { - /* - Use the minimum value among floor from floorModule per mediaType - If params.adfloor is set then take max(kadfloor, min(floors from floorModule)) - set imp.bidfloor only if it is more than 0 - */ - - let newRequest; - let floorModuleTestData; - let getFloor = function(req) { - // actual getFloor module does not work like this :) - // special treatment for banner since for other mediaTypes we pass * - if (req.mediaType === 'banner') { - return floorModuleTestData[req.mediaType][ req.size[0] + 'x' + req.size[1] ] || {}; - } - return floorModuleTestData[req.mediaType] || {}; - }; - - beforeEach(() => { - floorModuleTestData = { - 'banner': { - '300x250': { - 'currency': 'USD', - 'floor': 1.50 - }, - '300x600': { - 'currency': 'USD', - 'floor': 2.0 - } - }, - 'video': { - 'currency': 'USD', - 'floor': 2.50 - }, - 'native': { - 'currency': 'USD', - 'floor': 3.50 - } - }; - newRequest = utils.deepClone(bannerVideoAndNativeBidRequests); - newRequest[0].getFloor = getFloor; - }); - - it('bidfloor should be undefined if calculation is <= 0', function() { - floorModuleTestData.banner['300x250'].floor = 0; // lowest of them all - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(undefined); - }); - - if (FEATURES.VIDEO) { - it('ignore floormodule o/p if floor is not number', function() { - floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; - floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); - - it('ignore floormodule o/p if currency is not matched', function() { - floorModuleTestData.banner['300x250'].currency = 'INR'; - floorModuleTestData.banner['300x600'].currency = 'INR'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); - } - - it('kadfloor is not passed, use minimum from floorModule', function() { - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(1.5); - }); - - it('should use mediatype specific floor for multiformat request', function() { - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(1.50); - expect(data.native.ext.bidfloor).to.equal(3.50); - }); - - it('should use delete granular floor if impression level floor is same as granular level', function() { - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(1.50); - expect(data.bidfloorcur).to.equal('USD'); - expect(data.banner.ext).to.equal(undefined); - expect(data.native.ext.bidfloor).to.equal(3.50); - }); - - it('kadfloor is passed as 3, use kadfloor as it is highest', function() { - newRequest[0].params.kadfloor = '3.0';// yes, we want it as a string - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(3); - }); - - it('kadfloor is passed as 1, use min of floorModule as it is highest', function() { - newRequest[0].params.kadfloor = '1.0';// yes, we want it as a string - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(1.5); - }); - }); - - it('should NOT include coppa flag in bid request if coppa config is not present', () => { - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.regs) { - // in case GDPR is set then data.regs will exist - expect(data.regs.coppa).to.equal(undefined); - } else { - expect(data.regs).to.equal(undefined); - } - }); - - it('should include coppa flag in bid request if coppa is set to true', () => { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; - }); - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.regs.coppa).to.equal(1); - sandbox.restore(); - }); - - it('should NOT include coppa flag in bid request if coppa is set to false', () => { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': false - }; - return config[key]; - }); - const request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - if (data.regs) { - // in case GDPR is set then data.regs will exist - expect(data.regs.coppa).to.equal(undefined); - } else { - expect(data.regs).to.equal(undefined); - } - sandbox.restore(); - }); - - describe('userIdAsEids', function() { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('Request should have EIDs', function() { - bidRequests[0].userId = {}; - bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; - bidRequests[0].userIdAsEids = [{ - 'source': 'adserver.org', - 'uids': [{ - 'id': 'TTD_ID_FROM_USER_ID_MODULE', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]; - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(bidRequests[0].userIdAsEids); - }); - - it('Request should NOT have EIDs userIdAsEids is NOT object', function() { - let request = spec.buildRequests(bidRequests, {}); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal(undefined); - }); - }); - - it('should pass device.sua if present in bidderRequest fpd ortb2 object', function () { - const suaObject = {'source': 2, 'platform': {'brand': 'macOS', 'version': ['12', '4', '0']}, 'browsers': [{'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']}], 'mobile': 0, 'model': '', 'bitness': '64', 'architecture': 'x86'}; - let request = spec.buildRequests(multipleMediaRequests, { - auctionId: 'new-auction-id', - ortb2: { - device: { - sua: suaObject - } - } - }); - let data = JSON.parse(request.data); - expect(data.device.sua).to.exist.and.to.be.an('object'); - expect(data.device.sua).to.deep.equal(suaObject); - }); - - it('should pass device.ext.cdep if present in bidderRequest fpd ortb2 object', function () { - const cdepObj = { - cdep: 'example_label_1' - }; - let request = spec.buildRequests(multipleMediaRequests, { - auctionId: 'new-auction-id', - ortb2: { - device: { - ext: cdepObj - } - } - }); - let data = JSON.parse(request.data); - expect(data.device.ext.cdep).to.exist.and.to.be.an('string'); - expect(data.device.ext).to.deep.equal(cdepObj); - }); - - it('should pass enriched device data from ortb2 object if present in bidderRequest fpd', function () { - const fpdBidderRequest = { - auctionId: 'new-auction-id', - ortb2: { - device: { - w: 980, - h: 1720, - dnt: 0, - ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', - language: 'en', - devicetype: 1, - make: 'Apple', - model: 'iPhone 12 Pro Max', - os: 'iOS', - osv: '17.4', - } - }, - }; - - const request = spec.buildRequests(multipleMediaRequests, fpdBidderRequest); - const data = JSON.parse(request.data); - - expect(data.device.w).to.equal(fpdBidderRequest.ortb2.device.w); - expect(data.device.h).to.equal(fpdBidderRequest.ortb2.device.h); - expect(data.device.dnt).to.equal(fpdBidderRequest.ortb2.device.dnt); - expect(data.device.ua).to.equal(fpdBidderRequest.ortb2.device.ua); - expect(data.device.language).to.equal(fpdBidderRequest.ortb2.device.language); - expect(data.device.devicetype).to.equal(fpdBidderRequest.ortb2.device.devicetype); - expect(data.device.make).to.equal(fpdBidderRequest.ortb2.device.make); - expect(data.device.model).to.equal(fpdBidderRequest.ortb2.device.model); - expect(data.device.os).to.equal(fpdBidderRequest.ortb2.device.os); - expect(data.device.osv).to.equal(fpdBidderRequest.ortb2.device.osv); - }); - - it('Request params should have valid native bid request for all valid params', function () { - let request = spec.buildRequests(nativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].native).to.exist; - expect(data.imp[0].native['request']).to.exist; - expect(data.imp[0].tagid).to.equal('/43743431/NativeAutomationPrebid'); - expect(data.imp[0]['native']['request']).to.exist.and.to.be.an('string'); - expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpression.native.request); - }); - - it('Request params should not have valid native bid request for non native request', function () { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].native).to.not.exist; - }); - - it('Request params should have valid native bid request with valid required param values for all valid params', function () { - let request = spec.buildRequests(nativeBidRequestsWithRequiredParam, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].native).to.exist; - expect(data.imp[0].native['request']).to.exist; - expect(data.imp[0].tagid).to.equal('/43743431/NativeAutomationPrebid'); - expect(data.imp[0]['native']['request']).to.exist.and.to.be.an('string'); - expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithRequiredParam.native.request); - }); - - it('Request params should have valid native bid request for all native params', function () { - let request = spec.buildRequests(nativeBidRequestsWithAllParams, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].native).to.exist; - expect(data.imp[0].native['request']).to.exist; - expect(data.imp[0].tagid).to.equal('/43743431/NativeAutomationPrebid'); - expect(data.imp[0]['native']['request']).to.exist.and.to.be.an('string'); - expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithAllParams.native.request); - }); - - it('Request params - should handle banner and native format in single adunit', function() { - let request = spec.buildRequests(bannerAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - banner and native multiformat request - should have native object incase of invalid config present', function() { - bannerAndNativeBidRequests[0].mediaTypes.native = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }; - bannerAndNativeBidRequests[0].nativeParams = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - } - let request = spec.buildRequests(bannerAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.native).to.exist; - }); - - it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { - delete bannerAndNativeBidRequests[0].mediaTypes.banner; - bannerAndNativeBidRequests[0].sizes = [729, 90]; - - let request = spec.buildRequests(bannerAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - if (FEATURES.VIDEO) { - it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { - delete bannerAndVideoBidRequests[0].mediaTypes.banner; - bannerAndVideoBidRequests[0].params.sizes = [300, 250]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.not.exist; - }); - - it('Request params check for 1 banner and 1 video ad', function () { - let request = spec.buildRequests(multipleMediaRequests, { - auctionId: 'new-auction-id', - ortb2: { - device: { - geo: { - lat: '36.5189', - lon: '-76.4063' - } - }, - user: { - geo: { - lat: '26.8915', - lon: '-56.6340' - } - }, - } - }); - let data = JSON.parse(request.data); - - expect(data.imp).to.be.an('array') - expect(data.imp).with.length.above(1); - - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal('36.5189'); // Latitude - expect(data.device.geo.lon).to.equal('-76.4063'); // Lognitude - expect(data.user.geo.lat).to.equal('26.8915'); // Latitude - expect(data.user.geo.lon).to.equal('-56.6340'); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - // banner imp object check - expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - - // video imp object check - expect(data.imp[1].video).to.exist; - expect(data.imp[1].tagid).to.equal('Div1'); - expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); - expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); - expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); - expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); - expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); - - expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); - expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); - - expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); - expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); - - expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); - expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); - - expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); - expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); - - expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); - expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); - expect(data.imp[1]['video']['plcmt']).to.equal(multipleMediaRequests[1].params.video['plcmt']); - expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); - expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); - - expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); - expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); - }); - - // ================================================ - it('Request params - should handle banner and video format in single adunit', function() { - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); - expect(data.banner.pos).to.equal(1); - - // Case: when size is not present in adslo - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - expect(data.video.pos).to.equal(2); - }); - - it('Request params - should handle banner, video and native format in single adunit', function() { - let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); - expect(data.banner.pos).to.equal(0); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - should handle video and native format in single adunit', function() { - let request = spec.buildRequests(videoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot specifies a size as 300x250 - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(160); - expect(data.banner.format[0].h).to.equal(600); - - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(160); - expect(data.banner.h).to.equal(600); - expect(data.banner.format).to.not.exist; - - /* Adslot configured for banner and video. - banner size is set to [[728 90], ['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 728 and 90. - banner.format should have 300, 250 set in it - fluid is ignore - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(728); - expect(data.banner.h).to.equal(90); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(300); - expect(data.banner.format[0].h).to.equal(250); - - /* Adslot configured for banner and video. - banner size is set to [['fluid']] - adslot does not specify any size - => banner object should not be sent in the request. only video should be sent. - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - expect(data.video).to.exist; - }); - - it('Request params - video and native multiformat request - should have native object incase of invalid config present', function() { - videoAndNativeBidRequests[0].mediaTypes.native = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }; - videoAndNativeBidRequests[0].nativeParams = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - } - let request = spec.buildRequests(videoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.video).to.exist; - expect(data.native).to.exist; - }); - - it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - }); - - it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.video).to.exist; - expect(data.video.linearity).to.equal(1); - }); - - it('Request params check for video ad', function () { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0].tagid).to.equal('Div1'); - expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); - expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); - expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); - expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); - expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); - - expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); - expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); - - expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); - expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); - - expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); - expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); - - expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); - expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); - - expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); - expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); - expect(data.imp[0]['video']['plcmt']).to.equal(videoBidRequests[0].params.video['plcmt']); - expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); - expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); - - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - }); - } - - describe('GPP', function() { - it('Request params check with GPP Consent', function () { - let bidRequest = { - gppConsent: { - 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', - 'fullGppData': { - 'sectionId': 3, - 'gppVersion': 1, - 'sectionList': [ - 5, - 7 - ], - 'applicableSections': [ - 5 - ], - 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', - 'pingData': { - 'cmpStatus': 'loaded', - 'gppVersion': '1.0', - 'cmpDisplayStatus': 'visible', - 'supportedAPIs': [ - 'tcfca', - 'usnat', - 'usca', - 'usva', - 'usco', - 'usut', - 'usct' - ], - 'cmpId': 31 - }, - 'eventName': 'sectionChange' - }, - 'applicableSections': [ - 5 - ], - 'apiVersion': 1 - } - }; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); - expect(data.regs.gpp_sid[0]).to.equal(5); - }); - - it('Request params check without GPP Consent', function () { - let bidRequest = {}; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.regs).to.equal(undefined); - }); - - it('Request params check with GPP Consent read from ortb2', function () { - let bidRequest = { - ortb2: { - regs: { - 'gpp': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', - 'gpp_sid': [ - 5 - ] - } - } - }; - let request = spec.buildRequests(bidRequests, bidRequest); - let data = JSON.parse(request.data); - expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); - expect(data.regs.gpp_sid[0]).to.equal(5); - }); - }); - - describe('Fledge', function() { - it('should not send imp.ext.ae when FLEDGE is disabled, ', function () { - let bidRequest = Object.assign([], bidRequests); - bidRequest[0].ortb2Imp = { - ext: { ae: 1 } - }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: false} }); - let data = JSON.parse(req.data); - if (data.imp[0].ext) { - expect(data.imp[0].ext).to.not.have.property('ae'); - } - }); - - it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { - let bidRequest = Object.assign([], bidRequests); - delete bidRequest[0].params.test; - bidRequest[0].ortb2Imp = { - ext: { ae: 1 } - }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: true} }); - let data = JSON.parse(req.data); - expect(data.imp[0].ext.ae).to.equal(1); - }); - }); - - it('should send connectiontype parameter if browser contains navigator.connection property', function () { - const bidRequest = spec.buildRequests(bidRequests); - let data = JSON.parse(bidRequest.data); - if (window.navigator && window.navigator.connection) { - expect(data.device).to.include.any.keys('connectiontype'); - } - }); - - it('should send imp.pmp in request if pmp json is present in adUnit ortb2Imp object', function () { - let originalBidRequests = utils.deepClone(bidRequests); - originalBidRequests[0].ortb2Imp.pmp = { - 'private_auction': 0, - 'deals': [{ 'id': '5678' }] - } - const bidRequest = spec.buildRequests(originalBidRequests); - let data = JSON.parse(bidRequest.data); - expect(data.imp[0].pmp).to.exist.and.to.be.an('object'); - }) - - it('should not send imp.pmp in request if pmp json is not present in adUnit ortb2Imp object', function () { - let originalBidRequests = utils.deepClone(bidRequests); - const bidRequest = spec.buildRequests(originalBidRequests); - let data = JSON.parse(bidRequest.data); - expect(data.imp[0].pmp).to.deep.equal(undefined); - }) - }); - - it('Request params dctr check', function () { - let multipleBidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - dctr: 'key1=val1|key2=val2,!val3' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'GBP', - dctr: 'key1=val3|key2=val1,!val3|key3=val123' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - } - ]; - - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - - /* case 1 - - dctr is found in adunit[0] - */ - - expect(data.imp[0].ext).to.exist.and.to.be.an('object'); // dctr parameter - expect(data.imp[0].ext.key_val).to.exist.and.to.equal(multipleBidRequests[0].params.dctr); - - /* case 2 - - dctr not present in adunit[0] but present in adunit[1] - */ - delete multipleBidRequests[0].params.dctr; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - - expect(data.imp[0].ext).to.exist.and.to.deep.equal({}); - expect(data.imp[1].ext).to.exist.and.to.be.an('object'); // dctr parameter - expect(data.imp[1].ext.key_val).to.exist.and.to.equal(multipleBidRequests[1].params.dctr); - - /* case 3 - - dctr is present in adunit[0], but is not a string value - */ - multipleBidRequests[0].params.dctr = 123; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - - expect(data.imp[0].ext).to.exist.and.to.deep.equal({}); - }); - - it('Request params deals check', function () { - let multipleBidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - deals: ['deal-id-1', 'deal-id-2', 'dea'] // "dea" will not be passed as more than 3 characters needed - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'GBP', - deals: ['deal-id-100', 'deal-id-200'] - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - } - ]; - - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - // case 1 - deals are passed as expected, ['', ''] , in both adUnits - expect(data.imp[0].pmp).to.deep.equal({ - 'private_auction': 0, - 'deals': [ - { - 'id': 'deal-id-1' - }, - { - 'id': 'deal-id-2' - } - ] - }); - expect(data.imp[1].pmp).to.deep.equal({ - 'private_auction': 0, - 'deals': [ - { - 'id': 'deal-id-100' - }, - { - 'id': 'deal-id-200' - } - ] - }); - - // case 2 - deals not present in adunit[0] - delete multipleBidRequests[0].params.deals; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - expect(data.imp[0].pmp).to.not.exist; - - // case 3 - deals is present in adunit[0], but is not an array - multipleBidRequests[0].params.deals = 123; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - expect(data.imp[0].pmp).to.not.exist; - - // case 4 - deals is present in adunit[0] as an array but one of the value is not a string - multipleBidRequests[0].params.deals = [123, 'deal-id-1']; - request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - expect(data.imp[0].pmp).to.deep.equal({ - 'private_auction': 0, - 'deals': [ - { - 'id': 'deal-id-1' - } - ] - }); - }); - - describe('Request param acat checking', function() { - let multipleBidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - dctr: 'key1=val1|key2=val2,!val3' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'GBP', - dctr: 'key1=val3|key2=val1,!val3|key3=val123' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - } - ]; - - it('acat: pass only strings', function() { - multipleBidRequests[0].params.acat = [1, 2, 3, 'IAB1', 'IAB2']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.ext.acat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); - }); - - it('acat: trim the strings', function() { - multipleBidRequests[0].params.acat = [' IAB1 ', ' IAB2 ']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.ext.acat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); - }); - - it('acat: pass only unique strings', function() { - multipleBidRequests[0].params.acat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2']; - multipleBidRequests[1].params.acat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB3']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.ext.acat).to.exist.and.to.deep.equal(['IAB1', 'IAB2', 'IAB3']); - }); - it('ortb2.ext.prebid.bidderparams.pubmatic.acat should be passed in request payload', function() { - const ortb2 = { - ext: { - prebid: { - bidderparams: { - pubmatic: { - acat: ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2'] - } - } - } - } - }; - const request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id', - bidderCode: 'pubmatic', - ortb2 - }); - let data = JSON.parse(request.data); - expect(data.ext.acat).to.deep.equal(['IAB1', 'IAB2']); - }); + } + let validBidRequests = [firstBid]; + let bidderRequest = { + bids: [firstBid], + auctionId: 'ee3074fe-97ce-4681-9235-d7622aede74c', + auctionStart: 1725514077194, + bidderCode: 'pubmatic', + bidderRequestId: '1c56ad30b9b8ca8', + refererInfo: { + page: 'https://ebay.com', + ref: '' + }, + ortb2: { + device: { + w: 1200, + h: 1800, + sua: {}, + language: 'en', + js: 1, + connectiontype: 6 + }, + site: {domain: 'ebay.com', page: 'https://ebay.com'}, + source: {} + }, + timeout: 2000 + }; + let videoBidRequest, videoBidderRequest, utilsLogWarnMock, nativeBidderRequest; + + describe('Bid validations', () => { + it('should return true if publisherId is present in params', () => { + const isValid = spec.isBidRequestValid(validBidRequests[0]); + expect(isValid).to.equal(true); }); - describe('Request param bcat checking', function() { - let multipleBidRequests = [ - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - dctr: 'key1=val1|key2=val2,!val3' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: '301', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'GBP', - dctr: 'key1=val3|key2=val1,!val3|key3=val123' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]], - pos: 1 - } - } - } - ]; + it('should return false if publisherId is missing', () => { + const bid = utils.deepClone(validBidRequests[0]); + delete bid.params.publisherId; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); - it('bcat: pass only strings', function() { - multipleBidRequests[0].params.bcat = [1, 2, 3, 'IAB1', 'IAB2']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); - }); + it('should return false if publisherId is not of type string', () => { + const bid = utils.deepClone(validBidRequests[0]); + bid.params.publisherId = 5890; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); - it('bcat: pass strings with length greater than 3', function() { - multipleBidRequests[0].params.bcat = ['AB', 'CD', 'IAB1', 'IAB2']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + describe('VIDEO', () => { + beforeEach(() => { + videoBidRequest = utils.deepClone(validBidRequests[0]); + delete videoBidRequest.mediaTypes.banner; + videoBidRequest.mediaTypes.video = { + playerSize: [ + [640, 480] + ], + protocols: [1, 2, 5], + context: 'instream', + skippable: false, + skip: 1, + linearity: 2 + } }); - let data = JSON.parse(request.data); - expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); - }); - - it('bcat: trim the strings', function() { - multipleBidRequests[0].params.bcat = [' IAB1 ', ' IAB2 ']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' + it('should return false if mimes are missing in a video impression request', () => { + const isValid = spec.isBidRequestValid(videoBidRequest); + expect(isValid).to.equal(false); }); - let data = JSON.parse(request.data); - expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); - }); - it('bcat: pass only unique strings', function() { - // multi slot - multipleBidRequests[0].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2']; - multipleBidRequests[1].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB3']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2', 'IAB3']); - }); + it('should return false if context is missing in a video impression request', () => { + delete videoBidRequest.mediaTypes.context; + const isValid = spec.isBidRequestValid(videoBidRequest); + expect(isValid).to.equal(false); + }) - it('bcat: do not pass bcat if all entries are invalid', function() { - // multi slot - multipleBidRequests[0].params.bcat = ['', 'IAB', 'IAB']; - multipleBidRequests[1].params.bcat = [' ', 22, 99999, 'IA']; - let request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id' + it('should return true if banner/native present, but outstreamAU or renderer is missing', () => { + videoBidRequest.mediaTypes.video.mimes = ['video/flv']; + videoBidRequest.mediaTypes.video.context = 'outstream'; + videoBidRequest.mediaTypes.banner = { + sizes: [[728, 90], [160, 600]] + } + const isValid = spec.isBidRequestValid(videoBidRequest); + expect(isValid).to.equal(true); }); - let data = JSON.parse(request.data); - expect(data.bcat).to.deep.equal(undefined); - }); - it('ortb2.bcat should merged with slot level bcat param', function() { - multipleBidRequests[0].params.bcat = ['IAB-1', 'IAB-2']; - const ortb2 = { - bcat: ['IAB-3', 'IAB-4'] - }; - const request = spec.buildRequests(multipleBidRequests, { - auctionId: 'new-auction-id', - bidderCode: 'pubmatic', - ortb2 + it('should return false if outstreamAU or renderer is missing', () => { + const isValid = spec.isBidRequestValid(videoBidRequest); + expect(isValid).to.equal(false); }); - let data = JSON.parse(request.data); - expect(data.bcat).to.deep.equal(['IAB-1', 'IAB-2', 'IAB-3', 'IAB-4']); }); - }); + } + }); - describe('Response checking', function () { - it('should check for valid response values', function () { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - let response = spec.interpretResponse(bidResponses, request); - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); - expect(response[0].cpm).to.equal(parseFloat((bidResponses.body.seatbid[0].bid[0].price).toFixed(2))); - expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); - expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); - if (bidResponses.body.seatbid[0].bid[0].crid) { - expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); - } else { - expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); - } - expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); - expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(true); - expect(response[0].ttl).to.equal(360); - expect(response[0].meta.networkId).to.equal(123); - expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); - expect(response[0].meta.buyerId).to.equal('seat-id'); - expect(response[0].meta.dchain).to.equal('dchain'); - expect(response[0].meta.clickUrl).to.equal('blackrock.com'); - expect(response[0].meta.advertiserDomains[0]).to.equal('blackrock.com'); - expect(response[0].referrer).to.include(data.site.ref); - expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); - expect(response[0].pm_seat).to.equal(bidResponses.body.seatbid[0].seat); - expect(response[0].pm_dspid).to.equal(bidResponses.body.seatbid[0].bid[0].ext.dspid); - expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + describe('Request formation', () => { + describe('IMP', () => { + it('should generate request with banner', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('banner'); + expect(imp[0]).to.have.property('id').equal('3736271c3c4b27'); + }); - expect(response[1].requestId).to.equal(bidResponses.body.seatbid[1].bid[0].impid); - expect(response[1].cpm).to.equal(parseFloat((bidResponses.body.seatbid[1].bid[0].price).toFixed(2))); - expect(response[1].width).to.equal(bidResponses.body.seatbid[1].bid[0].w); - expect(response[1].height).to.equal(bidResponses.body.seatbid[1].bid[0].h); - if (bidResponses.body.seatbid[1].bid[0].crid) { - expect(response[1].creativeId).to.equal(bidResponses.body.seatbid[1].bid[0].crid); - } else { - expect(response[1].creativeId).to.equal(bidResponses.body.seatbid[1].bid[0].id); - } - expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); - expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(true); - expect(response[1].ttl).to.equal(360); - expect(response[1].meta.networkId).to.equal(422); - expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); - expect(response[1].meta.buyerId).to.equal(832); - expect(response[1].meta.clickUrl).to.equal('hivehome.com'); - expect(response[1].meta.advertiserDomains[0]).to.equal('hivehome.com'); - expect(response[1].referrer).to.include(data.site.ref); - expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); - expect(response[1].pm_seat).to.equal(bidResponses.body.seatbid[1].seat || null); - expect(response[1].pm_dspid).to.equal(bidResponses.body.seatbid[1].bid[0].ext.dspid); - expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + it('should add pmp if deals are present in parameters', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('pmp'); + expect(imp[0]).to.have.property('pmp').to.have.property('deals').with.lengthOf(2); }); - it('should check for dealChannel value selection', function () { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(bidResponses, request); - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].dealChannel).to.equal('PMPG'); - expect(response[1].dealChannel).to.equal('PREF'); + it('should not add pmp if deals are absent in parameters', () => { + delete validBidRequests[0].params.deals; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.not.have.property('pmp'); }); - it('should check for unexpected dealChannel value selection', function () { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let updateBiResponse = bidResponses; - updateBiResponse.body.seatbid[0].bid[0].ext.deal_channel = 11; + it('should add key_val property if dctr is present in parameters', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('ext'); + expect(imp[0]).to.have.property('ext').to.have.property('key_val'); + }); - let response = spec.interpretResponse(updateBiResponse, request); + it('should not add key_val if dctr is absent in parameters', () => { + delete validBidRequests[0].params.dctr; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('ext').to.not.have.property('key_val'); + }); + + it('should set w and h to the primary size for banner', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('banner'); + expect(imp[0]).to.have.property('banner').to.have.property('w').equal(300); + expect(imp[0]).to.have.property('banner').to.have.property('h').equal(250); + }); + + it('should have 1 size in the banner.format array', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('banner').to.have.property('format'); + expect(imp[0]).to.have.property('banner').to.have.property('format').with.lengthOf(2); + }); + + it('should add pmZoneId in ext if pmzoneid is present in parameters', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('ext'); + expect(imp[0]).to.have.property('ext').to.have.property('pmZoneId'); + }); + + it('should add bidfloor if kadfloor is present in parameters', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('bidfloor'); + expect(imp[0]).to.have.property('bidfloor').equal(1.2); + }); + + it('should add bidfloorcur if currency is present in parameters', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('bidfloorcur'); + expect(imp[0]).to.have.property('bidfloorcur').equal('AUD'); + }); + + it('should add bidfloorcur with default value if currency is missing in parameters', () => { + delete validBidRequests[0].params.currency; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('bidfloorcur'); + expect(imp[0]).to.have.property('bidfloorcur').equal('USD'); + }); + + it('should add tagid', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('tagid'); + expect(imp[0]).to.have.property('tagid').equal('/15671365/DMDemo'); + }); + + it('should add secure, displaymanager & displaymanagerver', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('secure').equal(1); + expect(imp[0]).to.have.property('displaymanager').equal('Prebid.js'); + expect(imp[0]).to.have.property('displaymanagerver'); + }); + + it('should include the properties topframe and format as an array', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('banner').to.have.property('topframe'); + expect(imp[0]).to.have.property('banner').to.have.property('format').to.be.an('array'); + }); + + it('should respect the publisher-provided pos for the banner impression', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('banner').to.have.property('pos'); + expect(imp[0]).to.have.property('banner').to.have.property('pos').equal(1); + }); + + it('should default pos to 0 if not explicitly provided by the publisher', () => { + delete bidderRequest.bids[0].mediaTypes.banner.pos; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('banner').to.have.property('pos'); + expect(imp[0]).to.have.property('banner').to.have.property('pos').equal(0); + }); + + if (FEATURES.VIDEO) { + describe('VIDEO', () => { + beforeEach(() => { + utilsLogWarnMock = sinon.stub(utils, 'logWarn'); + videoBidderRequest = utils.deepClone(bidderRequest); + delete videoBidderRequest.bids[0].mediaTypes.banner; + videoBidderRequest.bids[0].mediaTypes.video = { + skip: 1, + mimes: ['video/mp4', 'video/x-flv'], + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + linearity: 1, + placement: 2, + plcmt: 1, + minbitrate: 10, + maxbitrate: 10, + playerSize: [640, 480] + } + }); - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].dealChannel).to.equal(null); - }); + afterEach(() => { + utilsLogWarnMock.restore(); + }) - it('should have a valid native bid response', function() { - let request = spec.buildRequests(nativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data.imp[0].id = '2a5571261281d4'; - request.data = JSON.stringify(data); - let response = spec.interpretResponse(nativeBidResponse, request); - let assets = response[0].native.ortb.assets; - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].native).to.exist.and.to.be.an('object'); - expect(response[0].mediaType).to.exist.and.to.equal('native'); - expect(assets).to.be.an('array').with.length.above(0); - expect(assets[0].title).to.exist.and.to.be.an('object'); - expect(assets[1].img).to.exist.and.to.be.an('object'); - expect(assets[1].img.url).to.exist.and.to.be.an('string'); - expect(assets[1].img.h).to.exist; - expect(assets[1].img.w).to.exist; - expect(assets[2].data).to.exist.and.to.be.an('object'); - }); + it('should generate request with mediatype video', () => { + const request = spec.buildRequests(validBidRequests, videoBidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('video'); + }); - it('should check for valid banner mediaType in case of multiformat request', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(bannerBidResponse, request); + it('should log a warning if playerSize is missing', () => { + delete videoBidderRequest.bids[0].mediaTypes.video.playerSize; + const request = spec.buildRequests(validBidRequests, videoBidderRequest); + sinon.assert.called(utils.logWarn); + }); - expect(response[0].mediaType).to.equal('banner'); - }); + it('should log a warning if plcmt is missing', () => { + delete videoBidderRequest.bids[0].mediaTypes.video.plcmt; + const request = spec.buildRequests(validBidRequests, videoBidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + sinon.assert.called(utils.logWarn); + expect(imp.video).to.be.undefined; + }); - it('should check for valid native mediaType in case of multiformat request', function() { - let request = spec.buildRequests(nativeBidRequests, { - auctionId: 'new-auction-id' + it('should have all supporting parameters', () => { + const request = spec.buildRequests(validBidRequests, videoBidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('video'); + expect(imp[0]).to.have.property('video').to.have.property('mimes'); + expect(imp[0]).to.have.property('video').to.have.property('minbitrate'); + expect(imp[0]).to.have.property('video').to.have.property('maxbitrate'); + expect(imp[0]).to.have.property('video').to.have.property('minduration'); + expect(imp[0]).to.have.property('video').to.have.property('maxduration'); + expect(imp[0]).to.have.property('video').to.have.property('plcmt'); + expect(imp[0]).to.have.property('video').to.have.property('battr'); + expect(imp[0]).to.have.property('video').to.have.property('startdelay'); + expect(imp[0]).to.have.property('video').to.have.property('playbackmethod'); + expect(imp[0]).to.have.property('video').to.have.property('api'); + expect(imp[0]).to.have.property('video').to.have.property('protocols'); + expect(imp[0]).to.have.property('video').to.have.property('linearity'); + expect(imp[0]).to.have.property('video').to.have.property('placement'); + expect(imp[0]).to.have.property('video').to.have.property('skip'); + expect(imp[0]).to.have.property('video').to.have.property('w'); + expect(imp[0]).to.have.property('video').to.have.property('h'); + }); }); - let response = spec.interpretResponse(nativeBidResponse, request); + } + if (FEATURES.NATIVE) { + describe('NATIVE', () => { + beforeEach(() => { + utilsLogWarnMock = sinon.stub(utils, 'logWarn'); + nativeBidderRequest = utils.deepClone(bidderRequest); + delete nativeBidderRequest.bids[0].mediaTypes.banner; + nativeBidderRequest.bids[0].nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 0, + img: { + 'type': 3, + 'w': 300, + 'h': 250 + }, + required: 1, + }] + }; + nativeBidderRequest.bids[0].mediaTypes.native = { + title: { + required: true, + length: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + } + }); - expect(response[0].mediaType).to.equal('native'); - }); + afterEach(() => { + utilsLogWarnMock.restore(); + }) - it('should not assign renderer if bid is native', function() { - let request = spec.buildRequests(nativeBidRequests, { - auctionId: 'new-auction-id' + it('should generate request with mediatype native', () => { + const request = spec.buildRequests(validBidRequests, nativeBidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('native'); + }); }); - let response = spec.interpretResponse(nativeBidResponse, request); - expect(response[0].renderer).to.not.exist; - }); + } + // describe('MULTIFORMAT', () => { + // let multiFormatBidderRequest; + // it('should have both banner & video impressions', () => { + // multiFormatBidderRequest = utils.deepClone(bidderRequest); + // multiFormatBidderRequest.bids[0].mediaTypes.video = { + // skip: 1, + // mimes: ['video/mp4', 'video/x-flv'], + // minduration: 5, + // maxduration: 30, + // startdelay: 5, + // playbackmethod: [1, 3], + // api: [1, 2], + // protocols: [2, 3], + // battr: [13, 14], + // linearity: 1, + // placement: 2, + // plcmt: 1, + // minbitrate: 10, + // maxbitrate: 10, + // playerSize: [640, 480] + // } + // const request = spec.buildRequests(validBidRequests, multiFormatBidderRequest); + // const { imp } = request?.data; + // expect(imp).to.be.an('array'); + // expect(imp[0]).to.have.property('banner'); + // expect(imp[0].banner).to.have.property('topframe'); + // expect(imp[0].banner).to.have.property('format'); + // expect(imp[0]).to.have.property('video'); + // }); + + // it('should have both banner & native impressions', () => { + // multiFormatBidderRequest = utils.deepClone(bidderRequest); + // multiFormatBidderRequest.bids[0].nativeOrtbRequest = { + // ver: '1.2', + // assets: [{ + // id: 0, + // img: { + // 'type': 3, + // 'w': 300, + // 'h': 250 + // }, + // required: 1, + // }] + // }; + // const request = spec.buildRequests(validBidRequests, multiFormatBidderRequest); + // const { imp } = request?.data; + // expect(imp).to.be.an('array'); + // expect(imp[0]).to.have.property('banner'); + // expect(imp[0].banner).to.have.property('topframe'); + // expect(imp[0].banner).to.have.property('format'); + // expect(imp[0]).to.have.property('native'); + // }); + // }); + }); - it('should not assign renderer if bid is of banner', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' + describe('rest of ORTB request', () => { + describe('BCAT', () => { + it('should contain only string values', () => { + validBidRequests[0].params.bcat = [1, 2, 3, 'IAB1', 'IAB2']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('bcat'); + expect(request.data).to.have.property('bcat').to.deep.equal(['IAB1', 'IAB2']); }); - let response = spec.interpretResponse(bidResponses, request); - expect(response[0].renderer).to.not.exist; - }); - it('should set ibv field in bid.ext when bid.ext.ibv exists', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' + it('should contain string values with length greater than 3', function() { + validBidRequests[0].params.bcat = ['AB', 'CD', 'IAB1', 'IAB2']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('bcat'); + expect(request.data).to.have.property('bcat').to.deep.equal(['IAB1', 'IAB2']); }); - let copyOfBidResponse = utils.deepClone(bannerBidResponse); - let bidExt = utils.deepClone(copyOfBidResponse.body.seatbid[0].bid[0].ext); - copyOfBidResponse.body.seatbid[0].bid[0].ext = Object.assign(bidExt, { - ibv: true + it('should trim strings', function() { + validBidRequests[0].params.bcat = [' IAB1 ', ' IAB2 ']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('bcat'); + expect(request.data).to.have.property('bcat').to.deep.equal(['IAB1', 'IAB2']); }); - let response = spec.interpretResponse(copyOfBidResponse, request); - expect(response[0].ext.ibv).to.equal(true); - expect(response[0].meta.mediaType).to.equal('video'); - }); - - it('should not set ibv field when bid.ext does not exist ', function() { - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' + it('should pass unique strings', function() { + validBidRequests[0].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('bcat'); + expect(request.data).to.have.property('bcat').to.deep.equal(['IAB1', 'IAB2']); }); - let response = spec.interpretResponse(bannerBidResponse, request); - expect(response[0].ext).to.not.exist; - expect(response[0].meta).to.exist; - expect(response[0].meta.mediaType).to.equal('banner'); - }); - - if (FEATURES.VIDEO) { - it('should check for valid video mediaType in case of multiformat request', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); + it('should fail if validations are not met', function() { + validBidRequests[0].params.bcat = ['', 'IA', 'IB']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.not.have.property('bcat'); }); + }); - it('should assign renderer if bid is video and request is for outstream', function() { - let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.exist; + describe('ACAT', () => { + it('should contain only string values', () => { + validBidRequests[0].params.acat = [1, 2, 3, 'IAB1', 'IAB2']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('acat'); + expect(request.data).to.have.property('acat').to.deep.equal(['IAB1', 'IAB2']); }); - it('should not assign renderer if bidderRequest is not present', function() { - let request = spec.buildRequests(outstreamBidRequest, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.not.exist; + it('should trim strings', () => { + validBidRequests[0].params.acat = [' IAB1 ', ' IAB2 ']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('acat'); + expect(request.data).to.have.property('acat').to.deep.equal(['IAB1', 'IAB2']); }); - it('should not assign renderer if bid is video and request is for instream', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].renderer).to.not.exist; + it('should pass unique strings', () => { + validBidRequests[0].params.acat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('acat'); + expect(request.data).to.have.property('acat').to.deep.equal(['IAB1', 'IAB2']); }); - it('should assign mediaType by reading bid.ext.mediaType', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - 'video': { - 'mimes': ['video/mp4'], - 'skippable': true, - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - let newvideoBidResponses = { - 'body': { - 'id': '1621441141473', - 'cur': 'USD', - 'customdata': 'openrtb1', - 'ext': { - 'buyid': 'myBuyId' - }, - 'seatbid': [{ - 'bid': [{ - 'id': '2c95df014cfe97', - 'impid': '2c95df014cfe97', - 'price': 4.2, - 'cid': 'test1', - 'crid': 'test2', - 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", - 'w': 0, - 'h': 0, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', - 'ext': { - 'bidtype': 1 - } - }], - 'ext': { - 'buyid': 'myBuyId' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' - }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }) - - it('should assign mediaType even if bid.ext.mediaType does not exists', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - 'video': { - 'mimes': ['video/mp4'], - 'skippable': true, - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - let newvideoBidResponses = { - 'body': { - 'id': '1621441141473', - 'cur': 'USD', - 'customdata': 'openrtb1', - 'ext': { - 'buyid': 'myBuyId' - }, - 'seatbid': [{ - 'bid': [{ - 'id': '2c95df014cfe97', - 'impid': '2c95df014cfe97', - 'price': 4.2, - 'cid': 'test1', - 'crid': 'test2', - 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", - 'w': 0, - 'h': 0, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' - }], - 'ext': { - 'buyid': 'myBuyId' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' - }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') + it('should fail if validations are not met', () => { + validBidRequests[0].params.acat = ['', 'IA', 'IB']; + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('acat'); }); - } - }); - - describe('Fledge Auction config Response', function () { - let response; - let bidRequestConfigs = [ - { - bidder: 'pubmatic', - mediaTypes: { - banner: { - sizes: [[728, 90], [160, 600]] - } - }, - params: { - publisherId: '5670', - adSlot: '/15671365/DMDemo@300x250:0', - kadfloor: '1.2', - pmzoneid: 'aabc, ddef', - kadpageurl: 'www.publisher.com', - yob: '1986', - gender: 'M', - lat: '12.3', - lon: '23.7', - wiid: '1234567890', - profId: '100', - verId: '200', - currency: 'AUD', - dctr: 'key1:val1,val2|key2:val1' - }, - placementCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: 'test_bid_id', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - ortb2Imp: { - ext: { - tid: '92489f71-1bf2-49a0-adf9-000cea934729', - ae: 1 - } - }, - } - ]; - - let bidRequest = spec.buildRequests(bidRequestConfigs, {}); - let bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test_bid_id', - price: 2, - w: 728, - h: 250, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup' - }] - }], - cur: 'AUS', - ext: { - fledge_auction_configs: { - 'test_bid_id': { - seller: 'ads.pubmatic.com', - interestGroupBuyers: ['dsp1.com'], - sellerTimeout: 0, - perBuyerSignals: { - 'dsp1.com': { - bid_macros: 0.1, - disallowed_adv_ids: [ - '5678', - '5890' - ], - } - } - } - } - } - }; - - response = spec.interpretResponse({ body: bidResponse }, bidRequest); - it('should return FLEDGE auction_configs alongside bids', function () { - expect(response).to.have.property('bids'); - expect(response).to.have.property('paapi'); - expect(response.paapi.length).to.equal(1); - expect(response.paapi[0].bidId).to.equal('test_bid_id'); - }); - }); - - describe('Preapare metadata', function () { - it('Should copy all fields from ext to meta', function () { - const dsa = { - behalf: 'Advertiser', - paid: 'Advertiser', - transparency: [{ - domain: 'dsp1domain.com', - dsaparams: [1, 2] - }], - adrender: 1 - }; - - const bid = { - 'adomain': [ - 'mystartab.com' - ], - cat: ['IAB_CATEGORY'], - ext: { - advid: '12', - 'dspid': 6, - 'deal_channel': 1, - 'bidtype': 0, - advertiserId: 'adid', - dsa, - // networkName: 'nwnm', - // primaryCatId: 'pcid', - // advertiserName: 'adnm', - // agencyId: 'agid', - // agencyName: 'agnm', - // brandId: 'brid', - // brandName: 'brnm', - // dchain: 'dc', - // demandSource: 'ds', - // secondaryCatIds: ['secondaryCatIds'] - }, - }; - - const br = { - mediaType: 'video' - }; - prepareMetaObject(br, bid, null); - expect(br.meta.networkId).to.equal(6); // dspid - expect(br.meta.buyerId).to.equal('12'); // adid - expect(br.meta.advertiserId).to.equal('12'); - // expect(br.meta.networkName).to.equal('nwnm'); - expect(br.meta.primaryCatId).to.equal('IAB_CATEGORY'); - // expect(br.meta.advertiserName).to.equal('adnm'); - expect(br.meta.agencyId).to.equal('12'); - // expect(br.meta.agencyName).to.equal('agnm'); - expect(br.meta.brandId).to.equal('mystartab.com'); - // expect(br.meta.brandName).to.equal('brnm'); - // expect(br.meta.dchain).to.equal('dc'); - expect(br.meta.demandSource).to.equal(6); - expect(br.meta.secondaryCatIds).to.be.an('array').with.length.above(0); - expect(br.meta.secondaryCatIds[0]).to.equal('IAB_CATEGORY'); - expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain - expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain - expect(br.meta.dsa).to.equal(dsa); // dsa - expect(br.meta.mediaType).to.equal('video'); // mediaType - }); - - it('Should be empty, when ext and adomain is absent in bid object', function () { - const bid = {}; - const br = {}; - prepareMetaObject(br, bid, null); - expect(Object.keys(br.meta).length).to.equal(0); - }); - - it('Should be empty, when ext and adomain will not have properties', function () { - const bid = { - 'adomain': [], - ext: {} - }; - const br = {}; - prepareMetaObject(br, bid, null); - expect(Object.keys(br.meta).length).to.equal(0); - expect(br.meta.advertiserDomains).to.equal(undefined); // adomain - expect(br.meta.clickUrl).to.equal(undefined); // adomain - }); - - it('Should have buyerId,advertiserId, agencyId value of site ', function () { - const bid = { - 'adomain': [], - ext: { - advid: '12', - } - }; - const br = {}; - prepareMetaObject(br, bid, '5100'); - expect(br.meta.buyerId).to.equal('5100'); // adid - expect(br.meta.advertiserId).to.equal('5100'); - expect(br.meta.agencyId).to.equal('5100'); - }); - }); - - describe('getUserSyncs', function() { - const syncurl_iframe = 'https://ads.pubmatic.com/AdServer/js/user_sync.html?kdntuid=1&p=5670'; - const syncurl_image = 'https://image8.pubmatic.com/AdServer/ImgSync?p=5670'; - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - afterEach(function() { - sandbox.restore(); - }); - - it('execute as per config', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: syncurl_iframe - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ - type: 'image', url: syncurl_image - }]); }); - it('CCPA/USP', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&us_privacy=1NYN` - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&us_privacy=1NYN` - }]); - }); - - it('GDPR', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` - }]); - - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&gdpr=0&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=` - }]); - }); - - it('COPPA: true', function() { - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; + describe('TMAX, ID, AT, CUR, EXT', () => { + it('should have tmax', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('tmax').to.equal(2000); }); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&coppa=1` - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&coppa=1` - }]); - }); - it('COPPA: false', function() { - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': false - }; - return config[key]; + it('should remove test if pubmaticTest is not set', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('test').to.equal(undefined); }); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}` - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ - type: 'image', url: `${syncurl_image}` - }]); - }); - it('GDPR + COPPA:true + CCPA/USP', function() { - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; + it('should have id', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('id'); }); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` - }]); - expect(spec.getUserSyncs({ iframeEnabled: false }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` - }]); - }); - describe('GPP', function() { - it('should return userSync url without Gpp consent if gppConsent is undefined', () => { - const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, undefined); - expect(result).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}` - }]); + it('should set at to 1', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('at').to.equal(1); }); - it('should return userSync url without Gpp consent if gppConsent.gppString is undefined', () => { - const gppConsent = { applicableSections: ['5'] }; - const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); - expect(result).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}` - }]); + it('should have cur', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('cur').to.be.an('array').to.have.lengthOf(1); + expect(request.data).to.have.property('cur').to.include.members(['USD']); }); - it('should return userSync url without Gpp consent if gppConsent.applicableSections is undefined', () => { - const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN' }; - const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); - expect(result).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}` - }]); + it('should have ext', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('ext').to.have.property('epoch'); + expect(request.data).to.have.property('ext').to.have.property('wrapper'); + expect(request.data).to.have.property('ext').to.have.property('wrapper').to.have.property('profile'); + expect(request.data).to.have.property('ext').to.have.property('wrapper').to.have.property('wiid'); + expect(request.data).to.have.property('ext').to.have.property('wrapper').to.have.property('wv'); + expect(request.data).to.have.property('ext').to.have.property('wrapper').to.have.property('wp'); }); - it('should return userSync url without Gpp consent if gppConsent.applicableSections is an empty array', () => { - const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [] }; - const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); - expect(result).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}` - }]); + it('should have url with post method', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request).to.have.property('method').to.equal('POST'); + expect(request).to.have.property('url').to.equal('https://hbopenbid.pubmatic.com/translator?source=prebid-client'); }); + }); - it('should concatenate gppString and applicableSections values in the returned userSync iframe url', () => { - const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5] }; - const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); - expect(result).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gpp=${encodeURIComponent(gppConsent.gppString)}&gpp_sid=${encodeURIComponent(gppConsent.applicableSections)}` - }]); + describe('GROUPM', () => { + let bidderSettingStub; + beforeEach(() => { + bidderSettingStub = sinon.stub(bidderSettings, 'get'); }); - it('should concatenate gppString and applicableSections values in the returned userSync image url', () => { - const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5] }; - const result = spec.getUserSyncs({iframeEnabled: false}, undefined, undefined, undefined, gppConsent); - expect(result).to.deep.equal([{ - type: 'image', url: `${syncurl_image}&gpp=${encodeURIComponent(gppConsent.gppString)}&gpp_sid=${encodeURIComponent(gppConsent.applicableSections)}` - }]); + afterEach(() => { + bidderSettingStub.restore(); }); - }); - }); - describe('onBidWon', () => { - beforeEach(() => { - global.cpmAdjustment = {}; - }); + it('should skip setting the marketplace object in extension if allowAlternateBidderCodes is not defined', () => { + bidderSettingStub.returns(undefined); + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('ext').to.not.have.property('marketplace'); + }); - it('should do nothing if bid is undefined', () => { - spec.onBidWon(undefined); - expect(global.cpmAdjustment).to.deep.equal({}); - }); + it('should set the marketplace object in the extension when allowAlternateBidderCodes is set to "groupm"', () => { + bidderSettingStub.withArgs('pubmatic', 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs('pubmatic', 'allowedAlternateBidderCodes').returns(['groupm']); + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('ext').to.have.property('marketplace'); + expect(request.data).to.have.property('ext').to.have.property('marketplace').to.have.property('allowedbidders').to.be.an('array'); + expect(request.data.ext.marketplace.allowedbidders.length).to.equal(2); + expect(request.data.ext.marketplace.allowedbidders[0]).to.equal('pubmatic'); + expect(request.data.ext.marketplace.allowedbidders[1]).to.equal('groupm'); + }); - it('should do nothing if bid is null', () => { - spec.onBidWon(null); - expect(global.cpmAdjustment).to.deep.equal({}); + it('should be ALL when allowedAlternateBidderCodes is \'*\'', () => { + bidderSettingStub.withArgs('pubmatic', 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs('pubmatic', 'allowedAlternateBidderCodes').returns(['*']); + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data.ext.marketplace.allowedbidders).to.be.an('array'); + expect(request.data.ext.marketplace.allowedbidders[0]).to.equal('all'); + }); }); - it('should call _calculateBidCpmAdjustment and update cpmAdjustment correctly', () => { - const bid = { - cpm: 2.5, - originalCpm: 3, - originalCurrency: 'USD', - currency: 'USD', - mediaType: 'banner', - meta: { mediaType: 'banner' } - }; - spec.onBidWon(bid); - - expect(cpmAdjustment).to.deep.equal({ - currency: 'USD', - originalCurrency: 'USD', - adjustment: [ - { - cpmAdjustment: Number(((3 - 2.5) / 3).toFixed(2)), // Expected: 0.17 - mediaType: 'banner', - metaMediaType: 'banner', - cpm: 2.5, - originalCpm: 3 - } - ] + describe('SITE', () => { + it('should have site object', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('site'); }); - }); - }); - describe('getDeviceConnectionType', function() { - it('is a function', function(done) { - getDeviceConnectionType.should.be.a('function'); - done(); + it('should have site object with page, domain', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('site').to.have.property('page').to.equal('https://ebay.com'); + expect(request.data).to.have.property('site').to.have.property('domain').to.equal('ebay.com'); + }); }); - it('should return matched value if navigator.connection is present', function(done) { - const connectionValue = getDeviceConnectionType(); - if (window?.navigator?.connection) { - expect(connectionValue).to.be.a('number'); - } - done(); + describe('DEVICE', () => { + it('should have device object', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('device'); + expect(request.data.device).to.have.property('w').to.equal(1200); + expect(request.data.device).to.have.property('h').to.equal(1800); + expect(request.data.device).to.have.property('js').to.equal(1); + expect(request.data.device).to.have.property('connectiontype'); + expect(request.data.device).to.have.property('language').to.equal('en'); + }); }); - }); - if (FEATURES.VIDEO) { - describe('Checking for Video.plcmt property', function() { - let sandbox, utilsMock; - const adUnit = 'Div1'; - const msg_placement_missing = 'Video.plcmt param missing for Div1'; - let videoData = { - battr: [6, 7], - skipafter: 15, - maxduration: 50, - context: 'instream', - playerSize: [640, 480], - skip: 0, - connectiontype: [1, 2, 6], - skipmin: 10, - minduration: 10, - mimes: ['video/mp4', 'video/x-flv'], - } + describe('CONFIG/BADV', () => { + let copiedBidderRequest; beforeEach(() => { - utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); - sandbox.spy(utils, 'logWarn'); + copiedBidderRequest = utils.deepClone(bidderRequest); + copiedBidderRequest.ortb2.app = { + id: 'app-id', + name: 'app-name', + }; + copiedBidderRequest.ortb2.site.ext = { + id: 'site-id', + name: 'site-name', + } + copiedBidderRequest.ortb2.badv = ['example.com']; }); - afterEach(() => { - utilsMock.restore(); - sandbox.restore(); - }) - - it('should log Video.plcmt param missing', function() { - checkVideoPlacement(videoData, adUnit); - sinon.assert.calledWith(utils.logWarn, msg_placement_missing); - }) - it('shoud not log Video.plcmt param missing', function() { - videoData['plcmt'] = 1; - checkVideoPlacement(videoData, adUnit); - sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); - }) - }); - } - - describe('Banner Request param battr checking', function() { - it('should add battr params to bannerObj if present in ortb2Imp.banner', function() { - let originalBidRequests = utils.deepClone(bidRequests); - let bannerObj = utils.deepClone(originalBidRequests[0].ortb2Imp.banner); - originalBidRequests[0].ortb2Imp.banner = Object.assign(bannerObj, { - battr: [1, 2] + it('should have app if it is set in ortb2', () => { + const request = spec.buildRequests(validBidRequests, copiedBidderRequest); + expect(request.data).to.have.property('app'); }); - const req = spec.buildRequests(originalBidRequests, { - auctionId: 'new-auction-id' + it('should include app if it is defined in ortb2 but not site', () => { + const request = spec.buildRequests(validBidRequests, copiedBidderRequest); + expect(request.data).to.have.property('app'); + expect(request.data).to.not.have.property('site'); }); - let data = JSON.parse(req.data); - expect(data.imp[0]['banner']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['banner']['battr'][0]).to.equal(originalBidRequests[0].ortb2Imp.banner['battr'][0]); - expect(data.imp[0]['banner']['battr'][1]).to.equal(originalBidRequests[0].ortb2Imp.banner['battr'][1]); - }); - it('should not add battr params to bannerObj if not present in ortb2Imp.banner', function() { - const req = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' + it('should have badv if it is set in ortb2', () => { + const request = spec.buildRequests(validBidRequests, copiedBidderRequest); + expect(request.data).to.have.property('badv'); + expect(request.data.badv).to.deep.equal(['example.com']); }); - let data = JSON.parse(req.data); - expect(data.imp[0]['banner']['battr']).to.equal(undefined); }); - it('should not add battr params if _checkParamDataType returns undefined (Mismatch data type)', function() { - let originalBidRequests = utils.deepClone(bidRequests); - let bannerObj = utils.deepClone(originalBidRequests[0].ortb2Imp.banner); - originalBidRequests[0].ortb2Imp.banner = Object.assign(bannerObj, { - battr: 1 + describe('AUCTION ID', () => { + it('should use auctionId as wiid when it is not provided in params', () => { + const copiedValidBidRequests = utils.deepClone(validBidRequests); + delete copiedValidBidRequests[0].params.wiid; + const request = spec.buildRequests(copiedValidBidRequests, bidderRequest); + expect(request.data).to.have.property('ext'); + expect(request.data.ext).to.have.property('wrapper'); + expect(request.data.ext.wrapper).to.have.property('wiid'); + expect(request.data.ext.wrapper.wiid).to.equal('ee3074fe-97ce-4681-9235-d7622aede74c'); }); - const req = spec.buildRequests(originalBidRequests, { - auctionId: 'new-auction-id' + it('should use auctionId as wiid from params', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('ext'); + expect(request.data.ext).to.have.property('wrapper'); + expect(request.data.ext.wrapper).to.have.property('wiid'); + expect(request.data.ext.wrapper.wiid).to.equal('1234567890'); }); - let data = JSON.parse(req.data); - expect(data.imp[0]['banner']['battr']).to.equal(undefined); }); - }); - describe('setIBVField', function() { - it('should set ibv field in newBid.ext when bid.ext.ibv exists', function() { - const bid = { - ext: { - ibv: true + describe('GDPR', () => { + let copiedBidderRequest; + beforeEach(() => { + copiedBidderRequest = utils.deepClone(bidderRequest); + copiedBidderRequest.ortb2.user = { + ext: { + consent: 'kjfdniwjnifwenrif3' + } } - }; - const newBid = {}; - setIBVField(bid, newBid); - expect(newBid.ext).to.exist; - expect(newBid.ext.ibv).to.equal(true); - expect(newBid.meta).to.exist; - expect(newBid.meta.mediaType).to.equal('video'); - }); + }); - it('should not set ibv field when bid.ext.ibv does not exist', function() { - const bid = { - ext: {} - }; - const newBid = {}; - setIBVField(bid, newBid); - expect(newBid.ext).to.not.exist; - expect(newBid.meta).to.not.exist; + it('should have GDPR string', () => { + const request = spec.buildRequests(validBidRequests, copiedBidderRequest); + expect(request.data).to.have.property('user'); + expect(request.data.user).to.have.property('ext'); + expect(request.data.user.ext).to.have.property('consent').to.equal('kjfdniwjnifwenrif3'); + }); }); - it('should not set ibv field when bid.ext does not exist', function() { - const bid = {}; - const newBid = {}; - setIBVField(bid, newBid); - expect(newBid.ext).to.not.exist; - expect(newBid.meta).to.not.exist; + describe('GPP', () => { + it('should have gpp & gpp_sid in request if set using ortb2 and not present in request', () => { + let copiedBidderRequest = utils.deepClone(bidderRequest); + copiedBidderRequest.ortb2.regs = { + gpp: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + gpp_sid: [5] + } + const request = spec.buildRequests(validBidRequests, copiedBidderRequest); + expect(request.data).to.have.property('regs'); + expect(request.data.regs).to.have.property('gpp').to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(request.data.regs).to.have.property('gpp_sid').that.eql([5]); + }); }); - it('should preserve existing newBid.ext properties', function() { - const bid = { - ext: { - ibv: true - } - }; - const newBid = { - ext: { - existingProp: 'should remain' - } + describe('DSA', () => { + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'platform1domain.com', + dsaparams: [1] + }, + { + domain: 'SSP2domain.com', + dsaparams: [1, 2] + } + ] }; - setIBVField(bid, newBid); - expect(newBid.ext.existingProp).to.equal('should remain'); - expect(newBid.ext.ibv).to.equal(true); - expect(newBid.meta).to.exist; - expect(newBid.meta.mediaType).to.equal('video'); - }); - }); - }); + beforeEach(() => { + bidderRequest.ortb2.regs = {ext: { dsa }}; + }); - if (FEATURES.VIDEO) { - describe('Video request params', function() { - let sandbox, utilsMock, newVideoRequest; - beforeEach(() => { - utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); - sandbox.spy(utils, 'logWarn'); - newVideoRequest = utils.deepClone(videoBidRequests) + it('should have DSA in regs.ext', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('regs'); + expect(request.data.regs).to.have.property('ext'); + expect(request.data.regs.ext).to.have.property('dsa').to.deep.equal(dsa); + }); }); - afterEach(() => { - utilsMock.restore(); - sandbox.restore(); - }) - - it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { - delete newVideoRequest[0].mediaTypes.video; - delete newVideoRequest[0].params.video; - - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' + describe('ORTB2IMP', () => { + it('should send gpid if specified', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('imp'); + expect(request.data.imp[0]).to.have.property('ext'); + expect(request.data.imp[0].ext).to.have.property('gpid'); + expect(request.data.imp[0].ext.gpid).to.equal('/1111/homepage-leftnav'); }); - sinon.assert.calledOnce(utils.logWarn); - expect(request).to.equal(undefined); - }); + it('should send pbadslot if specified', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('imp'); + expect(request.data.imp[0]).to.have.property('ext'); + expect(request.data.imp[0].ext).to.have.property('data'); + expect(request.data.imp[0].ext.data).to.have.property('pbadslot'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('/1111/homepage-leftnav'); + }); - it('Should consider video params from mediaType object of bid', function () { - delete newVideoRequest[0].params.video; + it('should send adserver if specified', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('imp'); + expect(request.data.imp[0]).to.have.property('ext'); + expect(request.data.imp[0].ext).to.have.property('data'); + expect(request.data.imp[0].ext.data).to.have.property('adserver'); + expect(request.data.imp[0].ext.data.adserver).to.have.property('name'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('gam'); + expect(request.data.imp[0].ext.data.adserver).to.have.property('adslot'); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('/1111/homepage-leftnav'); + }); - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' + it('should send custom data if specified', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('imp'); + expect(request.data.imp[0]).to.have.property('ext'); + expect(request.data.imp[0].ext).to.have.property('data'); + expect(request.data.imp[0].ext.data).to.have.property('customData'); + expect(request.data.imp[0].ext.data.customData).to.have.property('id').to.equal('id-1'); }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - expect(data.imp[0]['video']['battr']).to.equal(undefined); }); - describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { - let videoSeatBid, request, newBid; - // let data = JSON.parse(request.data); - beforeEach(function () { - videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; - // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; - request = spec.buildRequests(bidRequests, validOutstreamBidRequest); - newBid = { - requestId: '47acc48ad47af5' - }; - videoSeatBid.ext = videoSeatBid.ext || {}; - videoSeatBid.ext.video = videoSeatBid.ext.video || {}; - // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; + describe('FLEDGE', () => { + it('should not send imp.ext.ae when FLEDGE is disabled, ', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.have.property('imp'); + expect(request.data.imp).to.be.an('array'); + expect(request.data.imp[0]).to.have.property('ext'); + expect(request.data.imp[0].ext).to.not.have.property('ae'); }); + }) - it('should not assign video object if deal priority is missing', function () { - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; + describe('cpm adjustment', () => { + beforeEach(() => { + global.cpmAdjustment = {}; }); - it('should not assign video object if context is not a adpod', function () { - videoSeatBid.ext.prebiddealpriority = 5; - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; + it('should not perform any action if the bid is undefined', () => { + spec.onBidWon(undefined); + expect(global.cpmAdjustment).to.deep.equal({}); }); - describe('when video deal tier object is present', function () { - beforeEach(function () { - videoSeatBid.ext.prebiddealpriority = 5; - request.bidderRequest.bids[0].mediaTypes.video = { - ...request.bidderRequest.bids[0].mediaTypes.video, - context: 'adpod', - maxduration: 50 - }; - }); - - it('should set video deal tier object, when maxduration is present in ext', function () { - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(50); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); - }); + it('should not perform any action if the bid is null', () => { + spec.onBidWon(null); + expect(global.cpmAdjustment).to.deep.equal({}); + }); + it('should invoke _calculateBidCpmAdjustment and correctly update cpmAdjustment', () => { + const bid = { + cpm: 2.5, + originalCpm: 3, + originalCurrency: 'USD', + currency: 'USD', + mediaType: 'banner', + meta: { mediaType: 'banner' } + }; - it('should set video deal tier object, when duration is present in ext', function () { - videoSeatBid.ext.video.duration = 20; - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(20); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + spec.onBidWon(bid); + + expect(cpmAdjustment).to.deep.equal({ + currency: 'USD', + originalCurrency: 'USD', + adjustment: [ + { + cpmAdjustment: Number(((3 - 2.5) / 3).toFixed(2)), // Expected: 0.17 + mediaType: 'banner', + metaMediaType: 'banner', + cpm: 2.5, + originalCpm: 3 + } + ] }); }); }); - }); - } - describe('Marketplace params', function () { - let sandbox, utilsMock, newBidRequests, newBidResponses; - beforeEach(() => { - utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); - sandbox.spy(utils, 'logInfo'); - newBidRequests = utils.deepClone(bidRequests) - newBidRequests[0].bidder = 'groupm'; - newBidResponses = utils.deepClone(bidResponses); - newBidResponses.body.seatbid[0].bid[0].ext.marketplace = 'groupm' - }); - - afterEach(() => { - utilsMock.restore(); - sandbox.restore(); - }) - - it('Should add bidder code as groupm for marketplace groupm response ', function () { - let request = spec.buildRequests(newBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(newBidResponses, request); - expect(response).to.be.an('array').with.length.above(0); - expect(response[0].bidderCode).to.equal('groupm'); + // describe('USER ID/ EIDS', () => { + // let copiedBidderRequest; + // beforeEach(() => { + // copiedBidderRequest = utils.deepClone(bidderRequest); + // copiedBidderRequest.bids[0].userId = { + // id5id : { + // uid: 'id5id-xyz-user-id' + // } + // } + // copiedBidderRequest.bids[0].userIdAsEids = [{ + // source: 'id5-sync.com', + // uids: [{ + // 'id': "ID5*G3_osFE_-UHoUjSuA4T8-f51U-JTNOoGcb2aMpx1APnDy8pDwkKCzXCcoSb1HXIIw9AjWBOWmZ3QbMUDTXKq8MPPW8h0II9mBYkP4F_IXkvD-XG64NuFFDPKvez1YGGx", + // 'atype': 1, + // 'ext': { + // 'linkType': 2, + // 'pba': 'q6Vzr0jEebxzmvS8aSrVQJFoJnOxs9gKBKCOLw1y6ew=' + // } + // }] + // }] + // }); + + // it('should send gpid if specified', () => { + // const request = spec.buildRequests(validBidRequests, copiedBidderRequest); + // expect(request.data).to.have.property('user'); + // expect(request.data.user).to.have.property('eids'); + // }); + // }); }); }); - describe('setTTL', function() { - it('should set ttl field in newBid.ttl when bid.exp exists', function() { - const bid = { - exp: 200 - }; - const newBid = {}; - setTTL(bid, newBid); - expect(newBid.ttl).to.equal(200); + describe('Response', () => { + it('should return response in prebid format', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const bidResponse = spec.interpretResponse(response, request); + expect(bidResponse).to.be.an('array'); + expect(bidResponse[0]).to.be.an('object'); + expect(bidResponse[0]).to.have.property('ad'); + expect(bidResponse[0]).to.have.property('dealId'); + expect(bidResponse[0]).to.have.property('dealChannel'); + expect(bidResponse[0]).to.have.property('currency'); + expect(bidResponse[0]).to.have.property('meta'); + expect(bidResponse[0]).to.have.property('mediaType'); + expect(bidResponse[0]).to.have.property('referrer'); + expect(bidResponse[0]).to.have.property('cpm'); + expect(bidResponse[0]).to.have.property('pm_seat'); + expect(bidResponse[0]).to.have.property('pm_dspid'); + expect(bidResponse[0]).to.have.property('sspID'); + expect(bidResponse[0]).to.have.property('partnerImpId'); + expect(bidResponse[0]).to.have.property('requestId'); + expect(bidResponse[0]).to.have.property('width'); + expect(bidResponse[0]).to.have.property('height'); + expect(bidResponse[0]).to.have.property('ttl'); + expect(bidResponse[0]).to.have.property('netRevenue'); + expect(bidResponse[0]).to.have.property('creativeId'); }); - it('should set ttl as 360 mediatype banner', function() { - const bid = {}; - const newBid = { - mediaType: 'banner' - }; - setTTL(bid, newBid); - expect(newBid.ttl).to.equal(360); + it('should return response and match with input values', () => { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const bidResponse = spec.interpretResponse(response, request); + expect(bidResponse).to.be.an('array'); + expect(bidResponse[0]).to.be.an('object'); + expect(bidResponse[0]).to.have.property('currency').to.be.equal('USD'); + expect(bidResponse[0]).to.have.property('dealId').to.equal('PUBDEAL1'); + expect(bidResponse[0]).to.have.property('dealChannel').to.equal('PMPG'); + expect(bidResponse[0]).to.have.property('meta').to.be.an('object'); + expect(bidResponse[0]).to.have.property('mediaType').to.equal('banner'); + expect(bidResponse[0]).to.have.property('cpm').to.equal(1.3); + expect(bidResponse[0]).to.have.property('pm_seat').to.equal('seat-id'); + expect(bidResponse[0]).to.have.property('pm_dspid').to.equal(123); + expect(bidResponse[0]).to.have.property('sspID').to.equal('74858439-49D7-4169-BA5D-44A046315B2F'); + expect(bidResponse[0]).to.have.property('requestId').to.equal('3736271c3c4b27'); + expect(bidResponse[0]).to.have.property('width').to.equal(300); + expect(bidResponse[0]).to.have.property('height').to.equal(250); + expect(bidResponse[0]).to.have.property('ttl').to.equal(360); }); - it('should set ttl as 1800 mediatype video', function() { - const bid = {}; - const newBid = { - mediaType: 'video' - }; - setTTL(bid, newBid); - expect(newBid.ttl).to.equal(1800); - }); - - it('should set ttl as 1800 mediatype native', function() { - const bid = {}; - const newBid = { - mediaType: 'native' - }; - setTTL(bid, newBid); - expect(newBid.ttl).to.equal(1800); + describe('DEALID', () => { + it('should set deal_channel to PMP if ext.deal_channel is missing', () => { + const copiedResponse = utils.deepClone(response); + delete copiedResponse.body.seatbid[0].bid[0].ext.deal_channel; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const bidResponse = spec.interpretResponse(copiedResponse, request); + expect(bidResponse).to.be.an('array'); + expect(bidResponse[0]).to.be.an('object'); + expect(bidResponse[0]).to.have.property('dealChannel').to.equal('PMP'); + }); + + it('should exclude deal_id and deal_channel from the response if the deal id is missing', () => { + const copiedResponse = utils.deepClone(response); + delete copiedResponse.body.seatbid[0].bid[0].ext.deal_channel; + delete copiedResponse.body.seatbid[0].bid[0].dealid; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const bidResponse = spec.interpretResponse(copiedResponse, request); + expect(bidResponse).to.be.an('array'); + expect(bidResponse[0]).to.be.an('object'); + expect(bidResponse[0]).to.not.have.property('dealId'); + expect(bidResponse[0]).to.not.have.property('dealChannel'); + }); }); + if (FEATURES.VIDEO) { + describe('VIDEO', () => { + beforeEach(() => { + let videoBidderRequest = utils.deepClone(bidderRequest); + delete videoBidderRequest.bids[0].mediaTypes.banner; + videoBidderRequest.bids[0].mediaTypes.video = { + skip: 1, + mimes: ['video/mp4', 'video/x-flv'], + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + linearity: 1, + placement: 2, + plcmt: 1, + context: 'outstream', + minbitrate: 10, + maxbitrate: 10, + playerSize: [640, 480] + } + }); - it('should set ttl as 360 as default if all condition fails', function() { - const bid = {}; - const newBid = {}; - setTTL(bid, newBid); - expect(newBid.ttl).to.equal(360); - }); - }); -}); + it('should generate video response', () => { + const request = spec.buildRequests(validBidRequests, videoBidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('video'); + const bidResponse = spec.interpretResponse(videoResponse, request); + expect(bidResponse).to.be.an('array'); + expect(bidResponse[0]).to.be.an('object'); + expect(bidResponse[0]).to.have.property('vastXml'); + expect(bidResponse[0]).to.have.property('mediaType'); + expect(bidResponse[0]).to.have.property('playerHeight'); + expect(bidResponse[0]).to.have.property('playerWidth'); + }); + + it('should generate video response with input values', () => { + const request = spec.buildRequests(validBidRequests, videoBidderRequest); + const { imp } = request?.data; + expect(imp).to.be.an('array'); + expect(imp[0]).to.have.property('video'); + const bidResponse = spec.interpretResponse(videoResponse, request); + expect(bidResponse).to.be.an('array'); + expect(bidResponse[0]).to.be.an('object'); + expect(bidResponse[0]).to.have.property('mediaType').to.equal('video'); + expect(bidResponse[0]).to.have.property('playerHeight').to.equal(480); + expect(bidResponse[0]).to.have.property('playerWidth').to.equal(640); + }); + }); + } + }) +}) From f89550a03586a7517e445bb9635a85ce1de37a05 Mon Sep 17 00:00:00 2001 From: Pavlo Kavulych <72217414+Chucky-choo@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:58:41 +0300 Subject: [PATCH 1090/1097] Adipolo Bid Adapter : initial release (#12883) * add adipolo Adapter * without aliases * retrigger checks * retrigger checks * retrigger checks * retrigger checks * fix test * retrigger checks --------- Co-authored-by: Chucky-choo --- modules/adipoloBidAdapter.js | 37 ++ modules/adipoloBidAdapter.md | 54 +++ test/spec/modules/adipoloBidAdapter_spec.js | 439 ++++++++++++++++++++ 3 files changed, 530 insertions(+) create mode 100644 modules/adipoloBidAdapter.js create mode 100644 modules/adipoloBidAdapter.md create mode 100644 test/spec/modules/adipoloBidAdapter_spec.js diff --git a/modules/adipoloBidAdapter.js b/modules/adipoloBidAdapter.js new file mode 100644 index 00000000000..f6638c25eb8 --- /dev/null +++ b/modules/adipoloBidAdapter.js @@ -0,0 +1,37 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {buildRequests, getUserSyncs, interpretResponse} from '../libraries/xeUtils/bidderUtils.js'; +import {deepAccess, getBidIdParameter, isArray, logError} from '../src/utils.js'; + +const BIDDER_CODE = 'adipolo'; +const ENDPOINT = 'https://prebid.adipolo.live'; + +function isBidRequestValid(bid) { + if (bid && typeof bid.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } + + if (!getBidIdParameter('pid', bid.params)) { + logError('Pid is not present in bidder params'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video') && !isArray(deepAccess(bid, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; + } + + return true; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/adipoloBidAdapter.md b/modules/adipoloBidAdapter.md new file mode 100644 index 00000000000..bebb770f0e7 --- /dev/null +++ b/modules/adipoloBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: Adipolo Bidder Adapter +Module Type: Adipolo Bidder Adapter +Maintainer: support@adipolo.com +``` + +# Description + +Module that connects to adipolo.com demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'adipolo', + params: { + env: 'adipolo', + pid: '40', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } + }, + bids: [{ + bidder: 'adipolo', + params: { + env: 'adipolo', + pid: '40', + ext: {} + } + }] + } +]; +``` diff --git a/test/spec/modules/adipoloBidAdapter_spec.js b/test/spec/modules/adipoloBidAdapter_spec.js new file mode 100644 index 00000000000..6764d7d20d8 --- /dev/null +++ b/test/spec/modules/adipoloBidAdapter_spec.js @@ -0,0 +1,439 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/adipoloBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; + +const ENDPOINT = 'https://prebid.adipolo.live'; + +const defaultRequest = { + tmax: 0, + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'adipolo', + params: { + pid: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; + +const videoBidderRequest = { + bidderCode: 'adipolo', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'adipolo', + bids: [{bidId: 'qwerty'}] +}; + +describe('adipoloBidAdapter', () => { + describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required pid param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.pid; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('tmax').and.to.equal(defaultRequest.tmax); + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprConsent').and.to.deep.equal({}); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + pid: '40' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); + }); + + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['adipolo'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['adipolo']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = {getFloor: 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = {getFloor: () => 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({floor: 'string', currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}); From ed74a03afc4ec019659af481b3de3e84f07b46e9 Mon Sep 17 00:00:00 2001 From: haruki yamaguchi <100411113+hrkhito@users.noreply.github.com> Date: Wed, 16 Apr 2025 02:00:31 +0900 Subject: [PATCH 1091/1097] SSP_Genie Bid Adapter : ID5 Compatible Adapter (#12974) * modify adUnit infomation * fix imuid module * feat(GenieeBidAdapter): Add support for GPID and pbadslot - Add support for GPID (Global Placement ID) from ortb2Imp.ext.gpid - Add fallback support for ortb2Imp.ext.data.pbadslot - Include gpid parameter in request when GPID exists - Add test cases to verify GPID, pbadslot, and priority behavior * Aladdin Bidder ID5 Compatible Adapter * add comment * modified test message * the import of buildExtuidQuery was missing * test: add test cases for id5id in extuid query * delete duplicate test --------- Co-authored-by: Murano Takamasa Co-authored-by: daikichiteranishi <49385718+daikichiteranishi@users.noreply.github.com> Co-authored-by: teranishi daikichi Co-authored-by: gn-daikichi <49385718+gn-daikichi@users.noreply.github.com> Co-authored-by: takumi-furukawa Co-authored-by: furukawaTakumi <45890154+furukawaTakumi@users.noreply.github.com> Co-authored-by: furukawaTakumi Co-authored-by: haruki-yamaguchi --- modules/ssp_genieeBidAdapter.js | 27 ++--- .../spec/modules/ssp_genieeBidAdapter_spec.js | 99 ++++++++++++++++++- 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/modules/ssp_genieeBidAdapter.js b/modules/ssp_genieeBidAdapter.js index 4b2c49c34f0..49afcaec033 100644 --- a/modules/ssp_genieeBidAdapter.js +++ b/modules/ssp_genieeBidAdapter.js @@ -124,6 +124,17 @@ function hasParamsNotBlankString(params, key) { ); } +export const buildExtuidQuery = ({id5, imuId}) => { + const params = [ + ...(id5 ? [`id5:${id5}`] : []), + ...(imuId ? [`im:${imuId}`] : []), + ]; + + const queryString = params.join('\t'); + if (!queryString) return null; + return queryString; +} + /** * making request data be used commonly banner and native * @see https://docs.prebid.org/dev-docs/bidder-adaptor.html#location-and-referrers @@ -215,9 +226,11 @@ function makeCommonRequestData(bid, geparameter, refererInfo) { } } - // imuid - const imuidQuery = getImuidAsQueryParameter(bid); - if (imuidQuery) data.extuid = imuidQuery; + // imuid, id5 + const id5 = utils.deepAccess(bid, 'userId.id5id.uid'); + const imuId = utils.deepAccess(bid, 'userId.imuid'); + const extuidQuery = buildExtuidQuery({id5, imuId}); + if (extuidQuery) data.extuid = extuidQuery; // makeUAQuery // To avoid double encoding, not using encodeURIComponent here @@ -311,14 +324,6 @@ function makeBidResponseAd(innerHTML) { return '' + innerHTML + ''; } -/** - * return imuid strings as query parameters - */ -function getImuidAsQueryParameter(bid) { - const imuid = utils.deepAccess(bid, 'userId.imuid'); - return imuid ? 'im:' + imuid : ''; // To avoid double encoding, not using encodeURIComponent here -} - function getUserAgent() { return storage.getDataFromLocalStorage('key') || null; } diff --git a/test/spec/modules/ssp_genieeBidAdapter_spec.js b/test/spec/modules/ssp_genieeBidAdapter_spec.js index 5dd3688561f..76f4775344d 100644 --- a/test/spec/modules/ssp_genieeBidAdapter_spec.js +++ b/test/spec/modules/ssp_genieeBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec, BANNER_ENDPOINT, + buildExtuidQuery, } from 'modules/ssp_genieeBidAdapter.js'; import { config } from 'src/config.js'; @@ -347,15 +348,103 @@ describe('ssp_genieeBidAdapter', function () { expect(request[0].data.apid).to.deep.equal(bundle); }); - it('should not include the extuid query when bid.userId.imuid does not exist', function () { + it('should include only imuid in extuid query when only imuid exists', function () { + const imuid = 'b.a4ad1d3eeb51e600'; + const request = spec.buildRequests([{...BANNER_BID, userId: {imuid}}]); + expect(request[0].data.extuid).to.deep.equal(`im:${imuid}`); + }); + + it('should include only id5id in extuid query when only id5id exists', function () { + const id5id = 'id5id'; + const request = spec.buildRequests([{...BANNER_BID, userId: {id5id: {uid: id5id}}}]); + expect(request[0].data.extuid).to.deep.equal(`id5:${id5id}`); + }); + + it('should include id5id and imuid in extuid query when id5id and imuid exists', function () { + const imuid = 'b.a4ad1d3eeb51e600'; + const id5id = 'id5id'; + const request = spec.buildRequests([{...BANNER_BID, userId: {id5id: {uid: id5id}, imuid: imuid}}]); + expect(request[0].data.extuid).to.deep.equal(`id5:${id5id}\tim:${imuid}`); + }); + + it('should not include the extuid query when both id5 and imuid are missing', function () { const request = spec.buildRequests([BANNER_BID]); expect(request[0].data).to.not.have.property('extuid'); }); - it('should include an extuid query when bid.userId.imuid exists', function () { - const imuid = 'b.a4ad1d3eeb51e600'; - const request = spec.buildRequests([{...BANNER_BID, userId: {imuid}}]); - expect(request[0].data.extuid).to.deep.equal(`im:${imuid}`); + describe('buildExtuidQuery', function() { + it('should return tab-separated string when both id5 and imuId exist', function() { + const result = buildExtuidQuery({ id5: 'test_id5', imuId: 'test_imu' }); + expect(result).to.equal('id5:test_id5\tim:test_imu'); + }); + + it('should return only id5 when imuId is missing', function() { + const result = buildExtuidQuery({ id5: 'test_id5', imuId: null }); + expect(result).to.equal('id5:test_id5'); + }); + + it('should return only imuId when id5 is missing', function() { + const result = buildExtuidQuery({ id5: null, imuId: 'test_imu' }); + expect(result).to.equal('im:test_imu'); + }); + + it('should return null when both id5 and imuId are missing', function() { + const result = buildExtuidQuery({ id5: null, imuId: null }); + expect(result).to.be.null; + }); + }); + + it('should include gpid when ortb2Imp.ext.gpid exists', function () { + const gpid = '/123/abc'; + const bidWithGpid = { + ...BANNER_BID, + ortb2Imp: { + ext: { + gpid: gpid + } + } + }; + const request = spec.buildRequests([bidWithGpid]); + expect(String(request[0].data.gpid)).to.have.string(gpid); + }); + + it('should include gpid when ortb2Imp.ext.data.pbadslot exists', function () { + const pbadslot = '/123/abc'; + const bidWithPbadslot = { + ...BANNER_BID, + ortb2Imp: { + ext: { + data: { + pbadslot: pbadslot + } + } + } + }; + const request = spec.buildRequests([bidWithPbadslot]); + expect(String(request[0].data.gpid)).to.have.string(pbadslot); + }); + + it('should prioritize ortb2Imp.ext.gpid over ortb2Imp.ext.data.pbadslot', function () { + const gpid = '/123/abc'; + const pbadslot = '/456/def'; + const bidWithBoth = { + ...BANNER_BID, + ortb2Imp: { + ext: { + gpid: gpid, + data: { + pbadslot: pbadslot + } + } + } + }; + const request = spec.buildRequests([bidWithBoth]); + expect(String(request[0].data.gpid)).to.have.string(gpid); + }); + + it('should not include gpid when neither ortb2Imp.ext.gpid nor ortb2Imp.ext.data.pbadslot exists', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('gpid'); }); it('should include gpid when ortb2Imp.ext.gpid exists', function () { From 8ae234e55bf658a6fca6491a259fe4fe806b63fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bendeg=C3=BAz=20=C3=81cs?= <30595431+acsbendi@users.noreply.github.com> Date: Wed, 16 Apr 2025 16:36:48 +0200 Subject: [PATCH 1092/1097] Kobler bid adapter: pass cid in bid response. (#12999) --- modules/koblerBidAdapter.js | 1 + test/spec/modules/koblerBidAdapter_spec.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 95ddb7f15d6..6331ed9bbbb 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -74,6 +74,7 @@ export const interpretResponse = function (serverResponse, request) { ttl: TIME_TO_LIVE_IN_SECONDS, ad: b.adm, nurl: b.nurl, + cid: b.cid, meta: { advertiserDomains: b.adomain } diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 49ea4fc092a..a06d0f2e969 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -590,6 +590,7 @@ describe('KoblerAdapter', function () { price: 7.981, 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}', crid: 'edea9b03-3a57-41aa-9c00-abd673e22006', + cid: '572', dealid: '', w: 320, h: 250, @@ -604,6 +605,7 @@ describe('KoblerAdapter', function () { 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}', crid: 'fa2d5af7-2678-4204-9023-44c526160742', dealid: '2783483223432342', + cid: '800', w: 580, h: 400, adm: '', @@ -632,6 +634,7 @@ describe('KoblerAdapter', function () { ttl: 600, 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}', + cid: '572', meta: { advertiserDomains: [ 'https://kobler.no' @@ -650,6 +653,7 @@ describe('KoblerAdapter', function () { ttl: 600, 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}', + cid: '800', meta: { advertiserDomains: [ 'https://bid.kobler.no' From b34f19b5fbefa4e112bd76e706053d4b9db3ff8c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 16 Apr 2025 15:05:08 +0000 Subject: [PATCH 1093/1097] Prebid 9.40.0 release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 190b5ee25ca..c0cbb0bdd1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.40.0-pre", + "version": "9.40.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.40.0-pre", + "version": "9.40.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", diff --git a/package.json b/package.json index fa57e545b01..a35665bd355 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.40.0-pre", + "version": "9.40.0", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { From 290ec3a1b6ac1316e97d88c4ac38cf1a5c6d27b0 Mon Sep 17 00:00:00 2001 From: Konrad Piegza Date: Wed, 23 Apr 2025 11:42:59 +0200 Subject: [PATCH 1094/1097] Update modules.json: rename consentManagement and add new modules --- modules.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules.json b/modules.json index 02663d55020..7c4b2721303 100644 --- a/modules.json +++ b/modules.json @@ -1,5 +1,5 @@ [ - "consentManagement", + "consentManagementTcf", "rubiconBidAdapter", "appnexusBidAdapter", "openxBidAdapter", @@ -9,7 +9,6 @@ "criteoBidAdapter", "rtbhouseBidAdapter", "categoryTranslation", - "dfpAdServerVideo", "smartadserverBidAdapter", "prebidServerBidAdapter", "ringieraxelspringerAnalyticsAdapter", @@ -23,12 +22,14 @@ "teadsBidAdapter", "invibesBidAdapter", "carodaBidAdapter", - "fledgeForGpt", "adqueryIdSystem", "topicsFpdModule", "priceFloors", "visxBidAdapter", "currency", "justIdSystem", - "dasBidAdapter" + "dasBidAdapter", + "paapi", + "paapiForGpt", + "dfpAdServerVideo" ] From 958e88725f6fb6b5323b7f373adc52b10d852117 Mon Sep 17 00:00:00 2001 From: Konrad Piegza Date: Wed, 23 Apr 2025 12:45:34 +0200 Subject: [PATCH 1095/1097] Update modules.json: add criteoIdSystem --- modules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules.json b/modules.json index 7c4b2721303..24cc1394859 100644 --- a/modules.json +++ b/modules.json @@ -31,5 +31,6 @@ "dasBidAdapter", "paapi", "paapiForGpt", - "dfpAdServerVideo" + "dfpAdServerVideo", + "criteoIdSystem" ] From 3fd2e3eefabc32e08f0920a13f02446c7aed14bc Mon Sep 17 00:00:00 2001 From: Konrad Piegza Date: Wed, 23 Apr 2025 12:52:42 +0200 Subject: [PATCH 1096/1097] Update modules.json: add connectadBidAdapter --- modules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules.json b/modules.json index 24cc1394859..1db3e5d4321 100644 --- a/modules.json +++ b/modules.json @@ -32,5 +32,6 @@ "paapi", "paapiForGpt", "dfpAdServerVideo", - "criteoIdSystem" + "criteoIdSystem", + "connectadBidAdapter" ] From 514212b88c35370fbbac47b7f2b4899bff821b56 Mon Sep 17 00:00:00 2001 From: Konrad Piegza Date: Wed, 23 Apr 2025 15:36:56 +0200 Subject: [PATCH 1097/1097] Update ci.yml: fix test command syntax and ensure newline at end of file --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bfde353bb7..4e3a8e19f29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - run: npm install && npm install -g gulp - name: Run tests - run: node_modules/.bin/gulp test --file test/spec/modules/ringieraxelspringerBidAdapter_spec.js,test/spec/modules/dasBidAdapter_spec.js + run: node_modules/.bin/gulp test --file test/spec/modules/ringieraxelspringerBidAdapter_spec.js --file test/spec/modules/dasBidAdapter_spec.js - name: build run: node_modules/.bin/gulp build --modules=modules.json @@ -65,4 +65,4 @@ jobs: release="${version}_$(date +"%Y%m%d%H%M%S")" echo "Uploading Prebid.js ver:${version} to OCDN" echo -e "\e[32mRelease: ${release}\e[0m" - aws s3 sync "${version}" s3://${BUCKET}/prebid/prod/${release}/ --region ocdn --endpoint "https://ocdn.eu" \ No newline at end of file + aws s3 sync "${version}" s3://${BUCKET}/prebid/prod/${release}/ --region ocdn --endpoint "https://ocdn.eu"